diff --git a/public/app/themes/clarity/functions.php b/public/app/themes/clarity/functions.php index 9763e6f9b..cdb9a3c78 100644 --- a/public/app/themes/clarity/functions.php +++ b/public/app/themes/clarity/functions.php @@ -59,7 +59,6 @@ require_once 'inc/amazon-s3-and-cloudfront-signing.php'; require_once 'inc/amazon-s3-and-cloudfront.php'; -require_once 'inc/api/get-posts-rest-api.php'; require_once 'inc/api/campaign-api.php'; require_once 'inc/api/get-campaign-posts-api.php'; require_once 'inc/api/get-news-rest-api.php'; @@ -76,6 +75,7 @@ require_once 'inc/cookies.php'; require_once 'inc/comments.php'; require_once 'inc/constants.php'; +require_once 'inc/content-filter/search-query.php'; require_once 'inc/content-filter/search.php'; require_once 'inc/enqueue.php'; require_once 'inc/form-builder.php'; @@ -94,7 +94,6 @@ require_once 'inc/menu.php'; require_once 'inc/utilities.php'; require_once 'inc/pagination.php'; -require_once 'inc/pagination-newscategory.php'; require_once 'inc/post-types/post.php'; require_once 'inc/post-types/event.php'; @@ -120,6 +119,9 @@ new MOJ\Intranet\WPOffloadMedia(); new MOJ\Intranet\WPElasticPress(); +$search = new MOJ\Intranet\Search(); +$search->hooks(); + /// Prevent the Agency Switcher page from being overwritten add_action('save_post', function ($post_id, $post) { if ($post->post_name === 'agency-switcher') { diff --git a/public/app/themes/clarity/inc/api/get-news-rest-api.php b/public/app/themes/clarity/inc/api/get-news-rest-api.php index 2e21c13ce..d28f654cc 100644 --- a/public/app/themes/clarity/inc/api/get-news-rest-api.php +++ b/public/app/themes/clarity/inc/api/get-news-rest-api.php @@ -17,7 +17,7 @@ function get_news_api($set_cpt = '') $oAgency = new Agency(); $activeAgency = $oAgency->getCurrentAgency(); $post_per_page = 10; - $post_type = 'news'; + $post_type = get_post_type(); $post_id = get_the_ID(); $region_id = get_the_terms($post_id, 'region'); $regional_template = get_post_meta(get_the_ID(), 'dw_regional_template', true); diff --git a/public/app/themes/clarity/inc/api/get-posts-rest-api.php b/public/app/themes/clarity/inc/api/get-posts-rest-api.php deleted file mode 100644 index a8628adb9..000000000 --- a/public/app/themes/clarity/inc/api/get-posts-rest-api.php +++ /dev/null @@ -1,41 +0,0 @@ -getCurrentAgency(); - - $args = [ - 'numberposts' => $blog_posts_number, - 'post_type' => 'post', - 'post_status' => 'publish', - 'tax_query' => [ - 'relation' => 'AND', - [ - 'taxonomy' => 'agency', - 'field' => 'term_id', - 'terms' => $activeAgency['wp_tag_id'] - ], - ] - ]; - - $posts = get_posts($args); - - echo '
'; - foreach ($posts as $key => $post) { - include locate_template('src/components/c-article-item/view-blog-feed.php'); - } - -} diff --git a/public/app/themes/clarity/inc/content-filter/search-query.php b/public/app/themes/clarity/inc/content-filter/search-query.php new file mode 100644 index 000000000..d719c66f7 --- /dev/null +++ b/public/app/themes/clarity/inc/content-filter/search-query.php @@ -0,0 +1,94 @@ +page ? (($this->page - 1) * $this->posts_per_page) : 0; + + $args = [ + 'posts_per_page' => $this->posts_per_page, + 'post_type' => $this->post_type, + 'post_status' => 'publish', + 'offset' => $offset, + ...($this->exclude_current ? ['post__not_in' => [get_the_ID()]] : []), + 'tax_query' => [ + 'relation' => 'AND', + [ + 'taxonomy' => 'agency', + 'field' => 'term_id', + 'terms' => $this->agency_term_id + ], + // If the region is set add its ID to the taxonomy query + ...(!empty($this->region_id) ? [ + 'taxonomy' => 'region', + 'field' => 'region_id', + 'terms' => $this->region_id, + ] : []), + // If the news category is set add its ID unless the query is regional, + // as it will have already been added to the tax query. + ...(!empty($this->news_category_id) && empty($this->region_id) ? [ + 'taxonomy' => 'news_category', + 'field' => 'category_id', + 'terms' => $this->news_category_id, + ] : []), + ] + ]; + + // Parse dates from the date filter. + if (!empty($this->date_filter)) { + preg_match('/&after=([^&]*)&before=([^&]*)/', $this->date_filter, $matches); + $args['date_query'] = [ + 'after' => date('Y-m-d', strtotime($matches[1])), + 'before' => date('Y-m-d', strtotime($matches[2])), + 'inclusive' => false, + ]; + } + + // If there is a search query, set the orderby to relevance. + if (!empty($this->keywords_filter)) { + $args['orderby'] = 'relevance'; + $args['s'] = $this->keywords_filter; + } + + return $args; + } +} \ No newline at end of file diff --git a/public/app/themes/clarity/inc/content-filter/search.php b/public/app/themes/clarity/inc/content-filter/search.php index fe92d8727..275145421 100644 --- a/public/app/themes/clarity/inc/content-filter/search.php +++ b/public/app/themes/clarity/inc/content-filter/search.php @@ -10,22 +10,23 @@ namespace MOJ\Intranet; +defined('ABSPATH') || exit; + use MOJ\Intranet\Agency; use MOJ\Intranet\EventsHelper; +use MOJ\Intranet\SearchQueryArgs; use WP_Query; -class FilterSearch +class Search { + /** * FilterSearch constructor. * * @return void */ - public function __construct() - { - $this->hooks(); - } + public function __construct() {} /** * Hooks @@ -34,10 +35,14 @@ public function __construct() */ public function hooks(): void { + // Add functions to handle AJAX requests. add_action('wp_ajax_load_search_results', [$this, 'loadSearchResults']); add_action('wp_ajax_nopriv_load_search_results', [$this, 'loadSearchResults']); add_action('wp_ajax_load_events_filter_results', [$this, 'loadEventSearchResults']); add_action('wp_ajax_nopriv_load_events_filter_results', [$this, 'loadEventSearchResults']); + + // Add templates to the footer. + add_action('wp_footer', [$this, 'addAjaxTemplates']); } /** @@ -48,69 +53,116 @@ public function hooks(): void public function loadEventSearchResults() { - if (!wp_verify_nonce($_POST['nonce_hash'], 'search_filter_nonce')) { + if (!wp_verify_nonce($_POST['_nonce'], 'search_filter_nonce')) { exit('Access not allowed.'); } - $active_agency = (new Agency())->getCurrentAgency(); - $agency_term_id = $active_agency['wp_tag_id']; - $date_filter = sanitize_text_field($_POST['valueSelected'] ?? 'all'); - $post_id = get_the_ID(); - $query = sanitize_text_field($_POST['query']); - - $filter_options = ['keyword_search' => $query]; - - if ($date_filter != 'all') { - $filter_options['date_filter'] = $date_filter; + + $agency_term_id =(new Agency())->getCurrentAgency()['wp_tag_id']; + + $filter_options = [ + 'keyword_search' => sanitize_text_field($_POST['keywords_filter'] ?? ''), + 'date_filter' => $_POST['date_filter'] == 'all' ? '' : sanitize_text_field($_POST['date_filter']), + ]; + + + if (isset($_POST['termID'])) { + $tax_id = sanitize_text_field($_POST['termID']); + + $filter_options['region_filter'] = $tax_id; } $events_helper = new EventsHelper(); - if (isset($_POST['termID'])) { - $tax_id = sanitize_text_field($_POST['termID']); + $events = $events_helper->get_events($agency_term_id, $filter_options) ?? []; - $filter_options['region_filter'] = $tax_id; + return wp_send_json([ + 'aggregates' => [ + 'totalResults' => count($events), + 'resultsPerPage' => -1, + 'currentPage' => 1, + ], + 'results' => [ + 'templateName' => "c-events-item-list", + 'posts' => array_map([$this, 'mapEventResult'], $events), + ], + ]); + } - $events = $events_helper->get_events($agency_term_id, $filter_options); - } else { - $events = $events_helper->get_events($agency_term_id, $filter_options); + public function mapEventResult($event) + { + // Assign some default values. + $time_formatted = 'All day'; + $datetime = 'P1D'; + + if (!$event->event_allday) { + $datetime = $event->event_start_time; + // If start date and end date selected are the same, just display first date. + if ($event->event_start_time === $event->event_end_time) { + $time_formatted = substr($event->event_start_time, 0, 5); + } else { + $time_formatted = substr($event->event_start_time, 0, 5) . ' - ' . substr($event->event_end_time, 0, 5); + } } - if ($events) { - echo '
'; - - foreach ($events as $key => $event) : - $event_id = $event->ID; - $post_url = $event->url; - $event_title = $event->post_title; + if ($event->event_start_date === $event->event_end_date) { + $multi_date = date('d M', strtotime($event->event_start_date)); + } else { + $multi_date = date('d M', strtotime($event->event_start_date)) . ' - ' . date('d M', strtotime($event->event_end_date)); + } - $start_date = $event->event_start_date; - $end_date = $event->event_end_date; - $start_time = $event->event_start_time; - $end_time = $event->event_end_time; - $location = $event->event_location; - $date = $event->event_start_date; - $day = date('l', strtotime($start_date)); - $month = date('M', strtotime($start_date)); - $year = date('Y', strtotime($start_date)); - $all_day = $event->event_allday; + return [ + 'permalink' => $event->url, + 'post_title' => $event->post_title, + 'year' => date('Y', strtotime($event->event_start_date)), + 'day' => date('l', strtotime($event->event_start_date)), + 'location' => $event->event_location, + 'time_formatted' => $time_formatted, + 'datetime_formatted' => $datetime, + 'multi_date_formatted' => $multi_date + ]; + } - if ($all_day == true) { - $all_day = 'all_day'; - } + public function mapNewsResult(\WP_Post $post) + { + return [ + 'ID' => $post->ID, + 'post_type' => get_post_type($post->ID), + 'post_title' => $post->post_title, + 'post_date_formatted' => get_gmt_from_date($post->post_date, 'j M Y'), + 'post_excerpt_formatted' => empty($post->post_excerpt) ? '' : "

