PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
mainwp / modules / cost-tracker / classes / class-cost-tracker-admin.php

class-cost-tracker-admin.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.2.1, at modules/cost-tracker/classes/class-cost-tracker-admin.php

1,423 lines 56.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MainWP Module Cost Tracker Admin class.
4 *
5 * @package MainWP\Dashboard
6 * @version 4.6
7 */
8
9 namespace MainWP\Dashboard\Module\CostTracker;
10
11 use MainWP\Dashboard\MainWP_Menu;
12 use MainWP\Dashboard\MainWP_UI;
13 use MainWP\Dashboard\MainWP_DB;
14 use MainWP\Dashboard\MainWP_Utility;
15 use MainWP\Dashboard\MainWP_System_Utility;
16 use MainWP\Dashboard\MainWP_Logger;
17 use MainWP\Dashboard\MainWP_Post_Handler;
18 use MainWP\Dashboard\MainWP_Settings;
19 use MainWP\Dashboard\MainWP_Settings_Indicator;
20 use MainWP\Dashboard\MainWP_Exception;
21 use MainWP\Dashboard\Module\CostTracker\Cost_Tracker_Import;
22
23 // Exit if accessed directly.
24 if ( ! defined( 'ABSPATH' ) ) {
25 exit;
26 }
27
28 /**
29 * Class Cost_Tracker_Admin
30 */
31 class Cost_Tracker_Admin { // phpcs:ignore -- NOSONAR - multi methods.
32
33 /**
34 * Variable to hold the version number.
35 *
36 * @var mixed Version.
37 */
38 public $version = '1.0';
39
40 /**
41 * Variable to hold the Page value.
42 *
43 * @static
44 * @var mixed Page value, default null.
45 */
46 public static $page = null;
47
48 /**
49 * Variable to hold the Sub Pages.
50 *
51 * @static
52 * @var mixed Sub Page, default null.
53 */
54 public static $subPages = null;
55
56 /**
57 * Static variable to hold the single instance of the class.
58 *
59 * @static
60 *
61 * @var mixed Default null
62 */
63 public static $instance = null;
64
65 /**
66 * Get Instance
67 *
68 * Creates public static instance.
69 *
70 * @static
71 *
72 * @return Cost_Tracker_Admin
73 */
74 public static function get_instance() {
75 if ( null === static::$instance ) {
76 static::$instance = new self();
77 }
78 return static::$instance;
79 }
80
81 /**
82 * Constructor
83 *
84 * Runs each time the class is called.
85 */
86 public function __construct() {
87 add_action( 'init', array( &$this, 'init' ) );
88 add_filter( 'mainwp_init_primary_menu_items', array( $this, 'hook_init_primary_menu_items' ), 10, 2 );
89 add_filter( 'mainwp_module_cost_tracker_get_default_cost_fields', array( $this, 'hook_get_default_cost_fields' ), 10, 2 );
90 add_filter( 'mainwp_module_cost_tracker_get_next_renewal', array( $this, 'hook_get_next_renewal' ), 10, 3 );
91 add_action( 'mainwp_delete_site', array( $this, 'hook_delete_site' ), 10, 3 );
92 add_action( 'mainwp_client_deleted', array( $this, 'hook_delete_client' ) );
93 add_action( 'mainwp_site_tag_action', array( $this, 'hook_site_tag_action' ), 10, 2 );
94 add_filter( 'mainwp_module_cost_tracker_get_total_cost', array( $this, 'hook_get_total_cost' ), 10, 2 );
95 }
96
97
98 /**
99 * Initiate Hooks
100 *
101 * Initiates hooks for the Cost Tracker module.
102 */
103 public function init() {
104 add_action( 'admin_init', array( &$this, 'admin_init' ) );
105 add_action( 'mainwp_help_sidebar_content', array( $this, 'mainwp_help_content' ) );
106 add_filter( 'mainwp_log_specific_actions', array( $this, 'hook_log_specific_actions' ) );
107 Cost_Tracker_Hooks::get_instance()->init();
108 Cost_Tracker_Dashboard::get_instance();
109 Cost_Tracker_DB::get_instance()->init();
110 $this->handle_sites_screen_settings();
111 }
112
113 /**
114 * Method hook_delete_site()
115 *
116 * Installs the new DB.
117 *
118 * @param mixed $site site object.
119 *
120 * @return bool result.
121 */
122 public function hook_delete_site( $site ) {
123 if ( empty( $site ) ) {
124 return false;
125 }
126 return MainWP_DB::instance()->delete_lookup_items(
127 'object_id',
128 array(
129 'item_name' => 'cost',
130 'object_id' => $site->id,
131 'object_names' => array( 'site' ),
132 )
133 );
134 }
135
136 /**
137 * Method hook_delete_client()
138 *
139 * Prunes the cost lookup rows of a deleted client. Left behind, they keep matching the
140 * client filter on the Costs page long after the client is gone.
141 *
142 * @param mixed $client client object.
143 *
144 * @return bool result.
145 */
146 public function hook_delete_client( $client ) {
147 if ( empty( $client ) || empty( $client->client_id ) ) {
148 return false;
149 }
150 return MainWP_DB::instance()->delete_lookup_items(
151 'object_id',
152 array(
153 'item_name' => 'cost',
154 'object_id' => $client->client_id,
155 'object_names' => array( 'client' ),
156 )
157 );
158 }
159
160 /**
161 * Method hook_site_tag_action()
162 *
163 * Prunes the cost lookup rows of a deleted tag. The hook also fires on create and update,
164 * which leave the rows alone.
165 *
166 * @param mixed $group tag object.
167 * @param string $action tag action.
168 *
169 * @return bool result.
170 */
171 public function hook_site_tag_action( $group, $action ) {
172 if ( 'deleted' !== $action || empty( $group ) || empty( $group->id ) ) {
173 return false;
174 }
175 return MainWP_DB::instance()->delete_lookup_items(
176 'object_id',
177 array(
178 'item_name' => 'cost',
179 'object_id' => $group->id,
180 'object_names' => array( 'tag' ),
181 )
182 );
183 }
184
185 /**
186 * Admin Init
187 *
188 * Initiates admin hooks.
189 */
190 public function admin_init() {
191
192 MainWP_Post_Handler::instance()->add_action( 'mainwp_module_cost_tracker_upload_product_icon', array( Cost_Tracker_Add_Edit::get_instance(), 'ajax_upload_product_icon' ) );
193 MainWP_Post_Handler::instance()->add_action( 'mainwp_cost_tracker_import_cost', array( static::class, 'ajax_import_cost' ) );
194
195 $this->handle_edit_cost_tracker_post();
196 $this->handle_settings_post();
197
198 $allow_pages = array( 'ManageCostTracker', 'CostTrackerAdd', 'CostTrackerSettings', 'CostSummary', 'CostTrackerImport' );
199 if ( isset( $_GET['page'] ) && in_array( $_GET['page'], $allow_pages, true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
200 $base_url = Cost_Tracker_Manager::get_location_path( 'url' );
201 wp_enqueue_script( 'mainwp-module-cost-tracker-extension', $base_url . 'ui/js/cost-tracker.js', array( 'jquery', 'mainwp' ), $this->version, true );
202 if ( 'CostSummary' === $_GET['page'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
203 add_filter(
204 'mainwp_admin_enqueue_scripts',
205 function ( $scripts ) {
206 if ( is_array( $scripts ) ) {
207 $scripts['apexcharts'] = true;
208 }
209 return $scripts;
210 }
211 );
212 }
213 if ( 'CostTrackerImport' === $_GET['page'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
214 wp_enqueue_script( 'mainwp-module-cost-tracker-import', $base_url . 'ui/js/cost-tracker-import.js', array( 'jquery' ), $this->version, true );
215 }
216 }
217 }
218
219 /**
220 * Hook hook_log_specific_actions.
221 *
222 * @param array $logs_spec Log actions.
223 *
224 * @return array $logs_spec Log actions.
225 */
226 public function hook_log_specific_actions( $logs_spec ) {
227 $logs_spec[ MainWP_Logger::COST_TRACKER_LOG_PRIORITY ] = __( 'Cost Tracker', 'mainwp' );
228 return $logs_spec;
229 }
230
231 /**
232 * Init sub menu logs settings.
233 *
234 * @param array $items Sub menu items.
235 * @param string $which_menu first|second.
236 *
237 * @return array $tmp_items Menu items.
238 */
239 public function hook_init_primary_menu_items( $items, $which_menu ) {
240 if ( ! is_array( $items ) || 'first' !== $which_menu ) {
241 return $items;
242 }
243 $items[] = array(
244 'slug' => 'ManageCostTracker',
245 'menu_level' => 2,
246 'menu_rights' => array(
247 'dashboard' => array(
248 'manage_cost_tracker',
249 ),
250 ),
251 'init_menu_callback' => array( static::class, 'init_menu' ),
252 'leftbar_order' => 2.8,
253 );
254 return $items;
255 }
256
257 /**
258 * Method hook_get_default_cost_fields().
259 *
260 * @param string $def_val Default value.
261 * @param string $field Field name.
262 *
263 * @return array Default fields values.
264 */
265 public function hook_get_default_cost_fields( $def_val = null, $field = 'all' ) {
266 unset( $def_val );
267 return static::get_default_fields_values( $field );
268 }
269
270 /**
271 * Method get_default_fields_values().
272 *
273 * @param string $field Field name.
274 *
275 * @return array Default fields values.
276 */
277 public static function get_default_fields_values( $field = 'all' ) {
278 if ( empty( $field ) || ! is_string( $field ) ) {
279 $field = 'all';
280 }
281
282 $license_types = array(
283 'single_site' => esc_html__( 'Single-Site License', 'mainwp' ),
284 'multi_site' => esc_html__( 'Multiple-Site License', 'mainwp' ),
285 );
286 $product_types = static::get_product_types();
287 $payment_types = array(
288 'subscription' => esc_html__( 'Recurring (Subscription)', 'mainwp' ),
289 'lifetime' => esc_html__( 'One-time (Lifetime)', 'mainwp' ),
290 );
291 $payment_methods = static::get_payment_methods();
292 $renewal_frequency = static::get_renewal_frequency();
293 $cost_status = static::get_cost_status();
294
295 $all_defaults = array(
296 'license_types' => $license_types,
297 'product_types' => $product_types,
298 'payment_types' => $payment_types,
299 'payment_methods' => $payment_methods,
300 'renewal_frequency' => $renewal_frequency,
301 'cost_status' => $cost_status,
302 );
303
304 if ( 'all' === $field ) {
305 return $all_defaults;
306 }
307 return isset( $all_defaults[ $field ] ) ? $all_defaults[ $field ] : array();
308 }
309
310
311 /**
312 * Method init_menu()
313 *
314 * Add Insights Overview sub menu "Insights".
315 */
316 public static function init_menu() {
317
318 static::$page = add_submenu_page(
319 'mainwp_tab',
320 esc_html__( 'Cost Tracker', 'mainwp' ),
321 '<span id="mainwp-cost-tracker-summary">' . esc_html__( 'Cost Tracker', 'mainwp' ) . '</span>',
322 'read',
323 'CostSummary',
324 array(
325 Cost_Tracker_Summary::instance(),
326 'render_summary_page',
327 )
328 );
329
330 add_submenu_page(
331 'mainwp_tab',
332 esc_html__( 'Manage Cost Tracker', 'mainwp' ),
333 '<div class="mainwp-hidden">' . esc_html__( 'Manage Cost Tracker', 'mainwp' ) . '</div>',
334 'read',
335 'ManageCostTracker',
336 array(
337 Cost_Tracker_Dashboard::get_instance(),
338 'render_overview_page',
339 )
340 );
341
342 add_submenu_page(
343 'mainwp_tab',
344 esc_html__( 'Add New', 'mainwp' ),
345 '<div class="mainwp-hidden">' . esc_html__( 'Add New', 'mainwp' ) . '</div>',
346 'read',
347 'CostTrackerAdd',
348 array(
349 Cost_Tracker_Add_Edit::get_instance(),
350 'render_add_edit_page',
351 )
352 );
353
354 add_submenu_page(
355 'mainwp_tab',
356 esc_html__( 'Import Costs', 'mainwp' ),
357 '<div class="mainwp-hidden">' . esc_html__( 'Import Costs', 'mainwp' ) . '</div>',
358 'read',
359 'CostTrackerImport',
360 array(
361 Cost_Tracker_Import::get_instance(),
362 'render_import_costs',
363 )
364 );
365
366 /**
367 * This hook allows you to add extra sub pages to the client page via the 'mainwp_getsubpages_cost_tracker' filter.
368 */
369 static::$subPages = apply_filters( 'mainwp_getsubpages_cost_tracker', array() );
370
371 if ( isset( static::$subPages ) && is_array( static::$subPages ) ) {
372 foreach ( static::$subPages as $subPage ) {
373 if ( empty( $subPage['slug'] ) || empty( $subPage['callback'] ) ) {
374 continue;
375 }
376 if ( MainWP_Menu::is_disable_menu_item( 3, 'ManageCostTracker' . $subPage['slug'] ) ) {
377 continue;
378 }
379 add_submenu_page( 'mainwp_tab', $subPage['title'], '<div class="mainwp-hidden">' . esc_html( $subPage['title'] ) . '</div>', 'read', 'ManageCostTracker' . $subPage['slug'], $subPage['callback'] );
380 }
381 }
382 add_action( 'load-' . static::$page, array( static::class, 'on_load_summary_page' ) );
383
384 if ( isset( $_GET['page'] ) && 'CostSummary' === $_GET['page'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
385 add_filter( 'mainwp_enqueue_script_gridster', '__return_true' );
386 }
387
388 static::init_left_menu( static::$subPages );
389 }
390
391 /**
392 * Method on_load_summary_page()
393 *
394 * Run on page load.
395 */
396 public static function on_load_summary_page() {
397 if ( ! \mainwp_current_user_can( 'dashboard', 'access_cost_summary_dashboard' ) && \mainwp_current_user_can( 'dashboard', 'manage_cost_tracker' ) ) {
398 wp_safe_redirect( 'admin.php?page=ManageCostTracker' );
399 exit();
400 }
401 Cost_Tracker_Summary::instance()->on_load_page( static::$page );
402 }
403
404 /**
405 * Initiates Cost Tracker menu.
406 *
407 * @param array $subPages Cost Tracker sub pages.
408 */
409 public static function init_left_menu( $subPages ) {
410 MainWP_Menu::add_left_menu(
411 array(
412 'title' => esc_html__( 'Costs', 'mainwp' ),
413 'parent_key' => 'mainwp_tab',
414 'slug' => 'CostSummary',
415 'href' => 'admin.php?page=CostSummary',
416 'icon' => '<i class="dollar sign icon"></i>',
417 'desc' => 'Costr Tracker Summary',
418 'nosubmenu' => true,
419 ),
420 0
421 );
422
423 $sum_item_class = '';
424
425 if ( ! \mainwp_current_user_can( 'dashboard', 'access_cost_summary_dashboard' ) ) {
426 $sum_item_class = 'hidden-menu-item';
427 }
428
429 $init_sub_subleftmenu = array(
430 array(
431 'title' => esc_html__( 'Cost Summary', 'mainwp' ),
432 'parent_key' => 'CostSummary',
433 'href' => 'admin.php?page=CostSummary',
434 'slug' => 'CostSummary',
435 'right' => 'manage_cost_tracker',
436 'item_class' => $sum_item_class,
437 ),
438 array(
439 'title' => esc_html__( 'Manage Costs', 'mainwp' ),
440 'parent_key' => 'CostSummary',
441 'href' => 'admin.php?page=ManageCostTracker',
442 'slug' => 'ManageCostTracker',
443 'right' => 'manage_cost_tracker',
444 ),
445 array(
446 'title' => esc_html__( 'Add New', 'mainwp' ),
447 'parent_key' => 'CostSummary',
448 'href' => 'admin.php?page=CostTrackerAdd',
449 'slug' => 'CostTrackerAdd',
450 'right' => '',
451 ),
452 array(
453 'title' => esc_html__( 'Import Costs', 'mainwp' ),
454 'parent_key' => 'CostSummary',
455 'href' => 'admin.php?page=CostTrackerImport',
456 'slug' => 'CostTrackerImport',
457 'right' => 'manage_cost_tracker',
458 ),
459 );
460
461 MainWP_Menu::init_subpages_left_menu( $subPages, $init_sub_subleftmenu, 'ManageCostTracker', 'ManageCostTracker' );
462
463 foreach ( $init_sub_subleftmenu as $item ) {
464 if ( MainWP_Menu::is_disable_menu_item( 3, $item['slug'] ) ) {
465 continue;
466 }
467 MainWP_Menu::add_left_menu( $item, 2 );
468 }
469 }
470
471
472
473 /**
474 * Method render_header()
475 *
476 * Render page header.
477 *
478 * @param string $shownPage Current Page.
479 */
480 public static function render_header( $shownPage = '' ) { //phpcs:ignore -- NOSONAR - complex.
481 $title = esc_html__( 'Operational Costs', 'mainwp' );
482 if ( 'edit' === $shownPage ) {
483 $title = esc_html__( 'Edit Operational Cost', 'mainwp' );
484 } elseif ( 'add' === $shownPage ) {
485 $title = esc_html__( 'Add Operational Cost', 'mainwp' );
486 }
487
488 $params = array(
489 'title' => $title,
490 'which' => 'page_cost_tracker_overview',
491 'wrap_class' => 'mainwp-module-cost-tracker-content-wrapper',
492 );
493
494 MainWP_UI::render_top_header( $params );
495
496 $renderItems = array();
497
498 if ( \mainwp_current_user_can( 'dashboard', 'manage_cost_tracker' ) ) {
499 $renderItems[] = array(
500 'title' => esc_html__( 'Operations', 'mainwp' ),
501 'href' => 'admin.php?page=ManageCostTracker',
502 'active' => ( '' === $shownPage ) ? true : false,
503 );
504 }
505
506 if ( ! MainWP_Menu::is_disable_menu_item( 3, 'CostTrackerAdd' ) ) {
507 $renderItems[] = array(
508 'title' => esc_html__( 'Add New', 'mainwp' ),
509 'href' => 'admin.php?page=CostTrackerAdd',
510 'active' => ( 'add' === $shownPage ) ? true : false,
511 );
512 }
513
514 if ( ! MainWP_Menu::is_disable_menu_item( 3, 'CostTrackerImport' ) ) {
515 $renderItems[] = array(
516 'title' => esc_html__( 'Import Costs', 'mainwp' ),
517 'href' => 'admin.php?page=CostTrackerImport',
518 'active' => ( 'ImportCosts' === $shownPage ) ? true : false,
519 );
520 }
521
522 $cost_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
523 if ( $cost_id ) {
524 $renderItems[] = array(
525 'title' => esc_html__( 'Edit Cost Tracker', 'mainwp' ),
526 'href' => 'admin.php?page=CostTrackerAdd&id=' . $cost_id,
527 'active' => ( 'edit' === $shownPage ) ? true : false,
528 );
529 }
530
531 if ( isset( static::$subPages ) && is_array( static::$subPages ) ) {
532 foreach ( static::$subPages as $subPage ) {
533 if ( MainWP_Menu::is_disable_menu_item( 3, 'ManageCostTracker' . $subPage['slug'] ) ) {
534 continue;
535 }
536
537 $item = array();
538 $item['title'] = $subPage['title'];
539
540 $item['href'] = 'admin.php?page=ManageCostTracker' . $subPage['slug'];
541 $item['active'] = ( $subPage['slug'] === $shownPage ) ? true : false;
542 $renderItems[] = $item;
543 }
544 }
545
546 MainWP_UI::render_page_navigation( $renderItems );
547 }
548
549 /**
550 * Edit subscription Post
551 *
552 * Handles the saving subscription.
553 *
554 * @return mixed Save output.
555 */
556 public static function handle_edit_cost_tracker_post() { //phpcs:ignore -- NOSONAR - complex method.
557
558 $updating = ! empty( $_POST['mainwp_module_cost_tracker_edit_id'] ) ? intval( $_POST['mainwp_module_cost_tracker_edit_id'] ) : ''; //phpcs:ignore -- -- NOSONAR - ok.
559
560 if ( ! isset( $_POST['mwp_cost_tracker_editing_submit'] ) || ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'module_cost_tracker_edit_nonce' . $updating ) ) {
561 return;
562 }
563 //phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
564 $last_renewal = isset( $_POST['mainwp_module_cost_tracker_edit_last_renewal'] ) ? strtotime( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_last_renewal'] ) ) : 0;
565 $update = array();
566 $update['name'] = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_name'] ) );
567 $update['type'] = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_payment_type'] ) );
568 $update['product_type'] = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_product_type'] ) );
569 $update['slug'] = isset( $_POST['mainwp_module_cost_tracker_edit_product_slug'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_product_slug'] ) ) : '';
570 $update['license_type'] = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_license_type'] ) );
571 $update['cost_status'] = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_cost_tracker_status'] ) );
572 $update['url'] = ! empty( $_POST['mainwp_module_cost_tracker_edit_url'] ) ? esc_url_raw( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_url'] ) ) : '';
573 $update['cost_icon'] = ! empty( $_POST['mainwp_module_cost_tracker_edit_icon_hidden'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_icon_hidden'] ) ) : '';
574 $update['cost_color'] = sanitize_hex_color( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_product_color'] ) );
575 $update['price'] = floatval( $_POST['mainwp_module_cost_tracker_edit_price'] );
576 $update['payment_method'] = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_payment_method'] ) );
577
578 $renewal_fequency = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_renewal_type'] ) );
579 $update['renewal_type'] = $renewal_fequency;
580 $update['last_renewal'] = $last_renewal; // labeled Purchase date.
581
582 $next_renewal = static::get_next_renewal( $last_renewal, $renewal_fequency );
583 $update['next_renewal'] = $next_renewal;
584
585 $note = isset( $_POST['mainwp_module_cost_tracker_edit_note'] ) ? wp_unslash( $_POST['mainwp_module_cost_tracker_edit_note'] ) : '';
586 $esc_note = apply_filters( 'mainwp_escape_content', $note );
587 $update['note'] = $esc_note;
588
589 $selected_sites = array();
590 $selected_groups = array();
591 $selected_clients = array();
592
593 if ( isset( $_POST['select_by'] ) ) {
594 if ( isset( $_POST['selected_sites'] ) && is_array( $_POST['selected_sites'] ) ) {
595 foreach ( wp_unslash( $_POST['selected_sites'] ) as $selected ) {
596 $selected_sites[] = intval( $selected );
597 }
598 }
599
600 if ( isset( $_POST['selected_groups'] ) && is_array( $_POST['selected_groups'] ) ) {
601 foreach ( wp_unslash( $_POST['selected_groups'] ) as $selected ) {
602 $selected_groups[] = intval( $selected );
603 }
604 }
605
606 if ( isset( $_POST['selected_clients'] ) && is_array( $_POST['selected_clients'] ) ) {
607 foreach ( wp_unslash( $_POST['selected_clients'] ) as $selected ) {
608 $selected_clients[] = intval( $selected );
609 }
610 }
611 }
612
613 $update['sites'] = ! empty( $selected_sites ) ? wp_json_encode( $selected_sites ) : '';
614 $update['groups'] = ! empty( $selected_groups ) ? wp_json_encode( $selected_groups ) : '';
615 $update['clients'] = ! empty( $selected_clients ) ? wp_json_encode( $selected_clients ) : '';
616
617 $current = false;
618 if ( ! empty( $_POST['mainwp_module_cost_tracker_edit_id'] ) ) {
619 $update['id'] = intval( $_POST['mainwp_module_cost_tracker_edit_id'] );
620 $current = Cost_Tracker_DB::get_instance()->get_cost_tracker_by( 'id', $update['id'] );
621 }
622
623 //phpcs:enable
624 $err_msg = '';
625 $output = false;
626 $saved_id = 0;
627 try {
628 $output = Cost_Tracker_DB::get_instance()->update_cost_tracker( $update );
629 if ( $output && ! empty( $output->id ) ) {
630 $saved_id = $output->id;
631 Cost_Tracker_DB::get_instance()->update_selected_lookup_cost( $output->id, $selected_sites, $selected_groups, $selected_clients );
632 if ( $current && ! empty( $current->cost_icon ) && false === strpos( $current->cost_icon, 'deficon:' ) && $current->cost_icon !== $update['cost_icon'] ) {
633 Cost_Tracker_Add_Edit::get_instance()->delete_product_icon_file( $current->cost_icon );
634 }
635 $next_today = static::calc_next_renewal_today( $output, $next_renewal );
636 Cost_Tracker_DB::get_instance()->update_cost_tracker(
637 array(
638 'id' => $output->id,
639 'next_renewal_today' => $next_today,
640 )
641 );
642 }
643 } catch ( MainWP_Exception $ex ) {
644 $err_msg = $ex->getMessage();
645 }
646
647 $msg_id = ! empty( $update['id'] ) ? intval( $update['id'] ) : 0;
648
649 if ( ! empty( $err_msg ) ) {
650 set_transient( 'mainwp_cost_tracker_update_error_' . $msg_id, $err_msg, HOUR_IN_SECONDS );
651 }
652
653 $snapshot_info = static::get_costs_snapshot_info();
654
655 /**
656 * Action hook for cost tracker update or insert.
657 *
658 * @since 6.0
659 *
660 * @param array $update The data used for the update.
661 * @param int $saved_id The saved cost tracker ID.
662 * @param string $err_msg The error message, if any.
663 * @param array $snapshot_info Costs snapshot info.
664 */
665 do_action( 'mainwp_cost_tracker_update_cost', $update, $saved_id, $err_msg, $snapshot_info );
666
667 if ( empty( $err_msg ) && ! empty( $output ) ) { // success.
668 wp_safe_redirect( admin_url( 'admin.php?page=ManageCostTracker&message=1' ) );
669 } elseif ( ! empty( $err_msg ) ) { // error.
670 wp_safe_redirect( admin_url( 'admin.php?page=CostTrackerAdd&message=2&id=' . $msg_id ) );
671 }
672 exit();
673 }
674
675 /**
676 * Handle get_costs_snapshot_info
677 *
678 * @return array
679 */
680 public static function get_costs_snapshot_info() {
681
682 $costs = Cost_Tracker_DB::get_instance()->get_cost_tracker_by( 'all' );
683
684 $total = 0;
685 $recurring = 0;
686 $one_time = 0;
687 $active = 0;
688
689 if ( is_array( $costs ) ) {
690 $total = count( $costs );
691 foreach ( $costs as $cost ) {
692 if ( 'active' === $cost->cost_status ) {
693 ++$active;
694 }
695 if ( 'subscription' === $cost->type ) {
696 ++$recurring;
697 } else {
698 ++$one_time;
699 }
700 }
701 }
702
703 return array(
704 'cost_items_total' => $total,
705 'cost_items_recurring_count' => $recurring,
706 'cost_items_one_time_count' => $one_time,
707 'cost_items_active_count' => $active,
708 );
709 }
710
711
712 /**
713 * Settigns Post
714 *
715 * Handles the save settings post request.
716 *
717 * @return mixed Save output.
718 */
719 public static function handle_settings_post() {
720 if ( ! isset( $_POST['mwp_cost_tracker_settings_submit'] ) || ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'module_cost_tracker_settings_nonce' ) ) {
721 return;
722 }
723
724 $old_settings = MainWP_Settings::get_all_settings_values();
725
726 $all_opts = Cost_Tracker_Utility::get_instance()->get_all_options();
727 $currency = isset( $_POST['mainwp_module_cost_tracker_settings_currency'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_settings_currency'] ) ) : '';
728 $currency_format = isset( $_POST['mainwp_module_cost_tracker_currency_format'] ) ? wp_unslash( $_POST['mainwp_module_cost_tracker_currency_format'] ) : array(); //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
729 $currency_format = Cost_Tracker_Utility::validate_currency_settings( $currency_format );
730
731 $product_types_colors = array();
732 $product_types_icons = array();
733
734 // first.
735 $cust_product_types = isset( $_POST['cost_tracker_custom_product_types'] ) ? wp_unslash( $_POST['cost_tracker_custom_product_types'] ) : array(); //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
736 $cust_product_types = static::validate_custom_settings_text_fields( $cust_product_types, $product_types_colors, $product_types_icons );
737
738 // second.
739 $default_product_colors = isset( $_POST['cost_tracker_default_product_types'] ) ? wp_unslash( $_POST['cost_tracker_default_product_types'] ) : array(); //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
740 static::validate_custom_settings_text_fields( $default_product_colors, $product_types_colors, $product_types_icons );
741
742 $cust_payment_methods = isset( $_POST['cost_tracker_custom_payment_methods'] ) ? wp_unslash( $_POST['cost_tracker_custom_payment_methods'] ) : array(); //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
743 $cust_payment_methods = static::validate_custom_settings_text_fields( $cust_payment_methods );
744
745 $all_opts['currency'] = $currency;
746 $all_opts['currency_format'] = $currency_format;
747 $all_opts['custom_product_types'] = wp_json_encode( $cust_product_types );
748 $all_opts['custom_payment_methods'] = wp_json_encode( $cust_payment_methods );
749 $all_opts['product_types_colors'] = wp_json_encode( $product_types_colors );
750 $all_opts['product_types_icons'] = wp_json_encode( $product_types_icons );
751
752 $all_opts = apply_filters( 'mainwp_module_cost_tracker_before_save_settings', $all_opts );
753
754 Cost_Tracker_Utility::get_instance()->save_options( $all_opts );
755
756 $new_settings = MainWP_Settings::get_all_settings_values();
757
758 /**
759 * Action: mainwp_after_save_settings
760 *
761 * Fires after save settings.
762 *
763 * @since 6.0
764 *
765 * @param array $new_settings The new settings.
766 * @param array $old_settings The old settings.
767 */
768 do_action( 'mainwp_after_save_settings', $new_settings, $old_settings );
769
770 wp_safe_redirect( admin_url( 'admin.php?page=CostTrackerSettings&message=1' ) );
771 exit();
772 }
773
774
775 /**
776 * Method array_validate_text_fields().
777 *
778 * @param array $arr Data to valid.
779 * @param mixed $product_types_colors Product types colors.
780 * @param mixed $product_types_icons Product types icons.
781 *
782 * @return array Validated array fields data.
783 */
784 public static function validate_custom_settings_text_fields( $arr, &$product_types_colors = null, &$product_types_icons = null ) {
785 if ( ! is_array( $arr ) || ! isset( $arr['title'] ) || ! is_array( $arr['title'] ) ) {
786 return array();
787 }
788 $valid_arr = array();
789 foreach ( $arr['title'] as $idx => $title ) {
790 $title = trim( $title );
791 if ( empty( $title ) ) {
792 continue;
793 }
794 $slug = isset( $arr['slug'][ $idx ] ) ? sanitize_title( $arr['slug'][ $idx ] ) : '';
795
796 if ( empty( $slug ) ) {
797 $slug = sanitize_title( $title );
798 }
799 $slug = strtolower( $slug );
800 $valid_arr[ $slug ] = sanitize_text_field( $title );
801
802 if ( is_array( $product_types_colors ) && is_array( $arr['color'] ) && isset( $arr['color'][ $idx ] ) ) {
803 $product_types_colors[ $slug ] = sanitize_hex_color( wp_unslash( $arr['color'][ $idx ] ) );
804 }
805
806 if ( is_array( $product_types_icons ) && is_array( $arr['icon'] ) && isset( $arr['icon'][ $idx ] ) ) {
807 $product_types_icons[ $slug ] = sanitize_text_field( wp_unslash( $arr['icon'][ $idx ] ) );
808 }
809 }
810 return $valid_arr;
811 }
812
813 /**
814 * Extract the scalar fields of an imported cost.
815 *
816 * @param array $cost Decoded cost object.
817 *
818 * @return array|false Fields for the DB write, or false when one of them carries a shape
819 * that cannot be stored.
820 */
821 private static function import_cost_fields( $cost ) {
822 // Coercing instead of refusing stored a price of 1 for [ 5 ] and a last renewal of
823 // 2026 for '2026-01-15', neither of which the admin pasted.
824 $spec = array(
825 'url' => 'url',
826 'type' => 'text',
827 'product_type' => 'text',
828 'license_type' => 'text',
829 'price' => 'float',
830 'payment_method' => 'text',
831 'renewal_type' => 'text',
832 'last_renewal' => 'int',
833 'cost_status' => 'text',
834 );
835
836 $fields = array();
837
838 foreach ( $spec as $field => $kind ) {
839 $value = isset( $cost[ $field ] ) ? $cost[ $field ] : null;
840
841 if ( 'float' === $kind || 'int' === $kind ) {
842 if ( null === $value ) {
843 $fields[ $field ] = 0;
844 continue;
845 }
846 if ( 'int' === $kind ) {
847 // is_numeric() lets 1700000000.9 and '1e9' through, and intval() would
848 // quietly turn them into a renewal timestamp nobody pasted.
849 if ( ! is_int( $value ) && ! ( is_string( $value ) && preg_match( '/^-?\d+$/', $value ) ) ) {
850 return false;
851 }
852 $fields[ $field ] = (int) $value;
853 continue;
854 }
855 if ( ! is_numeric( $value ) ) {
856 return false;
857 }
858 $fields[ $field ] = floatval( $value );
859 continue;
860 }
861
862 if ( null === $value ) {
863 $fields[ $field ] = '';
864 continue;
865 }
866
867 if ( ! is_string( $value ) ) {
868 return false;
869 }
870
871 $fields[ $field ] = 'url' === $kind ? esc_url_raw( $value ) : sanitize_text_field( $value );
872 }
873
874 return $fields;
875 }
876
877 /**
878 * AJAX handler for importing cost tracker entries.
879 *
880 * @return never wp_send_json_*() exits.
881 */
882 public static function ajax_import_cost() { //phpcs:ignore -- NOSONAR - complex method.
883 MainWP_Post_Handler::instance()->secure_request( 'mainwp_cost_tracker_import_cost' );
884
885 if ( ! isset( $_POST['encoded_data'] ) || ! is_string( $_POST['encoded_data'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- secure_request() verifies the nonce above.
886 return wp_send_json_error(
887 array(
888 'message' => esc_html__( 'No cost data provided', 'mainwp' ),
889 )
890 );
891 }
892
893 $encoded_data = wp_unslash( $_POST['encoded_data'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- nonce verified by secure_request() above; a JSON string, and each decoded field is type-checked and sanitized below before use.
894 $cost_data_raw = json_decode( $encoded_data, true );
895
896 // The decoded fields feed string functions, so a non-object cost shape is refused here.
897 if ( ! is_array( $cost_data_raw ) || ! isset( $cost_data_raw['cost'] ) || ! is_array( $cost_data_raw['cost'] ) ) {
898 return wp_send_json_error(
899 array(
900 'message' => esc_html__( 'Invalid cost data format', 'mainwp' ),
901 )
902 );
903 }
904
905 $cost_name = isset( $cost_data_raw['cost']['name'] ) && is_string( $cost_data_raw['cost']['name'] ) ? sanitize_text_field( $cost_data_raw['cost']['name'] ) : '';
906
907 if ( empty( $cost_name ) ) {
908 return wp_send_json_error(
909 array(
910 'message' => esc_html__( 'Cost name is required', 'mainwp' ),
911 )
912 );
913 }
914
915 $cost_fields = static::import_cost_fields( $cost_data_raw['cost'] );
916
917 if ( false === $cost_fields ) {
918 return wp_send_json_error(
919 array(
920 'message' => esc_html__( 'Invalid cost data format', 'mainwp' ),
921 )
922 );
923 }
924
925 $cost_data = array_merge( array( 'name' => $cost_name ), $cost_fields );
926
927 $select_sites = $cost_data_raw['cost']['select_sites'] ?? array();
928
929 if ( ! is_array( $select_sites ) ) {
930 return wp_send_json_error(
931 array(
932 'message' => esc_html__( 'Invalid cost data format', 'mainwp' ),
933 )
934 );
935 }
936
937 $selected_sites = array();
938
939 foreach ( $select_sites as $url ) {
940 if ( ! is_string( $url ) ) {
941 continue;
942 }
943 $url = esc_url_raw( trim( $url ) );
944 if ( empty( $url ) ) {
945 continue;
946 }
947
948 if ( '/' !== substr( $url, -1 ) ) {
949 $url .= '/';
950 }
951
952 $website = MainWP_DB::instance()->get_websites_by_url( $url );
953 $site_id = ! empty( $website ) ? current( $website )->id : null;
954
955 if ( ! empty( $site_id ) ) {
956 $selected_sites[] = $site_id;
957 }
958 }
959
960 $cost_data['sites'] = ! empty( $selected_sites ) ? wp_json_encode( array_values( array_unique( array_map( 'intval', $selected_sites ) ) ) ) : '';
961
962 try {
963 $inserted = Cost_Tracker_DB::get_instance()->update_cost_tracker( $cost_data );
964 } catch ( \MainWP_Exception $e ) {
965 return wp_send_json_error(
966 array(
967 'message' => $e->getMessage(),
968 )
969 );
970 }
971
972 if ( empty( $inserted ) || ! is_object( $inserted ) || ! isset( $inserted->id ) ) {
973 return wp_send_json_error(
974 array(
975 'message' => esc_html__( 'Failed to create cost tracker entry', 'mainwp' ),
976 )
977 );
978 }
979
980 if ( ! empty( $selected_sites ) ) {
981 Cost_Tracker_DB::get_instance()->update_selected_lookup_cost(
982 $inserted->id,
983 $selected_sites,
984 false,
985 false
986 );
987 }
988
989 return wp_send_json_success(
990 array(
991 'message' => esc_html__( 'Cost imported successfully.', 'mainwp' ),
992 'cost' => $inserted,
993 )
994 );
995 }
996
997 /**
998 * Method hook_get_next_renewal().
999 *
1000 * Get the next renewal.
1001 *
1002 * @param mixed $filter_input filter input value.
1003 * @param int $previous_renewal last renewal.
1004 * @param string $renewal_type renewal time.
1005 */
1006 public function hook_get_next_renewal( $filter_input, $previous_renewal, $renewal_type ) {
1007 unset( $filter_input );
1008 return static::get_next_renewal( $previous_renewal, $renewal_type );
1009 }
1010
1011 /**
1012 * Method hook_get_total_cost().
1013 *
1014 * Get total costs.
1015 */
1016 public function hook_get_total_cost() {
1017 return Cost_Tracker_DB::get_instance()->get_cost_tracker_by( 'count' );
1018 }
1019
1020 /**
1021 * Method get_next_renewal().
1022 *
1023 * Get the next renewal.
1024 *
1025 * @param int $previous_renewal last renewal.
1026 * @param string $renewal_type renewal time.
1027 * @param bool $get_real_renewal To get future renewal, true|false.
1028 */
1029 public static function get_next_renewal( $previous_renewal, $renewal_type, $get_real_renewal = true ) {
1030 $next_renewal = 0;
1031 if ( ! empty( $previous_renewal ) && ! empty( $renewal_type ) && 'disabled' !== $renewal_type ) {
1032
1033 if ( 'monthly' === $renewal_type ) {
1034 $next_renewal = strtotime( '+1 month', $previous_renewal );
1035 } elseif ( 'yearly' === $renewal_type ) {
1036 $next_renewal = strtotime( '+1 year', $previous_renewal );
1037 } elseif ( 'weekly' === $renewal_type ) {
1038 $next_renewal = strtotime( '+1 week', $previous_renewal );
1039 } elseif ( 'quarterly' === $renewal_type ) {
1040 $next_renewal = strtotime( '+3 month', $previous_renewal );
1041 }
1042
1043 $today_time = strtotime( gmdate( 'Y-m-d 00:00:00' ) );
1044
1045 if ( $get_real_renewal && $next_renewal < $today_time ) {
1046 $next_renewal = static::get_next_renewal( $next_renewal, $renewal_type, $get_real_renewal );
1047 }
1048 }
1049 return $next_renewal;
1050 }
1051
1052 /**
1053 * Method get_default_product_types().
1054 */
1055 public static function get_default_product_types() {
1056 return array(
1057 'plugin' => esc_html__( 'Plugin', 'mainwp' ),
1058 'theme' => esc_html__( 'Theme', 'mainwp' ),
1059 'hosting' => esc_html__( 'Hosting', 'mainwp' ),
1060 'service' => esc_html__( 'Service', 'mainwp' ),
1061 'other' => esc_html__( 'Other', 'mainwp' ),
1062 );
1063 }
1064
1065 /**
1066 * Method get_default_product_type_colors().
1067 */
1068 public static function get_default_product_type_colors() {
1069 return array(
1070 'plugin' => '#5ec130',
1071 'theme' => '#34afd8',
1072 'hosting' => '#f2e93a',
1073 'service' => '#ed1730',
1074 'other' => '#14f4fc',
1075 );
1076 }
1077
1078 /**
1079 * Method get_product_colors().
1080 *
1081 * @param string $type Product type.
1082 */
1083 public static function get_product_colors( $type = false ) {
1084 $defaults = static::get_default_product_type_colors();
1085
1086 $product_colors = Cost_Tracker_Utility::get_instance()->get_option( 'product_types_colors', array(), true );
1087 if ( is_array( $product_colors ) && ! empty( $product_colors ) ) {
1088 $product_colors = array_replace( $defaults, $product_colors ); // to fix: array merge with keys.
1089 } else {
1090 $product_colors = $defaults;
1091 }
1092
1093 if ( ! empty( $type ) && is_string( $type ) ) {
1094 return isset( $product_colors[ $type ] ) ? $product_colors[ $type ] : '';
1095 }
1096 return $product_colors; // return all colors.
1097 }
1098
1099
1100 /**
1101 * Method get_default_product_types_icons().
1102 */
1103 public static function get_default_product_types_icons() {
1104 return array(
1105 'plugin' => 'plug',
1106 'theme' => 'tint',
1107 'hosting' => 'server',
1108 'service' => 'wrench',
1109 'other' => 'folder',
1110 );
1111 }
1112
1113 /**
1114 * Method get_product_type_icons().
1115 *
1116 * @param string $type Product type.
1117 */
1118 public static function get_product_type_icons( $type = false ) {
1119 $defaults = static::get_default_product_types_icons();
1120 $product_icons = Cost_Tracker_Utility::get_instance()->get_option( 'product_types_icons', array(), true );
1121 if ( is_array( $product_icons ) && ! empty( $product_icons ) ) {
1122 $product_icons = array_replace( $defaults, $product_icons );
1123 } else {
1124 $product_icons = $defaults;
1125 }
1126 if ( ! empty( $type ) && is_string( $type ) ) {
1127 return isset( $product_icons[ $type ] ) ? $product_icons[ $type ] : '';
1128 }
1129 return $product_icons; // return all colors.
1130 }
1131
1132 /**
1133 * Method get_product_types().
1134 */
1135 public static function get_product_types() {
1136 $product_types = static::get_default_product_types();
1137 $cust_product_types = Cost_Tracker_Utility::get_instance()->get_option( 'custom_product_types', array(), true );
1138 if ( ! empty( $cust_product_types ) ) {
1139 $product_types = array_replace( $product_types, $cust_product_types ); // to fix: array merge with keys.
1140 }
1141 return $product_types;
1142 }
1143
1144 /**
1145 * Method get_payment_methods().
1146 */
1147 public static function get_payment_methods() {
1148 $payment_methods = array(
1149 'paypal' => esc_html__( 'PayPal', 'mainwp' ),
1150 'stripe' => esc_html__( 'Stripe', 'mainwp' ),
1151 'apple' => esc_html__( 'Apple Pay', 'mainwp' ),
1152 'amazon' => esc_html__( 'Amazon Pay', 'mainwp' ),
1153 'google' => esc_html__( 'Google Pay', 'mainwp' ),
1154 'credit_debit' => esc_html__( 'Credit Card', 'mainwp' ),
1155 'debit_card' => esc_html__( 'Debit Card', 'mainwp' ),
1156 'cash' => esc_html__( 'Cash', 'mainwp' ),
1157 );
1158 $cust_payment_methods = Cost_Tracker_Utility::get_instance()->get_option( 'custom_payment_methods', array(), true );
1159 if ( ! empty( $cust_payment_methods ) ) {
1160 $payment_methods = array_replace( $payment_methods, $cust_payment_methods ); // to fix: array merge with keys.
1161 }
1162 return $payment_methods;
1163 }
1164
1165 /**
1166 * Method get_renewal_frequency().
1167 */
1168 public static function get_renewal_frequency() {
1169 return array(
1170 'weekly' => esc_html__( 'Weekly', 'mainwp' ),
1171 'monthly' => esc_html__( 'Monthly', 'mainwp' ),
1172 'quarterly' => esc_html__( 'Quarterly', 'mainwp' ),
1173 'yearly' => esc_html__( 'Yearly', 'mainwp' ),
1174 );
1175 }
1176
1177 /**
1178 * Method get_cost_status().
1179 */
1180 public static function get_cost_status() {
1181 return array(
1182 'active' => esc_html__( 'Active', 'mainwp' ),
1183 'canceled' => esc_html__( 'Canceled', 'mainwp' ),
1184 'onhold' => esc_html__( 'On Hold', 'mainwp' ),
1185 'expired' => esc_html__( 'Expired', 'mainwp' ),
1186 'pending_cancelation' => esc_html__( 'Pending Cancelation', 'mainwp' ),
1187 );
1188 }
1189
1190 /**
1191 * Method handle_sites_screen_settings()
1192 *
1193 * Handle sites screen settings
1194 */
1195 public function handle_sites_screen_settings() { //phpcs:ignore -- NOSONAR - complex.
1196 if ( isset( $_POST['wp_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_nonce'] ), 'CostTrackerSitesScrOptions' ) ) {
1197 $show_cols = array();
1198 foreach ( array_map( 'sanitize_text_field', wp_unslash( $_POST ) ) as $key => $val ) {
1199 if ( false !== strpos( $key, 'mainwp_show_column_' ) ) {
1200 $col = str_replace( 'mainwp_show_column_', '', $key );
1201 $show_cols[ $col ] = 1;
1202 }
1203 }
1204 if ( isset( $_POST['show_columns_name'] ) ) {
1205 foreach ( array_map( 'sanitize_text_field', wp_unslash( $_POST['show_columns_name'] ) ) as $col ) {
1206 if ( ! isset( $show_cols[ $col ] ) ) {
1207 $show_cols[ $col ] = 0; // uncheck, hide columns.
1208 }
1209 }
1210 }
1211 $user = wp_get_current_user();
1212 if ( $user ) {
1213 update_user_option( $user->ID, 'mainwp_module_costs_tracker_manage_showhide_columns', $show_cols, true );
1214 }
1215 }
1216 }
1217
1218 /**
1219 * Method mainwp_help_content()
1220 *
1221 * Creates the MainWP Help Documentation List for the help component in the sidebar.
1222 */
1223 public static function mainwp_help_content() {
1224 $allow_pages = array( 'ManageCostTracker', 'CostTrackerAdd', 'CostTrackerSettings', 'CostSummary' );
1225 if ( isset( $_GET['page'] ) && in_array( $_GET['page'], $allow_pages, true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
1226 ?>
1227 <p><?php esc_html_e( 'If you need help with the Cost Tracker module, please review following help documents', 'mainwp' ); ?></p>
1228 <div class="ui list">
1229 <div class="item"><i class="external alternate icon"></i> <a href="https://docs.mainwp.com/add-ons/client/cost-tracker-extension#cost-tracker" target="_blank">Cost Tracker</a></div> <?php // NOSONAR -- compatible with help. ?>
1230 <div class="item"><i class="external alternate icon"></i> <a href="https://docs.mainwp.com/add-ons/client/cost-tracker-extension#manage-costs-page" target="_blank">Manage Costs</a></div> <?php // NOSONAR -- compatible with help. ?>
1231 <div class="item"><i class="external alternate icon"></i> <a href="https://docs.mainwp.com/add-ons/client/cost-tracker-extension#adding-a-new-cost-to-track" target="_blank">Adding a New Cost to track</a></div> <?php // NOSONAR -- compatible with help. ?>
1232 <div class="item"><i class="external alternate icon"></i> <a href="https://docs.mainwp.com/add-ons/client/cost-tracker-extension#edit-an-item" target="_blank">Edit Costs</a></div> <?php // NOSONAR -- compatible with help. ?>
1233 <div class="item"><i class="external alternate icon"></i> <a href="https://docs.mainwp.com/add-ons/client/cost-tracker-extension#delete-an-item" target="_blank">Delete Costs</a></div> <?php // NOSONAR -- compatible with help. ?>
1234 <div class="item"><i class="external alternate icon"></i> <a href="https://docs.mainwp.com/add-ons/client/cost-tracker-extension#settings-page" target="_blank">Cost Tracker Settings</a></div> <?php // NOSONAR -- compatible with help. ?>
1235 <div class="item"><i class="external alternate icon"></i> <a href="https://docs.mainwp.com/add-ons/client/cost-tracker-extension#cost-tracker-pro-extension" target="_blank">Cost Tracker Pro Extension</a></div> <?php // NOSONAR -- compatible with help. ?>
1236 <?php
1237 /**
1238 * Action: mainwp_module_cost_tracker_help_item
1239 *
1240 * Fires at the bottom of the help articles list in the Help sidebar on the Cost Tracker page.
1241 *
1242 * Suggested HTML markup:
1243 *
1244 * <div class="item"><a href="Your custom URL">Your custom text</a></div>
1245 *
1246 * @since 5.0
1247 */
1248 do_action( 'mainwp_module_cost_tracker_help_item' );
1249 ?>
1250 </div>
1251 <?php
1252 }
1253 }
1254
1255 /**
1256 * Method calc_next_renewal_today()
1257 *
1258 * Calculate next renewal today.
1259 *
1260 * @param object $subscription subscription.
1261 * @param int $next_renewal Next renewal.
1262 *
1263 * @return mixed result.
1264 */
1265 public static function calc_next_renewal_today( $subscription, $next_renewal = false ) {
1266 if ( 'lifetime' === $subscription->type ) {
1267 return 0;
1268 }
1269 $next_renewal = ! empty( $next_renewal ) ? intval( $next_renewal ) : (int) $subscription->next_renewal;
1270 if ( empty( $next_renewal ) ) {
1271 return 0;
1272 }
1273 if ( 'active' !== $subscription->cost_status ) {
1274 return 0;
1275 }
1276 return $next_renewal;
1277 }
1278
1279 /**
1280 * Method generate_next_renewal()
1281 *
1282 * Generate next renewal info.
1283 *
1284 * @param object $subscription subscription.
1285 * @param int $next_renewal Next renewal.
1286 *
1287 * @return void
1288 */
1289 public static function generate_next_renewal( $subscription, $next_renewal = false ) {
1290 if ( empty( $subscription ) || ! is_object( $subscription ) ) {
1291 echo '';
1292 return;
1293 }
1294
1295 if ( 'lifetime' === $subscription->type ) {
1296 echo '';
1297 return;
1298 }
1299
1300 $next_renewal = ! empty( $next_renewal ) ? intval( $next_renewal ) : (int) $subscription->next_renewal;
1301
1302 if ( empty( $next_renewal ) ) {
1303 echo '';
1304 return;
1305 }
1306 if ( 'active' !== $subscription->cost_status ) {
1307 echo '';
1308 return;
1309 }
1310
1311 $current_time = time();
1312 $renewal_html = MainWP_Utility::format_date( $next_renewal );
1313 $day1 = $next_renewal - 15 * DAY_IN_SECONDS;
1314 $day2 = $next_renewal - 7 * DAY_IN_SECONDS;
1315 if ( $day1 > $current_time ) {
1316 $renewal_html = '<strong>' . esc_html( $renewal_html ) . '</strong>';
1317 } elseif ( $day1 <= $current_time && $current_time < $day2 ) {
1318 $renewal_html = '<strong><span data-tooltip="Renewal approaching soon. Please review your subscription details." data-inverted="" data-position="left center"><i class="orange bell icon"></i></span>' . esc_html( $renewal_html ) . '</strong>';
1319 } elseif ( $day2 <= $current_time && $current_time < $next_renewal ) {
1320 $renewal_html = '<strong><span data-tooltip="Renewal approaching soon. Please review your subscription details." data-inverted="" data-position="left center"><i class="orange bell icon"></i></span>' . esc_html( $renewal_html ) . '</strong>';
1321 }
1322 echo $renewal_html; //phpcs:ignore -- ok.
1323 }
1324
1325 /**
1326 * Returns the label for a cost status.
1327 *
1328 * @param mixed $key false|string to get status of key.
1329 *
1330 * @return string
1331 */
1332 public static function get_cost_status_label( $key = false ) {
1333
1334 $default = array(
1335 'active' => array(
1336 'label' => esc_html__( 'Active', 'mainwp' ),
1337 'class' => 'basic green center aligned fluid',
1338 ),
1339 'onhold' => array(
1340 'label' => esc_html__( 'On Hold', 'mainwp' ),
1341 'class' => 'basic yellow center aligned fluid',
1342 ),
1343 'canceled' => array(
1344 'label' => esc_html__( 'Canceled', 'mainwp' ),
1345 'class' => 'basic grey center aligned fluid',
1346 ),
1347 'expired' => array(
1348 'label' => esc_html__( 'Expired', 'mainwp' ),
1349 'class' => 'basic purple center aligned fluid',
1350 ),
1351 'pending_cancelation' => array(
1352 'label' => esc_html__( 'Pending Cancelation', 'mainwp' ),
1353 'class' => 'basic black center aligned fluid',
1354 ),
1355 );
1356
1357 if ( false !== $key ) {
1358 if ( empty( $key ) ) {
1359 $key = 'active';
1360 }
1361 return isset( $default[ $key ] ) ? '<span class="ui small ' . $default[ $key ]['class'] . ' label">' . $default[ $key ]['label'] . '</span>' : $key;
1362 }
1363
1364 return $default;
1365 }
1366
1367 /**
1368 * Gets product icon to output.
1369 *
1370 * @param string $product Product object.
1371 * @param string $img_id_attr img id attr.
1372 * @param bool $with_color with color.
1373 */
1374 public function get_product_icon_display( $product = false, $img_id_attr = '', $with_color = true ) { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR - complexity.
1375
1376 $prod_icon = '';
1377 $prod_color = '';
1378
1379 if ( $product ) {
1380 $prod_icon = $product->cost_icon;
1381 $prod_color = $product->cost_color;
1382 }
1383
1384 if ( empty( $prod_icon ) || false !== strpos( $prod_icon, 'deficon:' ) ) {
1385 $upload_icon = '';
1386 } else {
1387 $upload_icon = $prod_icon;
1388 }
1389
1390 $style = 'width:28px;height:auto;display:inline-block;';
1391
1392 if ( empty( $prod_color ) ) {
1393 $prod_color = '#34424D';
1394 }
1395
1396 $color_style = '';
1397 if ( $with_color ) {
1398 $color_style = 'color:' . esc_attr( $prod_color ) . ';';
1399 }
1400
1401 $default_cls = 'module_cost_tracker_settings_upload_img_display';
1402 if ( empty( $prod_icon ) ) {
1403 $def_icon = Cost_Tracker_Utility::get_product_default_icons( false, 'default_product' );
1404 $icon_wrapper_attr = ! empty( $img_id_attr ) ? 'id="' . esc_attr( $img_id_attr ) . '" ' : 'class="' . esc_attr( $default_cls ) . '"';
1405 $icon = '<div style="display:inline-block;" ' . $icon_wrapper_attr . '><i style="' . $color_style . '" class="' . esc_attr( $def_icon ) . ' large icon"></i></div>';
1406 } elseif ( false !== strpos( $prod_icon, 'deficon:' ) ) {
1407 $icon_wrapper_attr = ! empty( $img_id_attr ) ? 'id="' . esc_attr( $img_id_attr ) . '" ' : 'class="' . esc_attr( $default_cls ) . '"';
1408 $icon = '<div style="display:inline-block;" ' . $icon_wrapper_attr . '><i class="' . str_replace( 'deficon:', '', $prod_icon ) . ' large icon" style="' . $color_style . '" ></i></div>';
1409 } else {
1410 if ( ! empty( $upload_icon ) ) {
1411 $dirs = MainWP_System_Utility::get_mainwp_dir( Cost_Tracker_Settings::$icon_sub_dir, true );
1412 $icon_base = $dirs[1];
1413 $scr = $icon_base . $upload_icon;
1414 } else {
1415 $scr = '';
1416 }
1417 $icon_wrapper_attr = ! empty( $img_id_attr ) ? 'id="' . esc_attr( $img_id_attr ) . '" class="ui circular image" ' : 'class="' . esc_attr( $default_cls ) . ' ui circular image "';
1418 $icon = '<div style="display:inline-block;" ' . $icon_wrapper_attr . '><img style="' . $style . '" alt="' . esc_attr__( 'Product icon', 'mainwp' ) . '" src="' . esc_attr( $scr ) . '"/></div>';
1419 }
1420 return $icon;
1421 }
1422 }
1423