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-summary.php +527 -493 5.0.2 → 6.2.2 View file →
@@ -7,554 +7,588 @@
7 7 */
8 8
9 9 namespace MainWP\Dashboard\Module\CostTracker;
10 10
11 -use MainWP\Dashboard\MainWP_Menu;
12 11 use MainWP\Dashboard\MainWP_UI;
13 12 use MainWP\Dashboard\MainWP_Utility;
14 -use MainWP\Dashboard\MainWP_DB_Client;
15 -use MainWP\Dashboard\MainWP_DB_Common;
16 -use MainWP\Dashboard\MainWP_Post_Handler;
17 -use MainWP\Dashboard\MainWP_Logger;
18 -use function MainWP\Dashboard\mainwp_current_user_have_right;
19 -use function MainWP\Dashboard\mainwp_do_not_have_permissions;
13 +use MainWP\Dashboard\MainWP_Overview;
14 +use MainWP\Dashboard\MainWP_Ui_Manage_Widgets_Layout;
20 15
16 +// Exit if accessed directly.
17 +if ( ! defined( 'ABSPATH' ) ) {
18 + exit;
19 +}
20 +
21 21 /**
22 - * Class Log_Insights_Page
22 + * Class class Cost_Tracker_Summary {
23 23 *
24 24 * @package MainWP\Dashboard
25 25 */
26 26 class Cost_Tracker_Summary {
27 27
28 - /**
29 - * Get Class Name
30 - *
31 - * @return string __CLASS__
32 - */
33 - public static function get_class_name() {
34 - return __CLASS__;
35 - }
28 + /**
29 + * Get Class Name
30 + *
31 + * @return string __CLASS__
32 + */
33 + public static function get_class_name() {
34 + return __CLASS__;
35 + }
36 36
37 - /**
38 - * The single instance of the class
39 - *
40 - * @var mixed Default null
41 - */
42 - protected static $instance = null;
37 + /**
38 + * The single instance of the class
39 + *
40 + * @var mixed Default null
41 + */
42 + protected static $instance = null;
43 43
44 - /**
45 - * Enabled widgets
46 - *
47 - * @var array $enable_widgets
48 - */
49 - private static $enable_widgets = array(
50 - 'cost_upcomming_renewals' => true,
51 - 'cost_monthly_renewals' => true,
52 - 'cost_yearly_renewals' => true,
53 - 'cost_monthly_totals' => true,
54 - 'cost_category_totals' => true,
55 - 'cost_payments_left_this_month' => true,
56 - );
44 + /**
45 + * Enabled widgets
46 + *
47 + * @var array $enable_widgets
48 + */
49 + private static $enable_widgets = array(
50 + 'cost_upcomming_renewals' => true,
51 + 'cost_monthly_renewals' => true,
52 + 'cost_yearly_renewals' => true,
53 + 'cost_monthly_totals' => true,
54 + 'cost_category_totals' => true,
55 + 'cost_payments_left_this_month' => true,
56 + );
57 57
58 - /**
59 - * Current page.
60 - *
61 - * @static
62 - * @var string $page Current page.
63 - */
64 - public static $page;
58 + /**
59 + * Current page.
60 + *
61 + * @static
62 + * @var string $page Current page.
63 + */
64 + public static $page;
65 65
66 - /**
67 - * Check if there is a session,
68 - * if there isn't one create it.
69 - *
70 - * @return self::singlton Overview Page Session.
71 - *
72 - * @uses \MainWP\Dashboard\MainWP_Overview
73 - */
74 - public static function instance() {
75 - if ( null === self::$instance ) {
76 - self::$instance = new self();
77 - }
66 + /**
67 + * Check if there is a session,
68 + * if there isn't one create it.
69 + *
70 + * @return static::singlton Overview Page Session.
71 + *
72 + * @uses \MainWP\Dashboard\MainWP_Overview
73 + */
74 + public static function instance() {
75 + if ( null === static::$instance ) {
76 + static::$instance = new self();
77 + }
78 78
79 - return self::$instance;
80 - }
79 + return static::$instance;
80 + }
81 81
82 - /**
83 - * MainWP_Overview constructor.
84 - *
85 - * Run each time the class is called.
86 - */
87 - public function __construct() {
88 - add_action( 'admin_init', array( $this, 'admin_init' ) );
89 - }
82 + /**
83 + * MainWP_Overview constructor.
84 + *
85 + * Run each time the class is called.
86 + */
87 + public function __construct() {
88 + add_action( 'admin_init', array( $this, 'admin_init' ) );
89 + }
90 90
91 - /**
92 - * Hook admin init.
93 - */
94 - public function admin_init() {
95 - $this->handle_update_screen_options();
96 - }
91 + /**
92 + * Hook admin init.
93 + */
94 + public function admin_init() {
95 + $this->handle_update_screen_options();
96 + }
97 97
98 98
99 - /**
100 - * Handle update screen options.
101 - */
102 - public function handle_update_screen_options() {
103 - $update_opts = false;
104 - if ( isset( $_POST['module_cost_tracker_summay_options_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['module_cost_tracker_summay_options_nonce'] ), 'module_cost_tracker_summay_options_nonce' ) ) {
105 - $update_opts = true;
106 - }
107 - if ( $update_opts ) {
108 - $show_wids = array();
109 - if ( isset( $_POST['mainwp_show_widgets'] ) && is_array( $_POST['mainwp_show_widgets'] ) ) {
110 - $selected_wids = array_map( 'sanitize_text_field', wp_unslash( $_POST['mainwp_show_widgets'] ) );
111 - foreach ( $selected_wids as $name ) {
112 - $show_wids[ $name ] = 1;
113 - }
114 - }
99 + /**
100 + * Handle update screen options.
101 + */
102 + public function handle_update_screen_options() { //phpcs:ignore -- NOSONAR - complex.
103 + $update_opts = false;
104 + if ( isset( $_POST['module_cost_tracker_summay_options_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['module_cost_tracker_summay_options_nonce'] ), 'module_cost_tracker_summay_options_nonce' ) ) {
105 + $update_opts = true;
106 + }
107 + if ( $update_opts ) {
108 + $show_wids = array();
109 + if ( isset( $_POST['mainwp_show_widgets'] ) && is_array( $_POST['mainwp_show_widgets'] ) ) {
110 + $selected_wids = array_map( 'sanitize_text_field', wp_unslash( $_POST['mainwp_show_widgets'] ) );
111 + foreach ( $selected_wids as $name ) {
112 + $show_wids[ $name ] = 1;
113 + }
114 + }
115 115
116 - if ( isset( $_POST['mainwp_widgets_name'] ) && is_array( $_POST['mainwp_widgets_name'] ) ) {
117 - $name_wids = array_map( 'sanitize_text_field', wp_unslash( $_POST['mainwp_widgets_name'] ) );
118 - foreach ( $name_wids as $name ) {
119 - if ( ! isset( $show_wids[ $name ] ) ) {
120 - $show_wids[ $name ] = 0;
121 - }
122 - }
123 - }
116 + if ( isset( $_POST['mainwp_widgets_name'] ) && is_array( $_POST['mainwp_widgets_name'] ) ) {
117 + $name_wids = array_map( 'sanitize_text_field', wp_unslash( $_POST['mainwp_widgets_name'] ) );
118 + foreach ( $name_wids as $name ) {
119 + if ( ! isset( $show_wids[ $name ] ) ) {
120 + $show_wids[ $name ] = 0;
121 + }
122 + }
123 + }
124 124
125 - global $current_user;
125 + global $current_user;
126 126
127 - if ( $current_user ) {
128 - update_user_option( $current_user->ID, 'mainwp_module_cost_tracker_summary_show_widgets', $show_wids, true );
129 - if ( isset( $_POST['reset_module_cost_tracker_summary_widgets_settings'] ) && ! empty( $_POST['reset_module_cost_tracker_summary_widgets_settings'] ) ) {
130 - update_user_option( $current_user->ID, 'mainwp_module_cost_tracker_summary_show_widgets', false, true );
131 - update_user_option( $current_user->ID, 'mainwp_widgets_sorted_' . strtolower( 'mainwp_page_CostSummary' ), false, true );
132 - }
133 - }
134 - }
135 - }
127 + if ( $current_user ) {
128 + update_user_option( $current_user->ID, 'mainwp_module_cost_tracker_summary_show_widgets', $show_wids, true );
129 + if ( isset( $_POST['reset_module_cost_tracker_summary_widgets_settings'] ) && ! empty( $_POST['reset_module_cost_tracker_summary_widgets_settings'] ) ) {
130 + update_user_option( $current_user->ID, 'mainwp_module_cost_tracker_summary_show_widgets', false, true );
131 + update_user_option( $current_user->ID, 'mainwp_widgets_sorted_' . strtolower( 'mainwp_page_CostSummary' ), false, true );
132 + }
133 + }
134 + }
135 + }
136 136
137 137
138 - /**
139 - * Method on_load_page()
140 - *
141 - * Run on page load.
142 - *
143 - * @param mixed $page Page name.
144 - */
145 - public function on_load_page( $page ) {
138 + /**
139 + * Method on_load_page()
140 + *
141 + * Run on page load.
142 + *
143 + * @param mixed $page Page name.
144 + */
145 + public function on_load_page( $page ) {
146 146
147 - self::$page = $page;
147 + static::$page = $page;
148 148
149 - $val = get_user_option( 'screen_layout_' . $page );
150 - if ( ! $val ) {
151 - global $current_user;
152 - update_user_option( $current_user->ID, 'screen_layout_' . $page, 2, true );
153 - }
149 + $val = get_user_option( 'screen_layout_' . $page );
150 + if ( ! $val ) {
151 + global $current_user;
152 + update_user_option( $current_user->ID, 'screen_layout_' . $page, 2, true );
153 + }
154 154
155 - wp_enqueue_script( 'common' );
156 - wp_enqueue_script( 'wp-lists' );
157 - wp_enqueue_script( 'postbox' );
158 - wp_enqueue_script( 'dashboard' );
159 - wp_enqueue_script( 'widgets' );
155 + wp_enqueue_script( 'common' );
156 + wp_enqueue_script( 'wp-lists' );
157 + wp_enqueue_script( 'postbox' );
158 + wp_enqueue_script( 'dashboard' );
159 + wp_enqueue_script( 'widgets' );
160 160
161 - self::add_meta_boxes( $page );
161 + static::add_meta_boxes( $page );
162 162
163 - add_filter( 'mainwp_header_actions_right', array( $this, 'screen_options' ), 10, 2 );
164 - add_filter( 'mainwp_widget_boxes_show_widgets', array( $this, 'hook_show_widgets' ), 10, 2 );
165 - }
163 + add_filter( 'mainwp_header_actions_right', array( $this, 'screen_options' ), 10, 2 );
164 + add_filter( 'mainwp_widget_boxes_show_widgets', array( $this, 'hook_show_widgets' ), 10, 2 );
165 + }
166 166
167 - /**
168 - * Method screen_options()
169 - *
170 - * Create Page Settings button.
171 - *
172 - * @param mixed $input Page Settings button HTML.
173 - *
174 - * @return mixed Page Settings button.
175 - */
176 - public function screen_options( $input ) {
177 - return $input .
178 - '<a class="ui button basic icon" onclick="mainwp_module_cost_tracker_summary_screen_options(); return false;" data-inverted="" data-position="bottom right" href="#" target="_blank" data-tooltip="' . esc_html__( 'Page Settings', 'mainwp' ) . '">
179 - <i class="cog icon"></i>
180 - </a>';
181 - }
167 + /**
168 + * Method screen_options()
169 + *
170 + * Create Page Settings button.
171 + *
172 + * @param mixed $input Page Settings button HTML.
173 + *
174 + * @return mixed Page Settings button.
175 + */
176 + public function screen_options( $input ) {
177 + return $input .
178 + '<a class="ui button basic icon" onclick="mainwp_module_cost_tracker_summary_screen_options(); return false;" data-inverted="" data-position="bottom right" href="#" target="_blank" data-tooltip="' . esc_html__( 'Page Settings', 'mainwp' ) . '" aria-label="' . esc_html__( 'Page Settings', 'mainwp' ) . '">
179 + <i class="cog icon"></i>
180 + </a>';
181 + }
182 182
183 - /**
184 - * Method hook_show_widgets()
185 - *
186 - * Hook show widgets.
187 - *
188 - * @param array $values Show widgets settings.
189 - * @param string $page Page slug.
190 - *
191 - * @return array $values Show widgets settings.
192 - */
193 - public function hook_show_widgets( $values, $page ) {
194 - if ( strtolower( $page ) === strtolower( self::$page ) ) {
195 - return get_user_option( 'mainwp_module_cost_tracker_summary_show_widgets' );
196 - }
197 - return $values;
198 - }
183 + /**
184 + * Method hook_show_widgets()
185 + *
186 + * Hook show widgets.
187 + *
188 + * @param array $values Show widgets settings.
189 + * @param string $page Page slug.
190 + *
191 + * @return array $values Show widgets settings.
192 + */
193 + public function hook_show_widgets( $values, $page ) {
194 + if ( strtolower( $page ) === strtolower( static::$page ) ) {
195 + return get_user_option( 'mainwp_module_cost_tracker_summary_show_widgets' );
196 + }
197 + return $values;
198 + }
199 199
200 200
201 - /**
202 - * Method add_meta_boxes()
203 - *
204 - * Add MainWP Overview Page Widgets.
205 - *
206 - * @param array $page Current page.
207 - *
208 - * @uses \MainWP\Dashboard\MainWP_System_Handler::apply_filters()
209 - * @uses \MainWP\Dashboard\MainWP_System_Utility::get_current_wpid()
210 - * @uses \MainWP\Dashboard\MainWP_UI::add_widget_box()
211 - * @uses \MainWP\Dashboard\MainWP_Connection_Status::get_class_name()
212 - * @uses \MainWP\Dashboard\MainWP_Recent_Pages::get_class_name()
213 - * @uses \MainWP\Dashboard\MainWP_Recent_Posts::get_class_name()
214 - * @uses \MainWP\Dashboard\MainWP_Security_Issues_Widget::get_class_name()
215 - * @uses \MainWP\Dashboard\MainWP_Updates_Overview::get_class_name()
216 - */
217 - public static function add_meta_boxes( $page ) { //phpcs:ignore -- complex method.
201 + /**
202 + * Method add_meta_boxes()
203 + *
204 + * Add MainWP Operations Page Widgets.
205 + *
206 + * @param array $page Current page.
207 + *
208 + * @uses \MainWP\Dashboard\MainWP_System_Handler::apply_filters()
209 + * @uses \MainWP\Dashboard\MainWP_System_Utility::get_current_wpid()
210 + * @uses \MainWP\Dashboard\MainWP_UI::add_widget_box()
211 + * @uses \MainWP\Dashboard\MainWP_Connection_Status::get_class_name()
212 + * @uses \MainWP\Dashboard\MainWP_Recent_Pages::get_class_name()
213 + * @uses \MainWP\Dashboard\MainWP_Recent_Posts::get_class_name()
214 + * @uses \MainWP\Dashboard\MainWP_Security_Issues_Widget::get_class_name()
215 + */
216 + public static function add_meta_boxes( $page ) { //phpcs:ignore -- NOSONAR - complex method.
218 217
219 - /**
220 - * Get getmetaboxes
221 - *
222 - * Adds metaboxes (widgets) to the Overview page.
223 - *
224 - * @since 4.6
225 - */
226 - $extMetaBoxs = array();
227 - $extMetaBoxs = apply_filters( 'mainwp_cost_summary_getmetaboxes', $extMetaBoxs );
218 + /**
219 + * Get getmetaboxes
220 + *
221 + * Adds metaboxes (widgets) to the Operations page.
222 + *
223 + * @since 4.6
224 + */
225 + $extMetaBoxs = array();
226 + $extMetaBoxs = apply_filters( 'mainwp_cost_summary_getmetaboxes', $extMetaBoxs );
228 227
229 - foreach ( $extMetaBoxs as $box ) {
230 - if ( isset( $box['plugin'] ) ) {
231 - $name = basename( $box['plugin'], '.php' );
232 - self::$enable_widgets[ $name ] = true;
233 - } elseif ( ! empty( $box['widget_id'] ) ) {
234 - self::$enable_widgets[ $box['widget_id'] ] = true;
235 - }
236 - }
228 + foreach ( $extMetaBoxs as $box ) {
229 + if ( isset( $box['plugin'] ) ) {
230 + $name = basename( $box['plugin'], '.php' );
231 + static::$enable_widgets[ $name ] = true;
232 + } elseif ( ! empty( $box['widget_id'] ) ) {
233 + static::$enable_widgets[ $box['widget_id'] ] = true;
234 + }
235 + }
237 236
238 - /**
239 - * Unset unwanted Widgets
240 - *
241 - * Contains the list of enabled widgets and allows user to unset unwanted widgets.
242 - *
243 - * @param array $enable_widgets Array containing enabled widgets.
244 - * @param int $dashboard_siteid Child site (Overview) ID.
245 - *
246 - * @since 4.6
247 - */
248 - $values = apply_filters( 'mainwp_module_cost_tracker_summary_enabled_widgets', self::$enable_widgets, null );
249 - self::$enable_widgets = array_merge( self::$enable_widgets, $values );
237 + /**
238 + * Unset unwanted Widgets
239 + *
240 + * Contains the list of enabled widgets and allows user to unset unwanted widgets.
241 + *
242 + * @param array $enable_widgets Array containing enabled widgets.
243 + * @param int $dashboard_siteid Child site (Operations) ID.
244 + *
245 + * @since 4.6
246 + */
247 + $values = apply_filters( 'mainwp_module_cost_tracker_summary_enabled_widgets', static::$enable_widgets, null );
248 + static::$enable_widgets = array_merge( static::$enable_widgets, $values );
250 249
251 - $i = 1;
252 - foreach ( $extMetaBoxs as $metaBox ) {
253 - $enabled = true;
254 - if ( isset( $metaBox['plugin'] ) ) {
255 - $name = basename( $metaBox['plugin'], '.php' );
256 - if ( isset( self::$enable_widgets[ $name ] ) && ! self::$enable_widgets[ $name ] ) {
257 - $enabled = false;
258 - }
259 - }
250 + // Load the widget.
251 + if ( ! empty( static::$enable_widgets['cost_monthly_totals'] ) ) {
252 + MainWP_UI::add_widget_box( 'cost_monthly_totals', array( Cost_Tracker_Monthly_Totals::instance(), 'render' ), $page, array( 1, 1, 12, 40 ) );
253 + }
254 + // Load the widget.
255 + if ( ! empty( static::$enable_widgets['cost_payments_left_this_month'] ) ) {
256 + MainWP_UI::add_widget_box( 'cost_payments_left_this_month', array( Cost_Tracker_Payment_Left_This_Month::instance(), 'render' ), $page, array( -1, -1, 6, 40 ) );
257 + }
258 + // Load the widget.
259 + if ( ! empty( static::$enable_widgets['cost_category_totals'] ) ) {
260 + MainWP_UI::add_widget_box( 'cost_category_totals', array( Cost_Tracker_Category_Totals::instance(), 'render' ), $page, array( -1, -1, 6, 40 ) );
261 + }
262 + // Load the widget.
263 + if ( ! empty( static::$enable_widgets['cost_upcomming_renewals'] ) ) {
264 + MainWP_UI::add_widget_box( 'cost_upcomming_renewals', array( Cost_Tracker_Upcoming_Renewals::instance(), 'render' ), $page, array( -1, -1, 4, 30 ) );
265 + }
266 + // Load the widget.
267 + if ( ! empty( static::$enable_widgets['cost_yearly_renewals'] ) ) {
268 + MainWP_UI::add_widget_box( 'cost_yearly_renewals', array( Cost_Tracker_Yearly_Renewals::instance(), 'render' ), $page, array( -1, -1, 4, 30 ) );
269 + }
270 + // Load the widget.
271 + if ( ! empty( static::$enable_widgets['cost_monthly_renewals'] ) ) {
272 + MainWP_UI::add_widget_box( 'cost_monthly_renewals', array( Cost_Tracker_Monthly_Renewals::instance(), 'render' ), $page, array( -1, -1, 4, 30 ) );
273 + }
260 274
261 - $id = isset( $metaBox['id'] ) ? $metaBox['id'] : $i++;
262 - $id = 'advanced-' . $id;
275 + $i = 1;
276 + foreach ( $extMetaBoxs as $metaBox ) {
277 + $enabled = true;
278 + if ( isset( $metaBox['plugin'] ) ) {
279 + $name = basename( $metaBox['plugin'], '.php' );
280 + if ( isset( static::$enable_widgets[ $name ] ) && ! static::$enable_widgets[ $name ] ) {
281 + $enabled = false;
282 + }
283 + }
263 284
264 - $layout = ! empty( $metaBox['layout'] ) && is_array( $metaBox['layout'] ) ? $metaBox['layout'] : array( 1, 1, 6, 13 );
285 + $id = isset( $metaBox['id'] ) ? $metaBox['id'] : $i++;
286 + $id = 'advanced-' . $id;
265 287
266 - if ( $enabled ) {
267 - MainWP_UI::add_widget_box( $id, $metaBox['callback'], $page, $layout );
268 - }
269 - }
288 + $layout = ! empty( $metaBox['layout'] ) && is_array( $metaBox['layout'] ) ? $metaBox['layout'] : array( -1, -1, 6, 30 );
270 289
271 - // Load the widget.
272 - if ( ! empty( self::$enable_widgets['cost_yearly_renewals'] ) ) {
273 - MainWP_UI::add_widget_box( 'cost_yearly_renewals', array( Cost_Tracker_Yearly_Renewals::instance(), 'render' ), $page, array( 1, 1, 6, 13 ) );
274 - }
290 + if ( $enabled ) {
291 + MainWP_UI::add_widget_box( $id, $metaBox['callback'], $page, $layout );
292 + }
293 + }
294 + }
275 295
276 - // Load the widget.
277 - if ( ! empty( self::$enable_widgets['cost_monthly_renewals'] ) ) {
278 - MainWP_UI::add_widget_box( 'cost_monthly_renewals', array( Cost_Tracker_Monthly_Renewals::instance(), 'render' ), $page, array( 1, 1, 6, 13 ) );
279 - }
296 + /**
297 + * Method get default logs filters()
298 + */
299 + public static function get_default_filters() {
300 + $format = 'Y-m-d';
301 + return array(
302 + 'ranges' => 'thismonth',
303 + 'dtsstart' => gmdate( $format, strtotime( gmdate( 'Y-m-01' ) ) ),
304 + 'dtsstop' => gmdate( $format, time() ),
305 + );
306 + }
280 307
281 - // Load the widget.
282 - if ( ! empty( self::$enable_widgets['cost_upcomming_renewals'] ) ) {
283 - MainWP_UI::add_widget_box( 'cost_upcomming_renewals', array( Cost_Tracker_Upcoming_Renewals::instance(), 'render' ), $page, array( 1, 1, 6, 13 ) );
284 - }
308 + /**
309 + * Method render_footer()
310 + *
311 + * Render Insights page footer. Closes the page container.
312 + */
313 + public static function render_footer() {
314 + echo '</div>';
315 + }
285 316
286 - // Load the widget.
287 - if ( ! empty( self::$enable_widgets['cost_category_totals'] ) ) {
288 - MainWP_UI::add_widget_box( 'cost_category_totals', array( Cost_Tracker_Category_Totals::instance(), 'render' ), $page, array( 1, 1, 6, 13 ) );
289 - }
317 + /**
318 + * Render summary page.
319 + *
320 + * @return void
321 + */
322 + public function render_summary_page() {
323 + if ( ! \mainwp_current_user_can( 'dashboard', 'access_cost_summary_dashboard' ) ) {
324 + \mainwp_do_not_have_permissions( esc_html__( 'cost summary', 'mainwp' ) );
325 + return;
326 + }
327 + $this->on_show_page();
328 + }
290 329
291 - // Load the widget.
292 - if ( ! empty( self::$enable_widgets['cost_monthly_totals'] ) ) {
293 - MainWP_UI::add_widget_box( 'cost_monthly_totals', array( Cost_Tracker_Monthly_Totals::instance(), 'render' ), $page, array( 1, 1, 6, 13 ) );
294 - }
295 330
296 - // Load the widget.
297 - if ( ! empty( self::$enable_widgets['cost_payments_left_this_month'] ) ) {
298 - MainWP_UI::add_widget_box( 'cost_payments_left_this_month', array( Cost_Tracker_Payment_Left_This_Month::instance(), 'render' ), $page, array( 1, 1, 6, 13 ) );
299 - }
300 - }
331 + /**
332 + * Method on_show_page()
333 + *
334 + * When the page loads render the body content.
335 + */
336 + public function on_show_page() {
337 + static::render_header();
338 + static::render_summary_body();
339 + }
301 340
302 - /**
303 - * Method get default logs filters()
304 - */
305 - public static function get_default_filters() {
306 - $format = 'Y-m-d';
307 - return array(
308 - 'ranges' => 'thismonth',
309 - 'dtsstart' => gmdate( $format, strtotime( gmdate( 'Y-m-01' ) ) ),
310 - 'dtsstop' => gmdate( $format, time() ),
311 - );
312 - }
341 + /**
342 + * Method render_header()
343 + *
344 + * Render Insights page header.
345 + */
346 + public static function render_header() {
347 + $params = array(
348 + 'title' => esc_html__( 'Operational Costs Summary', 'mainwp' ),
349 + 'which' => 'page_cost_summary',
350 + 'wrap_class' => 'mainwp-module-cost-summary-wrapper',
351 + );
352 + MainWP_UI::render_top_header( $params );
353 + }
313 354
314 - /**
315 - * Method render_footer()
316 - *
317 - * Render Insights page footer. Closes the page container.
318 - */
319 - public static function render_footer() {
320 - echo '</div>';
321 - }
322 355
323 - /**
324 - * Render summary page.
325 - *
326 - * @return void
327 - */
328 - public function render_summary_page() {
329 - if ( ! mainwp_current_user_have_right( 'dashboard', 'access_cost_summary_dashboard' ) ) {
330 - mainwp_do_not_have_permissions( esc_html__( 'cost summary', 'mainwp' ) );
331 - return;
332 - }
333 - $this->on_show_page();
334 - }
356 + /**
357 + * Method render_summary_body()
358 + *
359 + * Render the summary costs content.
360 + */
361 + public static function render_summary_body() {
362 + $screen = get_current_screen();
363 + $costs_data = Cost_Tracker_DB::get_instance()->get_summary_data( array( 'sum_data' => 'all' ) );
364 + $costs = Cost_Tracker_DB::get_instance()->get_cost_tracker_by( 'all' );
365 + $costs_count = count( $costs );
366 + ?>
367 + <div class="ui segment mainwp-primary-content-wrap">
368 + <?php if ( MainWP_Utility::show_mainwp_message( 'notice', 'mainwp-cost-summany-welcome-message' ) && 0 == $costs_count ) : ?>
369 + <div class="ui segment" style="margin-bottom:0px;">
370 + <div class="ui icon message mainwp-welcome-message" style="margin-bottom:0px;">
371 + <em data-emoji=":wave:" class="big"></em>
372 + <div class="content">
373 + <i class="close icon mainwp-notice-dismiss" notice-id="mainwp-cost-summany-welcome-message" style="float:right;cursor:pointer;"></i>
374 + <div class="ui massive header"><?php esc_html_e( 'Welcome to Cost Tracker', 'mainwp' ); ?></div>
375 + <p><?php esc_html_e( 'Track and forecast your agency\'s expenses across all sites and subscriptions.', 'mainwp' ); ?></p>
376 + <?php /* translators: 1: Opening link tag, 2: Closing link tag, 3: Opening link tag, 4: Closing link tag */ ?>
377 + <p><?php printf( esc_html__( 'Start by %1$sadding your first cost%2$s or %3$screating custom cost categories%4$s.', 'mainwp' ), '<a href="admin.php?page=CostTrackerAdd">', '</a>', '<a href="admin.php?page=CostTrackerSettings">', '</a>' ); ?></p>
378 + </div>
379 + </div>
380 + </div>
381 + <?php endif; ?>
382 + <?php if ( MainWP_Utility::show_mainwp_message( 'notice', 'cost-summany-widgets' ) ) : ?>
383 + <div class="ui segment" style="margin-bottom:0px;">
384 + <div class="ui message" style="margin-bottom:0px;">
385 + <i class="close icon mainwp-notice-dismiss" notice-id="cost-summany-widgets"></i>
386 + <?php /* translators: 1: Tip emoji icon, 2: Cog icon, 3: Layout icon */ ?>
387 + <?php printf( esc_html__( '%1$s Tip: You can drag and drop widgets to reorder your dashboard, use Page Settings (%2$s) to show or hide widgets, and use Layout (%3$s) in the header to save and load your widget layouts.', 'mainwp' ), '<em data-emoji=":bulb:" class="small"></em>', '<i class="cog fitted icon"></i>', '<i class="all border fitted icon"></i>' ); ?>
388 + </div>
389 + </div>
390 + <?php endif; ?>
391 + <?php if ( 0 < $costs_count && 3 >= $costs_count ) : ?>
392 + <?php if ( MainWP_Utility::show_mainwp_message( 'notice', 'cost-summany-low-data-info' ) ) : ?>
393 + <div class="ui segment" style="margin-bottom:0px;">
394 + <div class="ui info message" style="margin-bottom:0px;">
395 + <i class="close icon mainwp-notice-dismiss" notice-id="cost-summany-low-data-info"></i>
396 + <?php esc_html_e( 'Keep building your cost history. Add more expenses to unlock trend insights.', 'mainwp' ); ?>
397 + </div>
398 + </div>
399 + <?php endif; ?>
400 + <?php endif; ?>
401 + <div id="mainwp-message-zone" class="ui message" style="display:none;"></div>
335 402
403 + <?php
404 + /**
405 + * Action: mainwp_before_overview_widgets
406 + *
407 + * Fires at the top of the Operations page (before first widget).
408 + *
409 + * @since 4.6
410 + */
411 + do_action( 'mainwp_before_overview_widgets', 'costsummary' );
412 + ?>
413 + <div id="mainwp-grid-wrapper" class="gridster">
414 + <div id="mainwp-widgets-placeholder" class="ui page dimmer">
415 + <div class="ui double text loader"><?php esc_html_e( 'Loading...', 'mainwp' ); ?></div>
416 + </div>
417 + <script>
418 + jQuery( document ).ready( function () {
419 + jQuery('#mainwp-widgets-placeholder').dimmer('show');
420 + });
421 + </script>
422 + <?php MainWP_UI::do_widget_boxes( $screen->id, array( 'costs_data' => $costs_data, ) ); ?>
423 + </div>
424 + <?php
425 + /**
426 + * Action: 'mainwp_after_overview_widgets'
427 + *
428 + * Fires at the bottom of the Operations page (after the last widget).
429 + *
430 + * @since 4.6
431 + */
432 + do_action( 'mainwp_after_overview_widgets', 'costsummary' );
433 + ?>
434 + <script type="text/javascript">
435 + jQuery(function( $ ) {
436 + jQuery( '.mainwp-widget .mainwp-dropdown-tab .item' ).tab();
437 + mainwp_module_cost_tracker_summary_screen_options = function () {
438 + jQuery( '#mainwp-module-log-overview-screen-options-modal' ).modal( {
439 + allowMultiple: true,
440 + onHide: function () {
441 + }
442 + } ).modal( 'show' );
443 + return false;
444 + };
445 + jQuery('#reset-costs-summary-widgets-settings').on('click', function () {
446 + mainwp_confirm(__('Are you sure?'), function(){
447 + jQuery('.mainwp_hide_wpmenu_checkboxes input[name="mainwp_show_widgets[]"]').prop('checked', true);
448 + jQuery('input[name=reset_module_cost_tracker_summary_widgets_settings]').attr('value', 1);
449 + jQuery('#submit-cost-summary-widgets-settings').click();
450 + }, false, false, true);
451 + return false;
452 + });
336 453
337 - /**
338 - * Method on_show_page()
339 - *
340 - * When the page loads render the body content.
341 - */
342 - public function on_show_page() {
343 - self::render_header( 'cost_summary' );
344 - self::render_summary_body();
345 - }
454 + jQuery( '#mainwp-upcoming-renewals-table-today' ).DataTable();
455 + jQuery( '#mainwp-upcoming-renewals-table-tomorrow' ).DataTable();
456 + jQuery( '#mainwp-upcoming-renewals-table-week' ).DataTable();
457 + jQuery( '#mainwp-upcoming-renewals-table-next_week' ).DataTable();
458 + jQuery( '#mainwp-upcoming-renewals-table-month' ).DataTable();
459 + jQuery( '#mainwp-upcoming-renewals-table-next_month' ).DataTable();
460 + jQuery( '#mainwp-upcoming-renewals-table-year' ).DataTable();
461 + jQuery( '#mainwp-upcoming-renewals-table-next_year' ).DataTable();
462 + } );
463 + </script>
464 + </div>
465 + <?php MainWP_Ui_Manage_Widgets_Layout::render_modal_save_layout(); ?>
466 + <div class="ui modal" id="mainwp-module-log-overview-screen-options-modal">
467 + <i class="close icon"></i>
468 + <div class="header"><?php esc_html_e( 'Page Settings', 'mainwp' ); ?></div>
469 + <div class="content ui form">
470 + <?php
471 + /**
472 + * Action: mainwp_module_cost_tracker_summary_screen_options_top
473 + *
474 + * Fires at the top of the Sceen Options modal on the Operations page.
475 + *
476 + * @since 4.6
477 + */
478 + do_action( 'mainwp_module_cost_tracker_summary_screen_options_top' );
479 + ?>
480 + <form method="POST" action="" name="mainwp_module_cost_tracker_summary_screen_options_form" id="mainwp-module-log-overview-screen-options-form">
481 + <?php MainWP_UI::generate_wp_nonce( 'mainwp-admin-nonce' ); ?>
482 + <input type="hidden" name="module_cost_tracker_summay_options_nonce" value="<?php echo esc_attr( wp_create_nonce( 'module_cost_tracker_summay_options_nonce' ) ); ?>" />
483 + <?php static::render_screen_options(); ?>
484 + <?php
485 + /**
486 + * Action: mainwp_module_cost_tracker_summary_screen_options_bottom
487 + *
488 + * Fires at the bottom of the Sceen Options modal on the Operations page.
489 + *
490 + * @since 4.6
491 + */
492 + do_action( 'mainwp_module_cost_tracker_summary_screen_options_bottom' );
493 + ?>
494 + </div>
495 + <div class="actions">
496 + <div class="ui two columns grid">
497 + <div class="left aligned column">
498 + <span data-tooltip="<?php esc_attr_e( 'Resets the page to its original layout and reinstates relocated widgets.', 'mainwp' ); ?>" data-inverted="" data-position="top left"><input type="button" class="ui button" name="reset" id="reset-costs-summary-widgets-settings" value="<?php esc_attr_e( 'Reset Page', 'mainwp' ); ?>" /></span>
499 + </div>
500 + <div class="ui right aligned column">
501 + <input type="submit" class="ui green button" id="submit-cost-summary-widgets-settings" value="<?php esc_attr_e( 'Save Settings', 'mainwp' ); ?>" />
502 + </div>
503 + </div>
504 + </div>
346 505
347 - /**
348 - * Method render_header()
349 - *
350 - * Render Insights page header.
351 - */
352 - public static function render_header() {
353 - $params = array(
354 - 'title' => esc_html__( 'Cost Summary', 'mainwp' ),
355 - 'which' => 'page_cost_summary',
356 - 'wrap_class' => 'mainwp-module-cost-summary-wrapper',
357 - );
358 - MainWP_UI::render_top_header( $params );
359 - }
506 + <input type="hidden" name="reset_module_cost_tracker_summary_widgets_settings" value="" />
507 + </form>
508 + </div>
509 + </div>
510 + <?php
511 + }
360 512
513 + /**
514 + * Method render_screen_options()
515 + *
516 + * Render Page Settings.
517 + *
518 + * @return void Render Page Settings html.
519 + */
520 + public static function render_screen_options() {
521 + $default_widgets = array(
522 + 'cost_upcomming_renewals' => esc_html__( 'Upcoming Renewals', 'mainwp' ),
523 + 'cost_monthly_renewals' => esc_html__( 'Upcoming Monthly Renewals', 'mainwp' ),
524 + 'cost_yearly_renewals' => esc_html__( 'Upcoming Yearly Renewals', 'mainwp' ),
525 + 'cost_monthly_totals' => esc_html__( 'Monthly Totals', 'mainwp' ),
526 + 'cost_category_totals' => esc_html__( 'Annual Expense Distribution by Category', 'mainwp' ),
527 + 'cost_payments_left_this_month' => esc_html__( 'Payment Left For This Month', 'mainwp' ),
528 + );
361 529
362 - /**
363 - * Method render_summary_body()
364 - *
365 - * Render the summary costs content.
366 - */
367 - public static function render_summary_body() {
368 - $screen = get_current_screen();
369 - $costs_data = Cost_Tracker_DB::get_instance()->get_summary_data( array( 'sum_data' => 'all' ) );
370 - ?>
371 - <div class="mainwp-primary-content-wrap">
372 - <div id="mainwp-message-zone" class="ui message" style="display:none;"></div>
373 - <?php if ( MainWP_Utility::show_mainwp_message( 'notice', 'cost-summany-widgets' ) ) : ?>
374 - <div class="ui info message">
375 - <i class="close icon mainwp-notice-dismiss" notice-id="cost-summany-widgets"></i>
376 - <?php printf( esc_html__( 'To hide or show a widget, click the Cog (%1$s) icon.', 'mainwp' ), '<i class="cog icon"></i>' ); ?>
377 - </div>
378 - <?php endif; ?>
379 - <?php
380 - /**
381 - * Action: mainwp_before_overview_widgets
382 - *
383 - * Fires at the top of the Overview page (before first widget).
384 - *
385 - * @since 4.6
386 - */
387 - do_action( 'mainwp_before_overview_widgets', 'costsummary' );
388 - ?>
389 - <div id="mainwp-grid-wrapper" class="gridster">
390 - <?php
391 - MainWP_UI::do_widget_boxes(
392 - $screen->id,
393 - array(
394 - 'costs_data' => $costs_data,
395 - )
396 - );
397 - ?>
398 - </div>
399 - <?php
400 - /**
401 - * Action: 'mainwp_after_overview_widgets'
402 - *
403 - * Fires at the bottom of the Overview page (after the last widget).
404 - *
405 - * @since 4.6
406 - */
407 - do_action( 'mainwp_after_overview_widgets', 'costsummary' );
408 - ?>
409 - <script type="text/javascript">
410 - jQuery( document ).ready( function( $ ) {
530 + $custom_opts = array();
531 + /**
532 + * Filter: mainwp_module_cost_tracker_summary_widgets_screen_options
533 + *
534 + * Filters available widgets on the Operations page allowing users to unset unwanted widgets.
535 + *
536 + * @since 4.6
537 + */
538 + $custom_opts = apply_filters( 'mainwp_module_cost_tracker_summary_widgets_screen_options', $custom_opts );
411 539
412 - jQuery( '.mainwp-widget .mainwp-dropdown-tab .item' ).tab();
540 + if ( is_array( $custom_opts ) && ! empty( $custom_opts ) ) {
541 + $default_widgets = array_merge( $default_widgets, $custom_opts );
542 + }
413 543
414 - mainwp_module_cost_tracker_summary_screen_options = function () {
415 - jQuery( '#mainwp-module-log-overview-screen-options-modal' ).modal( {
416 - allowMultiple: true,
417 - onHide: function () {
418 - }
419 - } ).modal( 'show' );
420 - return false;
421 - };
422 - jQuery('#reset-costs-summary-widgets-settings').on('click', function () {
423 - mainwp_confirm(__('Are you sure.'), function(){
424 - jQuery('.mainwp_hide_wpmenu_checkboxes input[name="mainwp_show_widgets[]"]').prop('checked', true);
425 - jQuery('input[name=reset_module_cost_tracker_summary_widgets_settings]').attr('value', 1);
426 - jQuery('#submit-cost-summary-widgets-settings').click();
427 - }, false, false, true);
428 - return false;
429 - });
430 - } );
431 - </script>
432 - <div class="ui modal" id="mainwp-module-log-overview-screen-options-modal">
433 - <i class="close icon"></i>
434 - <div class="header"><?php esc_html_e( 'Page Settings', 'mainwp' ); ?></div>
435 - <div class="content ui form">
436 - <?php
437 - /**
438 - * Action: mainwp_module_cost_tracker_summary_screen_options_top
439 - *
440 - * Fires at the top of the Sceen Options modal on the Overview page.
441 - *
442 - * @since 4.6
443 - */
444 - do_action( 'mainwp_module_cost_tracker_summary_screen_options_top' );
445 - ?>
446 - <form method="POST" action="" name="mainwp_module_cost_tracker_summary_screen_options_form" id="mainwp-module-log-overview-screen-options-form">
447 - <?php wp_nonce_field( 'mainwp-admin-nonce' ); ?>
448 - <input type="hidden" name="module_cost_tracker_summay_options_nonce" value="<?php echo esc_attr( wp_create_nonce( 'module_cost_tracker_summay_options_nonce' ) ); ?>" />
449 - <?php self::render_screen_options( false ); ?>
450 - <?php
451 - /**
452 - * Action: mainwp_module_cost_tracker_summary_screen_options_bottom
453 - *
454 - * Fires at the bottom of the Sceen Options modal on the Overview page.
455 - *
456 - * @since 4.6
457 - */
458 - do_action( 'mainwp_module_cost_tracker_summary_screen_options_bottom' );
459 - ?>
460 - </div>
461 - <div class="actions">
462 - <div class="ui two columns grid">
463 - <div class="left aligned column">
464 - <span data-tooltip="<?php esc_attr_e( 'Resets the page to its original layout and reinstates relocated widgets.', 'mainwp' ); ?>" data-inverted="" data-position="top left"><input type="button" class="ui button" name="reset" id="reset-costs-summary-widgets-settings" value="<?php esc_attr_e( 'Reset Page', 'mainwp' ); ?>" /></span>
465 - </div>
466 - <div class="ui right aligned column">
467 - <input type="submit" class="ui green button" id="submit-cost-summary-widgets-settings" value="<?php esc_attr_e( 'Save Settings', 'mainwp' ); ?>" />
468 - </div>
469 - </div>
470 - </div>
544 + $show_widgets = get_user_option( 'mainwp_module_cost_tracker_summary_show_widgets' );
545 + if ( ! is_array( $show_widgets ) ) {
546 + $show_widgets = array();
547 + }
471 548
472 - <input type="hidden" name="reset_module_cost_tracker_summary_widgets_settings" value="" />
473 - </form>
474 - </div>
475 - </div>
476 - <?php
477 - }
478 -
479 - /**
480 - * Method render_screen_options()
481 - *
482 - * Render Page Settings.
483 - *
484 - * @return void Render Page Settings html.
485 - */
486 - public static function render_screen_options() {
487 - $default_widgets = array(
488 - 'cost_upcomming_renewals' => esc_html__( 'Upcoming Renewals', 'mainwp' ),
489 - 'cost_monthly_renewals' => esc_html__( 'Upcoming Monthly Renewals', 'mainwp' ),
490 - 'cost_yearly_renewals' => esc_html__( 'Upcoming Yearly Renewals', 'mainwp' ),
491 - 'cost_monthly_totals' => esc_html__( 'Monthly Totals', 'mainwp' ),
492 - 'cost_category_totals' => esc_html__( 'Annual Expense Distribution by Category', 'mainwp' ),
493 - 'cost_payments_left_this_month' => esc_html__( 'Payment Left For This Month', 'mainwp' ),
494 - );
495 -
496 - $custom_opts = array();
497 - /**
498 - * Filter: mainwp_module_cost_tracker_summary_widgets_screen_options
499 - *
500 - * Filters available widgets on the Overview page allowing users to unsent unwanted widgets.
501 - *
502 - * @since 4.6
503 - */
504 - $custom_opts = apply_filters( 'mainwp_module_cost_tracker_summary_widgets_screen_options', $custom_opts );
505 -
506 - if ( is_array( $custom_opts ) && 0 < count( $custom_opts ) ) {
507 - $default_widgets = array_merge( $default_widgets, $custom_opts );
508 - }
509 -
510 - $show_widgets = get_user_option( 'mainwp_module_cost_tracker_summary_show_widgets' );
511 - if ( ! is_array( $show_widgets ) ) {
512 - $show_widgets = array();
513 - }
514 -
515 - /**
516 - * Action: mainwp_screen_options_modal_top
517 - *
518 - * Fires at the top of the Page Settings modal element.
519 - *
520 - * @since 4.6
521 - */
522 - do_action( 'mainwp_screen_options_modal_top', 'costsummary' );
523 - ?>
524 - <?php if ( isset( $_GET['page'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ?>
525 - <div class="ui grid field">
526 - <label class="six wide column"><?php esc_html_e( 'Show widgets', 'mainwp' ); ?></label>
527 - <div class="ten wide column" <?php echo 'data-tooltip="' . esc_attr_e( 'Select widgets that you want to hide in the MainWP Overview page.', 'mainwp' ); ?> data-inverted="" data-position="top left">
528 - <ul class="mainwp_hide_wpmenu_checkboxes">
529 - <?php
530 - foreach ( $default_widgets as $name => $title ) {
531 - $_selected = '';
532 - if ( ! isset( $show_widgets[ $name ] ) || 1 === (int) $show_widgets[ $name ] ) {
533 - $_selected = 'checked';
534 - }
535 - ?>
536 - <li>
537 - <div class="ui checkbox">
538 - <input type="checkbox" id="mainwp_show_widget_<?php echo esc_attr( $name ); ?>" name="mainwp_show_widgets[]" <?php echo esc_html( $_selected ); ?> value="<?php echo esc_attr( $name ); ?>">
539 - <label for="mainwp_show_widget_<?php echo esc_attr( $name ); ?>" ><?php echo esc_html( $title ); ?></label>
540 - </div>
541 - <input type="hidden" name="mainwp_widgets_name[]" value="<?php echo esc_attr( $name ); ?>">
542 - </li>
543 - <?php
544 - }
545 - ?>
546 - </ul>
547 - </div>
548 - </div>
549 - <?php endif; ?>
550 - <?php
551 - /**
552 - * Action: mainwp_screen_options_modal_bottom
553 - *
554 - * Fires at the bottom of the Page Settings modal element.
555 - *
556 - * @since 4.6
557 - */
558 - do_action( 'mainwp_screen_options_modal_bottom', 'costsummary' );
559 - }
549 + /**
550 + * Action: mainwp_screen_options_modal_top
551 + *
552 + * Fires at the top of the Page Settings modal element.
553 + *
554 + * @since 4.6
555 + */
556 + do_action( 'mainwp_screen_options_modal_top', 'costsummary' );
557 + ?>
558 + <?php if ( isset( $_GET['page'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ?>
559 + <div class="ui grid field">
560 + <label class="six wide column"><?php esc_html_e( 'Show widgets', 'mainwp' ); ?></label>
561 + <div class="ten wide column" <?php echo 'data-tooltip="' . esc_attr__( 'Select widgets that you want to hide in the MainWP Operations page.', 'mainwp' ); ?> data-inverted="" data-position="top left">
562 + <ul class="mainwp_hide_wpmenu_checkboxes">
563 + <?php
564 + foreach ( $default_widgets as $name => $title ) {
565 + $_selected = '';
566 + if ( ! isset( $show_widgets[ $name ] ) || 1 === (int) $show_widgets[ $name ] ) {
567 + $_selected = 'checked';
568 + }
569 + ?>
570 + <li>
571 + <div class="ui checkbox">
572 + <input type="checkbox" id="mainwp_show_widget_<?php echo esc_attr( $name ); ?>" name="mainwp_show_widgets[]" <?php echo esc_html( $_selected ); ?> value="<?php echo esc_attr( $name ); ?>">
573 + <label for="mainwp_show_widget_<?php echo esc_attr( $name ); ?>" ><?php echo esc_html( $title ); ?></label>
574 + </div>
575 + <input type="hidden" name="mainwp_widgets_name[]" value="<?php echo esc_attr( $name ); ?>">
576 + </li>
577 + <?php
578 + }
579 + ?>
580 + </ul>
581 + </div>
582 + </div>
583 + <?php endif; ?>
584 + <?php
585 + /**
586 + * Action: mainwp_screen_options_modal_bottom
587 + *
588 + * Fires at the bottom of the Page Settings modal element.
589 + *
590 + * @since 4.6
591 + */
592 + do_action( 'mainwp_screen_options_modal_bottom', 'costsummary' );
593 + }
560 594 }