setup_hooks(); } /** * Set up hooks for various purposes. * * This method adds hooks for different purposes as needed. * * @since 1.0.0 * * @return void */ protected function setup_hooks(): void { add_action( sprintf( 'save_post_%s', self::POST_TYPE ), array( $this, 'add_venue_term' ), 10, 3 ); add_action( 'init', array( $this, 'register_post_type' ) ); add_action( 'init', array( $this, 'register_post_meta' ) ); add_action( 'init', array( $this, 'register_taxonomy' ) ); add_action( 'post_updated', array( $this, 'maybe_update_term_slug' ), 10, 3 ); add_action( 'delete_post', array( $this, 'delete_venue_term' ) ); } /** * Registers the custom post type for Venues. * * Initializes the Venues post type with all its associated labels, settings, * and supports features, making it accessible within the WordPress REST API, * searchable, and manageable within the custom 'Venues' menu in the dashboard. * It is designed to handle venue information for events, including titles, * descriptions, images, and custom fields. * * @since 1.0.0 * * @return void */ public function register_post_type(): void { $settings = Settings::get_instance(); $rewrite_slug = $settings->get_value( 'general', 'urls', 'venues' ); register_post_type( self::POST_TYPE, array( 'labels' => array( 'name' => _x( 'Venues', 'Admin menu and post type general name', 'gatherpress' ), 'singular_name' => _x( 'Venue', 'Admin menu and post type singular name', 'gatherpress' ), 'add_new' => __( 'Add New', 'gatherpress' ), 'add_new_item' => __( 'Add New Venue', 'gatherpress' ), 'edit_item' => __( 'Edit Venue', 'gatherpress' ), 'new_item' => __( 'New Venue', 'gatherpress' ), 'view_item' => __( 'View Venue', 'gatherpress' ), 'view_items' => __( 'View Venues', 'gatherpress' ), 'search_items' => __( 'Search Venues', 'gatherpress' ), 'not_found' => __( 'No Venues found.', 'gatherpress' ), 'not_found_in_trash' => __( 'No Venues found in Trash.', 'gatherpress' ), 'parent_item_colon' => __( 'Parent Venues:', 'gatherpress' ), 'all_items' => __( 'Venues', 'gatherpress' ), 'archives' => __( 'Venue Archives', 'gatherpress' ), 'attributes' => __( 'Venue Attributes', 'gatherpress' ), 'insert_into_item' => __( 'Insert into Venue', 'gatherpress' ), 'uploaded_to_this_item' => __( 'Uploaded to this Venue', 'gatherpress' ), 'menu_name' => _x( 'Venues', 'Admin menu label', 'gatherpress' ), 'filter_items_list' => __( 'Filter Venue list', 'gatherpress' ), 'filter_by_date' => __( 'Filter by date', 'gatherpress' ), 'items_list_navigation' => __( 'Venues list navigation', 'gatherpress' ), 'items_list' => __( 'Venues list', 'gatherpress' ), 'item_published' => __( 'Venue published.', 'gatherpress' ), 'item_published_privately' => __( 'Venue published privately.', 'gatherpress' ), 'item_reverted_to_draft' => __( 'Venue reverted to draft.', 'gatherpress' ), 'item_trashed' => __( 'Venue trashed.', 'gatherpress' ), 'item_scheduled' => __( 'Venue scheduled.', 'gatherpress' ), 'item_updated' => __( 'Venue updated.', 'gatherpress' ), 'item_link' => _x( 'Venue Link', 'Block editor link label', 'gatherpress' ), 'item_link_description' => _x( 'A link to a venue.', 'Block editor link description', 'gatherpress' ), ), 'show_in_rest' => true, 'rest_base' => 'gatherpress_venues', 'public' => true, 'hierarchical' => false, 'show_in_menu' => 'edit.php?post_type=gatherpress_event', 'supports' => array( 'title', 'author', 'editor', 'thumbnail', 'revisions', 'custom-fields', ), 'menu_icon' => 'dashicons-location', 'template' => array( array( 'core/pattern', array( 'slug' => 'gatherpress/venue-template' ) ), ), 'has_archive' => true, 'rewrite' => array( 'slug' => $rewrite_slug, 'with_front' => false, ), ) ); } /** * Returns the post type slug localized for the site language and sanitized as URL part. * * Do not use this directly, use get_value( 'general', 'urls', 'venues' ) instead. * * This method switches to the sites default language and gets the translation of 'venues' for the loaded locale. * After that, the method sanitizes the string to be safely used within an URL, * by removing accents, replacing special characters and replacing whitespace with dashes. * * @since 1.0.0 * * @return string */ public static function get_localized_post_type_slug(): string { $switched_locale = switch_to_locale( get_locale() ); $slug = _x( 'Venue', 'Admin menu and post type singular name', 'gatherpress' ); $slug = sanitize_title( $slug ); if ( $switched_locale ) { restore_previous_locale(); } return $slug; } /** * Registers custom meta fields for the Venue post type. * * Sets up meta fields associated with the Venue post type, such as 'venue_information', * configuring capabilities, sanitization, and REST API visibility. This method ensures * that only users with the appropriate permissions can edit these fields, and that * the data stored is properly sanitized. Each meta field is registered to be * single and of a string type, optimized for use within the WordPress REST API. * * @since 1.0.0 * * @return void */ public function register_post_meta(): void { $post_meta = array( 'gatherpress_venue_information' => array( 'auth_callback' => static function () { return current_user_can( 'edit_posts' ); // @codeCoverageIgnore }, 'sanitize_callback' => 'sanitize_text_field', 'show_in_rest' => true, 'single' => true, 'type' => 'string', ), ); foreach ( $post_meta as $meta_key => $args ) { register_post_meta( self::POST_TYPE, $meta_key, $args ); } } /** * Registers a custom taxonomy for the Venue post type, not accessible to users. * * This taxonomy, programmatically managed and linked to the Venue post type, is hidden from users * and designed for internal purposes only. Slugs for taxonomy terms are prefixed with an underscore, * emphasizing their programmatic nature and ensuring they remain uneditable through the WordPress UI. * It supports query var and REST API interactions but is entirely excluded from the admin UI * and user-facing interfaces. * * @since 1.0.0 * * @return void */ public function register_taxonomy(): void { register_taxonomy( self::TAXONOMY, Event::POST_TYPE, array( 'labels' => array( 'name' => _x( 'Venues', 'Admin menu and taxonomy general name', 'gatherpress' ), 'singular_name' => _x( 'Venue', 'Admin menu and taxonomy singular name', 'gatherpress' ), ), 'hierarchical' => false, 'public' => true, 'show_ui' => false, 'show_admin_column' => false, 'query_var' => true, 'publicly_queryable' => false, 'show_in_rest' => true, ) ); // It is necessary to make this taxonomy visible on event posts, within REST responses. register_taxonomy_for_object_type( self::TAXONOMY, Event::POST_TYPE ); } /** * Add a venue term when a venue post type is first saved. * * This method is responsible for automatically adding a term to the venue taxonomy * when a new venue post is created and published. * * @since 1.0.0 * * @param int $post_id Post ID of the venue post. * @param WP_Post $post The venue post object. * @param bool $update Whether this is an existing post being updated. * @return void */ public function add_venue_term( int $post_id, WP_Post $post, bool $update ): void { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { // @codeCoverageIgnore return; // @codeCoverageIgnore } if ( ! $update && ! empty( $post->post_name ) && 'publish' === $post->post_status ) { $term_slug = $this->get_venue_term_slug( $post->post_name ); $title = html_entity_decode( get_the_title( $post_id ) ); $term = term_exists( $term_slug, self::TAXONOMY ); if ( empty( $term ) ) { wp_insert_term( $title, self::TAXONOMY, array( 'slug' => $term_slug, ) ); } } } /** * Update the slug of the corresponding venue term if the venue post's slug changes. * * This method is triggered when a venue post is updated and checks if the slug of the * venue post has changed. If it has changed, it updates the corresponding venue term's * slug to match the new venue post slug. * * @since 1.0.0 * * @param int $post_id Post ID of the venue post. * @param WP_Post $post_after Post object after the save operation. * @param WP_Post $post_before Post object before the save operation. * @return void */ public function maybe_update_term_slug( int $post_id, WP_Post $post_after, WP_Post $post_before ): void { // Check if the post type is Venue. if ( self::POST_TYPE !== get_post_type( $post_id ) ) { return; } // Only proceed if the venue post is being published or trashed. if ( ! in_array( $post_after->post_status, array( 'publish', 'trash', ), true ) ) { return; } // Check if the post slug or title has changed. if ( $post_before->post_name !== $post_after->post_name || $post_before->post_title !== $post_after->post_title ) { // Calculate the old and new term slugs. $old_term_slug = $this->get_venue_term_slug( $post_before->post_name ); $new_term_slug = $this->get_venue_term_slug( $post_after->post_name ); // Decode the title to ensure special characters are handled correctly. $title = html_entity_decode( get_the_title( $post_id ) ); // Check if the old term exists, and if not, insert the new term. $term = term_exists( $old_term_slug, self::TAXONOMY ); if ( empty( $term ) ) { wp_insert_term( $title, self::TAXONOMY, array( 'slug' => $new_term_slug, ) ); } else { // Update the existing term with the new name and slug. wp_update_term( intval( $term['term_id'] ), self::TAXONOMY, array( 'name' => $title, 'slug' => $new_term_slug, ) ); } } } /** * Delete the corresponding venue term when a Venue post is deleted. * * This method is triggered when a Venue post is deleted. It checks if the deleted post * is of the Venue post type and, if so, deletes the corresponding venue term associated * with the post. * * @since 1.0.0 * * @param int $post_id Post ID of the Venue post being deleted. * @return void */ public function delete_venue_term( int $post_id ): void { // Check if the post type is Venue. if ( get_post_type( $post_id ) === self::POST_TYPE ) { // Retrieve the post object. $post = get_post( $post_id ); // Generate the term slug associated with the Venue post. $term_slug = $this->get_venue_term_slug( $post->post_name ); // Get the term by slug. $term = get_term_by( 'slug', $term_slug, self::TAXONOMY ); // Check if the term exists and delete it. if ( is_a( $term, '\WP_Term' ) ) { wp_delete_term( $term->term_id, self::TAXONOMY ); } } } /** * Generate a term slug for the Venue taxonomy based on the post slug of a Venue post. * * This method generates a unique term slug for the Venue taxonomy by incorporating * the post slug of a Venue post. It is used to create and identify the corresponding * venue term associated with a Venue post. * * @since 1.0.0 * * @param string $post_name Post name (slug) of the Venue post. * @return string The generated term slug. */ public function get_venue_term_slug( string $post_name ): string { // Generate the term slug by prefixing it with an underscore. return sprintf( '_%s', $post_name ); } /** * Retrieve the Venue Custom Post Type (CPT) from a Venue taxonomy slug. * * This method retrieves the Venue Custom Post Type (CPT) associated with a given * Venue taxonomy slug. It allows you to obtain the Venue post object based on the * taxonomy slug used for venues. * * @since 1.0.0 * * @param string $slug Slug of the Venue taxonomy to retrieve the Venue post. * @return null|WP_Post The Venue post object if found; otherwise, null. */ public function get_venue_post_from_term_slug( string $slug ): ?WP_Post { // Remove any leading underscores from the slug and retrieve the corresponding Venue post. return get_page_by_path( ltrim( $slug, '_' ), OBJECT, self::POST_TYPE ); } /** * Retrieve venue information from meta data. * * This method retrieves and assembles venue-related information from meta data * associated with a given post. The returned array includes details such as the * venue's name, whether it's an online event term, an online event link, and any * additional venue information stored as JSON in post meta. * * @since 1.0.0 * * @param int $post_id The post ID for which to retrieve venue information. * @param string $post_type The post type of the provided post ID. * @return array An array containing venue-related information. */ public function get_venue_meta( int $post_id, string $post_type ): array { $venue_post = null; $venue_slug = null; $venue_meta = array(); $venue_meta['isOnlineEventTerm'] = false; $venue_meta['onlineEventLink'] = ''; if ( Event::POST_TYPE === $post_type ) { $event = new Event( $post_id ); $venue_terms = get_the_terms( $post_id, self::TAXONOMY ); if ( ! empty( $venue_terms ) && is_array( $venue_terms ) ) { $venue_term = $venue_terms[0]; $venue_slug = $venue_term->slug; $venue_post = $this->get_venue_post_from_term_slug( $venue_slug ); } $venue_meta['isOnlineEventTerm'] = ( 'online-event' === $venue_slug ); $venue_meta['onlineEventLink'] = $event->maybe_get_online_event_link(); } if ( self::POST_TYPE === $post_type ) { $venue_post = get_post( $post_id ); } if ( is_a( $venue_post, 'WP_Post' ) ) { $venue_meta['name'] = get_the_title( $venue_post ); $venue_meta = array_merge( $venue_meta, (array) json_decode( get_post_meta( $venue_post->ID, 'gatherpress_venue_information', true ) ) ); } return $venue_meta; } }