/** * Plugin Name: Product Image Keylines * Description: Add configurable keylines to WooCommerce product and gallery images with per-image controls for padding, thickness, style, and color. * Version: 1.0.0 * Author: iTupi.co * Author URI: https://itupi.co/ * Text Domain: product-image-keylines * License: GPL-2.0-or-later * License URI: https://www.gnu.org/licenses/gpl-2.0.html * Requires at least: 6.0 * Tested up to: 6.8 * Requires PHP: 7.4 * WC requires at least: 8.0 * WC tested up to: 10.8 */ if ( ! defined( 'ABSPATH' ) ) { exit; } class Woo_Product_Image_Keylines { const POSITIONS_META_KEY = '_woo_product_image_keylines_positions'; const PADDING_META_KEY = '_woo_product_image_keylines_padding'; const WIDTH_META_KEY = '_woo_product_image_keylines_width'; const STYLE_META_KEY = '_woo_product_image_keylines_style'; const COLOR_META_KEY = '_woo_product_image_keylines_color'; const NONCE = 'woo_product_image_keylines_nonce'; public function __construct() { add_action( 'add_meta_boxes', array( $this, 'add_metabox' ) ); add_action( 'save_post_product', array( $this, 'save_metabox' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_front_assets' ) ); add_filter( 'woocommerce_single_product_image_thumbnail_html', array( $this, 'filter_single_product_image_html' ), 10, 2 ); } public function filter_main_product_image_html( $html, $post_thumbnail_id ) { $product_id = $this->current_product_id(); if ( ! $this->has_keyline_for_attachment( $product_id, $post_thumbnail_id ) ) { return $html; } return $this->add_img_class( $html ); } public function add_metabox() { add_meta_box( 'woo-product-image-keylines', __( 'Product Image Keylines', 'product-image-keylines' ), array( $this, 'render_metabox' ), 'product', 'side', 'default' ); } private function get_image_ids( $post_id ) { $ids = array(); $featured_id = (int) get_post_thumbnail_id( $post_id ); if ( $featured_id ) { $ids[] = $featured_id; } $gallery_ids = get_post_meta( $post_id, '_product_image_gallery', true ); if ( ! empty( $gallery_ids ) ) { $gallery_ids = array_map( 'absint', array_filter( array_map( 'trim', explode( ',', $gallery_ids ) ) ) ); $ids = array_merge( $ids, $gallery_ids ); } return array_values( array_unique( $ids ) ); } private function get_enabled_positions( $post_id ) { $stored = get_post_meta( $post_id, self::POSITIONS_META_KEY, true ); if ( empty( $stored ) || ! is_string( $stored ) ) { return array(); } $positions = array_map( 'absint', array_filter( array_map( 'trim', explode( ',', $stored ) ) ) ); $positions = array_values( array_unique( array_filter( $positions ) ) ); sort( $positions ); return $positions; } private function get_padding( $post_id ) { return max( 0, absint( get_post_meta( $post_id, self::PADDING_META_KEY, true ) ) ); } private function get_width( $post_id ) { $width = absint( get_post_meta( $post_id, self::WIDTH_META_KEY, true ) ); return $width >= 1 ? $width : 1; } private function get_style( $post_id ) { $style = get_post_meta( $post_id, self::STYLE_META_KEY, true ); $allowed = array( 'solid', 'dashed', 'dotted' ); return in_array( $style, $allowed, true ) ? $style : 'solid'; } private function get_color( $post_id ) { $color = get_post_meta( $post_id, self::COLOR_META_KEY, true ); if ( is_string( $color ) && preg_match( '/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/', $color ) ) { return $color; } return '#000000'; } public function render_metabox( $post ) { wp_nonce_field( 'woo_product_image_keylines_save', self::NONCE ); $image_ids = $this->get_image_ids( $post->ID ); $enabled_positions = $this->get_enabled_positions( $post->ID ); $padding = $this->get_padding( $post->ID ); $width = $this->get_width( $post->ID ); $style = $this->get_style( $post->ID ); $color = $this->get_color( $post->ID ); echo '
'; echo '
'; echo '


'; echo '

'; echo '


'; echo '

'; echo '


'; echo '

'; echo '


'; echo '

'; echo '
'; if ( empty( $image_ids ) ) { echo '

Add a featured image or gallery images first, then return here to choose which image positions get keylines.

'; echo '
'; return; } foreach ( $image_ids as $index => $image_id ) { $position = $index + 1; $thumb = wp_get_attachment_image( $image_id, array( 60, 60 ), false, array( 'style' => 'width:60px;height:60px;object-fit:cover;display:block;' ) ); $label = ( 1 === $position ) ? 'Main image' : 'Gallery image'; $is_checked = in_array( $position, $enabled_positions, true ); echo '
'; echo '
' . wp_kses( $thumb, array( 'img' => array( 'src' => true, 'srcset' => true, 'sizes' => true, 'class' => true, 'alt' => true, 'width' => true, 'height' => true, 'loading' => true, 'decoding' => true, 'style' => true, 'aria-describedby' => true, ), ) ) . '
'; echo '
'; echo '' . esc_html( $label ) . ' #' . esc_html( (string) $position ) . ''; echo ''; echo '
'; echo '
'; } $stored_value = get_post_meta( $post->ID, self::POSITIONS_META_KEY, true ); echo '

Saved positions meta: ' . esc_html( $stored_value ? $stored_value : '' ) . '

'; echo '

Example: 1,3,5 means the main image, third image, and fifth image in the product image order will have keylines.

'; echo ''; } public function save_metabox( $post_id ) { if ( ! isset( $_POST[ self::NONCE ] ) ) { return; } $nonce = sanitize_text_field( wp_unslash( $_POST[ self::NONCE ] ) ); if ( ! wp_verify_nonce( $nonce, 'woo_product_image_keylines_save' ) ) { return; } if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } if ( ! current_user_can( 'edit_post', $post_id ) ) { return; } $image_ids = $this->get_image_ids( $post_id ); $max_position = count( $image_ids ); $submitted = array(); if ( isset( $_POST['woo_product_image_keylines_positions'] ) && is_array( $_POST['woo_product_image_keylines_positions'] ) ) { $submitted = array_map( 'absint', wp_unslash( $_POST['woo_product_image_keylines_positions'] ) ); } $clean_positions = array(); foreach ( $submitted as $position ) { if ( $position >= 1 && $position <= $max_position ) { $clean_positions[] = $position; } } $clean_positions = array_values( array_unique( $clean_positions ) ); sort( $clean_positions ); update_post_meta( $post_id, self::POSITIONS_META_KEY, implode( ',', $clean_positions ) ); update_post_meta( $post_id, self::PADDING_META_KEY, max( 0, absint( wp_unslash( $_POST['woo_product_image_keylines_padding'] ?? 0 ) ) ) ); update_post_meta( $post_id, self::WIDTH_META_KEY, max( 1, absint( wp_unslash( $_POST['woo_product_image_keylines_width'] ?? 1 ) ) ) ); $style = sanitize_text_field( wp_unslash( $_POST['woo_product_image_keylines_style'] ?? 'solid' ) ); $allowed = array( 'solid', 'dashed', 'dotted' ); update_post_meta( $post_id, self::STYLE_META_KEY, in_array( $style, $allowed, true ) ? $style : 'solid' ); $color = sanitize_hex_color( wp_unslash( $_POST['woo_product_image_keylines_color'] ?? '#000000' ) ); update_post_meta( $post_id, self::COLOR_META_KEY, $color ? $color : '#000000' ); } public function enqueue_admin_assets( $hook ) { global $post; if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) { return; } if ( ! $post || 'product' !== get_post_type( $post ) ) { return; } $css = ' .woo-keylines-metabox { display:flex; flex-direction:column; gap:10px; } .woo-keylines-settings { padding-bottom:10px; margin-bottom:4px; border-bottom:1px solid #dcdcde; } .woo-keylines-settings p { margin:0 0 10px; } .woo-keylines-settings select, .woo-keylines-settings input[type="number"] { width:100%; max-width:100%; } .woo-keylines-item { display:flex; gap:10px; align-items:flex-start; padding:8px 0; border-bottom:1px solid #dcdcde; } .woo-keylines-item:last-of-type { border-bottom:none; } .woo-keylines-thumb img { border:1px solid #dcdcde; background:#fff; } .woo-keylines-fields { display:flex; flex-direction:column; gap:6px; min-width:0; } .woo-keylines-fields strong { line-height:1.3; } .woo-keylines-fields label { display:flex; gap:6px; align-items:center; } .woo-keylines-metabox code { word-break:break-word; } '; wp_register_style( 'woo-product-image-keylines-admin', false, array(), '1.0.0' ); wp_enqueue_style( 'woo-product-image-keylines-admin' ); wp_add_inline_style( 'woo-product-image-keylines-admin', $css ); } public function enqueue_front_assets() { if ( ! is_product() ) { return; } $product_id = get_the_ID(); if ( ! $product_id ) { return; } $padding = $this->get_padding( $product_id ); $width = $this->get_width( $product_id ); $style = $this->get_style( $product_id ); $color = $this->get_color( $product_id ); $css = ' img.woo-keyline-image, .woocommerce-product-gallery__image img.woo-keyline-image, .flex-control-thumbs img.woo-keyline-image, .flex-control-thumbs img.woo-keyline-image.flex-active, .flex-control-thumbs li img.woo-keyline-image.flex-active { border-width: ' . absint( $width ) . 'px !important; border-style: ' . esc_attr( $style ) . ' !important; border-color: ' . esc_attr( $color ) . ' !important; box-sizing: border-box; padding: ' . absint( $padding ) . 'px !important; background: transparent; } '; wp_register_style( 'woo-product-image-keylines-front', false, array(), '1.0.0' ); wp_enqueue_style( 'woo-product-image-keylines-front' ); wp_add_inline_style( 'woo-product-image-keylines-front', $css ); } private function current_product_id() { global $product, $post; if ( $product && is_a( $product, 'WC_Product' ) ) { return (int) $product->get_id(); } if ( $post && 'product' === get_post_type( $post ) ) { return (int) $post->ID; } return 0; } private function get_position_by_attachment_id( $product_id, $attachment_id ) { $image_ids = $this->get_image_ids( $product_id ); $index = array_search( (int) $attachment_id, $image_ids, true ); if ( false === $index ) { return 0; } return (int) $index + 1; } private function has_keyline_for_attachment( $product_id, $attachment_id ) { if ( ! $product_id || ! $attachment_id ) { return false; } $position = $this->get_position_by_attachment_id( $product_id, $attachment_id ); $enabled_positions = $this->get_enabled_positions( $product_id ); return $position && in_array( $position, $enabled_positions, true ); } private function add_img_class( $html ) { if ( false !== strpos( $html, 'woo-keyline-image' ) ) { return $html; } if ( preg_match( '/]*class=([\'"])(.*?)\1/i', $html ) ) { return preg_replace( '/]*?)class=([\'"])(.*?)\2([^>]*)>/i', '', $html, 1 ); } return preg_replace( '/]*class=)([^>]*)>/i', '', $html, 1 ); } public function filter_single_product_image_html( $html, $attachment_id ) { $product_id = $this->current_product_id(); if ( ! $this->has_keyline_for_attachment( $product_id, $attachment_id ) ) { return $html; } return $this->add_img_class( $html ); } public function filter_gallery_thumbnail_html( $html, $attachment_id ) { $product_id = $this->current_product_id(); if ( ! $this->has_keyline_for_attachment( $product_id, $attachment_id ) ) { return $html; } return $this->add_img_class( $html ); } } // Declare compatibility with WooCommerce High-Performance Order Storage (HPOS). add_action( 'before_woocommerce_init', function() { if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true ); } } ); new Woo_Product_Image_Keylines(); Page not found - BrownTrout
404

Oops! That page can’t be found.

It looks like nothing was found at this location. Maybe try one of the links below or a search?

error: Content is protected !!
This site uses cookies to offer you a better browsing experience. By browsing this website, you agree to our use of cookies.