exit; } if ( ! function_exists( 'wp_create_nonce' ) ) { require_once ABSPATH . WPINC . '/pluggable.php'; } class InkByAIGallery { const OPT_SET = 'ibag_opt'; const OPT_EFF = 'ibag_effects'; public static function sets(): array { return get_option( self::OPT_SET, [] ); } public static function effs(): array { return get_option( self::OPT_EFF, [ 'hover_effect' => 'hover-zoom', 'neon_color' => '#00ffff', 'lightbox_effect' => 'fade', 'lightbox_on' => true, 'share_position_top' => false, 'share_position_bottom' => false, ] ); } } /* ------------------------------------------------------------------------- * Uniwersalny URL do pojedynczego zdjęcia /foto/{ID} * ---------------------------------------------------------------------- */ function ibag_photo_url( int $id ): string { return home_url( user_trailingslashit( "foto/$id" ) ); } /* ------------------------------------------------------------------------- * REWRITE: /foto/{ID} ➜ index.php?photo_id={ID} * ---------------------------------------------------------------------- */ add_action( 'init', 'ibag_add_rewrite_rules' ); function ibag_add_rewrite_rules() { add_rewrite_rule( '^foto/([0-9]+)/?$', 'index.php?photo_id=$matches[1]', 'top' ); } /* —— SINGLE-IMAGE template —— */ add_filter( 'template_include', 'ibag_template_photo' ); function ibag_template_photo( $template ) { if ( get_query_var( 'photo_id' ) ) { return plugin_dir_path( __FILE__ ) . 'templates/ibag-photo.php'; } return $template; } add_filter( 'query_vars', static function ( $vars ) { $vars[] = 'photo_id'; return $vars; } ); // ← domknięta klamra function ibag_rewrite_flush() { ibag_add_rewrite_rules(); flush_rewrite_rules(); } register_activation_hook( __FILE__, 'ibag_rewrite_flush' ); register_deactivation_hook( __FILE__, 'flush_rewrite_rules' ); /* --------------------------------------------------------------------- */ function ibag_modula_active(): bool { return class_exists( 'ModulaLite' ) || class_exists( 'ModulaPro' ) || class_exists( 'Modula' ); } require_once __DIR__ . '/inkbyaig-compat.php'; /* ------------------------------------------------------------------------- * REST API – lista obrazów w galerii * ---------------------------------------------------------------------- */ add_action( 'rest_api_init', function () { register_rest_route( 'ibag/v1', '/gallery/(?P\\d+)', [ 'methods' => 'GET', 'callback' => 'ibag_rest_gallery', 'permission_callback' => '__return_true', ] ); } ); function ibag_rest_gallery( $request ) { $gallery_id = $request->get_param( 'id' ); $g = ibag_get_gals(); if ( ! isset( $g[ $gallery_id ] ) ) { return new WP_Error( 'not_found', 'Gallery not found', [ 'status' => 404 ] ); } $images = []; foreach ( $g[ $gallery_id ]['image_ids'] as $id ) { if ( wp_attachment_is_image( $id ) ) { $images[] = [ 'id' => $id, 'url' => wp_get_attachment_image_url( $id, 'full' ), 'alt' => get_post_meta( $id, '_wp_attachment_image_alt', true ) ?: get_the_title( $id ), ]; } } return [ 'id' => $gallery_id, 'name' => $g[ $gallery_id ]['name'] ?? '', 'images' => $images, ]; } /* ------------------------------------------------------------------------- * AJAX: miniatury pod lightboxem (50 % podobne / 50 % losowe) * ---------------------------------------------------------------------- */ add_action( 'wp_ajax_ibag_related', 'ibag_ajax_related' ); add_action( 'wp_ajax_nopriv_ibag_related', 'ibag_ajax_related' ); function ibag_ajax_related() { check_ajax_referer( 'ibag_nonce', 'sec' ); $id = absint( $_POST['id'] ?? 0 ); $limit = absint( $_POST['limit'] ?? 7 ); $gallery = isset( $_POST['gallery'] ) ? absint( $_POST['gallery'] ) : 0; if ( ! $id ) { wp_send_json_error(); } /* --- pobierz tagi bieżącego załącznika --- */ $tags = wp_get_object_terms( $id, 'post_tag', [ 'fields' => 'ids' ] ); /* --- 50 % podobne (po tagach) --- */ $similar = $tags ? get_posts( [ 'post_type' => 'attachment', 'post_status' => 'inherit', 'posts_per_page' => ceil( $limit * 0.5 ), 'post__not_in' => [ $id ], 'orderby' => 'rand', 'tax_query' => [ [ 'taxonomy' => 'post_tag', 'terms' => $tags ], ], ] ) : []; /* --- 50 % losowe z bieżącej galerii (fallback: ze wszystkich) --- */ if ( $gallery && isset( ibag_get_gals()[ $gallery ] ) ) { $all_ids = ibag_get_gals()[ $gallery ]['image_ids']; // tylko z tej galerii } else { $all_ids = array_unique( array_merge( ...array_column( ibag_get_gals(), 'image_ids' ) ) ); } $all_ids = array_diff( $all_ids, [ $id ], wp_list_pluck( $similar, 'ID' ) ); shuffle( $all_ids ); $rand_ids = array_slice( $all_ids, 0, $limit - count( $similar ) ); $random = $rand_ids ? get_posts( [ 'post_type' => 'attachment', 'post_status' => 'inherit', 'post__in' => $rand_ids, ] ) : []; /* --- scal & wyślij --- */ $items = array_merge( $similar, $random ); shuffle( $items ); $out = array_map( static function ( $p ) { return [ 'id' => $p->ID, 'thumb' => wp_get_attachment_image_url( $p->ID, 'thumbnail' ), 'url' => ibag_photo_url( $p->ID ), ]; }, $items ); wp_send_json_success( $out ); } /* ------------------------------------------------------------------------- * CACHE – czyść przy aktualizacji galerii * ---------------------------------------------------------------------- */ function ibag_clear_gallery_cache( $gallery_id ) { $file = WP_CONTENT_DIR . "/cache/ibag/gallery-{$gallery_id}.html"; if ( file_exists( $file ) ) { unlink( $file ); } } add_action( 'ibag_gallery_updated', 'ibag_clear_gallery_cache' ); /* ------------------------------------------------------------------------- * ADMIN MENU + skrypty * ---------------------------------------------------------------------- */ add_action( 'admin_menu', static function () { add_menu_page( 'InkByAI Gallery', 'InkByAI Gallery', 'manage_options', 'inkbyaig', static function () { require __DIR__ . '/admin-page-my.php'; }, 'dashicons-format-gallery', 76 ); add_submenu_page( 'inkbyaig', 'Ustawienia', 'Ustawienia', 'manage_options', 'inkbyaig_opt', static function () { require __DIR__ . '/admin-page-options.php'; } ); add_submenu_page( 'inkbyaig', 'Efekty', 'Efekty', 'manage_options', 'inkbyaig_eff', static function () { require __DIR__ . '/admin-page-effects.php'; } ); if ( ibag_modula_active() ) { add_submenu_page( 'inkbyaig', 'Import Modula', 'Import Modula', 'manage_options', 'inkbyaig_modula', static function () { require __DIR__ . '/admin-page-modula.php'; } ); } }, 5 ); add_action('admin_enqueue_scripts', static function ($hook) { if (strpos($hook, 'inkbyaig') === false) { return; } wp_enqueue_media(); wp_enqueue_script( 'ibag-media', plugin_dir_url(__FILE__) . 'ibag-media.js', ['jquery'], filemtime(__DIR__ . '/ibag-media.js'), true ); wp_localize_script('ibag-media', 'ibagAjax', [ 'url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('ibag_nonce'), 'restUrl' => rest_url('wp/v2/media'), 'restNonce' => wp_create_nonce('wp_rest'), ]); wp_enqueue_style( 'ibag-admin-style', plugin_dir_url(__FILE__) . 'inkbyaigallery.css', [], filemtime(__DIR__ . '/inkbyaigallery.css') ); }); /* ------------------------------------------------------------------------- * FRONT-END skrypty + style * ---------------------------------------------------------------------- */ add_action('wp_enqueue_scripts', static function () { if (is_admin() || wp_doing_ajax()) { return; } wp_enqueue_script( 'ibag-frontend', plugin_dir_url(__FILE__) . 'ibag-frontend.js', [], filemtime(__DIR__ . '/ibag-frontend.js'), true ); wp_script_add_data('ibag-frontend', 'rocket-exclude', '1'); wp_script_add_data('ibag-frontend', 'data-no-minify', '1'); wp_localize_script('ibag-frontend', 'ibagAjax', [ 'url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('ibag_nonce'), ]); wp_localize_script('ibag-frontend', 'ibagSet', [ 'settings' => InkByAIGallery::sets(), 'effects' => InkByAIGallery::effs(), ]); wp_enqueue_style( 'ibag-style', plugin_dir_url(__FILE__) . 'inkbyaigallery.css', [], filemtime(__DIR__ . '/inkbyaigallery.css') ); }); /* ------------------------------------------------------------------------- * HTML kafelka  — BOT-friendly (src = prawdziwa miniatura) * ---------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- * HTML kafelka – placeholder dla userów, data-src dla JS, * BOT-FIX zamieni na src=miniatura. * ---------------------------------------------------------------------- */ function ibag_thumb_html( int $id ): string { $opt = get_option( 'ibag_opt', [ 'thumb' => 150 ] ); $thumb_size = (int) ( $opt['thumb'] ?? 150 ); $src_medium = wp_get_attachment_image_url( $id, 'medium' ); $src_full = wp_get_attachment_image_url( $id, 'full' ); if ( ! $src_medium || ! $src_full ) { return ''; } $meta = wp_get_attachment_metadata( $id ); $w = (int) ( $meta['width'] ?? $thumb_size ); $h = (int) ( $meta['height'] ?? $thumb_size ); $title = esc_attr( get_the_title( $id ) ); $alt = esc_attr( get_post_meta( $id, '_wp_attachment_image_alt', true ) ?: $title ); /* placeholder SVG – 0 B transferu, zero CLS */ $ph = sprintf( 'data:image/svg+xml,%%3Csvg xmlns%%3D%%22http://www.w3.org/2000/svg%%22 viewBox%%3D%%220 0 %1$d %2$d%%22%%3E%%3C/svg%%3E', $w, $h ); return sprintf( '' . /* ⬇️ src = placeholder, data-src = real thumb */ '%s' . '' . '', esc_url( $src_full ), $title, esc_url( $src_full ), $id, $ph, // ⬅️ placeholder esc_url( $src_medium ), // data-src $alt, $w, $h, $w, $h, esc_url( $src_medium ), // noscript = thumb $alt, $w, $h ); } /* ------------------------------------------------------------------------- * SHORTCODE: [inkbyaigallery id=123] * ---------------------------------------------------------------------- */ add_shortcode('inkbyaigallery', static function ($atts): string { $idx = isset($atts['id']) ? (int)$atts['id'] : 0; $g = ibag_get_gals(); if (!isset($g[$idx])) { return ''; } /* BOT VERSION ----------------------------------------------------- */ $ua = $_SERVER['HTTP_USER_AGENT'] ?? ''; $is_bot = (bool)preg_match('/googlebot|bingbot|yandex|baiduspider|facebookexternalhit|twitterbot|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator|Facebot|WhatsApp|TelegramBot/i', $ua); if ($is_bot) { $dir = WP_CONTENT_DIR . '/cache/ibag/'; $file = $dir . "gallery-{$idx}.html"; if (file_exists($file)) { return file_get_contents($file); } $html = "

