PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.4.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.4.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
← All changes | includes/Admin/views/integrations.php +455 -200 1.10.1 → 2.4.0 View file →
@@ -11,10 +11,8 @@
11 11
12 12 // Exit if accessed directly.
13 13 defined( 'ABSPATH' ) || exit;
14 14
15 -wp_enqueue_style( 'wp-subscription-admin-components', SUBSCRPT_ASSETS . '/css/admin-components.css', [], SUBSCRPT_VERSION );
16 -
17 15 // Split by type.
18 16 $payment_gateways = array_filter(
19 17 $integrations,
20 18 function ( $i ) {
@@ -27,248 +25,505 @@
27 25 return ( $i['type'] ?? '' ) === 'third_party';
28 26 }
29 27 );
30 28
31 -// Category config.
29 +// Category config. The `section` key is the full sub-section heading; `label` is the short card badge.
32 30 $category_config = [
33 31 'lms' => [
34 - 'label' => __( 'LMS', 'subscription' ),
35 - 'bg' => '#ede9fe',
36 - 'color' => '#6d28d9',
32 + 'label' => __( 'LMS', 'subscription' ),
33 + 'section' => __( 'Learning Management System', 'subscription' ),
34 + 'bg' => '#ede9fe',
35 + 'color' => '#6d28d9',
37 36 ],
38 37 'crm' => [
39 - 'label' => __( 'CRM', 'subscription' ),
40 - 'bg' => '#fce7f3',
41 - 'color' => '#9d174d',
38 + 'label' => __( 'CRM', 'subscription' ),
39 + 'section' => __( 'Customer Relationship Management', 'subscription' ),
40 + 'bg' => '#fce7f3',
41 + 'color' => '#9d174d',
42 42 ],
43 43 'automation' => [
44 - 'label' => __( 'Automation', 'subscription' ),
45 - 'bg' => '#e0f2fe',
46 - 'color' => '#0369a1',
44 + 'label' => __( 'Automation', 'subscription' ),
45 + 'section' => __( 'Automation', 'subscription' ),
46 + 'bg' => '#e0f2fe',
47 + 'color' => '#0369a1',
47 48 ],
48 49 'email' => [
49 - 'label' => __( 'Email', 'subscription' ),
50 - 'bg' => '#dcfce7',
51 - 'color' => '#166534',
50 + 'label' => __( 'Email', 'subscription' ),
51 + 'section' => __( 'Email Marketing', 'subscription' ),
52 + 'bg' => '#dcfce7',
53 + 'color' => '#166534',
52 54 ],
53 55 'license' => [
54 - 'label' => __( 'License', 'subscription' ),
55 - 'bg' => '#fef3c7',
56 - 'color' => '#92400e',
56 + 'label' => __( 'License', 'subscription' ),
57 + 'section' => __( 'License Management', 'subscription' ),
58 + 'bg' => '#fef3c7',
59 + 'color' => '#92400e',
57 60 ],
58 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 +];
59 153 ?>
60 154
61 -<div class="wp-subscription-admin-content list-page subscrpt-subs-list">
155 +<div class="wp-subscription-admin-content list-page">
62 156
63 - <!-- Page header -->
64 - <div style="margin-bottom:20px;">
65 - <h1 style="font-size:1.375rem;font-weight:700;color:var(--wpsubs-text);margin:0 0 6px;line-height:1.2;"><?php esc_html_e( 'Integrations', 'subscription' ); ?></h1>
66 - <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0 0 12px;line-height:1.5;"><?php esc_html_e( 'Connect your subscriptions with payment gateways and third-party plugins.', 'subscription' ); ?></p>
67 - <div style="border-top:1px dashed #d0d3d7;"></div>
68 - </div>
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 + ?>
69 165
70 - <?php if ( ! empty( $_GET['subscrpt_installed'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
71 - <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>
72 - <?php endif; ?>
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">
73 187
74 - <!-- Payment Gateways -->
75 - <div style="margin-bottom:32px;">
76 - <div style="margin-bottom:12px;">
77 - <h2 style="font-size:12px;font-weight:600;color:var(--wpsubs-text-muted);text-transform:uppercase;letter-spacing:0.06em;margin:0 0 3px;line-height:1.4;margin-left:1px;"><?php esc_html_e( 'Payment Gateways', 'subscription' ); ?></h2>
78 - <p style="font-size:12px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;margin-left:1px;"><?php esc_html_e( 'Enable and configure payment methods that support recurring billing for subscription products.', 'subscription' ); ?></p>
79 - </div>
80 - <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;">
81 - <?php foreach ( $payment_gateways as $integration ) : ?>
82 - <?php
83 - $is_installed = ! empty( $integration['is_installed'] );
84 - $is_active = ! empty( $integration['is_active'] );
85 - $is_beta = ! empty( $integration['is_beta'] );
86 - $is_pro = ! empty( $integration['is_pro'] );
87 - $icon_url = $integration['icon_url'] ?? '';
88 - $icon_initial = $integration['icon_initial'] ?? strtoupper( substr( $integration['title'], 0, 2 ) );
89 - $icon_color = $integration['icon_color'] ?? '#64748b';
188 + <aside class="subscrpt-int-sidebar">
189 + <div class="subscrpt-int-filters" data-subscrpt-integration-filters hidden>
90 190
91 - if ( $is_active ) {
92 - $status_dot = '#16a34a';
93 - $status_text = __( 'Active', 'subscription' );
94 - } elseif ( $is_installed ) {
95 - $status_dot = '#d97706';
96 - $status_text = __( 'Inactive', 'subscription' );
97 - } else {
98 - $status_dot = '#9ca3af';
99 - $status_text = __( 'Not Installed', 'subscription' );
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;
100 218 }
219 + $subscrpt_is_single = in_array( $subscrpt_group['facet'], array( 'category', 'status' ), true );
101 220 ?>
102 - <div class="wpsubs-table-card" style="padding:16px;display:flex;flex-direction:column;gap:12px;">
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>
103 261
104 - <!-- Header: icon + name + status -->
105 - <div style="display:flex;gap:10px;align-items:flex-start;">
106 - <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;">
107 - <?php if ( $icon_url ) : ?>
108 - <img src="<?php echo esc_url( $icon_url ); ?>" style="width:100%;height:100%;object-fit:contain;" alt="" />
109 - <?php else : ?>
110 - <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>
111 - <?php endif; ?>
112 - </div>
113 - <div style="flex:1;min-width:0;">
114 - <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:6px;margin-bottom:5px;">
115 - <span style="font-size:13px;font-weight:600;color:var(--wpsubs-text);line-height:1.3;"><?php echo esc_html( $integration['title'] ); ?></span>
116 - <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;">
117 - <span style="width:7px;height:7px;border-radius:50%;background:<?php echo esc_attr( $status_dot ); ?>;flex-shrink:0;"></span>
118 - <?php echo esc_html( $status_text ); ?>
119 - </span>
120 - </div>
121 - <div style="display:flex;flex-wrap:wrap;gap:4px;">
122 - <?php if ( $is_pro ) : ?>
123 - <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:2px 7px;border-radius:10px;background:#f5f3ff;color:#7c3aed;line-height:1.6;"><?php esc_html_e( 'Pro', 'subscription' ); ?></span>
124 - <?php endif; ?>
125 - <?php if ( $is_beta ) : ?>
126 - <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>
127 - <?php endif; ?>
128 - <?php if ( ! empty( $integration['supports_recurring'] ) ) : ?>
129 - <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;">
130 - <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>
131 - <?php esc_html_e( 'Automatic recurring', 'subscription' ); ?>
132 - </span>
133 - <?php else : ?>
134 - <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>
135 - <?php endif; ?>
136 - </div>
137 - </div>
138 - </div>
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>
139 266
140 - <!-- Description -->
141 - <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;"><?php echo esc_html( $integration['description'] ?? '' ); ?></p>
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; ?>
142 270
143 - <!-- Separator -->
144 - <div style="border-top:1px solid var(--wpsubs-border);margin:auto -16px 0;"></div>
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; ?>
145 274
146 - <!-- Actions -->
147 - <div style="display:flex;gap:6px;flex-wrap:wrap;">
148 - <?php if ( $is_pro && ! defined( 'SUBSCRIPT_PRO_VERSION' ) ) : ?>
149 - <div style="width:100%;display:flex;align-items:center;gap:6px;background:#f5f3ff;border-radius:6px;padding:7px 10px;font-size:12px;font-weight:500;color:#7c3aed;line-height:1.4;">
150 - <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>
151 - <?php esc_html_e( 'WPSubscription Pro required', 'subscription' ); ?>
152 - </div>
153 - <?php else : ?>
154 - <?php
155 - foreach ( $integration['actions'] as $action ) :
156 - $is_primary = ( 'function' === $action['type'] );
157 - $btn_class = $is_primary ? 'wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm' : 'wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm';
158 - ?>
159 - <?php if ( 'link' === $action['type'] ) : ?>
160 - <a href="<?php echo esc_url( $action['url'] ); ?>" class="<?php echo esc_attr( $btn_class ); ?>"><?php echo esc_html( $action['label'] ); ?></a>
161 - <?php elseif ( 'external_link' === $action['type'] ) : ?>
162 - <a href="<?php echo esc_url( $action['url'] ); ?>" target="_blank" rel="noopener noreferrer" class="<?php echo esc_attr( $btn_class ); ?>">
163 - <?php echo esc_html( $action['label'] ); ?>
164 - <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>
165 - </a>
166 - <?php elseif ( 'function' === $action['type'] ) : ?>
167 - <button class="<?php echo esc_attr( $btn_class ); ?>" onclick="<?php echo esc_attr( $action['function'] ); ?>"><?php echo esc_html( $action['label'] ); ?></button>
168 - <?php endif; ?>
169 - <?php endforeach; ?>
170 - <?php endif; ?>
171 - </div>
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';
172 290
173 - </div>
174 - <?php endforeach; ?>
175 - </div>
176 - </div>
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 + }
177 301
178 - <!-- 3rd Party Integrations -->
179 - <?php if ( ! empty( $third_party ) ) : ?>
180 - <div style="margin-bottom:32px;">
181 - <div style="margin-bottom:12px;">
182 - <h2 style="font-size:12px;font-weight:600;color:var(--wpsubs-text-muted);text-transform:uppercase;letter-spacing:0.06em;margin:0 0 3px;line-height:1.4;margin-left:1px;"><?php esc_html_e( '3rd Party Integrations', 'subscription' ); ?></h2>
183 - <p style="font-size:12px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;margin-left:1px;"><?php esc_html_e( 'Connect subscription events with your LMS, CRM, automation, email, and license management tools.', 'subscription' ); ?></p>
184 - </div>
185 - <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;">
186 - <?php foreach ( $third_party as $integration ) : ?>
187 - <?php
188 - $is_active = ! empty( $integration['is_active'] );
189 - $is_pro = ! empty( $integration['is_pro'] );
190 - $icon_url = $integration['icon_url'] ?? '';
191 - $icon_initial = $integration['icon_initial'] ?? strtoupper( substr( $integration['title'], 0, 2 ) );
192 - $icon_color = $integration['icon_color'] ?? '#64748b';
193 - $category = $integration['category'] ?? '';
194 - $cat = $category_config[ $category ] ?? [
195 - 'label' => $category,
196 - 'bg' => '#f1f5f9',
197 - 'color' => '#475569',
198 - ];
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 + >
199 326
200 - $status_dot = $is_active ? '#16a34a' : '#9ca3af';
201 - $status_text = $is_active ? __( 'Active', 'subscription' ) : __( 'Not Installed', 'subscription' );
202 - ?>
203 - <div class="wpsubs-table-card" style="padding:16px;display:flex;flex-direction:column;gap:12px;">
204 -
205 - <!-- Header: icon + name + status -->
206 - <div style="display:flex;gap:10px;align-items:flex-start;">
207 - <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;">
208 - <?php if ( $icon_url ) : ?>
209 - <img src="<?php echo esc_url( $icon_url ); ?>" style="width:100%;height:100%;object-fit:contain;" alt="" />
210 - <?php else : ?>
211 - <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>
212 - <?php endif; ?>
213 - </div>
214 - <div style="flex:1;min-width:0;">
215 - <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:6px;margin-bottom:5px;">
216 - <span style="font-size:13px;font-weight:600;color:var(--wpsubs-text);line-height:1.3;"><?php echo esc_html( $integration['title'] ); ?></span>
217 - <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;">
218 - <span style="width:7px;height:7px;border-radius:50%;background:<?php echo esc_attr( $status_dot ); ?>;flex-shrink:0;"></span>
219 - <?php echo esc_html( $status_text ); ?>
220 - </span>
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; ?>
221 335 </div>
222 - <?php if ( $cat['label'] || $is_pro ) : ?>
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>
223 344 <div style="display:flex;flex-wrap:wrap;gap:4px;">
224 345 <?php if ( $is_pro ) : ?>
225 - <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:2px 7px;border-radius:10px;background:#f5f3ff;color:#7c3aed;line-height:1.6;"><?php esc_html_e( 'Pro', 'subscription' ); ?></span>
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>
226 347 <?php endif; ?>
227 - <?php if ( $cat['label'] ) : ?>
228 - <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>
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>
229 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; ?>
230 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; ?>
231 393 <?php endif; ?>
232 394 </div>
395 +
233 396 </div>
397 + <?php endforeach; ?>
398 + </div>
399 + </div>
234 400
235 - <!-- Description -->
236 - <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;"><?php echo esc_html( $integration['description'] ?? '' ); ?></p>
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 + ];
237 422
238 - <!-- Separator -->
239 - <div style="border-top:1px solid var(--wpsubs-border);margin:auto -16px 0;"></div>
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 + }
240 433
241 - <!-- Actions -->
242 - <div style="display:flex;gap:6px;flex-wrap:wrap;">
243 - <?php if ( $is_pro && ! defined( 'SUBSCRIPT_PRO_VERSION' ) ) : ?>
244 - <div style="width:100%;display:flex;align-items:center;gap:6px;background:#f5f3ff;border-radius:6px;padding:7px 10px;font-size:12px;font-weight:500;color:#7c3aed;line-height:1.4;">
245 - <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>
246 - <?php esc_html_e( 'WPSubscription Pro required', 'subscription' ); ?>
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; ?>
247 465 </div>
248 - <?php else : ?>
249 - <?php
250 - foreach ( $integration['actions'] as $action ) :
251 - $is_primary = ( 'function' === $action['type'] );
252 - $btn_class = $is_primary ? 'wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm' : 'wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm';
253 - ?>
254 - <?php if ( 'link' === $action['type'] ) : ?>
255 - <a href="<?php echo esc_url( $action['url'] ); ?>" class="<?php echo esc_attr( $btn_class ); ?>"><?php echo esc_html( $action['label'] ); ?></a>
256 - <?php elseif ( 'external_link' === $action['type'] ) : ?>
257 - <a href="<?php echo esc_url( $action['url'] ); ?>" target="_blank" rel="noopener noreferrer" class="<?php echo esc_attr( $btn_class ); ?>">
258 - <?php echo esc_html( $action['label'] ); ?>
259 - <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>
260 - </a>
261 - <?php elseif ( 'function' === $action['type'] ) : ?>
262 - <button class="<?php echo esc_attr( $btn_class ); ?>" onclick="<?php echo esc_attr( $action['function'] ); ?>"><?php echo esc_html( $action['label'] ); ?></button>
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>
263 483 <?php endif; ?>
264 - <?php endforeach; ?>
265 - <?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 +
266 520 </div>
521 + <?php endforeach; ?>
522 + </div>
523 + </div>
524 + <?php endforeach; ?>
267 525
268 - </div>
269 - <?php endforeach; ?>
270 526 </div>
527 +
271 528 </div>
272 - <?php endif; ?>
273 -
274 529 </div>