PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.2
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.2
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
← All changes | modules/cost-tracker/classes/class-cost-tracker-admin.php +377 -40 5.2.1 → 6.2.2 View file →
@@ -14,12 +14,17 @@
14 14 use MainWP\Dashboard\MainWP_Utility;
15 15 use MainWP\Dashboard\MainWP_System_Utility;
16 16 use MainWP\Dashboard\MainWP_Logger;
17 17 use MainWP\Dashboard\MainWP_Post_Handler;
18 +use MainWP\Dashboard\MainWP_Settings;
18 19 use MainWP\Dashboard\MainWP_Settings_Indicator;
19 20 use MainWP\Dashboard\MainWP_Exception;
21 +use MainWP\Dashboard\Module\CostTracker\Cost_Tracker_Import;
20 22
21 -use function MainWP\Dashboard\mainwp_current_user_have_right;
23 +// Exit if accessed directly.
24 +if ( ! defined( 'ABSPATH' ) ) {
25 + exit;
26 +}
22 27
23 28 /**
24 29 * Class Cost_Tracker_Admin
25 30 */
@@ -83,8 +88,10 @@
83 88 add_filter( 'mainwp_init_primary_menu_items', array( $this, 'hook_init_primary_menu_items' ), 10, 2 );
84 89 add_filter( 'mainwp_module_cost_tracker_get_default_cost_fields', array( $this, 'hook_get_default_cost_fields' ), 10, 2 );
85 90 add_filter( 'mainwp_module_cost_tracker_get_next_renewal', array( $this, 'hook_get_next_renewal' ), 10, 3 );
86 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 );
87 94 add_filter( 'mainwp_module_cost_tracker_get_total_cost', array( $this, 'hook_get_total_cost' ), 10, 2 );
88 95 }
89 96
90 97
@@ -126,8 +133,57 @@
126 133 );
127 134 }
128 135
129 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 + /**
130 186 * Admin Init
131 187 *
132 188 * Initiates admin hooks.
133 189 */
@@ -133,16 +189,17 @@
133 189 */
134 190 public function admin_init() {
135 191
136 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' ) );
137 194
138 195 $this->handle_edit_cost_tracker_post();
139 196 $this->handle_settings_post();
140 197
141 - $allow_pages = array( 'ManageCostTracker', 'CostTrackerAdd', 'CostTrackerSettings', 'CostSummary' );
198 + $allow_pages = array( 'ManageCostTracker', 'CostTrackerAdd', 'CostTrackerSettings', 'CostSummary', 'CostTrackerImport' );
142 199 if ( isset( $_GET['page'] ) && in_array( $_GET['page'], $allow_pages, true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
143 200 $base_url = Cost_Tracker_Manager::get_location_path( 'url' );
144 - wp_enqueue_script( 'mainwp-module-cost-tracker-extension', $base_url . 'ui/js/cost-tracker.js', array( 'jquery' ), $this->version, true );
201 + wp_enqueue_script( 'mainwp-module-cost-tracker-extension', $base_url . 'ui/js/cost-tracker.js', array( 'jquery', 'mainwp' ), $this->version, true );
145 202 if ( 'CostSummary' === $_GET['page'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
146 203 add_filter(
147 204 'mainwp_admin_enqueue_scripts',
148 205 function ( $scripts ) {
@@ -152,8 +209,11 @@
152 209 return $scripts;
153 210 }
154 211 );
155 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 + }
156 216 }
157 217 }
158 218
159 219 /**
@@ -224,10 +284,10 @@
224 284 'multi_site' => esc_html__( 'Multiple-Site License', 'mainwp' ),
225 285 );
226 286 $product_types = static::get_product_types();
227 287 $payment_types = array(
228 - 'subscription' => esc_html__( 'Subscription', 'mainwp' ),
229 - 'lifetime' => esc_html__( 'Lifetime', 'mainwp' ),
288 + 'subscription' => esc_html__( 'Recurring (Subscription)', 'mainwp' ),
289 + 'lifetime' => esc_html__( 'One-time (Lifetime)', 'mainwp' ),
230 290 );
231 291 $payment_methods = static::get_payment_methods();
232 292 $renewal_frequency = static::get_renewal_frequency();
233 293 $cost_status = static::get_cost_status();
@@ -292,15 +352,15 @@
292 352 );
293 353
294 354 add_submenu_page(
295 355 'mainwp_tab',
296 - esc_html__( 'Settings', 'mainwp' ),
297 - '<div class="mainwp-hidden">' . esc_html__( 'Settings', 'mainwp' ) . '</div>',
356 + esc_html__( 'Import Costs', 'mainwp' ),
357 + '<div class="mainwp-hidden">' . esc_html__( 'Import Costs', 'mainwp' ) . '</div>',
298 358 'read',
299 - 'CostTrackerSettings',
359 + 'CostTrackerImport',
300 360 array(
301 - Cost_Tracker_Settings::get_instance(),
302 - 'render_settings_page',
361 + Cost_Tracker_Import::get_instance(),
362 + 'render_import_costs',
303 363 )
304 364 );
305 365
306 366 /**
@@ -333,8 +393,12 @@
333 393 *
334 394 * Run on page load.
335 395 */
336 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 + }
337 401 Cost_Tracker_Summary::instance()->on_load_page( static::$page );
338 402 }
339 403
340 404 /**
@@ -344,9 +408,9 @@
344 408 */
345 409 public static function init_left_menu( $subPages ) {
346 410 MainWP_Menu::add_left_menu(
347 411 array(
348 - 'title' => esc_html__( 'Cost Tracker', 'mainwp' ),
412 + 'title' => esc_html__( 'Costs', 'mainwp' ),
349 413 'parent_key' => 'mainwp_tab',
350 414 'slug' => 'CostSummary',
351 415 'href' => 'admin.php?page=CostSummary',
352 416 'icon' => '<i class="dollar sign icon"></i>',
@@ -354,8 +418,15 @@
354 418 'nosubmenu' => true,
355 419 ),
356 420 0
357 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 +
358 429 $init_sub_subleftmenu = array(
359 430 array(
360 431 'title' => esc_html__( 'Cost Summary', 'mainwp' ),
361 432 'parent_key' => 'CostSummary',
@@ -361,8 +432,9 @@
361 432 'parent_key' => 'CostSummary',
362 433 'href' => 'admin.php?page=CostSummary',
363 434 'slug' => 'CostSummary',
364 435 'right' => 'manage_cost_tracker',
436 + 'item_class' => $sum_item_class,
365 437 ),
366 438 array(
367 439 'title' => esc_html__( 'Manage Costs', 'mainwp' ),
368 440 'parent_key' => 'CostSummary',
@@ -377,13 +449,13 @@
377 449 'slug' => 'CostTrackerAdd',
378 450 'right' => '',
379 451 ),
380 452 array(
381 - 'title' => esc_html__( 'Settings', 'mainwp' ),
453 + 'title' => esc_html__( 'Import Costs', 'mainwp' ),
382 454 'parent_key' => 'CostSummary',
383 - 'href' => 'admin.php?page=CostTrackerSettings',
384 - 'slug' => 'CostTrackerSettings',
385 - 'right' => '',
455 + 'href' => 'admin.php?page=CostTrackerImport',
456 + 'slug' => 'CostTrackerImport',
457 + 'right' => 'manage_cost_tracker',
386 458 ),
387 459 );
388 460
389 461 MainWP_Menu::init_subpages_left_menu( $subPages, $init_sub_subleftmenu, 'ManageCostTracker', 'ManageCostTracker' );
@@ -405,10 +477,17 @@
405 477 *
406 478 * @param string $shownPage Current Page.
407 479 */
408 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 +
409 488 $params = array(
410 - 'title' => esc_html__( 'Cost Tracker', 'mainwp' ),
489 + 'title' => $title,
411 490 'which' => 'page_cost_tracker_overview',
412 491 'wrap_class' => 'mainwp-module-cost-tracker-content-wrapper',
413 492 );
414 493
@@ -415,11 +494,11 @@
415 494 MainWP_UI::render_top_header( $params );
416 495
417 496 $renderItems = array();
418 497
419 - if ( mainwp_current_user_have_right( 'dashboard', 'manage_cost_tracker' ) ) {
498 + if ( \mainwp_current_user_can( 'dashboard', 'manage_cost_tracker' ) ) {
420 499 $renderItems[] = array(
421 - 'title' => esc_html__( 'Overview', 'mainwp' ),
500 + 'title' => esc_html__( 'Operations', 'mainwp' ),
422 501 'href' => 'admin.php?page=ManageCostTracker',
423 502 'active' => ( '' === $shownPage ) ? true : false,
424 503 );
425 504 }
@@ -431,8 +510,16 @@
431 510 'active' => ( 'add' === $shownPage ) ? true : false,
432 511 );
433 512 }
434 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 +
435 522 $cost_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
436 523 if ( $cost_id ) {
437 524 $renderItems[] = array(
438 525 'title' => esc_html__( 'Edit Cost Tracker', 'mainwp' ),
@@ -440,15 +527,8 @@
440 527 'active' => ( 'edit' === $shownPage ) ? true : false,
441 528 );
442 529 }
443 530
444 - if ( ! MainWP_Menu::is_disable_menu_item( 3, 'CostTrackerSettings' ) ) {
445 - $renderItems[] = array(
446 - 'title' => esc_html__( 'Settings', 'mainwp' ),
447 - 'href' => 'admin.php?page=CostTrackerSettings',
448 - 'active' => ( 'settings' === $shownPage ) ? true : false,
449 - );
450 - }
451 531 if ( isset( static::$subPages ) && is_array( static::$subPages ) ) {
452 532 foreach ( static::$subPages as $subPage ) {
453 533 if ( MainWP_Menu::is_disable_menu_item( 3, 'ManageCostTracker' . $subPage['slug'] ) ) {
454 534 continue;
@@ -474,9 +554,11 @@
474 554 * @return mixed Save output.
475 555 */
476 556 public static function handle_edit_cost_tracker_post() { //phpcs:ignore -- NOSONAR - complex method.
477 557
478 - 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' ) ) {
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 ) ) {
479 561 return;
480 562 }
481 563 //phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
482 564 $last_renewal = isset( $_POST['mainwp_module_cost_tracker_edit_last_renewal'] ) ? strtotime( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_last_renewal'] ) ) : 0;
@@ -538,13 +620,15 @@
538 620 $current = Cost_Tracker_DB::get_instance()->get_cost_tracker_by( 'id', $update['id'] );
539 621 }
540 622
541 623 //phpcs:enable
542 - $err_msg = '';
543 - $output = false;
624 + $err_msg = '';
625 + $output = false;
626 + $saved_id = 0;
544 627 try {
545 628 $output = Cost_Tracker_DB::get_instance()->update_cost_tracker( $update );
546 629 if ( $output && ! empty( $output->id ) ) {
630 + $saved_id = $output->id;
547 631 Cost_Tracker_DB::get_instance()->update_selected_lookup_cost( $output->id, $selected_sites, $selected_groups, $selected_clients );
548 632 if ( $current && ! empty( $current->cost_icon ) && false === strpos( $current->cost_icon, 'deficon:' ) && $current->cost_icon !== $update['cost_icon'] ) {
549 633 Cost_Tracker_Add_Edit::get_instance()->delete_product_icon_file( $current->cost_icon );
550 634 }
@@ -565,10 +649,24 @@
565 649 if ( ! empty( $err_msg ) ) {
566 650 set_transient( 'mainwp_cost_tracker_update_error_' . $msg_id, $err_msg, HOUR_IN_SECONDS );
567 651 }
568 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 +
569 667 if ( empty( $err_msg ) && ! empty( $output ) ) { // success.
570 - wp_safe_redirect( admin_url( 'admin.php?page=CostTrackerAdd&message=1&id=' . $output->id ) );
668 + wp_safe_redirect( admin_url( 'admin.php?page=ManageCostTracker&message=1' ) );
571 669 } elseif ( ! empty( $err_msg ) ) { // error.
572 670 wp_safe_redirect( admin_url( 'admin.php?page=CostTrackerAdd&message=2&id=' . $msg_id ) );
573 671 }
574 672 exit();
@@ -573,9 +671,45 @@
573 671 }
574 672 exit();
575 673 }
576 674
675 + /**
676 + * Handle get_costs_snapshot_info
677 + *
678 + * @return array
679 + */
680 + public static function get_costs_snapshot_info() {
577 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 +
578 712 /**
579 713 * Settigns Post
580 714 *
581 715 * Handles the save settings post request.
@@ -586,8 +720,10 @@
586 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' ) ) {
587 721 return;
588 722 }
589 723
724 + $old_settings = MainWP_Settings::get_all_settings_values();
725 +
590 726 $all_opts = Cost_Tracker_Utility::get_instance()->get_all_options();
591 727 $currency = isset( $_POST['mainwp_module_cost_tracker_settings_currency'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_settings_currency'] ) ) : '';
592 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
593 729 $currency_format = Cost_Tracker_Utility::validate_currency_settings( $currency_format );
@@ -616,8 +752,22 @@
616 752 $all_opts = apply_filters( 'mainwp_module_cost_tracker_before_save_settings', $all_opts );
617 753
618 754 Cost_Tracker_Utility::get_instance()->save_options( $all_opts );
619 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 +
620 770 wp_safe_redirect( admin_url( 'admin.php?page=CostTrackerSettings&message=1' ) );
621 771 exit();
622 772 }
623 773
@@ -660,8 +810,192 @@
660 810 return $valid_arr;
661 811 }
662 812
663 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 + /**
664 998 * Method hook_get_next_renewal().
665 999 *
666 1000 * Get the next renewal.
667 1001 *
@@ -781,11 +1115,14 @@
781 1115 *
782 1116 * @param string $type Product type.
783 1117 */
784 1118 public static function get_product_type_icons( $type = false ) {
1119 + $defaults = static::get_default_product_types_icons();
785 1120 $product_icons = Cost_Tracker_Utility::get_instance()->get_option( 'product_types_icons', array(), true );
786 - if ( ! is_array( $product_icons ) ) {
787 - $product_icons = array();
1121 + if ( is_array( $product_icons ) && ! empty( $product_icons ) ) {
1122 + $product_icons = array_replace( $defaults, $product_icons );
1123 + } else {
1124 + $product_icons = $defaults;
788 1125 }
789 1126 if ( ! empty( $type ) && is_string( $type ) ) {
790 1127 return isset( $product_icons[ $type ] ) ? $product_icons[ $type ] : '';
791 1128 }
@@ -888,15 +1225,15 @@
888 1225 if ( isset( $_GET['page'] ) && in_array( $_GET['page'], $allow_pages, true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
889 1226 ?>
890 1227 <p><?php esc_html_e( 'If you need help with the Cost Tracker module, please review following help documents', 'mainwp' ); ?></p>
891 1228 <div class="ui list">
892 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/mainwp-cost-tracker/" target="_blank">Cost Tracker</a></div> <?php // NOSONAR -- compatible with help. ?>
893 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/mainwp-cost-tracker/#manage-costs-page" target="_blank">Manage Costs</a></div> <?php // NOSONAR -- compatible with help. ?>
894 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/mainwp-cost-tracker/#adding-a-new-cost-to-track" target="_blank">Adding a New Cost to track</a></div> <?php // NOSONAR -- compatible with help. ?>
895 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/mainwp-cost-tracker/#edit-an-item" target="_blank">Edit Costs</a></div> <?php // NOSONAR -- compatible with help. ?>
896 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/mainwp-cost-tracker/#delete-an-item" target="_blank">Delete Costs</a></div> <?php // NOSONAR -- compatible with help. ?>
897 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/mainwp-cost-tracker/#settings-page" target="_blank">Cost Tracker Settings</a></div> <?php // NOSONAR -- compatible with help. ?>
898 - <div class="item"><i class="external alternate icon"></i> <a href="https://kb.mainwp.com/docs/mainwp-cost-tracker/#cost-tracker-pro-extension" target="_blank">Cost Tracker Pro Extension</a></div> <?php // NOSONAR -- compatible with help. ?>
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. ?>
899 1236 <?php
900 1237 /**
901 1238 * Action: mainwp_module_cost_tracker_help_item
902 1239 *
@@ -1049,9 +1386,9 @@
1049 1386 } else {
1050 1387 $upload_icon = $prod_icon;
1051 1388 }
1052 1389
1053 - $style = 'width:32px;height:auto;display:inline-block;';
1390 + $style = 'width:28px;height:auto;display:inline-block;';
1054 1391
1055 1392 if ( empty( $prod_color ) ) {
1056 1393 $prod_color = '#34424D';
1057 1394 }
@@ -1076,9 +1413,9 @@
1076 1413 $scr = $icon_base . $upload_icon;
1077 1414 } else {
1078 1415 $scr = '';
1079 1416 }
1080 - $icon_wrapper_attr = ! empty( $img_id_attr ) ? 'id="' . esc_attr( $img_id_attr ) . '" class="ui mini circular image" ' : 'class="' . esc_attr( $default_cls ) . ' ui mini circular image "';
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 "';
1081 1418 $icon = '<div style="display:inline-block;" ' . $icon_wrapper_attr . '><img style="' . $style . '" alt="' . esc_attr__( 'Product icon', 'mainwp' ) . '" src="' . esc_attr( $scr ) . '"/></div>';
1082 1419 }
1083 1420 return $icon;
1084 1421 }