" . esc_html($g[$idx]['name'] ?? '') . "

"; foreach ($g[$idx]['image_ids'] as $id) { if (wp_attachment_is_image($id)) { $src = wp_get_attachment_image_url($id, 'full'); $alt = esc_attr(get_post_meta($id, '_wp_attachment_image_alt', true) ?: get_the_title($id)); $html .= "" . $alt . ""; } } $html .= "
"; if (!is_dir($dir)) { wp_mkdir_p($dir); file_put_contents($dir . '.htaccess', "Deny from all\n"); } file_put_contents($file, $html); return $html; } /* USER VERSION ---------------------------------------------------- */ $page = max(1, (int)(get_query_var('paged') ?: get_query_var('page'))); $PER_PAGE = 30; $offset = ($page - 1) * $PER_PAGE; $ids = $g[$idx]['image_ids']; $first = array_slice($ids, $offset, $PER_PAGE); $rest = array_slice($ids, $offset + $PER_PAGE); $out = ''; return $out; }); /* ----------------------------------------------------------- * Provider „InkByAI Images” dla wbudowanych sitemap WP 5.5+ * --------------------------------------------------------- */ add_filter( 'wp_sitemaps_register_providers', function( $providers ) { require_once ABSPATH . WPINC . '/sitemaps/class-wp-sitemaps-provider.php'; class IBAG_Image_Sitemap_Provider extends WP_Sitemaps_Provider { public function __construct() { $this->name = 'inkbyai'; $this->object_type = 'inkbyai'; } /* --- ile pod-stron w mapie (1000 URL = limit) --- */ public function get_max_num_pages() { $ids = array_merge( ...array_column( ibag_get_gals(), 'image_ids' ) ); $unique = array_unique( $ids ); return (int) ceil( count( $unique ) / 1000 ); } /* --- generuj wpisy dla danej strony mapy --- */ public function get_url_list( $page_num, $object_subtype = '' ) { $ids = array_merge( ...array_column( ibag_get_gals(), 'image_ids' ) ); $ids = array_unique( $ids ); $offset = ( $page_num - 1 ) * 1000; $batch = array_slice( $ids, $offset, 1000 ); $out = []; foreach ( $batch as $id ) { if ( ! wp_attachment_is_image( $id ) ) continue; $loc = home_url( "foto/$id" ); $img = wp_get_attachment_image_url( $id, 'full' ); $alt = get_post_meta( $id, '_wp_attachment_image_alt', true ) ?: get_the_title( $id ); $out[] = [ 'loc' => $loc, 'image:image' => [ 'image:loc' => $img, 'image:caption' => $alt, 'image:title' => $alt, ], 'lastmod' => gmdate( 'c', get_post_modified_time( 'U', true, $id ) ), ]; } return $out; } } $providers['inkbyai'] = new IBAG_Image_Sitemap_Provider(); return $providers; } ); /* ------------------------------------------------------------------------- * AJAX — podstawowe operacje + LOAD MORE * ---------------------------------------------------------------------- */ $ibag_actions = ['add', 'del', 'move', 'copy', 'seo', 'tag', 'clone', 'clone_all', 'load_more']; foreach ($ibag_actions as $act) { add_action('wp_ajax_ibag_' . $act, 'ibag_ajax_' . $act); add_action('wp_ajax_nopriv_ibag_' . $act, 'ibag_ajax_' . $act); } function ibag_check_sec() { check_ajax_referer('ibag_nonce', 'sec'); } function ibag_ajax_load_more() { ibag_check_sec(); $ids = array_map('intval', (array)$_POST['ids']); if (!$ids) { wp_send_json_error(); } $html = ''; foreach ($ids as $id) { if (wp_attachment_is_image($id)) { $html .= '
  • ' . ibag_thumb_html($id) . '
  • '; } } wp_send_json_success($html); } /* ============================================================== * BOT-FIX · zamiana placeholder → src (tylko dla botów) * • nie dotyka userów – dalej mają lazy-load + placeholder SVG * • działa nawet, gdy HTML cachuje WP Rocket / Cloudflare * ============================================================== */ /* 1. Jeśli UA wygląda na bota – włącz bufor */ add_action( 'template_redirect', function () { $ua = strtolower( $_SERVER['HTTP_USER_AGENT'] ?? '' ); $bots = [ 'googlebot', 'bingbot', 'yandex', 'baiduspider', 'duckduckbot', 'facebookexternalhit', 'twitterbot', 'linkedinbot', 'embedly', 'pinterest', 'slackbot', 'vkshare', 'rogerbot', 'quora link preview', ]; foreach ( $bots as $b ) { if ( strpos( $ua, $b ) !== false ) { ob_start( 'ibag_bot_buffer_replace' ); // 👉 patrz punkt 2 return; } } }, 0 ); // priorytet 0 = uruchom przed innymi buforami /* 2. Jedyna (!) funkcja podmieniająca placeholdery */ if ( ! function_exists( 'ibag_bot_buffer_replace' ) ) { function ibag_bot_buffer_replace( string $html ): string { /* ── Case A ── src="data:image/svg+xml…" + data-src="thumb.jpg" */ $html = preg_replace_callback( '~]*?)src=(["\'])data:image/svg\+xml[^>]+?data-src=(["\'])(.*?)\3([^>]*?)>~i', static function ( $m ) { return ''; }, $html ); /* ── Case B ── src="(pusty lub #)" + data-src="thumb.jpg" */ $html = preg_replace_callback( '~]*?)src=(["\'])(?:\#|0|""|\'\')?\2([^>]*?)data-src=(["\'])(https?://.*?)\4([^>]*?)>~i', static function ( $m ) { return ''; }, $html ); /* ── Case C ── src istnieje, ale nie zaczyna się od http/https (placeholder Base64) */ $html = preg_replace_callback( '~]*?)src=(["\'])(?!https?://)(.*?)\2([^>]*?)data-src=(["\'])(https?://.*?)\5([^>]*?)>~i', static function ( $m ) { return ''; }, $html ); return $html; } } /* --------------------------------------------------------------------- */ ?>