Home › Forums › Calendar Products › Filter Bar › Created an alternative cost filter, looking for second opinions.
- This topic has 6 replies, 3 voices, and was last updated 9 years, 8 months ago by
Jay.
-
AuthorPosts
-
September 14, 2016 at 11:15 am #1164093
Jay
ParticipantHey everyone,
I’ve created a Boston tech events calendar, and the event prices ended up being a pretty large range. From meetups with $5 pizza costs to conferences with $5000+ registration fees. I wasn’t liking that my only options for the cost filter were a range slider that wasn’t really convenient for the distribution of the costs or a checklist of price-ranges that ended up being pretty arbitrary (and didn’t cover the ranges I actually wanted).
So I added a new filter, following the process I’d seen on these forums (where the category filter was used as an example to add a custom taxonomy filter). Basically I copied the code for the Cost filter class, modified it, and added it to my functions.php with a line to initialize it:
class BSG__Cost__Tribe__Events__Filterbar__Filters__Cost extends Tribe__Events__Filterbar__Filter { const EXPLICITLY_FREE = 'set_to_0'; const IMPLICITLY_FREE = 'unset_or_0'; public $type = 'range'; public $free = self::IMPLICITLY_FREE; private $min_cost = null; private $max_cost = null; protected function settings() { parent::settings(); $this->free_logic(); } protected function free_logic() { $settings = Tribe__Events__Filterbar__View::instance()->get_filter_settings(); $this->free = isset( $settings[ $this->slug ]['free'] ) && self::EXPLICITLY_FREE === $settings[ $this->slug ]['free'] ? self::EXPLICITLY_FREE : self::IMPLICITLY_FREE; } protected function get_submitted_value() { if ( ! empty( $_REQUEST[ 'tribe_' . $this->slug ] ) ) { $value = (array) $_REQUEST[ 'tribe_' . $this->slug ]; if ( isset( $value['min'] ) && isset( $value['max'] ) ) { return array( $value ); } else { foreach ( $value as &$v ) { $range = explode( '-', $v ); if ( ! preg_match( '/[0-9]+\-[0-9]+/', $v ) ) { continue; } $v = array( 'min' => $range[0], 'max' => $range[1] ); } return $value; } } return array(); } public function get_admin_form() { $title = $this->get_title_field(); $type = $this->get_type_field(); return $title.$type; } protected function get_type_field() { $name = $this->get_admin_field_name( 'type' ); $type_field = sprintf( __( 'Type: %s %s', 'tribe-events-filter-view' ), sprintf( '<label><input type="radio" name="%s" value="range" %s /> %s</label>', $name, checked( $this->type, 'range', false ), __( 'Range Slider', 'tribe-events-filter-view' ) ), sprintf( '<label><input type="radio" name="%s" value="checkbox" %s /> %s</label>', $name, checked( $this->type, 'checkbox', false ), __( 'Checkboxes', 'tribe-events-filter-view' ) ) ); $name = $this->get_admin_field_name( 'free' ); $cost_field = sprintf( __( 'Events are considered free when cost field is: %s %s', 'tribe-events-filter-view' ), sprintf( '<label><input type="radio" name="%s" value="unset_or_0" %s /> %s</label>', $name, checked( $this->free, 'unset_or_0', false ), sprintf( __( '"%s" or empty or set to zero', 'tribe-events-filter-view' ), '<strong><abbr title="' . __( 'Used as the identifier for free Events', 'tribe-events-filter-view' ) . '">' . $this->get_free_string() . '</abbr></strong>' ) ), sprintf( '<label><input type="radio" name="%s" value="set_to_0" %s /> %s</label>', $name, checked( $this->free, 'set_to_0', false ), __( 'Only when set to zero', 'tribe-events-filter-view' ) ) ); return '<div class="tribe_events_active_filter_type_options">' . $type_field . $cost_field . '</div>'; } protected function get_free_string() { return _x( 'Free', 'Used as the identifier for free Events', 'tribe-events-filter-view' ); } protected function get_values() { $this->set_min_and_max(); if ( $this->type == 'range' ) { return array( 'min' => $this->min_cost, 'max' => $this->max_cost ); } $cost_range = array(); if ( $this->has_non_numeric_costs() ) { $cost_range['other'] = __( 'Other', 'tribe-events-filter-view' ); } if ( $this->min_cost == 0 ) { $cost_range['0-0'] = __( 'Free', 'tribe-events-filter-view' ); } if ( $this->max_cost == $this->min_cost ) { if ( $this->max_cost != 0 ) { $cost_range[ $this->min_cost . '-' . $this->max_cost ] = $this->min_cost . '-' . $this->max_cost; } } else { //hard-coding my desired price ranges $cost_range['1-49'] = 'Inexpensive (\$1 - \$49)'; $cost_range['50-249'] = 'Moderate (\$50 - \$249)'; $cost_range['250-' . $this->max_cost] = 'Professional (\$250+)'; } $values = array(); foreach ( $cost_range as $key => $cost ) { $values[] = array( 'name' => $cost, 'value' => $key, ); } return $values; } private function partition_range( $min, $max, $count ) { $range_size = $max - $min + 1; $partition_size = floor( $range_size / $count ); $partition_remainder = $range_size % $count; $partitioned = array(); $mark = $min; for ( $i = 0; $i < $count; $i++ ) { $incr = ( $i < $partition_remainder ) ? $partition_size : $partition_size - 1; $partitioned[ $i ] = array( 'min' => $mark, 'max' => $mark + $incr, ); $mark += $incr + 1; } return $partitioned; } protected function is_selected( $option ) { if ( preg_match( '/[0-9]*\-[0-9]*/', $option ) ) { $option = explode( '-', $option ); $option = array( 'min' => $option[0], 'max' => $option[1], ); } elseif ( in_array( 'other', $this->currentValue ) ) { return true; } return in_array( (array) $option, $this->currentValue ); } protected function setup_query_filters() { if ( $this->currentValue ) { $this->set_min_and_max(); } parent::setup_query_filters(); } protected function setup_join_clause() { global $wpdb; $this->joinClause = " LEFT JOIN {$wpdb->postmeta} AS cost_filter ON ({$wpdb->posts}.ID = cost_filter.post_id)"; } protected function setup_where_clause() { global $wpdb; $clauses = array(); foreach ( $this->currentValue as $value ) { $free_clause = ''; if ( isset( $value['min'] ) ) { // Should we exclude events where a cost has not been provided? $free_clause = $this->free_clause( $value['min'] ); } if ( 'other' === $value ) { $length_clause = null; if ( self::IMPLICITLY_FREE === $this->free ) { $length_clause = 'AND LENGTH( TRIM( cost_filter.meta_value ) ) > 0'; } $clause = " ( cost_filter.meta_key = '_EventCost' $length_clause AND CAST( cost_filter.meta_value AS SIGNED ) = 0 AND cost_filter.meta_value != '0' "; if ( self::EXPLICITLY_FREE !== $this->free ) { $clause .= $wpdb->prepare( ' AND LOWER(cost_filter.meta_value) != %s', strtolower( $this->get_free_string() ) ); } // Close the Conditional $clause .= ')'; $clauses[] = $clause; } elseif ( isset( $value['min'], $value['max'] ) && $value['min'] == 0 && $value['max'] == 0 ) { $blank_clause = null; if ( self::IMPLICITLY_FREE === $this->free ) { $blank_clause = " OR cost_filter.meta_value = '' OR cost_filter.meta_value IS NULL OR $free_clause "; } $clauses[] = " ( cost_filter.meta_key = '_EventCost' AND ( cost_filter.meta_value = '0' $blank_clause ) ) "; } else { $clauses[] = $wpdb->prepare( "( cost_filter.meta_key = '_EventCost' AND cost_filter.meta_value >= %d AND cost_filter.meta_value IS NOT NULL AND CAST(cost_filter.meta_value AS SIGNED) BETWEEN %d AND %d ) ", $value['min'], $value['min'], $value['max'] ); } } $this->whereClause = ' AND (' . implode( ' OR ', $clauses ) . ') '; } protected function free_clause( $min ) { global $wpdb; if ( 0 !== (int) $min ) { return 'LENGTH( TRIM( cost_filter.meta_value ) ) > 0 AND CAST( cost_filter.meta_value AS SIGNED ) > 0'; } return $wpdb->prepare( '( LENGTH( TRIM( cost_filter.meta_value ) ) > 0 AND CAST( cost_filter.meta_value AS SIGNED ) = 0 AND LOWER( cost_filter.meta_value ) = %s )', strtolower( $this->get_free_string() ) ); } private function set_min_and_max() { if ( ! isset( $this->max_cost ) || ! isset( $this->min_cost ) ) { $this->max_cost = tribe_get_maximum_cost(); $this->min_cost = tribe_has_uncosted_events() ? 0 : tribe_get_minimum_cost(); } } private function has_non_numeric_costs() { $costs = Tribe__Events__Cost_Utils::instance()->get_all_costs(); foreach ( $costs as $index => $cost ) { if ( is_numeric( $index ) ) { unset( $costs[ $index ] ); } } return ! empty( $costs ); } } $cost_filter_alternative = new BSG__Cost__Tribe__Events__Filterbar__Filters__Cost('Price', 'price');The key bit is here, where I’ve removed Tribe’s chunking code and hard-coded my own ranges:
$cost_range = array(); if ( $this->has_non_numeric_costs() ) { $cost_range['other'] = __( 'Other', 'tribe-events-filter-view' ); } if ( $this->min_cost == 0 ) { $cost_range['0-0'] = __( 'Free', 'tribe-events-filter-view' ); } if ( $this->max_cost == $this->min_cost ) { if ( $this->max_cost != 0 ) { $cost_range[ $this->min_cost . '-' . $this->max_cost ] = $this->min_cost . '-' . $this->max_cost; } } else { //hard-coding my desired price ranges $cost_range['1-49'] = 'Inexpensive (\$1 - \$49)'; $cost_range['50-249'] = 'Moderate (\$50 - \$249)'; $cost_range['250-' . $this->max_cost] = 'Professional (\$250+)'; } $values = array(); foreach ( $cost_range as $key => $cost ) { $values[] = array( 'name' => $cost, 'value' => $key, ); } return $values;Now, this works. I have the filter available within my settings, I’ve added it, and it filters correctly. The only thing that’s weird about it is the parameter that gets added to the URL has some extra percent-encoding in it: ?tribe_paged=1&tribe_event_display=list&tribe_price%5B%5D=1-49 – not a big deal, but would be great if I could fix it.
What I’m really asking for is anyone with more development experience than me (I’m a non-CS-background, copy-and-paste backend developer) to tell me if I’m doing anything wrong with this implementation? I feel like I’m repeating a lot of code, most of which I don’t know what it does, and even though it seems to be working, stuff like the URL having unexplained extra characters in it makes me wonder if there are other unintended consequences I’m missing. Have I missed any steps anywhere? Is there a better way to do what I’m doing?
September 14, 2016 at 11:29 am #1164103George
ParticipantHey Jonathan,
Thanks for reaching out.
This is an awesome customization, but we are unfortunately not able to help with writing custom code or with providing analysis/insight on your own custom code. Please read our support policy to learn more about this → https://theeventscalendar.com/knowledgebase/what-support-is-provided-for-license-holders/
I’m sorry to disappoint.
While I cannot help with the code that you have posted, one thing I can recommend is to just skip the “Cost” filter altogether and make the categories of events you’ve made (Free, Inexpensive, Moderate, Professional) actual event categories.
Then, you can manually put events in these price ranges into those categories, and allow folks to filter by the category that way.
This isn’t ideal, but may be much simpler than trying to redo the cost filter and be less problematic.
Sincerely,
GeorgePS
Despite the policies I mentioned above, I did take a gander at your code. It’s unfortunately true that you have to pretty much copy the whole class just to modify that one method, because the values for that method are not currently filterable at this time.
So, despite the additional URL characters and your concerns, the back-end code changes you’ve made are actually pretty in line with what would be required to make these sorts of front-end changes you want here. If it works and you don’t run into issues or bugs, then I can’t honestly think of many ways to improve upon the current configuration you’re using.
If you are really keen on getting a Pro to help you implement this customization as perfectly as possible, I would recommend checking out the options on our Customizations page here → http://theeventscalendar.com/customizations
September 14, 2016 at 11:43 am #1164121Jay
ParticipantThanks George! Appreciate the pointer to your recommended freelancers and may consider reaching out to one of them for a round-up review of customizations I’ve made.
And really appreciate you taking a look and affirming my approach. =)
Is it okay if I leave this thread open in case any community members want to chime in?
September 14, 2016 at 3:55 pm #1164311George
ParticipantHey Jay,
Sounds good. When it comes to leaving the thread open, I would recommend instead leaving a website contact page any interested folks can contact you through, or your twitter profile, or something.
We try to keep threads closed to make the forums more manageable—it makes a big difference.
Cheers,
GeorgeSeptember 15, 2016 at 6:59 am #1164564Jay
ParticipantSounds good. I’m fine with leaving my email here in this format: jay [@at@] jayneely [.dot.] com — if anyone wants to reach out about customizing the cost filter, please feel free to drop me a line.
September 15, 2016 at 7:47 am #1164584George
ParticipantThanks Jay. Best of luck with your project!
Cheers,
George -
AuthorPosts
- The topic ‘Created an alternative cost filter, looking for second opinions.’ is closed to new replies.
