Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 244 additions & 26 deletions classes/monthly.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,49 @@
<?php

class FacetWP_Facet_Monthly extends FacetWP_Facet {
class FacetWP_Facet_Monthly extends FacetWP_Facet implements \JsonSerializable {

/**
* Default facet field values merged on load.
*
* @var array
*/
public $field_defaults = array(
'display_mode' => 'checkboxes',
);

function __construct() {
$this->label = __( 'Monthly Archive', 'fwp' );
}

/**
* FacetWP admin passes facet types through json_encode() as FWP.facet_types.
*
* The parent FacetWP_Facet declares public $fields = array(). That empty array makes the
* FacetWP 3.9+ Vue admin treat this type as using registered field groups only: it never
* injects settings_html() (FWP.clone), and it Vue.delete()s every facet key not in the
* combined whitelist — so label_any, orderby, count, display_mode disappear.
*
* Serializing without a `fields` key restores the legacy behaviour (same as pre-extend
* FacetWP_Facet_Monthly on master): facet-settings falls back to FWP.clone + v-model parsing.
*
* @return array<string, string>
*/
public function jsonSerialize() {
return array(
'label' => $this->label,
);
}

/**
* Whether the facet uses checkbox (multi) UI.
*
* @param array $facet Facet settings.
* @return bool
*/
protected function is_checkbox_display_mode( $facet ) {
return empty( $facet['display_mode'] ) || 'select' !== $facet['display_mode'];
}

/**
* Load the available choices
*/
Expand All @@ -14,10 +52,15 @@ function load_values( $params ) {

$facet = $params['facet'];

// Where
$where_clause = $params['where_clause'];
if ( $this->is_checkbox_display_mode( $facet ) ) {
// Same as FacetWP checkboxes in OR mode: drop this facet's selection from the
// post_id constraint so every month stays listed while selecting several.
$where_clause = $this->get_where_clause( $facet );
} else {
// Select / dropdown: standard constraint (historical behaviour on master).
$where_clause = $params['where_clause'];
}

// Orderby
$orderby = 'f.facet_display_value DESC';
if ( ! empty( $facet['orderby'] ) && 'asc' === $facet['orderby'] ) {
$orderby = 'f.facet_display_value ASC';
Expand Down Expand Up @@ -48,32 +91,94 @@ function load_values( $params ) {
*/
function render( $params ) {

$facet = $params['facet'];

if ( $this->is_checkbox_display_mode( $facet ) ) {
return $this->render_checkboxes( $params );
}

return $this->render_select( $params );
}

/**
* Checkbox markup (multi-select, OR filter).
*
* @param array $params Render params.
* @return string
*/
protected function render_checkboxes( $params ) {
$output = '';
$facet = $params['facet'];
$values = (array) $params['values'];
$selected_values = (array) $params['selected_values'];

foreach ( $values as $result ) {
$is_selected = in_array( $result['facet_value'], $selected_values, true );
$classes = 'facetwp-checkbox';
if ( $is_selected ) {
$classes .= ' checked';
}

$label = ucfirst( date_i18n( 'F Y', strtotime( $result['facet_display_value'] ) ) );
$label = apply_filters(
'facetwp_facet_display_value',
$label,
array(
'selected' => $is_selected,
'facet' => $facet,
'row' => $result,
)
);

$output .= '<div class="' . esc_attr( $classes ) . '" data-value="' . esc_attr( $result['facet_value'] ) . '" role="checkbox" tabindex="0" aria-checked="' . ( $is_selected ? 'true' : 'false' ) . '">';
$output .= '<span class="facetwp-display-value">' . esc_html( $label ) . '</span>';

$output .= '</div>';
}

return $output;
}

/**
* Select markup (single choice), same behaviour as legacy master branch.
*
* @param array $params Render params.
* @return string
*/
protected function render_select( $params ) {
$output = '';
$facet = $params['facet'];
$values = (array) $params['values'];
$selected_values = (array) $params['selected_values'];

$label_any = empty( $facet['label_any'] ) ? __( 'Any', 'fwp' ) : sprintf( __( '%s', 'fwp' ), $facet['label_any'] );
$label_any = empty( $facet['label_any'] ) ? __( 'Any', 'fwp' ) : $facet['label_any'];

// Setting classes for the select element.
$select_classes = empty( $selected_values ) ? 'facetwp-monthly facetwp-monthly-default' : 'facetwp-monthly';

// Building select HTML element for the facet backend.
$output .= '<select class="' . $select_classes . '">';
$output .= '<select class="' . esc_attr( $select_classes ) . '">';
$output .= sprintf( '<option value="">%s</option>', esc_html( $label_any ) );

foreach ( $values as $result ) {
$selected = in_array( $result['facet_value'], $selected_values ) ? ' selected' : '';
$selected = in_array( $result['facet_value'], $selected_values, true ) ? ' selected' : '';

$display_value = date_i18n( 'F Y', strtotime( $result['facet_display_value'] ) );
// Determine whether to show counts
$display_value = apply_filters(
'facetwp_facet_display_value',
$display_value,
array(
'selected' => ( '' !== $selected ),
'facet' => $facet,
'row' => $result,
)
);

$show_counts = apply_filters( 'facetwp_facet_dropdown_show_counts', true, $params );
if ( $show_counts ) {
$display_value .= sprintf( ' (%s)', $result['counter'] );
}

$output .= sprintf(
'<option value="%s" %s>%s</option>',
'<option value="%s"%s>%s</option>',
esc_attr( $result['facet_value'] ),
$selected,
esc_html( $display_value )
Expand All @@ -91,10 +196,63 @@ function render( $params ) {
function filter_posts( $params ) {
global $wpdb;

$output = array();
$facet = $params['facet'];
$selected_values = $params['selected_values'];

if ( $this->is_checkbox_display_mode( $facet ) ) {
return $this->filter_posts_checkboxes( $facet, $selected_values );
}

return $this->filter_posts_select( $facet, $selected_values );
}

/**
* OR filter across multiple months (checkbox mode).
*
* @param array $facet Facet settings.
* @param array $selected_values Selected values.
* @return array|string
*/
protected function filter_posts_checkboxes( $facet, $selected_values ) {
global $wpdb;

$sql_parts = array();
foreach ( $selected_values as $val ) {
$dates = explode( '-', $val );
if ( count( $dates ) === 2 ) {
$sql_parts[] = $wpdb->prepare(
'(YEAR(facet_value) = %d AND MONTH(facet_value) = %d)',
absint( $dates[0] ),
absint( $dates[1] )
);
}
}

if ( empty( $sql_parts ) ) {
return 'continue';
}

$where = implode( ' OR ', $sql_parts );

$sql = "
SELECT DISTINCT post_id
FROM {$wpdb->prefix}facetwp_index
WHERE facet_name = %s AND ($where)
ORDER BY facet_value DESC";

return $wpdb->get_col( $wpdb->prepare( $sql, $facet['name'] ) );
}

/**
* Single month filter (select mode, legacy master).
*
* @param array $facet Facet settings.
* @param array $selected_values Selected values.
* @return array|string
*/
protected function filter_posts_select( $facet, $selected_values ) {
global $wpdb;

// Convert the selected value into an array
// "0000-00" => array( year, month );
$dates = explode( '-', reset( $selected_values ) );
Expand All @@ -103,7 +261,8 @@ function filter_posts( $params ) {
$dates = array( date( 'Y' ), date( 'm' ) );
}

$sql = $wpdb->prepare( "SELECT DISTINCT post_id
$sql = $wpdb->prepare(
"SELECT DISTINCT post_id
FROM {$wpdb->prefix}facetwp_index
WHERE facet_name = %s
AND YEAR(facet_value) = %d
Expand All @@ -114,15 +273,31 @@ function filter_posts( $params ) {
absint( $dates[1] )
);

$output = $wpdb->get_col( $sql );

return $output;
return $wpdb->get_col( $sql );
}

/**
* Output admin settings HTML
*/
function settings_html() { ?>
function settings_html() {
?>
<div class="facetwp-row">
<div>
<?php _e( 'Display as', 'fwp' ); ?>:
<div class="facetwp-tooltip">
<span class="icon-question">?</span>
<div class="facetwp-tooltip-content">
<?php _e( 'Checkboxes allow multiple months; dropdown allows a single month.', 'fwp' ); ?>
</div>
</div>
</div>
<div>
<select class="facet-display-mode">
<option value="checkboxes" selected><?php esc_html_e( 'Checkboxes', 'fwp' ); ?></option>
<option value="select"><?php _e( 'Dropdown', 'fwp' ); ?></option>
</select>
</div>
</div>
<div class="facetwp-row">
<div>
<?php _e( 'Default label', 'fwp' ); ?>:
Expand Down Expand Up @@ -164,53 +339,96 @@ function settings_html() { ?>
</div>
<div><input type="text" class="facet-count" value="10"/></div>
</div>
<?php }
<?php
}

/**
* Output any admin scripts
*/
function admin_scripts() { ?>
function admin_scripts() {

?>
<script>
(function ($) {
FWP.hooks.addAction('facetwp/load/monthly', function ($this, obj) {
$this.find('.facet-source').val(obj['source']);
$this.find('.type-monthly .facet-display-mode').val(obj['display_mode'] || 'checkboxes');
$this.find('.type-monthly .facet-label-any').val(obj['label_any']);
$this.find('.type-monthly .facet-orderby').val(obj['orderby']);
$this.find('.type-monthly .facet-count').val(obj['count']);
});

FWP.hooks.addFilter('facetwp/save/monthly', function ($this, obj) {
obj['source'] = $this.find('.facet-source').val();
obj['display_mode'] = $this.find('.type-monthly .facet-display-mode').val();
obj['label_any'] = $this.find('.type-monthly .facet-label-any').val();
obj['orderby'] = $this.find('.type-monthly .facet-orderby').val();
obj['count'] = $this.find('.type-monthly .facet-count').val();
return obj;
});
})(jQuery);
</script>
<?php }
<?php
}

/**
* Output any front-end scripts
*/
function front_scripts() { ?>
function front_scripts() {

?>
<script>
(function ($) {
FWP.hooks.addAction('facetwp/refresh/monthly', function ($this, facet_name) {
var val = $this.find('.facetwp-monthly').val();
FWP.facets[facet_name] = val ? [val] : [];
var $select = $this.find('select.facetwp-monthly');
var $checkboxes = $this.find('.facetwp-checkbox');
if ($select.nodes.length > 0) {
var val = $select.val();
FWP.facets[facet_name] = val ? [val] : [];
} else if ($checkboxes.nodes.length > 0) {
var selected = [];
$this.find('.facetwp-checkbox.checked').each(function () {
selected.push($(this).attr('data-value'));
});
FWP.facets[facet_name] = selected;
}
});

function monthlyToggle($cb) {
var $facet = $cb.closest('.facetwp-facet');
$cb.toggleClass('checked');
$cb.attr('aria-checked', $cb.hasClass('checked') ? 'true' : 'false');

if ($facet.find('.facetwp-checkbox.checked').length) {
FWP.static_facet = $facet.attr('data-name');
}
FWP.autoload();
}

FWP.hooks.addAction('facetwp/ready', function () {
$(document).on('change', '.facetwp-facet .facetwp-monthly', function () {
var $facet = $(this).closest('.facetwp-facet');
var isDefault = $facet.find(':selected').val() === '';
if (!isDefault) FWP.static_facet = $facet.attr('data-name');
$facet.find('select').toggleClass('facetwp-monthly-default', isDefault)
if (!isDefault) {
FWP.static_facet = $facet.attr('data-name');
}
$facet.find('select').toggleClass('facetwp-monthly-default', isDefault);
FWP.autoload();
});

$(document).on('click', '.facetwp-type-monthly .facetwp-checkbox', function (e) {
e.preventDefault();
monthlyToggle($(this));
});
$(document).on('keydown', '.facetwp-type-monthly .facetwp-checkbox', function (e) {
if (32 === e.which || 13 === e.which) {
e.preventDefault();
monthlyToggle($(this));
}
});
});
})(jQuery);
</script>
<?php }
<?php
}
}
Loading