{$post->post_excerpt}

", + 'permalink' => get_permalink($post->ID), + 'post_thumbnail' => get_the_post_thumbnail_url($post->ID, 'user-thumb'), + 'post_thumbnail_alt' => get_post_meta(get_post_thumbnail_id($post->ID), '_wp_attachment_image_alt', true), + ]; + } - echo '
'; + public function mapPostResult(\WP_Post $post) + { - include locate_template('src/components/c-calendar-icon/view.php'); + $thumbnail = get_the_post_thumbnail_url($post->ID, 'user-thumb'); + $thumbnail_alt = get_post_meta(get_post_thumbnail_id($post->ID), '_wp_attachment_image_alt', true); - include locate_template('src/components/c-events-item-byline/view.php'); + $author = $post->post_author; + $author_display_name = $author ? get_the_author_meta('display_name', $author) : ''; - echo '
'; - endforeach; - } else { - echo 'No events found during this date range :('; + if (!$thumbnail) { + // Mutate thumbnail with author image. + $thumbnail = $author ? get_the_author_meta('thumbnail_avatar', $author) : false; + $thumbnail_alt = $author_display_name; } - die(); + + return [ + 'ID' => $post->ID, + 'post_type' => get_post_type($post->ID), + 'post_title' => $post->post_title, + 'post_date_formatted' => get_gmt_from_date($post->post_date, 'j M Y'), + 'post_excerpt_formatted' => empty($post->post_excerpt) ? '' : "

{$post->post_excerpt}

", + 'permalink' => get_permalink($post->ID), + 'post_thumbnail' => $thumbnail, + 'post_thumbnail_alt' => $thumbnail_alt, + 'author_display_name' => $author ? get_the_author_meta('display_name', $author) : '', + ]; } /** @@ -121,7 +173,7 @@ public function loadEventSearchResults() public function loadSearchResults() { - if (!wp_verify_nonce($_POST['nonce_hash'], 'search_filter_nonce')) { + if (!wp_verify_nonce($_POST['_nonce'], 'search_filter_nonce')) { exit('Access not allowed.'); } @@ -131,142 +183,81 @@ public function loadSearchResults() // Apply the weighting fields configuration to the query. add_filter('ep_enable_do_weighting', '__return_true'); - // Run a query based on generated query arguments. - $query = new WP_Query($this->getQueryArgs()); - - // Use output buffering to capture the HTML output. - // This is necessary to get the html without refactoring the component's code. - ob_start(); - echo '
'; - foreach ($query->posts as $post) { - // $post is used in the included file. - include locate_template('src/components/c-article-item/view-news-feed.php'); + $page = (int) $_POST['page'] ?? 1; + if ($page < 1 || $page > 1000) { + $page = 1; + } + + $posts_per_page = (int) ($_POST['posts_per_page'] ?? 10); + if ($posts_per_page < 1 || $posts_per_page > 100) { + $posts_per_page = 10; } - $result_html = ob_get_clean(); - // Get the pagination HTML. - $pagination_html = $this->getPagination( - sanitize_text_field($_POST['valueSelected']), - sanitize_text_field($_POST['nextPageToRetrieve']), - $query->max_num_pages + $allowed_post_types = ['post', 'news']; + + if (!in_array($_POST['post_type'], $allowed_post_types)) { + throw new \Exception('Invalid post type.'); + } + + $post_type = $_POST['post_type']; + + $query_args = new SearchQueryArgs( + (new Agency())->getCurrentAgency()['wp_tag_id'], + $post_type, + $page, + $posts_per_page, + false, + sanitize_text_field($_POST['keywords_filter'] ?? null), + sanitize_text_field($_POST['date_filter'] ?? null), + sanitize_text_field($_POST['news_category_id'] ?? null), + sanitize_text_field($_POST['region_id'] ?? null) ); - // Return the results as JSON. + // Run a query based on generated query arguments. + $query = new WP_Query($query_args->get()); + + $map_function = $post_type === 'news' ? 'mapNewsResult' : 'mapPostResult'; + return wp_send_json([ - 'results' => $result_html, - 'total' => $query->found_posts . ' search results', - 'pagination' => $pagination_html + 'aggregates' => [ + 'totalResults' => $query->found_posts, + 'resultsPerPage' => $posts_per_page, + 'currentPage' => $page, + ], + 'results' => [ + 'posts' => array_map([$this, $map_function], $query->posts), + 'templateName' => "view-{$post_type}-feed", + ], ]); } + /** - * Get Query Args + * Add AJAX templates to the footer. * - * @return array + * These JS templates are used to render the AJAX results to html. + * + * @return void */ - function getQueryArgs() + public function addAjaxTemplates() { - // Get the active agency. - $active_agency = (new Agency())->getCurrentAgency(); - - // Pagination. - $post_per_page = 10; - $next_page_to_retrieve = sanitize_text_field($_POST['nextPageToRetrieve'] ?? ''); - $offset = $next_page_to_retrieve ? (($next_page_to_retrieve - 1) * $post_per_page) : 0; - - // Post type, with a cleanup of the value. - $post_type = sanitize_text_field($_POST['postType'] ?? ''); - $post_type = $post_type === 'posts' ? 'post' : $post_type; - - // Is the request for a news category? - $news_category_id = sanitize_text_field($_POST['newsCategoryValue'] ?? ''); - - // Check if the post type is regional. - $is_regional = $post_type === 'regional_news' ? true : false; - - $args = [ - 'numberposts' => $post_per_page, - 'post_type' => $post_type, - 'post_status' => 'publish', - 'offset' => $offset, - 'tax_query' => [ - 'relation' => 'AND', - [ - 'taxonomy' => 'agency', - 'field' => 'term_id', - 'terms' => $active_agency['wp_tag_id'] - ], - // If the region is set add its ID to the taxonomy query - ...($is_regional ? [ - 'taxonomy' => 'region', - 'field' => 'region_id', - 'terms' => $news_category_id, - ] : []), - // If the news category is set add its ID unless the query is regional, - // as it will have already been added to the tax query. - ...(!empty($news_category_id) && !$is_regional ? [ - 'taxonomy' => 'news_category', - 'field' => 'category_id', - 'terms' => $news_category_id, - ] : []), - ] - ]; - // Get the date filter value. - $value_selected = sanitize_text_field($_POST['valueSelected'] ?? ''); - - // Parse dates from the value selected. - if (!empty($value_selected)) { - preg_match('/&after=([^&]*)&before=([^&]*)/', $value_selected, $matches); - $args['date_query'] = [ - 'after' => date('Y-m-d', strtotime($matches[1])), - 'before' => date('Y-m-d', strtotime($matches[2])), - 'inclusive' => false, - ]; + if (is_page_template('page_blog.php') || is_page_template('page_news.php')) { + get_template_part('src/components/c-pagination/view-infinite.ajax'); + echo ''; } - // Get the search query. - $query = sanitize_text_field($_POST['query'] ?? ''); - - // If there is a search query, set the orderby to relevance. - if (!empty($query)) { - $args['orderby'] = 'relevance'; - $args['s'] = $query; + if (is_page_template('page_blog.php')) { + get_template_part('src/components/c-article-item/view-blog-feed.ajax'); } - return $args; - } - - /** - * Get Pagination - * - * @param string $selected - * @param int|string $next - * @param int $total - * @return string - */ + if (is_page_template('page_news.php')) { + get_template_part('src/components/c-article-item/view-news-feed.ajax'); + } - function getPagination(string $selected, int|string $next, int $total): string - { - $html = ''; - if ($next == $total) { - $html .= ''; - $html .= 'No More Results'; - $html .= ''; - } elseif ($total <= 1) { - $html .= ''; - } else { - $html .= ''; + if (is_page_template('page_events.php')) { + get_template_part('src/components/c-events-item/view-list.ajax'); } - return $html; } } - -new FilterSearch(); diff --git a/public/app/themes/clarity/inc/pagination-newscategory.php b/public/app/themes/clarity/inc/pagination-newscategory.php deleted file mode 100644 index e914a6ea0..000000000 --- a/public/app/themes/clarity/inc/pagination-newscategory.php +++ /dev/null @@ -1,54 +0,0 @@ -getCurrentAgency(); - - $post_per_page = 10; - - $args = [ - 'numberposts' => $post_per_page, - 'post_type' => 'news', - 'post_status' => 'publish', - 'tax_query' => [ - 'relation' => 'AND', - [ - 'taxonomy' => 'agency', - 'field' => 'term_id', - 'terms' => $activeAgency['wp_tag_id'] - ], - // If the category_id is set add it to the taxonomy query - ...( $category_id ? [ - 'taxonomy' => 'news_category', - 'field' => 'category_id', - 'terms' => $category_id, - ] : []), - ] - ]; - - $query = new WP_Query($args); - $pagetotal = $query->max_num_pages; - - ?> -
- - 0) { ?> - - diff --git a/public/app/themes/clarity/page_blog.php b/public/app/themes/clarity/page_blog.php index 0ad71d22e..82508d1d7 100644 --- a/public/app/themes/clarity/page_blog.php +++ b/public/app/themes/clarity/page_blog.php @@ -1,31 +1,45 @@ getCurrentAgency(); +// Use the SearchQueryProps class to create the query properties. +$query_args = new SearchQueryArgs((new Agency())->getCurrentAgency()['wp_tag_id'], 'post', 1, 10); + +// Run the query. +$query = new WP_Query($query_args->get()); ?>
+

+
- + 'post', 'template' => 'view-news-feed']); ?>
+

