PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.9
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.9
4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 All 140 releases
learnpress / inc / TemplateHooks / Admin / AdminAddonsPage.php

AdminAddonsPage.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.9, at inc/TemplateHooks/Admin/AdminAddonsPage.php

987 lines 30.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace LearnPress\TemplateHooks\Admin;
4
5 use DateTime;
6 use Exception;
7 use LearnPress\Helpers\LPDateTime;
8 use LearnPress\Helpers\Response;
9 use LearnPress\Helpers\Singleton;
10 use LearnPress\Helpers\Template;
11 use LearnPress\Models\UserModel;
12 use LearnPress\Services\AddonService;
13 use LearnPress\TemplateHooks\TemplateAJAX;
14 use LP_Debug;
15 use LP_Helper;
16 use LP_Settings;
17 use stdClass;
18 use Throwable;
19
20 defined( 'ABSPATH' ) || exit();
21
22 /**
23 * Admin Add-ons page renderer.
24 *
25 * @since 4.2.8
26 * @version 1.0.2
27 */
28 class AdminAddonsPage {
29 use Singleton;
30
31 /**
32 * @var array Addons installed, not on wp.org, having newer version. Keyed by plugin basename.
33 */
34 private $addons_need_update = [];
35
36 /**
37 * Init hooks.
38 *
39 * @return void
40 */
41 public function init(): void {
42 if ( is_admin() ) {
43 // Show update notice for addons not on wp.org on plugins.php page.
44 add_filter( 'site_transient_update_plugins', array( $this, 'inject_update_wp_plugins' ) );
45 add_action( 'load-plugins.php', array( $this, 'wp_update_plugin_message' ) );
46 }
47 }
48
49 /**
50 * Inject update info of addons not on wp.org into update_plugins transient
51 * so WP core renders update rows on wp-admin/plugins.php.
52 *
53 * @param object $transient Value of site_transient_update_plugins.
54 *
55 * @return object
56 */
57 public function inject_update_wp_plugins( $transient ): object {
58 try {
59 if ( ! is_object( $transient ) ) {
60 $transient = new stdClass();
61 }
62
63 if ( ! isset( $transient->response ) || ! is_array( $transient->response ) ) {
64 $transient->response = [];
65 }
66
67 if ( ! isset( $transient->no_update ) || ! is_array( $transient->no_update ) ) {
68 $transient->no_update = [];
69 }
70
71 $addons = AddonService::instance()->get_remote_data();
72 $plugins = get_plugins();
73
74 foreach ( $addons as $slug => $addon ) {
75 // Skip addons hosted on wp.org - core already handles them.
76 if ( ! empty( $addon->is_org ) ) {
77 continue;
78 }
79
80 $basename = $addon->basename ?? '';
81 if ( empty( $basename ) || ! isset( $plugins[ $basename ] ) ) {
82 continue;
83 }
84
85 $item = (object) [
86 'id' => 'learnpress/' . $slug,
87 'slug' => $slug,
88 'plugin' => $basename,
89 'new_version' => $addon->version ?? '',
90 'url' => $addon->link ?? '',
91 'package' => '',
92 'icons' => isset( $addon->image ) ? [ 'default' => $addon->image ] : [],
93 ];
94
95 $current_version = $plugins[ $basename ]['Version'];
96 if ( ! empty( $item->new_version ) && version_compare( $current_version, $item->new_version, '<' ) ) {
97 $transient->response[ $basename ] = $item;
98 } else {
99 $transient->no_update[ $basename ] = $item;
100 }
101 }
102 } catch ( Throwable $e ) {
103 LP_Debug::error_log( $e );
104 }
105
106 return $transient;
107 }
108
109 /**
110 * Register hooks to append update link into plugin update rows on plugins.php.
111 * Only for installed addons not on wp.org and having a newer version.
112 *
113 * @return void
114 */
115 public function wp_update_plugin_message(): void {
116 try {
117 $addons = AddonService::instance()->get_remote_data();
118 $plugins = get_plugins();
119
120 foreach ( $addons as $addon ) {
121 if ( ! empty( $addon->is_org ) ) {
122 continue;
123 }
124
125 $basename = $addon->basename ?? '';
126 if ( empty( $basename ) || ! isset( $plugins[ $basename ] ) ) {
127 continue;
128 }
129
130 $new_version = $addon->version ?? '';
131 if ( empty( $new_version )
132 || version_compare( $plugins[ $basename ]['Version'], $new_version, '>=' ) ) {
133 continue;
134 }
135
136 $this->addons_need_update[ $basename ] = $addon;
137
138 // Priority 5: run before core wp_plugin_update_row (priority 10) to override it.
139 add_action( "after_plugin_row_{$basename}", array( $this, 'render_custom_update_row' ), 5, 2 );
140 }
141 } catch ( Throwable $e ) {
142 LP_Debug::error_log( $e );
143 }
144 }
145
146 /**
147 * Render custom update row to override core default message.
148 * Hook: after_plugin_row_{$basename} - priority 5, before core wp_plugin_update_row.
149 *
150 * @param string $file Plugin basename.
151 * @param array $plugin_data Plugin data.
152 *
153 * @return void
154 */
155 public function render_custom_update_row( string $file, array $plugin_data ): void {
156 // Remove core default update row to override the message.
157 remove_action( "after_plugin_row_{$file}", 'wp_plugin_update_row' );
158
159 $addon = $this->addons_need_update[ $file ] ?? null;
160 if ( ! $addon ) {
161 return;
162 }
163
164 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
165 $slug = sanitize_key( $addon->basename ?? $file );
166 $new_version = $addon->version ?? '';
167 $link_update = admin_url( 'admin.php?page=learn-press-addons&tab=update' );
168
169 $section = array(
170 'wrap' => sprintf(
171 '<tr class="plugin-update-tr active" id="%s-update" data-slug="%s" data-plugin="%s">',
172 esc_attr( $slug ),
173 esc_attr( $slug ),
174 esc_attr( $file )
175 ),
176 'cell_open' => sprintf(
177 '<td colspan="%s" class="plugin-update colspanchange">',
178 esc_attr( $wp_list_table->get_column_count() )
179 ),
180 'message_open' => '<div class="update-message notice inline notice-warning notice-alt"><p>',
181 'text' => sprintf(
182 /* translators: 1: addon name, 2: new version */
183 esc_html__( 'There is a new version of %1$s (v%2$s) available.', 'learnpress' ),
184 esc_html( $addon->name ?? $plugin_data['Name'] ),
185 esc_html( $new_version )
186 ),
187 'link' => sprintf(
188 ' <a href="%s">%s</a>',
189 esc_url( $link_update ),
190 esc_html__( 'Go to LearnPress Add-ons page to update.', 'learnpress' )
191 ),
192 'message_close' => '</p></div>',
193 'cell_close' => '</td>',
194 'warp-end' => '</tr>',
195 );
196
197 echo Template::combine_components( $section );
198 }
199
200 /**
201 * HTML the LearnPress Add-ons page.
202 *
203 * @return void
204 */
205 public function html_page() {
206 /** @use self::render_addons */
207 $section = apply_filters(
208 'learn-press/admin/addons/page-section',
209 array(
210 'wrapper' => '<div class="lp-addons-page">',
211 'notice' => sprintf(
212 '<div class="lp-addon-notice">%s</div>',
213 Template::print_message(
214 sprintf(
215 '<div>%s</div>',
216 sprintf(
217 __( 'To download or update add-ons: enter your purchase code (%s), or check your theme\'s plugins tab if using a bundled premium theme.', 'learnpress' ),
218 sprintf(
219 '<a href="%s">%s</a>',
220 'https://thimpress.com/my-account/',
221 __( 'get key', 'learnpress' )
222 ),
223 )
224 ),
225 'info',
226 false
227 )
228 ),
229 'subtitle' => sprintf(
230 '<p class="lp-addons-page-subtitle">%s</p>',
231 esc_html__(
232 'Discover high-performance Premium & Education themes optimized 100% for LearnPress LMS.',
233 'learnpress'
234 )
235 ),
236 'addons' => TemplateAJAX::load_content_via_ajax(
237 array(
238 'id_url' => 'data-addons',
239 'tab' => $_REQUEST['tab'] ?? '',
240 ),
241 array(
242 'class' => self::class,
243 'method' => 'render_addons',
244 )
245 ),
246 'wrapper_end' => '</div>',
247 )
248 );
249
250 echo AdminTemplate::html_on_wp_admin_screen(
251 array(
252 'content' => Template::combine_components( $section ),
253 'title' => __( 'LearnPress Add-ons', 'learnpress' ),
254 'id' => 'learn-press-addons',
255 )
256 );
257 }
258
259 /**
260 * Render add-ons list for the AJAX response.
261 *
262 * @param array $data
263 * @return stdClass
264 */
265 public static function render_addons( array $data = [] ): stdClass {
266 $response = new stdClass();
267
268 try {
269 // Check permission
270 if ( is_multisite() ? ! is_super_admin() : ! current_user_can( UserModel::ROLE_ADMINISTRATOR ) ) {
271 throw new Exception( __( 'Access denied.', 'learnpress' ) );
272 }
273
274 require_once ABSPATH . 'wp-admin/includes/plugin.php';
275
276 $addons = AddonService::instance()->get_addons();
277 $plugins_installed = get_plugins();
278 $plugins_activated = get_option( 'active_plugins', array() );
279 $active_tab = LP_Helper::sanitize_params_submitted( $data['tab'] ) ?? 'all';
280 $keys_purchase = LP_Settings::get_option( AddonService::instance()->key_purchase_addons, array() );
281
282 $section = apply_filters(
283 'learn-press/admin/addons/section',
284 array(
285 'wrapper' => '<div class="lp-addons-wrapper">',
286 'controls' => self::html_controls(
287 array(
288 'active_tab' => $active_tab,
289 )
290 ),
291 'list' => self::html_addons_list(
292 array(
293 'addons' => $addons,
294 'plugins_installed' => $plugins_installed,
295 'plugins_activated' => $plugins_activated,
296 'active_tab' => $active_tab,
297 'keys_purchase' => $keys_purchase,
298 )
299 ),
300 'wrapper_end' => '</div>',
301 ),
302 $addons,
303 $data
304 );
305
306 $response->content = Template::combine_components( $section );
307 } catch ( Throwable $e ) {
308 $response->content = Template::print_message(
309 $e->getMessage(),
310 Response::STATUS_ERROR,
311 false
312 );
313 }
314
315 return $response;
316 }
317
318 /**
319 * Render the add-ons filter controls: tabs + search.
320 *
321 * @param array $data Renderer data.
322 *
323 * @return string
324 */
325 public static function html_controls( array $data = [] ): string {
326 $active_tab = $data['active_tab'] ?? 'all';
327 $tabs = array(
328 'all' => sprintf( '%s (<span></span>)', __( 'All', 'learnpress' ) ),
329 'installed' => sprintf( '%s (<span></span>)', __( 'Installed', 'learnpress' ) ),
330 'purchase' => sprintf( '%s (<span></span>)', __( 'Paid', 'learnpress' ) ),
331 'free' => sprintf( '%s (<span></span>)', __( 'Free', 'learnpress' ) ),
332 'update' => sprintf( '%s (<span></span>)', __( 'Updated', 'learnpress' ) ),
333 'license' => sprintf( '%s (<span></span>)', __( 'License', 'learnpress' ) ),
334 'not_installed' => sprintf( '%s (<span></span>)', __( 'Not Installed', 'learnpress' ) ),
335 );
336 $addon_categories = array(
337 'create-course' => __( 'Create Course', 'learnpress' ),
338 'engagement' => __( 'Engagement', 'learnpress' ),
339 'learnpress' => __( 'LearnPress', 'learnpress' ),
340 'manage-course' => __( 'Manage Course', 'learnpress' ),
341 'marketing-optimization' => __( 'Marketing Optimization', 'learnpress' ),
342 'monetize-course' => __( 'Monetize Course', 'learnpress' ),
343 'payment' => __( 'Payment', 'learnpress' ),
344 );
345
346 $section_tabs = array();
347 foreach ( $tabs as $tab => $tab_title ) {
348 $active_class = ( $tab === $active_tab ) ? ' nav-tab-active' : '';
349
350 if ( $active_class ) {
351 $section_tabs[ $tab ] = sprintf(
352 '<a class="lp-addons-filter__item nav-tab%s" data-tab="%s" href="#" aria-pressed="true">%s</a>',
353 esc_attr( $active_class ),
354 esc_attr( $tab ),
355 wp_kses_post( $tab_title )
356 );
357 } else {
358 $section_tabs[ $tab ] = sprintf(
359 '<a class="lp-addons-filter__item nav-tab"
360 data-tab="%s" aria-pressed="false" href="?page=learn-press-addons&tab=%s">%s</a>',
361 esc_attr( $tab ),
362 esc_attr( $tab ),
363 wp_kses_post( $tab_title )
364 );
365 }
366 }
367
368 $section = array(
369 'wrapper' => '<div class="lp-addons-controls">',
370 'toolbar' => '<div class="lp-nav-tab-wrapper lp-addons-toolbar">',
371 'filter' => sprintf(
372 '<div class="lp-addons-filter" role="group" aria-label="%s">',
373 esc_attr__( 'Filter add-ons', 'learnpress' )
374 ),
375 'tabs' => Template::combine_components( $section_tabs ),
376 'filter_end' => '</div>',
377 'search' => '<label class="lp-search-addons lp-addons-search">',
378 'search_text' => sprintf(
379 '<span class="screen-reader-text">%s</span>',
380 esc_html__( 'Search add-ons', 'learnpress' )
381 ),
382 'search_icon' => '<span class="lp-addons-search__icon lp-icon-search" aria-hidden="true"></span>',
383 'search_input' => sprintf(
384 '<input id="lp-search-addons__input"
385 class="lp-addons-search__input" type="search" placeholder="%s"/>',
386 esc_attr__( 'Search add-ons by name…', 'learnpress' )
387 ),
388 'search_end' => '</label>',
389 'toolbar_end' => '</div>',
390 'categories' => self::html_categories(
391 array(
392 'addon_categories' => $addon_categories,
393 )
394 ),
395 'wrapper_end' => '</div>',
396 );
397
398 return Template::combine_components( $section );
399 }
400
401 /**
402 * Render the add-ons category filter buttons.
403 *
404 * @param array $data Renderer data.
405 *
406 * @return string
407 */
408 public static function html_categories( array $data = [] ): string {
409 $addon_categories = $data['addon_categories'] ?? array();
410 $section = array(
411 'wrapper' => sprintf(
412 '<div class="lp-addons-categories" role="group" aria-label="%s">',
413 esc_attr__( 'Filter add-ons by category', 'learnpress' )
414 ),
415 'all' => sprintf(
416 '<button type="button" class="lp-addons-category active"
417 data-category="all" aria-pressed="true">%s <span class="lp-addons-category__count"></span>
418 </button>',
419 esc_html__( 'All', 'learnpress' )
420 ),
421 );
422
423 foreach ( $addon_categories as $category_slug => $category_label ) {
424 $section[ $category_slug ] = sprintf(
425 '<button type="button" class="lp-addons-category"
426 data-category="%s" aria-pressed="false">%s <span class="lp-addons-category__count"></span>
427 </button>',
428 esc_attr( $category_slug ),
429 esc_html( $category_label )
430 );
431 }
432
433 $section['wrapper_end'] = '</div>';
434
435 return Template::combine_components( $section );
436 }
437
438 /**
439 * Render the list of add-on items.
440 *
441 * @param array $data Renderer data.
442 *
443 * @return string
444 * @throws Exception
445 */
446 public static function html_addons_list( array $data = [] ): string {
447 $addons = $data['addons'] ?? array();
448 $html = '';
449 foreach ( $addons as $slug => $addon ) {
450 $addon->slug = $slug;
451 $html .= self::html_addon_item( $addon, $data );
452 }
453
454 return sprintf( '<div id="lp-addons">%s</div>', $html );
455 }
456
457 /**
458 * Render a single add-on card.
459 *
460 * @param object $addon Current addon object.
461 * @param array $data Renderer data.
462 *
463 * @return string
464 * @throws Exception
465 */
466 public static function html_addon_item( object $addon, array $data = [] ): string {
467 $state = self::get_addon_classes_and_state( $addon, $data );
468
469 $section = apply_filters(
470 'learn-press/admin/addons/item',
471 array(
472 'wrapper' => sprintf(
473 '<div class="lp-addon-item %s" data-slug="%s" data-category="%s" data-addon="%s">',
474 esc_attr( implode( ' ', $state['classes_status'] ) ),
475 esc_attr( $addon->slug ),
476 esc_attr( $state['addon_category'] ),
477 esc_attr( htmlentities2( wp_json_encode( $addon ) ) )
478 ),
479 'content' => '<div class="lp-addon-item__content">',
480 'header' => self::html_addon_header( $addon ),
481 'license' => ! $state['is_free']
482 ? self::html_addon_license(
483 $addon,
484 array(
485 'license_status' => $state['license_status'],
486 'number_days_remaining' => $state['number_days_remaining'],
487 'date_expired' => $state['date_expired'],
488 'purchase_code_masked' => $state['purchase_code_masked'],
489 'show_license_panel' => $state['show_license_panel'],
490 )
491 )
492 : '',
493 'description' => sprintf(
494 '<p class="lp-addon-item__description" title="%s">%s</p>',
495 esc_attr( $addon->description ?? '' ),
496 esc_html( $addon->description ?? '' )
497 ),
498 'meta' => self::html_addon_meta(
499 $addon,
500 array(
501 'version_current' => $state['version_current'],
502 )
503 ),
504 'new_update' => $state['is_updated']
505 ? sprintf(
506 '<span class="new-update">%s: <strong>%s</strong></span>',
507 esc_html__( 'Latest version', 'learnpress' ),
508 esc_html( $state['version_latest'] )
509 )
510 : '',
511 'content_end' => '</div>',
512 'actions' => self::html_addon_actions(
513 $addon,
514 array(
515 'is_free' => $state['is_free'],
516 'is_installed' => $state['is_installed'],
517 'is_updated' => $state['is_updated'],
518 )
519 ),
520 'purchase' => self::html_purchase_panel(
521 $addon,
522 array(
523 'purchase_code_masked' => $state['purchase_code_masked'],
524 )
525 ),
526 'wrapper_end' => '</div>',
527 ),
528 $addon,
529 $data
530 );
531
532 return Template::combine_components( $section );
533 }
534
535 /**
536 * Compute status classes and state flags for an addon.
537 *
538 * @param object $addon Current addon object.
539 * @param array $data Addon state data.
540 *
541 * @return array
542 * @throws Exception
543 */
544 public static function get_addon_classes_and_state( object $addon, array $data = [] ): array {
545 $plugins_installed = $data['plugins_installed'] ?? array();
546 $plugins_activated = $data['plugins_activated'] ?? array();
547 $active_tab = $data['active_tab'] ?? 'all';
548 $keys_purchase = $data['keys_purchase'] ?? array();
549 $is_free = $addon->is_free ?? 0;
550 $addon_base = $addon->basename ?? '';
551 $version_latest = $addon->version ?? 0;
552 $version_current = $addon->version ?? 0;
553 $classes_status = array();
554 $addon_purchased = $addon->purchase_info ?? false;
555 $addon_category = implode( ' ', array_map( 'sanitize_title', $addon->category ?? array() ) );
556 $date_expired_str = '';
557 $number_days_remaining = null;
558 $date_expired = null;
559
560 if ( ! $is_free ) {
561 $classes_status[] = 'purchase';
562 } else {
563 $classes_status[] = 'free';
564 }
565
566 if ( isset( $plugins_installed[ $addon_base ] ) ) {
567 $is_installed = true;
568 $classes_status[] = 'installed';
569 $version_current = $plugins_installed[ $addon_base ]['Version'];
570 } else {
571 $is_installed = false;
572 $classes_status[] = 'not_installed';
573 }
574
575 if ( in_array( $addon_base, $plugins_activated, true ) ) {
576 $classes_status[] = 'activated';
577 }
578
579 $is_updated = false;
580 if ( $is_installed && version_compare( $version_current, $version_latest, '<' ) ) {
581 $classes_status[] = 'update';
582 $is_updated = true;
583 }
584
585 if ( $addon_purchased ) {
586 $classes_status[] = 'license';
587 $date_expired_str = $addon_purchased->date_expire ?? '';
588 if ( ! empty( $date_expired_str ) ) {
589 $date_expired = new DateTime( $date_expired_str );
590 $date_now = new DateTime( gmdate( 'Y-m-d' ) );
591 $date_diff = date_diff( $date_now, $date_expired );
592 $number_days_remaining = $date_diff->invert ? 0 : $date_diff->days;
593 }
594 }
595
596 $show_license_panel = ! $is_free && $is_installed;
597 $license_status = 'not-activated';
598 if ( $addon_purchased ) {
599 $license_status = 'active';
600 if ( ! empty( $date_expired_str )
601 && isset( $number_days_remaining )
602 && 0 === $number_days_remaining ) {
603 $license_status = 'expired';
604 $classes_status[] = 'expired';
605 }
606 }
607
608 $purchase_code_masked = AddonService::mask_purchase_code( $keys_purchase[ $addon->slug ] ?? '' );
609
610 if ( ! in_array( $active_tab, $classes_status, true ) && 'all' !== $active_tab ) {
611 $classes_status[] = 'hide';
612 }
613
614 return array(
615 'classes_status' => $classes_status,
616 'is_free' => $is_free,
617 'is_installed' => $is_installed,
618 'is_updated' => $is_updated,
619 'version_current' => $version_current,
620 'version_latest' => $version_latest,
621 'license_status' => $license_status,
622 'number_days_remaining' => $number_days_remaining,
623 'date_expired' => $date_expired,
624 'purchase_code_masked' => $purchase_code_masked,
625 'show_license_panel' => $show_license_panel,
626 'addon_category' => $addon_category,
627 );
628 }
629
630 /**
631 * Render the header section of an addon item.
632 *
633 * @param object $addon Current addon object.
634 * @param array $data Renderer data.
635 *
636 * @return string
637 */
638 public static function html_addon_header( object $addon, array $data = [] ): string {
639 $section = array(
640 'wrapper' => '<div class="lp-addon-item__header">',
641 'image' => sprintf(
642 '<img class="lp-addon-item__image" src="%s" alt=""/>',
643 esc_url( $addon->image ?? '' )
644 ),
645 'heading' => '<div class="lp-addon-item__heading">',
646 'title' => '<h3 class="lp-addon-item__title">',
647 'link' => sprintf(
648 '<a href="%s" target="_blank" rel="noopener">%s</a>',
649 esc_url( $addon->link ?? '' ),
650 esc_html( $addon->name ?? '' )
651 ),
652 'title_end' => '</h3>',
653 'badge' => ! empty( $addon->badge ) ? sprintf(
654 '<span class="lp-addon-item__badge">%s</span>',
655 esc_html( $addon->badge )
656 ) : '',
657 'heading_end' => '</div>',
658 'wrapper_end' => '</div>',
659 );
660
661 return Template::combine_components( $section );
662 }
663
664 /**
665 * Render the license status block for a paid addon.
666 *
667 * @param object $addon Current addon object.
668 * @param array $data Renderer data.
669 *
670 * @return string
671 */
672 public static function html_addon_license( object $addon, array $data = [] ): string {
673 $license_status = $data['license_status'] ?? '';
674 $number_days_remaining = $data['number_days_remaining'] ?? null;
675 /** @var DateTime $date_expired */
676 $date_expired = $data['date_expired'] ?? null;
677 $purchase_code_masked = $data['purchase_code_masked'] ?? '';
678 $show_license_panel = $data['show_license_panel'] ?? false;
679 $license_status_label = 'active' === $license_status
680 ? __( 'Active', 'learnpress' )
681 : ( 'expired' === $license_status
682 ? __( 'Expired', 'learnpress' )
683 : ( 'deactivated' === $license_status
684 ? __( 'Deactivated', 'learnpress' )
685 : __( 'Not Activated', 'learnpress' ) ) );
686
687 $expiry_hidden = empty( $date_expired )
688 || ! in_array( $license_status, array( 'active', 'expired' ), true ) ? ' hidden' : '';
689
690 $expiry_text = '';
691 if ( ! empty( $date_expired ) ) {
692 $lpDate = new LPDateTime( $date_expired->format( LPDateTime::FORMAT_MYSQL ) );
693
694 $expiry_format = 'expired' === $license_status
695 ? __( '(on %s)', 'learnpress' )
696 : __( '(Updates until %s)', 'learnpress' );
697 $expiry_text = esc_html( sprintf( $expiry_format, $lpDate->format( LPDateTime::FORMAT_I18N_DATE ) ) );
698 }
699
700 $extend_link = '';
701 if ( 'expired' === $license_status ) {
702 $extend_link = sprintf(
703 '<a class="need-extend__link" href="%s" target="_blank" rel="noopener">%s</a>',
704 esc_url( $addon->link ?? '' ),
705 esc_html__( 'Extend now', 'learnpress' )
706 );
707 }
708
709 $message = '';
710 $button_extends = '';
711 if ( ! empty( $addon->purchase_info ) ) {
712 $button_extends = sprintf(
713 '<a class="need-extend__link" href="%s" target="_blank" rel="noopener">%s</a>',
714 esc_url( $addon->link ?? '' ),
715 esc_html__( 'Extend now', 'learnpress' )
716 );
717
718 if ( isset( $number_days_remaining ) && $number_days_remaining > 0 && $number_days_remaining < 61 ) {
719 $message = sprintf(
720 __( 'You have a license for this item with %s day(s) of update & support remaining. Please extend your update & support license to continue receiving the latest versions and customer support from Thimpress before it expires.', 'learnpress' ),
721 sprintf( '<strong class="need-extend__days">%d</strong>', $number_days_remaining )
722 );
723 } else {
724 $button_extends = '';
725 }
726 }
727
728 $need_extend = '';
729 if ( ! empty( $message ) || ! empty( $button_extends ) ) {
730 $need_extend = sprintf(
731 '<span class="need-extend">%s %s</span>',
732 $message,
733 $button_extends
734 );
735 }
736
737 $section = array(
738 'wrapper' => sprintf(
739 '<div class="lp-addon-license lp-addon-license--%s" data-purchase-code-masked="%s"%s>',
740 esc_attr( $license_status ),
741 esc_attr( $purchase_code_masked ),
742 $show_license_panel ? '' : ' hidden'
743 ),
744 'summary' => '<div class="lp-addon-license__summary">',
745 'label' => sprintf(
746 '<span>%s</span>',
747 esc_html__( 'License:', 'learnpress' )
748 ),
749 'status' => sprintf(
750 '<strong class="lp-addon-license__status">%s</strong>',
751 esc_html( $license_status_label )
752 ),
753 'expiry' => sprintf(
754 '<span class="lp-addon-license__expiry"%s>%s</span>',
755 $expiry_hidden,
756 $expiry_text
757 ),
758 'extend_link' => $extend_link,
759 'summary_end' => '</div>',
760 'need_extend' => $need_extend,
761 'manage' => sprintf(
762 '<button class="btn-addon-action lp-addon-license__manage"
763 data-action="update-purchase-code" type="button">%s</button>',
764 esc_html__( 'Manage', 'learnpress' )
765 ),
766 'wrapper_end' => '</div>',
767 );
768
769 return Template::combine_components( $section );
770 }
771
772 /**
773 * Render addon meta: version, docs, view details.
774 *
775 * @param object $addon Current addon object.
776 * @param array $data Renderer data.
777 *
778 * @return string
779 */
780 public static function html_addon_meta( object $addon, array $data = [] ): string {
781 $version_current = $data['version_current'] ?? '';
782 $section = array(
783 'wrapper' => '<div class="lp-addon-item__meta">',
784 'version' => $version_current
785 ? sprintf(
786 '<span>%s <span class="addon-version-current">%s</span></span>',
787 esc_html__( 'Version', 'learnpress' ),
788 esc_html( $version_current )
789 )
790 : '',
791 'docs' => ! empty( $addon->link_doc )
792 ? sprintf(
793 '<a href="%s" target="_blank" rel="noopener">%s</a>',
794 esc_url( $addon->link_doc ),
795 esc_html__( 'Docs', 'learnpress' )
796 )
797 : '',
798 'details' => sprintf(
799 '<a href="%s" target="_blank" rel="noopener">%s</a>',
800 esc_url( $addon->link ?? '' ),
801 esc_html__( 'View Details', 'learnpress' )
802 ),
803 'settings' => ! empty( $addon->setting )
804 ? sprintf(
805 '<a data-action="setting" href="%s" target="_blank" rel="noopener">%s</a>',
806 esc_url( site_url( $addon->setting ) ),
807 esc_html__( 'Settings', 'learnpress' )
808 )
809 : '',
810 'wrapper_end' => '</div>',
811 );
812
813 return Template::combine_components( $section );
814 }
815
816 /**
817 * Render addon price block.
818 *
819 * @param object $addon Current addon object.
820 * @param array $data Renderer data.
821 *
822 * @return string
823 */
824 public static function html_addon_price( object $addon, array $data = [] ): string {
825 $is_free = $addon->is_free ?? 0;
826
827 if ( $is_free ) {
828 return sprintf(
829 '<strong class="lp-addon-item__price-regular lp-addon-item__price-regular--free">%s</strong>',
830 esc_html__( 'Free', 'learnpress' )
831 );
832 }
833
834 if ( ! empty( $addon->sale_price ) ) {
835 $section = array(
836 'sale' => sprintf(
837 '<strong class="lp-addon-item__price-sale">$%s</strong>',
838 esc_html( number_format_i18n( $addon->sale_price ) )
839 ),
840 'regular' => sprintf(
841 '<del class="lp-addon-item__price-regular">$%s</del>',
842 esc_html( number_format_i18n( $addon->regular_price ?? 0 ) )
843 ),
844 );
845
846 return Template::combine_components( $section );
847 }
848
849 return sprintf(
850 '<strong class="lp-addon-item__price-regular lp-addon-item__price-regular--current">$%s</strong>',
851 esc_html( number_format_i18n( $addon->regular_price ?? 0 ) )
852 );
853 }
854
855 /**
856 * Render addon action buttons.
857 *
858 * @param object $addon Current addon object.
859 * @param array $data Renderer data.
860 *
861 * @return string
862 */
863 public static function html_addon_actions( object $addon, array $data = [] ): string {
864 $is_free = $data['is_free'] ?? false;
865 $section_right = apply_filters(
866 'learn-press/admin/addons/actions/section_right',
867 array(
868 'install' => $is_free
869 ? sprintf(
870 '<a class="lp-button btn-addon-action" data-action="install" href="%s"
871 target="_blank" rel="noopener">%s</a>',
872 esc_url( $addon->link ?? '' ),
873 esc_html__( 'Install', 'learnpress' )
874 )
875 : sprintf(
876 '<button class="lp-button btn-addon-action" data-action="install">
877 <span class="text">%s</span>
878 </button>',
879 esc_html__( 'Install', 'learnpress' )
880 ),
881 'purchase' => sprintf(
882 '<button class="lp-button btn-addon-action" data-action="purchase">%s</button>',
883 esc_html__( 'Install', 'learnpress' )
884 ),
885 'update' => sprintf(
886 '<button class="lp-button btn-addon-action" data-action="update">
887 <span class="text">%s</span>
888 </button>',
889 esc_html__( 'Update', 'learnpress' )
890 ),
891 'deactivate' => sprintf(
892 '<button class="lp-button btn-addon-action" data-action="deactivate">
893 <span class="text">%s</span>
894 </button>',
895 esc_html__( 'Deactivate', 'learnpress' )
896 ),
897 'activate' => sprintf(
898 '<button class="lp-button btn-addon-action" data-action="activate">
899 <span class="text">%s</span>
900 </button>',
901 esc_html__( 'Activate', 'learnpress' )
902 ),
903 ),
904 $addon,
905 $data
906 );
907
908 $section = array(
909 'wrapper' => '<div class="lp-addon-item__actions">',
910 'actions_left' => '<div class="lp-addon-item__actions__left">',
911 'price' => sprintf(
912 '<div class="lp-addon-item__price">%s</div>',
913 self::html_addon_price( $addon )
914 ),
915 'actions_left_end' => '</div>',
916 'actions_right' => '<div class="lp-addon-item__actions__right">',
917 'actions' => Template::combine_components( $section_right ),
918 'actions_right_end' => '</div>',
919 'wrapper_end' => '</div>',
920 );
921
922 return Template::combine_components( $section );
923 }
924
925 /**
926 * Render the purchase-code overlay panel.
927 *
928 * @param object $addon Current addon object.
929 * @param array $data Renderer data.
930 *
931 * @return string
932 */
933 public static function html_purchase_panel( object $addon, array $data = [] ): string {
934 $panel = array(
935 'wrapper' => '<div class="purchase-install">',
936 'header' => '<div class="lp-addon-purchase__header">',
937 'title' => sprintf(
938 '<strong class="lp-addon-purchase__title">%s</strong>',
939 esc_html__( 'Purchase Code', 'learnpress' )
940 ),
941 'close' => sprintf(
942 '<button class="btn-addon-action
943 lp-addon-purchase__close" data-action="cancel" type="button" aria-label="%s">',
944 esc_attr__( 'Close', 'learnpress' )
945 ),
946 'close_icon' => '<span class="lp-addon-purchase__close-symbol" aria-hidden="true">&times;</span>',
947 'close_end' => '</button>',
948 'header_end' => '</div>',
949 'field' => '<label class="lp-addon-purchase__field">',
950 'field_text' => sprintf(
951 '<span class="screen-reader-text">%s</span>',
952 esc_html__( 'Purchase Code', 'learnpress' )
953 ),
954 'field_input' => sprintf(
955 '<input type="text" class="enter-purchase-code" placeholder="%s" value="">',
956 esc_attr__( 'Enter Purchase Code', 'learnpress' )
957 ),
958 'field_end' => '</label>',
959 'submit' => '<button class="lp-button btn-addon-action lp-addon-purchase__submit"
960 data-action="install" type="button">',
961 'submit_text' => sprintf( '<span class="text">%s</span>', esc_html__( 'Submit', 'learnpress' ) ),
962 'submit_end' => '</button>',
963 'divider' => sprintf(
964 '<div class="lp-addon-purchase__divider"><span>%s</span></div>',
965 esc_html__( 'Don\'t have a code?', 'learnpress' )
966 ),
967 'buy' => sprintf(
968 '<a class="btn-addon-action lp-addon-purchase__buy" href="%s" target="_blank" rel="noopener">%s</a>',
969 esc_url( $addon->link ?? '' ),
970 esc_html__( 'Buy Now', 'learnpress' )
971 ),
972 'wrapper_end' => '</div>',
973 );
974
975 $section = array(
976 'wrapper' => '<div class="lp-addon-item__purchase">',
977 'purchase' => '<div class="lp-addon-item__purchase__wrapper">',
978 'panel' => Template::combine_components( $panel ),
979 'purchase_code' => '<input type="hidden" name="purchase-code" value="">',
980 'purchase_end' => '</div>',
981 'wrapper_end' => '</div>',
982 );
983
984 return Template::combine_components( $section );
985 }
986 }
987