field key */ function bcal_picker_taxonomies() { return array( 'bcal_venue' => 'venue', 'bcal_city' => 'city', 'bcal_organizer' => 'organizer', ); } /** * Short description shown under a term name. * * @param WP_Term $term Term. * @return string */ function bcal_term_summary( $term ) { switch ( $term->taxonomy ) { case 'bcal_venue': $city = bcal_venue_city( $term ); $parts = array( (string) get_term_meta( $term->term_id, 'bcal_address', true ), trim( (string) get_term_meta( $term->term_id, 'bcal_postal', true ) . ' ' . ( $city ? $city->name : '' ) ), ); return implode( ', ', array_filter( array_map( 'trim', $parts ) ) ); case 'bcal_organizer': $url = (string) get_term_meta( $term->term_id, 'bcal_url', true ); $host = $url ? wp_parse_url( $url, PHP_URL_HOST ) : ''; return $host ? preg_replace( '/^www\./', '', (string) $host ) : ''; case 'bcal_city': if ( $term->parent ) { $parent = get_term( $term->parent, 'bcal_city' ); return ( $parent && ! is_wp_error( $parent ) ) ? $parent->name : ''; } return ''; } return ''; } /** * Term data for the picker. * * @param WP_Term $term Term. * @return array */ function bcal_term_item( $term ) { $item = array( 'id' => (int) $term->term_id, 'name' => html_entity_decode( $term->name, ENT_QUOTES, 'UTF-8' ), 'summary' => bcal_term_summary( $term ), 'count' => (int) $term->count, 'edit' => (string) get_edit_term_link( $term->term_id, $term->taxonomy, BCAL_PT ), ); if ( 'bcal_venue' === $term->taxonomy ) { $city = bcal_venue_city( $term ); if ( $city ) { $item['city'] = array( 'id' => (int) $city->term_id, 'name' => html_entity_decode( $city->name, ENT_QUOTES, 'UTF-8' ), 'summary' => bcal_term_summary( $city ), 'count' => (int) $city->count, 'edit' => (string) get_edit_term_link( $city->term_id, 'bcal_city', BCAL_PT ), ); } } return $item; } /** * Checks the request for picker endpoints. * * @param string $cap Capability key of the taxonomy (assign_terms, manage_terms). * @return string Taxonomy, or ends the request with an error. */ function bcal_picker_request_taxonomy( $cap = 'assign_terms' ) { check_ajax_referer( 'bcal_admin', 'nonce' ); $taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_key( wp_unslash( $_POST['taxonomy'] ) ) : ''; if ( ! array_key_exists( $taxonomy, bcal_picker_taxonomies() ) ) { wp_send_json_error( null, 400 ); } $tax = get_taxonomy( $taxonomy ); if ( ! $tax || ! isset( $tax->cap->$cap ) || ! current_user_can( $tax->cap->$cap ) ) { wp_send_json_error( null, 403 ); } return $taxonomy; } /** * Whether the current user may add terms (and their details) to a * taxonomy. * * @param string $taxonomy Taxonomy. * @return bool */ function bcal_can_create_terms( $taxonomy ) { $tax = get_taxonomy( $taxonomy ); return $tax && current_user_can( $tax->cap->manage_terms ) && current_user_can( $tax->cap->edit_terms ); } add_action( 'wp_ajax_bcal_terms_search', function () { $taxonomy = bcal_picker_request_taxonomy(); $query = isset( $_POST['q'] ) ? sanitize_text_field( wp_unslash( $_POST['q'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing $args = array( 'taxonomy' => $taxonomy, 'hide_empty' => false, 'number' => '' === $query ? 8 : 20, 'orderby' => '' === $query ? 'count' : 'name', 'order' => '' === $query ? 'DESC' : 'ASC', ); if ( '' !== $query ) { $args['search'] = $query; } $terms = get_terms( $args ); $terms = is_wp_error( $terms ) ? array() : $terms; $ids = wp_list_pluck( $terms, 'term_id' ); // A match on an alias goes first, it is exactly what the user meant. if ( '' !== $query ) { $alias_id = bcal_resolve_term( $taxonomy, $query, false ); if ( $alias_id && ! in_array( $alias_id, $ids, true ) ) { $term = get_term( $alias_id, $taxonomy ); if ( $term && ! is_wp_error( $term ) ) { array_unshift( $terms, $term ); } } } wp_send_json_success( array_map( 'bcal_term_item', $terms ) ); } ); add_action( 'wp_ajax_bcal_term_create', function () { $taxonomy = bcal_picker_request_taxonomy( 'manage_terms' ); if ( ! bcal_can_create_terms( $taxonomy ) ) { wp_send_json_error( null, 403 ); } $in = isset( $_POST['fields'] ) && is_array( $_POST['fields'] ) ? wp_unslash( $_POST['fields'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing $get = function ( $key ) use ( $in ) { return isset( $in[ $key ] ) && is_scalar( $in[ $key ] ) ? trim( sanitize_text_field( (string) $in[ $key ] ) ) : ''; }; $name = $get( 'name' ); if ( '' === $name ) { wp_send_json_error( array( 'message' => __( 'Enter a name.', 'bearius-calendar' ) ), 400 ); } $existing = bcal_resolve_term( $taxonomy, $name, false ); if ( $existing ) { $term = get_term( $existing, $taxonomy ); wp_send_json_success( array( 'existing' => true, 'item' => bcal_term_item( $term ), ) ); } $result = wp_insert_term( $name, $taxonomy ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ), 400 ); } $term_id = (int) $result['term_id']; if ( 'bcal_venue' === $taxonomy ) { foreach ( array( 'address' => 'bcal_address', 'postal' => 'bcal_postal' ) as $field => $meta ) { if ( '' !== $get( $field ) ) { update_term_meta( $term_id, $meta, $get( $field ) ); } } foreach ( array( 'lat' => 'bcal_lat', 'lng' => 'bcal_lng' ) as $field => $meta ) { $coord = str_replace( ',', '.', $get( $field ) ); if ( is_numeric( $coord ) ) { update_term_meta( $term_id, $meta, $coord ); } } $city_id = absint( $get( 'city_id' ) ); if ( ! $city_id && '' !== $get( 'city_new' ) && bcal_can_create_terms( 'bcal_city' ) ) { $city_id = bcal_resolve_term( 'bcal_city', $get( 'city_new' ), true ); } if ( $city_id && term_exists( $city_id, 'bcal_city' ) ) { update_term_meta( $term_id, 'bcal_city_id', $city_id ); } } if ( 'bcal_organizer' === $taxonomy && '' !== $get( 'url' ) ) { $url = esc_url_raw( isset( $in['url'] ) && is_scalar( $in['url'] ) ? trim( (string) $in['url'] ) : '' ); if ( $url ) { update_term_meta( $term_id, 'bcal_url', $url ); } } wp_send_json_success( array( 'existing' => false, 'item' => bcal_term_item( get_term( $term_id, $taxonomy ) ), ) ); } ); /** * Saves picker choices. Only runs when the meta box was on the screen, * so imports, quick edit and REST updates keep their terms. * * @param int $post_id Post ID. * @param array $in Submitted meta box values. */ function bcal_save_picker_terms( $post_id, $in ) { if ( empty( $in['terms'] ) ) { return; } foreach ( bcal_picker_taxonomies() as $taxonomy => $field ) { $tax = get_taxonomy( $taxonomy ); if ( ! $tax || ! current_user_can( $tax->cap->assign_terms ) ) { continue; } $term_id = isset( $in[ $field ] ) && is_scalar( $in[ $field ] ) ? absint( $in[ $field ] ) : 0; if ( $term_id && term_exists( $term_id, $taxonomy ) ) { wp_set_object_terms( $post_id, array( $term_id ), $taxonomy ); } else { wp_set_object_terms( $post_id, array(), $taxonomy ); } } // A venue with a saved city fills an empty city. $venue = bcal_first_term( $post_id, 'bcal_venue' ); if ( $venue && ! bcal_first_term( $post_id, 'bcal_city' ) ) { $city = bcal_venue_city( $venue ); if ( $city ) { wp_set_object_terms( $post_id, array( (int) $city->term_id ), 'bcal_city' ); } } } /** * Picker markup. * * @param int $post_id Event ID. * @param string $taxonomy Taxonomy. * @param array $opts { label, placeholder, create: modal|inline, template }. */ function bcal_render_picker( $post_id, $taxonomy, $opts ) { $fields = bcal_picker_taxonomies(); $field = $fields[ $taxonomy ]; $term = bcal_first_term( $post_id, $taxonomy ); $item = $term ? bcal_term_item( $term ) : null; $uid = 'bcal-picker-' . $field; $tax_obj = get_taxonomy( $taxonomy ); $can = $tax_obj && current_user_can( $tax_obj->cap->assign_terms ); $create = $can && bcal_can_create_terms( $taxonomy ) ? $opts['create'] : 'none'; ?>
>
>