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