query_vars['tribe-edit-orders'] ) && $query->query_vars['tribe-edit-orders']; if ( ! $is_correct_page ) { return; } // This has no Performance problems, since get_post uses caching and we use this method later on. $post = get_post( absint( $query->query_vars['p'] ) ); if ( ! $post ) { return; } if ( 'page' !== $post->post_type ) { return; } // Unset the p variable, we dont need it anymore unset( $query->query_vars['p'] ); // Set `page_id` for faster query $query->query_vars['page_id'] = $post->ID; } /** * Tries to Flush the Rewrite rules * * @return void */ public function maybe_regenerate_rewrite_rules() { // if they don't have any rewrite rules, do nothing // Don't try to run stuff for non-logged users (too time consuming) if ( ! is_array( $GLOBALS['wp_rewrite']->rules ) || ! is_user_logged_in() ) { return; } $rules = $this->rewrite_rules_array(); $diff = array_diff( $rules, $GLOBALS['wp_rewrite']->rules ); $key_diff = array_diff_assoc( $rules, $GLOBALS['wp_rewrite']->rules ); if ( empty( $diff ) && empty( $key_diff ) ) { return; } flush_rewrite_rules(); } /** * Gets the List of Rewrite rules we are using here * * @return array */ public function rewrite_rules_array() { $bases = $this->add_rewrite_base_slug(); $rules = array( sanitize_title_with_dashes( $bases['tickets'][0] ) . '/([0-9]{1,})/?' => 'index.php?p=$matches[1]&tribe-edit-orders=1', ); return $rules; } /** * For non events the links will be a little bit weird, but it's the safest way * * @param WP_Rewrite $wp_rewrite */ public function add_non_event_permalinks( WP_Rewrite $wp_rewrite ) { $wp_rewrite->rules = $this->rewrite_rules_array() + $wp_rewrite->rules; } /** * Add a new Query Var to allow tickets editing * @param array $vars * @return array */ public function add_query_vars( $vars ) { $vars[] = 'tribe-edit-orders'; return $vars; } /** * Update the RSVP and Tickets values for each Attendee */ public function update_tickets() { $is_correct_page = get_query_var( 'tribe-edit-orders', false ); // Now fetch the display and check it $display = get_query_var( 'eventDisplay', false ); if ( 'tickets' !== $display && ! $is_correct_page ) { return; } if ( empty( $_POST['process-tickets'] ) || ( empty( $_POST['attendee'] ) && empty( $_POST['tribe-tickets-meta'] ) ) ) { return; } $post_id = get_the_ID(); $attendees = ! empty( $_POST['attendee'] ) ? $_POST['attendee'] : array(); foreach ( $attendees as $order_id => $data ) { /** * An Action fired for each one of the Attendees that were posted on the Order Tickets page * * @var $data Infomation that we are trying to save * @var $order_id ID of attendee ticket * @var $post_id ID of event */ do_action( 'event_tickets_attendee_update', $data, $order_id, $post_id ); } /** * A way for Meta to be saved, because it's grouped in a different way * * @var $post_id ID of event */ do_action( 'event_tickets_after_attendees_update', $post_id ); // After Editing the Values we Update the Transient Tribe__Post_Transient::instance()->delete( $post_id, Tribe__Tickets__Tickets::ATTENDEES_CACHE ); // If it's not events CPT if ( $is_correct_page ) { $url = home_url( 'tickets/' ) . $post_id; } else { $url = get_permalink( $post_id ) . '/tickets'; } $url = add_query_arg( 'tribe_updated', 1, $url ); wp_safe_redirect( esc_url_raw( $url ) ); exit; } /** * Makes sure only logged users can See the Tickets page. * * @return void */ public function authorization_redirect() { global $wp_query; /** * @todo Remove this after we implement the Rewrites in Common */ $is_event_query = ! empty( $GLOBALS['wp_query']->tribe_is_event_query ); // When it's not Events Query and we have TEC active we dont care if ( class_exists( 'Tribe__Events__Main' ) && ! $is_event_query ) { return; } // If we got here and it's a 404 + single if ( is_single() && is_404() ) { return; } // Now fetch the display and check it $display = get_query_var( 'eventDisplay', false ); if ( 'tickets' !== $display ) { return; } // Only goes to the Redirect if user is not logged in if ( is_user_logged_in() ) { return; } // Loop back to the Event, this page is only for Logged users wp_redirect( get_permalink() ); exit; } /** * To allow `tickets` to be translatable we need to add it as a base * * @param array $bases The translatable bases * @return array */ public function add_rewrite_base_slug( $bases = array() ) { /** * Allows users to filter and change the base for the order page * * @param string $slug * @param array $bases */ $bases['tickets'] = (array) apply_filters( 'event_tickets_rewrite_slug_orders_page', 'tickets', $bases ); return $bases; } /** * Adds the Permalink for the tickets end point * * @param Tribe__Events__Rewrite $rewrite */ public function add_permalink( Tribe__Events__Rewrite $rewrite ) { // Adds the 'tickets' endpoint for single event pages. $rewrite->single( array( '{{ tickets }}' ), array( Tribe__Events__Main::POSTTYPE => '%1', 'post_type' => Tribe__Events__Main::POSTTYPE, 'eventDisplay' => 'tickets', ) ); // Adds the `tickets` endpoint for recurring events $rewrite->single( array( '(\d{4}-\d{2}-\d{2})', '{{ tickets }}' ), array( Tribe__Events__Main::POSTTYPE => '%1', 'eventDate' => '%2', 'post_type' => Tribe__Events__Main::POSTTYPE, 'eventDisplay' => 'tickets', ) ); } /** * Intercepts the_content from the posts to include the orders structure * * @param string $content Normally the_content of a post * @return string */ public function intercept_content( $content = '' ) { // Prevents firing more then it needs too outside of the loop $in_the_loop = isset( $GLOBALS['wp_query']->in_the_loop ) && $GLOBALS['wp_query']->in_the_loop; // Prevents Weird $is_correct_page = get_query_var( 'tribe-edit-orders', false ); if ( ! $is_correct_page || ! $in_the_loop ) { return $content; } ob_start(); include Tribe__Tickets__Templates::get_template_hierarchy( 'tickets/orders.php' ); $content = ob_get_clean(); return $content; } /** * We need to intercept the template loading and load the correct file * * @param string $old_file Non important variable with the previous path * @param string $template Which template we are dealing with * @return string The correct File path for the tickets endpoint */ public function intercept_template( $old_file, $template ) { global $wp_query; /** * @todo Remove this after we implement the Rewrites in Common */ $is_event_query = ! empty( $wp_query->tribe_is_event_query ); // When it's not our query we don't care if ( ! $is_event_query ) { return $old_file; } // If we got here and it's a 404 + single if ( is_single() && is_404() ) { return $old_file; } // Now fetch the display and check it $display = get_query_var( 'eventDisplay', false ); if ( 'tickets' !== $display ) { return $old_file; } // If for some reason it's not `single-event.php` we don't care either if ( 'single-event.php' !== $template ) { return $old_file; } // Fetch the Correct File using the Tickets Hiearchy $file = Tribe__Tickets__Templates::get_template_hierarchy( 'tickets/orders.php' ); return $file; } /** * Injects the Link to The front-end Tickets page normally at `tribe_events_single_event_after_the_meta` * * @return void */ public function inject_link_template() { $event_id = get_the_ID(); $user_id = get_current_user_id(); if ( ! $this->has_rsvp_attendees( $event_id, $user_id ) && ! $this->has_ticket_attendees( $event_id, $user_id ) ) { return; } $file = Tribe__Tickets__Templates::get_template_hierarchy( 'tickets/orders-link.php' ); include $file; } /** * Injects the Link to The front-end Tickets page to non Events * * @param string $content The content form the post * @return string $content */ public function inject_link_template_the_content( $content ) { // Prevents firing more then it needs too outside of the loop $in_the_loop = isset( $GLOBALS['wp_query']->in_the_loop ) && $GLOBALS['wp_query']->in_the_loop; $post_id = get_the_ID(); $user_id = get_current_user_id(); /** * @todo Remove this after we implement the Rewrites in Common */ $is_event_query = ! empty( $GLOBALS['wp_query']->tribe_is_event_query ); // When it's not our query we don't care if ( ( class_exists( 'Tribe__Events__Main' ) && $is_event_query ) || ! $in_the_loop ) { return $content; } // If we have this we are already on the tickets page $is_correct_page = get_query_var( 'tribe-edit-orders', false ); if ( $is_correct_page ) { return $content; } if ( ! $this->has_rsvp_attendees( $post_id, $user_id ) && ! $this->has_ticket_attendees( $post_id, $user_id ) ) { return $content; } ob_start(); include Tribe__Tickets__Templates::get_template_hierarchy( 'tickets/orders-link.php' ); $content .= ob_get_clean(); return $content; } /** * Fetches from the Cached attendees list the ones that are relevant for this user and event * Important to note that this method will bring the attendees organized by order id * * @param int $event_id The Event ID it relates to * @param int|null $user_id An Optional User ID * @param boolean $include_rsvp If this should include RSVP, which by default is false * @return array List of Attendees grouped by order id */ public function get_event_attendees_by_order( $event_id, $user_id = null, $include_rsvp = false ) { $attendees = Tribe__Tickets__Tickets::get_event_attendees( $event_id ); $orders = array(); foreach ( $attendees as $key => $attendee ) { // Ignore RSVP if we don't tell it specifically if ( 'rsvp' === $attendee['provider_slug'] && ! $include_rsvp ) { continue; } // If we have a user_id then test it and ignore the ones that don't have it if ( ! is_null( $user_id ) ) { if ( empty( $attendee['user_id'] ) || $attendee['user_id'] != $user_id ) { continue; } } $orders[ (int) $attendee['order_id'] ][] = $attendee; } return $orders; } /** * Fetches from the Cached attendees list the ones that are relevant for this user and event * Important to note that this method will bring the attendees from RSVP * * @param int $event_id The Event ID it relates to * @param int|null $user_id An Optional User ID * @return array Array with the RSVP attendees */ public function get_event_rsvp_attendees( $event_id, $user_id = null ) { $all_attendees = Tribe__Tickets__Tickets::get_event_attendees( $event_id ); $attendees = array(); foreach ( $all_attendees as $key => $attendee ) { // Skip Non RSVP if ( 'rsvp' !== $attendee['provider_slug'] ) { continue; } // If we have a user_id then test it and ignore the ones that don't have it if ( ! is_null( $user_id ) ) { if ( empty( $attendee['user_id'] ) || $attendee['user_id'] != $user_id ) { continue; } } $attendees[] = $attendee; } return $attendees; } /** * Groups RSVP attendees by purchaser name/email * * @param int $event_id The Event ID it relates to * @param int|null $user_id An optional user ID * @return array Array with the RSVP attendees grouped by purchaser name/email */ public function get_event_rsvp_attendees_by_purchaser( $event_id, $user_id = null ) { $attendees = $this->get_event_rsvp_attendees( $event_id, $user_id ); if ( ! $attendees ) { return array(); } $attendee_groups = array(); foreach ( $attendees as $attendee ) { $key = $attendee['purchaser_name'] . '::' . $attendee['purchaser_email']; if ( ! isset( $attendee_groups[ $key ] ) ) { $attendee_groups[ $key ] = array(); } $attendee_groups[ $key ][] = $attendee; } return $attendee_groups; } /** * Gets a List of Possible RSVP answers * * @param string $selected Allows users to check if an option exists or get it's label * @param bool $just_labels Whether just the options labels should be returned. * * @return array|bool An array containing the RSVP states, an array containing the selected * option data or `false` if the selected option does not exist. */ public function get_rsvp_options( $selected = null, $just_labels = true ) { $options = array( 'yes' => array( 'label' => __( 'Going', 'event-tickets' ), 'decrease_stock_by' => 1 ), 'no' => array( 'label' => __( 'Not Going', 'event-tickets' ), 'decrease_stock_by' => 0 ), ); /** * Allow users to add more RSVP options. * * Additional RSVP options should be specified in the following formats: * * [ * 'slug' => 'Option 1 label', * 'slug' => [ 'label' => 'Option 3 label' ], * 'slug' => [ 'label' => 'Option 2 label', 'decrease_stock_by' => 1 ], * ] * * The `decrease_stock_by` key can be omitted and will default to `1`. * * @param array $options * @param string $selected */ $options = apply_filters( 'event_tickets_rsvp_options', $options, $selected ); $options = array_filter( $options, array( $this, 'has_rsvp_format' ) ); array_walk( $options, array( $this, 'normalize_rsvp_option' ) ); // If an option was passed return it's label, but if doesn't exist return false if ( ! is_null( $selected ) ) { return isset( $options[ $selected ] ) ? $options[ $selected ]['label'] : false; } return $just_labels ? array_combine( array_keys( $options ), wp_list_pluck( $options, 'label' ) ) : $options; } /** * Check if the RSVP is a valid option * * @param string $option Which rsvp option to check * @return boolean */ public function is_valid_rsvp_option( $option ) { return in_array( $option, array_keys( $this->get_rsvp_options() ) ); } /** * Counts the Amount of RSVP attendees * * @param int $event_id The Event ID it relates to * @param int|null $user_id An Optional User ID * @return int */ public function count_rsvp_attendees( $event_id, $user_id = null ) { $rsvp_orders = $this->get_event_rsvp_attendees( $event_id, $user_id ); return count( $rsvp_orders ); } /** * Counts the Amount of Tickets attendees * * @param int $event_id The Event ID it relates to * @param int|null $user_id An Optional User ID * @return int */ public function count_ticket_attendees( $event_id, $user_id = null ) { $ticket_orders = $this->get_event_attendees_by_order( $event_id, $user_id ); $i = 0; foreach ( $ticket_orders as $orders ) { $i += count( $orders ); } return $i; } /** * Verifies if we have RSVP attendees for this user and event * * @param int $event_id The Event ID it relates to * @param int|null $user_id An Optional User ID * @return int */ public function has_rsvp_attendees( $event_id, $user_id = null ) { $rsvp_orders = $this->get_event_rsvp_attendees( $event_id, $user_id ); return ! empty( $rsvp_orders ); } /** * Verifies if we have Tickets attendees for this user and event * * @param int $event_id The Event ID it relates to * @param int|null $user_id An Optional User ID * @return int */ public function has_ticket_attendees( $event_id, $user_id = null ) { $ticket_orders = $this->get_event_attendees_by_order( $event_id, $user_id ); return ! empty( $ticket_orders ); } /** * Gets a String to descript which type of Tickets/RSVP we are dealign with * * @param int $event_id The Event ID it relates to * @param int|null $user_id An Optional User ID * @param boolean $plurals Return the Strings as Plural * @return int */ public function get_description_rsvp_ticket( $event_id, $user_id = null, $plurals = false ) { $what_to_update = array(); if ( $this->has_rsvp_attendees( $event_id, $user_id ) ) { $what_to_update[] = $plurals ? esc_html__( 'RSVPs', 'event-tickets' ) : esc_html__( 'RSVP', 'event-tickets' ); } if ( $this->has_ticket_attendees( $event_id, $user_id ) ) { $what_to_update[] = $plurals ? esc_html__( 'Tickets', 'event-tickets' ) : esc_html__( 'Ticket', 'event-tickets' ); } // Just Return false if array is empty if ( empty( $what_to_update ) ) { return false; } return implode( esc_html__( ' and ', 'event-tickets' ), $what_to_update ); } /** * Creates the HTML for the Select Element for RSVP options * * @param string $name The Name of the Field * @param string $selected The Current selected option * @param int $event_id The Event/Post ID (optional) * @param int $ticket_id The Ticket/RSVP ID (optional) * @return void */ public function render_rsvp_selector( $name, $selected, $event_id = null, $ticket_id = null ) { $options = $this->get_rsvp_options(); ?> is_rsvp_restricted( $event_id, $ticket_id ) ) { $is_disabled = 'disabled title="' . esc_attr__( 'This RSVP is no longer active.', 'event-tickets' ) . '"'; } return $is_disabled; } /** * Creates the HTML for the status of the RSVP choice. * * @param string $name The Name of the Field * @param string $selected The Current selected option * @param int $event_id The Event/Post ID (optional) * @param int $ticket_id The Ticket/RSVP ID (optional) * @return void */ public function render_rsvp_status( $name, $selected, $event_id = null, $ticket_id = null ) { $options = $this->get_rsvp_options(); echo sprintf( '%s', esc_html( $options[ $selected ] ) ); } /** * Prunes RSVP options that are arrays and are not defining a label. * * @param array|string $option * * @return bool */ protected function has_rsvp_format( $option ) { if ( ! is_array( $option ) ) { return true; } // label is the bare minimum if ( ! isset( $option['label'] ) ) { return false; } return empty( $option['decrease_stock_by'] ) || ( is_numeric( $option['decrease_stock_by'] ) && intval( $option['decrease_stock_by'] ) == $option['decrease_stock_by'] && intval( $option['decrease_stock_by'] ) >= 0 ); } /** * Normalizes the RSVP option conforming it to the array format. * * @param array|string $option */ protected function normalize_rsvp_option( &$option ) { $label_only_format = ! is_array( $option ); if ( $label_only_format ) { $option = array( 'label' => $option, 'decrease_stock_by' => 1 ); } else { $option['decrease_stock_by'] = isset( $option['decrease_stock_by'] ) ? $option['decrease_stock_by'] : 1; } } }