1+ <?php
2+ /**
3+ * Admin class.
4+ *
5+ * @since 1.0.1
6+ * @package Multisite_Exporter
7+ * @subpackage Multisite_Exporter/Admin
8+ */
9+
10+ // If this file is called directly, abort.
11+ if ( ! defined ( 'ABSPATH ' ) ) {
12+ exit ;
13+ }
14+
15+ /**
16+ * Admin class.
17+ */
18+ class ME_Admin {
19+
20+ /**
21+ * The single instance of the class.
22+ *
23+ * @since 1.0.1
24+ * @var ME_Admin
25+ */
26+ protected static $ _instance = null ;
27+
28+ /**
29+ * Main ME_Admin Instance.
30+ *
31+ * @return ME_Admin - Main instance.
32+ */
33+ public static function instance () {
34+ if ( is_null ( self ::$ _instance ) ) {
35+ self ::$ _instance = new self ();
36+ }
37+ return self ::$ _instance ;
38+ }
39+
40+ /**
41+ * Constructor.
42+ */
43+ public function __construct () {
44+ $ this ->init_hooks ();
45+ }
46+
47+ /**
48+ * Initialize hooks.
49+ */
50+ private function init_hooks () {
51+ // Add menu
52+ add_action ( 'network_admin_menu ' , array ( $ this , 'add_admin_menu ' ) );
53+
54+ // Register plugin assets (styles and scripts)
55+ add_action ( 'admin_enqueue_scripts ' , array ( $ this , 'enqueue_admin_assets ' ) );
56+
57+ // AJAX handlers
58+ add_action ( 'wp_ajax_me_download_export ' , array ( $ this , 'handle_export_download ' ) );
59+ add_action ( 'wp_ajax_me_download_selected_exports ' , array ( $ this , 'download_selected_exports ' ) );
60+ }
61+
62+ /**
63+ * Enqueue admin assets (styles and scripts).
64+ *
65+ * @param string $hook The current admin page hook.
66+ */
67+ public function enqueue_admin_assets ( $ hook ) {
68+ // Only load assets on our plugin pages
69+ if ( strpos ( $ hook , 'multisite-exporter ' ) === false ) {
70+ return ;
71+ }
72+
73+ // Enqueue common styles
74+ wp_enqueue_style (
75+ 'multisite-exporter-styles ' ,
76+ MULTISITE_EXPORTER_PLUGIN_URL . 'css/multisite-exporter.css ' ,
77+ array (),
78+ filemtime ( MULTISITE_EXPORTER_PLUGIN_DIR . 'css/multisite-exporter.css ' )
79+ );
80+
81+ // Enqueue common admin scripts
82+ wp_enqueue_script (
83+ 'multisite-exporter-admin ' ,
84+ MULTISITE_EXPORTER_PLUGIN_URL . 'js/multisite-exporter-admin.js ' ,
85+ array ( 'jquery ' ),
86+ filemtime ( MULTISITE_EXPORTER_PLUGIN_DIR . 'js/multisite-exporter-admin.js ' ),
87+ true
88+ );
89+
90+ // Enqueue page-specific scripts
91+ if ( strpos ( $ hook , 'multisite-exporter-history ' ) !== false ) {
92+ wp_enqueue_script (
93+ 'multisite-exporter-history ' ,
94+ MULTISITE_EXPORTER_PLUGIN_URL . 'js/history-page.js ' ,
95+ array ( 'jquery ' ),
96+ filemtime ( MULTISITE_EXPORTER_PLUGIN_DIR . 'js/history-page.js ' ),
97+ true
98+ );
99+ }
100+ }
101+
102+ /**
103+ * Add top-level admin menu for Multisite Exporter.
104+ */
105+ public function add_admin_menu () {
106+ add_menu_page (
107+ 'Multisite Exporter ' , // Page title
108+ 'MS Exporter ' , // Menu title (shorter for menu space)
109+ 'manage_network ' , // Capability
110+ 'multisite-exporter ' , // Menu slug
111+ array ( $ this , 'render_main_page ' ), // Callback function
112+ 'dashicons-download ' , // Icon (download icon is appropriate for export)
113+ 30 // Position (after Comments which is 25)
114+ );
115+
116+ // Add submenu for export history
117+ add_submenu_page (
118+ 'multisite-exporter ' , // Parent slug
119+ 'Export History ' , // Page title
120+ 'Export History ' , // Menu title
121+ 'manage_network ' , // Capability
122+ 'multisite-exporter-history ' , // Menu slug
123+ array ( $ this , 'render_history_page ' ) // Callback function
124+ );
125+
126+ // Add submenu for Action Scheduler admin
127+ add_submenu_page (
128+ 'multisite-exporter ' , // Parent slug
129+ esc_html__ ( 'Scheduled Actions ' , 'multisite-exporter ' ), // Page title
130+ esc_html__ ( 'Scheduled Actions ' , 'multisite-exporter ' ), // Menu title
131+ 'manage_network ' , // Capability
132+ 'me-scheduled-actions ' , // Menu slug - unique to avoid conflicts
133+ array ( $ this , 'render_action_scheduler_page ' ) // Callback function
134+ );
135+ }
136+
137+ /**
138+ * Render the main admin page.
139+ */
140+ public function render_main_page () {
141+ // Add nonce for security
142+ if ( isset ( $ _POST [ 'me_run ' ] ) && check_admin_referer ( 'multisite_exporter_action ' , 'me_nonce ' ) ) {
143+ $ export_args = [
144+ 'content ' => sanitize_text_field ( $ _POST [ 'content ' ] ?? 'all ' ),
145+ 'post_type ' => sanitize_text_field ( $ _POST [ 'post_type ' ] ?? '' ),
146+ 'start_date ' => sanitize_text_field ( $ _POST [ 'start_date ' ] ?? '' ),
147+ 'end_date ' => sanitize_text_field ( $ _POST [ 'end_date ' ] ?? '' ),
148+ ];
149+
150+ $ init = ME_Init::instance ();
151+ $ init ->schedule_exports ( $ export_args );
152+
153+ echo '<div class="updated"><p> ' . esc_html__ ( 'Export has been scheduled for all subsites! View results in the ' , 'multisite-exporter ' ) . ' <a href=" ' . esc_url ( admin_url ( 'admin.php?page=multisite-exporter-history ' ) ) . '"> ' . esc_html__ ( 'Export History ' , 'multisite-exporter ' ) . '</a> ' . esc_html__ ( 'page. ' , 'multisite-exporter ' ) . '</p></div> ' ;
154+ }
155+
156+ // Include the main page view
157+ include_once MULTISITE_EXPORTER_PLUGIN_DIR . 'includes/admin/views/view-main-page.php ' ;
158+ }
159+
160+ /**
161+ * Render the export history page.
162+ */
163+ public function render_history_page () {
164+ // Include the history page view
165+ include_once MULTISITE_EXPORTER_PLUGIN_DIR . 'includes/admin/views/view-history-page.php ' ;
166+ }
167+
168+ /**
169+ * Display the Action Scheduler admin screen under our own menu
170+ */
171+ public function render_action_scheduler_page () {
172+ // Include the action scheduler page view
173+ include_once MULTISITE_EXPORTER_PLUGIN_DIR . 'includes/admin/views/view-scheduled-actions-page.php ' ;
174+ }
175+
176+ /**
177+ * Handle export file downloads
178+ */
179+ public function handle_export_download () {
180+ // Check if user has permission
181+ if ( ! current_user_can ( 'manage_network ' ) ) {
182+ wp_die ( 'You do not have permission to access this file. ' );
183+ }
184+
185+ // Verify nonce
186+ $ file = isset ( $ _GET [ 'file ' ] ) ? sanitize_text_field ( base64_decode ( $ _GET [ 'file ' ] ) ) : '' ;
187+ if ( ! wp_verify_nonce ( $ _GET [ 'nonce ' ], 'download_export_ ' . $ file ) ) {
188+ wp_die ( 'Security check failed. ' );
189+ }
190+
191+ // Use the Export class to download the file
192+ $ exporter = ME_Export::instance ();
193+ $ exporter ->download_export_file ( $ file );
194+ }
195+
196+ /**
197+ * Handle the download of multiple selected export files as a zip archive
198+ */
199+ public function download_selected_exports () {
200+ // Check nonce for security
201+ if ( ! isset ( $ _POST [ 'me_download_nonce ' ] ) || ! wp_verify_nonce ( $ _POST [ 'me_download_nonce ' ], 'download_selected_exports ' ) ) {
202+ wp_die ( esc_html__ ( 'Security check failed. ' , 'multisite-exporter ' ) );
203+ }
204+
205+ // Check if we're selecting all exports across all pages
206+ $ select_all_pages = isset ( $ _POST [ 'select_all_pages ' ] ) && $ _POST [ 'select_all_pages ' ] == '1 ' ;
207+
208+ // Get selected exports or all exports if select_all_pages is true
209+ $ all_exports = get_site_transient ( 'multisite_exports ' ) ?: array ();
210+
211+ if ( $ select_all_pages ) {
212+ // Use all available exports
213+ $ selected = array_column ( $ all_exports , 'file_name ' );
214+ } else {
215+ // Use only explicitly selected exports
216+ $ selected = isset ( $ _POST [ 'selected_exports ' ] ) ? (array ) $ _POST [ 'selected_exports ' ] : array ();
217+ }
218+
219+ // Ensure we have selected exports
220+ if ( empty ( $ selected ) ) {
221+ wp_die ( esc_html__ ( 'No exports selected. ' , 'multisite-exporter ' ) );
222+ }
223+
224+ // If there's only one file selected, just download it directly
225+ if ( count ( $ selected ) === 1 ) {
226+ $ file_name = $ selected [ 0 ];
227+ $ exporter = ME_Export::instance ();
228+ $ exporter ->download_export_file ( $ file_name );
229+ exit ;
230+ }
231+
232+ // Create a zip file containing all selected exports
233+ $ zip = new ZipArchive ();
234+ $ temp_file = tempnam ( sys_get_temp_dir (), 'me_exports_ ' );
235+
236+ if ( $ zip ->open ( $ temp_file , ZipArchive::CREATE ) !== true ) {
237+ wp_die ( esc_html__ ( 'Could not create ZIP file. ' , 'multisite-exporter ' ) );
238+ }
239+
240+ // Get the export directory
241+ $ init = ME_Init::instance ();
242+ $ export_dir = $ init ->get_export_directory ();
243+
244+ $ found_files = array ();
245+
246+ // Add files to the zip
247+ foreach ( $ selected as $ file_name ) {
248+ $ file_path = trailingslashit ( $ export_dir ) . $ file_name ;
249+
250+ if ( file_exists ( $ file_path ) ) {
251+ $ zip ->addFile ( $ file_path , $ file_name );
252+ $ found_files [] = $ file_name ;
253+ }
254+ }
255+
256+ // Check if we found all files
257+ if ( count ( $ found_files ) !== count ( $ selected ) ) {
258+ $ missing = array_diff ( $ selected , $ found_files );
259+ $ error_message = sprintf (
260+ /* translators: %s: Comma-separated list of missing files */
261+ esc_html__ ( 'Could not find some export files: %s ' , 'multisite-exporter ' ),
262+ implode ( ', ' , $ missing )
263+ );
264+ wp_die ( $ error_message );
265+ }
266+
267+ $ zip ->close ();
268+
269+ // Output the zip file
270+ header ( 'Content-Type: application/zip ' );
271+ header ( 'Content-Disposition: attachment; filename="multisite-exports- ' . date ( 'Y-m-d ' ) . '.zip" ' );
272+ header ( 'Content-Length: ' . filesize ( $ temp_file ) );
273+ header ( 'Pragma: no-cache ' );
274+ header ( 'Expires: 0 ' );
275+
276+ readfile ( $ temp_file );
277+ unlink ( $ temp_file );
278+ exit ;
279+ }
280+
281+ /**
282+ * Force Action Scheduler query args to only show our hook
283+ */
284+ public function force_hook_filter ( $ query_args ) {
285+ $ query_args [ 'hook ' ] = 'me_process_site_export ' ;
286+ return $ query_args ;
287+ }
288+
289+ /**
290+ * Filter the SQL query directly if needed
291+ */
292+ public function filter_as_sql_query ( $ sql ) {
293+ global $ wpdb ;
294+
295+ // Only modify the SQL if we're on our custom page
296+ if ( isset ( $ _GET [ 'page ' ] ) && $ _GET [ 'page ' ] === 'me-scheduled-actions ' ) {
297+ // Ensure the SQL contains a WHERE clause for our hook
298+ if ( ! strpos ( $ sql , "hook = 'me_process_site_export' " ) ) {
299+ // If there's a WHERE clause, add our condition
300+ if ( strpos ( $ sql , 'WHERE ' ) !== false ) {
301+ $ sql = str_replace (
302+ 'WHERE ' ,
303+ "WHERE (a.hook = 'me_process_site_export') AND " ,
304+ $ sql
305+ );
306+ } else {
307+ // If no WHERE clause exists, add one
308+ $ sql .= " WHERE (a.hook = 'me_process_site_export') " ;
309+ }
310+ }
311+ }
312+
313+ return $ sql ;
314+ }
315+ }
0 commit comments