set_supported_post_types(); add_filter('post_row_actions', array($this, 'add_export_action'), 10, 2); add_filter('page_row_actions', array($this, 'add_export_action'), 10, 2); add_action('admin_init', array($this, 'handle_export_request')); add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts')); add_action('admin_menu', array($this, 'add_admin_menu')); } private function set_supported_post_types() { $post_types = get_post_types(array('public' => true), 'names'); if (isset($post_types['attachment'])) { unset($post_types['attachment']); } $this->supported_post_types = apply_filters('pageexli_supported_post_types', $post_types); $saved_post_types = get_option('pageexli_post_types', array()); if (! empty($saved_post_types)) { $this->supported_post_types = array_intersect($this->supported_post_types, $saved_post_types); } } private function is_supported_post_type($post_type) { return in_array($post_type, $this->supported_post_types, true); } public function add_export_action($actions, $post) { if (! current_user_can('administrator') || ! $this->is_supported_post_type($post->post_type)) { return $actions; } $export_url = wp_nonce_url( admin_url('admin.php?action=pageexli_export_post&post_id=' . absint($post->ID)), 'pageexli_export_post_' . $post->ID ); /* translators: %s: Post type label (e.g. "page" or "post") */ $title_text = sprintf( // translators: %s is the post type name (e.g., "page", "post") __('Export this %s', 'alenjoseph-page-exporter-lite'), $post->post_type ); $actions['pageexli_export'] = sprintf( '%s', esc_url($export_url), esc_attr($title_text), esc_html__('Export', 'alenjoseph-page-exporter-lite') ); return $actions; } public function handle_export_request() { if (! isset($_GET['action']) || sanitize_key(wp_unslash($_GET['action'])) !== 'pageexli_export_post') { return; } // Validate post_id existence if (! isset($_GET['post_id'])) { wp_die(esc_html__('Invalid post ID.', 'alenjoseph-page-exporter-lite')); } // Properly sanitize and validate $_GET['post_id'] $post_id_raw = sanitize_text_field(wp_unslash($_GET['post_id'])); // Validate numeric value before conversion if (! is_numeric($post_id_raw)) { wp_die(esc_html__('Invalid post ID.', 'alenjoseph-page-exporter-lite')); } $post_id = absint($post_id_raw); if (! isset($_GET['_wpnonce'])) { wp_die(esc_html__('Missing security token.', 'alenjoseph-page-exporter-lite')); } $nonce = sanitize_text_field(wp_unslash($_GET['_wpnonce'])); if (! wp_verify_nonce($nonce, 'pageexli_export_post_' . $post_id)) { wp_die(esc_html__('Security check failed.', 'alenjoseph-page-exporter-lite')); } if (! current_user_can('administrator')) { wp_die(esc_html__('You do not have permission to export posts.', 'alenjoseph-page-exporter-lite')); } $post = get_post($post_id); if (! $post || ! $this->is_supported_post_type($post->post_type)) { wp_die(esc_html__('Invalid or unsupported post type.', 'alenjoseph-page-exporter-lite')); } $this->export_post($post); } private function export_post($post) { $attachments = get_attached_media('', $post->ID); $post_type_obj = get_post_type_object($post->post_type); $post_type_label = $post_type_obj ? strtolower($post_type_obj->labels->singular_name) : $post->post_type; $filename = sanitize_file_name($post_type_label . '-' . ($post->post_name ? $post->post_name : $post->ID)) . '.xml'; // Send headers if (! headers_sent()) { header('Content-Type: application/xml; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Pragma: public'); } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output is a well-formed export XML echo $this->generate_xml($post, $attachments); exit; } private function generate_xml($post, $attachments) { $sitename = get_bloginfo('name'); $site_url = get_bloginfo('url'); $xml = '' . "\n"; $xml .= '' . "\n\n"; $xml .= '' . "\n\n"; $xml .= "\n"; $xml .= " " . $this->cdata($sitename) . "\n"; $xml .= " " . esc_url($site_url) . "\n"; $xml .= " " . $this->cdata(get_bloginfo('description')) . "\n"; $xml .= " " . gmdate('D, d M Y H:i:s +0000') . "\n"; $xml .= " " . get_bloginfo('language') . "\n"; $xml .= " 1.2\n"; $xml .= " " . esc_url($site_url) . "\n"; $xml .= " " . esc_url($site_url) . "\n\n"; if ($post->post_type === 'post') { $xml .= $this->get_categories_xml($post); $xml .= $this->get_tags_xml($post); } $xml .= $this->get_custom_taxonomies_xml($post); $xml .= $this->get_post_xml($post); foreach ($attachments as $attachment) { $xml .= $this->get_attachment_xml($attachment); } $xml .= "\n"; return $xml; } private function get_categories_xml($post) { $xml = ''; $categories = get_the_category($post->ID); if ($categories && ! is_wp_error($categories)) { foreach ($categories as $category) { $parent_slug = $category->parent ? get_category($category->parent)->slug : ''; $xml .= " \n"; $xml .= " {$category->term_id}\n"; $xml .= " " . $this->cdata($category->slug) . "\n"; $xml .= " " . $this->cdata($parent_slug) . "\n"; $xml .= " " . $this->cdata($category->name) . "\n"; $xml .= " \n"; } } return $xml; } private function get_tags_xml($post) { $xml = ''; $tags = get_the_tags($post->ID); if ($tags && ! is_wp_error($tags)) { foreach ($tags as $tag) { $xml .= " \n"; $xml .= " {$tag->term_id}\n"; $xml .= " " . $this->cdata($tag->slug) . "\n"; $xml .= " " . $this->cdata($tag->name) . "\n"; $xml .= " \n"; } } return $xml; } private function get_custom_taxonomies_xml($post) { $xml = ''; $taxonomies = get_object_taxonomies($post->post_type, 'objects'); foreach ($taxonomies as $taxonomy) { if (in_array($taxonomy->name, array('category', 'post_tag'), true)) { continue; } $terms = get_the_terms($post->ID, $taxonomy->name); if ($terms && ! is_wp_error($terms)) { foreach ($terms as $term) { $parent_slug = $term->parent ? get_term($term->parent)->slug : ''; $xml .= " \n"; $xml .= " {$term->term_id}\n"; $xml .= " " . $this->cdata($taxonomy->name) . "\n"; $xml .= " " . $this->cdata($term->slug) . "\n"; $xml .= " " . $this->cdata($parent_slug) . "\n"; $xml .= " " . $this->cdata($term->name) . "\n"; $xml .= " \n"; } } } return $xml; } private function get_post_xml($post) { $pub_date = gmdate('D, d M Y H:i:s +0000', strtotime($post->post_date)); $post_date = $post->post_date; $post_date_gmt = $post->post_date_gmt; $xml = " \n"; $xml .= " " . $this->cdata($post->post_title) . "\n"; $xml .= " " . esc_url(get_permalink($post->ID)) . "\n"; $xml .= " $pub_date\n"; $xml .= " " . $this->cdata(get_the_author_meta('login', $post->post_author)) . "\n"; $xml .= " " . esc_url(get_permalink($post->ID)) . "\n"; $xml .= " \n"; $xml .= " " . $this->cdata($post->post_content) . "\n"; $xml .= " " . $this->cdata($post->post_excerpt) . "\n"; $xml .= " {$post->ID}\n"; $xml .= " " . $this->cdata($post_date) . "\n"; $xml .= " " . $this->cdata($post_date_gmt) . "\n"; $xml .= " " . $this->cdata($post->comment_status) . "\n"; $xml .= " " . $this->cdata($post->ping_status) . "\n"; $xml .= " " . $this->cdata($post->post_name) . "\n"; $xml .= " " . $this->cdata($post->post_status) . "\n"; $xml .= " {$post->post_parent}\n"; $xml .= " {$post->menu_order}\n"; $xml .= " " . $this->cdata($post->post_type) . "\n"; $xml .= " " . $this->cdata($post->post_password) . "\n"; $xml .= " " . (is_sticky($post->ID) ? '1' : '0') . "\n"; $taxonomies = get_object_taxonomies($post->post_type); foreach ($taxonomies as $taxonomy) { if (in_array($taxonomy, array('category', 'post_tag'), true)) { continue; } $terms = get_the_terms($post->ID, $taxonomy); if ($terms && ! is_wp_error($terms)) { foreach ($terms as $term) { $xml .= ' ' . $this->cdata($term->name) . '' . "\n"; } } } $custom_fields = get_post_meta($post->ID); foreach ($custom_fields as $key => $values) { $include_field = true; if (strpos($key, '_') === 0) { $acf_patterns = array( '/^_[a-z0-9_]+$/', '/^_wp_page_template$/', '/^_thumbnail_id$/', '/^_edit_lock$/', '/^_edit_last$/', '/^_wp_attachment_metadata$/', '/^_wp_attached_file$/' ); $include_field = false; foreach ($acf_patterns as $pattern) { if (preg_match($pattern, $key)) { $include_field = true; break; } } } if (! $include_field) { continue; } foreach ($values as $value) { $xml .= " \n"; $xml .= " " . $this->cdata($key) . "\n"; $xml .= " " . $this->cdata($value) . "\n"; $xml .= " \n"; } } $xml .= " \n\n"; return $xml; } private function get_attachment_xml($attachment) { $pub_date = gmdate('D, d M Y H:i:s +0000', strtotime($attachment->post_date)); $xml = " \n"; $xml .= " " . $this->cdata($attachment->post_title) . "\n"; $xml .= " " . esc_url(wp_get_attachment_url($attachment->ID)) . "\n"; $xml .= " $pub_date\n"; $xml .= " " . $this->cdata(get_the_author_meta('login', $attachment->post_author)) . "\n"; $xml .= " " . esc_url(wp_get_attachment_url($attachment->ID)) . "\n"; $xml .= " \n"; $xml .= " " . $this->cdata($attachment->post_content) . "\n"; $xml .= " " . $this->cdata($attachment->post_excerpt) . "\n"; $xml .= " {$attachment->ID}\n"; $xml .= " " . $this->cdata($attachment->post_date) . "\n"; $xml .= " " . $this->cdata($attachment->post_date_gmt) . "\n"; $xml .= " " . $this->cdata($attachment->comment_status) . "\n"; $xml .= " " . $this->cdata($attachment->ping_status) . "\n"; $xml .= " " . $this->cdata($attachment->post_name) . "\n"; $xml .= " " . $this->cdata($attachment->post_status) . "\n"; $xml .= " {$attachment->post_parent}\n"; $xml .= " {$attachment->menu_order}\n"; $xml .= " " . $this->cdata($attachment->post_type) . "\n"; $xml .= " " . $this->cdata($attachment->post_password) . "\n"; $xml .= " 0\n"; $attachment_url = wp_get_attachment_url($attachment->ID); $xml .= " " . $this->cdata($attachment_url) . "\n"; $attachment_meta = wp_get_attachment_metadata($attachment->ID); if ($attachment_meta) { $xml .= " \n"; $xml .= " _wp_attachment_metadata\n"; $xml .= " " . $this->cdata(serialize($attachment_meta)) . "\n"; $xml .= " \n"; } $custom_fields = get_post_meta($attachment->ID); foreach ($custom_fields as $key => $values) { if ($key === '_wp_attachment_metadata') { continue; } foreach ($values as $value) { $xml .= " \n"; $xml .= " " . $this->cdata($key) . "\n"; $xml .= " " . $this->cdata($value) . "\n"; $xml .= " \n"; } } $xml .= " \n\n"; return $xml; } public function add_admin_menu() { add_options_page( __('Page Exporter Lite Settings', 'alenjoseph-page-exporter-lite'), __('Page Exporter Lite', 'alenjoseph-page-exporter-lite'), 'manage_options', 'alenjoseph-page-exporter-lite', array($this, 'settings_page') ); } public function settings_page() { if (isset($_POST['submit'])) { check_admin_referer('pageexli_settings'); $post_types = array(); // Properly sanitize $_POST['pel_post_types'] if (isset($_POST['pageexli_post_types']) && is_array($_POST['pageexli_post_types'])) { // Sanitize the entire array immediately $post_types_raw = array_map('sanitize_text_field', wp_unslash($_POST['pageexli_post_types'])); // Verify it's an array if (is_array($post_types_raw)) { foreach ($post_types_raw as $pt) { // Additional validation: ensure it's a valid post type if (post_type_exists($pt)) { $post_types[] = $pt; } } } } update_option('pageexli_post_types', $post_types); echo '

' . esc_html__('Settings saved.', 'alenjoseph-page-exporter-lite') . '

'; $this->set_supported_post_types(); } $all_post_types = get_post_types(array('public' => true), 'objects'); if (isset($all_post_types['attachment'])) { unset($all_post_types['attachment']); } $selected_post_types = get_option('pageexli_post_types', array_keys($all_post_types)); ?>

$obj) : ?>
'; } public function enqueue_scripts($hook) { if ('edit.php' !== $hook) { return; } $screen = get_current_screen(); if (! $this->is_supported_post_type($screen->post_type)) { return; } wp_enqueue_style( 'alenjoseph-page-exporter-lite', PAGEEXLI_PLUGIN_URL . 'admin/admin.css', array(), PAGEEXLI_PLUGIN_VERSION ); wp_enqueue_script( 'alenjoseph-page-exporter-lite', PAGEEXLI_PLUGIN_URL . 'admin/admin.js', array('jquery'), PAGEEXLI_PLUGIN_VERSION, true ); wp_localize_script( 'alenjoseph-page-exporter-lite', 'pageexli_ajax', array( 'nonce' => wp_create_nonce('pageexli_nonce'), 'exporting' => esc_html__('Exporting...', 'alenjoseph-page-exporter-lite'), 'export_complete' => esc_html__('Export complete!', 'alenjoseph-page-exporter-lite'), 'export_error' => esc_html__('Export failed. Please try again.', 'alenjoseph-page-exporter-lite'), ) ); } } new PAGEEXLI_Page_Exporter(); register_activation_hook(__FILE__, 'pageexli_activation'); function pageexli_activation() { if (version_compare(get_bloginfo('version'), '6.0', '<')) { deactivate_plugins(plugin_basename(__FILE__)); wp_die(esc_html__('Page Exporter Lite requires WordPress 6.0 or higher.', 'alenjoseph-page-exporter-lite')); } $default = array('post', 'page'); add_option('pageexli_post_types', $default); } register_deactivation_hook(__FILE__, 'pageexli_deactivation'); function pageexli_deactivation() { delete_option('pageexli_post_types'); }