array( 'name' => __( 'Projetos', 'gustavoo-portfolio-core' ), 'singular_name' => __( 'Projeto', 'gustavoo-portfolio-core' ), 'menu_name' => __( 'Portfólio', 'gustavoo-portfolio-core' ), 'name_admin_bar' => __( 'Projeto', 'gustavoo-portfolio-core' ), 'add_new' => __( 'Adicionar projeto', 'gustavoo-portfolio-core' ), 'add_new_item' => __( 'Adicionar novo projeto', 'gustavoo-portfolio-core' ), 'edit_item' => __( 'Editar projeto', 'gustavoo-portfolio-core' ), 'new_item' => __( 'Novo projeto', 'gustavoo-portfolio-core' ), 'view_item' => __( 'Ver projeto', 'gustavoo-portfolio-core' ), 'search_items' => __( 'Buscar projetos', 'gustavoo-portfolio-core' ), 'not_found' => __( 'Nenhum projeto encontrado.', 'gustavoo-portfolio-core' ), 'not_found_in_trash' => __( 'Nenhum projeto encontrado na lixeira.', 'gustavoo-portfolio-core' ), 'all_items' => __( 'Projetos', 'gustavoo-portfolio-core' ), 'featured_image' => __( 'Imagem do projeto', 'gustavoo-portfolio-core' ), 'set_featured_image' => __( 'Definir imagem do projeto', 'gustavoo-portfolio-core' ), 'remove_featured_image' => __( 'Remover imagem do projeto', 'gustavoo-portfolio-core' ), ), 'public' => false, 'publicly_queryable' => false, 'exclude_from_search' => true, 'show_ui' => true, 'show_in_menu' => true, 'show_in_admin_bar' => true, 'show_in_nav_menus' => false, 'show_in_rest' => true, 'menu_position' => 25, 'menu_icon' => 'dashicons-portfolio', 'capability_type' => 'post', 'map_meta_cap' => true, 'hierarchical' => false, 'has_archive' => false, 'rewrite' => false, 'query_var' => false, 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'page-attributes', 'revisions', 'custom-fields' ), ) ); register_taxonomy( self::TAX_TYPE, self::POST_TYPE, array( 'labels' => array( 'name' => __( 'Tipos de projeto', 'gustavoo-portfolio-core' ), 'singular_name' => __( 'Tipo de projeto', 'gustavoo-portfolio-core' ), 'menu_name' => __( 'Tipos', 'gustavoo-portfolio-core' ), 'all_items' => __( 'Todos os tipos', 'gustavoo-portfolio-core' ), 'edit_item' => __( 'Editar tipo', 'gustavoo-portfolio-core' ), 'add_new_item' => __( 'Adicionar tipo', 'gustavoo-portfolio-core' ), 'search_items' => __( 'Buscar tipos', 'gustavoo-portfolio-core' ), ), 'public' => false, 'publicly_queryable' => false, 'show_ui' => true, 'show_admin_column' => false, 'show_in_rest' => true, 'hierarchical' => true, 'rewrite' => false, ) ); register_taxonomy( self::TAX_TECH, self::POST_TYPE, array( 'labels' => array( 'name' => __( 'Tecnologias', 'gustavoo-portfolio-core' ), 'singular_name' => __( 'Tecnologia', 'gustavoo-portfolio-core' ), 'menu_name' => __( 'Tecnologias', 'gustavoo-portfolio-core' ), 'all_items' => __( 'Todas as tecnologias', 'gustavoo-portfolio-core' ), 'edit_item' => __( 'Editar tecnologia', 'gustavoo-portfolio-core' ), 'add_new_item' => __( 'Adicionar tecnologia', 'gustavoo-portfolio-core' ), 'search_items' => __( 'Buscar tecnologias', 'gustavoo-portfolio-core' ), 'separate_items_with_commas' => __( 'Separe tecnologias com vírgulas', 'gustavoo-portfolio-core' ), ), 'public' => false, 'publicly_queryable' => false, 'show_ui' => true, 'show_admin_column' => false, 'show_in_rest' => true, 'hierarchical' => false, 'rewrite' => false, ) ); self::register_meta(); } /** * Register project metadata. * * @return void */ private static function register_meta() { $common = array( 'single' => true, 'show_in_rest' => true, 'auth_callback' => array( __CLASS__, 'authorize_meta' ), ); register_post_meta( self::POST_TYPE, self::META_URL, array_merge( $common, array( 'type' => 'string', 'sanitize_callback' => array( __CLASS__, 'sanitize_external_url' ), ) ) ); foreach ( array( self::META_CLIENT, self::META_LINK_LABEL ) as $meta_key ) { register_post_meta( self::POST_TYPE, $meta_key, array_merge( $common, array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ) ) ); } register_post_meta( self::POST_TYPE, self::META_YEAR, array_merge( $common, array( 'type' => 'integer', 'sanitize_callback' => array( __CLASS__, 'sanitize_year' ), ) ) ); register_post_meta( self::POST_TYPE, self::META_FEATURED, array_merge( $common, array( 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', ) ) ); register_post_meta( self::POST_TYPE, self::META_ACCENT, array_merge( $common, array( 'type' => 'string', 'sanitize_callback' => 'sanitize_hex_color', ) ) ); } /** * Restrict meta updates to users who can edit the project. * * @param bool $allowed Current authorization result. * @param string $meta_key Meta key. * @param int $post_id Project ID. * @return bool */ public static function authorize_meta( $allowed, $meta_key, $post_id ) { unset( $allowed, $meta_key ); return current_user_can( 'edit_post', (int) $post_id ); } /** * Allow only valid external HTTP(S) URLs. * * @param mixed $value Raw URL. * @return string */ public static function sanitize_external_url( $value ) { $url = esc_url_raw( trim( (string) $value ), array( 'http', 'https' ) ); return $url && wp_http_validate_url( $url ) ? $url : ''; } /** * Validate a four-digit project year. * * @param mixed $value Raw year. * @return int */ public static function sanitize_year( $value ) { $year = absint( $value ); $max_year = (int) gmdate( 'Y' ) + 5; return $year >= 1900 && $year <= $max_year ? $year : 0; } /** * Register the project details metabox. * * @return void */ public static function add_meta_boxes() { add_meta_box( 'gso-project-details', __( 'Detalhes do projeto', 'gustavoo-portfolio-core' ), array( __CLASS__, 'render_meta_box' ), self::POST_TYPE, 'normal', 'high' ); } /** * Render project fields. * * @param WP_Post $post Current project. * @return void */ public static function render_meta_box( $post ) { $url = (string) get_post_meta( $post->ID, self::META_URL, true ); $client = (string) get_post_meta( $post->ID, self::META_CLIENT, true ); $year = absint( get_post_meta( $post->ID, self::META_YEAR, true ) ); $featured = (bool) get_post_meta( $post->ID, self::META_FEATURED, true ); $link_label = (string) get_post_meta( $post->ID, self::META_LINK_LABEL, true ); $accent = sanitize_hex_color( get_post_meta( $post->ID, self::META_ACCENT, true ) ); if ( ! $accent ) { $accent = '#7cf3da'; } wp_nonce_field( 'gso_save_project_details', 'gso_project_details_nonce' ); ?> $columns Existing columns. * @return array */ public static function admin_columns( $columns ) { return array( 'cb' => $columns['cb'] ?? '', 'gso_thumbnail' => __( 'Imagem', 'gustavoo-portfolio-core' ), 'title' => __( 'Projeto', 'gustavoo-portfolio-core' ), 'gso_client' => __( 'Cliente', 'gustavoo-portfolio-core' ), 'gso_type' => __( 'Tipo', 'gustavoo-portfolio-core' ), 'gso_tech' => __( 'Tecnologias', 'gustavoo-portfolio-core' ), 'gso_year' => __( 'Ano', 'gustavoo-portfolio-core' ), 'gso_featured' => __( 'Destaque', 'gustavoo-portfolio-core' ), 'gso_url' => __( 'URL', 'gustavoo-portfolio-core' ), 'gso_menu_order' => __( 'Ordem', 'gustavoo-portfolio-core' ), 'date' => $columns['date'] ?? __( 'Data', 'gustavoo-portfolio-core' ), ); } /** * Render one project list cell. * * @param string $column Column ID. * @param int $post_id Project ID. * @return void */ public static function render_admin_column( $column, $post_id ) { switch ( $column ) { case 'gso_thumbnail': if ( has_post_thumbnail( $post_id ) ) { echo wp_kses_post( get_the_post_thumbnail( $post_id, array( 64, 48 ), array( 'style' => 'width:64px;height:48px;object-fit:cover;border-radius:4px;' ) ) ); } else { echo '' . esc_html__( 'Sem imagem', 'gustavoo-portfolio-core' ) . ''; } break; case 'gso_client': echo esc_html( (string) get_post_meta( $post_id, self::META_CLIENT, true ) ?: '—' ); break; case 'gso_type': self::render_terms_column( $post_id, self::TAX_TYPE ); break; case 'gso_tech': self::render_terms_column( $post_id, self::TAX_TECH ); break; case 'gso_year': $year = absint( get_post_meta( $post_id, self::META_YEAR, true ) ); echo $year ? esc_html( (string) $year ) : '—'; break; case 'gso_featured': if ( get_post_meta( $post_id, self::META_FEATURED, true ) ) { echo '' . esc_html__( 'Sim', 'gustavoo-portfolio-core' ) . ''; } else { echo '' . esc_html__( 'Não', 'gustavoo-portfolio-core' ) . ''; } break; case 'gso_url': $url = self::sanitize_external_url( get_post_meta( $post_id, self::META_URL, true ) ); if ( $url ) { $host = wp_parse_url( $url, PHP_URL_HOST ); printf( '%2$s %3$s', esc_url( $url ), esc_html( $host ?: $url ), esc_html__( '(abre em nova aba)', 'gustavoo-portfolio-core' ) ); } else { echo '—'; } break; case 'gso_menu_order': echo esc_html( (string) get_post_field( 'menu_order', $post_id ) ); break; } } /** * Render taxonomy terms in an admin column. * * @param int $post_id Project ID. * @param string $taxonomy Taxonomy name. * @return void */ private static function render_terms_column( $post_id, $taxonomy ) { $terms = get_the_terms( $post_id, $taxonomy ); if ( ! $terms || is_wp_error( $terms ) ) { echo '—'; return; } echo esc_html( implode( ', ', wp_list_pluck( $terms, 'name' ) ) ); } /** * Make selected columns sortable. * * @param array $columns Existing sortable columns. * @return array */ public static function sortable_columns( $columns ) { $columns['gso_client'] = 'gso_client'; $columns['gso_year'] = 'gso_year'; $columns['gso_featured'] = 'gso_featured'; $columns['gso_menu_order'] = 'menu_order'; return $columns; } /** * Apply safe sorting to the main project admin query. * * @param WP_Query $query Current query. * @return void */ public static function apply_admin_sorting( $query ) { if ( ! is_admin() || ! $query->is_main_query() || self::POST_TYPE !== $query->get( 'post_type' ) ) { return; } switch ( $query->get( 'orderby' ) ) { case 'gso_client': $query->set( 'meta_key', self::META_CLIENT ); $query->set( 'orderby', 'meta_value' ); break; case 'gso_year': $query->set( 'meta_key', self::META_YEAR ); $query->set( 'orderby', 'meta_value_num' ); break; case 'gso_featured': $query->set( 'meta_key', self::META_FEATURED ); $query->set( 'orderby', 'meta_value_num' ); break; case 'menu_order': $query->set( 'orderby', 'menu_order title' ); break; } } /** * Add type and technology filters above the project list. * * @param string $post_type Current list post type. * @return void */ public static function taxonomy_filters( $post_type ) { if ( self::POST_TYPE !== $post_type ) { return; } foreach ( array( self::TAX_TYPE, self::TAX_TECH ) as $taxonomy ) { $tax_object = get_taxonomy( $taxonomy ); if ( ! $tax_object ) { continue; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only list filter. $selected = isset( $_GET[ $taxonomy ] ) ? sanitize_title( wp_unslash( $_GET[ $taxonomy ] ) ) : ''; wp_dropdown_categories( array( 'show_option_all' => sprintf( /* translators: %s: taxonomy label. */ __( 'Todos: %s', 'gustavoo-portfolio-core' ), $tax_object->labels->name ), 'taxonomy' => $taxonomy, 'name' => $taxonomy, 'orderby' => 'name', 'selected' => $selected, 'hide_empty' => false, 'hierarchical' => $tax_object->hierarchical, 'value_field' => 'slug', ) ); } } }