PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.0.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.0.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 1.4.1 1.4.2 1.5.0 1.5.1 All 61 releases
← All changes | includes/Admin/SettingsHelper.php +166 -1 1.11.22.0.0 View file →
@@ -109,8 +109,173 @@
109 109 return $fields;
110 110 }
111 111
112 112 /**
113 + * Label a settings group for its tab.
114 + *
115 + * The label is the group's `heading` field, which is also what the panel
116 + * shows, so a tab and its panel can never disagree. An add-on that adds a
117 + * group without a heading still gets a usable tab rather than a blank one:
118 + * `live_qr_settings` reads as "Live Qr Settings", which is wrong-ish but
119 + * findable, and the fix is for that add-on to add a heading.
120 + *
121 + * `main` is the exception. It is what a field with no `group` falls back to,
122 + * so it holds whatever nobody placed rather than anything named "Main". No
123 + * field ships in it — this plugin has no settings that are merely general —
124 + * and it only becomes a tab when something lands there uninvited.
125 + *
126 + * @param string $group_id Group key.
127 + * @param array $group Group data: `fields`, `priority`.
128 + * @return string Unescaped label.
129 + */
130 + public static function group_label( $group_id, array $group ) {
131 + foreach ( $group['fields'] ?? array() as $field ) {
132 + if ( 'heading' === ( $field['type'] ?? '' ) && ! empty( $field['field_data']['title'] ) ) {
133 + return $field['field_data']['title'];
134 + }
135 + }
136 +
137 + if ( 'main' === $group_id ) {
138 + return __( 'General', 'subscription' );
139 + }
140 +
141 + return ucwords( str_replace( array( '_', '-' ), ' ', (string) $group_id ) );
142 + }
143 +
144 + /**
145 + * Whether every field in a group is locked behind Pro.
146 + *
147 + * Drives the "Pro" marker on the tab, so the whole panel does not have to be
148 + * opened to find out that none of it can be changed yet.
149 + *
150 + * @param array $group Group data.
151 + * @return bool
152 + */
153 + public static function group_is_pro_locked( array $group ) {
154 + $has_field = false;
155 +
156 + foreach ( $group['fields'] ?? array() as $field ) {
157 + if ( 'heading' === ( $field['type'] ?? '' ) ) {
158 + continue;
159 + }
160 + $has_field = true;
161 + if ( empty( $field['field_data']['pro_locked'] ) ) {
162 + return false;
163 + }
164 + }
165 +
166 + return $has_field;
167 + }
168 +
169 + /**
170 + * The settings sections, in display order.
171 + *
172 + * One level, named for the job a merchant came to do rather than for the
173 + * plugin's internals. Each section is one rail item and one panel; the
174 + * groups inside it stack, so nothing is ever two clicks deep.
175 + *
176 + * There is deliberately no "All settings" entry. It duplicated every panel
177 + * on one page, which made the rail beside it look like decoration and gave
178 + * every setting two addresses.
179 + *
180 + * @return array<string,string> Section key => label.
181 + */
182 + public static function categories() {
183 + return array(
184 + 'renewals' => __( 'Renewals', 'subscription' ),
185 + 'payments' => __( 'Payments', 'subscription' ),
186 + 'switching' => __( 'Switching & Upgrades', 'subscription' ),
187 + 'customers' => __( 'Customers', 'subscription' ),
188 + 'advanced' => __( 'Advanced', 'subscription' ),
189 + );
190 + }
191 +
192 + /**
193 + * Which section a settings group belongs to.
194 + *
195 + * Groups are merged rather than mapped one-to-one: a section holding a
196 + * single option is a wasted click, so `health_queue` sits with the other
197 + * plumbing in Advanced, and everything the customer meets — the role they
198 + * are given, checking out as a guest, what their subscription's quick view
199 + * shows — is in Customers.
200 + *
201 + * Unmapped groups, including any an add-on registers without knowing
202 + * sections exist, fall into `advanced`, so a new group is always reachable.
203 + *
204 + * @param string $group_id Group key.
205 + * @return string Section key.
206 + */
207 + public static function group_category( $group_id ) {
208 + $map = array(
209 + 'renewals' => 'renewals',
210 + 'payment_gateways' => 'payments',
211 + 'payment_failure' => 'payments',
212 + 'grace_period' => 'payments',
213 + 'switching' => 'switching',
214 + 'role_based_settings' => 'customers',
215 + 'guest_checkout' => 'customers',
216 + 'live_qr_settings' => 'customers',
217 + 'api_settings' => 'advanced',
218 + 'health_queue' => 'advanced',
219 + );
220 +
221 + return $map[ $group_id ] ?? 'advanced';
222 + }
223 +
224 + /**
225 + * Group keys bucketed by section, each list in the order the groups already
226 + * sort in.
227 + *
228 + * Empty sections are dropped. Most of them are filled by Pro, and free
229 + * alone would otherwise show rail items that open onto nothing.
230 + *
231 + * @param array $settings_fields Grouped, sorted settings fields.
232 + * @return array<string,string[]> Section key => ordered group keys.
233 + */
234 + public static function category_groups( array $settings_fields ) {
235 + $out = array();
236 + foreach ( array_keys( self::categories() ) as $cat ) {
237 + $out[ $cat ] = array();
238 + }
239 +
240 + foreach ( array_keys( $settings_fields ) as $group_id ) {
241 + $cat = self::group_category( $group_id );
242 + $out[ $cat ][] = $group_id;
243 + }
244 +
245 + return array_filter(
246 + $out,
247 + function ( $group_ids ) {
248 + return ! empty( $group_ids );
249 + }
250 + );
251 + }
252 +
253 + /**
254 + * Whether every group in a section is locked behind Pro.
255 + *
256 + * Drives the "Pro" marker on the rail item, so a section none of which can
257 + * be changed yet says so before it is opened.
258 + *
259 + * @param string[] $group_ids Group keys in the section.
260 + * @param array $settings_fields Grouped settings fields.
261 + * @return bool
262 + */
263 + public static function category_is_pro_locked( array $group_ids, array $settings_fields ) {
264 + if ( empty( $group_ids ) ) {
265 + return false;
266 + }
267 +
268 + foreach ( $group_ids as $group_id ) {
269 + if ( ! self::group_is_pro_locked( $settings_fields[ $group_id ] ?? array() ) ) {
270 + return false;
271 + }
272 + }
273 +
274 + return true;
275 + }
276 +
277 + /**
113 278 * Render specified settings field.
114 279 *
115 280 * @param string $field Field type.
116 281 * @param array $args Field arguments.
@@ -583,9 +748,9 @@
583 748 *
584 749 * Generic and reusable: a sortable list of text items with per-row remove/move
585 750 * controls and an inline input + add button. The ordered list is serialized as
586 751 * JSON (`[{ key, label }]`) into a hidden input so it submits with the form;
587 - * behaviour is wired by `WPSubsEditList` (admin-components.js). All user-facing
752 + * behaviour is wired by `WPSubsEditList` (admin-components/editlist.js). All user-facing
588 753 * strings are overridable so the field carries no feature-specific text.
589 754 *
590 755 * When `modal` is true the list lives inside a `wpsubs-modal` (via the
591 756 * `WPSubsModal` component) and the settings row only shows a trigger button with