plugin_basename(__FILE__), // this is the slug of your plugin 'proper_folder_name' => 'plugin-name', // this is the name of the folder your plugin lives in 'api_url' => 'https://api.github.com/repos/username/repository-name', // the GitHub API url of your GitHub repo 'raw_url' => 'https://raw.github.com/username/repository-name/master', // the GitHub raw url of your GitHub repo 'github_url' => 'https://github.com/username/repository-name', // the GitHub url of your GitHub repo 'zip_url' => 'https://github.com/username/repository-name/zipball/master', // the zip url of the GitHub repo 'sslverify' => true, // whether WP should check the validity of the SSL cert when getting an update, see https://github.com/jkudish/WordPress-GitHub-Plugin-Updater/issues/2 and https://github.com/jkudish/WordPress-GitHub-Plugin-Updater/issues/4 for details 'requires' => '3.0', // which version of WordPress does your plugin require? 'tested' => '3.3', // which version of WordPress is your plugin tested up to? 'readme' => 'README.md', // which file to use as the readme for the version number 'access_token' => '', // Access private repositories by authorizing under Plugins > GitHub Updates when this example plugin is installed ); new WpGitHubUpdater($config); } } add_action('init', 'cfInit'); class ContentFilterPlugin { /** Registers the necessary actions and filters for the Content Filter plugin. * * Hooks include: * - admin_menu: Adds the Content Filter settings page to the WordPress admin menu. * - wp_enqueue_scripts: Enqueues the necessary scripts and styles for the resource filter. * - wp_ajax_filter_resources: Handles AJAX requests for filtering resources. * - wp_ajax_nopriv_filter_resources: Handles non-privileged AJAX requests for filtering resources. * * @since 1.0.0 */ public function __construct() { add_action('admin_menu', [$this, 'cfAdminMenu']); add_shortcode('resource_filter', [$this, 'renderFilterForm']); add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']); add_action('wp_ajax_filter_resources', [$this, 'filterResources']); add_action('wp_ajax_nopriv_filter_resources', [$this, 'filterResources']); } /** Registers the Content Filter plugin settings page in the WordPress admin menu. * * The settings page is accessible under the 'Settings' menu, and is only visible to users with the 'manage_options' capability. * * @since 1.4.0 */ public function cfAdminMenu() { add_menu_page( 'Content Filter Settings', // Page title 'Content Filter', // Menu title 'manage_options', // Capability 'content-filter-settings', // Menu slug [$this, 'renderAdminPage'], // Callback function 'dashicons-filter', // Menu icon 25 // Position ); } /** Renders the Content Filter settings page in the WordPress admin dashboard. * * The page is accessible under the 'Settings' menu, and is only visible to users with the 'manage_options' capability. * * The page includes a form for selecting the post types and taxonomies to include in the filter, as well as a number field for setting the number of posts to display per page in the filter results. * * @since 1.4.0 */ public function renderAdminPage() { if (!current_user_can('manage_options')) { return; } // Handle form submission if ($_SERVER['REQUEST_METHOD'] === 'POST') { check_admin_referer('content_filter_settings'); $post_types = isset($_POST['post_types']) ? array_map('sanitize_text_field', $_POST['post_types']) : []; $taxonomies = isset($_POST['taxonomies']) ? array_map('sanitize_text_field', $_POST['taxonomies']) : []; $posts_per_page = isset($_POST['posts_per_page']) ? intval($_POST['posts_per_page']) : 12; update_option('content_filter_post_types', $post_types); update_option('content_filter_taxonomies', $taxonomies); update_option('content_filter_posts_per_page', $posts_per_page); echo '
Settings saved!
Error: Form template not found.
'; } if ($atts['type'] === 'default') { if ($summary) { include_once $summary; } else { echo 'Error: Summary template not found.
'; } ?>Error: Results template not found.
'; } wp_reset_postdata(); } /** AJAX handler for filtering resources. * * Searches for resources based on search term, resource type, and/or resource subject. * Returns a JSON response with the count of resources found and the HTML for the resource results. * * Verifies the nonce and sanitizes the input data. * * @since 1.0.0 */ public function filterResources() { $is_ajax = defined('DOING_AJAX') && DOING_AJAX; if ($is_ajax) { check_ajax_referer('resource_filter_nonce', 'nonce'); } $query_args = $this->buildQueryArgs(); $query = new WP_Query($query_args); ob_start(); $resources = $query->posts; $resResults = rfGetTemplate('resource-results.php'); if ($resResults) { include_once $resResults; } else { echo 'Error: Results template not found.
'; } if ($is_ajax) { $this->sendAjaxResponse($query); } else { echo ob_get_clean(); } } /** Build the query arguments array for filtering resources. * * Uses the $_POST data to generate the query arguments for the WP_Query object. * Sanitizes the input data to prevent SQL injection attacks. * * @return array The query arguments array. * * @since 1.0.0 */ private function buildQueryArgs() { $post_types = get_option('content_filter_post_types', []); $sort_order = isset($_POST['sort_order']) ? sanitize_text_field($_POST['sort_order']) : 'date_desc'; $query_args = [ 'post_type' => !empty($post_types) ? $post_types : ['post'], 'posts_per_page' => get_option('content_filter_posts_per_page', 12), 'paged' => isset($_POST['paged']) ? intval($_POST['paged']) : 1, // Get current page number 'tax_query' => $this->buildDynamicTaxQuery(), 's' => isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '', ]; $query_args = $this->applySorting($query_args, $sort_order); $query_args['tax_query'] = $this->buildTaxQuery(); return $query_args; } /** Generate a dynamic tax query based on the $_POST data. * * Looks through the taxonomies specified in the settings and builds a tax query * for each one that has a value in the $_POST data. Sanitizes the input data to * prevent SQL injection attacks. * * @return array The tax query array. * * @since 1.4.0 */ private function buildDynamicTaxQuery() { $taxonomies = get_option('content_filter_taxonomies', []); $tax_query = []; if (!empty($taxonomies)) { foreach ($taxonomies as $taxonomy) { if (!empty($_POST[$taxonomy])) { $terms = is_array($_POST[$taxonomy]) ? array_map('sanitize_text_field', $_POST[$taxonomy]) : sanitize_text_field($_POST[$taxonomy]); $tax_query[] = [ 'taxonomy' => $taxonomy, 'field' => 'slug', 'terms' => $terms, 'operator' => 'IN', ]; } } } if (!empty($tax_query)) { return [ 'relation' => 'AND', ...$tax_query, ]; } return []; } /** Builds a taxonomy query array for filtering resources by type and subject. * * Constructs a taxonomy query based on the 'resource_type' and 'resource_subject' * POST parameters. The function checks if these parameters are present and * properly sanitizes them before adding them to the query. If both parameters * are provided, the query will require both conditions to match using an 'AND' * relation. * * @return array The constructed tax query array, or an empty array if no filters are applied. */ private function buildTaxQuery() { $tax_query = []; if (!empty($_POST['resource_type'])) { $resType = is_array($_POST['resource_type']) ? array_map('sanitize_text_field', $_POST['resource_type']) : sanitize_text_field($_POST['resource_type']); $tax_query[] = [ 'taxonomy' => 'resource_type', 'field' => 'slug', 'terms' => $resType, 'operator' => 'IN' ]; } if (!empty($_POST['resource_subject'])) { $tax_query[] = [ 'taxonomy' => 'resource_subject', 'field' => 'slug', 'terms' => array_map('sanitize_text_field', $_POST['resource_subject']), 'operator' => 'IN' ]; } if (!empty($tax_query)) { return [ 'relation' => 'AND', // Both filters must match ...$tax_query ]; } return $tax_query; } /** Sends an AJAX response with resource filtering results. * * Constructs a response array containing the number of resources found, * the applied filters, the HTML output of the resources, and pagination links. * The response is then encoded as a JSON object and sent back to the client. * * @param WP_Query $query The query object containing the filtered resources. * * @since 1.0.0 */ private function sendAjaxResponse($query) { $response = [ 'count' => $query->found_posts, 'filters' => [ 'search' => isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '', 'resource_type' => !empty($_POST['resource_type']) ? sanitize_text_field($_POST['resource_type']) : '', 'resource_subject' => !empty($_POST['resource_subject']) ? sanitize_text_field($_POST['resource_subject']) : '' ], 'html' => ob_get_clean(), 'pagination' => paginate_links([ 'total' => $query->max_num_pages, 'current' => isset($_POST['paged']) ? intval($_POST['paged']) : 1, 'format' => '?paged=%#%', 'add_args' => [], // Pass additional query arguments 'prev_text' => '«', 'next_text' => '»', 'type' => 'array', ]) ]; echo json_encode($response); wp_die(); } } new ContentFilterPlugin();