ID, '_rh_tm_instagram', true ); $location = (string) get_post_meta( $post->ID, '_rh_tm_location', true ); $media_ids = get_post_meta( $post->ID, '_rh_tm_media_ids', true ); $media_ids = is_array( $media_ids ) ? array_filter( array_map( 'absint', $media_ids ) ) : array(); // Back-compat: fold a legacy single media id into the list. if ( empty( $media_ids ) ) { $legacy = (int) get_post_meta( $post->ID, '_rh_tm_media_id', true ); if ( $legacy ) { $media_ids = array( $legacy ); } } $poster_id = (int) get_post_meta( $post->ID, '_rh_tm_poster_id', true ); $poster_url = $poster_id ? wp_get_attachment_image_url( $poster_id, 'medium' ) : ''; $pinned = (int) get_post_meta( $post->ID, '_rh_tm_pinned', true ); $product_id = (int) get_post_meta( $post->ID, '_rh_tm_product', true ); $scope = TestimonialType::sanitizeScope( get_post_meta( $post->ID, '_rh_tm_scope', true ) ); $via_insta = (int) get_post_meta( $post->ID, '_rh_tm_via_instagram', true ); $verified = (int) get_post_meta( $post->ID, '_rh_tm_verified', true ); $scopes = array( 'all' => __( 'همه‌جا — اسلایدر نظرات و صفحهٔ محصول', 'rahkar-toolkit' ), 'product' => __( 'فقط صفحهٔ محصول', 'rahkar-toolkit' ), 'rail' => __( 'فقط اسلایدر نظرات', 'rahkar-toolkit' ), ); ?> .rh-tm-mb .rh-tm-row { margin: 14px 0; } .rh-tm-mb label.rh-tm-lbl { display:block; font-weight:var(--wp--custom--fw--strong); margin-bottom:6px; } .rh-tm-mb input[type=text], .rh-tm-mb select { width: 100%; max-width: 420px; } .rh-tm-mb .rh-tm-file { display:flex; align-items:center; gap:10px; flex-wrap:wrap; } .rh-tm-mb .rh-tm-filename { color:#555; } .rh-tm-mb .rh-tm-poster-prev img { max-width:120px; height:auto; border-radius:6px; margin-top:6px; display:block; } .rh-tm-mb .rh-tm-hint { color:#777; font-size:12px; margin-top:4px; } .rh-tm-mb .rh-tm-media-prev { display:flex; gap:8px; flex-wrap:wrap; margin-top:8px; } .rh-tm-mb .rh-tm-thumb img { width:60px; height:60px; object-fit:cover; border-radius:6px; display:block; background:#eee; }

' . wp_kses_post( wp_get_attachment_image( $mid, array( 60, 60 ), true ) ) . ''; } ?>

