PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / trunk
Subscriptions for WooCommerce with Stripe Recurring Payments vtrunk
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 1.4.1 1.4.2 1.5.0 1.5.1 All 61 releases
subscription / includes / Admin / views / integrations.php

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

517 lines 24.1 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 <!-- Payment Gateways -->
272 <div style="margin-bottom:32px;" data-subscrpt-int-section>
273 <div style="margin-bottom:12px;">
274 <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>
275 </div>
276 <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;">
277 <?php foreach ( $payment_gateways as $integration ) : ?>
278 <?php
279 $is_installed = ! empty( $integration['is_installed'] );
280 $is_active = ! empty( $integration['is_active'] );
281 $is_beta = ! empty( $integration['is_beta'] );
282 $is_pro = ! empty( $integration['is_pro'] );
283 $icon_url = $integration['icon_url'] ?? '';
284 $icon_initial = $integration['icon_initial'] ?? strtoupper( substr( $integration['title'], 0, 2 ) );
285 $icon_color = $integration['icon_color'] ?? '#64748b';
286
287 if ( $is_active ) {
288 $status_dot = '#16a34a';
289 $status_text = __( 'Active', 'subscription' );
290 } elseif ( $is_installed ) {
291 $status_dot = '#d97706';
292 $status_text = __( 'Inactive', 'subscription' );
293 } else {
294 $status_dot = '#9ca3af';
295 $status_text = __( 'Not Installed', 'subscription' );
296 }
297
298 // Filtering reads these rather than scraping the rendered markup,
299 // which would break the moment a label is translated.
300 $subscrpt_card_status = $subscrpt_status_of( $integration );
301 $subscrpt_card_tags = array_keys(
302 array_filter(
303 [
304 'free' => ! $is_pro,
305 'pro' => $is_pro,
306 'beta' => $is_beta,
307 'recurring' => ! empty( $integration['supports_recurring'] ),
308 ]
309 )
310 );
311 ?>
312 <div
313 class="wpsubs-table-card subscrpt-int-card"
314 style="padding:16px;display:flex;flex-direction:column;gap:12px;"
315 data-subscrpt-int-card
316 data-name="<?php echo esc_attr( $integration['title'] ); ?>"
317 data-search="<?php echo esc_attr( strtolower( $integration['title'] . ' ' . ( $integration['description'] ?? '' ) ) ); ?>"
318 data-category="payment_gateway"
319 data-status="<?php echo esc_attr( $subscrpt_card_status ); ?>"
320 data-tags="<?php echo esc_attr( implode( ' ', $subscrpt_card_tags ) ); ?>"
321 >
322
323 <!-- Header: icon + name + status -->
324 <div style="display:flex;gap:10px;align-items:flex-start;">
325 <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;">
326 <?php if ( $icon_url ) : ?>
327 <img src="<?php echo esc_url( $icon_url ); ?>" style="width:100%;height:100%;object-fit:contain;" alt="" />
328 <?php else : ?>
329 <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>
330 <?php endif; ?>
331 </div>
332 <div style="flex:1;min-width:0;">
333 <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:6px;margin-bottom:5px;">
334 <span style="font-size:13px;font-weight:600;color:var(--wpsubs-text);line-height:1.3;"><?php echo esc_html( $integration['title'] ); ?></span>
335 <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;">
336 <span style="width:7px;height:7px;border-radius:50%;background:<?php echo esc_attr( $status_dot ); ?>;flex-shrink:0;"></span>
337 <?php echo esc_html( $status_text ); ?>
338 </span>
339 </div>
340 <div style="display:flex;flex-wrap:wrap;gap:4px;">
341 <?php if ( $is_pro ) : ?>
342 <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>
343 <?php endif; ?>
344 <?php if ( $is_beta ) : ?>
345 <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>
346 <?php endif; ?>
347 <?php if ( ! empty( $integration['supports_recurring'] ) ) : ?>
348 <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;">
349 <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>
350 <?php esc_html_e( 'Automatic recurring', 'subscription' ); ?>
351 </span>
352 <?php else : ?>
353 <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>
354 <?php endif; ?>
355 </div>
356 </div>
357 </div>
358
359 <!-- Description -->
360 <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;"><?php echo esc_html( $integration['description'] ?? '' ); ?></p>
361
362 <!-- Separator -->
363 <div style="border-top:1px solid var(--wpsubs-border);margin:auto -16px 0;"></div>
364
365 <!-- Actions -->
366 <div style="display:flex;gap:6px;flex-wrap:wrap;">
367 <?php if ( $is_pro && ! defined( 'SUBSCRIPT_PRO_VERSION' ) ) : ?>
368 <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;">
369 <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>
370 <?php esc_html_e( 'WPSubscription Pro required', 'subscription' ); ?>
371 </div>
372 <?php else : ?>
373 <?php
374 foreach ( $integration['actions'] as $action ) :
375 $is_primary = ( 'function' === $action['type'] );
376 $btn_class = $is_primary ? 'wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm' : 'wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm';
377 ?>
378 <?php if ( 'link' === $action['type'] ) : ?>
379 <a href="<?php echo esc_url( $action['url'] ); ?>" class="<?php echo esc_attr( $btn_class ); ?>"><?php echo esc_html( $action['label'] ); ?></a>
380 <?php elseif ( 'external_link' === $action['type'] ) : ?>
381 <a href="<?php echo esc_url( $action['url'] ); ?>" target="_blank" rel="noopener noreferrer" class="<?php echo esc_attr( $btn_class ); ?>">
382 <?php echo esc_html( $action['label'] ); ?>
383 <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>
384 </a>
385 <?php elseif ( 'function' === $action['type'] ) : ?>
386 <button class="<?php echo esc_attr( $btn_class ); ?>" onclick="<?php echo esc_attr( $action['function'] ); ?>"><?php echo esc_html( $action['label'] ); ?></button>
387 <?php endif; ?>
388 <?php endforeach; ?>
389 <?php endif; ?>
390 </div>
391
392 </div>
393 <?php endforeach; ?>
394 </div>
395 </div>
396
397 <!-- 3rd Party Integrations -->
398 <?php foreach ( $third_party_grouped as $cat_key => $cat_integrations ) : ?>
399 <div style="margin-bottom:32px;" data-subscrpt-int-section>
400 <div style="margin-bottom:12px;">
401 <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>
402 </div>
403 <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;">
404 <?php foreach ( $cat_integrations as $integration ) : ?>
405 <?php
406 $is_active = ! empty( $integration['is_active'] );
407 $is_pro = ! empty( $integration['is_pro'] );
408 $icon_url = $integration['icon_url'] ?? '';
409 $icon_initial = $integration['icon_initial'] ?? strtoupper( substr( $integration['title'], 0, 2 ) );
410 $icon_color = $integration['icon_color'] ?? '#64748b';
411 $category = $integration['category'] ?? '';
412 $cat = $category_config[ $category ] ?? [
413 'label' => $category,
414 'bg' => '#f1f5f9',
415 'color' => '#475569',
416 ];
417
418 $status_dot = $is_active ? '#16a34a' : '#9ca3af';
419 $status_text = $is_active ? __( 'Active', 'subscription' ) : __( 'Not Installed', 'subscription' );
420
421 $subscrpt_card_status = $subscrpt_status_of( $integration );
422 $subscrpt_card_tags = array_keys(
423 array_filter(
424 [
425 'free' => ! $is_pro,
426 'pro' => $is_pro,
427 'beta' => ! empty( $integration['is_beta'] ),
428 'recurring' => ! empty( $integration['supports_recurring'] ),
429 ]
430 )
431 );
432 ?>
433 <div
434 class="wpsubs-table-card subscrpt-int-card"
435 style="padding:16px;display:flex;flex-direction:column;gap:12px;"
436 data-subscrpt-int-card
437 data-name="<?php echo esc_attr( $integration['title'] ); ?>"
438 data-search="<?php echo esc_attr( strtolower( $integration['title'] . ' ' . ( $integration['description'] ?? '' ) ) ); ?>"
439 data-category="<?php echo esc_attr( isset( $category_config[ $category ] ) ? $category : 'other' ); ?>"
440 data-status="<?php echo esc_attr( $subscrpt_card_status ); ?>"
441 data-tags="<?php echo esc_attr( implode( ' ', $subscrpt_card_tags ) ); ?>"
442 >
443
444 <!-- Header: icon + name + status -->
445 <div style="display:flex;gap:10px;align-items:flex-start;">
446 <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;">
447 <?php if ( $icon_url ) : ?>
448 <img src="<?php echo esc_url( $icon_url ); ?>" style="width:100%;height:100%;object-fit:contain;" alt="" />
449 <?php else : ?>
450 <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>
451 <?php endif; ?>
452 </div>
453 <div style="flex:1;min-width:0;">
454 <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:6px;margin-bottom:5px;">
455 <span style="font-size:13px;font-weight:600;color:var(--wpsubs-text);line-height:1.3;"><?php echo esc_html( $integration['title'] ); ?></span>
456 <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;">
457 <span style="width:7px;height:7px;border-radius:50%;background:<?php echo esc_attr( $status_dot ); ?>;flex-shrink:0;"></span>
458 <?php echo esc_html( $status_text ); ?>
459 </span>
460 </div>
461 <?php if ( $cat['label'] || $is_pro ) : ?>
462 <div style="display:flex;flex-wrap:wrap;gap:4px;">
463 <?php if ( $is_pro ) : ?>
464 <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>
465 <?php endif; ?>
466 <?php if ( $cat['label'] ) : ?>
467 <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>
468 <?php endif; ?>
469 </div>
470 <?php endif; ?>
471 </div>
472 </div>
473
474 <!-- Description -->
475 <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;"><?php echo esc_html( $integration['description'] ?? '' ); ?></p>
476
477 <!-- Separator -->
478 <div style="border-top:1px solid var(--wpsubs-border);margin:auto -16px 0;"></div>
479
480 <!-- Actions -->
481 <div style="display:flex;gap:6px;flex-wrap:wrap;">
482 <?php if ( $is_pro && ! defined( 'SUBSCRIPT_PRO_VERSION' ) ) : ?>
483 <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;">
484 <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>
485 <?php esc_html_e( 'WPSubscription Pro required', 'subscription' ); ?>
486 </div>
487 <?php else : ?>
488 <?php
489 foreach ( $integration['actions'] as $action ) :
490 $is_primary = ( 'function' === $action['type'] );
491 $btn_class = $is_primary ? 'wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm' : 'wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm';
492 ?>
493 <?php if ( 'link' === $action['type'] ) : ?>
494 <a href="<?php echo esc_url( $action['url'] ); ?>" class="<?php echo esc_attr( $btn_class ); ?>"><?php echo esc_html( $action['label'] ); ?></a>
495 <?php elseif ( 'external_link' === $action['type'] ) : ?>
496 <a href="<?php echo esc_url( $action['url'] ); ?>" target="_blank" rel="noopener noreferrer" class="<?php echo esc_attr( $btn_class ); ?>">
497 <?php echo esc_html( $action['label'] ); ?>
498 <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>
499 </a>
500 <?php elseif ( 'function' === $action['type'] ) : ?>
501 <button class="<?php echo esc_attr( $btn_class ); ?>" onclick="<?php echo esc_attr( $action['function'] ); ?>"><?php echo esc_html( $action['label'] ); ?></button>
502 <?php endif; ?>
503 <?php endforeach; ?>
504 <?php endif; ?>
505 </div>
506
507 </div>
508 <?php endforeach; ?>
509 </div>
510 </div>
511 <?php endforeach; ?>
512
513 </div>
514
515 </div>
516 </div>
517