[ 'name' => __( 'نظرات مشتریان', 'rahkar-toolkit' ), 'singular_name' => __( 'نظر مشتری', 'rahkar-toolkit' ), 'add_new' => __( 'افزودن نظر', 'rahkar-toolkit' ), 'add_new_item' => __( 'افزودن نظر مشتری', 'rahkar-toolkit' ), 'edit_item' => __( 'ویرایش نظر مشتری', 'rahkar-toolkit' ), 'new_item' => __( 'نظر جدید', 'rahkar-toolkit' ), 'view_item' => __( 'مشاهده نظر', 'rahkar-toolkit' ), 'search_items' => __( 'جستجوی نظرات', 'rahkar-toolkit' ), 'not_found' => __( 'نظری یافت نشد.', 'rahkar-toolkit' ), 'not_found_in_trash' => __( 'نظری در زباله‌دان نیست.', 'rahkar-toolkit' ), 'all_items' => __( 'همه‌ی نظرات', 'rahkar-toolkit' ), 'menu_name' => __( 'نظرات مشتریان', 'rahkar-toolkit' ), ], // No public single page — a theme renders these in a section. The // admin UI and REST stay on, because this is also the moderation // queue for what customers submit. 'public' => false, 'show_ui' => true, 'show_in_menu' => true, 'show_in_rest' => true, 'publicly_queryable' => false, 'has_archive' => false, 'exclude_from_search' => true, 'rewrite' => false, 'menu_position' => 26, 'menu_icon' => 'dashicons-format-quote', // page-attributes = the «ترتیب» field, so the shop controls the // sequence instead of it being pinned to publish date. 'supports' => [ 'title', 'editor', 'thumbnail', 'author', 'page-attributes' ], ] ); } /** * Register the post meta. * * @internal Hooked callback. */ public function registerMeta(): void { $str = array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'sanitize_callback' => 'sanitize_text_field', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ); $int = array( 'type' => 'integer', 'single' => true, 'show_in_rest' => true, 'sanitize_callback' => 'absint', 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, ); register_post_meta( self::POST_TYPE, '_rh_tm_instagram', $str ); register_post_meta( self::POST_TYPE, '_rh_tm_location', $str ); register_post_meta( self::POST_TYPE, '_rh_tm_poster_id', $int ); /* * Pinned ("منتخب"). The block's `source` control splits the archive on * this flag: the front-page rail shows the pinned ones, a second page * shows everything else — same block, one attribute apart. * * ALWAYS written on save (0 or 1, never deleted) so the "rest" query can * stay a plain `= 0` compare for anything saved since this shipped; the * NOT EXISTS branch is only there for rows written before it. */ register_post_meta( self::POST_TYPE, '_rh_tm_pinned', $int ); /* * Optional product this testimonial is about. Drives the "نظر مشتری‌ها" * section on that product's page. Stored as a plain post ID. */ register_post_meta( self::POST_TYPE, '_rh_tm_product', $int ); /* * Where it shows: `product` only on its product's page, `rail` only in * the testimonials rail, absent (or `all`) both. Absent rather than a * written `all`, so every testimonial saved before this existed keeps * showing everywhere with no migration. */ register_post_meta( self::POST_TYPE, '_rh_tm_scope', array_merge( $str, array( 'sanitize_callback' => array( self::class, 'sanitizeScope' ) ) ) ); // The customer's own «از اینستاگرام خریدم» tick — a claim, not a fact. register_post_meta( self::POST_TYPE, '_rh_tm_via_instagram', $int ); // Set only from the order table: the customer has a paid order for the // tagged product. The «خریدار سایت» badge. Never taken from a form. register_post_meta( self::POST_TYPE, '_rh_tm_verified', $int ); // Multi-media: an ordered list of attachment IDs (images, and optionally a // voice/video). Each item's type is derived from its MIME at render time. // Managed via the admin meta box / REST submit, not the block editor, so // show_in_rest is off to avoid an array schema. register_post_meta( self::POST_TYPE, '_rh_tm_media_ids', array( 'type' => 'array', 'single' => true, 'show_in_rest' => false, 'auth_callback' => function () { return current_user_can( 'edit_posts' ); }, 'sanitize_callback' => function ( $v ) { return array_values( array_filter( array_map( 'absint', (array) $v ) ) ); }, ) ); } /** * Constrain the display scope to the places that exist. * * @param mixed $value Raw value. */ public static function sanitizeScope( $value ): string { $value = is_string( $value ) ? $value : ''; return in_array( $value, array( 'product', 'rail' ), true ) ? $value : 'all'; } /** * Constrain the media-type field to the kinds that exist. * * @param mixed $value Raw value. */ public static function sanitizeMediaType( $value ): string { $value = is_string( $value ) ? strtolower( trim( $value ) ) : ''; return in_array( $value, array( 'image', 'audio', 'video' ), true ) ? $value : 'none'; } }