PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.3.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.3.0
2.4.0 2.3.0 2.2.0 2.1.0 2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 All 65 releases
subscription / includes / Admin / views / integrations.php

integrations.php in Subscriptions for WooCommerce with Stripe Recurring Payments 2.3.0, at includes/Admin/views/integrations.php

530 lines 24.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Integrations Admin Page
4 *
5 * Displays payment gateways and third-party integrations as cards.
6 *
7 * @package SpringDevs\Subscription\Admin
8 *
9 * @var array $integrations Filtered integrations list.
10 */
11
12 // Exit if accessed directly.
13 defined( 'ABSPATH' ) || exit;
14
15 // Split by type.
16 $payment_gateways = array_filter(
17 $integrations,
18 function ( $i ) {
19 return ( $i['type'] ?? 'payment_gateway' ) === 'payment_gateway';
20 }
21 );
22 $third_party = array_filter(
23 $integrations,
24 function ( $i ) {
25 return ( $i['type'] ?? '' ) === 'third_party';
26 }
27 );
28
29 // Category config. The `section` key is the full sub-section heading; `label` is the short card badge.
30 $category_config = [
31 'lms' => [
32 'label' => __( 'LMS', 'subscription' ),
33 'section' => __( 'Learning Management System', 'subscription' ),
34 'bg' => '#ede9fe',
35 'color' => '#6d28d9',
36 ],
37 'crm' => [
38 'label' => __( 'CRM', 'subscription' ),
39 'section' => __( 'Customer Relationship Management', 'subscription' ),
40 'bg' => '#fce7f3',
41 'color' => '#9d174d',
42 ],
43 'automation' => [
44 'label' => __( 'Automation', 'subscription' ),
45 'section' => __( 'Automation', 'subscription' ),
46 'bg' => '#e0f2fe',
47 'color' => '#0369a1',
48 ],
49 'email' => [
50 'label' => __( 'Email', 'subscription' ),
51 'section' => __( 'Email Marketing', 'subscription' ),
52 'bg' => '#dcfce7',
53 'color' => '#166534',
54 ],
55 'license' => [
56 'label' => __( 'License', 'subscription' ),
57 'section' => __( 'License Management', 'subscription' ),
58 'bg' => '#fef3c7',
59 'color' => '#92400e',
60 ],
61 ];
62
63 // Group third-party integrations by category, preserving the config order above
64 // and appending any uncategorised ones under "Other".
65 $third_party_grouped = [];
66 foreach ( array_keys( $category_config ) as $cat_key ) {
67 $third_party_grouped[ $cat_key ] = [];
68 }
69 foreach ( $third_party as $integration ) {
70 $cat_key = $integration['category'] ?? '';
71 $cat_key = isset( $category_config[ $cat_key ] ) ? $cat_key : 'other';
72 $third_party_grouped[ $cat_key ][] = $integration;
73 }
74 $third_party_grouped = array_filter( $third_party_grouped );
75
76 /**
77 * Facet counts for the filter bar.
78 *
79 * Derived from the same array the cards render from, so a count can never
80 * disagree with what is on screen — including when filter_integration_actions()
81 * has removed something, or when a future integration is added and nobody
82 * remembers to update a hardcoded number.
83 */
84 $subscrpt_status_of = static function ( array $integration ): string {
85 if ( ! empty( $integration['is_active'] ) ) {
86 return 'active';
87 }
88 return ! empty( $integration['is_installed'] ) ? 'inactive' : 'not-installed';
89 };
90
91 $subscrpt_facets = [
92 'category' => [],
93 'status' => [
94 'active' => 0,
95 'inactive' => 0,
96 'not-installed' => 0,
97 ],
98 // No `recurring` facet: every integration that supports automatic
99 // recurring is a payment gateway, so the chip selected exactly the same
100 // six cards as the Payment Gateways category. The card badge still shows
101 // it, where it says something about that one integration.
102 'tag' => [
103 'free' => 0,
104 'pro' => 0,
105 'beta' => 0,
106 ],
107 ];
108
109 foreach ( $integrations as $integration ) {
110 $cat_key = ( ( $integration['type'] ?? '' ) === 'payment_gateway' )
111 ? 'payment_gateway'
112 : ( isset( $category_config[ $integration['category'] ?? '' ] ) ? $integration['category'] : 'other' );
113
114 $subscrpt_facets['category'][ $cat_key ] = ( $subscrpt_facets['category'][ $cat_key ] ?? 0 ) + 1;
115 ++$subscrpt_facets['status'][ $subscrpt_status_of( $integration ) ];
116
117 if ( ! empty( $integration['is_pro'] ) ) {
118 ++$subscrpt_facets['tag']['pro'];
119 } else {
120 ++$subscrpt_facets['tag']['free'];
121 }
122 if ( ! empty( $integration['is_beta'] ) ) {
123 ++$subscrpt_facets['tag']['beta'];
124 }
125 }
126
127 $subscrpt_total = count( $integrations );
128
129 // Labels for the category chips. Payment gateways are a `type`, not a
130 // `category`, but on this page they read as one more group.
131 //
132 // The short `label` rather than the full `section`: in a 232px rail
133 // "Customer Relationship Management" wraps to two lines and the group of
134 // chips ends up taller than the viewport. The section headings above the
135 // cards still spell it out in full.
136 $subscrpt_category_labels = [ 'payment_gateway' => __( 'Payment Gateways', 'subscription' ) ];
137 foreach ( $category_config as $key => $cfg ) {
138 $subscrpt_category_labels[ $key ] = $cfg['label'] ?? $cfg['section'];
139 }
140 $subscrpt_category_labels['other'] = __( 'Other', 'subscription' );
141
142 $subscrpt_status_labels = [
143 'active' => __( 'Active', 'subscription' ),
144 'inactive' => __( 'Inactive', 'subscription' ),
145 'not-installed' => __( 'Not installed', 'subscription' ),
146 ];
147
148 $subscrpt_tag_labels = [
149 'free' => __( 'Free', 'subscription' ),
150 'pro' => __( 'Pro', 'subscription' ),
151 'beta' => __( 'Beta', 'subscription' ),
152 ];
153 ?>
154
155 <div class="wp-subscription-admin-content list-page">
156
157 <?php
158 wpsubs_render_page_header(
159 array(
160 'title' => __( 'Integrations', 'subscription' ),
161 'description' => __( 'Connect your subscriptions with payment gateways and third-party plugins.', 'subscription' ),
162 )
163 );
164 ?>
165
166 <?php
167 /**
168 * Filter bar.
169 *
170 * Filtering is done in the browser against cards that are all already in the
171 * DOM — there are fifteen of them, so a round trip per keystroke would be
172 * slower and would lose the page's scroll position for nothing.
173 *
174 * Every control degrades to "everything visible" without JavaScript, which
175 * is the state the page is in today.
176 */
177 ?>
178 <?php
179 /*
180 * Two columns: filters in a sticky rail, results beside them. There are
181 * three facet groups and a dozen chips — as a horizontal bar above the
182 * grid that pushed the first card most of the way down the viewport,
183 * and scrolled out of reach exactly when a long list needed narrowing.
184 */
185 ?>
186 <div class="subscrpt-int-layout">
187
188 <aside class="subscrpt-int-sidebar">
189 <div class="subscrpt-int-filters" data-subscrpt-integration-filters hidden>
190
191 <?php
192 $subscrpt_chip_groups = [
193 [
194 'facet' => 'category',
195 'legend' => __( 'Category', 'subscription' ),
196 'counts' => $subscrpt_facets['category'],
197 'labels' => $subscrpt_category_labels,
198 ],
199 [
200 'facet' => 'status',
201 'legend' => __( 'Status', 'subscription' ),
202 'counts' => $subscrpt_facets['status'],
203 'labels' => $subscrpt_status_labels,
204 ],
205 [
206 'facet' => 'tag',
207 'legend' => __( 'Tags', 'subscription' ),
208 'counts' => $subscrpt_facets['tag'],
209 'labels' => $subscrpt_tag_labels,
210 ],
211 ];
212
213 foreach ( $subscrpt_chip_groups as $subscrpt_group ) :
214 // A facet nothing has is noise, not information.
215 $subscrpt_shown = array_filter( $subscrpt_group['counts'] );
216 if ( empty( $subscrpt_shown ) ) {
217 continue;
218 }
219 $subscrpt_is_single = in_array( $subscrpt_group['facet'], array( 'category', 'status' ), true );
220 ?>
221 <nav class="wpsubs-vnav" aria-label="<?php echo esc_attr( $subscrpt_group['legend'] ); ?>">
222 <p class="wpsubs-vnav__title"><?php echo esc_html( $subscrpt_group['legend'] ); ?></p>
223 <?php
224 /*
225 * Category and status are single-select — a card has exactly
226 * one of each, so picking two could only ever return nothing.
227 * Both therefore need an explicit "All" to come back to.
228 */
229 if ( $subscrpt_is_single ) :
230 ?>
231 <button type="button" class="wpsubs-vnav__item is-active" data-subscrpt-int-chip data-facet="<?php echo esc_attr( $subscrpt_group['facet'] ); ?>" data-value="">
232 <span class="wpsubs-vnav__label"><?php esc_html_e( 'All', 'subscription' ); ?></span>
233 <span class="subscrpt-int-chip__count"><?php echo esc_html( number_format_i18n( $subscrpt_total ) ); ?></span>
234 </button>
235 <?php
236 endif;
237 ?>
238 <?php foreach ( $subscrpt_shown as $subscrpt_key => $subscrpt_count ) : ?>
239 <button
240 type="button"
241 class="wpsubs-vnav__item"
242 data-subscrpt-int-chip
243 data-facet="<?php echo esc_attr( $subscrpt_group['facet'] ); ?>"
244 data-value="<?php echo esc_attr( $subscrpt_key ); ?>"
245 aria-pressed="false"
246 >
247 <span class="wpsubs-vnav__label"><?php echo esc_html( $subscrpt_group['labels'][ $subscrpt_key ] ?? $subscrpt_key ); ?></span>
248 <span class="subscrpt-int-chip__count"><?php echo esc_html( number_format_i18n( $subscrpt_count ) ); ?></span>
249 </button>
250 <?php endforeach; ?>
251 </nav>
252 <?php
253 endforeach;
254 ?>
255 <?php // Last in the rail: it only appears once a filter is on, and it undoes what is above it. ?>
256 <button type="button" class="wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm" data-subscrpt-int-reset hidden>
257 <?php esc_html_e( 'Reset', 'subscription' ); ?>
258 </button>
259 </div>
260 </aside>
261
262 <div class="subscrpt-int-results">
263 <p class="subscrpt-int-empty" data-subscrpt-int-empty hidden>
264 <?php esc_html_e( 'No integrations match these filters.', 'subscription' ); ?>
265 </p>
266
267 <?php if ( ! empty( $_GET['subscrpt_installed'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
268 <div class="notice notice-success is-dismissible" style="margin:0 0 16px;"><p><?php esc_html_e( 'Plugin installed and activated successfully.', 'subscription' ); ?></p></div>
269 <?php endif; ?>
270
271 <?php if ( ! empty( $_GET['subscrpt_activated'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
272 <div class="notice notice-success is-dismissible" style="margin:0 0 16px;"><p><?php esc_html_e( 'Plugin activated successfully.', 'subscription' ); ?></p></div>
273 <?php endif; ?>
274
275 <!-- Payment Gateways -->
276 <div style="margin-bottom:32px;" data-subscrpt-int-section>
277 <div style="margin-bottom:12px;">
278 <h2 style="font-size:12px;font-weight:600;color:var(--wpsubs-text-muted);text-transform:uppercase;letter-spacing:0.06em;margin:0;line-height:1.4;margin-left:1px;"><?php esc_html_e( 'Payment Gateways', 'subscription' ); ?></h2>
279 </div>
280 <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;">
281 <?php foreach ( $payment_gateways as $integration ) : ?>
282 <?php
283 $is_installed = ! empty( $integration['is_installed'] );
284 $is_active = ! empty( $integration['is_active'] );
285 $is_beta = ! empty( $integration['is_beta'] );
286 $is_pro = ! empty( $integration['is_pro'] );
287 $icon_url = $integration['icon_url'] ?? '';
288 $icon_initial = $integration['icon_initial'] ?? strtoupper( substr( $integration['title'], 0, 2 ) );
289 $icon_color = $integration['icon_color'] ?? '#64748b';
290
291 if ( $is_active ) {
292 $status_dot = '#16a34a';
293 $status_text = __( 'Active', 'subscription' );
294 } elseif ( $is_installed ) {
295 $status_dot = '#d97706';
296 $status_text = __( 'Inactive', 'subscription' );
297 } else {
298 $status_dot = '#9ca3af';
299 $status_text = __( 'Not Installed', 'subscription' );
300 }
301
302 // Filtering reads these rather than scraping the rendered markup,
303 // which would break the moment a label is translated.
304 $subscrpt_card_status = $subscrpt_status_of( $integration );
305 $subscrpt_card_tags = array_keys(
306 array_filter(
307 [
308 'free' => ! $is_pro,
309 'pro' => $is_pro,
310 'beta' => $is_beta,
311 'recurring' => ! empty( $integration['supports_recurring'] ),
312 ]
313 )
314 );
315 ?>
316 <div
317 class="wpsubs-table-card subscrpt-int-card"
318 style="padding:16px;display:flex;flex-direction:column;gap:12px;"
319 data-subscrpt-int-card
320 data-name="<?php echo esc_attr( $integration['title'] ); ?>"
321 data-search="<?php echo esc_attr( strtolower( $integration['title'] . ' ' . ( $integration['description'] ?? '' ) ) ); ?>"
322 data-category="payment_gateway"
323 data-status="<?php echo esc_attr( $subscrpt_card_status ); ?>"
324 data-tags="<?php echo esc_attr( implode( ' ', $subscrpt_card_tags ) ); ?>"
325 >
326
327 <!-- Header: icon + name + status -->
328 <div style="display:flex;gap:10px;align-items:flex-start;">
329 <div style="width:40px;height:40px;flex-shrink:0;border-radius:8px;overflow:hidden;border:1px solid var(--wpsubs-border);background:var(--wpsubs-surface-muted);display:flex;align-items:center;justify-content:center;">
330 <?php if ( $icon_url ) : ?>
331 <img src="<?php echo esc_url( $icon_url ); ?>" style="width:100%;height:100%;object-fit:contain;" alt="" />
332 <?php else : ?>
333 <span style="font-size:11px;font-weight:700;color:#fff;background:<?php echo esc_attr( $icon_color ); ?>;width:100%;height:100%;display:flex;align-items:center;justify-content:center;"><?php echo esc_html( $icon_initial ); ?></span>
334 <?php endif; ?>
335 </div>
336 <div style="flex:1;min-width:0;">
337 <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:6px;margin-bottom:5px;">
338 <span style="font-size:13px;font-weight:600;color:var(--wpsubs-text);line-height:1.3;"><?php echo esc_html( $integration['title'] ); ?></span>
339 <span style="display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:500;color:<?php echo esc_attr( $status_dot ); ?>;white-space:nowrap;flex-shrink:0;">
340 <span style="width:7px;height:7px;border-radius:50%;background:<?php echo esc_attr( $status_dot ); ?>;flex-shrink:0;"></span>
341 <?php echo esc_html( $status_text ); ?>
342 </span>
343 </div>
344 <div style="display:flex;flex-wrap:wrap;gap:4px;">
345 <?php if ( $is_pro ) : ?>
346 <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:2px 7px;border-radius:10px;background:var(--wpsubs-brand-light,#fff1eb);color:var(--wpsubs-brand-dark,#d93f00);line-height:1.6;"><?php esc_html_e( 'Pro', 'subscription' ); ?></span>
347 <?php endif; ?>
348 <?php if ( $is_beta ) : ?>
349 <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:#fff7ed;color:#c2410c;line-height:1.6;"><?php esc_html_e( 'Beta', 'subscription' ); ?></span>
350 <?php endif; ?>
351 <?php if ( ! empty( $integration['supports_recurring'] ) ) : ?>
352 <span style="display:inline-flex;align-items:center;gap:3px;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:#dcfce7;color:#166534;line-height:1.6;">
353 <svg width="10" height="10" viewBox="0 0 24 24" fill="none"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" fill="#166534"/></svg>
354 <?php esc_html_e( 'Automatic recurring', 'subscription' ); ?>
355 </span>
356 <?php else : ?>
357 <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:#fff3e0;color:#c2410c;line-height:1.6;"><?php esc_html_e( 'Manual renewals only', 'subscription' ); ?></span>
358 <?php endif; ?>
359 </div>
360 </div>
361 </div>
362
363 <!-- Description -->
364 <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;"><?php echo esc_html( $integration['description'] ?? '' ); ?></p>
365
366 <!-- Separator -->
367 <div style="border-top:1px solid var(--wpsubs-border);margin:auto -16px 0;"></div>
368
369 <!-- Actions -->
370 <div style="display:flex;gap:6px;flex-wrap:wrap;">
371 <?php if ( $is_pro && ! defined( 'SUBSCRIPT_PRO_VERSION' ) ) : ?>
372 <div style="width:100%;display:flex;align-items:center;gap:6px;background:var(--wpsubs-brand-light,#fff1eb);border-radius:6px;padding:7px 10px;font-size:12px;font-weight:500;color:var(--wpsubs-brand-dark,#d93f00);line-height:1.4;">
373 <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex-shrink:0;"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
374 <?php esc_html_e( 'WPSubscription Pro required', 'subscription' ); ?>
375 </div>
376 <?php else : ?>
377 <?php
378 foreach ( $integration['actions'] as $action ) :
379 $is_primary = ( 'function' === $action['type'] );
380 $btn_class = $is_primary ? 'wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm' : 'wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm';
381 ?>
382 <?php if ( 'link' === $action['type'] ) : ?>
383 <a href="<?php echo esc_url( $action['url'] ); ?>" class="<?php echo esc_attr( $btn_class ); ?>"><?php echo esc_html( $action['label'] ); ?></a>
384 <?php elseif ( 'external_link' === $action['type'] ) : ?>
385 <a href="<?php echo esc_url( $action['url'] ); ?>" target="_blank" rel="noopener noreferrer" class="<?php echo esc_attr( $btn_class ); ?>">
386 <?php echo esc_html( $action['label'] ); ?>
387 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
388 </a>
389 <?php elseif ( 'function' === $action['type'] ) : ?>
390 <button class="<?php echo esc_attr( $btn_class ); ?>" onclick="<?php echo esc_attr( $action['function'] ); ?>"><?php echo esc_html( $action['label'] ); ?></button>
391 <?php endif; ?>
392 <?php endforeach; ?>
393 <?php endif; ?>
394 </div>
395
396 </div>
397 <?php endforeach; ?>
398 </div>
399 </div>
400
401 <!-- 3rd Party Integrations -->
402 <?php foreach ( $third_party_grouped as $cat_key => $cat_integrations ) : ?>
403 <div style="margin-bottom:32px;" data-subscrpt-int-section>
404 <div style="margin-bottom:12px;">
405 <h2 style="font-size:12px;font-weight:600;color:var(--wpsubs-text-muted);text-transform:uppercase;letter-spacing:0.06em;margin:0;line-height:1.4;margin-left:1px;"><?php echo esc_html( $category_config[ $cat_key ]['section'] ?? __( 'Other', 'subscription' ) ); ?></h2>
406 </div>
407 <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;">
408 <?php foreach ( $cat_integrations as $integration ) : ?>
409 <?php
410 $is_installed = ! empty( $integration['is_installed'] );
411 $is_active = ! empty( $integration['is_active'] );
412 $is_pro = ! empty( $integration['is_pro'] );
413 $icon_url = $integration['icon_url'] ?? '';
414 $icon_initial = $integration['icon_initial'] ?? strtoupper( substr( $integration['title'], 0, 2 ) );
415 $icon_color = $integration['icon_color'] ?? '#64748b';
416 $category = $integration['category'] ?? '';
417 $cat = $category_config[ $category ] ?? [
418 'label' => $category,
419 'bg' => '#f1f5f9',
420 'color' => '#475569',
421 ];
422
423 if ( $is_active ) {
424 $status_dot = '#16a34a';
425 $status_text = __( 'Active', 'subscription' );
426 } elseif ( $is_installed ) {
427 $status_dot = '#d97706';
428 $status_text = __( 'Inactive', 'subscription' );
429 } else {
430 $status_dot = '#9ca3af';
431 $status_text = __( 'Not Installed', 'subscription' );
432 }
433
434 $subscrpt_card_status = $subscrpt_status_of( $integration );
435 $subscrpt_card_tags = array_keys(
436 array_filter(
437 [
438 'free' => ! $is_pro,
439 'pro' => $is_pro,
440 'beta' => ! empty( $integration['is_beta'] ),
441 'recurring' => ! empty( $integration['supports_recurring'] ),
442 ]
443 )
444 );
445 ?>
446 <div
447 class="wpsubs-table-card subscrpt-int-card"
448 style="padding:16px;display:flex;flex-direction:column;gap:12px;"
449 data-subscrpt-int-card
450 data-name="<?php echo esc_attr( $integration['title'] ); ?>"
451 data-search="<?php echo esc_attr( strtolower( $integration['title'] . ' ' . ( $integration['description'] ?? '' ) ) ); ?>"
452 data-category="<?php echo esc_attr( isset( $category_config[ $category ] ) ? $category : 'other' ); ?>"
453 data-status="<?php echo esc_attr( $subscrpt_card_status ); ?>"
454 data-tags="<?php echo esc_attr( implode( ' ', $subscrpt_card_tags ) ); ?>"
455 >
456
457 <!-- Header: icon + name + status -->
458 <div style="display:flex;gap:10px;align-items:flex-start;">
459 <div style="width:40px;height:40px;flex-shrink:0;border-radius:8px;overflow:hidden;border:1px solid var(--wpsubs-border);background:var(--wpsubs-surface-muted);display:flex;align-items:center;justify-content:center;">
460 <?php if ( $icon_url ) : ?>
461 <img src="<?php echo esc_url( $icon_url ); ?>" style="width:100%;height:100%;object-fit:contain;" alt="" />
462 <?php else : ?>
463 <span style="font-size:11px;font-weight:700;color:#fff;background:<?php echo esc_attr( $icon_color ); ?>;width:100%;height:100%;display:flex;align-items:center;justify-content:center;"><?php echo esc_html( $icon_initial ); ?></span>
464 <?php endif; ?>
465 </div>
466 <div style="flex:1;min-width:0;">
467 <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:6px;margin-bottom:5px;">
468 <span style="font-size:13px;font-weight:600;color:var(--wpsubs-text);line-height:1.3;"><?php echo esc_html( $integration['title'] ); ?></span>
469 <span style="display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:500;color:<?php echo esc_attr( $status_dot ); ?>;white-space:nowrap;flex-shrink:0;">
470 <span style="width:7px;height:7px;border-radius:50%;background:<?php echo esc_attr( $status_dot ); ?>;flex-shrink:0;"></span>
471 <?php echo esc_html( $status_text ); ?>
472 </span>
473 </div>
474 <?php if ( $cat['label'] || $is_pro ) : ?>
475 <div style="display:flex;flex-wrap:wrap;gap:4px;">
476 <?php if ( $is_pro ) : ?>
477 <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:2px 7px;border-radius:10px;background:var(--wpsubs-brand-light,#fff1eb);color:var(--wpsubs-brand-dark,#d93f00);line-height:1.6;"><?php esc_html_e( 'Pro', 'subscription' ); ?></span>
478 <?php endif; ?>
479 <?php if ( $cat['label'] ) : ?>
480 <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:<?php echo esc_attr( $cat['bg'] ); ?>;color:<?php echo esc_attr( $cat['color'] ); ?>;line-height:1.6;"><?php echo esc_html( $cat['label'] ); ?></span>
481 <?php endif; ?>
482 </div>
483 <?php endif; ?>
484 </div>
485 </div>
486
487 <!-- Description -->
488 <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;"><?php echo esc_html( $integration['description'] ?? '' ); ?></p>
489
490 <!-- Separator -->
491 <div style="border-top:1px solid var(--wpsubs-border);margin:auto -16px 0;"></div>
492
493 <!-- Actions -->
494 <div style="display:flex;gap:6px;flex-wrap:wrap;">
495 <?php if ( $is_pro && ! defined( 'SUBSCRIPT_PRO_VERSION' ) ) : ?>
496 <div style="width:100%;display:flex;align-items:center;gap:6px;background:var(--wpsubs-brand-light,#fff1eb);border-radius:6px;padding:7px 10px;font-size:12px;font-weight:500;color:var(--wpsubs-brand-dark,#d93f00);line-height:1.4;">
497 <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex-shrink:0;"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
498 <?php esc_html_e( 'WPSubscription Pro required', 'subscription' ); ?>
499 </div>
500 <?php else : ?>
501 <?php
502 foreach ( $integration['actions'] as $action ) :
503 $is_primary = ( 'function' === $action['type'] );
504 $btn_class = $is_primary ? 'wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm' : 'wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm';
505 ?>
506 <?php if ( 'link' === $action['type'] ) : ?>
507 <a href="<?php echo esc_url( $action['url'] ); ?>" class="<?php echo esc_attr( $btn_class ); ?>"><?php echo esc_html( $action['label'] ); ?></a>
508 <?php elseif ( 'external_link' === $action['type'] ) : ?>
509 <a href="<?php echo esc_url( $action['url'] ); ?>" target="_blank" rel="noopener noreferrer" class="<?php echo esc_attr( $btn_class ); ?>">
510 <?php echo esc_html( $action['label'] ); ?>
511 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
512 </a>
513 <?php elseif ( 'function' === $action['type'] ) : ?>
514 <button class="<?php echo esc_attr( $btn_class ); ?>" onclick="<?php echo esc_attr( $action['function'] ); ?>"><?php echo esc_html( $action['label'] ); ?></button>
515 <?php endif; ?>
516 <?php endforeach; ?>
517 <?php endif; ?>
518 </div>
519
520 </div>
521 <?php endforeach; ?>
522 </div>
523 </div>
524 <?php endforeach; ?>
525
526 </div>
527
528 </div>
529 </div>
530