' : ''; ?>
( function () { /* ── Media: multiple, append to the existing selection ── */ var mediaBtn = document.querySelector( '.rh-tm-pick-media' ); var mediaClear = document.querySelector( '.rh-tm-clear-media' ); var idsInput = document.getElementById( 'rh-tm-media-ids' ); var mediaPrev = document.querySelector( '.rh-tm-media-prev' ); var mediaFrame = null; function currentIds() { return idsInput.value ? idsInput.value.split( ',' ).filter( Boolean ) : []; } function thumbUrl( a ) { return ( a.sizes && a.sizes.thumbnail && a.sizes.thumbnail.url ) || a.icon || a.url; } function addThumb( a ) { var s = document.createElement( 'span' ); s.className = 'rh-tm-thumb'; s.innerHTML = ''; mediaPrev.appendChild( s ); } mediaBtn && mediaBtn.addEventListener( 'click', function ( e ) { e.preventDefault(); mediaFrame = wp.media( { title: , button: { text: }, multiple: 'add', } ); mediaFrame.on( 'select', function () { var ids = currentIds(); mediaFrame.state().get( 'selection' ).toJSON().forEach( function ( a ) { if ( ids.indexOf( String( a.id ) ) === -1 ) { ids.push( String( a.id ) ); addThumb( a ); } } ); idsInput.value = ids.join( ',' ); if ( ids.length && mediaClear ) { mediaClear.style.display = ''; } } ); mediaFrame.open(); } ); mediaClear && mediaClear.addEventListener( 'click', function ( e ) { e.preventDefault(); idsInput.value = ''; mediaPrev.innerHTML = ''; mediaClear.style.display = 'none'; } ); /* ── Poster: single image ── */ var posBtn = document.querySelector( '.rh-tm-pick-poster' ); var posClear = document.querySelector( '.rh-tm-clear-poster' ); var posInput = document.getElementById( 'rh-tm-poster-id' ); var posPrev = document.querySelector( '.rh-tm-poster-prev' ); var posFrame = null; posBtn && posBtn.addEventListener( 'click', function ( e ) { e.preventDefault(); if ( posFrame ) { posFrame.open(); return; } posFrame = wp.media( { title: , library: { type: 'image' }, multiple: false, } ); posFrame.on( 'select', function () { var a = posFrame.state().get( 'selection' ).first().toJSON(); posInput.value = a.id; var url = ( a.sizes && a.sizes.medium && a.sizes.medium.url ) || a.url; posPrev.innerHTML = url ? '' : ''; if ( posClear ) { posClear.style.display = ''; } } ); posFrame.open(); } ); posClear && posClear.addEventListener( 'click', function ( e ) { e.preventDefault(); posInput.value = ''; posPrev.innerHTML = ''; posClear.style.display = 'none'; } ); } )(); post_type ) { return; } wp_enqueue_media(); // WooCommerce's enhanced select powers the AJAX product search field. Both // handles are registered by WC itself; we only ask for them on this one // screen, so no other admin page pays for select2. if ( class_exists( 'WooCommerce' ) ) { wp_enqueue_script( 'wc-enhanced-select' ); wp_enqueue_style( 'woocommerce_admin_styles' ); } } /** * Persist what the moderator changed. * * @param int $post_id Testimonial id. * @param WP_Post $post The post object. * * @internal Hooked callback. */ public function save( $post_id, $post ): void { if ( ! isset( $_POST['rh_testimonial_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['rh_testimonial_nonce'] ) ), 'rh_testimonial_save' ) ) { return; } if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } if ( ! current_user_can( 'edit_post', $post_id ) ) { return; } $instagram = isset( $_POST['rh_tm_instagram'] ) ? sanitize_text_field( wp_unslash( $_POST['rh_tm_instagram'] ) ) : ''; $location = isset( $_POST['rh_tm_location'] ) ? sanitize_text_field( wp_unslash( $_POST['rh_tm_location'] ) ) : ''; $ids_raw = isset( $_POST['rh_tm_media_ids'] ) ? sanitize_text_field( wp_unslash( $_POST['rh_tm_media_ids'] ) ) : ''; $media_ids = array_values( array_filter( array_map( 'absint', explode( ',', $ids_raw ) ) ) ); $poster_id = isset( $_POST['rh_tm_poster_id'] ) ? absint( $_POST['rh_tm_poster_id'] ) : 0; // Pinned is written on EVERY save, 0 or 1 — never deleted. That keeps the // "rest" query a cheap `= 0` compare for everything saved from here on. $pinned = ! empty( $_POST['rh_tm_pinned'] ) ? 1 : 0; // Product: accept only a real, existing product ID. A stale or hand-typed // value would otherwise sit in the map and render a section on nothing. $product_id = isset( $_POST['rh_tm_product'] ) ? absint( $_POST['rh_tm_product'] ) : 0; if ( $product_id > 0 && 'product' !== get_post_type( $product_id ) ) { $product_id = 0; } $scope = TestimonialType::sanitizeScope( isset( $_POST['rh_tm_scope'] ) ? sanitize_key( wp_unslash( $_POST['rh_tm_scope'] ) ) : '' ); /* * «خریدار سایت» is re-derived only when the product changes. A * moderator cannot tick it — it comes from the order table — and an * unrelated edit must not drop a badge the customer earned when they * submitted (the order may since have been archived). */ $previous_product = (int) get_post_meta( $post_id, '_rh_tm_product', true ); if ( $product_id !== $previous_product ) { if ( $product_id && Purchases::bought( (int) $post->post_author, $product_id ) ) { update_post_meta( $post_id, '_rh_tm_verified', 1 ); } else { delete_post_meta( $post_id, '_rh_tm_verified' ); } } update_post_meta( $post_id, '_rh_tm_instagram', $instagram ); update_post_meta( $post_id, '_rh_tm_location', $location ); update_post_meta( $post_id, '_rh_tm_media_ids', $media_ids ); update_post_meta( $post_id, '_rh_tm_poster_id', $media_ids ? $poster_id : 0 ); update_post_meta( $post_id, '_rh_tm_pinned', $pinned ); // Absent means «everywhere» — the state of every testimonial saved // before this setting existed, so the rail's exclusion stays a cheap // NOT EXISTS / != compare. if ( 'all' === $scope ) { delete_post_meta( $post_id, '_rh_tm_scope' ); } else { update_post_meta( $post_id, '_rh_tm_scope', $scope ); } if ( ! empty( $_POST['rh_tm_via_instagram'] ) ) { update_post_meta( $post_id, '_rh_tm_via_instagram', 1 ); } else { delete_post_meta( $post_id, '_rh_tm_via_instagram' ); } if ( $product_id ) { update_post_meta( $post_id, '_rh_tm_product', $product_id ); } else { // Deleted, not set to 0: the product map query uses EXISTS, so an // unassigned testimonial must carry no row at all. delete_post_meta( $post_id, '_rh_tm_product' ); } // Drop legacy single-media meta so it can't shadow the new list. delete_post_meta( $post_id, '_rh_tm_media_id' ); delete_post_meta( $post_id, '_rh_tm_media_type' ); } }