Settings saved!

'; } // Get saved options $post_types = get_option('content_filter_post_types', []); $taxonomies = get_option('content_filter_taxonomies', []); $posts_per_page = get_option('content_filter_posts_per_page', 12); $homepage_taxonomy = get_option('content_filter_homepage_taxonomy', ''); // Get all available post types and taxonomies $all_post_types = get_post_types(['public' => true], 'objects'); $all_taxonomies = get_taxonomies(['public' => true], 'objects'); ?>

Content Filter Settings

Post Types

Select the post types to include in the filter.


Taxonomies

Select the taxonomies to include in the filter.


Posts Per Page

Set the number of posts to display per page in the filter results.

Homepage Taxonomy

Select one taxonomy to include in the homepage search form.

admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('resource_filter_nonce') ]); } } /** Renders the resource filter form. * * Loads the filter form template and displays a summary of the total resources. * Displays the number of resources and applied filters. * Calls the loadResources method to display the initial list of resources. * * @return string The HTML output of the filter form and resource list. * * @since 1.0.0 */ public function renderFilterForm($atts) { $atts = shortcode_atts([ 'type' => 'default' // Accepts 'default' or 'homepage' ], $atts, 'resource_filter'); $query = $this->getQuery(); define('RF_TOTAL_RESOURCES', $query->found_posts); ob_start(); $attTmpl = $this->getTemplate($atts['type']); if (!$attTmpl) { return ob_get_clean(); } $resForm = rfGetTemplate($attTmpl); $summary = rfGetTemplate('filter-summary.php'); $this->includeTemplate($resForm, 'Form template not found.'); if ($atts['type'] === 'default') { $this->includeTemplate($summary, 'Summary template not found.'); $this->renderResourceResults($query); } return ob_get_clean(); } /** Retrieves the template file for the given filter type. * * @param string $type The type of filter. Accepts 'default' or 'homepage'. * @return string|false The path to the template file or false if the homepage filter has no configured taxonomy. * * @since 1.0.0 */ private function getTemplate($type) { if ($type === 'homepage') { $homepage_taxonomy = get_option('content_filter_homepage_taxonomy', ''); if (!empty($homepage_taxonomy) && taxonomy_exists($homepage_taxonomy)) { $GLOBALS['homepage_taxonomy'] = $homepage_taxonomy; return 'filter-homepage.php'; } else { echo '

Error: No valid taxonomy configured for the homepage filter.

'; return false; } } else { return 'filter-form.php'; } } /** Includes a template file. * * If the template file is found, it is included once. Otherwise, an error message is displayed. * * @param string $template The path to the template file. * @param string $errorMessage The error message to display if the template file is not found. * * @since 1.0.0 */ private function includeTemplate($template, $errorMessage) { if ($template) { include_once $template; } else { echo '

Error: ' . $errorMessage . '

'; } } /** Renders the resource results. * * If the request method is POST, this directly renders the filtered results. * Otherwise, it loads all resources initially. * * This function also handles pagination for the resource results. * * @param \WP_Query $query The WP_Query object for the resource results. * * @since 1.0.0 */ private function renderResourceResults($query) { ?>
filterResources(); // Directly render filtered results } else { $this->loadResources(); // Load all resources initially } $pagination_links = paginate_links([ 'total' => $query->max_num_pages, 'current' => max(1, get_query_var('paged', 1)), 'format' => '?paged=%#%', 'prev_text' => '«', 'next_text' => '»', 'type' => 'list' ]); ?>
' . $pagination_links . ''; } } /** Generate a WP_Query object based on the request method. * * If the request method is POST, build the query args based on the * $_POST data. Otherwise, build a basic query for all resources. * * @return WP_Query The query object */ private function getQuery() { if ($_SERVER['REQUEST_METHOD'] === 'POST') { $sort_order = isset($_POST['sort_order']) ? sanitize_text_field($_POST['sort_order']) : 'date_desc'; $query_args = [ 'post_type' => get_option('content_filter_post_types', ['post']), 'posts_per_page' => get_option('content_filter_posts_per_page', 12), 'paged' => max(1, get_query_var('paged', 1)), // Get current page number 'tax_query' => $this->buildDynamicTaxQuery(), 's' => isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '', ]; // Sorting logic $query_args = $this->applySorting($query_args, $sort_order); $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']); $query_args['tax_query'][] = [ 'taxonomy' => 'resource_type', 'field' => 'slug', 'terms' => $resType, 'operator' => 'IN' ]; } if (!empty($_POST['resource_subject'])) { $query_args['tax_query'][] = [ 'taxonomy' => 'resource_subject', 'field' => 'slug', 'terms' => array_map('sanitize_text_field', $_POST['resource_subject']), 'operator' => 'IN' ]; } if (!empty($tax_query)) { $query_args['tax_query'] = [ 'relation' => 'AND', // Both filters must match ...$tax_query ]; } return new WP_Query($query_args); } else { return new WP_Query([ 'post_type' => get_option('content_filter_post_types', ['post']), 'posts_per_page' => get_option('content_filter_posts_per_page', 12), 'paged' => max(1, get_query_var('paged', 1)), // Get current page number ]); } } /** Modify the query args based on the specified sort order. * * The sort order can be one of the following: * - date_asc: Sort by date in ascending order (oldest first). * - date_desc: Sort by date in descending order (newest first). * - title_asc: Sort by title in ascending order (A-Z). * - title_desc: Sort by title in descending order (Z-A). * * If an invalid sort order is provided, the query args will default to sorting * by date in descending order (newest first). * * @param array $query_args The query args to modify. * @param string $sort_order The desired sort order. * * @return array The modified query args. * * @since 1.0.0 */ private function applySorting($query_args, $sort_order) { switch ($sort_order) { case 'date_asc': $query_args['orderby'] = 'date'; $query_args['order'] = 'ASC'; break; case 'date_desc': $query_args['orderby'] = 'date'; $query_args['order'] = 'DESC'; break; case 'title_asc': $query_args['orderby'] = 'title'; $query_args['order'] = 'ASC'; break; case 'title_desc': $query_args['orderby'] = 'title'; $query_args['order'] = 'DESC'; break; default: $query_args['orderby'] = 'date'; $query_args['order'] = 'DESC'; } return $query_args; } /** Load and display resources. * * @return void * * @since 1.0.0 */ public function loadResources() { $query_args = [ 'post_type' => get_option('content_filter_post_types', ['post']), 'posts_per_page' => get_option('content_filter_posts_per_page', 12), 'paged' => max(1, get_query_var('paged', 1)), // Get current page number 'tax_query' => $this->buildDynamicTaxQuery(), ]; $query = new WP_Query($query_args); $resources['count'] = $query->found_posts; $resources = $query->posts; $resResults = rfGetTemplate('resource-results.php'); if ($resResults) { include_once $resResults; } else { echo '

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(); $gitHubUpdater = new GitHubUpdater(__FILE__); $gitHubUpdater->setChangelog('CHANGELOG.md'); $gitHubUpdater->setPluginIcon('assets/icon-256x256.png'); $gitHubUpdater->setPluginBannerLarge('assets/banner.jpg'); $gitHubUpdater->setPluginBannerSmall('assets/banner.jpg'); $gitHubUpdater->add();