'affirm-blocks', 'editor_style' => 'affirm-blocks-editor', 'render_callback' => array($this, 'render_image_block'), 'attributes' => array( 'imageId' => array('type' => 'string', 'default' => ''), 'thumbnailUrl' => array('type' => 'string', 'default' => ''), 'filename' => array('type' => 'string', 'default' => ''), 'width' => array('type' => 'string', 'default' => '100%'), 'height' => array('type' => 'string', 'default' => 'auto'), 'mode' => array('type' => 'string', 'default' => ''), 'imgWidth' => array('type' => 'number', 'default' => 0), 'imgHeight' => array('type' => 'number', 'default' => 0), ), )); register_block_type('affirm/gallery', array( 'editor_script' => 'affirm-blocks', 'editor_style' => 'affirm-blocks-editor', 'render_callback' => array($this, 'render_gallery_block'), 'attributes' => array( 'images' => array('type' => 'array', 'default' => array()), 'columns' => array('type' => 'number', 'default' => 3), 'gap' => array('type' => 'number', 'default' => 8), 'template' => array('type' => 'string', 'default' => 'grid'), 'lightbox' => array('type' => 'boolean', 'default' => true), 'captions' => array('type' => 'boolean', 'default' => false), 'hoverEffect' => array('type' => 'string', 'default' => 'zoom'), 'borderRadius' => array('type' => 'number', 'default' => 8), 'mode' => array('type' => 'string', 'default' => ''), ), )); } /** * Enqueue block editor scripts and styles */ public function enqueue_block_assets() { if (!get_option('affirm_api_key')) return; wp_enqueue_script( 'affirm-blocks', AFFIRM_PLUGIN_URL . 'assets/js/blocks.js', array('wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-data', 'jquery'), AFFIRM_VERSION, true ); wp_enqueue_style( 'affirm-blocks-editor', AFFIRM_PLUGIN_URL . 'assets/css/blocks.css', array('wp-edit-blocks'), AFFIRM_VERSION ); if (!wp_script_is('affirm-media-vault', 'enqueued')) { global $post; $post_id = ($post && isset($post->ID)) ? $post->ID : 0; wp_localize_script('affirm-blocks', 'affirmVault', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('affirm_admin_nonce'), 'postId' => $post_id, 'backendUrl' => affirm_get_backend_url(), 'featuredImageId' => $post_id ? get_post_meta($post_id, 'affirm_featured_image_id', true) : '', )); } } /** * Frontend: enqueue gallery CSS + lightbox JS only when the post has a gallery block */ public function enqueue_frontend_gallery_assets() { if (!is_singular()) return; global $post; if (!$post || !has_block('affirm/gallery', $post)) return; wp_enqueue_style( 'affirm-gallery', AFFIRM_PLUGIN_URL . 'assets/css/gallery.css', array(), AFFIRM_VERSION ); wp_enqueue_script( 'affirm-gallery', AFFIRM_PLUGIN_URL . 'assets/js/gallery.js', array(), AFFIRM_VERSION, true ); } /** * Server-side render: affirm/image block */ public function render_image_block($attributes) { if (empty($attributes['imageId'])) return ''; $id = esc_attr(sanitize_text_field($attributes['imageId'])); // Strictly validate dimensions so block attributes can't inject CSS. $width = Affirm_Shortcode::sanitize_dimension(!empty($attributes['width']) ? $attributes['width'] : '100%', '100%'); $height = Affirm_Shortcode::sanitize_dimension(!empty($attributes['height']) ? $attributes['height'] : 'auto', 'auto'); $mode = !empty($attributes['mode']) ? ' mode="' . esc_attr($attributes['mode']) . '"' : ''; return do_shortcode('[affirm_protect id="' . $id . '" width="' . $width . '" height="' . $height . '"' . $mode . ']'); } /** * Server-side render: affirm/gallery block * Outputs proper gallery HTML with template classes, lightbox data, and captions */ public function render_gallery_block($attributes) { if (empty($attributes['images']) || !is_array($attributes['images'])) return ''; $template = !empty($attributes['template']) ? sanitize_text_field($attributes['template']) : 'grid'; $columns = max(1, min(6, isset($attributes['columns']) ? absint($attributes['columns']) : 3)); $gap = max(0, min(32, isset($attributes['gap']) ? absint($attributes['gap']) : 8)); $lightbox = isset($attributes['lightbox']) ? (bool) $attributes['lightbox'] : true; $captions = isset($attributes['captions']) ? (bool) $attributes['captions'] : false; $hover_effect = !empty($attributes['hoverEffect']) ? sanitize_text_field($attributes['hoverEffect']) : 'zoom'; $border_radius = isset($attributes['borderRadius']) ? absint($attributes['borderRadius']) : 8; $mode = !empty($attributes['mode']) ? sanitize_text_field($attributes['mode']) : ''; $gallery_id = 'affirm-gallery-' . wp_unique_id(); $classes = array('affirm-gallery', 'affirm-gallery--' . $template); if ($lightbox) $classes[] = 'affirm-gallery--lightbox'; if ($hover_effect) $classes[] = 'affirm-gallery--hover-' . $hover_effect; // Build CSS custom properties for the gallery $style_vars = sprintf( '--af-columns:%d;--af-gap:%dpx;--af-radius:%dpx;', $columns, $gap, $border_radius ); $items = ''; $index = 0; foreach ($attributes['images'] as $img) { if (empty($img['id'])) continue; $img_id = esc_attr(sanitize_text_field($img['id'])); $filename = !empty($img['filename']) ? esc_attr($img['filename']) : ''; $thumb = !empty($img['thumbnailUrl']) ? esc_url($img['thumbnailUrl']) : ''; // Build the shortcode for the actual protected image $sc = '[affirm_protect id="' . $img_id . '"'; if ($mode) $sc .= ' mode="' . esc_attr($mode) . '"'; $sc .= ' width="100%" height="auto"]'; $item_html = ''; $items .= $item_html; $index++; } if (empty($items)) return ''; // Slider uses a wrapper structure: outer (relative) > track (scrollable) + nav + dots if ($template === 'slider') { $inner = ''; $inner .= '' . '' . ''; return sprintf( '
%s
', esc_attr($gallery_id), esc_attr(implode(' ', $classes)), esc_attr($style_vars), esc_attr($template), $columns, $inner ); } return sprintf( '
%s
', esc_attr($gallery_id), esc_attr(implode(' ', $classes)), esc_attr($style_vars), esc_attr($template), $columns, $items ); } /** * Classic Editor: Add aFFirmFirst button to TinyMCE toolbar */ public function register_tinymce_button($buttons) { if (!current_user_can('edit_posts') || !get_option('affirm_api_key')) { return $buttons; } $buttons[] = 'affirm_insert'; return $buttons; } /** * Classic Editor: Register TinyMCE plugin JS */ public function register_tinymce_plugin($plugins) { if (!current_user_can('edit_posts') || !get_option('affirm_api_key')) { return $plugins; } $plugins['affirm_insert'] = AFFIRM_PLUGIN_URL . 'assets/js/tinymce-plugin.js?v=' . AFFIRM_VERSION; return $plugins; } }