*/ public static function page_data( int $product_id, string $page ): array { $product = wc_get_product( $product_id ); $data = array( 'page' => $page, 'id' => $product_id, 'hasKey' => Config::has_api_key(), // Тип → id фотосессии, '' — такой фотосессии у товара нет. 'photoSessions' => Photo_Kinds::stored_sessions( $product_id ), // Строка о товаре под кнопками — готовой: «N photos in the product · дата», // '' — фото aiModelka в товар ещё не сохранялись. Число и дату отдельно скрипт // не получает: форму слова по числу и формат даты сайта он сам правильно // не построит. После сохранения свежую строку он берёт из ответа «save» // (Rest\Routes::save). На витрине строки нет, но данные общие. 'savedText' => Saved_Photos::product_box_text( $product_id ), 'preparePath' => '/aimodelka/v1/products/' . $product_id . '/prepare', 'photosPath' => '/aimodelka/v1/products/' . $product_id . '/photos', 'uploadPath' => '/aimodelka/v1/products/' . $product_id . '/photos/upload', 'statePath' => '/aimodelka/v1/products/' . $product_id . '/state', 'savePath' => '/aimodelka/v1/products/' . $product_id . '/save', // Начало имени окна — по товару и сайту; скрипт дописывает тип. Повторное нажатие той // же кнопки — и в редакторе, и на витрине — попадает в то же окно, кнопка другого // типа открывает своё, а одинаковый id товара в двух магазинах окна не делит. 'windowName' => 'aimodelka_' . substr( md5( home_url() ), 0, 8 ) . '_' . $product_id, 'siteOrigin' => Config::site_origin(), 'texts' => self::texts(), 'productName' => $product ? trim( wp_strip_all_tags( $product->get_name( 'edit' ) ) ) : '', 'imagesPath' => '/aimodelka/v1/products/' . $product_id . '/images', 'photosTexts' => self::photos_texts(), ); if ( self::PAGE_STOREFRONT === $page ) { $data['gallery'] = self::saved_gallery( $product_id ); $data['collapsePath'] = '/aimodelka/v1/storefront-block'; } return $data; } /** * Печатает кнопки и строку состояния — содержимое блока; обёртку печатает вызывающий. * * Сверху вниз: три кнопки окна в порядке меню «Создать фото» aiModelka — только при ключе * aiModelka (окну без ключа открыть нечего) — и «Упорядочить фото», которая открывает окошко * порядка фото прямо на странице и ключа не требует (Documents/WPPlugin-HLD.md §2.10). * Кнопки — во всю ширину блока на обеих страницах. Под ними строка состояния, а в редакторе * при ключе ещё строка о товаре: подсказка, что делают кнопки окна, или «N photos in the * product · дата». Id элементов одни на обе страницы — скрипт находит их по id, а страница * открыта одна; классы — свои у каждой страницы, под свои стили. * * @param int $product_id Id товара. * @param string $page PAGE_EDITOR или PAGE_STOREFRONT. * @return void */ public static function render( int $product_id, string $page ): void { $sessions = Photo_Kinds::stored_sessions( $product_id ); $prefix = self::PAGE_STOREFRONT === $page ? 'aimodelka-storefront' : 'aimodelka-product-box'; $photos_button = sprintf( '', esc_attr( $prefix ), esc_html( self::photos_texts()['button'] ) ); if ( self::PAGE_STOREFRONT === $page ) { echo '
'; } printf( '', esc_attr( $prefix ) ); if ( self::PAGE_EDITOR === $page && Config::has_api_key() ) { printf( '%s
', esc_html( self::summary( $product_id, array() !== array_filter( $sessions ) ) ) ); } } /** * Разметка кнопки одного типа: пиктограмма слева, название и — в редакторе — состояние. * * Класс «button» общий: в админке это кнопка WordPress, на витрине — кнопка WooCommerce, * которую оформляет тема. Основной синий вид WordPress кнопкам не нужен: три залитые кнопки * слишком выделялись бы среди блоков редактора. * Пиктограмма — та же картинка, что в меню «Создать фото» aiModelka; слова о ней говорит * название рядом, поэтому alt пустой. * * @param string $kind Тип фотосессии. * @param bool $has_session Есть ли у товара фотосессия этого типа. * @param string $page PAGE_EDITOR или PAGE_STOREFRONT. * @param string $prefix Приставка классов страницы. * @return string Разметка. */ private static function kind_button( string $kind, bool $has_session, string $page, string $prefix ): string { $texts = self::texts(); $classes = array( 'button', $prefix . '__button', 'aimodelka-kind-button' ); $storefront = self::PAGE_STOREFRONT === $page; $state = $has_session ? $texts['buttonOpen'] : $texts['buttonGenerate']; return sprintf( '', esc_attr( implode( ' ', $classes ) ), esc_attr( $kind ), esc_url( add_query_arg( 'ver', AIMODELKA_VERSION, AIMODELKA_PLUGIN_URL . 'assets/kinds/' . $kind . '.jpg' ) ), esc_html( self::kind_name( $kind ) ), $storefront ? '' : '', $storefront ? 24 : 40 ); } /** * Название типа фотосессии на кнопке — те же слова, что в меню «Создать фото» aiModelka. * * @param string $kind Тип фотосессии. * @return string Название на языке страницы. */ private static function kind_name( string $kind ): string { switch ( $kind ) { case Photo_Kinds::GHOST_MANNEQUIN: return __( 'Ghost mannequin', 'aimodelka' ); case Photo_Kinds::PRODUCT_SCENE: return __( 'Product in scene', 'aimodelka' ); default: return __( 'Photo on an AI model', 'aimodelka' ); } } /** * Изображение товара и галерея из сохранённого товара — для витрины, где полей формы нет. * * @param int $product_id Id товара. * @return array{featuredId: int, galleryIds: int[]} Изображение товара (0 — его нет) и галерея по порядку. */ public static function saved_gallery( int $product_id ): array { $product = wc_get_product( $product_id ); if ( ! $product ) { return array( 'featuredId' => 0, 'galleryIds' => array(), ); } return array( 'featuredId' => (int) $product->get_image_id( 'edit' ), 'galleryIds' => array_values( array_map( 'intval', $product->get_gallery_image_ids( 'edit' ) ) ), ); } /** * Строка о товаре под кнопками в редакторе — та же, что выберет скрипт страницы * (aimodelka-admin.js, renderBox): ни одной фотосессии нет — подсказка, что делают кнопки; * есть хоть одна — сколько фото aiModelka в товаре по всем типам, а если их ещё нет — что * фотосессия начата. * * @param int $product_id Id товара. * @param bool $has_session Есть ли у товара хоть одна фотосессия. * @return string Строка. */ private static function summary( int $product_id, bool $has_session ): string { $texts = self::texts(); if ( ! $has_session ) { return $texts['hint']; } $saved = Saved_Photos::product_box_text( $product_id ); return '' !== $saved ? $saved : $texts['shootStarted']; } /** * Тексты блока, скрипта страницы товара и временной страницы окна — на языке страницы. * * Кнопки в советах называются «the aiModelka button in the product» или «the same button»: * кнопок окна три, и название одной из них в общем совете указало бы не на ту. * * @return array