PluginProbe
Datafeedr API / 1.4.3
Datafeedr API v1.4.3
1.4.3 1.4.2 1.0.125 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 1.0.22 1.0.23 1.0.24 1.0.25 1.0.26 1.0.27 1.0.28 1.0.29 1.0.3 1.0.30 1.0.31 1.0.32 All 182 releases
datafeedr-api / functions / global.php

global.php in Datafeedr API 1.4.3, at functions/global.php

3,504 lines 102.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 defined( 'ABSPATH' ) || exit;
4
5 /**
6 * Returns true if the user's Datafeedr API keys exist or false if they do not.
7 *
8 * @return bool
9 */
10 function dfrapi_datafeedr_api_keys_exist(): bool {
11 return dfrapi_get_datafeedr_access_id() && dfrapi_get_datafeedr_secret_key();
12 }
13
14 /**
15 * Returns the user's Datafeedr API Access ID or false if it does not exist.
16 *
17 * @return false|string
18 */
19 function dfrapi_get_datafeedr_access_id() {
20
21 $configuration = (array) get_option( 'dfrapi_configuration', [] );
22
23 if ( ! isset( $configuration['access_id'] ) ) {
24 return false;
25 }
26
27 $access_id = trim( $configuration['access_id'] );
28
29 return ! empty( $access_id ) ? $access_id : false;
30 }
31
32 /**
33 * Returns the user's Datafeedr API Secret Key or false if it does not exist.
34 *
35 * @return false|string
36 */
37 function dfrapi_get_datafeedr_secret_key() {
38
39 $configuration = (array) get_option( 'dfrapi_configuration', [] );
40
41 if ( ! isset( $configuration['secret_key'] ) ) {
42 return false;
43 }
44
45 $secret_key = trim( $configuration['secret_key'] );
46
47 return ! empty( $secret_key ) ? $secret_key : false;
48 }
49
50 /**
51 * Returns the user's Datafeedr API Version or the default.
52 *
53 * @return string
54 */
55 function dfrapi_get_datafeedr_api_version(): string {
56
57 $configuration = (array) get_option( 'dfrapi_configuration', [] );
58
59 if ( ! isset( $configuration['api_version'] ) ) {
60 return dfrapi_get_default_api_version();
61 }
62
63 $api_version = trim( $configuration['api_version'] );
64
65 return in_array( $api_version, dfrapi_get_valid_api_versions(), true )
66 ? $api_version
67 : dfrapi_get_default_api_version();
68 }
69
70 /**
71 * Returns the valid API versions.
72 *
73 * @return array
74 */
75 function dfrapi_get_valid_api_versions(): array {
76 return [ 'stable' ];
77 }
78
79 /**
80 * Returns the default API version.
81 *
82 * @return string
83 */
84 function dfrapi_get_default_api_version(): string {
85 return 'stable';
86 }
87
88 /**
89 * Returns the user's API requests usage as a percentage of their total requests allowed.
90 *
91 * @return float|int
92 */
93 function dfrapi_get_api_usage_percentage() {
94 _deprecated_function( __FUNCTION__, '1.3.0', 'dfrapi_get_api_usage_as_percentage()' );
95
96 return dfrapi_get_api_usage_as_percentage();
97 }
98
99 /**
100 * Returns true if the user has used more than 90% of their API requests. Otherwise returns false.
101 *
102 * @return bool
103 */
104 function dfrapi_api_usage_over_90_percent(): bool {
105 return dfrapi_get_api_usage_as_percentage() >= 90;
106 }
107
108 /**
109 * Helper method for setting emails as HTML.
110 *
111 * @return string
112 */
113 function dfrapi_set_html_content_type(): string {
114 return 'text/html';
115 }
116
117 /**
118 * Get Zanox Keys.
119 *
120 * @return array|bool Array of keys or false if they do not exist.
121 */
122 function dfrapi_get_zanox_keys() {
123
124 $configuration = (array) get_option( 'dfrapi_configuration' );
125
126 $zanox_connection_key = false;
127 $zanox_secret_key = false;
128
129 if ( isset( $configuration['zanox_connection_key'] ) && ( $configuration['zanox_connection_key'] != '' ) ) {
130 $zanox_connection_key = $configuration['zanox_connection_key'];
131 }
132
133 if ( isset( $configuration['zanox_secret_key'] ) && ( $configuration['zanox_secret_key'] != '' ) ) {
134 $zanox_secret_key = $configuration['zanox_secret_key'];
135 }
136
137 if ( $zanox_connection_key && $zanox_secret_key ) {
138 return array(
139 'connection_key' => $zanox_connection_key,
140 'secret_key' => $zanox_secret_key,
141 );
142 }
143
144 return false;
145 }
146
147 /**
148 * Get Partnerize Keys.
149 *
150 * @since 1.0.66
151 *
152 * @return array|bool Array of keys or false if they do not exist.
153 */
154 function dfrapi_get_ph_keys() {
155
156 $configuration = (array) get_option( 'dfrapi_configuration' );
157
158 $ph_application_key = false;
159 $ph_user_api_key = false;
160 $ph_publisher_id = false;
161
162 if ( isset( $configuration['ph_application_key'] ) && ( $configuration['ph_application_key'] != '' ) ) {
163 $ph_application_key = $configuration['ph_application_key'];
164 }
165
166 if ( isset( $configuration['ph_user_api_key'] ) && ( $configuration['ph_user_api_key'] != '' ) ) {
167 $ph_user_api_key = $configuration['ph_user_api_key'];
168 }
169
170 if ( isset( $configuration['ph_publisher_id'] ) && ( $configuration['ph_publisher_id'] != '' ) ) {
171 $ph_publisher_id = $configuration['ph_publisher_id'];
172 }
173
174 if ( $ph_application_key && $ph_user_api_key && $ph_publisher_id ) {
175 return array(
176 'application_key' => $ph_application_key,
177 'user_api_key' => $ph_user_api_key,
178 'publisher_id' => $ph_publisher_id,
179 );
180 }
181
182 return false;
183 }
184
185 /**
186 * Get Effiliation Keys.
187 *
188 * @since 1.0.81
189 *
190 * @return array|bool Array of keys or false if they do not exist.
191 */
192 function dfrapi_get_effiliation_keys() {
193
194 $configuration = (array) get_option( 'dfrapi_configuration' );
195
196 $effiliation_key = false;
197
198 if ( isset( $configuration['effiliation_key'] ) && ( $configuration['effiliation_key'] != '' ) ) {
199 $effiliation_key = $configuration['effiliation_key'];
200 }
201
202 if ( $effiliation_key ) {
203 return array(
204 'effiliation_key' => $effiliation_key,
205 );
206 }
207
208 return false;
209 }
210
211 /**
212 * Returns Amazon API key credentials if they exist.
213 *
214 * If the Amazon API keys such as the Access Key ID, Secret Access Key, Tracking ID and Locale all
215 * exists, then this function returns them in array format. Otherwise it returns false.
216 *
217 * @since 1.0.33
218 *
219 * @return array|bool Returns array of values if all values exist, otherwise false.
220 */
221 function dfrapi_get_amazon_keys() {
222
223 $configuration = (array) get_option( 'dfrapi_configuration' );
224
225 $amazon_access_key_id = false;
226 $amazon_secret_access_key = false;
227 $amazon_tracking_id = false;
228 $amazon_locale = false;
229
230 if ( isset( $configuration['amazon_access_key_id'] ) && ( $configuration['amazon_access_key_id'] != '' ) ) {
231 $amazon_access_key_id = $configuration['amazon_access_key_id'];
232 }
233
234 if ( isset( $configuration['amazon_secret_access_key'] ) && ( $configuration['amazon_secret_access_key'] != '' ) ) {
235 $amazon_secret_access_key = $configuration['amazon_secret_access_key'];
236 }
237
238 if ( isset( $configuration['amazon_tracking_id'] ) && ( $configuration['amazon_tracking_id'] != '' ) ) {
239 $amazon_tracking_id = $configuration['amazon_tracking_id'];
240 }
241
242 if ( isset( $configuration['amazon_locale'] ) && ( $configuration['amazon_locale'] != '' ) ) {
243 $amazon_locale = $configuration['amazon_locale'];
244 }
245
246 if ( $amazon_access_key_id && $amazon_secret_access_key && $amazon_tracking_id && $amazon_locale ) {
247 return array(
248 'amazon_access_key_id' => $amazon_access_key_id,
249 'amazon_secret_access_key' => $amazon_secret_access_key,
250 'amazon_tracking_id' => $amazon_tracking_id,
251 'amazon_locale' => $amazon_locale,
252 );
253 }
254
255 return false;
256 }
257
258 /**
259 * Returns a link to a user page on v4.datafeedr.com.
260 */
261 function dfrapi_user_pages( $page ) {
262
263 $pages = array(
264 'edit' => 'https://datafeedr.me/dashboard',
265 'invoices' => 'https://datafeedr.me/dashboard',
266 'billing' => 'https://datafeedr.me/dashboard',
267 'cancel' => 'https://datafeedr.me/dashboard',
268 'change' => 'https://datafeedr.me/dashboard',
269 'signup' => 'https://datafeedr.me/dashboard',
270 'summary' => 'https://datafeedr.me/dashboard',
271 'api' => 'https://datafeedr.me/dashboard',
272 'factory' => 'https://datafeedr.me/dashboard',
273 );
274
275 return $pages[ $page ];
276 }
277
278 /**
279 * Adds option name to transient whitelist. This is so we know
280 * all transient options that can be deleted when deleting the
281 * API cache on Tools page.
282 */
283 function dfrapi_update_transient_whitelist( $option_name ) {
284 $whitelist = (array) get_option( 'dfrapi_transient_whitelist', [] );
285 $whitelist[] = $option_name;
286 update_option( 'dfrapi_transient_whitelist', array_unique( $whitelist ) );
287 }
288
289 /**
290 * Sanitizes a single-line setting value such as an API key, affiliate ID or tracking ID.
291 *
292 * Unlike sanitize_text_field(), this does not remove percent-encoded octets (ie. "%2F"),
293 * which can legitimately appear in keys and tracking IDs.
294 *
295 * @since 1.4.3
296 *
297 * @param mixed $value
298 *
299 * @return string
300 */
301 function dfrapi_sanitize_setting_text( $value ): string {
302 if ( ! is_scalar( $value ) ) {
303 return '';
304 }
305
306 $value = wp_strip_all_tags( (string) $value );
307 $value = preg_replace( '/[\x00-\x1F\x7F]/', '', $value );
308
309 return trim( $value );
310 }
311
312 /**
313 * Sanitizes the "dfrapi_networks" option. Only the expected structure is kept:
314 *
315 * [ 'ids' => [ (int) network_id => [ 'nid' => '123', 'aid' => '...', 'tid' => '...' ] ] ]
316 *
317 * Networks without a "nid" (ie. unchecked networks) are removed.
318 *
319 * @since 1.4.3
320 *
321 * @param mixed $input
322 *
323 * @return array
324 */
325 function dfrapi_sanitize_networks_option( $input ): array {
326
327 $clean = [ 'ids' => [] ];
328
329 if ( ! is_array( $input ) || empty( $input['ids'] ) || ! is_array( $input['ids'] ) ) {
330 return $clean;
331 }
332
333 foreach ( $input['ids'] as $network_id => $network ) {
334
335 $network_id = absint( $network_id );
336
337 if ( $network_id === 0 || ! is_array( $network ) || empty( $network['nid'] ) || ! is_scalar( $network['nid'] ) ) {
338 continue;
339 }
340
341 $entry = [ 'nid' => (string) absint( $network['nid'] ) ];
342
343 foreach ( [ 'aid', 'tid' ] as $field ) {
344 if ( isset( $network[ $field ] ) ) {
345 $entry[ $field ] = dfrapi_sanitize_setting_text( $network[ $field ] );
346 }
347 }
348
349 $clean['ids'][ $network_id ] = $entry;
350 }
351
352 return $clean;
353 }
354
355 /**
356 * Sanitizes the "dfrapi_merchants" option. Accepts an array of IDs or a comma-separated
357 * string of IDs. IDs are stored as numeric strings to match the existing stored format.
358 *
359 * @since 1.4.3
360 *
361 * @param mixed $input
362 *
363 * @return array
364 */
365 function dfrapi_sanitize_merchants_option( $input ): array {
366
367 $ids = $input['ids'] ?? [];
368
369 if ( is_string( $ids ) ) {
370 $ids = explode( ',', $ids );
371 }
372
373 if ( ! is_array( $ids ) ) {
374 return [ 'ids' => [] ];
375 }
376
377 $ids = array_filter( array_map( static function ( $id ) {
378 $id = is_scalar( $id ) ? trim( (string) $id ) : '';
379
380 return ctype_digit( $id ) ? absint( $id ) : 0;
381 }, $ids ) );
382
383 return [ 'ids' => array_values( array_map( 'strval', array_unique( $ids ) ) ) ];
384 }
385
386 /**
387 * Add affiliate ID and tracking ID to an affiliate link.
388 *
389 * @param array $product An array of a single Datafeedr $product.
390 *
391 * @return string A URL with affiliate ID inserted or an empty string if the affiliate ID is missing.
392 */
393 function dfrapi_url( $product ) {
394
395 // Get all the user's selected networks.
396 $networks = (array) get_option( 'dfrapi_networks' );
397
398 // Support added for Amazon in version 1.0.60 (2017-10-18) Ticket #15201
399 if ( substr( $product['source'], 0, 6 ) === "Amazon" ) {
400 // Get the user's Amazon Associate Tag
401 $affiliate_id = dfrapi_get_amazon_associate_tag();
402 } else {
403 // Extract the affiliate ID from the $networks array.
404 $affiliate_id = isset( $networks['ids'][ $product['source_id'] ]['aid'] ) ? $networks['ids'][ $product['source_id'] ]['aid'] : '';
405 }
406
407 $affiliate_id = apply_filters( 'dfrapi_affiliate_id', $affiliate_id, $product, $networks );
408 $affiliate_id = trim( $affiliate_id );
409
410 // Extract the Tracking ID from the $networks array.
411 $tracking_id = ( isset( $networks['ids'][ $product['source_id'] ]['tid'] ) ) ? $networks['ids'][ $product['source_id'] ]['tid'] : '';
412 $tracking_id = apply_filters( 'dfrapi_tracking_id', $tracking_id, $product, $networks );
413 $tracking_id = trim( $tracking_id );
414
415 // Affiliate ID is missing. Do action and return empty string.
416 if ( $affiliate_id == '' ) {
417 do_action( 'dfrapi_affiliate_id_is_missing', $product );
418
419 return '';
420 }
421
422 // Determine which URL field to get: 'url' OR 'ref_url'. Return 'url' if $tracking_id is empty, otherwise, use 'ref_url'.
423 $url = ( $tracking_id !== '' && isset( $product['ref_url'] ) ) ? $product['ref_url'] : $product['url'];
424
425 // Apply filters to URL before affiliate & tracking ID insertion.
426 $url = apply_filters( 'dfrapi_before_affiliate_id_insertion', $url, $product, $affiliate_id );
427 $url = apply_filters( 'dfrapi_before_tracking_id_insertion', $url, $product, $tracking_id );
428
429 // Replace placeholders in URL.
430 $placeholders = array( "@@@", "###" );
431 $replacements = array( $affiliate_id, $tracking_id );
432 $url = str_replace( $placeholders, $replacements, $url );
433
434 // Apply filters to URL after affiliate & tracking ID insertion.
435 $url = apply_filters( 'dfrapi_after_affiliate_id_insertion', $url, $product, $affiliate_id );
436 $url = apply_filters( 'dfrapi_after_tracking_id_insertion', $url, $product, $tracking_id );
437
438 // Return URL
439 return $url;
440 }
441
442 /**
443 * Return Amazon Associate Tag (ie. Tracking ID).
444 *
445 * @since 1.0.60
446 *
447 * @return string Associate Tag or empty string if it does not exist.
448 */
449 function dfrapi_get_amazon_associate_tag() {
450 $config = get_option( 'dfrapi_configuration' );
451
452 return ( isset( $config['amazon_tracking_id'] ) ) ? $config['amazon_tracking_id'] : '';
453 }
454
455 /**
456 * Add affiliate ID to impression URL.
457 *
458 * Since 1.0.39
459 *
460 * @param $product - An array of a single's product's information.
461 */
462 function dfrapi_impression_url( $product ) {
463
464 $impression_url = ( isset( $product['impressionurl'] ) ) ? trim( $product['impressionurl'] ) : false;
465
466 if ( ! $impression_url ) {
467 return '';
468 }
469
470 // Get all the user's selected networks.
471 $networks = (array) get_option( 'dfrapi_networks' );
472
473 // Extract the affiliate ID from the $networks array.
474 $affiliate_id = $networks['ids'][ $product['source_id'] ]['aid'];
475 $affiliate_id = apply_filters( 'dfrapi_affiliate_id', $affiliate_id, $product, $networks );
476 $affiliate_id = trim( $affiliate_id );
477
478 // Affiliate ID is missing. Do action and return empty string.
479 if ( $affiliate_id == '' ) {
480 do_action( 'dfrapi_affiliate_id_is_missing_impression', $product );
481
482 return '';
483 }
484
485 // Apply filters to URL before affiliate & tracking ID insertion.
486 $impression_url = apply_filters( 'dfrapi_before_affiliate_id_insertion_impression', $impression_url, $product, $affiliate_id );
487
488 // Replace placeholders in URL.
489 $placeholders = array( "@@@" );
490 $replacements = array( $affiliate_id );
491 $impression_url = str_replace( $placeholders, $replacements, $impression_url );
492
493 // Apply filters to URL after affiliate & tracking ID insertion.
494 $impression_url = apply_filters( 'dfrapi_after_affiliate_id_insertion_impression', $impression_url, $product, $affiliate_id );
495
496 // Return URL
497 return $impression_url;
498 }
499
500 /**
501 * Output an error message generated by the API.
502 */
503 function dfrapi_output_api_error( $data ) {
504 $error = @$data['dfrapi_api_error'];
505 $params = @$data['dfrapi_api_error']['params'];
506 ?>
507 <div class="dfrapi_api_error">
508 <div class="dfrapi_head"><?php _e( 'Datafeedr API Error', 'datafeedr-api' ); ?></div>
509 <div class="dfrapi_msg">
510 <strong><?php _e( 'Message:', 'datafeedr-api' ); ?></strong> <?php echo esc_html( $error['msg'] ); ?>
511 </div>
512 <div class="dfrapi_code"><strong><?php _e( 'Code:', 'datafeedr-api' ); ?></strong> <?php echo esc_html( $error['code'] ); ?>
513 </div>
514 <div class="dfrapi_class">
515 <strong><?php _e( 'Class:', 'datafeedr-api' ); ?></strong> <?php echo esc_html( $error['class'] ); ?></div>
516 <?php if ( is_array( $params ) ) : ?>
517 <div class="dfrps_query"><strong><?php _e( 'Query:', 'datafeedr-api' ); ?></strong>
518 <span><?php echo dfrapi_display_api_request( $params ); ?></span></div>
519 <?php endif; ?>
520 </div>
521 <?php
522 }
523
524 /**
525 * Convert a currency code to sign. USD => $
526 *
527 * @https://github.com/pelle/bux/blob/master/src/bux/currencies.clj
528 *
529 * Currently supported currencies:
530 *
531 * AUD Australia &#36;
532 * BRL Brazil R$
533 * CAD Canada &#36;
534 * CHF Switzerland Fr
535 * DKK Denmark kr
536 * EUR Belgium &euro;
537 * EUR Finland &euro;
538 * EUR France &euro;
539 * EUR Germany &euro;
540 * EUR Ireland &euro;
541 * EUR Italy &euro;
542 * EUR Netherlands &euro;
543 * EUR Spain &euro;
544 * GBP United Kingdom &pound;
545 * HUF Hungary Fr
546 * INR India &#8377;
547 * MYR Malaysia RM
548 * NOK Norway kr
549 * NZD New Zealand &#36;
550 * PHP Philippines &#8369;
551 * PLN Poland zł
552 * RON Romania L
553 * RUB Russia ₽
554 * SEK Sweden kr
555 * TRY Turkey &#8356;
556 * USD United States &#36;
557 *
558 * @param string $code 3-character ISO 4217 currency code.
559 *
560 * @return mixed|string
561 */
562 function dfrapi_currency_code_to_sign( $code ) {
563 return dfrapi_currency( $code )->get_currency_symbol();
564 }
565
566 /**
567 * This displays the API request in PHP format.
568 */
569 function dfrapi_display_api_request( $params = array() ) {
570
571 $html = '';
572
573 if ( empty( $params ) ) {
574 return $html;
575 }
576
577 $html .= '$search = $api->searchRequest();<br />';
578 foreach ( $params as $k => $v ) {
579
580 // Handle query.
581 if ( $k === 'query' ) {
582 foreach ( $v as $query ) {
583 if ( substr( $query, 0, 9 ) !== 'source_id' || substr( $query, 0, 11 ) !== 'merchant_id' ) {
584 $query = str_replace( ",", ", ", $query );
585 }
586 $html .= '$search->addFilter( \'' . esc_html( $query ) . '\' );<br />';
587 }
588 }
589
590 // Handle sort.
591 if ( $k === 'sort' ) {
592 foreach ( $v as $sort ) {
593 $html .= '$search->addSort( \'' . esc_html( stripslashes( $sort ) ) . '\' );<br />';
594 }
595 }
596
597 // Handle limit.
598 if ( $k === 'limit' ) {
599 $html .= '$search->setLimit( \'' . esc_html( stripslashes( $v ) ) . '\' );<br />';
600 }
601
602 // Handle merchant_limit.
603 if ( $k === 'merchant_limit' ) {
604 $html .= '$search->setMerchantLimit( \'' . stripslashes( absint( $v ) ) . '\' );<br />';
605 }
606
607 // Handle Offset.
608 if ( $k === 'offset' ) {
609 $html .= '$search->setOffset( \'' . esc_html( stripslashes( $v ) ) . '\' );<br />';
610 }
611
612 // Handle Exclude duplicates.
613 if ( $k === 'exclude_duplicates' ) {
614 $html .= '$search->excludeDuplicates( \'' . esc_html( $v ) . '\' );<br />';
615 }
616 }
617
618 $html .= '$products = $search->execute();';
619
620 return $html;
621
622 }
623
624 function dfrapi_get_query_param( $query, $param ) {
625 if ( is_array( $query ) && ! empty( $query ) ) {
626 foreach ( $query as $k => $v ) {
627 if ( $v['field'] == $param ) {
628 return array(
629 'field' => @$v['field'],
630 'operator' => @$v['operator'],
631 'value' => @$v['value'],
632 );
633 }
634 }
635 }
636
637 return false;
638 }
639
640 /**
641 * Converts a value in cents into a value with proper
642 * decimal placement.
643 *
644 * Example: 14999 => 149.99
645 */
646 function dfrapi_int_to_price( $price ) {
647 return number_format( ( $price / 100 ), 2 );
648 }
649
650 /**
651 * Converts decimal or none decimal prices into values in cents.
652 *
653 * assert(dfrapi_price_to_int('123') ==12300);
654 * assert(dfrapi_price_to_int('123.4') ==12340);
655 * assert(dfrapi_price_to_int('1234.56') ==123456);
656 * assert(dfrapi_price_to_int('123,4') ==12340);
657 * assert(dfrapi_price_to_int('1234,56') ==123456);
658 * assert(dfrapi_price_to_int('1,234,567') ==123456700);
659 * assert(dfrapi_price_to_int('1,234,567.8') ==123456780);
660 * assert(dfrapi_price_to_int('1,234,567.89') ==123456789);
661 * assert(dfrapi_price_to_int('1.234.567') ==123456700);
662 * assert(dfrapi_price_to_int('1.234.567,8') ==123456780);
663 * assert(dfrapi_price_to_int('1.234.567,89') ==123456789);
664 * assert(dfrapi_price_to_int('FOO 123 BAR') ==12300);
665 */
666 function dfrapi_price_to_int( $price ) {
667 $d = $price;
668 $d = preg_replace( '~^[^\d.,]+~', '', $d );
669 $d = preg_replace( '~[^\d.,]+$~', '', $d );
670
671 // 123 => 12300
672 if ( preg_match( '~^(\d+)$~', $d, $m ) ) {
673 return intval( $m[1] . '00' );
674 }
675
676 // 123.4 => 12340, 123,45 => 12345
677 if ( preg_match( '~^(\d+)[.,](\d{1,2})$~', $d, $m ) ) {
678 return intval( $m[1] . substr( $m[2] . '0000', 0, 2 ) );
679 }
680
681 // 1,234,567.89 => 123456789
682 if ( preg_match( '~^((?:\d{1,3})(?:,\d{3})*)(\.\d{1,2})?$~', $d, $m ) ) {
683 $f = isset( $m[2] ) ? $m[2] : '.';
684
685 return intval( str_replace( ',', '', $m[1] ) . substr( $f . '0000', 1, 2 ) );
686 }
687
688 // 1.234.567,89 => 123456789
689 if ( preg_match( '~^((?:\d{1,3})(?:\.\d{3})*)(,\d{1,2})?$~', $d, $m ) ) {
690 $f = isset( $m[2] ) ? $m[2] : '.';
691
692 return intval( str_replace( '.', '', $m[1] ) . substr( $f . '0000', 1, 2 ) );
693 }
694
695 return null;
696 }
697
698 function dfrapi_html_output_api_error( $data ) {
699 $error = $data['dfrapi_api_error'];
700 $params = @$data['dfrapi_api_error']['params'];
701 ?>
702 <div class="dfrapi_api_error">
703 <div class="dfrapi_head"><?php _e( 'Datafeedr API Error', 'datafeedr-api' ); ?></div>
704 <div class="dfrapi_msg">
705 <strong><?php _e( 'Message:', 'datafeedr-api' ); ?></strong> <?php echo esc_html( $error['msg'] ); ?>
706 </div>
707 <div class="dfrapi_code"><strong><?php _e( 'Code:', 'datafeedr-api' ); ?></strong> <?php echo esc_html( $error['code'] ); ?>
708 </div>
709 <div class="dfrapi_class">
710 <strong><?php _e( 'Class:', 'datafeedr-api' ); ?></strong> <?php echo esc_html( $error['class'] ); ?></div>
711 <?php if ( is_array( $params ) ) : ?>
712 <div class="dfrapi_query"><strong><?php _e( 'Query:', 'datafeedr-api' ); ?></strong>
713 <span><?php echo dfrapi_display_api_request( $params ); ?></span></div>
714 <?php endif; ?>
715 </div>
716 <?php
717 }
718
719 /**
720 * Returns the total number of products in the Datafeedr database (pulled from dfrapi_account option).
721 *
722 * @param bool $formatted
723 * @param mixed $default
724 *
725 * @return int|string
726 */
727 function dfrapi_get_total_products_in_db( $formatted = true, $default = 0 ) {
728 $account = (array) get_option( 'dfrapi_account', [] );
729 $count = absint( $account['product_count'] ?? $default );
730
731 return $formatted ? number_format_i18n( $count ) : $count;
732 }
733
734 /**
735 * Imports an image from a URL into the WordPress Media Library.
736 *
737 * @since 1.2.2 Will return either the Attachment ID or WP_Error if there was an error importing the image.
738 *
739 * @since 1.0.71
740 *
741 * @param string $url Image URL.
742 * @param array $args Optional. An array of options.
743 *
744 * $args = array(
745 *
746 * This is the ID of the post we want to attach this image to. If we do not
747 * want this image to be attached to a post, leave this set to 0.
748 * 'post_id' => 0,
749 *
750 * This is name of the file name the image will have once it is stored on
751 * on the server in the WordPress uploads directory.
752 * 'file_name' => '',
753 *
754 * This is the ID of the User this image will be associated with.
755 * 'user_id' => 0,
756 *
757 * This is the title of the image (which is different than the file name).
758 * 'title' => '',
759 *
760 * The description of the image.
761 * 'description' => '',
762 *
763 * The caption for the image.
764 * 'caption' => '',
765 *
766 * The alt text. Text to display if image cannot be loaded.
767 * 'alt_text' => '',
768 *
769 * Whether this image should be set as the post's thumbnail. If the post_id is 0, this setting will be ignored.
770 * 'is_post_thumbnail' => false,
771 *
772 * The number of seconds to spend attempting to download the image.
773 * 'timeout' => 5
774 *
775 * Sets the image's owner and source. _owner_datafeedr : dfrapi
776 * '_source_plugin' => 'dfrapi'
777 * );
778 *
779 * @return Datafeedr_Image_Importer|int|WP_Error
780 */
781 function datafeedr_import_image( $url, $args = [] ) {
782
783 if ( dfrapi_use_legacy_image_importer() ) {
784 return ( new Datafeedr_Image_Importer( $url, $args ) )->import();
785 }
786
787 $default_args = [
788 'title' => '',
789 'file_name' => '',
790 'description' => '',
791 'caption' => '',
792 'alt_text' => '',
793 'user_id' => 0,
794 'post_id' => 0,
795 'is_post_thumbnail' => true,
796 'timeout' => 5,
797 '_source_plugin' => 'dfrapi',
798 ];
799
800 $args = array_merge( $default_args, $args );
801
802 $image_data = dfrapi_image_data( $url );
803
804 $image_data->set_title( $args['title'] );
805 $image_data->set_filename( $args['file_name'] );
806 $image_data->set_description( $args['description'] );
807 $image_data->set_caption( $args['caption'] );
808 $image_data->set_alternative_text( $args['alt_text'] );
809 $image_data->set_author_id( absint( $args['user_id'] ) );
810 $image_data->set_post_parent_id( absint( $args['post_id'] ) );
811 $image_data->set_post_thumbnail( (bool) $args['is_post_thumbnail'] );
812
813 $image_data = apply_filters( 'datafeedr_import_image_image_data', $image_data, $url, $args );
814
815 $uploader = dfrapi_image_uploader( $image_data );
816
817 $uploader->set_timeout( absint( $args['timeout'] ) );
818
819 $attachment_id = $uploader->upload();
820
821 if ( ! is_wp_error( $attachment_id ) ) {
822 update_post_meta( $attachment_id, '_owner_datafeedr', sanitize_text_field( $args['_source_plugin'] ) );
823 }
824
825 do_action( 'datafeedr_import_image_attachment_id', $attachment_id, $image_data, $url, $args );
826
827 return $attachment_id;
828 }
829
830 /**
831 * Returns true if the $string starts with one of the $patterns. Otherwise returns false.
832 *
833 * @since 1.0.78
834 *
835 * @param string|array $patterns The patterns to search for in the beginning of the $string.
836 *
837 * @param string $string The haystack.
838 *
839 * @return bool True if string starts with the pattern(s) else returns false.
840 */
841 function dfrapi_string_starts_with( $string, $patterns ) {
842 $patterns = ( is_string( $patterns ) ) ? array( $patterns ) : $patterns;
843 foreach ( $patterns as $pattern ) {
844 $length = mb_strlen( $pattern );
845 if ( mb_substr( $string, 0, $length ) === $pattern ) {
846 return true;
847 }
848 }
849
850 return false;
851 }
852
853 /**
854 * Returns true if we are viewing a Datafeedr-specific page in the WordPress Admin Area.
855 *
856 * @since 1.0.84
857 *
858 * @return bool
859 * @global $pagenow
860 *
861 */
862 function dfrapi_is_datafeedr_admin_page() {
863
864 /**
865 * For post edit pages (ie. post.php?post=1&action=edit).
866 */
867 $post_types = [
868 'datafeedr-productset',
869 ];
870
871 /**
872 * For $_GET params (ie. admin.php?page=dfrps_configuration).
873 */
874 $params = [
875 'page' => [
876 'dfrapi',
877 'dfrapi_networks',
878 'dfrapi_merchants',
879 'dfrapi_tools',
880 'dfrapi_export',
881 'dfrapi_import',
882 'dfrapi_account',
883 'dfrcs_options',
884 'dfrps_configuration',
885 'dfrps_tools',
886 'dfrpswc_options',
887 ],
888 'post_type' => [
889 'datafeedr-productset'
890 ]
891 ];
892
893 foreach ( $params as $k => $v ) {
894 if ( isset( $_GET[ $k ] ) && in_array( $_GET[ $k ], $v ) ) {
895 return true;
896 }
897 }
898
899 global $pagenow;
900
901 if ( 'post.php' === $pagenow && in_array( get_post_type(), $post_types ) ) {
902 return true;
903 }
904
905 return false;
906 }
907
908 /**
909 * @param integer $network_id
910 * @param string $id_type
911 *
912 * @return WP_Error|string
913 */
914 function dfrapi_get_affiliate_and_tracking_id( $network_id, $id_type = 'aid' ) {
915
916 static $networks = null;
917
918 $key = 'ids';
919 $type = ( 'tid' === $id_type ) ? 'tid' : 'aid';
920
921 if ( null === $networks ) {
922 $networks = get_option( 'dfrapi_networks', [] );
923 }
924
925 if ( empty( $networks ) ) {
926 return new WP_Error(
927 'dfrapi_get_affiliate_id_no_networks',
928 __( 'No networks selected.', 'datafeedr-api' )
929 );
930 }
931
932 if ( ! isset( $networks[ $key ] ) ) {
933 return new WP_Error(
934 'dfrapi_get_affiliate_id_no_network_ids',
935 __( 'No network IDs selected.', 'datafeedr-api' )
936 );
937 }
938
939 if ( ! isset( $networks[ $key ][ $network_id ] ) ) {
940 return new WP_Error(
941 'dfrapi_get_affiliate_id_no_network_ids',
942 __( 'No data for network with ID of ' . intval( $network_id ), 'datafeedr-api' )
943 );
944 }
945
946 if ( ! isset( $networks[ $key ][ $network_id ][ $type ] ) || empty( $networks[ $key ][ $network_id ][ $type ] ) ) {
947 return new WP_Error(
948 'dfrapi_get_affiliate_id_empty_type',
949 __( 'No affiliate or tracking ID entered for network with ID of ' . intval( $network_id ), 'datafeedr-api' )
950 );
951 }
952
953 return $networks[ $key ][ $network_id ][ $type ];
954 }
955
956 /**
957 * Get The Affiliate Gateway SID from this page WordPress Admin Area > Datafeedr API > Configuration
958 *
959 * @since 1.0.102
960 * @return string|WP_Error
961 */
962 function dfrapi_get_affiliate_gateway_sid() {
963
964 static $sid = null;
965
966 if ( null === $sid ) {
967
968 $config = get_option( 'dfrapi_configuration', [] );
969
970 $sid = ( isset( $config['affiliate_gateway_sid'] ) && ! empty( $config['affiliate_gateway_sid'] ) ) ?
971 trim( $config['affiliate_gateway_sid'] ) :
972 new WP_Error(
973 'missing_affiliate_gateway_sid',
974 'Please enter your The Affiliate Gateway SID <a href="' . admin_url( 'admin.php?page=dfrapi' ) . '" target="_blank">here</a>.'
975 );
976 }
977
978 return $sid;
979 }
980
981 /**
982 * Get Adservice Media ID from this page WordPress Admin Area > Datafeedr API > Configuration
983 *
984 * @since 1.0.102
985 * @return string|WP_Error
986 */
987 function dfrapi_get_adservice_mid() {
988
989 static $sid = null;
990
991 if ( null === $sid ) {
992
993 $config = get_option( 'dfrapi_configuration', [] );
994
995 $sid = ( isset( $config['adservice_mid'] ) && ! empty( $config['adservice_mid'] ) ) ?
996 trim( $config['adservice_mid'] ) :
997 new WP_Error(
998 'missing_adservice_mid',
999 'Please enter your Adservice Media ID <a href="' . admin_url( 'admin.php?page=dfrapi' ) . '" target="_blank">here</a>.'
1000 );
1001 }
1002
1003 return $sid;
1004 }
1005
1006 /**
1007 * Get Belboon Adspace ID from this page WordPress Admin Area > Datafeedr API > Configuration
1008 *
1009 * @since 1.0.124
1010 * @return string|WP_Error
1011 */
1012 function dfrapi_get_belboon_adspace_id() {
1013
1014 static $aid = null;
1015
1016 if ( null === $aid ) {
1017
1018 $config = get_option( 'dfrapi_configuration', [] );
1019
1020 $aid = ( isset( $config['belboon_aid'] ) && ! empty( $config['belboon_aid'] ) ) ?
1021 trim( $config['belboon_aid'] ) :
1022 new WP_Error(
1023 'missing_belboon_aid',
1024 'Please enter your Belboon Adspace ID <a href="' . admin_url( 'admin.php?page=dfrapi' ) . '" target="_blank">here</a>.'
1025 );
1026 }
1027
1028 return $aid;
1029 }
1030
1031 /**
1032 * @param string $url
1033 * @param string $method
1034 * @param array $args
1035 *
1036 * @return SimpleXMLElement|WP_Error
1037 */
1038 function dfrapi_get_xml_response( $url, $method = 'GET', array $args = [] ) {
1039
1040 $response = $method === 'GET' ? wp_remote_get( $url, $args ) : wp_remote_post( $url, $args );
1041
1042 if ( is_wp_error( $response ) ) {
1043 return $response;
1044 }
1045
1046 $code = wp_remote_retrieve_response_code( $response );
1047 $body = wp_remote_retrieve_body( $response );
1048
1049 if ( $code < 200 || $code >= 300 ) {
1050 return new WP_Error( $code, strip_tags( $body ) );
1051 }
1052
1053 if ( ! strlen( $body ) ) {
1054 return new WP_Error( 'connection_error', esc_html__( 'Empty response', 'datafeedr' ) );
1055 }
1056
1057 $xml = simplexml_load_string( $body, null, LIBXML_NOCDATA );
1058
1059 if ( $xml === false ) {
1060 return new WP_Error( 'invalid_xml', esc_html__( 'Invalid XML response', 'datafeedr-api' ) );
1061 }
1062
1063 if ( $xml->getName() === 'error' ) {
1064 return new WP_Error( $code, esc_html( strval( $xml->message ) ) );
1065 }
1066
1067 return $xml;
1068 }
1069
1070 /**
1071 * Determine if a given string ends with a given substring.
1072 *
1073 * @link https://github.com/illuminate/support/blob/7.x/Str.php
1074 *
1075 * @param string $haystack
1076 * @param string|string[] $needles
1077 *
1078 * @return bool
1079 */
1080 function dfrapi_ends_with( $haystack, $needles ) {
1081 foreach ( (array) $needles as $needle ) {
1082 if ( $needle !== '' && substr( $haystack, - strlen( $needle ) ) === (string) $needle ) {
1083 return true;
1084 }
1085 }
1086
1087 return false;
1088 }
1089
1090 /**
1091 * Determine if a given string starts with a given substring.
1092 *
1093 * @link https://github.com/illuminate/support/blob/7.x/Str.php
1094 *
1095 * @param string $haystack
1096 * @param string|string[] $needles
1097 *
1098 * @return bool
1099 */
1100 function dfrapi_starts_with( $haystack, $needles ) {
1101 foreach ( (array) $needles as $needle ) {
1102 if ( (string) $needle !== '' && strncmp( $haystack, $needle, strlen( $needle ) ) === 0 ) {
1103 return true;
1104 }
1105 }
1106
1107 return false;
1108 }
1109
1110 /**
1111 * Returns the portion of string specified by the start and length parameters.
1112 *
1113 * @link https://github.com/illuminate/support/blob/7.x/Str.php
1114 *
1115 * @param string $string
1116 * @param int $start
1117 * @param int|null $length
1118 *
1119 * @return string
1120 */
1121 function dfrapi_substr( $string, $start, $length = null ) {
1122 return mb_substr( $string, $start, $length, 'UTF-8' );
1123 }
1124
1125 /**
1126 * Get the portion of a string before the first occurrence of a given value.
1127 *
1128 * @link https://github.com/illuminate/support/blob/7.x/Str.php
1129 *
1130 * @param string $subject
1131 * @param string $search
1132 *
1133 * @return string
1134 */
1135 function dfrapi_str_before( $subject, $search ) {
1136 return $search === '' ? $subject : explode( $search, $subject )[0];
1137 }
1138
1139 /**
1140 * Get the portion of a string before the last occurrence of a given value.
1141 *
1142 * @link https://github.com/illuminate/support/blob/7.x/Str.php
1143 *
1144 * @param string $subject
1145 * @param string $search
1146 *
1147 * @return string
1148 */
1149 function dfrapi_str_before_last( $subject, $search ) {
1150
1151 if ( $search === '' ) {
1152 return $subject;
1153 }
1154
1155 $pos = mb_strrpos( $subject, $search );
1156
1157 if ( $pos === false ) {
1158 return $subject;
1159 }
1160
1161 return dfrapi_substr( $subject, 0, $pos );
1162 }
1163
1164 /**
1165 * Get the portion of a string between two given values.
1166 *
1167 * @link https://github.com/illuminate/support/blob/7.x/Str.php
1168 *
1169 * @param string $subject
1170 * @param string $from
1171 * @param string $to
1172 *
1173 * @return string
1174 */
1175 function dfrapi_str_between( $subject, $from, $to ) {
1176 if ( $from === '' || $to === '' ) {
1177 return $subject;
1178 }
1179
1180 return dfrapi_str_before_last( dfrapi_str_after( $subject, $from ), $to );
1181 }
1182
1183 /**
1184 * Return the remainder of a string after the first occurrence of a given value.
1185 *
1186 * @link https://github.com/illuminate/support/blob/7.x/Str.php
1187 *
1188 * @param string $subject
1189 * @param string $search
1190 *
1191 * @return string
1192 */
1193 function dfrapi_str_after( $subject, $search ): string {
1194 return $search === '' ? $subject : array_reverse( explode( $search, $subject, 2 ) )[0];
1195 }
1196
1197 /**
1198 * Return the remainder of a string after the last occurrence of a given value.
1199 *
1200 * @link https://github.com/illuminate/support/blob/7.x/Str.php
1201 *
1202 * @param string $subject
1203 * @param string $search
1204 *
1205 * @return string
1206 */
1207 function dfrapi_str_after_last( $subject, $search ) {
1208
1209 if ( $search === '' ) {
1210 return $subject;
1211 }
1212
1213 $position = strrpos( $subject, (string) $search );
1214
1215 if ( $position === false ) {
1216 return $subject;
1217 }
1218
1219 return substr( $subject, $position + strlen( $search ) );
1220 }
1221
1222 /**
1223 * Determine if a given string contains a given substring.
1224 *
1225 * @link https://github.com/illuminate/support/blob/7.x/Str.php
1226 *
1227 * @param string $haystack
1228 * @param string|string[] $needles
1229 *
1230 * @return bool
1231 */
1232 function dfrapi_str_contains( $haystack, $needles ) {
1233 foreach ( (array) $needles as $needle ) {
1234 if ( $needle !== '' && mb_strpos( $haystack, $needle ) !== false ) {
1235 return true;
1236 }
1237 }
1238
1239 return false;
1240 }
1241
1242 /**
1243 * Determine if a given string contains all array values.
1244 *
1245 * @link https://github.com/illuminate/support/blob/7.x/Str.php
1246 *
1247 * @param string $haystack
1248 * @param string[] $needles
1249 *
1250 * @return bool
1251 */
1252 function dfrapi_str_contains_all( $haystack, array $needles ) {
1253 foreach ( $needles as $needle ) {
1254 if ( ! dfrapi_str_contains( $haystack, $needle ) ) {
1255 return false;
1256 }
1257 }
1258
1259 return true;
1260 }
1261
1262 /**
1263 * Returns an integer value of string by:
1264 * - removing all non-numeric characters
1265 * - retaining any negative sign
1266 * - running through intval to clean up any other weirdness.
1267 *
1268 * This function is pretty forgiving.
1269 *
1270 * Examples:
1271 *
1272 * '123.45' => 12345
1273 * '-123.45' => -12345
1274 * 'foo' => 0
1275 * '-foo' => 0
1276 * '-123-56' => -123
1277 * '123-45' => 123
1278 * '-123,56' => -12356
1279 * '$-1.111,45' => -111145
1280 * '00012.34' => 1234
1281 *
1282 * @param string|int|numeric $int
1283 *
1284 * @return int
1285 */
1286 function dfrapi_intify( $int ): int {
1287 return (int) preg_replace( '/[^0-9-]/', '', $int );
1288 }
1289
1290 /**
1291 * Returns an instance of the Dfrapi_Price class.
1292 *
1293 * @param mixed $value The value to use as the price.
1294 * @param string $currency_code 3-character ISO 4217 currency code.
1295 * @param mixed $context Optional.
1296 *
1297 * @return Dfrapi_Price
1298 */
1299 function dfrapi_price( $value, $currency_code, $context = null ): Dfrapi_Price {
1300 return new Dfrapi_Price( $value, dfrapi_currency( $currency_code, $context ), $context );
1301 }
1302
1303 /**
1304 * Returns an instance of the Dfrapi_Currency class.
1305 *
1306 * @param string $currency_code 3-character ISO 4217 currency code.
1307 * @param mixed $context Optional.
1308 *
1309 * @return Dfrapi_Currency
1310 */
1311 function dfrapi_currency( $currency_code, $context = null ): Dfrapi_Currency {
1312 return new Dfrapi_Currency( $currency_code, $context );
1313 }
1314
1315 /**
1316 * Returns the fully formatted price.
1317 *
1318 * @param mixed $value The value to use as the price.
1319 * @param string $currency_code 3-character ISO 4217 currency code.
1320 * @param mixed $context Optional.
1321 *
1322 * @return string
1323 */
1324 function dfrapi_get_price( $value, $currency_code, $context = null ): string {
1325 return dfrapi_price( $value, $currency_code, $context )->get_price();
1326 }
1327
1328 /**
1329 * Returns an instance of the Dfrapi_Image_Data class.
1330 *
1331 * @param string $url The URL of the image we will be uploading.
1332 *
1333 * @return Dfrapi_Image_Data
1334 */
1335 function dfrapi_image_data( string $url ): Dfrapi_Image_Data {
1336 return new Dfrapi_Image_Data( $url );
1337 }
1338
1339 /**
1340 * Returns an instance of the Dfrapi_Image_Uploader class.
1341 *
1342 * @param Dfrapi_Image_Data $image_data
1343 *
1344 * @return Dfrapi_Image_Uploader
1345 */
1346 function dfrapi_image_uploader( Dfrapi_Image_Data $image_data ): Dfrapi_Image_Uploader {
1347 return new Dfrapi_Image_Uploader( $image_data );
1348 }
1349
1350 /**
1351 * Returns the string to use as the prefix for the ActionScheduler hook name.
1352 *
1353 * @return string
1354 */
1355 function dfrapi_as_hook_prefix(): string {
1356 return 'dfrapi_as_';
1357 }
1358
1359 /**
1360 * Formats and returns the hook name.
1361 *
1362 * @param string $hook
1363 *
1364 * @return string
1365 */
1366 function dfrapi_as_hook_name( string $hook ): string {
1367 return dfrapi_as_hook_prefix() . trim( $hook );
1368 }
1369
1370 /**
1371 * Returns true is the ActionScheduler library exists otherwise returns WP_Error.
1372 *
1373 * The ActionScheduler ships with WooCommerce but can also be installed independently
1374 * here: https://wordpress.org/plugins/action-scheduler/
1375 *
1376 * @return true|WP_Error
1377 */
1378 function dfrapi_action_scheduler_exists() {
1379 return function_exists( 'as_schedule_recurring_action' )
1380 ? true
1381 : new WP_Error( 'dfrapi_action_scheduler_does_not_exist.', __( 'The ActionScheduler library does not exist.', 'datafeedr-api' ) );
1382 }
1383
1384 /**
1385 * Enqueue an action to run one time, as soon as possible
1386 *
1387 * @param string $hook
1388 * @param array $args
1389 * @param string $group
1390 *
1391 * @return string The action ID.
1392 */
1393 function dfrapi_schedule_async_action( string $hook, array $args = [], string $group = 'datafeedr' ) {
1394 return ( dfrapi_action_scheduler_exists() === true )
1395 ? as_enqueue_async_action( dfrapi_as_hook_name( $hook ), $args, $group )
1396 : dfrapi_action_scheduler_exists();
1397 }
1398
1399 /**
1400 * Schedule an action to run one time
1401 *
1402 * @param int $timestamp
1403 * @param string $hook
1404 * @param array $args
1405 * @param string $group
1406 *
1407 * @return string The action ID
1408 */
1409 function dfrapi_schedule_single_action( int $timestamp, string $hook, array $args = [], string $group = 'datafeedr' ) {
1410 return ( dfrapi_action_scheduler_exists() === true )
1411 ? as_schedule_single_action( $timestamp, dfrapi_as_hook_name( $hook ), $args, $group )
1412 : dfrapi_action_scheduler_exists();
1413 }
1414
1415 /**
1416 * Schedule a recurring action using ActionScheduler.
1417 *
1418 * @param int $timestamp
1419 * @param int $interval_in_seconds
1420 * @param string $hook
1421 * @param array $args
1422 * @param string $group
1423 *
1424 * @return int|WP_Error The action ID or WP_Error if as_schedule_recurring_action() function does not exist.
1425 */
1426 function dfrapi_schedule_recurring_action( int $timestamp, int $interval_in_seconds, string $hook, array $args = [], string $group = 'datafeedr' ) {
1427 return ( dfrapi_action_scheduler_exists() === true )
1428 ? as_schedule_recurring_action( $timestamp, $interval_in_seconds, dfrapi_as_hook_name( $hook ), $args, $group )
1429 : dfrapi_action_scheduler_exists();
1430 }
1431
1432 /**
1433 * Schedule an action that recurs on a cron-like schedule.
1434 *
1435 * @param int $timestamp The first instance of the action will be scheduled to run at a time calculated after this timestamp matching the cron expression. This can be used to delay the first instance of the action.
1436 * @param string $schedule A cron-like schedule string (See: http://en.wikipedia.org/wiki/Cron)
1437 * * * * * * *
1438 * ┬ ┬ ┬ ┬ ┬ ┬
1439 * | | | | | |
1440 * | | | | | + year [optional]
1441 * | | | | +----- day of week (0 - 7) (Sunday=0 or 7)
1442 * | | | +---------- month (1 - 12)
1443 * | | +--------------- day of month (1 - 31)
1444 * | +-------------------- hour (0 - 23)
1445 * +------------------------- min (0 - 59)
1446 * @param string $hook
1447 * @param array $args
1448 * @param string $group
1449 *
1450 * @return int|WP_Error The action ID or WP_Error if as_schedule_cron_action() function does not exist.
1451 */
1452 function dfrapi_schedule_cron_action( int $timestamp, string $schedule, string $hook, array $args = [], string $group = 'datafeedr' ) {
1453 return ( dfrapi_action_scheduler_exists() === true )
1454 ? as_schedule_cron_action( $timestamp, $schedule, dfrapi_as_hook_name( $hook ), $args, $group )
1455 : dfrapi_action_scheduler_exists();
1456 }
1457
1458 /**
1459 * Unschedule a scheduled action.
1460 *
1461 * @param string $hook
1462 * @param array $args
1463 * @param string $group
1464 *
1465 * @return string|null|WP_Error The scheduled action ID if a scheduled action was found, or null if no matching action found. WP_Error if as_ function doesn't exist.
1466 */
1467 function dfrapi_unschedule_action( string $hook, array $args = [], string $group = 'datafeedr' ) {
1468 return ( dfrapi_action_scheduler_exists() === true )
1469 ? as_unschedule_action( dfrapi_as_hook_name( $hook ), $args, $group )
1470 : dfrapi_action_scheduler_exists();
1471 }
1472
1473 /**
1474 * Returns the timestamp for the next occurrence of a pending scheduled action,
1475 * returns true for an async or in-progress action or false if there is no matching action.
1476 *
1477 * @param string $hook
1478 * @param array $args
1479 * @param string $group
1480 *
1481 * @return int|bool|WP_Error The timestamp for the next occurrence of a pending scheduled action, true for an async or in-progress action or false if there is no matching action. WP_Error if as_ function doesn't exist.
1482 */
1483 function dfrapi_next_scheduled_action( string $hook, array $args = [], string $group = 'datafeedr' ) {
1484 return ( dfrapi_action_scheduler_exists() === true )
1485 ? as_next_scheduled_action( dfrapi_as_hook_name( $hook ), $args, $group )
1486 : dfrapi_action_scheduler_exists();
1487 }
1488
1489 /**
1490 * Returns true if the Jetpack::class exists.
1491 *
1492 * @return bool
1493 */
1494 function dfrapi_jetpack_exists(): bool {
1495 return class_exists( Jetpack::class, false );
1496 }
1497
1498 /**
1499 * Returns true is Jetpack is active, otherwise returns false.
1500 *
1501 * @return bool
1502 */
1503 function dfrapi_jetpack_is_active(): bool {
1504 return dfrapi_jetpack_exists() ? Jetpack::is_active() : false;
1505 }
1506
1507 /**
1508 * Returns true if Jetpack is in Dev/Debug mode.
1509 *
1510 * @return bool
1511 */
1512 function dfrapi_jetpack_is_in_dev_mode(): bool {
1513 return dfrapi_jetpack_exists() ? defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG === true : false;
1514 }
1515
1516 /**
1517 * Returns true if "Speed up image load times" is ON here:
1518 * WordPress Admin Area > Jetpack > Settings > Performance > Performance & speed
1519 *
1520 * @return bool
1521 */
1522 function dfrapi_jetpack_photon_module_is_active(): bool {
1523 return dfrapi_jetpack_exists() ? in_array( 'photon', Jetpack::get_active_modules() ) : false;
1524 }
1525
1526 /**
1527 * Returns true if "Speed up static file load times" is ON here:
1528 * WordPress Admin Area > Jetpack > Settings > Performance > Performance & speed
1529 *
1530 * @return bool
1531 */
1532 function dfrapi_jetpack_photon_cdn_module_is_active(): bool {
1533 return dfrapi_jetpack_exists() ? in_array( 'photon-cdn', Jetpack::get_active_modules() ) : false;
1534 }
1535
1536 /**
1537 * @param string $image_url URL to the publicly accessible image you want to manipulate.
1538 * @param array|string $args An array of arguments, i.e. array( 'w' => '300', 'resize' => array( 123, 456 ) ), or in string form (w=123&h=456).
1539 * @param string|null $scheme URL protocol.
1540 *
1541 * @return false|string
1542 */
1543 function dfrapi_jetpack_photon_url( $image_url, $args = [], $scheme = null ) {
1544
1545 if ( ! dfrapi_jetpack_is_active() && ! dfrapi_jetpack_is_in_dev_mode() ) {
1546 return $image_url;
1547 }
1548
1549 return jetpack_photon_url( $image_url, $args, $scheme );
1550 }
1551
1552 /**
1553 * Whether to use the legacy image importer. Default false.
1554 *
1555 * @return bool
1556 */
1557 function dfrapi_use_legacy_image_importer(): bool {
1558 return (bool) apply_filters( 'dfrapi_use_legacy_image_importer', false );
1559 }
1560
1561 /**
1562 * Formats an Admin Notice and echos it.
1563 *
1564 * You must send a fully escaped $message to dfrapi_admin_notice() because this
1565 * function will NOT escape the $message variable.
1566 *
1567 * Also, $message is already wrapped in <p></p> tags. Therefore, it cannot contain
1568 * additional <p> tags or any other HTML element not allowed as a child to <p> tags.
1569 *
1570 * @param string $message Notice message. Will NOT be escaped.
1571 * @param string $status Either error, warning, success or info.
1572 * @param string|null $heading Optional. The notice heading or title.
1573 * @param string|null $plugin Optional. The name of the plugin responsible for generating this notice.
1574 *
1575 * @return void
1576 */
1577 function dfrapi_admin_notice( string $message, string $status, ?string $heading = null, ?string $plugin = null ) {
1578 $plugin = $plugin ? esc_html( trim( $plugin ) ) : '';
1579 $heading = $heading ? esc_html( trim( $heading ) ) : '';
1580 $separator = $plugin && $heading ? ' &mdash; ' : '';
1581 $label = $plugin || $heading ? sprintf( '<strong>%1$s%2$s%3$s</strong><br>', $plugin, $separator, $heading ) : '';
1582
1583 $status = in_array( $status, [ 'error', 'warning', 'success', 'info' ] ) ? $status : 'info';
1584 $class = esc_attr( 'notice notice-' . $status );
1585
1586 printf( '<div class="%1$s"><p>%2$s%3$s</p></div>', $class, $label, $message );
1587 }
1588
1589 /**
1590 * Get selected networks. Format is like this:
1591 *
1592 * Array (
1593 * [ids] => Array (
1594 * [18] => Array (
1595 * [nid] => 18
1596 * [aid] => abc123
1597 * [tid] =>
1598 * )
1599 * [1200] => Array (
1600 * [nid] => 1200
1601 * [aid] => qwerty
1602 * [tid] =>
1603 * )
1604 * [126] => Array (
1605 * [nid] => 126
1606 * [aid] => 15759
1607 * [tid] =>
1608 * )
1609 * )
1610 * )
1611 *
1612 * @return array
1613 */
1614 function dfrapi_get_selected_networks(): array {
1615 return (array) get_option( 'dfrapi_networks', [] );
1616 }
1617
1618 /**
1619 * Get the list of selected Network IDs.
1620 *
1621 * @return array
1622 */
1623 function dfrapi_get_selected_network_ids(): array {
1624
1625 static $ids = null;
1626
1627 if ( $ids === null ) {
1628
1629 $ids = [];
1630 $networks = dfrapi_get_selected_networks();
1631
1632 if ( ! isset( $networks['ids'] ) ) {
1633 return $ids;
1634 }
1635
1636 if ( empty( $networks['ids'] ) ) {
1637 return $ids;
1638 }
1639
1640 if ( ! is_array( $networks['ids'] ) ) {
1641 return $ids;
1642 }
1643
1644 foreach ( $networks['ids'] as $k => $v ) {
1645 $nid = absint( $v['nid'] ?? 0 );
1646 if ( $nid > 0 ) {
1647 $ids[] = absint( $nid );
1648 }
1649 }
1650
1651 $ids = array_filter( array_unique( $ids ) );
1652 }
1653
1654 return $ids;
1655 }
1656
1657 /**
1658 * Get the total number of selected networks.
1659 *
1660 * @return int
1661 */
1662 function dfrapi_selected_network_count(): int {
1663 return count( dfrapi_get_selected_network_ids() );
1664 }
1665
1666 /**
1667 * Returns true if the user has selected at least 1 network. Otherwise, returns false.
1668 *
1669 * @return bool
1670 */
1671 function dfrapi_user_has_selected_networks(): bool {
1672 return dfrapi_selected_network_count() > 0;
1673 }
1674
1675 /**
1676 * Get selected merchants. Format is like this:
1677 *
1678 * Array (
1679 * [ids] => Array (
1680 * [0] => 1258
1681 * [1] => 12927
1682 * [2] => 1312
1683 * [3] => 14342
1684 * )
1685 * )
1686 *
1687 * @return array
1688 */
1689 function dfrapi_get_selected_merchants(): array {
1690 return (array) get_option( 'dfrapi_merchants', [] );
1691 }
1692
1693 /**
1694 * Get the list of selected Merchant IDs.
1695 *
1696 * @return array
1697 */
1698 function dfrapi_get_selected_merchant_ids(): array {
1699
1700 static $ids = null;
1701
1702 if ( $ids === null ) {
1703
1704 $ids = [];
1705 $merchants = dfrapi_get_selected_merchants();
1706
1707 if ( ! isset( $merchants['ids'] ) ) {
1708 return $ids;
1709 }
1710
1711 if ( empty( $merchants['ids'] ) ) {
1712 return $ids;
1713 }
1714
1715 if ( ! is_array( $merchants['ids'] ) ) {
1716 return $ids;
1717 }
1718
1719 foreach ( $merchants['ids'] as $id ) {
1720 $ids[] = absint( $id );
1721 }
1722
1723 $ids = array_filter( array_unique( $ids ) );
1724 }
1725
1726 return $ids;
1727 }
1728
1729 /**
1730 * Get the total number of selected merchants.
1731 *
1732 * @return int
1733 */
1734 function dfrapi_selected_merchant_count(): int {
1735 return count( dfrapi_get_selected_merchant_ids() );
1736 }
1737
1738 /**
1739 * Returns true if the user has selected at least 1 merchant. Otherwise, returns false.
1740 *
1741 * @return bool
1742 */
1743 function dfrapi_user_has_selected_merchants(): bool {
1744 return dfrapi_selected_merchant_count() > 0;
1745 }
1746
1747 /**
1748 * Returns the absolute URL for the Datafeedr API > Networks page.
1749 *
1750 * @return string
1751 */
1752 function dfrapi_networks_page_url(): string {
1753 return add_query_arg( [ 'page' => 'dfrapi_networks' ], admin_url( 'admin.php' ) );
1754 }
1755
1756 /**
1757 * Returns the absolute URL for the Datafeedr API > Merchants page.
1758 *
1759 * @return string
1760 */
1761 function dfrapi_merchants_page_url(): string {
1762 return add_query_arg( [ 'page' => 'dfrapi_merchants' ], admin_url( 'admin.php' ) );
1763 }
1764
1765 /**
1766 * Returns the absolute URL for the Datafeedr API > Configuration page.
1767 *
1768 * @return string
1769 */
1770 function dfrapi_configuration_page_url(): string {
1771 return add_query_arg( [ 'page' => 'dfrapi' ], admin_url( 'admin.php' ) );
1772 }
1773
1774 /**
1775 * Returns the absolute URL for the Datafeedr API > Tools page.
1776 *
1777 * @return string
1778 */
1779 function dfrapi_tools_page_url(): string {
1780 return add_query_arg( [ 'page' => 'dfrapi_tools' ], admin_url( 'admin.php' ) );
1781 }
1782
1783 /**
1784 * Returns an array of Network IDs of those networks who require an affiliate ID but the
1785 * user has not yet entered an affiliate ID.
1786 *
1787 * @return array
1788 */
1789 function dfrapi_get_network_ids_missing_affiliate_id(): array {
1790
1791 static $ids = null;
1792
1793 if ( $ids === null ) {
1794
1795 $ids = [];
1796 $networks = dfrapi_get_selected_networks();
1797
1798 if ( ! isset( $networks['ids'] ) ) {
1799 return $ids;
1800 }
1801
1802 if ( ! is_array( $networks['ids'] ) ) {
1803 return $ids;
1804 }
1805
1806 $no_affiliate_id_required = dfrapi_get_ids_of_networks_which_dont_require_affiliate_ids();
1807
1808 foreach ( $networks['ids'] as $k => $v ) {
1809
1810 $nid = absint( $v['nid'] ?? 0 );
1811
1812 if ( in_array( $nid, $no_affiliate_id_required, true ) ) {
1813 continue;
1814 }
1815
1816 $aid = trim( $v['aid'] ?? '' );
1817
1818 if ( $nid > 0 && empty( $aid ) ) {
1819 $ids[] = $nid;
1820 }
1821 }
1822 }
1823
1824 return array_filter( array_unique( $ids ) );
1825 }
1826
1827 /**
1828 * Returns an array of Network IDs which don't require the user to enter an affiliate ID.
1829 *
1830 * @return array
1831 */
1832 function dfrapi_get_ids_of_networks_which_dont_require_affiliate_ids(): array {
1833 return array_merge(
1834 dfrapi_get_partnerize_network_ids(),
1835 dfrapi_get_effiliation_network_ids()
1836 );
1837 }
1838
1839 /**
1840 * Returns true if user is missing at least one affiliate ID. Otherwise, returns false.
1841 *
1842 * @return bool
1843 */
1844 function dfrapi_user_is_missing_affiliate_ids(): bool {
1845 return count( dfrapi_get_network_ids_missing_affiliate_id() ) > 0;
1846 }
1847
1848 // Functions from functions/api.php 2022-02-14 14:02:41 from here to effiliation_ids function
1849 function dfrapi_api_get_status() {
1850 $api = dfrapi_api( dfrapi_get_transport_method() );
1851 try {
1852 $status = $api->getStatus();
1853 dfrapi_api_update_status( $api );
1854
1855 return $status;
1856 } catch ( Exception $err ) {
1857 return dfrapi_api_error( $err );
1858 }
1859 }
1860
1861 /**
1862 * Removed configuration. Always returns 'wordpress'. 2017-02-21 10:23:10
1863 */
1864 function dfrapi_get_transport_method(): string {
1865 return 'wordpress';
1866 }
1867
1868 /**
1869 * This instantiates the Datafeedr API Library and returns the $api object.
1870 */
1871 function dfrapi_api( $transport = 'curl', $timeout = 0, $returnObjects = false ) {
1872
1873 $configuration = (array) get_option( 'dfrapi_configuration' );
1874
1875 if ( isset( $configuration['disable_api'] ) && ( $configuration['disable_api'] === 'yes' ) ) {
1876 $configuration['disable_api'] = 'no';
1877 update_option( 'dfrapi_configuration', $configuration );
1878 }
1879
1880 $access_id = false;
1881 $secret_key = false;
1882 $transport = dfrapi_get_transport_method();
1883
1884 if ( isset( $configuration['access_id'] ) && ( $configuration['access_id'] != '' ) ) {
1885 $access_id = $configuration['access_id'];
1886 }
1887
1888 if ( isset( $configuration['secret_key'] ) && ( $configuration['secret_key'] != '' ) ) {
1889 $secret_key = $configuration['secret_key'];
1890 }
1891
1892 if ( $access_id && $secret_key ) {
1893
1894 $options = [
1895 'transport' => 'wordpress',
1896 'timeout' => 60,
1897 'returnObjects' => false,
1898 'retry' => 3, // The number of retries if an API request times-out.
1899 'retryTimeout' => 5, // The number of seconds to wait between retries.
1900 ];
1901
1902 $options = apply_filters( 'dfrapi_api_options', $options );
1903
1904 // Always connect over HTTPS. Set after the filter so it can't be downgraded to HTTP.
1905 $options['https'] = true;
1906
1907 $options['domain'] = parse_url( get_site_url(), PHP_URL_HOST );
1908
1909 return new DatafeedrApi( $access_id, $secret_key, $options );
1910
1911 } else {
1912 return false;
1913 }
1914 }
1915
1916 /**
1917 * Creates an associate array with the API's error details.
1918 */
1919 function dfrapi_api_error( $error, $params = false ) {
1920
1921 // Change "request_count" to "max_requests" because sometimes there's
1922 // not even enough API requests left to update the Account info with
1923 // the most update to date information.
1924 if ( $error->getCode() == 301 ) {
1925 $account = get_option( 'dfrapi_account', array() );
1926 $account['request_count'] = $account['max_requests'];
1927 update_option( 'dfrapi_account', $account );
1928 }
1929
1930 return array(
1931 'dfrapi_api_error' => array(
1932 'class' => get_class( $error ),
1933 'code' => $error->getCode(),
1934 'msg' => $error->getMessage(),
1935 'params' => $params,
1936 )
1937 );
1938 }
1939
1940 /**
1941 * Creates the proper API request from the $query.
1942 */
1943 function dfrapi_api_query_to_filters( $query, $useSelected = true ) {
1944 $sform = new Dfrapi_SearchForm();
1945
1946 return $sform->makeFilters( $query, $useSelected );
1947 }
1948
1949 /**
1950 * Returns a parameter value from the $query array.
1951 */
1952 function dfrapi_api_get_query_param( $query, $param ) {
1953 if ( is_array( $query ) && ! empty( $query ) ) {
1954 foreach ( $query as $k => $v ) {
1955 if ( $v['field'] == $param ) {
1956 return array(
1957 'field' => $v['field'] ?? '',
1958 'operator' => $v['operator'] ?? '',
1959 'value' => $v['value'] ?? '',
1960 );
1961 }
1962 }
1963 }
1964
1965 return false;
1966 }
1967
1968 /**
1969 * This updates the "dfrapi_account" option with the most recent
1970 * API status information for this user.
1971 */
1972 function dfrapi_api_update_status( &$api ) {
1973 if ( $status = $api->lastStatus() ) {
1974 $account = get_option( 'dfrapi_account', array() );
1975 $account['user_id'] = $status['user_id'];
1976 $account['plan_id'] = $status['plan_id'];
1977 $account['bill_day'] = $status['bill_day'];
1978 $account['max_total'] = $status['max_total'];
1979 $account['max_length'] = $status['max_length'];
1980 $account['max_requests'] = $status['max_requests'];
1981 $account['request_count'] = $status['request_count'];
1982 $account['network_count'] = $status['network_count'];
1983 $account['product_count'] = $status['product_count'];
1984 $account['merchant_count'] = $status['merchant_count'];
1985 update_option( 'dfrapi_account', $account );
1986 }
1987 }
1988
1989 /**
1990 * This returns all affiliate networks' information.
1991 * This accepts an array of source_ids (network ids)
1992 * to return a subset of networks.
1993 */
1994 function dfrapi_api_get_all_networks( $nids = array() ) {
1995 $option_name = 'dfrapi_all_networks';
1996 $use_cache = wp_using_ext_object_cache( false );
1997 $networks = get_transient( $option_name );
1998 wp_using_ext_object_cache( $use_cache );
1999 if ( false === $networks || empty ( $networks ) ) {
2000 $api = dfrapi_api( dfrapi_get_transport_method() );
2001 try {
2002 $networks = $api->getNetworks( $nids, true );
2003 dfrapi_api_set_network_types( $networks );
2004 dfrapi_api_update_status( $api );
2005 } catch ( Exception $err ) {
2006 return dfrapi_api_error( $err );
2007 }
2008 $use_cache = wp_using_ext_object_cache( false );
2009 set_transient( $option_name, $networks, MONTH_IN_SECONDS );
2010 wp_using_ext_object_cache( $use_cache );
2011 }
2012 dfrapi_update_transient_whitelist( $option_name );
2013
2014 usort( $networks, function ( $a, $b ) {
2015 return strnatcasecmp( $a['name'], $b['name'] );
2016 } );
2017
2018 return array_filter( $networks, static function ( $network ) {
2019 return ! in_array( absint( $network['_id'] ), dfrapi_inactive_networks(), true );
2020 } );
2021 }
2022
2023 /**
2024 * Returns a Zanox zmid value.
2025 */
2026 function dfrapi_api_get_zanox_zmid( $merchant_id, $adspace_id ) {
2027
2028 $option_name = 'zmid_' . $merchant_id . '_' . $adspace_id;
2029 $use_cache = wp_using_ext_object_cache( false );
2030 $zmid = get_transient( $option_name );
2031 wp_using_ext_object_cache( $use_cache );
2032
2033 if ( $zmid ) {
2034 return $zmid;
2035 }
2036
2037 $keys = dfrapi_get_zanox_keys();
2038 $api = dfrapi_api();
2039
2040 try {
2041 $zmid = $api->getZanoxMerchantIds(
2042 $merchant_id,
2043 $adspace_id,
2044 $keys['connection_key']
2045 );
2046 } catch ( Exception $err ) {
2047 $zmid = 'dfrapi_unapproved_zanox_merchant';
2048 }
2049
2050 $use_cache = wp_using_ext_object_cache( false );
2051 set_transient( $option_name, $zmid, WEEK_IN_SECONDS );
2052 wp_using_ext_object_cache( $use_cache );
2053
2054 dfrapi_update_transient_whitelist( $option_name );
2055
2056 return $zmid;
2057 }
2058
2059 /**
2060 * Returns a Partnerize camref value.
2061 */
2062 function dfrapi_api_get_ph_camref( $merchant_id ) {
2063
2064 $option_name = 'camref_' . $merchant_id;
2065 $use_cache = wp_using_ext_object_cache( false );
2066 $camref = get_transient( $option_name );
2067 wp_using_ext_object_cache( $use_cache );
2068
2069 if ( $camref ) {
2070 return $camref;
2071 }
2072
2073 $keys = dfrapi_get_ph_keys();
2074 $api = dfrapi_api();
2075
2076 try {
2077 $camref = $api->getPerformanceHorizonCamrefs(
2078 $merchant_id,
2079 $keys['application_key'],
2080 $keys['user_api_key'],
2081 $keys['publisher_id']
2082 );
2083 } catch ( Exception $err ) {
2084 $camref = 'dfrapi_unapproved_ph_merchant';
2085 }
2086
2087 $use_cache = wp_using_ext_object_cache( false );
2088 set_transient( $option_name, $camref, WEEK_IN_SECONDS );
2089 wp_using_ext_object_cache( $use_cache );
2090
2091 dfrapi_update_transient_whitelist( $option_name );
2092
2093 return $camref;
2094 }
2095
2096 /**
2097 * Returns a Effiliation affiliate ID.
2098 *
2099 * @since 1.0.81
2100 */
2101 function dfrapi_api_get_effiliation_affiliate_id( $merchant_id ) {
2102
2103 $option_name = 'effiliation_' . $merchant_id;
2104 $use_cache = wp_using_ext_object_cache( false );
2105 $affiliate_id = get_transient( $option_name );
2106 wp_using_ext_object_cache( $use_cache );
2107
2108 if ( $affiliate_id ) {
2109 return $affiliate_id;
2110 }
2111
2112 try {
2113 $affiliate_id = dfrapi_get_affiliate_id_for_effiliation_merchant( $merchant_id );
2114 } catch ( Exception $err ) {
2115 $affiliate_id = 'dfrapi_unapproved_effiliation_merchant';
2116 }
2117
2118 $use_cache = wp_using_ext_object_cache( false );
2119 set_transient( $option_name, $affiliate_id, WEEK_IN_SECONDS );
2120 wp_using_ext_object_cache( $use_cache );
2121
2122 dfrapi_update_transient_whitelist( $option_name );
2123
2124 return $affiliate_id;
2125 }
2126
2127 /**
2128 * This creates 2 options in the options table each time the option
2129 * "dfrapi_all_networks" is updated with new network information from the API.
2130 *
2131 * - dfrapi_product_networks
2132 * - dfrapi_coupon_networks
2133 *
2134 * These are just helper options to figure out if a network is a "product"
2135 * network or a "coupon" network.
2136 */
2137 function dfrapi_api_set_network_types( $networks ) {
2138 $product_networks = array();
2139 $coupon_networks = array();
2140 foreach ( $networks as $network ) {
2141 if ( $network['type'] === 'products' ) {
2142 $product_networks[ $network['_id'] ] = $network;
2143 } elseif ( $network['type'] === 'coupons' ) {
2144 $coupon_networks[ $network['_id'] ] = $network;
2145 }
2146 }
2147 update_option( 'dfrapi_product_networks', $product_networks );
2148 update_option( 'dfrapi_coupon_networks', $coupon_networks );
2149 }
2150
2151 /**
2152 * This stores all merchants for a given source_id ($nid).
2153 *
2154 * It is possible to pass "all" to this function however this creates
2155 * memory_limit errors when memory is set to less than 64MB.
2156 */
2157 function dfrapi_api_get_all_merchants( $nid ) {
2158 $option_name = 'dfrapi_all_merchants_for_nid_' . $nid;
2159 $use_cache = wp_using_ext_object_cache( false );
2160 $merchants = get_transient( $option_name );
2161 wp_using_ext_object_cache( $use_cache );
2162 if ( false === $merchants || empty ( $merchants ) ) {
2163 $api = dfrapi_api( dfrapi_get_transport_method() );
2164 try {
2165 $merchants = $api->getMerchants( array( intval( $nid ) ), true );
2166 dfrapi_api_update_status( $api );
2167 } catch ( Exception $err ) {
2168 return dfrapi_api_error( $err );
2169 }
2170 $use_cache = wp_using_ext_object_cache( false );
2171 set_transient( $option_name, $merchants, MONTH_IN_SECONDS );
2172 wp_using_ext_object_cache( $use_cache );
2173 }
2174 dfrapi_update_transient_whitelist( $option_name );
2175
2176 return $merchants;
2177 }
2178
2179 /**
2180 * This returns merchant or merchants' information by merchant_id or
2181 * an array of merchant IDs.
2182 */
2183 function dfrapi_api_get_merchants_by_id( $ids, $includeEmpty = false ) {
2184 $name = false;
2185 if ( is_array( $ids ) ) {
2186 sort( $ids, SORT_NUMERIC );
2187 $id_string = implode( ",", $ids );
2188 $name = md5( $id_string );
2189 } elseif ( $ids != '' ) {
2190 $name = trim( $ids );
2191 }
2192 if ( ! $name ) {
2193 return;
2194 }
2195 $name = substr( $name, 0, 20 );
2196 $option_name = 'dfrapi_merchants_byid_' . $name;
2197 $use_cache = wp_using_ext_object_cache( false );
2198 $merchants = get_transient( $option_name );
2199 wp_using_ext_object_cache( $use_cache );
2200 if ( false === $merchants || empty ( $merchants ) ) {
2201 $api = dfrapi_api( dfrapi_get_transport_method() );
2202 try {
2203 $merchants = $api->getMerchantsById( $ids, $includeEmpty );
2204 dfrapi_api_update_status( $api );
2205 } catch ( Exception $err ) {
2206 return dfrapi_api_error( $err );
2207 }
2208 $use_cache = wp_using_ext_object_cache( false );
2209 set_transient( $option_name, $merchants, MONTH_IN_SECONDS );
2210 wp_using_ext_object_cache( $use_cache );
2211 }
2212 dfrapi_update_transient_whitelist( $option_name );
2213
2214 return $merchants;
2215 }
2216
2217 /**
2218 * Returns a $response array containing:
2219 * - ids: the query passed to the function.
2220 * - products: array of products.
2221 * - last_status: value of $api->lastStatus().
2222 * - found_count: value of $search->getFoundCount().
2223 *
2224 * If the API throws an exception, that will return dfrapi_api_error( $err );
2225 *
2226 * @param array $ids An array of product IDs.
2227 * @param int $ppp The number of products to return in 1 API request. Max is dictated by API, not plugin.
2228 * @param int $page The page number for returning products. This is used to figure the offset.
2229 */
2230 function dfrapi_api_get_products_by_id( $ids, $ppp = 20, $page = 1 ) {
2231
2232 $response = array();
2233
2234 // Return false if no $ids or no $postid
2235 if ( empty( $ids ) ) {
2236 return $response;
2237 }
2238
2239 // Make sure $page is a positive integer.
2240 $page = absint( $page );
2241
2242 // Make sure $ppp is a positive integer.
2243 $ppp = absint( $ppp );
2244
2245 // Make sure $ppp is not greater than "max_length".
2246 $account = (array) get_option( 'dfrapi_account' );
2247 if ( $ppp > $account['max_length'] ) {
2248 $ppp = $account['max_length'];
2249 }
2250
2251 // The maximum number of results a request to the API can return.
2252 // Changing this will only break your site. It's not overridable.
2253 $max_total = $account['max_total'];
2254
2255 // Determine offset.
2256 $offset = ( ( $page - 1 ) * $ppp );
2257
2258 // Make sure $limit doesn't go over 10,000.
2259 if ( ( $offset + $ppp ) > $max_total ) {
2260 $ppp = ( $max_total - $offset );
2261 }
2262
2263 // If $ppp is negative, return empty array();
2264 if ( $ppp < 1 ) {
2265 return array();
2266 }
2267
2268 // If offset is greater than 10,000 return empty array();
2269 if ( $offset >= ( $max_total - $ppp ) ) {
2270 return array();
2271 }
2272
2273 try {
2274
2275 // Initialize API.
2276 $api = dfrapi_api( dfrapi_get_transport_method() );
2277 if ( ! $api ) {
2278 return $response;
2279 }
2280
2281 // Get a range of product IDs to query.
2282 $id_range = array_slice( $ids, $offset, $ppp );
2283
2284 // Return immediately if $id_range is empty.
2285 if ( empty( $id_range ) ) {
2286 $response['ids'] = array();
2287 $response['products'] = array();
2288 $response['last_status'] = $api->lastStatus();
2289 $response['found_count'] = 0;
2290
2291 return $response;
2292 }
2293
2294 // Begin query
2295 $search = $api->searchRequest();
2296
2297 // Get filters
2298 $filters = dfrapi_api_query_to_filters( array() );
2299 if ( isset( $filters['error'] ) ) {
2300 throw new DatafeedrError( $filters['error'], 0 );
2301 }
2302
2303 // Loop thru filters.
2304 foreach ( $filters as $filter ) {
2305 $search->addFilter( $filter );
2306 }
2307
2308 $search->addFilter( 'id IN ' . implode( ",", $id_range ) );
2309 $search->setLimit( $ppp );
2310 $products = $search->execute();
2311
2312 // Keep track of IDs which were returned via the API to compare with $id_range (unreturned)
2313 $included_ids = array();
2314 if ( ! empty( $products ) ) {
2315 foreach ( $products as $product ) {
2316 $included_ids[] = $product['_id'];
2317 }
2318 }
2319
2320 // Excluded product IDs.
2321 $excluded_ids = array_diff( $id_range, $included_ids );
2322
2323 // Add "message" values to excluded IDs if there are some.
2324 $excluded_products = array();
2325 if ( ! empty( $included_ids ) && ! empty( $excluded_ids ) ) {
2326 foreach ( $excluded_ids as $excluded_id ) {
2327
2328 $wc_url = add_query_arg(
2329 array(
2330 's' => $excluded_id,
2331 'post_status' => 'trash',
2332 'post_type' => 'product',
2333 ),
2334 admin_url( 'edit.php' )
2335 );
2336
2337 // Do not add a 'url' field to this array or the unavailable product WILL be imported.
2338 // See /datafeedr-product-sets/classes/class-dfrps-update.php:73
2339 $excluded_products[] = array(
2340 '_id' => $excluded_id,
2341 '_wc_url' => $wc_url,
2342 'name' => $excluded_id . ' - ' . __( 'Unavailable', 'datafeedr-api' ),
2343 'price' => 0,
2344 'finalprice' => 0,
2345 'description' => __( 'This product is either temporarily or permanently unavailable.', 'datafeedr-api' ),
2346 'image' => DFRAPI_URL . 'images/icons/noimage.png',
2347 'merchant' => 'n/a',
2348 'source' => 'n/a',
2349 );
2350 }
2351 }
2352
2353 // Update API status
2354 dfrapi_api_update_status( $api );
2355
2356 // Build $response array().
2357 $response['ids'] = $ids;
2358 $response['products'] = array_merge( $products, $excluded_products );
2359 $response['last_status'] = $api->lastStatus();
2360 $response['found_count'] = count( $ids );
2361 $response['params'] = $search->getParams();
2362 $response['score'] = $search->getQueryScore();
2363
2364 // Return it!
2365 return $response;
2366
2367 } catch ( Exception $err ) {
2368 return dfrapi_api_error( $err );
2369 }
2370 }
2371
2372 /**
2373 * Returns a $response array containing:
2374 * - query: the query passed to the function.
2375 * - excluded: ids of excluded products.
2376 * - products: array of products.
2377 * - last_status: value of $api->lastStatus().
2378 * - found_count: value of $search->getFoundCount().
2379 * - params: value of $search->getParams().
2380 *
2381 * Example of $query array():
2382 *
2383 *
2384 * $query[] = array(
2385 * 'value' => 'shoes',
2386 * 'field' => 'any',
2387 * 'operator' => 'contain'
2388 * );
2389 *
2390 * $query[] = array(
2391 * 'value' => 'image',
2392 * 'field' => 'duplicates',
2393 * 'operator' => 'is'
2394 * );
2395 *
2396 * $query[] = array(
2397 * 'field' => 'sort',
2398 * 'operator' => '+saleprice'
2399 * );
2400 *
2401 *
2402 * If the API throws an exception, that will return dfrapi_api_error( $err, $params );
2403 *
2404 * @param array $query The complete query to pass to the API.
2405 * @param int $ppp The number of products to return in 1 API request. Max is dictated by API, not plugin.
2406 * @param int $page The page number for returning products. This is used to figure the offset.
2407 * @param array $excluded An array of product IDs to exclude from being returned.
2408 */
2409 function dfrapi_api_get_products_by_query( $query, $ppp = 20, $page = 1, $excluded = array() ) {
2410
2411 $response = array();
2412
2413 // Return false if no $query.
2414 if ( empty( $query ) ) {
2415 return $response;
2416 }
2417
2418 // Make sure $page is a positive integer.
2419 $page = absint( $page );
2420
2421 // Make sure $ppp is a positive integer.
2422 $ppp = absint( $ppp );
2423
2424 // Make sure $ppp is not greater than "max_length".
2425 $account = (array) get_option( 'dfrapi_account' );
2426 if ( $ppp > $account['max_length'] ) {
2427 $ppp = $account['max_length'];
2428 }
2429
2430 // The maximum number of results a request to the API can return.
2431 // Changing this will only break your site. It's not overridable.
2432 $max_total = $account['max_total'];
2433
2434 // Determine query limit (if exists).
2435 $query_limit = dfrapi_api_get_query_param( $query, 'limit' );
2436 $query_limit = ( $query_limit )
2437 ? $query_limit['value']
2438 : false;
2439
2440 // No query shall try to return more than 10,000 products.
2441 if ( $query_limit && ( $query_limit > $max_total ) ) {
2442 $query_limit = $max_total;
2443 }
2444
2445 // Determine merchant limit (if exists).
2446 $merchant_limit = dfrapi_api_get_query_param( $query, 'merchant_limit' );
2447 $merchant_limit = ( $merchant_limit )
2448 ? absint( $merchant_limit['value'] )
2449 : 0;
2450
2451 // Determine offset.
2452 $offset = ( ( $page - 1 ) * $ppp );
2453
2454 // If offset is greater than 10,000 return empty array();
2455 if ( $offset >= $max_total ) {
2456 return array();
2457 }
2458
2459 // Factor in query limit
2460 if ( $query_limit ) {
2461 if ( ( $ppp + $offset ) > $query_limit ) {
2462 $ppp = ( $query_limit - $offset );
2463 }
2464 }
2465
2466 // Make sure $limit doesn't go over 10,000.
2467 if ( ( $offset + $ppp ) > $max_total ) {
2468 $ppp = ( $max_total - $offset );
2469 }
2470
2471 // If $ppp is negative, return empty array();
2472 if ( $ppp < 1 ) {
2473 return $response;
2474 }
2475
2476 try {
2477
2478 // Initialize API.
2479 $api = dfrapi_api( dfrapi_get_transport_method() );
2480 if ( ! $api ) {
2481 return $response;
2482 }
2483
2484 $search = $api->searchRequest();
2485
2486 // Get filters
2487 $filters = dfrapi_api_query_to_filters( $query );
2488 if ( isset( $filters['error'] ) ) {
2489 throw new DatafeedrError( $filters['error'], 0 );
2490 }
2491
2492 // Loop thru filters.
2493 foreach ( $filters as $filter ) {
2494 $search->addFilter( $filter );
2495 }
2496
2497 // Exclude duplicates.
2498 $duplicates = dfrapi_api_get_query_param( $query, 'duplicates' );
2499 if ( $duplicates ) {
2500 $excludes = $duplicates['value'];
2501 $search->excludeDuplicates( $excludes );
2502 }
2503
2504 // Exclude blocked products.
2505 $excluded = (array) $excluded;
2506 if ( ! empty( $excluded ) ) {
2507 $search->addFilter( 'id !IN ' . implode( ",", $excluded ) );
2508 }
2509
2510 // Sort products.
2511 $sort = dfrapi_api_get_query_param( $query, 'sort' );
2512 if ( $sort && strlen( $sort['operator'] ) ) {
2513 $search->addSort( $sort['operator'] );
2514 }
2515
2516 // Set Merchant Limit
2517 $search->setMerchantLimit( $merchant_limit );
2518
2519 // Set limits and offset.
2520 $search->setLimit( $ppp );
2521 $search->setOffset( $offset );
2522
2523 // Execute query.
2524 $products = $search->execute();
2525
2526 // Update API status
2527 dfrapi_api_update_status( $api );
2528
2529 // Build $response array().
2530 $response['query'] = $query;
2531 $response['excluded'] = $excluded;
2532 $response['products'] = $products;
2533 $response['last_status'] = $api->lastStatus();
2534 $response['found_count'] = $search->getResultCount();
2535 $response['params'] = $search->getParams();
2536 $response['score'] = $search->getQueryScore();
2537
2538 // Return it!
2539 return $response;
2540
2541 } catch ( Exception $err ) {
2542 $params = $search->getParams();
2543
2544 return dfrapi_api_error( $err, $params );
2545
2546 }
2547 }
2548
2549 /**
2550 * Returns the URL to get the Effiliation product feeds URL with user's API injected into URL.
2551 *
2552 * @param string $api_key
2553 *
2554 * @return string
2555 */
2556 function dfrapi_get_effiliation_product_feeds_url( string $api_key ): string {
2557 return sprintf( 'https://apiv2.effiliation.com/apiv2/productfeeds.xml?key=%s&filter=mines&type=33&fields=0001010000110001', $api_key );
2558 }
2559
2560 /**
2561 * Get affiliate IDs from Effiliation.
2562 *
2563 * @param $api_key
2564 *
2565 * @return array|mixed|SimpleXMLElement|WP_Error
2566 */
2567 function dfrapi_request_effiliation_affiliate_ids( $api_key = null ) {
2568
2569 $option_name = 'effiliation_affiliate_ids';
2570 $use_cache = wp_using_ext_object_cache( false );
2571 $affiliate_ids = get_transient( $option_name );
2572 wp_using_ext_object_cache( $use_cache );
2573
2574 if ( $affiliate_ids ) {
2575 return $affiliate_ids;
2576 }
2577
2578 $keys = dfrapi_get_effiliation_keys();
2579 $api_key = $api_key ?: $keys['effiliation_key'];
2580 $method = 'GET';
2581 $url = dfrapi_get_effiliation_product_feeds_url( $api_key );
2582
2583 $xml = dfrapi_get_xml_response( $url, $method, [ 'timeout' => 30 ] );
2584
2585 if ( is_wp_error( $xml ) ) {
2586 return $xml;
2587 }
2588
2589 $affiliate_ids = [];
2590
2591 foreach ( $xml->feed as $e ) {
2592 $item = json_decode( json_encode( $e ), true );
2593 $suid = sanitize_text_field( $item['id_affilieur'] );
2594
2595 $affiliate_ids[ $suid ]['suid'] = ( $suid );
2596 $affiliate_ids[ $suid ]['affiliate_id'] = sanitize_text_field( $item['id_compteur'] );
2597 }
2598
2599 $use_cache = wp_using_ext_object_cache( false );
2600 set_transient( $option_name, $affiliate_ids, ( MINUTE_IN_SECONDS * 20 ) );
2601 wp_using_ext_object_cache( $use_cache );
2602 dfrapi_update_transient_whitelist( $option_name );
2603
2604 return $affiliate_ids;
2605 }
2606
2607 /**
2608 * @param $merchant_id
2609 *
2610 * @return mixed|string
2611 * @throws Exception
2612 */
2613 function dfrapi_get_affiliate_id_for_effiliation_merchant( $merchant_id ) {
2614 $merchants = dfrapi_api_get_merchants_by_id( $merchant_id );
2615 $merchant = $merchants[0] ?? [ 'suids' => '' ];
2616 $affiliate_ids = dfrapi_request_effiliation_affiliate_ids();
2617
2618 if ( is_wp_error( $affiliate_ids ) ) {
2619 throw new Exception( 'Unable to query Effiliation at this time. Please try again in 15 minutes.' );
2620 }
2621
2622 if ( ! isset( $affiliate_ids[ $merchant['suids'] ]['affiliate_id'] ) ) {
2623 throw new Exception( 'Suid does not exist for affiliate ID.' );
2624 }
2625
2626 return $affiliate_ids[ $merchant['suids'] ]['affiliate_id'];
2627 }
2628
2629 /**
2630 * An array of data about the user's Datafeedr account. Formatted like:
2631 *
2632 * Array (
2633 * [network_count] => 227
2634 * [plan_id] => 30600000
2635 * [user_id] => 70123
2636 * [max_total] => 10000
2637 * [merchant_count] => 84031
2638 * [max_requests] => 100000
2639 * [bill_day] => 25
2640 * [request_count] => 11061
2641 * [product_count] => 797373259
2642 * [max_length] => 100
2643 * )
2644 *
2645 * @return array
2646 */
2647 function dfrapi_get_user_account_data(): array {
2648 return (array) get_option( 'dfrapi_account', [] );
2649 }
2650
2651 /**
2652 * Returns the total number of networks in the Datafeedr API.
2653 *
2654 * @return int
2655 */
2656 function dfrapi_get_network_count(): int {
2657 $data = dfrapi_get_user_account_data();
2658
2659 return absint( $data['network_count'] ?? 0 );
2660 }
2661
2662 /**
2663 * Returns the total number of merchants in the Datafeedr API.
2664 *
2665 * @return int
2666 */
2667 function dfrapi_get_merchant_count(): int {
2668 $data = dfrapi_get_user_account_data();
2669
2670 return absint( $data['merchant_count'] ?? 0 );
2671 }
2672
2673 /**
2674 * Returns the total number of products in the Datafeedr API.
2675 *
2676 * @return int
2677 */
2678 function dfrapi_get_product_count(): int {
2679 $data = dfrapi_get_user_account_data();
2680
2681 return absint( $data['product_count'] ?? 0 );
2682 }
2683
2684 /**
2685 * The maximum number of API requests the user is allowed to make during a single subscription period (i.e. 30 days).
2686 *
2687 * @return int
2688 */
2689 function dfrapi_get_max_requests(): int {
2690 $data = dfrapi_get_user_account_data();
2691
2692 return absint( $data['max_requests'] ?? 0 );
2693 }
2694
2695 /**
2696 * The current number of API requests the user has made during the current subscription period (i.e. 30 days).
2697 *
2698 * @return int
2699 */
2700 function dfrapi_get_request_count(): int {
2701 $data = dfrapi_get_user_account_data();
2702
2703 return absint( $data['request_count'] ?? 0 );
2704 }
2705
2706 /**
2707 * Returns the user's API requests usage as a percentage of their total requests allowed.
2708 *
2709 * @param int $precision Default: 2
2710 *
2711 * @return float|int
2712 */
2713 function dfrapi_get_api_usage_as_percentage( int $precision = 2 ) {
2714 $max_requests = dfrapi_get_max_requests();
2715 $request_count = dfrapi_get_request_count();
2716
2717 return $max_requests > 0 ? round( ( $request_count / $max_requests * 100 ), $precision ) : 0;
2718 }
2719
2720 /**
2721 * Returns an array of network IDs for the Partnerize affiliate network.
2722 *
2723 * @return int[]
2724 */
2725 function dfrapi_get_partnerize_network_ids(): array {
2726 return [ 801, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823 ];
2727 }
2728
2729 /**
2730 * Returns the Group ID for Partnerize.
2731 *
2732 * @return int
2733 */
2734 function dfrapi_get_partnerize_group_id(): int {
2735 return 10027;
2736 }
2737
2738 /**
2739 * Returns an array of network IDs for the Effiliation affiliate network.
2740 *
2741 * @return int[]
2742 */
2743 function dfrapi_get_effiliation_network_ids(): array {
2744 return [ 805, 806, 807 ];
2745 }
2746
2747 /**
2748 * Returns the Group ID for Effiliation.
2749 *
2750 * @return int
2751 */
2752 function dfrapi_get_effiliation_group_id(): int {
2753 return 10017;
2754 }
2755
2756 /**
2757 * Returns the Group ID for Belboon.
2758 *
2759 * @return int
2760 */
2761 function dfrapi_get_belboon_group_id(): int {
2762 return 10007;
2763 }
2764
2765 /**
2766 * Get the affiliate ID for a specific network.
2767 *
2768 * @param int $network_id
2769 * @param mixed $default
2770 *
2771 * @return mixed|string Returns the affiliate ID if found otherwise it returns the value of $default.
2772 */
2773 function dfrapi_get_affiliate_id_by_network_id( int $network_id, $default = false ) {
2774
2775 static $network_ids = null;
2776
2777 if ( $network_ids === null ) {
2778
2779 $network_ids = [];
2780
2781 $networks = dfrapi_get_selected_networks();
2782
2783 if ( isset( $networks['ids'] ) && is_array( $networks['ids'] ) && ! empty( $networks['ids'] ) ) {
2784 $network_ids = $networks['ids'];
2785 }
2786 }
2787
2788 foreach ( $network_ids as $k => $v ) {
2789 $nid = absint( $v['nid'] ?? 0 );
2790 if ( $nid === $network_id ) {
2791 $aid = trim( $v['aid'] ?? '' );
2792
2793 return ! empty( $aid ) ? $aid : $default;
2794 }
2795 }
2796
2797 return $default;
2798 }
2799
2800 /**
2801 * Get one or more fields from a Datafeedr Product array.
2802 *
2803 * @since 1.3.1
2804 *
2805 * @param array $product A Datafeedr Product array (as returned from Datafeedr API).
2806 * @param string|array $fields A single field or an array of fields to return. Examples:
2807 * - 'barcode'
2808 * - ['barcode']
2809 * - ['barcode', 'ean']
2810 * @param mixed $default Value to return if no fields are found in $product array. Default: null
2811 * @param false|string $concatenate False to return the first field found or a separator to concatenate all found fields.
2812 *
2813 * @return mixed
2814 */
2815 function dfrapi_get_fields_from_product( array $product, $fields, $default = null, $concatenate = false ) {
2816
2817 if ( ! is_string( $fields ) && ! is_array( $fields ) ) {
2818 return $default;
2819 }
2820
2821 if ( is_string( $fields ) ) {
2822 $fields = [ (string) $fields ];
2823 }
2824
2825 $fields = array_filter( $fields );
2826
2827 if ( empty( $fields ) ) {
2828 return $default;
2829 }
2830
2831 if ( count( $fields ) === 1 ) {
2832 return $product[ $fields[0] ] ?? $default;
2833 }
2834
2835 $values = [];
2836
2837 foreach ( $fields as $field ) {
2838 if ( isset( $product[ $field ] ) ) {
2839 $values[] = $product[ $field ];
2840 }
2841 }
2842
2843 if ( empty( $values ) ) {
2844 return $default;
2845 }
2846
2847 return is_string( $concatenate ) ? implode( $concatenate, $values ) : $values[0];
2848 }
2849
2850 /**
2851 * Returns a URL to install a plugin from the WordPress.org repo.
2852 *
2853 * @since 1.3.1
2854 *
2855 * @param string $plugin Path to the plugin file relative to the plugin's directory. Ex: datafeedr-api/datafeedr-api.php
2856 *
2857 * @return string
2858 */
2859 function dfrapi_get_install_plugin_url( string $plugin ): string {
2860
2861 if ( ! current_user_can( 'install_plugins' ) ) {
2862 return admin_url( 'plugins.php' );
2863 }
2864
2865 return add_query_arg(
2866 [ 'action' => 'install-plugin', 'plugin' => dfrapi_parse_plugin_path( $plugin, 'dirname' ) ],
2867 wp_nonce_url( admin_url( 'update.php' ), 'install-plugin_' . dfrapi_parse_plugin_path( $plugin, 'dirname' ) )
2868 );
2869 }
2870
2871 /**
2872 * Returns a URL to activate a plugin.
2873 *
2874 * @since 1.3.1
2875 *
2876 * @param string $plugin Path to the plugin file relative to the plugin's directory. Ex: datafeedr-api/datafeedr-api.php
2877 *
2878 * @return string
2879 */
2880 function dfrapi_get_activate_plugin_url( string $plugin ): string {
2881
2882 if ( ! current_user_can( 'activate_plugins' ) ) {
2883 return admin_url( 'plugins.php' );
2884 }
2885
2886 return add_query_arg(
2887 [ 'action' => 'activate', 'plugin' => dfrapi_parse_plugin_path( $plugin ), 'paged' => '1', 's' => '' ],
2888 wp_nonce_url( network_admin_url( 'plugins.php' ), 'activate-plugin_' . dfrapi_parse_plugin_path( $plugin ) )
2889 );
2890 }
2891
2892 /**
2893 * Returns true if plugin is installed. Otherwise, returns false.
2894 *
2895 * @since 1.3.1
2896 *
2897 * @param string $plugin Path to the plugin file relative to the plugin's directory. Ex: datafeedr-api/datafeedr-api.php
2898 *
2899 * @return bool
2900 */
2901 function dfrapi_plugin_is_installed( string $plugin ): bool {
2902 return file_exists( dfrapi_parse_plugin_path( $plugin, 'absolute' ) );
2903 }
2904
2905 /**
2906 * This function parses and sanitizes a plugin path and returns it in the desired format.
2907 *
2908 * @since 1.3.1
2909 *
2910 * @param string $plugin Path to the plugin file relative to the plugin's directory. Ex: datafeedr-api/datafeedr-api.php
2911 * @param string $format The format in which to return the plugin info.
2912 *
2913 * @return string
2914 */
2915 function dfrapi_parse_plugin_path( string $plugin, string $format = 'relative' ): string {
2916
2917 // For examples below, if $plugin equals = "hello-dolly/hello.php"...
2918 $valid_formats = [
2919 'absolute', // /home/public_html/user/wp-content/plugins/hello-dolly/hello.php
2920 'relative', // hello-dolly/hello.php
2921 'dirname', // hello-dolly
2922 'basename', // hello.php
2923 'filename', // hello
2924 'extension', // php
2925 ];
2926
2927 $path = pathinfo( $plugin );
2928
2929 $dirname = sanitize_file_name( $path['dirname'] ?? '' );
2930 $basename = sanitize_file_name( $path['basename'] ?? '' );
2931 $extension = sanitize_file_name( $path['extension'] ?? '' );
2932 $filename = sanitize_file_name( $path['filename'] ?? '' );
2933
2934 $format = in_array( $format, $valid_formats, true ) ? $format : 'relative';
2935
2936 if ( $format === 'absolute' ) {
2937 return trailingslashit( WP_PLUGIN_DIR ) . trailingslashit( $dirname ) . $basename;
2938 }
2939
2940 if ( $format === 'dirname' ) {
2941 return $dirname;
2942 }
2943
2944 if ( $format === 'basename' ) {
2945 return $basename;
2946 }
2947
2948 if ( $format === 'filename' ) {
2949 return $filename;
2950 }
2951
2952 if ( $format === 'extension' ) {
2953 return $extension;
2954 }
2955
2956 return empty( $dirname ) ? $basename : trailingslashit( $dirname ) . $basename;
2957 }
2958
2959 /**
2960 * Returns an array of Network IDs which should be considered inactive.
2961 *
2962 * @since 1.3.8
2963 *
2964 * @return array
2965 */
2966 function dfrapi_inactive_networks(): array {
2967
2968 $inactive_network_ids = [
2969 14, // Prophetably
2970 ];
2971
2972 return array_map( 'absint', apply_filters( 'dfrapi_inactive_networks', $inactive_network_ids ) );
2973 }
2974
2975 /**
2976 * Returns the Amazon API to use.
2977 *
2978 * @since 1.4.0
2979 *
2980 * @return string The Amazon API to use.
2981 */
2982 function dfrapi_get_amazon_api(): string {
2983 $configuration = (array) get_option( 'dfrapi_configuration' );
2984
2985 return $configuration['amazon_api'] ?? '';
2986 }
2987
2988 /**
2989 * Returns an array of Amazon Creator API regions.
2990 *
2991 * @since 1.4.0
2992 *
2993 * @return array The Amazon Creator API regions.
2994 */
2995 function dfrapi_get_capi_regions(): array {
2996
2997 $regions = [];
2998
2999 $regions['NA'] = [
3000 'code' => 'NA',
3001 'name' => 'North America',
3002 'version' => '2.1',
3003 'token_endpoint' => 'creatorsapi.auth.us-east-1.amazoncognito.com/oauth2/token',
3004 ];
3005
3006 $regions['EU'] = [
3007 'code' => 'EU',
3008 'name' => 'Europe',
3009 'version' => '2.2',
3010 'token_endpoint' => 'creatorsapi.auth.eu-south-2.amazoncognito.com/oauth2/token',
3011 ];
3012
3013 $regions['FE'] = [
3014 'code' => 'FE',
3015 'name' => 'Far East',
3016 'version' => '2.3',
3017 'token_endpoint' => 'creatorsapi.auth.us-west-2.amazoncognito.com/oauth2/token',
3018 ];
3019
3020 return $regions;
3021 }
3022
3023 /**
3024 * Returns an Amazon Creator API region by its code.
3025 *
3026 * @since 1.4.0
3027 *
3028 * @param string $code The region code (Ex. NA, EU, FE).
3029 *
3030 * @return array|WP_Error The region data or WP_Error if the code is invalid.
3031 */
3032 function dfrapi_get_capi_region( string $code ) {
3033 $code = strtoupper( trim( $code ) );
3034 $regions = dfrapi_get_capi_regions();
3035
3036 return $regions[ $code ] ?? new WP_Error( 'invalid_capi_region_code', 'Invalid Creator API region code.', [ 'code' => $code ] );
3037 }
3038
3039 /**
3040 * Returns an array of Amazon Creator API marketplaces.
3041 *
3042 * @since 1.4.0
3043 *
3044 * @return array The Amazon Creator API marketplaces.
3045 */
3046 function dfrapi_get_capi_marketplaces(): array {
3047
3048 $regions = dfrapi_get_capi_regions();
3049
3050 $marketplaces = [];
3051 $marketplaces['AU'] = [
3052 'locale' => 'Australia',
3053 'domain' => 'www.amazon.com.au',
3054 'region' => $regions['FE'],
3055 ];
3056
3057 $marketplaces['BE'] = [
3058 'locale' => 'Belgium',
3059 'domain' => 'www.amazon.com.be',
3060 'region' => $regions['EU'],
3061 ];
3062
3063 $marketplaces['BR'] = [
3064 'locale' => 'Brazil',
3065 'domain' => 'www.amazon.com.br',
3066 'region' => $regions['NA'],
3067 ];
3068
3069 $marketplaces['CA'] = [
3070 'locale' => 'Canada',
3071 'domain' => 'www.amazon.ca',
3072 'region' => $regions['NA'],
3073 ];
3074
3075 $marketplaces['EG'] = [
3076 'locale' => 'Egypt',
3077 'domain' => 'www.amazon.eg',
3078 'region' => $regions['EU'],
3079 ];
3080
3081 $marketplaces['FR'] = [
3082 'locale' => 'France',
3083 'domain' => 'www.amazon.fr',
3084 'region' => $regions['EU'],
3085 ];
3086
3087 $marketplaces['DE'] = [
3088 'locale' => 'Germany',
3089 'domain' => 'www.amazon.de',
3090 'region' => $regions['EU'],
3091 ];
3092
3093 $marketplaces['IN'] = [
3094 'locale' => 'India',
3095 'domain' => 'www.amazon.in',
3096 'region' => $regions['EU'],
3097 ];
3098
3099 $marketplaces['IE'] = [
3100 'locale' => 'Ireland',
3101 'domain' => 'www.amazon.ie',
3102 'region' => $regions['EU'],
3103 ];
3104
3105 $marketplaces['IT'] = [
3106 'locale' => 'Italy',
3107 'domain' => 'www.amazon.it',
3108 'region' => $regions['EU'],
3109 ];
3110
3111 $marketplaces['JP'] = [
3112 'locale' => 'Japan',
3113 'domain' => 'www.amazon.co.jp',
3114 'region' => $regions['FE'],
3115 ];
3116
3117 $marketplaces['MX'] = [
3118 'locale' => 'Mexico',
3119 'domain' => 'www.amazon.com.mx',
3120 'region' => $regions['NA'],
3121 ];
3122
3123 $marketplaces['NL'] = [
3124 'locale' => 'Netherlands',
3125 'domain' => 'www.amazon.nl',
3126 'region' => $regions['EU'],
3127 ];
3128
3129 $marketplaces['PL'] = [
3130 'locale' => 'Poland',
3131 'domain' => 'www.amazon.pl',
3132 'region' => $regions['EU'],
3133 ];
3134
3135 $marketplaces['SG'] = [
3136 'locale' => 'Singapore',
3137 'domain' => 'www.amazon.sg',
3138 'region' => $regions['FE'],
3139 ];
3140
3141 $marketplaces['SA'] = [
3142 'locale' => 'Saudi Arabia',
3143 'domain' => 'www.amazon.sa',
3144 'region' => $regions['EU'],
3145 ];
3146
3147 $marketplaces['ES'] = [
3148 'locale' => 'Spain',
3149 'domain' => 'www.amazon.es',
3150 'region' => $regions['EU'],
3151 ];
3152
3153 $marketplaces['SE'] = [
3154 'locale' => 'Sweden',
3155 'domain' => 'www.amazon.se',
3156 'region' => $regions['EU'],
3157 ];
3158
3159 $marketplaces['TR'] = [
3160 'locale' => 'Turkey',
3161 'domain' => 'www.amazon.com.tr',
3162 'region' => $regions['EU'],
3163 ];
3164
3165 $marketplaces['AE'] = [
3166 'locale' => 'United Arab Emirates',
3167 'domain' => 'www.amazon.ae',
3168 'region' => $regions['EU'],
3169 ];
3170
3171 $marketplaces['UK'] = [
3172 'locale' => 'United Kingdom',
3173 'domain' => 'www.amazon.co.uk',
3174 'region' => $regions['EU'],
3175 ];
3176
3177 $marketplaces['US'] = [
3178 'locale' => 'United States',
3179 'domain' => 'www.amazon.com',
3180 'region' => $regions['NA'],
3181 ];
3182
3183 return $marketplaces;
3184 }
3185
3186 /**
3187 * Returns an Amazon Creator API marketplace by its code.
3188 *
3189 * @since 1.4.0
3190 *
3191 * @param string $code The marketplace code (Ex. US, CA, UK, etc.).
3192 *
3193 * @return array|WP_Error The marketplace data or WP_Error if the code is invalid.
3194 */
3195 function dfrapi_get_capi_marketplace( string $code ) {
3196 $code = strtoupper( trim( $code ) );
3197 $marketplaces = dfrapi_get_capi_marketplaces();
3198
3199 return $marketplaces[ $code ] ?? new WP_Error( 'invalid_capi_marketplace_code', 'Invalid Creator API marketplace code.', [ 'code' => $code ] );
3200 }
3201
3202 /**
3203 * Returns Creator API credentials.
3204 *
3205 * @return array {
3206 * Creator API credentials.
3207 *
3208 * @type string $id Creator API credential ID.
3209 * @type string $secret Creator API credential secret.
3210 * @type string $marketplace Marketplace code (Ex. US, CA, UK, DE, etc...)
3211 * @type string $partner_tag Partner tag (Ex. xyz-20).
3212 * @type string $version Version (2.1, 2.2 or 2.3).
3213 * @type string $endpoint Access token generation endpoint URL.
3214 * }
3215 */
3216 function dfrapi_get_capi_credentials(): array {
3217
3218 $credentials = [];
3219 $configuration = (array) get_option( 'dfrapi_configuration' );
3220
3221 $credentials['id'] = trim( $configuration['capi_credential_id'] ?? '' );
3222 $credentials['secret'] = trim( $configuration['capi_credential_secret'] ?? '' );
3223 $credentials['partner_tag'] = trim( $configuration['capi_partner_tag'] ?? '' );
3224 $credentials['marketplace'] = trim( $configuration['capi_marketplace'] ?? 'US' );
3225
3226 $marketplace = dfrapi_get_capi_marketplace( $credentials['marketplace'] );
3227
3228 if ( ! is_wp_error( $marketplace ) ) {
3229 $credentials['version'] = $marketplace['region']['version'];
3230 $credentials['endpoint'] = $marketplace['region']['token_endpoint'];
3231 }
3232
3233 return $credentials;
3234 }
3235
3236 /**
3237 * Checks if Amazon Creator API credentials exist.
3238 *
3239 * @since 1.4.0
3240 *
3241 * @return bool True if credentials exist, false otherwise.
3242 */
3243 function dfrapi_capi_credentials_exist(): bool {
3244
3245 $credentials = dfrapi_get_capi_credentials();
3246
3247 $keys = [ 'id', 'secret', 'partner_tag', 'version', 'endpoint', 'marketplace' ];
3248
3249 foreach ( $keys as $key ) {
3250 if ( empty( $credentials[ $key ] ) ) {
3251 return false;
3252 }
3253 }
3254
3255 return true;
3256 }
3257
3258 /**
3259 * Returns an Amazon Creator API access token.
3260 *
3261 * @since 1.4.0
3262 *
3263 * @return string|WP_Error The access token or WP_Error on failure.
3264 */
3265 function dfrapi_get_capi_access_token() {
3266
3267 $transient_key = 'dfrapi_capi_access_token';
3268
3269 $capi_access_token = get_transient( $transient_key );
3270
3271 if ( is_string( $capi_access_token ) && $capi_access_token !== '' ) {
3272 return $capi_access_token;
3273 }
3274
3275 if ( ! dfrapi_capi_credentials_exist() ) {
3276 return new WP_Error( 'missing_capi_credentials', 'Missing Amazon Creators API credentials' );
3277 }
3278
3279 $credentials = dfrapi_get_capi_credentials();
3280
3281 $response = wp_remote_request(
3282 'https://' . $credentials['endpoint'],
3283 [
3284 'method' => 'POST',
3285 'headers' => [
3286 'Content-Type' => 'application/x-www-form-urlencoded',
3287 ],
3288 'body' => [
3289 'grant_type' => 'client_credentials',
3290 'client_id' => $credentials['id'],
3291 'client_secret' => $credentials['secret'],
3292 'scope' => 'creatorsapi/default',
3293 ],
3294 'timeout' => 15,
3295 ]
3296 );
3297
3298 if ( is_wp_error( $response ) ) {
3299 error_log( $response->get_error_message() );
3300
3301 return $response;
3302 }
3303
3304 $status = wp_remote_retrieve_response_code( $response );
3305 $body = wp_remote_retrieve_body( $response );
3306
3307 /**
3308 * [
3309 * "access_token" => "eyJra.....YXE3A",
3310 * "expires_in" => 3600,
3311 * "token_type" => "Bearer",
3312 * ]
3313 */
3314 $data = json_decode( $body, true );
3315
3316 if ( 200 !== $status || empty( $data['access_token'] ) ) {
3317 $error_message = isset( $data['error'] ) ? $data['error'] : 'Failed to retrieve CAPI access token (HTTP ' . $status . ')';
3318 error_log( '[Datafeedr CAPI] Token error: ' . $error_message );
3319
3320 return new WP_Error( 'capi_token_error', $error_message );
3321 }
3322
3323 $capi_access_token = $data['access_token'];
3324 $capi_expires_in = (int) $data['expires_in'];
3325
3326 set_transient(
3327 $transient_key,
3328 $capi_access_token,
3329 max( 60, $capi_expires_in - 60 ) // refresh 1 minute early
3330 );
3331
3332 return $capi_access_token;
3333 }
3334
3335 /**
3336 * Returns a value from a multi-dimensional array using dot notation.
3337 *
3338 * @since 1.4.0
3339 *
3340 * @param array $array The array to search.
3341 * @param string $path The path to the value using dot notation.
3342 * @param mixed $default The default value to return if the path is not found.
3343 *
3344 * @return mixed The value from the array or the default value.
3345 */
3346 function dfrapi_array_get_dot( array $array, string $path, $default = null ) {
3347
3348 foreach ( explode( '.', $path ) as $key ) {
3349
3350 if ( ! is_array( $array ) || ! array_key_exists( $key, $array ) ) {
3351 return $default;
3352 }
3353
3354 $array = $array[ $key ];
3355 }
3356
3357 return $array;
3358 }
3359
3360 /**
3361 * Transforms a CAPI item array into a Datafeedr product array.
3362 *
3363 * @since 1.4.0
3364 *
3365 * @param array $item The CAPI item data.
3366 *
3367 * @return array The transformed Datafeedr product array.
3368 */
3369 function dfrapi_transform_capi_item_into_datafeedr_product_array( array $item ): array {
3370
3371 $product = [];
3372 $asin = dfrapi_array_get_dot( $item, 'asin', '' );
3373
3374 // Hard-coded values.
3375 $product['id'] = 7777 . $asin;
3376 $product['v5_id'] = 7777 . $asin;
3377 $product['network_id'] = 7777;
3378 $product['source_id'] = 7777;
3379 $product['merchant_id'] = 7777;
3380 $product['asin'] = $asin;
3381 $product['v5_suid'] = $asin;
3382 $product['network'] = 'Amazon';
3383 $product['source'] = 'Amazon';
3384 $product['merchant'] = 'Amazon';
3385 $product['time_added'] = date_i18n( 'Y-m-d H:i:s' );
3386 $product['time_updated'] = date_i18n( 'Y-m-d H:i:s' );
3387 $product['iscommissionable'] = 1;
3388
3389 // Specific item values.
3390 $product['name'] = dfrapi_array_get_dot( $item, 'itemInfo.title.displayValue' );
3391 $product['description'] = dfrapi_array_get_dot( $item, 'itemInfo.title.displayValue' );
3392
3393 $features = dfrapi_array_get_dot( $item, 'itemInfo.features.displayValues' );
3394 if ( is_array( $features ) && ! empty( $features ) ) {
3395 $product['description'] = implode( ' ', $features );
3396 }
3397
3398 $product['brand'] = dfrapi_array_get_dot( $item, 'itemInfo.byLineInfo.brand.displayValue' );
3399 $product['color'] = dfrapi_array_get_dot( $item, 'itemInfo.productInfo.color.displayValue' );
3400 $product['manufacturer'] = dfrapi_array_get_dot( $item, 'itemInfo.byLineInfo.manufacturer.displayValue' );
3401 $product['url'] = dfrapi_array_get_dot( $item, 'detailPageURL' );
3402 $product['ref_url'] = dfrapi_array_get_dot( $item, 'detailPageURL' );
3403 $product['image'] = dfrapi_array_get_dot( $item, 'images.primary.large.url' );
3404 $product['thumbnail'] = dfrapi_array_get_dot( $item, 'images.primary.medium.url' );
3405
3406 $upc = dfrapi_array_get_dot( $item, 'itemInfo.externalIds.upcs.displayValues.0' );
3407 $ean = dfrapi_array_get_dot( $item, 'itemInfo.externalIds.eans.displayValues.0' );
3408 $isbn = dfrapi_array_get_dot( $item, 'itemInfo.externalIds.isbns.displayValues.0' );
3409 $gtin = dfrapi_array_get_dot( $item, 'itemInfo.externalIds.gtins.displayValues.0' );
3410
3411 if ( $upc ) {
3412 $product['upc'] = $upc;
3413 }
3414 if ( $ean ) {
3415 $product['ean'] = $ean;
3416 }
3417 if ( $isbn ) {
3418 $product['isbn'] = $isbn;
3419 }
3420 if ( $gtin ) {
3421 $product['gtin'] = $gtin;
3422 }
3423
3424 if ( ! empty( $product['upc'] ) ) {
3425 $product['barcode'] = $product['upc'];
3426 } elseif ( ! empty( $product['ean'] ) ) {
3427 $product['barcode'] = $product['ean'];
3428 } elseif ( ! empty( $product['isbn'] ) ) {
3429 $product['barcode'] = $product['isbn'];
3430 } elseif ( ! empty( $product['gtin'] ) ) {
3431 $product['barcode'] = $product['gtin'];
3432 }
3433
3434 $listings = dfrapi_array_get_dot( $item, 'offersV2.listings', [] );
3435
3436 $info = [];
3437
3438 foreach ( $listings as $listing ) {
3439
3440 // Valid Condition Values: New, Used, Refurbished, Unknown
3441 $condition = strtolower( dfrapi_array_get_dot( $listing, 'condition.value' ) );
3442
3443 // If the list price is missing, use the current price as the base price
3444 $price_amount = dfrapi_array_get_dot( $listing, 'price.money.amount', 0 );
3445 $list_price_amount = dfrapi_array_get_dot( $listing, 'price.savingBasis.money.amount' );
3446 $regular_price = $list_price_amount !== null ? $list_price_amount : $price_amount;
3447
3448 // Add pricing info for each $condition.
3449 $info[ $condition ]['currency'] = dfrapi_array_get_dot( $listing, 'price.money.currency', 'USD' );
3450 $info[ $condition ]['price'] = dfrapi_price_to_int( $regular_price );
3451
3452 $saleprice = dfrapi_price_to_int( $price_amount );
3453 if ( $saleprice < $info[ $condition ]['price'] ) {
3454 $info[ $condition ]['saleprice'] = $saleprice;
3455 }
3456
3457 $info[ $condition ]['finalprice'] = dfrapi_price_to_int( $price_amount );
3458 $info[ $condition ]['salediscount'] = dfrapi_array_get_dot( $listing, 'price.savings.percentage', 0 );
3459
3460 // Set the usedprice if applicable.
3461 if ( in_array( $condition, [ 'used', 'refurbished' ], true ) ) {
3462 $info[ $condition ]['usedprice'] = dfrapi_price_to_int( $price_amount );
3463 }
3464
3465 // Set `onsale`
3466 $info[ $condition ]['onsale'] = ( $info[ $condition ]['finalprice'] < $info[ $condition ]['price'] ) ? 1 : 0;
3467
3468 // Set availability
3469 $availability = dfrapi_array_get_dot( $listing, 'availability.type' );
3470 if ( in_array( $availability, [ 'IN_STOCK', 'IN_STOCK_SCARCE' ], true ) ) {
3471 $info[ $condition ]['instock'] = 1;
3472 } elseif ( in_array( $availability, [ 'UNKNOWN' ], true ) ) {
3473 $info[ $condition ]['instock'] = 9;
3474 } else {
3475 $info[ $condition ]['instock'] = 0;
3476 }
3477 }
3478
3479 // Set the `usedprice` key if a used or refurbished listing exists.
3480 if ( isset( $info['used']['finalprice'] ) ) {
3481 $product['usedprice'] = $info['used']['finalprice'];
3482 } elseif ( isset( $info['refurbished']['finalprice'] ) ) {
3483 $product['usedprice'] = $info['refurbished']['finalprice'];
3484 }
3485
3486 // Set defaults from priority condition
3487 foreach ( [ 'new', 'unknown', 'used', 'refurbished' ] as $cond ) {
3488 if ( isset( $info[ $cond ] ) ) {
3489 $product = array_merge( $product, $info[ $cond ] );
3490 break;
3491 }
3492 }
3493
3494 /**
3495 * Filters the transformed Datafeedr product array.
3496 *
3497 * @since 1.0.0
3498 *
3499 * @param array $product The transformed Datafeedr product array.
3500 * @param array $item The original CAPI item data.
3501 */
3502 return apply_filters( 'dfrapi_transform_capi_item_into_datafeedr_product_array', $product, $item );
3503 }
3504