PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.2.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.2.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 59 releases
storeengine / includes / cli / license.php

license.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.2.0, at includes/cli/license.php

420 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace StoreEngine\Cli;
4
5 use StoreEnginePro\Addons\LicenseManagement\Classes\License as LicenseEntity;
6 use StoreEnginePro\Addons\LicenseManagement\Classes\LicenseCollection;
7 use StoreEnginePro\Addons\LicenseManagement\Classes\Utils;
8 use WP_CLI;
9 use WP_CLI_Command;
10 use function WP_CLI\Utils\make_progress_bar;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 class License extends WP_CLI_Command {
17
18 /**
19 * List licenses.
20 *
21 * ## OPTIONS
22 *
23 * [--status=<status>]
24 * : License status to filter by (e.g. active, inactive, expired, revoked).
25 *
26 * [--limit=<limit>]
27 * : Maximum number of licenses to list. Defaults to 10. Set to -1 for all.
28 * ---
29 * default: 10
30 * ---
31 *
32 * ## EXAMPLES
33 *
34 * wp storeengine license list --status=completed --limit=50
35 *
36 * @subcommand list
37 */
38 public function list( $args, $assoc_args ) {
39 $status = $assoc_args['status'] ?? '';
40 $limit = $assoc_args['limit'] ?? 10;
41
42 $query_args = [
43 'per_page' => $limit,
44 'orderby' => 'id',
45 'order' => 'DESC',
46 ];
47
48 if ( $status ) {
49 $query_args['where'][] = [
50 'key' => 'status',
51 'value' => $status,
52 ];
53 }
54
55
56 $query = new LicenseCollection( $query_args );
57
58 if ( ! $query->have_results() ) {
59 WP_CLI::warning( 'No licenses found.' );
60
61 return;
62 }
63
64 $items = [];
65 foreach ( $query->get_results() as $license ) {
66 $items[] = [
67 'ID' => $license->get_id(),
68 'Key' => $license->get_license_key(),
69 'Status' => $license->get_status(),
70 'Customer' => $license->get_customer_name() . ' [#' . $license->get_customer_id() . ']',
71 'Activations' => $license->get_activation_count() . '/' . $license->get_activation_limit(),
72 'Expire' => $license->get_expires_at() ? $license->get_expires_at()->format( 'Y-m-d H:i:s' ) : '♾️',
73 'Updated' => $license->get_updated_at() ? $license->get_updated_at()->format( 'Y-m-d H:i:s' ) : '',
74 'Created' => $license->get_created_at() ? $license->get_created_at()->format( 'Y-m-d H:i:s' ) : '',
75 ];
76 }
77
78 WP_CLI\Utils\format_items( 'table', $items, [
79 'ID',
80 'Key',
81 'Status',
82 'Customer',
83 'Activations',
84 'Expire',
85 'Updated',
86 'Created',
87 ] );
88 }
89
90 /**
91 * Sync activations for licenses.
92 *
93 * ## OPTIONS
94 *
95 * [--id=<id>]
96 * : Specific license ID to delete. Set to "all" to delete all licenses.
97 *
98 * ## EXAMPLES
99 *
100 * wp storeengine license sync --id=50
101 * wp storeengine license sync --id=all
102 *
103 * @subcommand sync-activations
104 */
105 public function sync_activations( $args, $assoc_args ) {
106 $id = $assoc_args['id'] ?? null;
107
108 if ( ! $id ) {
109 WP_CLI::error( 'ID is required. E.g. --id=<id>' );
110 }
111
112 if ( $id && 'all' !== $id ) {
113 $query = new LicenseCollection( [ 'id' => $id ] );
114 if ( ! $query->have_results() ) {
115 WP_CLI::error( "License $id not found." );
116 }
117 } else {
118 $query_args = [ 'per_page' => - 1 ];
119
120 $query = new LicenseCollection( $query_args );
121
122 if ( ! $query->have_results() ) {
123 WP_CLI::error( "No licenses found." );
124 }
125 }
126
127 $progress = make_progress_bar( 'Synchronizing license activations', $query->get_found_results() );
128
129 $count = 0;
130
131 foreach ( $query->get_results() as $license ) {
132 try {
133 if ( $license->sync_activation_count() ) {
134 $count ++;
135 } else {
136 WP_CLI::warning( "Failed to sync license {$license->get_id()}." );
137 }
138 } catch ( \Throwable $e ) {
139 WP_CLI::warning( "Failed to sync license {$license->get_id()}. Error: {$e->getMessage()}" );
140 }
141
142 $progress->tick();
143 }
144
145 $progress->finish();
146
147 WP_CLI::success( "Successfully synced $count license(s)." );
148 }
149
150 /**
151 * Delete licenses.
152 *
153 * ## OPTIONS
154 *
155 * [--id=<id>]
156 * : Specific license ID to delete. Set to "all" to delete all licenses.
157 *
158 * [--status=<status>]
159 * : Delete all licenses matching a specific status.
160 *
161 * [--force]
162 * : Skip confirmation prompt.
163 *
164 * ## EXAMPLES
165 *
166 * wp storeengine license delete --id=123
167 * wp storeengine license delete --status=inactive --force
168 *
169 * @subcommand delete
170 */
171 public function delete( $args, $assoc_args ) {
172 $id = $assoc_args['id'] ?? null;
173 $status = $assoc_args['status'] ?? null;
174 $force = $assoc_args['force'] ?? false;
175 $skip_trash = $assoc_args['skip-trash'] ?? false;
176
177 if ( ! $id && ! $status ) {
178 WP_CLI::error( 'Please provide either --id=<id>, --id=all, or --status=<status>' );
179 }
180
181 if ( $id && 'all' !== $id ) {
182 $query = new LicenseCollection( [ 'id' => $id ] );
183 if ( ! $query->have_results() ) {
184 WP_CLI::error( "License $id not found." );
185 }
186 } else {
187 $query_args = [ 'per_page' => - 1 ];
188
189 if ( $status ) {
190 $query_args['where'][] = [
191 'key' => 'status',
192 'value' => $status,
193 ];
194 }
195
196 $query = new LicenseCollection( $query_args );
197
198 if ( ! $query->have_results() ) {
199 $msg = $status ? "No licenses found with status '$status'." : "No licenses found.";
200 WP_CLI::success( $msg );
201
202 return;
203 }
204 }
205
206 if ( ! $force ) {
207 WP_CLI::confirm( sprintf( "Are you sure you want to delete %s license(s)?", $query->get_found_results() ) );
208 }
209
210 $progress = make_progress_bar( 'Deleting licenses', $query->get_found_results() );
211
212 $deleted_count = 0;
213
214 foreach ( $query->get_results() as $license ) {
215 try {
216 if ( $license->delete( $skip_trash ) ) {
217 $deleted_count ++;
218 } else {
219 WP_CLI::warning( "Failed to delete license {$license->get_id()}." );
220 }
221 } catch ( \Throwable $e ) {
222 WP_CLI::warning( "Failed to delete license {$license->get_id()}. Error: {$e->getMessage()}" );
223 }
224
225 $progress->tick();
226 }
227
228 $progress->finish();
229
230 WP_CLI::success( "Successfully deleted $deleted_count license(s)." );
231 }
232
233 /**
234 * Detect and repair license keys that fail client activation.
235 *
236 * A key containing characters the client SDK strips on submit — notably "%"
237 * followed by two hex digits — can never match what was issued, so activation
238 * returns "Invalid license". This scans for such keys and reissues each one
239 * with a fresh, URL-safe key. Keys that are already valid are left untouched,
240 * so working activations are never disturbed.
241 *
242 * ## OPTIONS
243 *
244 * [--id=<id>]
245 * : Only check a single license ID.
246 *
247 * [--dry-run]
248 * : Detect and report affected licenses without changing anything.
249 *
250 * [--yes]
251 * : Skip the confirmation prompt.
252 *
253 * ## EXAMPLES
254 *
255 * # Detect only — list keys that would fail activation.
256 * wp storeengine license heal-keys --dry-run
257 *
258 * # Detect and regenerate every affected key.
259 * wp storeengine license heal-keys
260 *
261 * # Repair a single license non-interactively.
262 * wp storeengine license heal-keys --id=3065 --yes
263 *
264 * @subcommand heal-keys
265 */
266 public function heal_keys( $args, $assoc_args ) {
267 $id = $assoc_args['id'] ?? null;
268 $dry_run = isset( $assoc_args['dry-run'] );
269 $yes = isset( $assoc_args['yes'] );
270
271 if ( $id ) {
272 $query = new LicenseCollection( [ 'id' => $id ] );
273 if ( ! $query->have_results() ) {
274 WP_CLI::error( "License $id not found." );
275 }
276 } else {
277 $query = new LicenseCollection( [ 'per_page' => - 1 ] );
278 if ( ! $query->have_results() ) {
279 WP_CLI::error( 'No licenses found.' );
280 }
281 }
282
283 // 1) Detect.
284 $unsafe = [];
285 foreach ( $query->get_results() as $license ) {
286 if ( ! Utils::is_license_key_safe( $license->get_license_key( 'edit' ) ) ) {
287 $unsafe[] = $license;
288 }
289 }
290
291 if ( ! $unsafe ) {
292 WP_CLI::success( 'All license keys are valid — nothing to repair.' );
293
294 return;
295 }
296
297 WP_CLI::log( sprintf( 'Found %d license(s) with an unsafe key:', count( $unsafe ) ) );
298 WP_CLI\Utils\format_items( 'table', array_map( static function ( $license ) {
299 return [
300 'ID' => $license->get_id(),
301 'Key' => $license->get_license_key(),
302 'Status' => $license->get_status(),
303 'Customer' => $license->get_customer_name() . ' [#' . $license->get_customer_id() . ']',
304 ];
305 }, $unsafe ), [ 'ID', 'Key', 'Status', 'Customer' ] );
306
307 if ( $dry_run ) {
308 WP_CLI::log( 'Dry run — no keys were changed. Re-run without --dry-run to repair.' );
309
310 return;
311 }
312
313 if ( ! $yes ) {
314 WP_CLI::confirm( sprintf( 'Regenerate %d license key(s)? The old keys will stop working.', count( $unsafe ) ) );
315 }
316
317 // 2) Regenerate.
318 $progress = make_progress_bar( 'Regenerating license keys', count( $unsafe ) );
319 $healed = [];
320
321 foreach ( $unsafe as $license ) {
322 try {
323 $change = $license->heal_unsafe_key();
324 if ( $change ) {
325 $healed[] = [
326 'ID' => $license->get_id(),
327 'Old' => $change['old'],
328 'New' => $change['new'],
329 ];
330 }
331 } catch ( \Throwable $e ) {
332 WP_CLI::warning( "Failed to regenerate license {$license->get_id()}. Error: {$e->getMessage()}" );
333 }
334
335 $progress->tick();
336 }
337
338 $progress->finish();
339
340 if ( $healed ) {
341 WP_CLI\Utils\format_items( 'table', $healed, [ 'ID', 'Old', 'New' ] );
342 }
343
344 WP_CLI::success( sprintf( 'Regenerated %d license key(s).', count( $healed ) ) );
345 }
346
347 /**
348 * Force-regenerate the key for specific license(s).
349 *
350 * Unlike `heal-keys` (which only touches keys that would fail activation),
351 * this reissues the key for the given license regardless of its current
352 * value — e.g. to hand a customer a fresh key on request. The OLD key stops
353 * working immediately, so the new key must be shared.
354 *
355 * ## OPTIONS
356 *
357 * --id=<id>
358 * : License ID to regenerate. Accepts a comma-separated list of IDs.
359 *
360 * [--yes]
361 * : Skip the confirmation prompt.
362 *
363 * ## EXAMPLES
364 *
365 * wp storeengine license regenerate --id=3065
366 * wp storeengine license regenerate --id=3065,3066 --yes
367 *
368 * @subcommand regenerate
369 */
370 public function regenerate( $args, $assoc_args ) {
371 $id = $assoc_args['id'] ?? null;
372 $yes = isset( $assoc_args['yes'] );
373
374 if ( ! $id ) {
375 WP_CLI::error( 'License ID is required. E.g. --id=3065' );
376 }
377
378 $ids = array_values( array_unique( array_filter( array_map( 'absint', explode( ',', (string) $id ) ) ) ) );
379
380 if ( ! $ids ) {
381 WP_CLI::error( 'No valid license ID provided.' );
382 }
383
384 if ( ! $yes ) {
385 WP_CLI::confirm( sprintf( 'Regenerate %d license key(s)? The old key(s) will stop working.', count( $ids ) ) );
386 }
387
388 $rows = [];
389
390 foreach ( $ids as $license_id ) {
391 try {
392 $license = new LicenseEntity( $license_id );
393
394 if ( ! $license->get_id() ) {
395 WP_CLI::warning( "License $license_id not found." );
396 continue;
397 }
398
399 $old = $license->get_license_key( 'edit' );
400 $new = $license->regenerate_license_key();
401 $license->save();
402
403 $rows[] = [
404 'ID' => $license_id,
405 'Old' => $old,
406 'New' => $new,
407 ];
408 } catch ( \Throwable $e ) {
409 WP_CLI::warning( "Failed to regenerate license $license_id. Error: {$e->getMessage()}" );
410 }
411 }
412
413 if ( $rows ) {
414 WP_CLI\Utils\format_items( 'table', $rows, [ 'ID', 'Old', 'New' ] );
415 }
416
417 WP_CLI::success( sprintf( 'Regenerated %d license key(s).', count( $rows ) ) );
418 }
419 }
420