ID ) ) { return; } add_meta_box( self::META_BOX_ID, self::title_html(), array( $this, 'render' ), 'product', 'side', 'default', // Заголовок блока — картинка. Имя простым текстом WordPress показывает // в «Настройках экрана», в списке блоков, которые можно скрыть. array( '__widget_basename' => __( 'aiModelka', 'aimodelka' ) ) ); // Только вместе с блоком: без него вставлять в порядок нечего. add_filter( 'get_user_option_meta-box-order_product', array( $this, 'place_before_product_image' ) ); } /** * Вставляет блок aiModelka в сохранённый порядок блоков прямо перед «Изображением * товара» — только при чтении порядка и только пока нашего блока в нём нет (см. описание * класса). * * Если блока «Изображение товара» нет (у человека нет права загружать файлы, и WordPress * его не показывает), наш блок встаёт перед «Галереей товара». Нет и её — порядок * не меняется, и блок стоит там, куда его ставит приоритет. * * @param mixed $order Сохранённый порядок: колонка («side», «normal», «advanced») → id блоков * через запятую; false — порядок не сохранён. * @return mixed Порядок для вывода страницы. */ public function place_before_product_image( mixed $order ): mixed { if ( ! is_array( $order ) ) { return $order; } foreach ( $order as $ids ) { if ( in_array( self::META_BOX_ID, explode( ',', (string) $ids ), true ) ) { return $order; } } foreach ( array( self::PRODUCT_IMAGE_BOX_ID, self::PRODUCT_GALLERY_BOX_ID ) as $anchor ) { foreach ( $order as $context => $ids ) { $boxes = explode( ',', (string) $ids ); $position = array_search( $anchor, $boxes, true ); if ( false !== $position ) { array_splice( $boxes, $position, 0, self::META_BOX_ID ); $order[ $context ] = implode( ',', $boxes ); return $order; } } } return $order; } /** * Выводит содержимое блока aiModelka. * * @param mixed $post Товар, который открыт в редакторе. * @return void */ public function render( mixed $post ): void { if ( ! $post instanceof \WP_Post || ! current_user_can( 'edit_product', $post->ID ) ) { return; } echo '
'; // Кнопка окна (при ключе) и «Упорядочить фото» (всегда) — общие с витриной. Product_Block::render( (int) $post->ID, Product_Block::PAGE_EDITOR ); if ( ! Config::has_api_key() && current_user_can( 'manage_woocommerce' ) ) { printf( '

%1$s %3$s

', esc_html__( 'To generate photos on a model, add your aiModelka key in the plugin settings.', 'aimodelka' ), esc_url( Settings_Page::url() ), esc_html__( 'Open settings', 'aimodelka' ) ); } elseif ( ! Config::has_api_key() ) { printf( '

%s

', esc_html__( 'To generate photos on a model, ask the shop administrator to add the aiModelka key in WooCommerce → aiModelka.', 'aimodelka' ) ); } echo '
'; } /** * Подключает скрипт на странице редактирования товара и передаёт ему данные товара. * * @param string $hook_suffix Какая страница админки открыта. * @return void */ public function enqueue_assets( string $hook_suffix ): void { if ( 'post.php' !== $hook_suffix && 'post-new.php' !== $hook_suffix ) { return; } $screen = get_current_screen(); $post = get_post(); if ( null === $screen || 'product' !== $screen->post_type || ! $post || ! current_user_can( 'edit_product', $post->ID ) ) { return; } Assets::enqueue_editor( array( 'screen' => 'product', 'product' => Product_Block::page_data( (int) $post->ID, Product_Block::PAGE_EDITOR ), ) ); } /** * Заголовок блока: логотип aiModelka и его название для программ чтения с экрана. * * WordPress выводит заголовок блока как есть, без экранирования, поэтому всё, что сюда * попадает, экранируется здесь. Картинка — без подписи (alt=""): название рядом, скрытым * текстом, и WordPress берёт его же, когда подписывает блок для программ чтения с экрана. * Картинку нельзя утащить мышью и она не ловит нажатия: заголовок блока — это ручка, * за которую блок перетаскивают и сворачивают. * * @return string Разметка заголовка. */ private static function title_html(): string { return sprintf( '%4$s', esc_url( add_query_arg( 'ver', AIMODELKA_VERSION, AIMODELKA_PLUGIN_URL . self::LOGO_FILE ) ), self::LOGO_WIDTH, self::LOGO_HEIGHT, esc_html__( 'aiModelka', 'aimodelka' ) ); } }