PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 1.9.0
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v1.9.0
3.4.2 3.4.1 3.4.0 3.3.9 3.3.8 3.3.7 3.3.6 3.3.5 3.3.4 3.3.3 3.3.2 3.3.1 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.8 2.2.9 2.3.0 2.3.1 2.3.2 All 195 releases
convertkit / includes / class-convertkit-api.php

class-convertkit-api.php in Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages 1.9.0, at includes/class-convertkit-api.php

578 lines 15.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ConvertKit API class
4 *
5 * @package ConvertKit
6 * @author ConvertKit
7 */
8
9 use oldmine\RelativeToAbsoluteUrl\RelativeToAbsoluteUrl;
10 use KubAT\PhpSimple\HtmlDomParser;
11
12 /**
13 * ConvertKit_API Class
14 * Establishes API connection to ConvertKit App
15 */
16 class ConvertKit_API {
17
18 /**
19 * ConvertKit API Key
20 *
21 * @var string
22 */
23 protected $api_key;
24
25 /**
26 * ConvertKit API Secret
27 *
28 * @var string
29 */
30 protected $api_secret;
31
32 /**
33 * Save debug data to log
34 *
35 * @var string
36 */
37 protected $debug;
38
39 /**
40 * Version of ConvertKit API
41 *
42 * @var string
43 */
44 protected $api_version = 'v3';
45
46 /**
47 * ConvertKit API URL
48 *
49 * @var string
50 */
51 protected $api_url_base = 'https://api.convertkit.com/';
52
53 /**
54 * API resources
55 *
56 * @var array
57 */
58 protected $resources = array();
59
60 /**
61 * Additional markup
62 *
63 * @var array
64 */
65 protected $markup = array();
66
67 /**
68 * Constructor for ConvertKitAPI instance
69 *
70 * @param string $api_key ConvertKit API Key.
71 * @param string $api_secret ConvertKit API Secret.
72 * @param string $debug Save data to log.
73 */
74 public function __construct( $api_key, $api_secret, $debug ) {
75 $this->api_key = $api_key;
76 $this->api_secret = $api_secret;
77 $this->debug = $debug;
78 }
79
80 /**
81 * Adds a tag to a subscriber
82 *
83 * @param int $tag Tag ID
84 * @param array $options Array of user data
85 * @return object
86 */
87 public function add_tag( $tag, $options ) {
88 $request = $this->api_version . sprintf( '/tags/%s/subscribe', $tag );
89
90 $args = array(
91 'api_key' => $this->api_key,
92 'email' => $options['email'],
93 );
94
95 return $this->make_request( $request, 'POST', $args );
96 }
97
98 /**
99 * Update resources in local options table.
100 *
101 * @param string $api_key
102 */
103 public function update_resources( $api_key, $api_secret ) {
104
105 $this->api_key = $api_key;
106
107 $forms = array();
108 $landing_pages = array();
109 $tags = array();
110
111 WP_ConvertKit::log( 'Updating resource with API key: ' . $api_key );
112 // Forms and Landing Pages
113 $api_response = $this->_get_api_response( 'forms' );
114 WP_ConvertKit::log( 'API Response: ' . print_r( $api_response, true ) );
115
116 if ( is_null( $api_response )
117 || is_wp_error( $api_response )
118 || isset( $api_response['error'] )
119 || isset( $api_response['error_message'] ) ) {
120 $error_message = isset( $api_response['error'] ) ? $api_response['error'] : 'unknown error';
121 $error_message .= is_wp_error( $api_response ) ? $api_response->get_error_message() : '';
122 WP_ConvertKit::log( 'Error contacting API: ' . $error_message );
123
124 $forms[0] = array(
125 'id' => '-2',
126 'name' => 'Error contacting API',
127 );
128
129 $update_forms = $this->maybe_update_option( 'convertkit_forms', $forms );
130 $update_landing_pages = $this->maybe_update_option( 'convertkit_landing_pages', $landing_pages );
131 $update_tags = $this->maybe_update_option( 'convertkit_tags', $tags );
132
133 } else {
134
135 $response = isset( $api_response['forms'] ) ? $api_response['forms'] : array();
136 foreach ( $response as $form ) {
137 if ( isset( $form['archived'] ) && $form['archived'] ) {
138 continue;
139 }
140
141 if ( 'hosted' === $form['type'] ) {
142 $landing_pages[ $form['id'] ] = $form;
143 } else {
144 $forms[ $form['id'] ] = $form;
145 }
146 }
147 $update_forms = $this->maybe_update_option( 'convertkit_forms', $forms );
148 $update_landing_pages = $this->maybe_update_option( 'convertkit_landing_pages', $landing_pages );
149
150 // Tags
151 $api_response = $this->_get_api_response( 'tags' );
152 $response = isset( $api_response['tags'] ) ? $api_response['tags'] : array();
153 foreach ( $response as $tag ) {
154 $tags[] = $tag;
155 }
156 $update_tags = $this->maybe_update_option( 'convertkit_tags', $tags );
157
158 $this->update_account_name( $api_secret );
159 }
160
161
162 return $update_forms && $update_landing_pages && $update_tags;
163 }
164
165 public function update_account_name( $api_secret ) {
166 $response = $this->_get_api_response( 'account', $api_secret );
167
168 return $this->maybe_update_option( 'convertkit_account_name', $response['name'] );
169 }
170
171 /**
172 * Attempt to store updated forms, tags, or landing pages retrieved from the ConvertKit API.
173 * If they match what is already stored, WordPress's built in update_option() function will return
174 * false, which is unhelpful. So, if it returns false, we check if what we tried to store matches what is
175 * already stored, and if so, we return true.
176 *
177 * This way, we know if there was a failure or not.
178 *
179 * @param string $option_name
180 * @param mixed $option_value
181 *
182 * @return bool true if option was updated or if no update was needed, false if failure
183 */
184 public function maybe_update_option( $option_name, $option_value ) {
185 $result = update_option( $option_name, $option_value );
186
187 if ( !$result ) {
188 $old = get_option( $option_name, $option_value );
189 $result = $old === $option_value ? true : false;
190 }
191
192 return $result;
193 }
194
195 /**
196 * Gets a resource index
197 *
198 * GET /{$resource}/
199 *
200 * @param string $resource Resource type.
201 * @return object API response
202 */
203 public function get_resources( $resource ) {
204
205 if ( ! array_key_exists( $resource, $this->resources ) ) {
206
207 if ( 'landing_pages' === $resource ) {
208 $api_response = $this->_get_api_response( 'forms' );
209 } else {
210 $api_response = $this->_get_api_response( $resource );
211 }
212
213 if ( is_null( $api_response ) || is_wp_error( $api_response ) || isset( $api_response['error'] ) || isset( $api_response['error_message'] ) ) {
214 $this->resources[ $resource ] = array(
215 array(
216 'id' => '-2',
217 'name' => 'Error contacting API',
218 ),
219 );
220 } else {
221 $_resource = array();
222
223 if ( 'forms' === $resource ) {
224 $response = isset( $api_response['forms'] ) ? $api_response['forms'] : array();
225 $forms = array();
226 foreach ( $response as $form ) {
227 if ( isset( $form['archived'] ) && $form['archived'] ) {
228 continue;
229 }
230 $_resource[] = $form;
231 $forms[ $form['id'] ] = $form;
232
233 }
234 update_option( 'convertkit_forms', $forms );
235 } elseif ( 'landing_pages' === $resource ) {
236
237 $response = isset( $api_response['forms'] ) ? $api_response['forms'] : array();
238 foreach ( $response as $landing_page ) {
239 if ( 'hosted' === $landing_page['type'] ) {
240 if ( isset( $landing_page['archived'] ) && $landing_page['archived'] ) {
241 continue;
242 }
243 $_resource[] = $landing_page;
244 }
245 }
246 update_option( 'convertkit_landing_pages', $_resource );
247 } elseif ( 'subscription_forms' === $resource ) {
248 foreach ( $api_response as $mapping ) {
249 if ( isset( $mapping['archived'] ) && $mapping['archived'] ) {
250 continue;
251 }
252 $_resource[ $mapping['id'] ] = $mapping['form_id'];
253 }
254 update_option( 'convertkit_subscription_forms', $_resource );
255 } elseif ( 'tags' === $resource ) {
256 $response = isset( $api_response['tags'] ) ? $api_response['tags'] : array();
257 foreach ( $response as $tag ) {
258 $_resource[] = $tag;
259 }
260 update_option( 'convertkit_tags', $_resource );
261 }
262
263 $this->resources[ $resource ] = $_resource;
264 } // End if().
265 } // End if().
266
267 return $this->resources[ $resource ];
268 }
269
270 /**
271 * Adds a subscriber to a form.
272 *
273 * @param string $form_id Form ID.
274 * @param array $options Array of user data.
275 */
276 public function form_subscribe( $form_id, $options ) {
277 $request = $this->api_version . sprintf( '/forms/%s/subscribe', $form_id );
278
279 $args = array(
280 'api_key' => $this->api_key,
281 'email' => $options['email'],
282 'name' => $options['name'],
283 );
284
285 $this->make_request( $request, 'POST', $args );
286 }
287
288 /**
289 * Remove subscription from a form
290 *
291 * @param array $options Array of user data.
292 */
293 public function form_unsubscribe( $options ) {
294 $request = $this->api_version . '/unsubscribe';
295
296 $args = array(
297 'api_secret' => $this->api_secret,
298 'email' => $options['email'],
299 );
300
301 $this->make_request( $request, 'PUT', $args );
302 }
303
304 /**
305 * Get the ConvertKit subscriber ID associated with email address if it exists.
306 * Return 0 if subscriber not found.
307 *
308 * @param $email_address
309 * @return int $subscriber_id
310 */
311 public function get_subscriber_id( $email_address ) {
312
313 $url = add_query_arg(
314 array(
315 'api_secret' => WP_ConvertKit::get_api_secret(),
316 'email_address' => $email_address,
317 'status' => 'all',
318 ),
319 'https://api.convertkit.com/v3/subscribers'
320 );
321
322 WP_ConvertKit::log( 'get_subscriber_id for: ' . $email_address );
323
324 $result = $this->get_resource( $url );
325 if ( is_wp_error( $result ) ) {
326 WP_ConvertKit::log( 'Error getting resource for: ' . $url . '. Error: ' . $result->get_error_messages() );
327 return 0;
328 }
329
330 $subs = json_decode( $result );
331
332 $subscribers = is_array( $subs->subscribers ) ? $subs->subscribers : array();
333 if ( $subscribers ) {
334 $subscriber = array_pop( $subscribers );
335 WP_ConvertKit::log( 'Found ' . count( $subscribers ) . ' subscribers' );
336 WP_ConvertKit::log( 'ID (' . $subscriber->id . ') ' . $subscriber->email_address );
337 return $subscriber->id;
338 }
339 WP_ConvertKit::log( 'Subscriber not found with email ' . $email_address );
340 // subscriber not found
341 return 0;
342
343 }
344
345 /**
346 * @param $subscriber_id
347 *
348 * @return int
349 */
350 public function get_subscriber( $subscriber_id ) {
351 $url = add_query_arg(
352 array(
353 'api_secret' => WP_ConvertKit::get_api_secret(),
354 ),
355 'https://api.convertkit.com/v3/subscribers/' . $subscriber_id
356 );
357
358 WP_ConvertKit::log( 'get_subscriber info for id: ' . $subscriber_id );
359
360 $result = $this->get_resource( $url );
361 if ( is_wp_error( $result ) ) {
362 WP_ConvertKit::log( 'Error getting resource for: ' . $url . '. Error: ' . $result->get_error_messages() );
363 return 0;
364 }
365
366 $result = json_decode( $result );
367
368 if ( isset( $result->subscriber ) ) {
369 $subscriber = $result->subscriber;
370 WP_ConvertKit::log( 'Found: (' . $subscriber->id . ') ' . $subscriber->email_address );
371 return $subscriber;
372 }
373
374 // subscriber not found
375 return 0;
376 }
377
378 /**
379 * Get a list of the tags for a subscriber.
380 *
381 * @param $subscriber_id
382 * @return array $subscriber_tags Array of tags for customer with key of tag_id
383 */
384 public function get_subscriber_tags( $subscriber_id ) {
385
386 $url = add_query_arg(
387 array(
388 'api_key' => WP_ConvertKit::get_api_key(),
389 ),
390 'https://api.convertkit.com/v3/subscribers/' . $subscriber_id . '/tags'
391 );
392
393 WP_ConvertKit::log( 'get_subscriber_tags for: ' . $subscriber_id );
394
395 $result = $this->get_resource( $url );
396 if ( is_wp_error( $result ) ) {
397 WP_ConvertKit::log( 'Error getting resource for: ' . $url . '. Error: ' . $result->get_error_messages() );
398 return array();
399 }
400 $result = json_decode( $result );
401 $tags = isset( $result->tags ) ? $result->tags : array();
402
403 if ( empty( $tags ) ) {
404 WP_ConvertKit::log( 'No tags found for customer.' );
405 } else {
406 WP_ConvertKit::log( 'Found ' . count( $tags ) . 'tags for subscriber' );
407 $tags = wp_list_pluck( $tags, 'name', 'id' );
408 }
409
410 return $tags;
411
412 }
413
414 /**
415 * Get markup from ConvertKit for the provided $url
416 *
417 * @param string $url URL of API action.
418 * @return string
419 */
420 public function get_resource( $url ) {
421 $resource = '';
422
423 if ( ! empty( $url ) && isset( $this->markup[ $url ] ) ) {
424 $resource = $this->markup[ $url ];
425 } elseif ( ! empty( $url ) ) {
426 WP_ConvertKit::log( 'API Request (get_resource): ' . $url );
427
428 $response = wp_remote_get(
429 $url,
430 array(
431 'timeout' => 10,
432 'Accept-Encoding' => 'gzip',
433 )
434 );
435
436 if ( ! is_wp_error( $response ) ) {
437
438 // Maybe inflate response body.
439 // @see https://wordpress.stackexchange.com/questions/10088/how-do-i-troubleshoot-responses-with-wp-http-api
440 $inflate = @gzinflate( $response['body'] );
441 if ( $inflate ) {
442 $response['body'] = $inflate;
443 }
444
445 $body = wp_remote_retrieve_body( $response );
446
447 /** @var \simple_html_dom\simple_html_dom $html */
448 $html = HtmlDomParser::str_get_html( $body );
449 foreach ( $html->find( 'a, link' ) as $element ) {
450 if ( isset( $element->href ) ) {
451 $element->href = RelativeToAbsoluteUrl::urlToAbsolute( $url, $element->href );
452 }
453 }
454
455 foreach ( $html->find( 'img, script' ) as $element ) {
456 if ( isset( $element->src ) ) {
457 $element->src = RelativeToAbsoluteUrl::urlToAbsolute( $url, $element->src );
458 }
459 }
460
461 foreach ( $html->find( 'form' ) as $element ) {
462 if ( isset( $element->action ) ) {
463 $element->action = RelativeToAbsoluteUrl::urlToAbsolute( $url, $element->action );
464 } else {
465 $element->action = $url;
466 }
467 }
468
469 // Check `status_code` for 200, otherwise log error.
470 if ( 200 === $response['response']['code'] ) {
471 $resource = $html->save();
472 $this->markup[ $url ] = $resource;
473 } else {
474 WP_ConvertKit::log( 'Status Code (' . $response['response']['code'] . ') for URL (' . $url . '): ' . $html->save() );
475 }
476 } else {
477 WP_ConvertKit::log( 'API Response was WP_Error (get_resource): ' .
478 'Code: ' . $response->get_error_code() . ' ' .
479 'Message: ' . $response->get_error_message()
480 );
481 } // End if().
482 } // End if().
483
484 return $resource;
485 }
486
487 /**
488 * Do a remote request.
489 *
490 * @param string $path Part of URL.
491 * @return array
492 */
493 private function _get_api_response( $path = '', $api_secret = null ) {
494
495 $args = array(
496 'api_key' => $this->api_key,
497 );
498
499 if ( $api_secret ) {
500 $args['api_secret'] = $api_secret;
501 }
502
503 $api_path = $this->api_url_base . $this->api_version;
504 $url = add_query_arg( $args, path_join( $api_path, $path ) );
505
506 $response = wp_remote_get(
507 $url,
508 array(
509 'timeout' => 10,
510 'Accept-Encoding' => 'gzip',
511 'sslverify' => false,
512 )
513 );
514
515 if ( is_wp_error( $response ) ) {
516 WP_ConvertKit::log( 'Error: ' . $response->get_error_message() );
517
518 return array(
519 'error' => $response->get_error_message(),
520 );
521 } else {
522
523 // Maybe inflate response body.
524 // @see https://wordpress.stackexchange.com/questions/10088/how-do-i-troubleshoot-responses-with-wp-http-api
525 $inflate = @gzinflate( $response['body'] );
526 if ( $inflate ) {
527 $response['body'] = $inflate;
528 }
529
530 $body = wp_remote_retrieve_body( $response );
531 $data = json_decode( $body, true );
532 }
533
534 return $data;
535 }
536
537 /**
538 * Make a request to the ConvertKit API
539 *
540 * @param string $request Request string.
541 * @param string $method HTTP Method.
542 * @param array $args Request arguments.
543 * @return object Response object
544 */
545 private function make_request( $request, $method, $args = array() ) {
546
547 WP_ConvertKit::log( 'API Request (make_request): ' . $request . ' Args: ' . wp_json_encode( $args ) );
548
549 $url = $this->api_url_base . $request;
550
551 $headers = array(
552 'Content-Type' => 'application/json; charset=utf-8',
553 );
554
555 $settings = array(
556 'headers' => $headers,
557 'method' => $method,
558 'body' => wp_json_encode( $args ),
559 );
560
561 $result = wp_remote_request( $url, $settings );
562
563 if ( is_wp_error( $result ) ) {
564 WP_ConvertKit::log( 'API Response (make_request): WPError: ' . $result->get_error_message() );
565 } elseif ( isset( $result['response']['code'] ) && '200' === $result['response']['code'] ) {
566 if ( isset( $result['body'] ) ) {
567 WP_ConvertKit::log( 'API Response (make_request): ' . $result['body'] );
568 } else {
569 WP_ConvertKit::log( 'API Response (make_request): Response code 200, but body is not set.' );
570 }
571 } else {
572 WP_ConvertKit::log( 'API Response (make_request): Result code: ' . $result['response']['code'] . ' ' . $result['response']['message'] );
573 }
574
575 }
576
577 }
578