PluginProbe
AutomatorWP – No-Code Workflow Automation, Integration & Webhooks Plugin, now with AI / 6.0.3
AutomatorWP – No-Code Workflow Automation, Integration & Webhooks Plugin, now with AI v6.0.3
6.0.3 6.0.2 6.0.1 6.0.0 5.8.6 5.8.5 5.8.4 5.8.3 5.8.2 5.8.1 5.8.0 5.7.9.2 5.7.9.1 5.7.8 5.7.9 5.7.6 5.7.7 5.7.5 5.7.4 5.7.3 5.7.2 5.7.1 trunk 5.6.0 5.6.1 All 34 releases
automatorwp / includes / admin / pages / add-ons.php

add-ons.php in AutomatorWP – No-Code Workflow Automation, Integration & Webhooks Plugin, now with AI 6.0.3, at includes/admin/pages/add-ons.php

720 lines 23.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin Add-ons Page
4 *
5 * @package AutomatorWP\Admin\Add_ons
6 * @author AutomatorWP <[email protected]>, Ruben Garcia <[email protected]>
7 * @since 1.0.0
8 */
9 // Exit if accessed directly
10 if( !defined( 'ABSPATH' ) ) exit;
11
12 /**
13 * Add-ons page
14 *
15 * @since 1.0.0
16 *
17 * @return void
18 */
19 function automatorwp_add_ons_page() {
20
21 if( ! function_exists( 'plugins_api' ) ) {
22 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
23 }
24
25 wp_enqueue_script( 'plugin-install' );
26 add_thickbox();
27 wp_enqueue_script( 'updates' );
28
29 $current = 'premium';
30
31 if( isset( $_GET['tab'] ) ) {
32 if( $_GET['tab'] === 'integrations' ) $current = 'integrations';
33 if( $_GET['tab'] === 'app' ) $current = 'app';
34 }
35
36 ?>
37 <div class="wrap">
38 <div id="icon-options-general" class="icon32"></div>
39 <h1 class="wp-heading-inline"><?php _e( 'AutomatorWP Add-ons', 'automatorwp' ); ?></h1>
40 <hr class="wp-header-end">
41
42 <div class="wp-filter">
43 <ul class="filter-links">
44 <li class="plugins-premium"><a href="#" data-target="automatorwp-premium-add-on" class="<?php echo ( $current === 'premium' ? 'current' : '' ); ?>"><?php echo automatorwp_dashicon( 'automatorwp', 'span' ); ?> <?php _e( 'Add-ons', 'automatorwp' ); ?></a></li>
45 <li class="plugins-integrations"><a href="#" data-target="automatorwp-integration-add-on" class="<?php echo ( $current === 'integrations' ? 'current' : '' ); ?>"><?php echo automatorwp_dashicon( 'admin-plugins', 'span' ); ?> <?php _e( 'Integrations', 'automatorwp' ); ?></a></li>
46 <li class="plugins-app"><a href="#" data-target="automatorwp-app-add-on" class="<?php echo ( $current === 'app' ? 'current' : '' ); ?>"><?php echo automatorwp_dashicon( 'star-filled', 'span' ); ?> <?php _e( 'Apps Integrations', 'automatorwp' ); ?></a></li>
47 </ul>
48 </div>
49
50 <p><?php _e( 'Add-ons to extend and expand the functionality of AutomatorWP.', 'automatorwp' ); ?></p>
51
52 <form id="plugin-filter" method="post">
53 <div class="wp-list-table widefat automatorwp-add-ons">
54
55 <?php
56
57 $plugins = automatorwp_plugins_api();
58
59 if ( is_wp_error( $plugins ) ) {
60 echo $plugins->get_error_message();
61 return;
62 }
63
64 foreach ( $plugins as $plugin ) {
65
66 if ( ! str_contains($plugin->info->slug, '-pass')) {
67 automatorwp_render_plugin_card( $plugin );
68 }
69
70 }
71
72 ?>
73 </div>
74 </form>
75 </div>
76 <?php
77 }
78
79 /**
80 * Return an array of all installed plugins slugs
81 *
82 * @since 1.0.0
83 *
84 * @return array
85 */
86 function automatorwp_get_installed_plugins_slugs() {
87
88 $slugs = array();
89
90 $plugin_info = get_site_transient( 'update_plugins' );
91
92 if ( isset( $plugin_info->no_update ) ) {
93 foreach ( $plugin_info->no_update as $plugin ) {
94 $slugs[] = $plugin->slug;
95 }
96 }
97
98 if ( isset( $plugin_info->response ) ) {
99 foreach ( $plugin_info->response as $plugin ) {
100 $slugs[] = $plugin->slug;
101 }
102 }
103
104 return $slugs;
105
106 }
107
108 /**
109 * Helper function to render a plugin card from the add-ons page
110 *
111 * @since 1.0.0
112 *
113 * @param stdClass $plugin
114 *
115 * @return void
116 */
117 function automatorwp_render_plugin_card( $plugin ) {
118
119 // Plugin title
120 $name = $plugin->info->title;
121
122 // Plugin slug
123 $slug = property_exists( $plugin, 'wp_info' ) ? $plugin->wp_info->slug : 'automatorwp-' . $plugin->info->slug;
124
125 // Available actions for this plugin
126 $action_links = array();
127
128 $details_link = esc_url( 'https://automatorwp.com/add-ons/' . $plugin->info->slug );
129
130 if( property_exists( $plugin, 'wp_info' ) ) {
131 // Free add-ons
132
133 $class = 'automatorwp-free-add-on';
134
135 // Check plugin status
136 if ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) {
137 $status = install_plugin_install_status( $plugin->wp_info );
138
139 switch ( $status['status'] ) {
140 case 'install':
141 if ( $status['url'] ) {
142 $action_links[] = '<a class="install-now button" data-slug="' . esc_attr( $slug ) . '" href="' . esc_url( $status['url'] ) . '" aria-label="' . esc_attr( sprintf( __( 'Install %s now' ), $name ) ) . '" data-name="' . esc_attr( $name ) . '">' . __( 'Install Now' ) . '</a>';
143 }
144 break;
145
146 case 'update_available':
147 if ( $status['url'] ) {
148 $action_links[] = '<a class="update-now button aria-button-if-js" data-plugin="' . esc_attr( $status['file'] ) . '" data-slug="' . esc_attr( $slug ) . '" href="' . esc_url( $status['url'] ) . '" aria-label="' . esc_attr( sprintf( __( 'Update %s now' ), $name ) ) . '" data-name="' . esc_attr( $name ) . '">' . __( 'Update Now' ) . '</a>';
149 }
150 break;
151
152 case 'latest_installed':
153 case 'newer_installed':
154 if ( is_plugin_active( $status['file'] ) ) {
155 $action_links[] = '<button type="button" class="button button-disabled" disabled="disabled">' . _x( 'Active', 'plugin' ) . '</button>';
156 } elseif ( current_user_can( 'activate_plugins' ) ) {
157 $button_text = __( 'Activate' );
158 $button_label = _x( 'Activate %s', 'plugin' );
159 $activate_url = add_query_arg( array(
160 '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $status['file'] ),
161 'action' => 'activate',
162 'plugin' => $status['file'],
163 ), network_admin_url( 'plugins.php' ) );
164
165 if ( is_network_admin() ) {
166 $button_text = __( 'Network Activate' );
167 $button_label = _x( 'Network Activate %s', 'plugin' );
168 $activate_url = add_query_arg( array( 'networkwide' => 1 ), $activate_url );
169 }
170
171 $action_links[] = sprintf(
172 '<a href="%1$s" class="button activate-now" aria-label="%2$s">%3$s</a>',
173 esc_url( $activate_url ),
174 esc_attr( sprintf( $button_label, $name ) ),
175 $button_text
176 );
177 } else {
178 $action_links[] = '<button type="button" class="button button-disabled" disabled="disabled">' . _x( 'Installed', 'plugin' ) . '</button>';
179 }
180 break;
181 }
182 }
183 } else {
184 // Premium add-ons
185
186 $premium_plugins = automatorwp_get_premium_plugins();
187 $app_plugins = automatorwp_get_app_plugins();
188
189 if( in_array( $plugin->info->slug, $premium_plugins ) ) {
190 $class = 'automatorwp-premium-add-on';
191 } else if( in_array( $plugin->info->slug, $app_plugins ) ) {
192 $class = 'automatorwp-app-add-on';
193 } else {
194 $class = 'automatorwp-integration-add-on';
195 }
196
197 $plugin_file = $slug . '/' . $slug . '.php';
198
199 // If is installed
200 if ( is_dir( WP_PLUGIN_DIR . '/' . $slug ) ) {
201
202 // If is active
203 if ( is_plugin_active( $slug . '/' . $slug . '.php' ) ) {
204
205 // If has licensing enabled
206 if( $plugin->licensing->enabled ) {
207
208 // Plugin installed and active, so field should be registered
209 $field = cmb2_get_field( $slug . '-license', str_replace( '-', '_', $slug ) . '_license', 'automatorwp_settings', 'options-page' );
210
211 if( $field ) {
212 $license_key = $field->escaped_value();
213
214 $license = rgc_cmb2_edd_license_data( $license_key );
215 $license_status = ( $license !== false ) ? $license->license : false;
216
217 if( $license_status !== 'valid' ) {
218 // "Activate License" action
219 $action_links[] = '<a href="' . admin_url( 'admin.php?page=automatorwp_licenses' ) . '" class="button">' . __( 'Activate License', 'automatorwp' ) . '</a>';
220 } else {
221 // "Active and License Registered" action
222 $action_links[] = '<button type="button" class="button button-disabled" disabled="disabled">' . __( 'Active and License Registered', 'automatorwp' ) . '</button>';
223 }
224 }
225
226 } else {
227 // "Active" action
228 $action_links[] = '<button type="button" class="button button-disabled" disabled="disabled">' . __( 'Active', 'automatorwp' ) . '</button>';
229 }
230
231 } else if ( current_user_can( 'activate_plugins' ) ) {
232 // If not active and current user can activate plugins, then add the "Activate" action
233
234 $button_text = __( 'Activate' );
235 $button_label = _x( 'Activate %s', 'plugin' );
236 $activate_url = add_query_arg( array(
237 '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $plugin_file ),
238 'action' => 'activate',
239 'plugin' => $plugin_file,
240 ), network_admin_url( 'plugins.php' ) );
241
242 if ( is_network_admin() ) {
243 $button_text = __( 'Network Activate' );
244 $button_label = _x( 'Network Activate %s', 'plugin' );
245 $activate_url = add_query_arg( array( 'networkwide' => 1 ), $activate_url );
246 }
247
248 // "Activate" action
249 $action_links[] = sprintf(
250 '<a href="%1$s" class="button activate-now" aria-label="%2$s">%3$s</a>',
251 esc_url( $activate_url ),
252 esc_attr( sprintf( $button_label, $name ) ),
253 $button_text
254 );
255 }
256 } else if( automatorwp_is_plugin_pass( $plugin ) ) {
257
258 // "Get this pass" action
259 $action_links[] = '<a href="https://automatorwp.com/add-ons/' . $plugin->info->slug . '" class="button button-primary" target="_blank">' . __( 'Get this pass', 'automatorwp' ) . '</a>';
260
261 } else {
262
263 // "Get this add-on" action
264 $action_links[] = '<a href="https://automatorwp.com/add-ons/' . $plugin->info->slug . '" class="button button-primary" target="_blank">' . __( 'Get this add-on', 'automatorwp' ) . '</a>';
265
266 }
267 }
268
269 $thumbnail = 'https://automatorwp.com/wp-content/themes/automatorwp-theme/assets/img/integrations/' . $plugin->info->slug . '.svg';
270
271 if( ! empty( $details_link ) ) {
272 // "More Details" action
273 //$action_links[] = '<a href="' . esc_url( $details_link ) . '" class="more-details" aria-label="' . esc_attr( sprintf( __( 'More information about %s' ), $name ) ) . '" data-title="' . esc_attr( $name ) . '" target="_blank">' . __( 'More Details' ) . '</a>';
274 } ?>
275
276 <div class="automatorwp-plugin-card plugin-card plugin-card-<?php echo sanitize_html_class( $slug ); ?> <?php echo $class; ?>">
277
278 <div class="plugin-card-top cmb-tooltip">
279
280 <?php if( is_plugin_active( $slug . '/' . $slug . '.php' ) ) : ?>
281 <div class="active column-active">
282 <span><?php esc_html_e( 'Active', 'automatorwp' ) ?></span>
283 </div>
284 <?php elseif( is_dir( WP_PLUGIN_DIR . '/' . $slug ) ) : ?>
285 <div class="installed column-installed">
286 <span><?php esc_html_e( 'Installed', 'automatorwp' ) ?></span>
287 </div>
288 <?php endif; ?>
289
290 <div class="thumbnail column-thumbnail">
291 <a href="<?php echo esc_url( $details_link ); ?>" class="open-plugin-details-modal" target="_blank">
292 <img src="<?php echo esc_attr( $thumbnail ) ?>" class="plugin-thumbnail" alt="">
293 </a>
294 </div>
295
296 <div class="name column-name">
297 <h3>
298 <a href="<?php echo esc_url( $details_link ); ?>" target="_blank">
299 <?php echo $name; ?>
300 </a>
301 </h3>
302 </div>
303
304 <div class="desc column-description cmb-tooltip-desc cmb-tooltip-top">
305 <p><?php echo automatorwp_esc_plugin_excerpt( $plugin->info->excerpt ); ?></p>
306 </div>
307
308 </div>
309
310 <div class="plugin-card-bottom">
311 <div class="action-links">
312 <?php if ( $action_links ) {
313 echo '<ul class="plugin-action-buttons"><li>' . implode( '</li><li>', $action_links ) . '</li></ul>';
314 } ?>
315 </div>
316 </div>
317
318 </div>
319
320 <?php
321 }
322
323 /**
324 * Function to contact with the AutomatorWP plugins API
325 *
326 * @since 1.0.0
327 *
328 * @return object|WP_Error Object with AutomatorWP plugins
329 */
330 function automatorwp_plugins_api() {
331
332 $cache = automatorwp_get_cache( 'automatorwp_plugins_api', false, false );
333
334 // If result already cached, return it
335 if( $cache !== false ) {
336 return $cache;
337 }
338
339 // If a plugins api request has been cached already, then use cached plugins
340 if ( false !== ( $res = get_transient( 'automatorwp_plugins_api' ) ) ) {
341 return $res;
342 }
343
344 $url = $http_url = 'https://automatorwp.com/wp-json/api/add-ons';
345
346 if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) {
347 $url = set_url_scheme( $url, 'https' );
348 }
349
350 $http_args = array(
351 'timeout' => 15,
352 );
353
354 $request = wp_remote_get( $url, $http_args );
355
356 if ( $ssl && is_wp_error( $request ) ) {
357 trigger_error(
358 sprintf(
359 __( 'An unexpected error occurred. Something may be wrong with automatorwp.com or this server&#8217;s configuration. If you continue to have problems, please try to <a href="%s">contact us</a>.', 'automatorwp' ),
360 'https://automatorwp.com/contact-us/'
361 ) . ' ' . __( '(WordPress could not establish a secure connection to automatorwp.com. Please contact your server administrator.)' ),
362 headers_sent() || WP_DEBUG ? E_USER_WARNING : E_USER_NOTICE
363 );
364
365 $request = wp_remote_get( $http_url, $http_args );
366 }
367
368 if ( is_wp_error( $request ) ) {
369 $res = new WP_Error( 'automatorwp_plugins_api_failed',
370 sprintf(
371 __( 'An unexpected error occurred. Something may be wrong with automatorwp.com or this server&#8217;s configuration. If you continue to have problems, please try to <a href="%s">contact us</a>.', 'automatorwp' ),
372 'https://automatorwp.com/contact-us/'
373 ),
374 $request->get_error_message()
375 );
376 } else {
377 $res = json_decode( $request['body'] );
378
379 $res = (array) $res->products;
380
381 // Set a transient for 1 week with api plugins
382 set_transient( 'automatorwp_plugins_api', $res, ( 24 * 7 ) * HOUR_IN_SECONDS );
383
384 automatorwp_set_cache( 'automatorwp_plugins_api', $res );
385 }
386
387 return $res;
388
389 }
390
391 /**
392 * Function to contact with the AutomatorWP website API
393 *
394 * @since 1.0.0
395 *
396 * @param string $action
397 * @param array $data
398 *
399 * @return object|WP_Error Object with AutomatorWP website API response
400 */
401 function automatorwp_api_request( $action, $data ) {
402
403 // Slug is required to meet the plugin to work
404 if( ! isset( $data['slug'] ) ) {
405 return false;
406 }
407
408 $slug = $data['slug'];
409
410 $cache_key = "automatorwp_api_request_{$action}_{$slug}";
411
412 // If a AutomatorWP api request has been cached already, then use cached response
413 if ( false !== ( $response = get_transient( $cache_key ) ) ) {
414 return $response;
415 }
416
417 $api_params = array(
418 'edd_action' => 'get_version',
419 'license' => ! empty( $data['license'] ) ? $data['license'] : '',
420 'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
421 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
422 'version' => isset( $data['version'] ) ? $data['version'] : false,
423 'slug' => $data['slug'],
424 'author' => isset( $data['author'] ) ? $data['author'] : '',
425 'url' => home_url(),
426 'beta' => ! empty( $data['beta'] ),
427 );
428
429 $request = wp_remote_post( 'http://automatorwp.com/edd-sl-api/', array( 'timeout' => 15, 'sslverify' => true, 'body' => $api_params ) );
430
431 if ( is_wp_error( $request ) ) {
432 // Return the error
433 return $request;
434 }
435
436 $request = json_decode( wp_remote_retrieve_body( $request ) );
437
438 // Unserialize sections
439 if ( $request && isset( $request->sections ) ) {
440 $request->sections = maybe_unserialize( $request->sections );
441 } else {
442 $request = false;
443 }
444
445 // Unserialize banners
446 if ( $request && isset( $request->banners ) ) {
447 $request->banners = maybe_unserialize( $request->banners );
448 }
449
450 // Turn each section into array
451 if( ! empty( $request->sections ) ) {
452 foreach( $request->sections as $key => $section ) {
453 $request->$key = (array) $section;
454 }
455 }
456
457 // Set a transient of 24 hours with AutomatorWP api response
458 set_transient( $cache_key, $request, 24 * HOUR_IN_SECONDS );
459
460 return $request;
461 }
462
463 /**
464 * Overrides the plugin information when plugin is not stored on WordPress.org just on AutomatorWP.com
465 *
466 * @since 1.0.0
467 *
468 * @param mixed $data
469 * @param string $action
470 * @param object $args
471 *
472 * @return object $data
473 */
474 function automatorwp_override_plugin_information( $data, $action = '', $args = null ) {
475
476 if ( $action != 'plugin_information' ) {
477 return $data;
478 }
479
480 if ( ! isset( $args->slug ) ) {
481 return $data;
482 }
483
484 $plugins = automatorwp_plugins_api();
485
486 $override_plugin = false;
487
488 foreach( $plugins as $plugin ) {
489 // Just override if plugin has not assigned to a WordPress.org plugin
490 if ( $args->slug === 'automatorwp-' . $plugin->info->slug && ! property_exists( $plugin, 'wp_info' ) ) {
491
492 $override_plugin = $plugin;
493
494 // Plugin to override found, so exit loop
495 break;
496
497 }
498 }
499
500 // if not plugin to override, return
501 if ( $override_plugin === false ) {
502 return $data;
503 }
504
505 $to_send = array(
506 'slug' => $override_plugin->info->slug,
507 'item_name' => $override_plugin->info->title,
508 'is_ssl' => is_ssl(),
509 'fields' => array(
510 'banners' => array(),
511 'reviews' => false
512 )
513 );
514
515 $api_response = automatorwp_api_request( 'plugin_information', $to_send );
516
517 if ( $api_response !== false) {
518 $data = $api_response;
519 }
520
521 // Convert sections into an associative array, since we're getting an object, but Core expects an array.
522 if ( isset( $data->sections ) && is_array( $data->sections ) ) {
523 $new_sections = array();
524 foreach ( $data->sections as $key => $value ) {
525 $new_sections[ $key ] = $value;
526 }
527
528 $data->sections = $new_sections;
529 }
530
531 // Convert banners into an associative array, since we're getting an object, but Core expects an array.
532 if ( isset( $data->banners ) && ! is_array( $data->banners ) ) {
533 $new_banners = array();
534 foreach ( $data->banners as $key => $value ) {
535 $new_banners[ $key ] = $value;
536 }
537
538 $data->banners = $new_banners;
539 }
540
541 return $data;
542
543 }
544 add_filter( 'plugins_api', 'automatorwp_override_plugin_information', 10, 3 );
545
546 /**
547 * Escape plugin description
548 *
549 * @since 1.0.0
550 *
551 * @param string $excerpt
552 *
553 * @return string
554 */
555 function automatorwp_esc_plugin_excerpt( $excerpt ) {
556
557 // To prevent execute shortcodes on website, the are double capsuled on []
558 $excerpt = str_replace( '[[', '[', $excerpt );
559 $excerpt = str_replace( ']]', ']', $excerpt );
560
561 return $excerpt;
562
563 }
564
565 /**
566 * Append custom body classes to plugin information iframe
567 *
568 * @since 1.0.0
569 *
570 * @param string $classes
571 *
572 * @return string
573 */
574 function automatorwp_plugin_information_admin_body_class( $classes ) {
575
576 $body_id = ( isset( $GLOBALS['body_id'] ) ? $GLOBALS['body_id'] : '' );
577
578 if( $body_id === 'plugin-information' && isset( $_REQUEST['plugin'] ) ) {
579
580 // Get the plugin queried
581 $plugin_slug = wp_unslash( $_REQUEST['plugin'] );
582
583 // Check if is a automatorwp plugin
584 $plugins = automatorwp_plugins_api();
585
586 $override_plugin = false;
587
588 foreach( $plugins as $plugin ) {
589
590 // Just override if is a plugin we own
591 if ( $plugin_slug === 'automatorwp-' . $plugin->info->slug ) {
592
593 $override_plugin = $plugin;
594
595 // Plugin to override found, so exit loop
596 break;
597
598 }
599 }
600
601 // if not plugin to override, return
602 if ( $override_plugin === false ) {
603 return $classes;
604 }
605
606 $classes .= ' is-automatorwp-plugin';
607 }
608
609 return $classes;
610 }
611 add_filter( 'admin_body_class', 'automatorwp_plugin_information_admin_body_class' );
612
613 /**
614 * Helper function to determine if give plugin has the passes category
615 *
616 * @since 1.0.0
617 *
618 * @param stdClass $plugin
619 *
620 * @return bool
621 */
622 function automatorwp_is_plugin_pass( $plugin ) {
623
624 // Check if plugin has categories
625 if( is_array( $plugin->info->category ) && count( $plugin->info->category ) ) {
626
627 // Loop plugin categories
628 foreach( $plugin->info->category as $category ) {
629
630 // Passes category found
631 if( $category->slug === 'access-pass' ) {
632 return true;
633 }
634 }
635
636 }
637
638 return false;
639 }
640
641 /**
642 * Helper function to get all plugins categorized as premium
643 *
644 * @since 1.0.0
645 *
646 * @return array
647 */
648 function automatorwp_get_premium_plugins() {
649 return array(
650 'automatorwp-pro',
651 'block-users',
652 'button',
653 'calculator',
654 'code',
655 'comments',
656 'csv',
657 'custom-user-fields',
658 'emails',
659 'formatter',
660 'generator',
661 'link',
662 'multimedia-content',
663 'posts',
664 'qr-code',
665 'redirect',
666 'restrict-content',
667 'run-now',
668 'schedule-actions',
669 'user-lists',
670 'users',
671 'webhooks',
672 );
673 }
674
675 /**
676 * Helper function to get all plugins categorized as add
677 *
678 * @since 1.0.0
679 *
680 * @return array
681 */
682 function automatorwp_get_app_plugins() {
683 return array(
684 'activecampaign',
685 'airtable',
686 'anthropic-claude',
687 'aweber',
688 'bluesky',
689 'campaign-monitor',
690 'clickup',
691 'convertkit',
692 'facebook',
693 'google-calendar',
694 'google-drive',
695 'google-gemini',
696 'google-sheets',
697 'hubspot',
698 'ifttt',
699 'instagram',
700 'integrately',
701 'integromat',
702 'konnectzit',
703 'mailchimp',
704 'mailerlite',
705 'make',
706 'mautic',
707 'mistral-ai',
708 'notion',
709 'openai',
710 'trello',
711 'twilio',
712 'typeform',
713 'vimeo',
714 'whatsapp',
715 'youtube',
716 'twitter',
717 'zapier',
718 'zoom',
719 );
720 }