Latest

+ +
- + posts as $key => $post) { + include locate_template('src/components/c-article-item/view-blog-feed.php'); + } ?>
- + + $query->max_num_pages, 'page' => 1]); ?> +
+
getCurrentAgency(); +// Use the SearchQueryProps class to create the query properties. +$query_args = new SearchQueryArgs((new Agency())->getCurrentAgency()['wp_tag_id'], 'news', 1, 10); + +// Run the query. +$query = new WP_Query($query_args->get()); + ?> -
-

-
- -
-
-

Latest

-
- -
- + +
+ +

+ +
+ 'news', 'template' => 'view-news-feed']); ?> +
+ +
+ +

Latest

+ +
+ posts as $key => $post) { + include locate_template('src/components/c-article-item/view-news-feed.php'); + } ?>
-
+ + $query->max_num_pages, 'page' => 1]); ?> + +
+
+ + diff --git a/public/app/themes/clarity/src/components/c-article-item/view-blog-feed.php b/public/app/themes/clarity/src/components/c-article-item/view-blog-feed.php index f749911b7..af79a0ec2 100644 --- a/public/app/themes/clarity/src/components/c-article-item/view-blog-feed.php +++ b/public/app/themes/clarity/src/components/c-article-item/view-blog-feed.php @@ -9,73 +9,63 @@ use MOJ\Intranet\Authors; -$oAuthor = new Authors(); $id = $post->ID; -$authors = $oAuthor->getAuthorInfo($id); + $thumbnail = get_the_post_thumbnail_url($id, 'user-thumb'); $thumbnail_alt = get_post_meta(get_post_thumbnail_id($id), '_wp_attachment_image_alt', true); -$link = get_the_permalink($id); + +// TODO: Why is this here? It's not used. +// If it should be here, make sure to update `Search->mapPostResult()` +$oAuthor = new Authors(); +$authors = $oAuthor->getAuthorInfo($id); + $author = $post->post_author; $author_display_name = $author ? get_the_author_meta('display_name', $author) : ''; -$author_avatar = $author ? get_the_author_meta('thumbnail_avatar', $author) : ''; - -// Filter right-hand blog list so the page your on isn't duplicated and doesn't appear in that list -if (is_singular('post')) { - $post_id = $post_id ?? 0; - if ($post_id === $id) { - $id = ''; - } -} -if ($id != '') : - ?> -
+if (!$thumbnail) { + // Mutate thumbnail with author image. + $thumbnail = $author ? get_the_author_meta('thumbnail_avatar', $author) : false; + $thumbnail_alt = $author_display_name; +} - +?> - +
- - + + - - '; ?> +
- +

- +

+ By + | + +
- + + -
-

-
+
+

+
-
- +
\ No newline at end of file diff --git a/public/app/themes/clarity/src/components/c-article-item/view-news-feed.ajax.php b/public/app/themes/clarity/src/components/c-article-item/view-news-feed.ajax.php new file mode 100644 index 000000000..79444e11c --- /dev/null +++ b/public/app/themes/clarity/src/components/c-article-item/view-news-feed.ajax.php @@ -0,0 +1,33 @@ + + + diff --git a/public/app/themes/clarity/src/components/c-article-item/view-news-feed.php b/public/app/themes/clarity/src/components/c-article-item/view-news-feed.php index 494199581..a0796597f 100644 --- a/public/app/themes/clarity/src/components/c-article-item/view-news-feed.php +++ b/public/app/themes/clarity/src/components/c-article-item/view-news-feed.php @@ -18,17 +18,11 @@
- '; - echo '' . $thumbnail_alt . ''; - echo ''; - else : - echo ''; - endif; - - ?> + + +
@@ -37,18 +31,16 @@
- +
- -
- - -
-

-
- - + +
+

+
+
diff --git a/public/app/themes/clarity/src/components/c-article/view.php b/public/app/themes/clarity/src/components/c-article/view.php index 721a7e64c..6fbe8ddca 100644 --- a/public/app/themes/clarity/src/components/c-article/view.php +++ b/public/app/themes/clarity/src/components/c-article/view.php @@ -5,6 +5,28 @@ * */ + +use MOJ\Intranet\Agency; +use MOJ\Intranet\SearchQueryArgs; + +/* +* Template Name: Blog archive +*/ + +if (!defined('ABSPATH')) { + die(); +} + +defined('ABSPATH') || exit; + +get_header(); + +// Use the SearchQueryProps class to create the query properties. +$query_args = new SearchQueryArgs((new Agency())->getCurrentAgency()['wp_tag_id'], 'post', 1, 5, true); + +// Run the query. +$query = new WP_Query($query_args->get()); + ?>
@@ -22,8 +44,9 @@ diff --git a/public/app/themes/clarity/src/components/c-calendar-icon/view.ajax.php b/public/app/themes/clarity/src/components/c-calendar-icon/view.ajax.php new file mode 100644 index 000000000..e4730e8f5 --- /dev/null +++ b/public/app/themes/clarity/src/components/c-calendar-icon/view.ajax.php @@ -0,0 +1,26 @@ + + + + +
+ Date: + +
+ + + diff --git a/public/app/themes/clarity/src/components/c-content-filter/view-events.php b/public/app/themes/clarity/src/components/c-content-filter/view-events.php index a944ebf5e..b4e1cc0f6 100644 --- a/public/app/themes/clarity/src/components/c-content-filter/view-events.php +++ b/public/app/themes/clarity/src/components/c-content-filter/view-events.php @@ -9,7 +9,7 @@
-
+

Filter by

@@ -35,6 +35,12 @@ // Hidden field to pass nonce for improved security form_builder('hidden', '', false, '_nonce', '_search_filter_wpnonce', $nonce, null, null, false, null, null); ?> + + + +
diff --git a/public/app/themes/clarity/src/components/c-content-filter/view.php b/public/app/themes/clarity/src/components/c-content-filter/view.php index c31ef96cf..65de4ceea 100644 --- a/public/app/themes/clarity/src/components/c-content-filter/view.php +++ b/public/app/themes/clarity/src/components/c-content-filter/view.php @@ -1,39 +1,46 @@ getCurrentAgency(); - $agency = $activeAgency['shortcode']; +if (empty($args['post_type']) || empty($args['template'])) { + return; +} - $archives_args = [ - 'type' => 'monthly', - 'format' => 'custom', - 'show_post_count' => false, - ]; - ?> +$archives_args = [ + 'type' => 'monthly', + 'format' => 'custom', + 'show_post_count' => false, +]; + +?>
-
+
-

Filter by

+ +

Filter by

+
- +
- - $nonce = wp_create_nonce('search_filter_nonce'); + + - form_builder('text', $prefix, 'Keywords', 'keywords_filter', null, null, 'Contains words such as ‘food’', null, false, null, null); + + + + + + - // Hidden field to pass nonce for improved security - form_builder('hidden', '', false, '_nonce', '_search_filter_wpnonce', $nonce, null, null, false, null, null); - ?>
diff --git a/public/app/themes/clarity/src/components/c-events-item-byline/view.ajax.php b/public/app/themes/clarity/src/components/c-events-item-byline/view.ajax.php new file mode 100644 index 000000000..a252d3273 --- /dev/null +++ b/public/app/themes/clarity/src/components/c-events-item-byline/view.ajax.php @@ -0,0 +1,35 @@ + + + + + + diff --git a/public/app/themes/clarity/src/components/c-events-item-byline/view.php b/public/app/themes/clarity/src/components/c-events-item-byline/view.php index 1129bd5ba..2186e19ab 100644 --- a/public/app/themes/clarity/src/components/c-events-item-byline/view.php +++ b/public/app/themes/clarity/src/components/c-events-item-byline/view.php @@ -25,6 +25,7 @@ } } else { $time = 'All day'; + // TODO: fix bug, I think this variable leaks over to next events in the loop. $datetime = 'P1D'; //period 1 day duration } ?> diff --git a/public/app/themes/clarity/src/components/c-events-item/view-list.ajax.php b/public/app/themes/clarity/src/components/c-events-item/view-list.ajax.php new file mode 100644 index 000000000..235ccdcf0 --- /dev/null +++ b/public/app/themes/clarity/src/components/c-events-item/view-list.ajax.php @@ -0,0 +1,19 @@ + + + diff --git a/public/app/themes/clarity/src/components/c-news-article/view.php b/public/app/themes/clarity/src/components/c-news-article/view.php index d65a5c932..19156c083 100644 --- a/public/app/themes/clarity/src/components/c-news-article/view.php +++ b/public/app/themes/clarity/src/components/c-news-article/view.php @@ -1,3 +1,24 @@ +getCurrentAgency()['wp_tag_id'], 'news', 1, 6, true); + +// Run the query. +$query = new WP_Query($query_args->get()); + +?> +
@@ -16,8 +37,9 @@ Recent news'; - $news_posts_per_page = ''; - get_news_api(); + foreach ($query->posts as $key => $post) { + include locate_template('src/components/c-article-item/view-news-feed.php'); + } ?> diff --git a/public/app/themes/clarity/src/components/c-pagination/style.styl b/public/app/themes/clarity/src/components/c-pagination/style.styl index d05de63df..7c577af17 100644 --- a/public/app/themes/clarity/src/components/c-pagination/style.styl +++ b/public/app/themes/clarity/src/components/c-pagination/style.styl @@ -30,6 +30,11 @@ display: block; float: left; } + &[disabled=disabled] { + span.u-icon { + display: none; + } + } } .more-btn:hoover{ background-color: #f8f8f8; diff --git a/public/app/themes/clarity/src/components/c-pagination/view-infinite.ajax.php b/public/app/themes/clarity/src/components/c-pagination/view-infinite.ajax.php new file mode 100644 index 000000000..c6aaafe78 --- /dev/null +++ b/public/app/themes/clarity/src/components/c-pagination/view-infinite.ajax.php @@ -0,0 +1,21 @@ + + + + + diff --git a/public/app/themes/clarity/src/components/c-pagination/view-infinite.php b/public/app/themes/clarity/src/components/c-pagination/view-infinite.php new file mode 100644 index 000000000..ac2e4215e --- /dev/null +++ b/public/app/themes/clarity/src/components/c-pagination/view-infinite.php @@ -0,0 +1,41 @@ + $query->max_num_pages, 'page' => 1]); + * + * @package Clarity + */ + +defined('ABSPATH') || exit; + +if (empty($args['total_pages']) || empty($args['page'])) { + return; +} + +?> + + + diff --git a/public/app/themes/clarity/src/globals/js/ajax-filter.js b/public/app/themes/clarity/src/globals/js/ajax-filter.js new file mode 100644 index 000000000..8c5451fc4 --- /dev/null +++ b/public/app/themes/clarity/src/globals/js/ajax-filter.js @@ -0,0 +1,71 @@ +import { getFormData, renderResults, renderPagination } from "./ajax-utils.js"; + +export default (function ($) { + $.fn.moji_ajaxFilter = function () { + // Default ajax props. + const DEFAULT_AJAX_PROPS = { + type: "POST", + url: mojAjax.ajaxurl, + dataType: "json", + success: (response) => { + renderResults(response); + renderPagination(response.aggregates); + }, + }; + + const $form = $("#ff, #ff_events"); + + // On page load get the ajax props and store them on the pagination element. + $(".c-pagination").data("ajax-props", { + ...DEFAULT_AJAX_PROPS, + data: getFormData($form.get(0)), + }); + + /** + * Handle a form submit event. + * + * @param {Event} e + * @returns {void} + */ + + $form.on("submit", function (e) { + e.preventDefault(); + + const ajaxProps = { + ...DEFAULT_AJAX_PROPS, + data: getFormData(this), + }; + + // Make the ajax request. + $.ajax(ajaxProps); + + // Store the ajax props on the pagination element. + $(".c-pagination").data("ajax-props", ajaxProps); + }); + + /** + * Handle events on the pagination button. + * + * @param {Event} e + * @returns {void} + */ + + $(".c-pagination").on("click keydown", "button", function (e) { + e.stopPropagation(); + + // If the event is a keydown event, only allow enter or space keys. + if (e.type === "keydown" && ![13, 32].includes(e.keyCode)) { + return; + } + + // Get the ajax props from the pagination element. + const ajaxProps = $(this).closest(".c-pagination").data("ajax-props"); + + // Get the page number from the button. + const page = $(this).data("page"); + + // Make an ajax request with the new page number. + $.ajax({ ...ajaxProps, data: { ...ajaxProps.data, page } }); + }); + }; +})(jQuery); diff --git a/public/app/themes/clarity/src/globals/js/ajax-templating.js b/public/app/themes/clarity/src/globals/js/ajax-templating.js new file mode 100644 index 000000000..8df0fe418 --- /dev/null +++ b/public/app/themes/clarity/src/globals/js/ajax-templating.js @@ -0,0 +1,88 @@ +/** + * This class is responsible for rendering HTML from an AJAX template. + * + * This is an extension of a bare bones templating engine. + * @see https://stackoverflow.com/a/39065147/6671505 + * + * @example + * const template = new AjaxTemplating("results-template"); + * const html = template.renderHtml({ + * title: "Hello World", + * content: "This is a test." + * }); + */ + +export default class AjaxTemplating { + /** + * Constructor + * + * @param {string} templateName + */ + constructor(templateName) { + this.resultsTemplate = this.loadTemplate(templateName); + } + + /** + * Load the template from the DOM. + * + * Returns an array of strings where: + * - every odd index is a template variable + * - every even is a string of html text + * + * @param {string} templateName + * @returns {string[]} + */ + + loadTemplate(templateName) { + // do this without jQuery + return document + .querySelector(`script[data-template="${templateName}"]`) + .textContent.split(/\$\{(.+?)\}/g); + } + + /** + * Render the HTML from the template and props. + * + * @param {Object} props + * @returns {string} + */ + + renderHtml(props) { + // Keep track of the conditional blocks + // If a conditional block is not met, we skip the block + let skip = false; + + let parts = []; + + for (let i = 0; i < this.resultsTemplate.length; i++) { + const tok = this.resultsTemplate[i]; + + // Handle the html text - even indexes + + if (i % 2 === 0 && !skip) { + parts.push(tok); + continue; + } + + if (i % 2 === 0) { + continue; + } + + // Handle the template variables - odd indexes + + if (tok.startsWith("?") && !props[tok.substring(1)]) { + skip = true; + } + + if (tok.startsWith("/?") && !props[tok.substring(2)]) { + skip = false; + } + + if (!skip) { + parts.push(props[tok]); + } + } + + return parts.join(""); + } +} diff --git a/public/app/themes/clarity/src/globals/js/ajax-utils.js b/public/app/themes/clarity/src/globals/js/ajax-utils.js new file mode 100644 index 000000000..9bfd410fc --- /dev/null +++ b/public/app/themes/clarity/src/globals/js/ajax-utils.js @@ -0,0 +1,144 @@ +import AjaxTemplating from "./ajax-templating.js"; + +/** + * Parse the values of a form instance into an object. + * + * @param {HTMLElement} form + * @returns {[string, string][]} + */ + +export const getFormData = (form) => { + const formData = new FormData(form); + + const prefix = formData.get("prefix"); + + // Loop all form entries, remove the prefix from keys, and assign to data. + const entries = [...formData.entries()] + .map(([key, value]) => { + // Remove prefix if it exists. + const newKey = key.startsWith(prefix) ? key.replace(prefix, "") : key; + + // Parse the page number to integer. + if ("page" === newKey) { + value = parseInt(value); + } + + return [newKey, value]; + }) + .filter(([key, value]) => { + // Skip prefix and template keys, and empty values. + if (["prefix"].includes(key) || value === "") { + return false; + } + + return true; + }); + + entries.push(["action", $(form).attr("action")]); + + return Object.fromEntries(entries); +}; + +/** + * Render the response to the page. + * + * The results type should match the php response object + * as returned by the `FilterSearch->mapResults()` method. + * + * @typedef {Object} Post + * @property {string} ID + * @property {string} post_title + * @property {string} post_date_formatted + * @property {string} post_excerpt_formatted + * @property {string} permalink + * @property {string} post_type + * @property {string} post_thumbnail + * @property {string} post_thumbnail_alt + * + * @typedef {Object} Results + * @property {Post[]} posts + * @property {string} templateName + * + * The response type should match the php response object + * as returned by the `FilterSearch->loadSearchResults()` method. + * + * @typedef {Object} Response + * @property {Object} aggregates + * @property {number} aggregates.currentPage + * @property {number} aggregates.resultsPerPage + * @property {number} aggregates.totalResults + * @property {Results} results + * + * @param {Response} response + */ + +export const renderResults = ({ + results: { posts, templateName }, + aggregates: { currentPage, totalResults }, +}) => { + // Remove all articles if page is 1. + if (currentPage === 1) { + $(".c-article-item, .c-events-item-list").remove(); + } + + // Load an instance of the AjaxTemplating class. + const t = new AjaxTemplating(templateName); + + // Render the html for each post. + const resultsHtml = posts.map((props) => t.renderHtml(props)); + + // Append the html to the content section. + $("#content").append(resultsHtml); + + // Update the title. + $("#title-section").text(`${totalResults} search results`); +}; + +/** + * Render pagination to the page. + * + * @param {Object} props + * @param {number} props.currentPage + * @param {number} props.resultsPerPage + * @param {number} props.totalResults + * + * @returns {void} + */ + +export const renderPagination = ({ currentPage, resultsPerPage, totalResults }) => { + if (resultsPerPage === undefined || resultsPerPage === -1) { + return; + } + + const isLastPage = currentPage * resultsPerPage >= totalResults; + + const paginationTitle = ({ totalResults, isLastPage }) => { + if (!totalResults) { + return "No Results"; + } + if (isLastPage) { + return "No More Results"; + } + return `Load Next ${resultsPerPage} Results`; + }; + + const template = new AjaxTemplating("pagination"); + + // Update the pagination. + const paginationHtml = template.renderHtml({ + title: paginationTitle({ totalResults, isLastPage }), + // Disable the button if it's the last page. + disabled: isLastPage ? `disabled="disabled"` : "", + // Adjust the zero-indexed current page. + currentPageFormatted: parseInt(currentPage), + // Calculate the total pages - if no results, then set as 1 to render '1 of 1'. + totalPages: !totalResults ? 1 : Math.ceil(totalResults / resultsPerPage), + }); + + $(".c-pagination").html(paginationHtml); + + // Update the page number on the pagination element. + if (!isLastPage) { + $(".c-pagination button").attr("data-page", currentPage + 1); + } +}; diff --git a/public/app/themes/clarity/src/globals/js/blog-content_filter.js b/public/app/themes/clarity/src/globals/js/blog-content_filter.js deleted file mode 100644 index 62adadab5..000000000 --- a/public/app/themes/clarity/src/globals/js/blog-content_filter.js +++ /dev/null @@ -1,113 +0,0 @@ -export default (function ($) { - $.fn.moji_ajaxFilter = function () { - const postType = $(".data-type").data("type"); - const termID = $(".l-secondary").data("termid"); - const nonceHash = $("#_search_filter_wpnonce").val(); - - $(document).on("submit", "#ff", function (e) { - e.preventDefault(); - - const nextPageToRetrieve = $("#ff").data("page") + 1; - $(".more-btn").attr("data-page", nextPageToRetrieve); - - const valueSelected = $(this) - .find( - "#ff_date_filter option:selected, #ff_region_news_date_filter option:selected", - ) - ?.val(); - const newsCategoryValue = - $(this) - .find( - '#ff_categories_filter_e-news, input[name="ff_categories_filter_regions"]', - ) - ?.val() ?? 0; - - $.ajax({ - type: "post", - url: mojAjax.ajaxurl, - dataType: "json", - data: { - action: "load_search_results", - query: $(this).find("#ff_keywords_filter").val(), - nextPageToRetrieve, - valueSelected, - postType, - newsCategoryValue, - termID, - nonce_hash: nonceHash, - }, - success: function (response) { - $(".c-article-item").remove(); - $("#content").html(response.results); - $("#title-section").html(response.total); - $(".c-pagination").html(response.pagination); - }, - }); - - return false; - }); - - $(".c-pagination").on("click", function () { - $("#load_more div.data-type").addClass("shown-item"); - - const nextPageToRetrieve = $(".more-btn").data("page") + 1; - $(".more-btn").attr("data-page", nextPageToRetrieve); - - const newsCategoryValue = - $('input[name="ff_categories_filter_news-category"]:checked')?.val() ?? - 0; - - $.ajax({ - type: "post", - url: mojAjax.ajaxurl, - dataType: "json", - data: { - action: "load_search_results", - query: $(this).find("#ff_keywords_filter").val(), - valueSelected: $(".more-btn, .nomore-btn").data("date"), - nextPageToRetrieve, - postType, - newsCategoryValue, - termID, - nonce_hash: nonceHash, - }, - - success: function (response) { - $("#load_more").append(response.results); - $(".c-pagination").html(response.pagination); - $( - "#load_more div.data-type:not('.shown-item')+article div.content a", - ).focus(); - }, - }); - - return false; - }); - - $(document).on("submit", "#ff_events", function (e) { - e.preventDefault(); - - const nextPageToRetrieve = $("#ff").data("page") + 1; - $(".more-btn").attr("data-page", nextPageToRetrieve); - - $.ajax({ - type: "post", - url: mojAjax.ajaxurl, - dataType: "html", - data: { - action: "load_events_filter_results", - query: $(this).find('input[name="ff_keywords_filter"]').val(), - valueSelected: $(this).find("#ff_date_filter option:selected").val(), - postType, - termID, - nonce_hash: nonceHash, - }, - success: function (response) { - $(".c-article-item").remove(); - $("#content").html(response); - }, - }); - return false; - }); - }; -})(jQuery); diff --git a/public/app/themes/clarity/src/globals/js/script-loader.js b/public/app/themes/clarity/src/globals/js/script-loader.js index fe8e38070..741a721c0 100644 --- a/public/app/themes/clarity/src/globals/js/script-loader.js +++ b/public/app/themes/clarity/src/globals/js/script-loader.js @@ -15,7 +15,7 @@ import "../../components/c-notes-from-antonia/lazy_load.js"; // Global scripts import "../../../inc/admin/js/feedback.js"; import "./auth-heartbeat.js"; -import "./blog-content_filter.js"; +import "./ajax-filter.js"; import "./condolences-filter.js"; import "./equaliser.js"; import "./slider.js";