gs' => 'tag', 'tags' => 'tag', 'exclude-event_tag' => 'exclude-tag', 'exclude-event_tags' => 'exclude-tag', 'exclude-post_tag' => 'exclude-tag', 'exclude-post_tags' => 'exclude-tag', 'exclude-tags' => 'exclude-tag', 'start_date' => 'date', 'week_layout' => 'layout', ]; /** * Toggles the filtering of URLs to match the place where. * We tend to hook into P15 to allow other things to happen before shortcode. * * @since 4.7.5 * * @param bool $toggle Whether to turn the hooks on or off. * * @return void */ protected function toggle_view_hooks( $toggle ) { if ( $toggle ) { $this->add_shortcode_hooks(); } else { $this->remove_shortcode_hooks(); } /** * Fires after View hooks have been toggled while rendering a shortcode. * * @since 5.0.0 * * @param bool $toggle Whether the hooks should be turned on or off. This value is `true` before a shortcode * HTML is rendered and `false` after the shortcode HTML rendered. * @param static $this The shortcode object that is toggling the View hooks. */ do_action( 'tribe_events_pro_shortcode_toggle_view_hooks', $toggle, $this ); } /** * Toggles off portions of the template based on shortcode params. * This runs on the `tribe_events_pro_shortcode_toggle_view_hooks` hook when the toggle is true. * * @since 5.5.0 */ protected function add_shortcode_hooks() { add_filter( 'tribe_events_views_v2_url_query_args', [ $this, 'filter_view_query_args' ], 15, 3 ); add_filter( 'tribe_events_views_v2_view_repository_args', [ $this, 'filter_view_repository_args' ], 10, 3 ); add_filter( 'tribe_events_views_v2_view_html_classes', [ $this, 'filter_view_html_classes' ], 10, 3 ); add_filter( 'tribe_events_views_v2_view_container_data', [ $this, 'filter_view_data' ], 10, 3 ); add_filter( 'tribe_events_views_v2_view_url_query_args', [ $this, 'filter_view_url_query_args' ], 10, 3 ); add_filter( 'tribe_events_views_v2_view_context', [ $this, 'filter_view_context' ], 10, 3 ); add_filter( 'tribe_events_views_v2_manager_default_view', [ $this, 'filter_default_url' ] ); add_filter( 'tribe_events_views_v2_view_url', [ $this, 'filter_view_url' ], 10, 3 ); add_filter( 'tribe_events_views_v2_view_next_url', [ $this, 'filter_view_url' ], 10, 3 ); add_filter( 'tribe_events_views_v2_view_prev_url', [ $this, 'filter_view_url' ], 10, 3 ); add_filter( 'tribe_events_views_v2_view_week_breakpoints', [ $this, 'filter_week_view_breakpoints' ], 10, 2 ); add_filter( 'tribe_events_views_v2_ff_link_next_event', [ $this, 'filter_ff_link_next_event' ], 10, 2 ); // Removing tribe-bar when that argument is `false`. if ( ! tribe_is_truthy( $this->get_argument( 'tribe-bar' ) ) || tribe_is_truthy( $this->get_argument( 'is-widget' ) ) ) { add_filter( 'tribe_template_html:events/v2/components/events-bar', '__return_false' ); } // Removing export button when that argument is `true`. if ( tribe_is_truthy( $this->get_argument( 'hide-export' ) ) || tribe_is_truthy( $this->get_argument( 'is-widget' ) ) ) { add_filter( 'tribe_template_html:events/v2/components/ical-link', '__return_false' ); } /* Filter Bar */ if ( ! tribe_is_truthy( $this->get_argument( 'filter-bar' ) ) || ! tribe_is_truthy( $this->get_argument( 'tribe-bar' ) ) || tribe_is_truthy( $this->get_argument( 'is-widget' ) ) ) { add_filter( 'tribe_events_filter_bar_views_v2_should_display_filters', '__return_false' ); add_filter( 'tribe_events_filter_bar_views_v2_1_should_display_filters', '__return_false' ); add_filter( 'tribe_events_filter_bar_views_v2_assets_should_enqueue_frontend', '__return_false' ); add_filter( 'tribe_events_views_v2_filter_bar_view_html_classes', '__return_false' ); if ( tribe()->isBound( 'filterbar.views.v2_1.hooks' ) ) { remove_filter( 'tribe_events_pro_shortcode_tribe_events_before_assets', [ tribe( 'filterbar.views.v2_1.hooks' ), 'action_include_assets' ] ); } else if ( tribe()->isBound( 'filterbar.views.v2.hooks' ) ) { remove_filter( 'tribe_events_pro_shortcode_tribe_events_before_assets', [ tribe( 'filterbar.views.v2.hooks' ), 'action_include_assets' ] ); } } /* Month widget only. */ if ( \Tribe\Events\Views\V2\Views\Month_View::get_view_slug() === $this->get_argument( 'view' ) && tribe_is_truthy( $this->get_argument( 'is-widget' ) ) ) { /* Mobile "footer" nav */ add_filter( 'tribe_template_html:events/v2/month/mobile-events/nav', '__return_false' ); } /* Week view & widget only. */ $view = (string) $this->get_argument( 'view' ); if ( 0 === stripos( $view, \Tribe\Events\Pro\Views\V2\Views\Week_View::get_view_slug() ) ) { // Allows for the "hide_weekends" attribute. if ( tribe_is_truthy( $this->get_argument( 'hide_weekends' ) ) ) { add_filter( 'tribe_get_option', [ $this, 'week_view_hide_weekends' ], 10, 2 ); } add_filter( 'tribe_events_views_v2_week_events_per_day', [ $this, 'filter_week_events_per_day' ], 10, 2 ); } // Removing datepicker when that argument is `true`. if ( tribe_is_truthy( $this->get_argument( 'hide-datepicker' ) ) || tribe_is_truthy( $this->get_argument( 'is-widget' ) ) ) { add_filter( 'tribe_template_html:events/v2/month/top-bar/datepicker', '__return_false' ); add_filter( 'tribe_template_html:events-pro/v2/week/top-bar/datepicker', '__return_false' ); } if ( ! tribe_is_truthy( $this->get_argument( 'jsonld' ) ) ) { add_filter( 'tribe_template_html:events/v2/components/json-ld-data', '__return_false' ); } // Past Events - don't navigate to empty months. if ( tribe_is_truthy( $this->get_argument( 'past', false ) ) ) { add_filter( 'tribe_events_views_v2_month_nav_skip_empty', [ $this, 'filter_skip_empty' ] ); } } /** * Hide weekends on shortcode. * * @since 5.6.0 * * @param mixed $value The value for the option. * @param string $option_name The name of the option. * * @return mixed The value for the option. */ public function week_view_hide_weekends( $value, $option_name ) { if ( 'week_view_hide_weekends' !== $option_name ) { return $value; } return true; } /** * Toggles on portions of the template that were toggled off in `template_removes()` above. * This runs on the `tribe_events_pro_shortcode_toggle_view_hooks` hook when the toggle is false. * Thus encapsulating our control of these shared pieces to only when the shortcode is rendering. * * @since 5.5.0 */ protected function remove_shortcode_hooks() { remove_filter( 'tribe_events_views_v2_url_query_args', [ $this, 'filter_view_query_args' ], 15 ); remove_filter( 'tribe_events_views_v2_view_repository_args', [ $this, 'filter_view_repository_args' ], 10 ); remove_filter( 'tribe_events_views_v2_view_html_classes', [ $this, 'filter_view_html_classes' ], 10 ); remove_filter( 'tribe_events_views_v2_view_container_data', [ $this, 'filter_view_data' ], 10 ); remove_filter( 'tribe_events_views_v2_view_url_query_args', [ $this, 'filter_view_url_query_args' ], 10 ); remove_filter( 'tribe_events_views_v2_view_context', [ $this, 'filter_view_context' ], 10 ); remove_filter( 'tribe_events_views_v2_manager_default_view', [ $this, 'filter_default_url' ] ); remove_filter( 'tribe_events_views_v2_view_url', [ $this, 'filter_view_url' ], 10 ); remove_filter( 'tribe_events_views_v2_view_next_url', [ $this, 'filter_view_url' ], 10 ); remove_filter( 'tribe_events_views_v2_view_prev_url', [ $this, 'filter_view_url' ], 10 ); remove_filter( 'tribe_template_html:events/v2/components/events-bar', '__return_false' ); // tribe-bar. remove_filter( 'tribe_template_html:events/v2/components/ical-link', '__return_false' ); // hide-export. remove_filter( 'tribe_template_html:events/v2/month/top-bar/datepicker', '__return_false' ); // hide-datepicker. remove_filter( 'tribe_template_html:events-pro/v2/week/top-bar/datepicker', '__return_false' ); // hide-datepicker. // Filter Bar. remove_filter( 'tribe_events_filter_bar_views_v2_should_display_filters', '__return_false' ); remove_filter( 'tribe_events_filter_bar_views_v2_1_should_display_filters', '__return_false' ); remove_filter( 'tribe_events_filter_bar_views_v2_assets_should_enqueue_frontend', '__return_false' ); remove_filter( 'tribe_events_views_v2_filter_bar_view_html_classes', '__return_false' ); // Yes, add - we're adding it back. if ( tribe()->isBound( 'filterbar.views.v2_1.hooks' ) ) { add_filter( 'tribe_events_pro_shortcode_tribe_events_before_assets', [ tribe( 'filterbar.views.v2_1.hooks' ), 'action_include_assets' ] ); } elseif ( tribe()->isBound( 'filterbar.views.v2.hooks' ) ) { add_filter( 'tribe_events_pro_shortcode_tribe_events_before_assets', [ tribe( 'filterbar.views.v2.hooks' ), 'action_include_assets' ] ); } remove_filter( 'tribe_get_option', [ $this, 'week_view_hide_weekends' ] ); remove_filter( 'tribe_events_views_v2_view_week_breakpoints', [ $this, 'filter_week_view_breakpoints' ], 10 ); remove_filter( 'tribe_events_views_v2_week_events_per_day', [ $this, 'views_v2_week_events_per_day' ], 10 ); remove_filter( 'tribe_events_views_v2_ff_link_next_event', [ $this, 'filter_ff_link_next_event' ], 10 ); // Past Events - don't navigate to empty months. remove_filter( 'tribe_events_views_v2_month_nav_skip_empty', [ $this, 'filter_skip_empty' ] ); } /** * Maybe toggles the hooks for this shortcode class on a rest request. * * @since 5.5.0 * * @param string $slug The current view Slug. * @param array $params Params so far that will be used to build this view. * @param \WP_REST_Request $request The rest request that generated this call. */ public static function maybe_toggle_hooks_for_rest( $slug, $params, \WP_REST_Request $request ) { $shortcode = Arr::get( $params, 'shortcode', false ); // Bail when not a shortcode request. if ( ! $shortcode ) { return; } $shortcode_instance = new static(); $db_args = $shortcode_instance->get_database_arguments( $shortcode ); // When no params were found it means it's not a valid Shortcode. if ( empty( $db_args ) ) { return; } $shortcode_instance->setup( $db_args, '' ); $shortcode_instance->toggle_view_hooks( true ); } /** * Verifies if in this Shortcode we should allow View URL management. * * @since 4.7.5 * * @return bool */ public function should_manage_url() { // Defaults to true due to old behaviors on Views V1. $should_manage_url = $this->get_argument( 'should_manage_url', $this->default_arguments['should_manage_url'] ); $disallowed_locations = [ 'widget_text_content', ]; /** * Allows filtering of the disallowed locations for URL management. * * @since 4.7.5 * * @param mixed $disallowed_locations Which filters we don't allow URL management. * @param static $instance Which instance of shortcode we are dealing with. */ $disallowed_locations = apply_filters( 'tribe_events_pro_shortcode_tribe_events_manage_url_disallowed_locations', $disallowed_locations, $this ); // Block certain locations. foreach ( $disallowed_locations as $location ) { // If any we are in any of the disallowed locations. if ( doing_filter( $location ) ) { $should_manage_url = $this->default_arguments['should_manage_url']; } } /** * Allows filtering if a shortcode URL management is active. * * @since 4.7.5 * * @param mixed $should_manage_url Should we manage the URL for this views shortcode instance. * @param static $instance Which instance of shortcode we are dealing with. */ $should_manage_url = apply_filters( 'tribe_events_pro_shortcode_tribe_events_should_manage_url', $should_manage_url, $this ); return $should_manage_url; } /** * @inheritDoc * * @since 5.5.0 * * @return array List of validated arguments mapping. */ public function get_validated_arguments_map() { $map = parent::get_validated_arguments_map(); $map['category'] = static function ( $terms ) { return Taxonomy::normalize_to_term_ids( $terms, TEC::TAXONOMY ); }; $map['exclude-category'] = static function ( $terms ) { return Taxonomy::normalize_to_term_ids( $terms, TEC::TAXONOMY ); }; $map['tag'] = static function ( $terms ) { return Taxonomy::normalize_to_term_ids( $terms, 'post_tag' ); }; $map['exclude-tag'] = static function ( $terms ) { return Taxonomy::normalize_to_term_ids( $terms, 'post_tag' ); }; return $map; } /** * Changes the URL to match the Shortcode if needed. * * @since 4.7.5 * * @param array $query_args Current URL for this view. * * @return array The filtered View query args, with the shortcode ID added. */ public function filter_view_query_args( $query_args ) { // Always add the id of the shortcode to the URLs. $query_args['shortcode'] = $this->get_id(); return $query_args; } /** * Fetches from the database the params of a given shortcode based on the ID created. * * @since 4.7.9 * * @param string $shortcode_id The shortcode identifier, or `null` to use the current one. * * @return array Array of params configuring the Shortcode. */ public function get_database_arguments( $shortcode_id = null ) { $shortcode_id = $shortcode_id ?: $this->get_id(); $transient_key = static::TRANSIENT_PREFIX . $shortcode_id; $transient_arguments = get_transient( $transient_key ); return (array) $transient_arguments; } /** * Configures the Relationship between shortcode ID and their params in the database * allowing us to pass the URL as the base for the Queries. * * @since 4.7.9 * * @return bool Return if we have the arguments configured or not. */ public function set_database_params() { $shortcode_id = $this->get_id(); $transient_key = static::TRANSIENT_PREFIX . $shortcode_id; $db_arguments = $this->get_database_arguments(); $db_arguments['id'] = $shortcode_id; // If the value is the same it's already in the Database. if ( $db_arguments === $this->get_arguments() ) { return true; } return set_transient( $transient_key, $this->get_arguments() ); } /** * Alters the shortcode context with its arguments. * * @since 4.7.9 * * @param \Tribe__Context $context Context we will use to build the view. * @param array $arguments Arguments to be used to alter the context. * * @return \Tribe__Context Context after shortcodes changes. */ public function alter_context( Context $context, array $arguments = [] ) { $shortcode_id = $context->get( 'id' ); if ( empty( $arguments ) ) { $arguments = $this->get_arguments(); $shortcode_id = $this->get_id(); } $alter_context = $this->args_to_context( $arguments, $context ); // The View will consume this information on initial state. $alter_context['shortcode'] = $shortcode_id; $alter_context['id'] = $shortcode_id; $context = $context->alter( $alter_context ); return $context; } /** * Based on the either a argument "id" of the shortcode definition * or the 8 first characters of the hashed version of a string serialization * of the params sent to the shortcode we will create/get an ID for this * instance of the tribe_events shortcode * * @since 4.7.9 * * @return string The shortcode unique(ish) identifier. */ public function get_id() { $arguments = $this->get_arguments(); // In case we have the ID argument we just return that. if ( ! empty( $arguments['id'] ) ) { return $arguments['id']; } // @todo: We hates it, my precious - find a better way. if ( is_array( $arguments ) ) { ksort( $arguments ); } /* * Generate a string id based on the arguments used to setup the shortcode. * Note that arguments are sorted to catch substantially same shortcode w. diff. order argument. */ return substr( md5( maybe_serialize( $arguments ) ), 0, 8 ); } /** * Should not be used carelessly this will remove all request based locations from the read of the context. * Which if not used properly will break all other uses of the context unless it's a shortcode. * * @since 5.5.0 * * @deprecated 7.4.4 * * @param array $locations An array of read and write location in the shape of the `Context::$locations` one, * `[ => [ 'read' => , 'write' => ] ]`. * @param Context $context Instance of the context. * * @return array Locations after removing the request based ones. */ public function remove_request_based_context_locations( array $locations, Context $context ) { return $locations; } /** * Allows us to print Scripts and Styles inside of the template but outside of the container. * Preventing Shortcodes from failing on Block Editor saving. * * Hooked to `tribe_template_before_include@P15` and will unhook itself after the first template called. * * @since 5.5.0 * * @param string $file Complete path to include the PHP File. * @param array $name Template name. * @param \Tribe__Template $template Current instance of the Tribe__Template. */ public function enqueue_assets_before_template( $file = null, $name = null, $template = null ) { // Prevent other templates from triggering this. if ( ! $template instanceof \Tribe\Events\Views\V2\Template ) { return; } /* * Make sure we aren't triggered before we expect to be. * If this isn't true, our query info will be suspect and our checks will fail. */ if ( ! ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) && ! did_action( 'wp_print_scripts' ) ) { return; } /** * Triggers an action to allow other plugins or extensions to load assets. * * @since 4.7.9 * * @param self $shortcode Instance of this class. */ do_action( 'tribe_events_pro_shortcode_tribe_events_before_assets', $this ); // Make sure to enqueue assets. tribe_asset_enqueue_group( Pro_Assets::$group_key ); tribe_asset_enqueue_group( Event_Assets::$group_key ); /** * Triggers an action to allow other plugins or extensions to load assets. * * @since 4.7.9 * * @param self $shortcode Instance of this class. */ do_action( 'tribe_events_pro_shortcode_tribe_events_after_assets', $this ); // This action once triggered removes itself. remove_action( 'tribe_template_before_include', [ $this, 'enqueue_assets_before_template' ], 15 ); } /** * Determines if we should display the shortcode in a given page. * * @since 5.9.0 * * @return mixed|void */ public function should_display() { /** * On blocks editor shortcodes are being rendered in the screen which for some unknown reason makes the admin * URL soft redirect (browser history only) to the front-end view URL of that shortcode. * * @see TEC-3157 */ $should_display = true; /** * If we should display the shortcode. * * @since 5.9.0 * * @param bool $should_display Whether we should display or not. * @param static $shortcode Instance of the shortcode we are dealing with. */ $should_display = apply_filters( 'tribe_events_shortcode_tribe_events_should_display', $should_display, $this ); return tribe_is_truthy( $should_display ); } /** * {@inheritDoc} */ public function get_html() { if ( ! $this->should_display() ) { return ''; } $context = tribe_context(); // Before anything happens we set a DB ID and value for this shortcode entry. $this->set_database_params(); // Modifies the Context for the shortcode params. $context = $this->alter_context( $context ); $context->disable_read_from( [ Context::REQUEST_VAR, Context::QUERY_VAR, Context::WP_MATCHED_QUERY, Context::WP_PARSED ] ); // Fetches if we have a specific view are building. $view_slug = $this->get_argument( 'view', $context->get( 'view' ) ); // Toggle the shortcode required modifications. $this->toggle_view_hooks( true ); $shortcode_object = $this; add_filter( 'tribe_events_views_v2_view_cached_html', static function ( $cached_html, $view ) use ( $shortcode_object ) { $shortcode_object->enqueue_assets_before_template( null, null, $view->get_template() ); return $cached_html; }, 15, 2 ); add_action( 'tribe_template_before_include', [ $this, 'enqueue_assets_before_template' ], 15, 3 ); // Setup the view instance. $view = View::make( $view_slug, $context ); // Setup whether this view should manage url or not. $view->get_template()->set( 'should_manage_url', $this->should_manage_url() ); $theme_compatibility = tribe( Theme_Compatibility::class ); $html = ''; /** * Allows removing the compatibility container. * * @since 5.5.0 * * @param bool $compatibility_required Is compatibility required for this shortcode. * @param static $shortcode Shortcode instance that is being rendered. */ $compatibility_required = apply_filters( 'tribe_events_pro_shortcode_compatibility_required', $theme_compatibility->is_compatibility_required(), $this ); if ( $compatibility_required ) { $container = [ 'tribe-compatibility-container' ]; $classes = array_merge( $container, $theme_compatibility::get_compatibility_classes() ); $element_classes = new Element_Classes( $classes ); $html .= '
get_attribute() . '>'; } $html .= $view->get_html(); if ( $compatibility_required ) { $html .= '
'; } // Toggle the shortcode required modifications. $this->toggle_view_hooks( false ); return $html; } /** * Filters the View repository args to add the ones required by shortcodes to work. * * @since 4.7.9 * @since 7.7.1 Add support for shortcode ID in context. * * @param array $repository_args An array of repository arguments that will be set for all Views. * @param \Tribe__Context $context The current render context object. * @param View_Interface $view The View that will use the repository arguments. * * @return array Repository arguments after shortcode args added. */ public function filter_view_repository_args( $repository_args, $context, $view ) { if ( ! $context instanceof Context ) { return $repository_args; } $shortcode_id = $context->get( 'shortcode', false ); // If no shortcode ID in context and not in AJAX/REST request, bail early. if ( false === $shortcode_id && ! tribe( 'context' )->doing_ajax() && ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) ) { return $repository_args; } // If no shortcode ID in context, try to get it from URL parameters. if ( false === $shortcode_id ) { $shortcode_id = tribe_get_query_var( 'shortcode', false ); } if ( false === $shortcode_id ) { return $repository_args; } $shortcode_args = $this->get_database_arguments( $shortcode_id ); $repository_args = $this->args_to_repository( (array) $repository_args, (array) $shortcode_args, $context, $view ); return $repository_args; } /** * Filters the context locations to add the ones used by Shortcodes. * * @since 4.7.9 * @since 5.5.0 Transform into static method to avoid creating instance when not using. * * @since 5.5.0 Move this to a method inside of Shortcodes|Tribe_Events * * @param array $locations The array of context locations. * * @return array The modified context locations. */ public static function filter_context_locations( array $locations = [] ) { $locations['shortcode'] = [ 'read' => [ Context::REQUEST_VAR => 'shortcode', Context::LOCATION_FUNC => [ 'view_prev_url', static function ( $url ) { return tribe_get_query_var( $url, 'shortcode', Context::NOT_FOUND ); }, ], ], ]; return $locations; } /** * Translates shortcode arguments to their Context argument counterpart. * * @since 4.7.9 * * @param array $arguments The shortcode arguments to translate. * @param Context $context The request context. * * @return array The translated shortcode arguments. */ protected function args_to_context( array $arguments, Context $context ) { $context_args = []; if ( ! empty( $arguments['date'] ) ) { $context_args['event_date'] = $arguments['date']; } if ( isset( $arguments['featured'] ) ) { $context_args['featured'] = tribe_is_truthy( $arguments['featured'] ); } if ( ! empty( $arguments['events_per_page'] ) ) { $context_args['events_per_page'] = (int) $arguments['events_per_page']; } if ( tribe_is_truthy( $arguments['is-widget'] ) ) { $context_args['is-widget'] = tribe_is_truthy( $arguments['is-widget'] ); } if ( ! empty( $arguments['month_events_per_day'] ) ) { $context_args['month_posts_per_page'] = (int) $arguments['month_events_per_day']; } if ( ! empty( $arguments['week_events_per_day'] ) ) { $context_args['week_events_per_day'] = (int) $arguments['week_events_per_day']; } if ( ! empty( $arguments['keyword'] ) ) { $context_args['keyword'] = sanitize_text_field( $arguments['keyword'] ); } if ( null === $context->get( 'eventDisplay' ) ) { if ( ! empty( $arguments['past'] ) && tribe_is_truthy( $arguments['past'] ) ) { $month_slug = Month_View::get_view_slug(); $manager = tribe( Views_Manager::class ); $views = $manager->get_publicly_visible_views(); $view_slug = ! empty( $views[ $month_slug ] ) ? $month_slug : $manager->get_default_view_slug(); $context_args['view'] = $view_slug; $context_args['event_display_mode'] = $view_slug; } elseif ( empty( $arguments['view'] ) ) { $default_view_class = tribe( Views_Manager::class )->get_default_view(); $context_args['view'] = $context_args['event_display_mode'] = tribe( Views_Manager::class )->get_view_slug_by_class( $default_view_class ); } else { $context_args['view'] = $context_args['event_display_mode'] = $arguments['view']; } } if ( ! empty( $arguments['past'] ) && tribe_is_truthy( $arguments['past'] ) ) { $context_args['past'] = tribe_is_truthy( $arguments['past'] ); $context_args['ends_before'] = tribe_end_of_day( current_time( 'mysql' ) ); $context_args['latest_event_date'] = tribe_end_of_day( current_time( 'mysql' ) ); // Make sure this isn't set to avoid logic conflicts. unset( $context_args['starts_after'] ); } return $context_args; } /** * Translates shortcode arguments to their Repository argument counterpart. * * @since 4.7.9 * * @param array $repository_args The current repository arguments. * @param array $arguments The shortcode arguments to translate. * @param Context $context The shortcode arguments to translate. * @param View_Interface $view The View that will use the repository arguments. * * @return array The translated shortcode arguments. */ public function args_to_repository( array $repository_args, array $arguments, $context, $view ) { if ( ! empty( $arguments['tag'] ) || ! empty( $arguments['category'] ) ) { $operand = Arr::get( $arguments, 'tax-operand', 'OR' ); // Makes sure tax query exists. if ( empty( $repository_args['tax_query'] ) ) { $repository_args['tax_query'] = []; } $items = [ 'tag' => 'post_tag', 'category' => TEC::TAXONOMY, ]; foreach ( $items as $key => $taxonomy ) { if ( empty( $arguments[ $key ] ) ) { continue; } $repository_args['tax_query'] = Arr::merge_recursive_query_vars( $repository_args['tax_query'], Taxonomy::translate_to_repository_args( $taxonomy, $arguments[ $key ], $operand ) ); } $repository_args['tax_query']['relation'] = $operand; } if ( ! empty( $arguments['exclude-tag'] ) || ! empty( $arguments['exclude-category'] ) ) { $operand = 'AND'; // Makes sure tax query exists. if ( empty( $repository_args['tax_query'] ) ) { $repository_args['tax_query'] = []; } $items = [ 'exclude-tag' => 'post_tag', 'exclude-category' => TEC::TAXONOMY, ]; foreach ( $items as $key => $taxonomy ) { if ( empty( $arguments[ $key ] ) ) { continue; } $repo = tribe_events(); $repo->by( 'term_not_in', $taxonomy, $arguments[ $key ] ); $built_query = $repo->build_query(); if ( ! empty( $built_query->query_vars['tax_query'] ) ) { $repository_args['tax_query'] = Arr::merge_recursive_query_vars( $repository_args['tax_query'], $built_query->query_vars['tax_query'] ); } } $repository_args['tax_query']['relation'] = $operand; } if ( isset( $arguments['date'] ) ) { // The date can be used in many ways, so we juggle a bit here. $date_filters = tribe_events()->get_date_filters(); $date_keys = array_filter( $repository_args, static function ( $key ) use ( $date_filters ) { return in_array( $key, $date_filters, true ); }, ARRAY_FILTER_USE_KEY ); if ( count( $date_keys ) === 1 ) { $date_indices = array_keys( $date_keys ); $date_index = reset( $date_indices ); $date_key = $date_keys[ $date_index ]; if ( $date_key === $arguments['date'] ) { // Let's only set it if we are sure. $repository_args[ $date_index ] = $arguments['date']; } else { $repository_args[ $date_index ] = $date_key; } } } if ( ! empty( $arguments['author'] ) ) { if ( ! is_numeric( $arguments['author'] ) ) { $author = get_user_by( 'login', $arguments['author'] ); } else { $author = get_user_by( 'id', $arguments['author'] ); } if ( empty( $author->ID ) ) { // -1, 0, and strings all prevent excluding posts by author. Using PHP_INT_MAX appropriately causes the filter to function. $repository_args['author'] = PHP_INT_MAX; } else { $repository_args['author'] = $author->ID; } } if ( ! empty( $arguments['organizer'] ) ) { if ( ! is_numeric( $arguments['organizer'] ) ) { $organizer_id = tribe_organizers() ->where( 'title', $arguments['organizer'] ) ->per_page( 1 ) ->fields( 'ids' ) ->first(); if ( empty( $organizer_id ) ) { $organizer_id = tribe_organizers() ->where( 'name', $arguments['organizer'] ) ->per_page( 1 ) ->fields( 'ids' ) ->first(); } } else { $organizer_id = $arguments['organizer']; } if ( empty( $organizer_id ) ) { $repository_args['organizer'] = -1; } else { $repository_args['organizer'] = $organizer_id; } } if ( ! empty( $arguments['venue'] ) ) { if ( ! is_numeric( $arguments['venue'] ) ) { $venue_id = tribe_venues() ->where( 'title', $arguments['venue'] ) ->per_page( 1 ) ->fields( 'ids' ) ->first(); if ( empty( $venue_id ) ) { $venue_id = tribe_venues() ->where( 'name', $arguments['venue'] ) ->per_page( 1 ) ->fields( 'ids' ) ->first(); } } else { $venue_id = $arguments['venue']; } if ( empty( $venue_id ) ) { $repository_args['venue'] = -1; } else { $repository_args['venue'] = $venue_id; } } if ( isset( $arguments['featured'] ) ) { $repository_args['featured'] = tribe_is_truthy( $arguments['featured'] ); } if ( isset( $arguments['past'] ) && tribe_is_truthy( $arguments['past'] ) ) { $repository_args['past'] = tribe_is_truthy( $arguments['past'] ); $repository_args['ends_before'] = tribe_end_of_day( current_time( 'mysql' ) ); // Make sure this isn't set to avoid logic conflicts. unset( $repository_args['starts_after'] ); } return $repository_args; } /** * Alters the context of the view based on the shortcode params stored in the database based on the ID. * * @since 5.0.0 * * @param Context $view_context Context for this request. * @param string $view_slug Slug of the view we are building. * @param View $instance Which view instance we are dealing with. * * @return Context Altered version of the context ready for shortcodes. */ public function filter_view_context( Context $view_context, $view_slug, $instance ) { if ( ! $shortcode_id = $view_context->get( 'shortcode' ) ) { return $view_context; } $arguments = $this->get_database_arguments( $shortcode_id ); if ( empty( $arguments ) ) { return $view_context; } /* Week view/widget only. */ if ( false !== stripos( $view_slug, \Tribe\Events\Pro\Views\V2\Views\Week_View::get_view_slug() ) ) { $offset = $this->get_argument( 'week_offset' ); if ( tribe_is_truthy( $offset ) && empty( $view_context->get( 'eventDate' ) ) ) { $start_date = $this->get_argument( 'date', 'now' ); $start_date = Dates::build_date_object( $start_date ); $is_negative = '-' === substr( $offset, 0, 1 ); // Set up for negative weeks. $interval = ( $is_negative ) ? substr( $offset, 1, 1 ) : $offset; $di = Dates::interval( "P{$interval}W" ); $di->invert = absint( $is_negative ); $start_date->add( $di ); $arguments['date'] = $start_date->format( Dates::DBDATEFORMAT ); } elseif ( ! empty( $view_context->get( 'eventDate' ) ) ) { $start_date = Dates::build_date_object( $view_context->get( 'eventDate' ) ); $arguments['date'] = $start_date->format( Dates::DBDATEFORMAT ); } $arguments['week_events_per_day'] = $this->get_argument( 'week_events_per_day' ); } elseif ( false !== stripos( $view_slug, \Tribe\Events\Views\V2\Views\Day_View::get_view_slug() ) ) { /* Day view/widget only. */ $event_date = $view_context->get( 'eventDate' ); if ( ! empty( $event_date ) ) { $arguments['date'] = $event_date; } } else { // Works for month view. $arguments['date'] = $view_context->get( 'tribe-bar-date' ); } return $this->alter_context( $view_context, $arguments ); } /** * Filters the default view in the views manager for shortcodes navigation. * * @since 4.7.9 * * @param string $view_class Fully qualified class name for default view. * * @return string Fully qualified class name for default view of the shortcode in question. */ public function filter_default_url( $view_class ) { if ( tribe_context()->doing_php_initial_state() ) { return $view_class; } // Use the global context here as we should be in the context of an AJAX shortcode request. $shortcode_id = tribe_context()->get( 'shortcode', false ); if ( false === $shortcode_id ) { // If we're not in the context of an AJAX shortcode request, bail. return $view_class; } $shortcode_args = $this->get_database_arguments( $shortcode_id ); if ( ! $shortcode_args['view'] ) { return $view_class; } return tribe( Views_Manager::class )->get_view_class_by_slug( $shortcode_args['view'] ); } /** * Filters the View HTML classes to add some related to PRO features. * * @since 5.0.0 * * @param array $html_classes The current View HTML classes. * @param string $slug The View registered slug. * @param View_Interface $view The View currently rendering. * * @return array The filtered HTML classes. */ public function filter_view_html_classes( $html_classes, $slug, $view ) { $context = $view->get_context(); if ( ! $context instanceof Context ) { return $html_classes; } $shortcode = $context->get( 'shortcode', false ); if ( ! $shortcode ) { return $html_classes; } $shortcode_args = $this->get_database_arguments( $shortcode ); $html_classes[] = 'tribe-events-view--shortcode'; $html_classes[] = 'tribe-events-view--shortcode-' . $shortcode; $container_classes = Arr::get( $shortcode_args, 'container-classes', '' ); if ( ! empty( $container_classes ) ) { $html_classes = array_merge( $html_classes, $container_classes ); } return $html_classes; } /** * Cleans up an array of values as html classes. * * @since 5.5.0 * * @param mixed $value Which classes we are cleaning up. * * @return array Resulting clean html classes. */ public static function validate_array_html_classes( $value ) { if ( ! is_array( $value ) ) { $value = explode( ' ', $value ); } return array_map( 'sanitize_html_class', (array) $value ); } /** * Filters the View data attributes to add some related to PRO features. * * @since 5.0.0 * * @param array $data The current View data attributes classes. * @param string $slug The View registered slug. * @param View_Interface $view The View currently rendering. * * @return array The filtered data attributes. */ public function filter_view_data( $data, $slug, $view ) { if ( ! $view instanceof View_Interface ) { return $data; } $context = $view->get_context(); if ( ! $context instanceof Context ) { return $data; } if ( $shortcode = $context->get( 'shortcode', false ) ) { $data['shortcode'] = $shortcode; } return $data; } /** * Filters the View URL to add the shortcode query arg, if required. * * @since 4.7.9 * @since 5.5.0 Moved this from deprecated Shortcodes\Manager. * * @param string $url The View current URL. * @param bool $canonical Whether to return the canonical version of the URL or the normal one. * @param View_Interface $view This view instance. * * @return string The URL for the view shortcode. */ public function filter_view_url( $url, $canonical, View_Interface $view ) { $context = $view->get_context(); if ( empty( $url ) ) { return $url; } if ( ! $context instanceof Context ) { return $url; } $shortcode_id = $context->get( 'shortcode', false ); if ( false === $shortcode_id ) { return $url; } return add_query_arg( [ 'shortcode' => $shortcode_id ], $url ); } /** * Filters the query arguments array and add the Shortcodes. * * @since 4.7.9 * @since 5.5.0 Moved this from deprecated Shortcodes\Manager. * * @param array $query Arguments used to build the URL. * @param string $view_slug The current view slug. * @param View_Interface $view The current View object. * * @return array Filtered the query arguments for shortcodes. */ public function filter_view_url_query_args( array $query, $view_slug, View_Interface $view ) { $context = $view->get_context(); if ( ! $context instanceof Context ) { return $query; } $shortcode = $context->get( 'shortcode', false ); if ( false === $shortcode ) { return $query; } $query['shortcode'] = $shortcode; return $query; } /** * Filter the breakpoints for the week view widget based on layout. * * @since 5.6.0 * * @param array $breakpoints All breakpoints available. * @param View $view The current View instance being rendered. * * @return array Modified array of available breakpoints. */ public function filter_week_view_breakpoints( $breakpoints, $view ) { $context = $view->get_context(); $widget = $context->get( 'is-widget', false ); $shortcode = $context->get( 'shortcode', false ); if ( false === $widget ) { return $breakpoints; } if ( false === $shortcode ) { return $breakpoints; } $shortcode_args = $this->get_database_arguments( $shortcode ); if ( ! $shortcode_args ) { return $breakpoints; } if ( 'vertical' === $shortcode_args['layout'] ) { // Remove all breakpoints to remain in "mobile view". return []; } elseif ( 'horizontal' === $shortcode_args['layout'] ) { // Simplify breakpoints to remain in "desktop view". unset( $breakpoints['xsmall'] ); $breakpoints['medium'] = 0; return $breakpoints; } // Fallback and space for "auto". return $breakpoints; } /** * Modify the Week events per day of a given view based on arguments from Shortcode. * * @since 5.6.0 * * @param int|string $events_per_day Number of events per day. * @param View $view Current view being rendered. * * @return mixed */ public function filter_week_events_per_day( $events_per_day, $view ) { $context = $view->get_context(); $shortcode = $context->get( 'shortcode', false ); if ( false === $shortcode ) { return $events_per_day; } $shortcode_args = $this->get_database_arguments( $shortcode ); if ( ! $shortcode_args || ! isset( $shortcode_args['count'] ) ) { return $events_per_day; } return $shortcode_args['count']; } /** * Modify the events repository query for the fast-forward link. * * @since 5.14.2 * * @param Tribe__Repository__Interface $next_event Current instance of the events repository class. * @param View_Interface $view The View currently rendering. * * @return Tribe__Repository__Interface $next_event The modified repository instance. */ public function filter_ff_link_next_event( $next_event, $view ) { $shortcode = $view->get_context()->get( 'shortcode' ); if ( empty( $shortcode ) ) { return $next_event; } $args = $this->get_database_arguments( $shortcode ); if ( ! empty( $args['category'] ) ) { $next_event = $next_event->where( 'category', (array) $args['category'] ); } if ( ! empty( $args['tag'] ) ) { $next_event = $next_event->where( 'tag', (array) $args['tag'] ); } if ( ! empty( $args['exclude-category'] ) ) { $next_event = $next_event->where( 'category_not_in', (array) $args['exclude-category'] ); } if ( ! empty( $args['exclude-tag'] ) ) { $next_event = $next_event->where( 'tag__not_in', (array) $args['exclude-tag'] ); } if ( ! empty( $args['author'] ) ) { $next_event = $next_event->where( 'author', $args['author'] ); } if ( ! empty( $args['organizer'] ) ) { $next_event = $next_event->where( 'organizer', $args['organizer'] ); } if ( ! empty( $args['venue'] ) ) { $next_event = $next_event->where( 'venue', $args['venue'] ); } return $next_event; } /** * Allows the user to specify that they want to skip empty views. * * @since 6.5.1 * * @param bool $skip Whether to skip empty views. * * @return bool Whether to skip empty views. */ public function filter_skip_empty( $skip ): bool { $arguments = $this->get_arguments(); if ( ! isset( $arguments['skip-empty'] ) ) { return $skip; } return tribe_is_truthy( $arguments['skip-empty'] ); } }