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.3 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 All 156 releases
← All changes | modules/cost-tracker/classes/class-cost-tracker-admin.php +1283 -911 5.0.3 → 6.2.2 View file →
@@ -14,1037 +14,1409 @@
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;
20 +use MainWP\Dashboard\MainWP_Exception;
21 +use MainWP\Dashboard\Module\CostTracker\Cost_Tracker_Import;
19 22
20 -use function MainWP\Dashboard\mainwp_current_user_have_right;
23 +// Exit if accessed directly.
24 +if ( ! defined( 'ABSPATH' ) ) {
25 + exit;
26 +}
21 27
22 28 /**
23 29 * Class Cost_Tracker_Admin
24 30 */
25 -class Cost_Tracker_Admin {
31 +class Cost_Tracker_Admin { // phpcs:ignore -- NOSONAR - multi methods.
26 32
27 - /**
28 - * Variable to hold the version number.
29 - *
30 - * @var mixed Version.
31 - */
32 - public $version = '1.0';
33 + /**
34 + * Variable to hold the version number.
35 + *
36 + * @var mixed Version.
37 + */
38 + public $version = '1.0';
33 39
34 - /**
35 - * Variable to hold the Page value.
36 - *
37 - * @static
38 - * @var mixed Page value, default null.
39 - */
40 - public static $page = null;
40 + /**
41 + * Variable to hold the Page value.
42 + *
43 + * @static
44 + * @var mixed Page value, default null.
45 + */
46 + public static $page = null;
41 47
42 - /**
43 - * Variable to hold the Sub Pages.
44 - *
45 - * @static
46 - * @var mixed Sub Page, default null.
47 - */
48 - public static $subPages = null;
48 + /**
49 + * Variable to hold the Sub Pages.
50 + *
51 + * @static
52 + * @var mixed Sub Page, default null.
53 + */
54 + public static $subPages = null;
49 55
50 - /**
51 - * Static variable to hold the single instance of the class.
52 - *
53 - * @static
54 - *
55 - * @var mixed Default null
56 - */
57 - public static $instance = null;
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;
58 64
59 - /**
60 - * Get Instance
61 - *
62 - * Creates public static instance.
63 - *
64 - * @static
65 - *
66 - * @return Cost_Tracker_Admin
67 - */
68 - public static function get_instance() {
69 - if ( null === self::$instance ) {
70 - self::$instance = new self();
71 - }
72 - return self::$instance;
73 - }
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 + }
74 80
75 - /**
76 - * Constructor
77 - *
78 - * Runs each time the class is called.
79 - */
80 - public function __construct() {
81 - add_action( 'init', array( &$this, 'init' ) );
82 - add_filter( 'mainwp_init_primary_menu_items', array( $this, 'hook_init_primary_menu_items' ), 10, 2 );
83 - add_filter( 'mainwp_module_cost_tracker_get_default_cost_fields', array( $this, 'hook_get_default_cost_fields' ), 10, 2 );
84 - add_filter( 'mainwp_module_cost_tracker_get_next_renewal', array( $this, 'hook_get_next_renewal' ), 10, 3 );
85 - add_action( 'mainwp_delete_site', array( $this, 'hook_delete_site' ), 10, 3 );
86 - add_filter( 'mainwp_module_cost_tracker_get_total_cost', array( $this, 'hook_get_total_cost' ), 10, 2 );
87 - }
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 + }
88 96
89 97
90 - /**
91 - * Initiate Hooks
92 - *
93 - * Initiates hooks for the Subscription extension.
94 - */
95 - public function init() {
96 - add_action( 'admin_init', array( &$this, 'admin_init' ) );
97 - add_action( 'mainwp_help_sidebar_content', array( $this, 'mainwp_help_content' ) );
98 - add_filter( 'mainwp_log_specific_actions', array( $this, 'hook_log_specific_actions' ) );
99 - Cost_Tracker_Hooks::get_instance()->init();
100 - Cost_Tracker_Dashboard::get_instance();
101 - Cost_Tracker_DB::get_instance()->init();
102 - $this->handle_sites_screen_settings();
103 - }
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 + }
104 112
105 - /**
106 - * Method hook_delete_site()
107 - *
108 - * Installs the new DB.
109 - *
110 - * @param mixed $site site object.
111 - *
112 - * @return bool result.
113 - */
114 - public function hook_delete_site( $site ) {
115 - if ( empty( $site ) ) {
116 - return false;
117 - }
118 - return MainWP_DB::instance()->delete_lookup_items(
119 - 'object_id',
120 - array(
121 - 'item_name' => 'cost',
122 - 'object_id' => $site->id,
123 - 'object_names' => array( 'site' ),
124 - )
125 - );
126 - }
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 + }
127 135
128 - /**
129 - * Admin Init
130 - *
131 - * Initiates admin hooks.
132 - */
133 - public function admin_init() {
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 + }
134 159
135 - MainWP_Post_Handler::instance()->add_action( 'mainwp_module_cost_tracker_upload_product_icon', array( Cost_Tracker_Add_Edit::get_instance(), 'ajax_upload_product_icon' ) );
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 + }
136 184
137 - $this->handle_edit_cost_tracker_post();
138 - $this->handle_settings_post();
185 + /**
186 + * Admin Init
187 + *
188 + * Initiates admin hooks.
189 + */
190 + public function admin_init() {
139 191
140 - $allow_pages = array( 'ManageCostTracker', 'CostTrackerAdd', 'CostTrackerSettings', 'CostSummary' );
141 - if ( isset( $_GET['page'] ) && in_array( $_GET['page'], $allow_pages, true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
142 - $base_url = Cost_Tracker_Manager::get_location_path( 'url' );
143 - wp_enqueue_style( 'mainwp-module-cost-tracker-extension', $base_url . 'ui/css/cost-tracker.css', array(), $this->version );
144 - wp_enqueue_script( 'mainwp-module-cost-tracker-extension', $base_url . 'ui/js/cost-tracker.js', array( 'jquery' ), $this->version, true );
145 - if ( 'CostSummary' === $_GET['page'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
146 - add_filter(
147 - 'mainwp_admin_enqueue_scripts',
148 - function ( $scripts ) {
149 - if ( is_array( $scripts ) ) {
150 - $scripts['apexcharts'] = true;
151 - }
152 - return $scripts;
153 - }
154 - );
155 - }
156 - }
157 - }
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' ) );
158 194
159 - /**
160 - * Hook hook_log_specific_actions.
161 - *
162 - * @param array $logs_spec Log actions.
163 - *
164 - * @return array $logs_spec Log actions.
165 - */
166 - public function hook_log_specific_actions( $logs_spec ) {
167 - $logs_spec[ MainWP_Logger::COST_TRACKER_LOG_PRIORITY ] = __( 'Cost Tracker', 'mainwp' );
168 - return $logs_spec;
169 - }
195 + $this->handle_edit_cost_tracker_post();
196 + $this->handle_settings_post();
170 197
171 - /**
172 - * Init sub menu logs settings.
173 - *
174 - * @param array $items Sub menu items.
175 - * @param string $which_menu first|second.
176 - *
177 - * @return array $tmp_items Menu items.
178 - */
179 - public function hook_init_primary_menu_items( $items, $which_menu ) {
180 - if ( ! is_array( $items ) || 'first' !== $which_menu ) {
181 - return $items;
182 - }
183 - $items[] = array(
184 - 'slug' => 'ManageCostTracker',
185 - 'menu_level' => 2,
186 - 'menu_rights' => array(
187 - 'dashboard' => array(
188 - 'manage_cost_tracker',
189 - ),
190 - ),
191 - 'init_menu_callback' => array( self::class, 'init_menu' ),
192 - 'leftbar_order' => 2.8,
193 - );
194 - return $items;
195 - }
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 + }
196 218
197 - /**
198 - * Method hook_get_default_cost_fields().
199 - *
200 - * @param string $def_val Default value.
201 - * @param string $field Field name.
202 - *
203 - * @return array Default fields values.
204 - */
205 - public function hook_get_default_cost_fields( $def_val = null, $field = 'all' ) {
206 - unset( $def_val );
207 - return self::get_default_fields_values( $field );
208 - }
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 + }
209 230
210 - /**
211 - * Method get_default_fields_values().
212 - *
213 - * @param string $field Field name.
214 - *
215 - * @return array Default fields values.
216 - */
217 - public static function get_default_fields_values( $field = 'all' ) {
218 - if ( empty( $field ) || ! is_string( $field ) ) {
219 - $field = 'all';
220 - }
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 + }
221 256
222 - $license_types = array(
223 - 'single_site' => esc_html__( 'Single-Site License', 'mainwp' ),
224 - 'multi_site' => esc_html__( 'Multiple-Site License', 'mainwp' ),
225 - );
226 - $product_types = self::get_product_types();
227 - $payment_types = array(
228 - 'subscription' => esc_html__( 'Subscription', 'mainwp' ),
229 - 'lifetime' => esc_html__( 'Lifetime', 'mainwp' ),
230 - );
231 - $payment_methods = self::get_payment_methods();
232 - $renewal_frequency = self::get_renewal_frequency();
233 - $cost_status = self::get_cost_status();
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 + }
234 269
235 - $all_defaults = array(
236 - 'license_types' => $license_types,
237 - 'product_types' => $product_types,
238 - 'payment_types' => $payment_types,
239 - 'payment_methods' => $payment_methods,
240 - 'renewal_frequency' => $renewal_frequency,
241 - 'cost_status' => $cost_status,
242 - );
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 + }
243 281
244 - if ( 'all' === $field ) {
245 - return $all_defaults;
246 - }
247 - return isset( $all_defaults[ $field ] ) ? $all_defaults[ $field ] : array();
248 - }
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();
249 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 + );
250 303
251 - /**
252 - * Method init_menu()
253 - *
254 - * Add Insights Overview sub menu "Insights".
255 - */
256 - public static function init_menu() {
304 + if ( 'all' === $field ) {
305 + return $all_defaults;
306 + }
307 + return isset( $all_defaults[ $field ] ) ? $all_defaults[ $field ] : array();
308 + }
257 309
258 - self::$page = add_submenu_page(
259 - 'mainwp_tab',
260 - esc_html__( 'Cost Tracker', 'mainwp' ),
261 - '<span id="mainwp-cost-tracker-summary">' . esc_html__( 'Cost Tracker', 'mainwp' ) . '</span>',
262 - 'read',
263 - 'CostSummary',
264 - array(
265 - Cost_Tracker_Summary::instance(),
266 - 'render_summary_page',
267 - )
268 - );
269 310
270 - add_submenu_page(
271 - 'mainwp_tab',
272 - esc_html__( 'Manage Cost Tracker', 'mainwp' ),
273 - '<div class="mainwp-hidden">' . esc_html__( 'Manage Cost Tracker', 'mainwp' ) . '</div>',
274 - 'read',
275 - 'ManageCostTracker',
276 - array(
277 - Cost_Tracker_Dashboard::get_instance(),
278 - 'render_overview_page',
279 - )
280 - );
311 + /**
312 + * Method init_menu()
313 + *
314 + * Add Insights Overview sub menu "Insights".
315 + */
316 + public static function init_menu() {
281 317
282 - add_submenu_page(
283 - 'mainwp_tab',
284 - esc_html__( 'Add New', 'mainwp' ),
285 - '<div class="mainwp-hidden">' . esc_html__( 'Add New', 'mainwp' ) . '</div>',
286 - 'read',
287 - 'CostTrackerAdd',
288 - array(
289 - Cost_Tracker_Add_Edit::get_instance(),
290 - 'render_add_edit_page',
291 - )
292 - );
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 + );
293 329
294 - add_submenu_page(
295 - 'mainwp_tab',
296 - esc_html__( 'Settings', 'mainwp' ),
297 - '<div class="mainwp-hidden">' . esc_html__( 'Settings', 'mainwp' ) . '</div>',
298 - 'read',
299 - 'CostTrackerSettings',
300 - array(
301 - Cost_Tracker_Settings::get_instance(),
302 - 'render_settings_page',
303 - )
304 - );
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 + );
305 341
306 - /**
307 - * This hook allows you to add extra sub pages to the client page via the 'mainwp_getsubpages_cost_tracker' filter.
308 - */
309 - self::$subPages = apply_filters( 'mainwp_getsubpages_cost_tracker', array() );
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 + );
310 353
311 - if ( isset( self::$subPages ) && is_array( self::$subPages ) ) {
312 - foreach ( self::$subPages as $subPage ) {
313 - if ( empty( $subPage['slug'] ) || empty( $subPage['callback'] ) ) {
314 - continue;
315 - }
316 - if ( MainWP_Menu::is_disable_menu_item( 3, 'ManageCostTracker' . $subPage['slug'] ) ) {
317 - continue;
318 - }
319 - add_submenu_page( 'mainwp_tab', $subPage['title'], '<div class="mainwp-hidden">' . esc_html( $subPage['title'] ) . '</div>', 'read', 'ManageCostTracker' . $subPage['slug'], $subPage['callback'] );
320 - }
321 - }
322 - add_action( 'load-' . self::$page, array( self::class, 'on_load_summary_page' ) );
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 + );
323 365
324 - if ( isset( $_GET['page'] ) && 'CostSummary' === $_GET['page'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
325 - add_filter( 'mainwp_enqueue_script_gridster', '__return_true' );
326 - }
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() );
327 370
328 - self::init_left_menu( self::$subPages );
329 - }
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' ) );
330 383
331 - /**
332 - * Method on_load_summary_page()
333 - *
334 - * Run on page load.
335 - */
336 - public static function on_load_summary_page() {
337 - Cost_Tracker_Summary::instance()->on_load_page( self::$page );
338 - }
384 + if ( isset( $_GET['page'] ) && 'CostSummary' === $_GET['page'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
385 + add_filter( 'mainwp_enqueue_script_gridster', '__return_true' );
386 + }
339 387
340 - /**
341 - * Initiates Cost Tracker menu.
342 - *
343 - * @param array $subPages Cost Tracker sub pages.
344 - */
345 - public static function init_left_menu( $subPages ) {
346 - MainWP_Menu::add_left_menu(
347 - array(
348 - 'title' => esc_html__( 'Cost Tracker', 'mainwp' ),
349 - 'parent_key' => 'mainwp_tab',
350 - 'slug' => 'CostSummary',
351 - 'href' => 'admin.php?page=CostSummary',
352 - 'icon' => '<i class="dollar sign icon"></i>',
353 - 'desc' => 'Costr Tracker Summary',
354 - 'nosubmenu' => true,
355 - ),
356 - 0
357 - );
358 - $init_sub_subleftmenu = array(
359 - array(
360 - 'title' => esc_html__( 'Cost Summary', 'mainwp' ),
361 - 'parent_key' => 'CostSummary',
362 - 'href' => 'admin.php?page=CostSummary',
363 - 'slug' => 'CostSummary',
364 - 'right' => 'manage_cost_tracker',
365 - ),
366 - array(
367 - 'title' => esc_html__( 'Manage Costs', 'mainwp' ),
368 - 'parent_key' => 'CostSummary',
369 - 'href' => 'admin.php?page=ManageCostTracker',
370 - 'slug' => 'ManageCostTracker',
371 - 'right' => 'manage_cost_tracker',
372 - ),
373 - array(
374 - 'title' => esc_html__( 'Add New', 'mainwp' ),
375 - 'parent_key' => 'CostSummary',
376 - 'href' => 'admin.php?page=CostTrackerAdd',
377 - 'slug' => 'CostTrackerAdd',
378 - 'right' => '',
379 - ),
380 - array(
381 - 'title' => esc_html__( 'Settings', 'mainwp' ),
382 - 'parent_key' => 'CostSummary',
383 - 'href' => 'admin.php?page=CostTrackerSettings',
384 - 'slug' => 'CostTrackerSettings',
385 - 'right' => '',
386 - ),
387 - );
388 + static::init_left_menu( static::$subPages );
389 + }
388 390
389 - MainWP_Menu::init_subpages_left_menu( $subPages, $init_sub_subleftmenu, 'ManageCostTracker', 'ManageCostTracker' );
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 + }
390 403
391 - foreach ( $init_sub_subleftmenu as $item ) {
392 - if ( MainWP_Menu::is_disable_menu_item( 3, $item['slug'] ) ) {
393 - continue;
394 - }
395 - MainWP_Menu::add_left_menu( $item, 2 );
396 - }
397 - }
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 + );
398 422
423 + $sum_item_class = '';
399 424
425 + if ( ! \mainwp_current_user_can( 'dashboard', 'access_cost_summary_dashboard' ) ) {
426 + $sum_item_class = 'hidden-menu-item';
427 + }
400 428
401 - /**
402 - * Method render_header()
403 - *
404 - * Render page header.
405 - *
406 - * @param string $shownPage Current Page.
407 - */
408 - public static function render_header( $shownPage = '' ) {
409 - $params = array(
410 - 'title' => esc_html__( 'Cost Tracker', 'mainwp' ),
411 - 'which' => 'page_cost_tracker_overview',
412 - 'wrap_class' => 'mainwp-module-cost-tracker-content-wrapper',
413 - );
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 + );
414 460
415 - MainWP_UI::render_top_header( $params );
461 + MainWP_Menu::init_subpages_left_menu( $subPages, $init_sub_subleftmenu, 'ManageCostTracker', 'ManageCostTracker' );
416 462
417 - $renderItems = array();
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 + }
418 470
419 - if ( mainwp_current_user_have_right( 'dashboard', 'manage_cost_tracker' ) ) {
420 - $renderItems[] = array(
421 - 'title' => esc_html__( 'Overview', 'mainwp' ),
422 - 'href' => 'admin.php?page=ManageCostTracker',
423 - 'active' => ( '' === $shownPage ) ? true : false,
424 - );
425 - }
426 471
427 - if ( ! MainWP_Menu::is_disable_menu_item( 3, 'CostTrackerAdd' ) ) {
428 - $renderItems[] = array(
429 - 'title' => esc_html__( 'Add New', 'mainwp' ),
430 - 'href' => 'admin.php?page=CostTrackerAdd',
431 - 'active' => ( 'add' === $shownPage ) ? true : false,
432 - );
433 - }
434 472
435 - $cost_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
436 - if ( $cost_id ) {
437 - $renderItems[] = array(
438 - 'title' => esc_html__( 'Edit Cost Tracker', 'mainwp' ),
439 - 'href' => 'admin.php?page=CostTrackerAdd&id=' . $cost_id,
440 - 'active' => ( 'edit' === $shownPage ) ? true : false,
441 - );
442 - }
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 + }
443 487
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 - if ( isset( self::$subPages ) && is_array( self::$subPages ) ) {
452 - foreach ( self::$subPages as $subPage ) {
453 - if ( MainWP_Menu::is_disable_menu_item( 3, 'ManageCostTracker' . $subPage['slug'] ) ) {
454 - continue;
455 - }
488 + $params = array(
489 + 'title' => $title,
490 + 'which' => 'page_cost_tracker_overview',
491 + 'wrap_class' => 'mainwp-module-cost-tracker-content-wrapper',
492 + );
456 493
457 - $item = array();
458 - $item['title'] = $subPage['title'];
494 + MainWP_UI::render_top_header( $params );
459 495
460 - $item['href'] = 'admin.php?page=ManageCostTracker' . $subPage['slug'];
461 - $item['active'] = ( $subPage['slug'] === $shownPage ) ? true : false;
462 - $renderItems[] = $item;
463 - }
464 - }
496 + $renderItems = array();
465 497
466 - MainWP_UI::render_page_navigation( $renderItems );
467 - }
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 + }
468 505
469 - /**
470 - * Edit subscription Post
471 - *
472 - * Handles the saving subscription.
473 - *
474 - * @return mixed Save output.
475 - */
476 - public static function handle_edit_cost_tracker_post() { //phpcs:ignore -- complex method.
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 + }
477 513
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' ) ) {
479 - return;
480 - }
481 - //phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
482 - $last_renewal = isset( $_POST['mainwp_module_cost_tracker_edit_last_renewal'] ) ? strtotime( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_last_renewal'] ) ) : 0;
483 - $update = array();
484 - $update['name'] = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_name'] ) );
485 - $update['type'] = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_payment_type'] ) );
486 - $update['product_type'] = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_product_type'] ) );
487 - $update['slug'] = isset( $_POST['mainwp_module_cost_tracker_edit_product_slug'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_product_slug'] ) ) : '';
488 - $update['license_type'] = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_license_type'] ) );
489 - $update['cost_status'] = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_cost_tracker_status'] ) );
490 - $update['url'] = ! empty( $_POST['mainwp_module_cost_tracker_edit_url'] ) ? esc_url_raw( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_url'] ) ) : '';
491 - $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'] ) ) : '';
492 - $update['cost_color'] = sanitize_hex_color( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_product_color'] ) );
493 - $update['price'] = floatval( $_POST['mainwp_module_cost_tracker_edit_price'] );
494 - $update['payment_method'] = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_payment_method'] ) );
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 + }
495 521
496 - $renewal_fequency = sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_edit_renewal_type'] ) );
497 - $update['renewal_type'] = $renewal_fequency;
498 - $update['last_renewal'] = $last_renewal; // labeled Purchase date.
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 + }
499 530
500 - $next_renewal = self::get_next_renewal( $last_renewal, $renewal_fequency );
501 - $update['next_renewal'] = $next_renewal;
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 + }
502 536
503 - $note = isset( $_POST['mainwp_module_cost_tracker_edit_note'] ) ? wp_unslash( $_POST['mainwp_module_cost_tracker_edit_note'] ) : '';
504 - $esc_note = apply_filters( 'mainwp_escape_content', $note );
505 - $update['note'] = $esc_note;
537 + $item = array();
538 + $item['title'] = $subPage['title'];
506 539
507 - $selected_sites = array();
508 - $selected_groups = array();
509 - $selected_clients = array();
540 + $item['href'] = 'admin.php?page=ManageCostTracker' . $subPage['slug'];
541 + $item['active'] = ( $subPage['slug'] === $shownPage ) ? true : false;
542 + $renderItems[] = $item;
543 + }
544 + }
510 545
511 - if ( isset( $_POST['select_by'] ) ) {
512 - if ( isset( $_POST['selected_sites'] ) && is_array( $_POST['selected_sites'] ) ) {
513 - foreach ( wp_unslash( $_POST['selected_sites'] ) as $selected ) {
514 - $selected_sites[] = intval( $selected );
515 - }
516 - }
546 + MainWP_UI::render_page_navigation( $renderItems );
547 + }
517 548
518 - if ( isset( $_POST['selected_groups'] ) && is_array( $_POST['selected_groups'] ) ) {
519 - foreach ( wp_unslash( $_POST['selected_groups'] ) as $selected ) {
520 - $selected_groups[] = intval( $selected );
521 - }
522 - }
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.
523 557
524 - if ( isset( $_POST['selected_clients'] ) && is_array( $_POST['selected_clients'] ) ) {
525 - foreach ( wp_unslash( $_POST['selected_clients'] ) as $selected ) {
526 - $selected_clients[] = intval( $selected );
527 - }
528 - }
529 - }
558 + $updating = ! empty( $_POST['mainwp_module_cost_tracker_edit_id'] ) ? intval( $_POST['mainwp_module_cost_tracker_edit_id'] ) : ''; //phpcs:ignore -- -- NOSONAR - ok.
530 559
531 - $update['sites'] = ! empty( $selected_sites ) ? wp_json_encode( $selected_sites ) : '';
532 - $update['groups'] = ! empty( $selected_groups ) ? wp_json_encode( $selected_groups ) : '';
533 - $update['clients'] = ! empty( $selected_clients ) ? wp_json_encode( $selected_clients ) : '';
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'] ) );
534 577
535 - $current = false;
536 - if ( ! empty( $_POST['mainwp_module_cost_tracker_edit_id'] ) ) {
537 - $update['id'] = intval( $_POST['mainwp_module_cost_tracker_edit_id'] );
538 - $current = Cost_Tracker_DB::get_instance()->get_cost_tracker_by( 'id', $update['id'] );
539 - }
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.
540 581
541 - //phpcs:enable
542 - $err_msg = '';
543 - $output = false;
544 - try {
545 - $output = Cost_Tracker_DB::get_instance()->update_cost_tracker( $update );
546 - if ( $output && ! empty( $output->id ) ) {
547 - Cost_Tracker_DB::get_instance()->update_selected_lookup_cost( $output->id, $selected_sites, $selected_groups, $selected_clients );
548 - if ( $current && ! empty( $current->cost_icon ) && false === strpos( $current->cost_icon, 'deficon:' ) && $current->cost_icon !== $update['cost_icon'] ) {
549 - Cost_Tracker_Add_Edit::get_instance()->delete_product_icon_file( $current->cost_icon );
550 - }
551 - }
552 - } catch ( \Exception $ex ) {
553 - $err_msg = $ex->getMessage();
554 - }
582 + $next_renewal = static::get_next_renewal( $last_renewal, $renewal_fequency );
583 + $update['next_renewal'] = $next_renewal;
555 584
556 - $msg_id = ! empty( $update['id'] ) ? intval( $update['id'] ) : 0;
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;
557 588
558 - if ( ! empty( $err_msg ) ) {
559 - set_transient( 'mainwp_cost_tracker_update_error_' . $msg_id, $err_msg, HOUR_IN_SECONDS );
560 - }
589 + $selected_sites = array();
590 + $selected_groups = array();
591 + $selected_clients = array();
561 592
562 - if ( empty( $err_msg ) && ! empty( $output ) ) { // success.
563 - wp_safe_redirect( admin_url( 'admin.php?page=CostTrackerAdd&message=1&id=' . $output->id ) );
564 - } elseif ( ! empty( $err_msg ) ) { // error.
565 - wp_safe_redirect( admin_url( 'admin.php?page=CostTrackerAdd&message=2&id=' . $msg_id ) );
566 - }
567 - exit();
568 - }
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 + }
569 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 + }
570 605
571 - /**
572 - * Settigns Post
573 - *
574 - * Handles the save settings post request.
575 - *
576 - * @return mixed Save output.
577 - */
578 - public static function handle_settings_post() {
579 - 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' ) ) {
580 - return;
581 - }
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 + }
582 612
583 - $all_opts = Cost_Tracker_Utility::get_instance()->get_all_options();
584 - $currency = isset( $_POST['mainwp_module_cost_tracker_settings_currency'] ) ? sanitize_text_field( wp_unslash( $_POST['mainwp_module_cost_tracker_settings_currency'] ) ) : '';
585 - $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
586 - $currency_format = Cost_Tracker_Utility::validate_currency_settings( $currency_format );
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 ) : '';
587 616
588 - $product_types_colors = array();
589 - $product_types_icons = array();
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 + }
590 622
591 - // first.
592 - $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
593 - $cust_product_types = self::validate_custom_settings_text_fields( $cust_product_types, $product_types_colors, $product_types_icons );
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 + }
594 646
595 - // second.
596 - $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
597 - self::validate_custom_settings_text_fields( $default_product_colors, $product_types_colors, $product_types_icons );
647 + $msg_id = ! empty( $update['id'] ) ? intval( $update['id'] ) : 0;
598 648
599 - $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
600 - $cust_payment_methods = self::validate_custom_settings_text_fields( $cust_payment_methods );
649 + if ( ! empty( $err_msg ) ) {
650 + set_transient( 'mainwp_cost_tracker_update_error_' . $msg_id, $err_msg, HOUR_IN_SECONDS );
651 + }
601 652
602 - $all_opts['currency'] = $currency;
603 - $all_opts['currency_format'] = $currency_format;
604 - $all_opts['custom_product_types'] = wp_json_encode( $cust_product_types );
605 - $all_opts['custom_payment_methods'] = wp_json_encode( $cust_payment_methods );
606 - $all_opts['product_types_colors'] = wp_json_encode( $product_types_colors );
607 - $all_opts['product_types_icons'] = wp_json_encode( $product_types_icons );
653 + $snapshot_info = static::get_costs_snapshot_info();
608 654
609 - $all_opts = apply_filters( 'mainwp_module_cost_tracker_before_save_settings', $all_opts );
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 );
610 666
611 - Cost_Tracker_Utility::get_instance()->save_options( $all_opts );
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 + }
612 674
613 - wp_safe_redirect( admin_url( 'admin.php?page=CostTrackerSettings&message=1' ) );
614 - exit();
615 - }
675 + /**
676 + * Handle get_costs_snapshot_info
677 + *
678 + * @return array
679 + */
680 + public static function get_costs_snapshot_info() {
616 681
682 + $costs = Cost_Tracker_DB::get_instance()->get_cost_tracker_by( 'all' );
617 683
618 - /**
619 - * Method array_validate_text_fields().
620 - *
621 - * @param array $arr Data to valid.
622 - * @param mixed $product_types_colors Product types colors.
623 - * @param mixed $product_types_icons Product types icons.
624 - *
625 - * @return array Validated array fields data.
626 - */
627 - public static function validate_custom_settings_text_fields( $arr, &$product_types_colors = null, &$product_types_icons = null ) {
628 - if ( ! is_array( $arr ) || ! isset( $arr['title'] ) || ! is_array( $arr['title'] ) ) {
629 - return array();
630 - }
631 - $valid_arr = array();
632 - foreach ( $arr['title'] as $idx => $title ) {
633 - $title = trim( $title );
634 - if ( empty( $title ) ) {
635 - continue;
636 - }
637 - $slug = isset( $arr['slug'][ $idx ] ) ? sanitize_title( $arr['slug'][ $idx ] ) : '';
684 + $total = 0;
685 + $recurring = 0;
686 + $one_time = 0;
687 + $active = 0;
638 688
639 - if ( empty( $slug ) ) {
640 - $slug = sanitize_title( $title );
641 - }
642 - $slug = strtolower( $slug );
643 - $valid_arr[ $slug ] = sanitize_text_field( $title );
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 + }
644 702
645 - if ( is_array( $product_types_colors ) && is_array( $arr['color'] ) && isset( $arr['color'][ $idx ] ) ) {
646 - $product_types_colors[ $slug ] = sanitize_hex_color( wp_unslash( $arr['color'][ $idx ] ) );
647 - }
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 + }
648 710
649 - if ( is_array( $product_types_icons ) && is_array( $arr['icon'] ) && isset( $arr['icon'][ $idx ] ) ) {
650 - $product_types_icons[ $slug ] = sanitize_text_field( wp_unslash( $arr['icon'][ $idx ] ) );
651 - }
652 - }
653 - return $valid_arr;
654 - }
655 711
656 - /**
657 - * Method hook_get_next_renewal().
658 - *
659 - * Get the next renewal.
660 - *
661 - * @param mixed $filter_input filter input value.
662 - * @param int $previous_renewal last renewal.
663 - * @param string $renewal_type renewal time.
664 - */
665 - public function hook_get_next_renewal( $filter_input, $previous_renewal, $renewal_type ) {
666 - unset( $filter_input );
667 - return self::get_next_renewal( $previous_renewal, $renewal_type );
668 - }
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 + }
669 723
670 - /**
671 - * Method hook_get_total_cost().
672 - *
673 - * Get total costs.
674 - */
675 - public function hook_get_total_cost() {
676 - return Cost_Tracker_DB::get_instance()->get_cost_tracker_by( 'count' );
677 - }
724 + $old_settings = MainWP_Settings::get_all_settings_values();
678 725
679 - /**
680 - * Method get_next_renewal().
681 - *
682 - * Get the next renewal.
683 - *
684 - * @param int $previous_renewal last renewal.
685 - * @param string $renewal_type renewal time.
686 - * @param bool $get_real_renewal To get future renewal, true|false.
687 - */
688 - public static function get_next_renewal( $previous_renewal, $renewal_type, $get_real_renewal = true ) {
689 - $next_renewal = 0;
690 - if ( ! empty( $previous_renewal ) && ! empty( $renewal_type ) && 'disabled' !== $renewal_type ) {
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 );
691 730
692 - if ( 'monthly' === $renewal_type ) {
693 - $next_renewal = strtotime( '+1 month', $previous_renewal );
694 - } elseif ( 'yearly' === $renewal_type ) {
695 - $next_renewal = strtotime( '+1 year', $previous_renewal );
696 - } elseif ( 'weekly' === $renewal_type ) {
697 - $next_renewal = strtotime( '+1 week', $previous_renewal );
698 - } elseif ( 'quarterly' === $renewal_type ) {
699 - $next_renewal = strtotime( '+3 month', $previous_renewal );
700 - }
731 + $product_types_colors = array();
732 + $product_types_icons = array();
701 733
702 - $today_time = strtotime( gmdate( 'Y-m-d 00:00:00' ) );
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 );
703 737
704 - if ( $get_real_renewal && $next_renewal < $today_time ) {
705 - $next_renewal = self::get_next_renewal( $next_renewal, $renewal_type, $get_real_renewal );
706 - }
707 - }
708 - return $next_renewal;
709 - }
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 );
710 741
711 - /**
712 - * Method get_default_product_types().
713 - */
714 - public static function get_default_product_types() {
715 - return array(
716 - 'plugin' => esc_html__( 'Plugin', 'mainwp' ),
717 - 'theme' => esc_html__( 'Theme', 'mainwp' ),
718 - 'hosting' => esc_html__( 'Hosting', 'mainwp' ),
719 - 'service' => esc_html__( 'Service', 'mainwp' ),
720 - 'other' => esc_html__( 'Other', 'mainwp' ),
721 - );
722 - }
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 );
723 744
724 - /**
725 - * Method get_default_product_type_colors().
726 - */
727 - public static function get_default_product_type_colors() {
728 - return array(
729 - 'plugin' => '#5ec130',
730 - 'theme' => '#34afd8',
731 - 'hosting' => '#f2e93a',
732 - 'service' => '#ed1730',
733 - 'other' => '#14f4fc',
734 - );
735 - }
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 );
736 751
737 - /**
738 - * Method get_product_colors().
739 - *
740 - * @param string $type Product type.
741 - */
742 - public static function get_product_colors( $type = false ) {
743 - $defaults = self::get_default_product_type_colors();
752 + $all_opts = apply_filters( 'mainwp_module_cost_tracker_before_save_settings', $all_opts );
744 753
745 - $product_colors = Cost_Tracker_Utility::get_instance()->get_option( 'product_types_colors', array(), true );
746 - if ( is_array( $product_colors ) && ! empty( $product_colors ) ) {
747 - $product_colors = array_replace( $defaults, $product_colors ); // to fix: array merge with keys.
748 - } else {
749 - $product_colors = $defaults;
750 - }
754 + Cost_Tracker_Utility::get_instance()->save_options( $all_opts );
751 755
752 - if ( ! empty( $type ) && is_string( $type ) ) {
753 - return isset( $product_colors[ $type ] ) ? $product_colors[ $type ] : '';
754 - }
755 - return $product_colors; // return all colors.
756 - }
756 + $new_settings = MainWP_Settings::get_all_settings_values();
757 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 );
758 769
759 - /**
760 - * Method get_default_product_types_icons().
761 - */
762 - public static function get_default_product_types_icons() {
763 - return array(
764 - 'plugin' => 'plug',
765 - 'theme' => 'tint',
766 - 'hosting' => 'server',
767 - 'service' => 'wrench',
768 - 'other' => 'folder',
769 - );
770 - }
770 + wp_safe_redirect( admin_url( 'admin.php?page=CostTrackerSettings&message=1' ) );
771 + exit();
772 + }
771 773
772 - /**
773 - * Method get_product_type_icons().
774 - *
775 - * @param string $type Product type.
776 - */
777 - public static function get_product_type_icons( $type = false ) {
778 - $product_icons = Cost_Tracker_Utility::get_instance()->get_option( 'product_types_icons', array(), true );
779 - if ( ! is_array( $product_icons ) ) {
780 - $product_icons = array();
781 - }
782 - if ( ! empty( $type ) && is_string( $type ) ) {
783 - return isset( $product_icons[ $type ] ) ? $product_icons[ $type ] : '';
784 - }
785 - return $product_icons; // return all colors.
786 - }
787 774
788 - /**
789 - * Method get_product_types().
790 - */
791 - public static function get_product_types() {
792 - $product_types = self::get_default_product_types();
793 - $cust_product_types = Cost_Tracker_Utility::get_instance()->get_option( 'custom_product_types', array(), true );
794 - if ( ! empty( $cust_product_types ) ) {
795 - $product_types = array_replace( $product_types, $cust_product_types ); // to fix: array merge with keys.
796 - }
797 - return $product_types;
798 - }
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 ] ) : '';
799 795
800 - /**
801 - * Method get_payment_methods().
802 - */
803 - public static function get_payment_methods() {
804 - $payment_methods = array(
805 - 'paypal' => esc_html__( 'PayPal', 'mainwp' ),
806 - 'stripe' => esc_html__( 'Stripe', 'mainwp' ),
807 - 'apple' => esc_html__( 'Apple Pay', 'mainwp' ),
808 - 'amazon' => esc_html__( 'Amazon Pay', 'mainwp' ),
809 - 'google' => esc_html__( 'Google Pay', 'mainwp' ),
810 - 'credit_debit' => esc_html__( 'Credit Card', 'mainwp' ),
811 - 'debit_card' => esc_html__( 'Debit Card', 'mainwp' ),
812 - 'cash' => esc_html__( 'Cash', 'mainwp' ),
813 - );
814 - $cust_payment_methods = Cost_Tracker_Utility::get_instance()->get_option( 'custom_payment_methods', array(), true );
815 - if ( ! empty( $cust_payment_methods ) ) {
816 - $payment_methods = array_replace( $payment_methods, $cust_payment_methods ); // to fix: array merge with keys.
817 - }
818 - return $payment_methods;
819 - }
796 + if ( empty( $slug ) ) {
797 + $slug = sanitize_title( $title );
798 + }
799 + $slug = strtolower( $slug );
800 + $valid_arr[ $slug ] = sanitize_text_field( $title );
820 801
821 - /**
822 - * Method get_renewal_frequency().
823 - */
824 - public static function get_renewal_frequency() {
825 - return array(
826 - 'weekly' => esc_html__( 'Weekly', 'mainwp' ),
827 - 'monthly' => esc_html__( 'Monthly', 'mainwp' ),
828 - 'quarterly' => esc_html__( 'Quarterly', 'mainwp' ),
829 - 'yearly' => esc_html__( 'Yearly', 'mainwp' ),
830 - );
831 - }
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 + }
832 805
833 - /**
834 - * Method get_cost_status().
835 - */
836 - public static function get_cost_status() {
837 - return array(
838 - 'active' => esc_html__( 'Active', 'mainwp' ),
839 - 'canceled' => esc_html__( 'Canceled', 'mainwp' ),
840 - 'onhold' => esc_html__( 'On Hold', 'mainwp' ),
841 - 'expired' => esc_html__( 'Expired', 'mainwp' ),
842 - 'pending_cancelation' => esc_html__( 'Pending Cancelation', 'mainwp' ),
843 - );
844 - }
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 + }
845 812
846 - /**
847 - * Method handle_sites_screen_settings()
848 - *
849 - * Handle sites screen settings
850 - */
851 - public function handle_sites_screen_settings() {
852 - if ( isset( $_POST['wp_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_nonce'] ), 'CostTrackerSitesScrOptions' ) ) {
853 - $show_cols = array();
854 - foreach ( array_map( 'sanitize_text_field', wp_unslash( $_POST ) ) as $key => $val ) {
855 - if ( false !== strpos( $key, 'mainwp_show_column_' ) ) {
856 - $col = str_replace( 'mainwp_show_column_', '', $key );
857 - $show_cols[ $col ] = 1;
858 - }
859 - }
860 - if ( isset( $_POST['show_columns_name'] ) ) {
861 - foreach ( array_map( 'sanitize_text_field', wp_unslash( $_POST['show_columns_name'] ) ) as $col ) {
862 - if ( ! isset( $show_cols[ $col ] ) ) {
863 - $show_cols[ $col ] = 0; // uncheck, hide columns.
864 - }
865 - }
866 - }
867 - $user = wp_get_current_user();
868 - if ( $user ) {
869 - update_user_option( $user->ID, 'mainwp_module_costs_tracker_manage_showhide_columns', $show_cols, true );
870 - }
871 - }
872 - }
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 + );
873 835
874 - /**
875 - * Hooks the section help content to the Help Sidebar element.
876 - */
877 - public static function mainwp_help_content() {
878 - $allow_pages = array( 'ManageCostTracker', 'CostTrackerAdd', 'CostTrackerSettings' );
879 - if ( isset( $_GET['page'] ) && in_array( $_GET['page'], $allow_pages, true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
880 - ?>
881 - <p><?php esc_html_e( 'If you need help with the Cost Tracker extension, please review following help documents', 'mainwp' ); ?></p>
882 - <div class="ui relaxed bulleted list">
883 - <div class="item"><a href="" target="_blank"></a></div>
884 - <div class="item"><a href="" target="_blank"></a></div>
885 - <?php
886 - /**
887 - * Action: mainwp_module_cost_tracker_help_item
888 - *
889 - * Fires at the bottom of the help articles list in the Help sidebar on the Themes page.
890 - *
891 - * Suggested HTML markup:
892 - *
893 - * <div class="item"><a href="Your custom URL">Your custom text</a></div>
894 - *
895 - * @since 4.0
896 - */
897 - do_action( 'mainwp_module_cost_tracker_help_item' );
898 - ?>
899 - </div>
900 - <?php
901 - }
902 - }
836 + $fields = array();
903 837
904 - /**
905 - * Method generate_next_renewal()
906 - *
907 - * Generate next renewal info.
908 - *
909 - * @param object $subscription subscription.
910 - * @param int $next_renewal Next renewal.
911 - *
912 - * @return void
913 - */
914 - public static function generate_next_renewal( $subscription, $next_renewal = false ) {
915 - if ( empty( $subscription ) || ! is_object( $subscription ) ) {
916 - echo '';
917 - return;
918 - }
838 + foreach ( $spec as $field => $kind ) {
839 + $value = isset( $cost[ $field ] ) ? $cost[ $field ] : null;
919 840
920 - if ( 'lifetime' === $subscription->type ) {
921 - esc_html_e( '', 'mainwp' );
922 - return;
923 - }
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 + }
924 861
925 - $next_renewal = ! empty( $next_renewal ) ? intval( $next_renewal ) : (int) $subscription->next_renewal;
862 + if ( null === $value ) {
863 + $fields[ $field ] = '';
864 + continue;
865 + }
926 866
927 - if ( empty( $next_renewal ) ) {
928 - echo esc_html__( '', 'mainwp' );
929 - return;
930 - }
931 - if ( 'active' !== $subscription->cost_status ) {
932 - echo esc_html__( '', 'mainwp' );
933 - return;
934 - }
867 + if ( ! is_string( $value ) ) {
868 + return false;
869 + }
935 870
936 - $current_time = time();
937 - $renewal_html = MainWP_Utility::format_date( $next_renewal );
938 - $day1 = $next_renewal - 15 * DAY_IN_SECONDS;
939 - $day2 = $next_renewal - 7 * DAY_IN_SECONDS;
940 - if ( $day1 > $current_time ) {
941 - $renewal_html = '<strong>' . esc_html( $renewal_html ) . '</strong>';
942 - } elseif ( $day1 <= $current_time && $current_time < $day2 ) {
943 - $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>';
944 - } elseif ( $day2 <= $current_time && $current_time < $next_renewal ) {
945 - $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>';
946 - }
947 - echo $renewal_html; //phpcs:ignore -- ok.
948 - }
871 + $fields[ $field ] = 'url' === $kind ? esc_url_raw( $value ) : sanitize_text_field( $value );
872 + }
949 873
950 - /**
951 - * Returns the label for a cost status.
952 - *
953 - * @param mixed $key false|string to get status of key.
954 - *
955 - * @return string
956 - */
957 - public static function get_cost_status_label( $key = false ) {
874 + return $fields;
875 + }
958 876
959 - $default = array(
960 - 'active' => array(
961 - 'label' => esc_html__( 'Active', 'mainwp' ),
962 - 'class' => 'basic green center aligned fluid',
963 - ),
964 - 'onhold' => array(
965 - 'label' => esc_html__( 'On Hold', 'mainwp' ),
966 - 'class' => 'basic yellow center aligned fluid',
967 - ),
968 - 'canceled' => array(
969 - 'label' => esc_html__( 'Canceled', 'mainwp' ),
970 - 'class' => 'basic grey center aligned fluid',
971 - ),
972 - 'expired' => array(
973 - 'label' => esc_html__( 'Expired', 'mainwp' ),
974 - 'class' => 'basic purple center aligned fluid',
975 - ),
976 - 'pending_cancelation' => array(
977 - 'label' => esc_html__( 'Pending Cancelation', 'mainwp' ),
978 - 'class' => 'basic black center aligned fluid',
979 - ),
980 - );
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' );
981 884
982 - if ( false !== $key ) {
983 - if ( empty( $key ) ) {
984 - $key = 'active';
985 - }
986 - return isset( $default[ $key ] ) ? '<span class="ui small ' . $default[ $key ]['class'] . ' label">' . $default[ $key ]['label'] . '</span>' : $key;
987 - }
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 + }
988 892
989 - return $default;
990 - }
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 );
991 895
992 - /**
993 - * Gets product icon to output.
994 - *
995 - * @param string $product Product object.
996 - * @param string $img_id_attr img id attr.
997 - * @param bool $with_color with color.
998 - */
999 - public function get_product_icon_display( $product = false, $img_id_attr = '', $with_color = true ) {
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 + }
1000 904
1001 - $prod_icon = '';
1002 - $prod_color = '';
905 + $cost_name = isset( $cost_data_raw['cost']['name'] ) && is_string( $cost_data_raw['cost']['name'] ) ? sanitize_text_field( $cost_data_raw['cost']['name'] ) : '';
1003 906
1004 - if ( $product ) {
1005 - $prod_icon = $product->cost_icon;
1006 - $prod_color = $product->cost_color;
1007 - }
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 + }
1008 914
1009 - $dirs = MainWP_System_Utility::get_mainwp_dir( Cost_Tracker_Settings::$icon_sub_dir, true );
1010 - $icon_base = $dirs[1];
915 + $cost_fields = static::import_cost_fields( $cost_data_raw['cost'] );
1011 916
1012 - if ( empty( $prod_icon ) || false !== strpos( $prod_icon, 'deficon:' ) ) {
1013 - $upload_icon = '';
1014 - } else {
1015 - $upload_icon = $prod_icon;
1016 - }
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 + }
1017 924
1018 - $style = 'width:32px;height:auto;display:inline-block;';
925 + $cost_data = array_merge( array( 'name' => $cost_name ), $cost_fields );
1019 926
1020 - if ( empty( $prod_color ) ) {
1021 - $prod_color = '#34424D';
1022 - }
927 + $select_sites = $cost_data_raw['cost']['select_sites'] ?? array();
1023 928
1024 - $color_style = '';
1025 - if ( $with_color ) {
1026 - $color_style = 'color:' . esc_attr( $prod_color ) . ';';
1027 - }
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 + }
1028 936
1029 - $default_cls = 'module_cost_tracker_settings_upload_img_display';
1030 - if ( empty( $prod_icon ) ) {
1031 - $def_icon = Cost_Tracker_Utility::get_product_default_icons( false, 'default_product' );
1032 - $icon_wrapper_attr = ! empty( $img_id_attr ) ? 'id="' . esc_attr( $img_id_attr ) . '" ' : 'class="' . esc_attr( $default_cls ) . '"';
1033 - $icon = '<div style="display:inline-block;" ' . $icon_wrapper_attr . '><i style="' . $color_style . '" class="' . esc_attr( $def_icon ) . ' large icon"></i></div>';
1034 - } elseif ( false !== strpos( $prod_icon, 'deficon:' ) ) {
1035 - $icon_wrapper_attr = ! empty( $img_id_attr ) ? 'id="' . esc_attr( $img_id_attr ) . '" ' : 'class="' . esc_attr( $default_cls ) . '"';
1036 - $icon = '<div style="display:inline-block;" ' . $icon_wrapper_attr . '><i class="' . str_replace( 'deficon:', '', $prod_icon ) . ' large icon" style="' . $color_style . '" ></i></div>';
1037 - } else {
1038 - if ( ! empty( $upload_icon ) ) {
1039 - $dirs = MainWP_System_Utility::get_mainwp_dir( Cost_Tracker_Settings::$icon_sub_dir, true );
1040 - $icon_base = $dirs[1];
1041 - $scr = $icon_base . $upload_icon;
1042 - } else {
1043 - $scr = '';
1044 - }
1045 - $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 "';
1046 - $icon = '<div style="display:inline-block;" ' . $icon_wrapper_attr . '><img style="' . $style . '" src="' . esc_attr( $scr ) . '"/></div>';
1047 - }
1048 - return $icon;
1049 - }
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 + }
1050 1422 }