// 本文中の全画像を、ご提示の ImageObject 形式で独立出力(配列を直出し) add_action('wp_head', function () { if (!is_singular('post')) return; global $post; if (empty($post)) return; $content = apply_filters('the_content', $post->post_content); if (!$content) return; $images = mysite_extract_images_from_content($content); if (empty($images)) return; // 既定値(必要に応じて編集) $license_page = get_permalink($post); // 例:記事詳細をライセンス案内ページにする $copyright_year = date_i18n('Y'); // 例:今年 $default_credit = 'FANZA'; // 例:"FANZA" など固定したい場合に設定 $jsonld_images = []; foreach ($images as $img) { // name / caption の作り方:title/figcaption/alt から生成済み値を利用 $name = $img['caption'] ?: $img['description'] ?: ''; $caption = $img['caption'] ?: $img['description'] ?: ''; // creditText / creator はドメインから推測 or 既定値 $brand = $default_credit ?: mysite_brand_from_url($img['url']); $obj = [ '@context' => 'https://schema.org', '@type' => 'ImageObject', 'contentUrl' => $img['url'], 'url' => $img['url'], ]; if ($name) $obj['name'] = $name; if ($caption) $obj['caption'] = $caption; if (!empty($img['license'])) { $obj['license'] = $img['license']; } else { // 明示ライセンスが取れない場合はサイト側の案内ページを設定(不要なら削除) $obj['license'] = $license_page; } if ($brand) { $obj['creditText'] = $brand; $obj['creator'] = ['@type' => 'Organization', 'name' => $brand]; $obj['copyrightNotice'] = '© ' . $copyright_year . ' ' . $brand; } // 取得できれば acquisition ページ(= ライセンス案内ページでもOK) $obj['acquireLicensePage'] = $license_page; // 参考情報(任意) if (!empty($img['thumbnailUrl'])) $obj['thumbnailUrl'] = $img['thumbnailUrl']; if (!empty($img['uploadDate'])) $obj['uploadDate'] = $img['uploadDate']; if (!empty($img['width'])) $obj['width'] = (int)$img['width']; if (!empty($img['height'])) $obj['height'] = (int)$img['height']; $jsonld_images[] = $obj; if (count($jsonld_images) >= 50) break; // 上限 } // 配列のまま \n"; }, 100); /* ---------- 画像抽出(前提ユーティリティ) ---------- */ function mysite_extract_images_from_content(string $content): array { $html = '' . $content; $dom = new DOMDocument(); libxml_use_internal_errors(true); $dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); libxml_clear_errors(); $xpath = new DOMXPath($dom); $img_nodes = $xpath->query('//img'); $out = []; $seen = []; foreach ($img_nodes as $img) { /** @var DOMElement $img */ $url = mysite_pick_img_url($img); if (!$url) continue; $url = mysite_make_absolute_url($url); if (!$url) continue; $key = md5($url); if (isset($seen[$key])) continue; $seen[$key] = true; $caption = mysite_find_figcaption($img) ?: trim($img->getAttribute('title')); $alt = trim($img->getAttribute('alt')); $description = $alt ?: $caption; $width = $img->getAttribute('width') ?: null; $height = $img->getAttribute('height') ?: null; $thumb = null; $upload = null; $attachment_id = function_exists('attachment_url_to_postid') ? attachment_url_to_postid($url) : 0; if ($attachment_id) { if (!$width || !$height) { $meta = wp_get_attachment_metadata($attachment_id); if (!empty($meta['width'])) $width = $meta['width']; if (!empty($meta['height'])) $height = $meta['height']; } $thumb_src = wp_get_attachment_image_src($attachment_id, 'thumbnail'); if (!empty($thumb_src[0])) $thumb = $thumb_src[0]; $att_post = get_post($attachment_id); if ($att_post && !is_wp_error($att_post)) { $upload = get_date_from_gmt(gmdate('Y-m-d H:i:s', strtotime($att_post->post_date_gmt)), 'c'); if (empty($caption) && !empty($att_post->post_excerpt)) $caption = wp_strip_all_tags($att_post->post_excerpt); if (empty($description) && !empty($att_post->post_content)) $description = wp_strip_all_tags($att_post->post_content); } } $out[] = array_filter([ 'url' => $url, 'caption' => $caption ?: null, 'description' => $description ?: null, 'width' => $width ? (int)$width : null, 'height' => $height ? (int)$height : null, 'thumbnailUrl' => $thumb, 'uploadDate' => $upload, // 任意:投稿やメディアのカスタムフィールドから license を拾うならここで設定 // 'license' => get_post_meta($attachment_id, 'license_url', true) ?: null, ], fn($v) => $v !== null && $v !== ''); if (count($out) >= 50) break; } return $out; } function mysite_pick_img_url(DOMElement $img): ?string { $candidates = [ $img->getAttribute('src'), $img->getAttribute('data-src'), $img->getAttribute('data-lazy-src'), $img->getAttribute('data-original'), ]; $srcset = $img->getAttribute('srcset'); if ($srcset) { $parts = array_map('trim', explode(',', $srcset)); if (!empty($parts[0])) { $first = trim(explode(' ', $parts[0])[0]); $candidates[] = $first; } } foreach ($candidates as $u) { if ($u && stripos($u, 'data:') !== 0) return $u; } return null; } function mysite_find_figcaption(DOMElement $img): ?string { $parent = $img->parentNode; while ($parent && $parent instanceof DOMElement) { if (strtolower($parent->nodeName) === 'figure') { foreach ($parent->childNodes as $child) { if ($child instanceof DOMElement && strtolower($child->nodeName) === 'figcaption') { return trim(preg_replace('/\s+/', ' ', $child->textContent)); } } break; } $parent = $parent->parentNode; } return null; } function mysite_make_absolute_url(string $url): ?string { if (!$url) return null; if (preg_match('~^https?://~i', $url)) return $url; $home = rtrim(home_url('/'), '/'); if (strpos($url, '//') === 0) { $scheme = parse_url($home, PHP_URL_SCHEME) ?: 'https'; return $scheme . ':' . $url; } if ($url[0] === '/') return $home . $url; $base = rtrim(get_permalink(), '/'); return $base . '/' . ltrim($url, './'); } /** * 画像URLのドメインから簡易的にブランド名を推定 * 例)"https://doujin-assets.dmm.co.jp/..." -> "dmm.co.jp" -> "DMM" * 必要ならマッピングを拡充してください */ function mysite_brand_from_url(string $url): string { $host = parse_url($url, PHP_URL_HOST) ?: ''; $host = preg_replace('/^www\./i', '', $host); // カスタムマップ(必要に応じて追加) $map = [ 'doujin-assets.dmm.co.jp' => 'FANZA', 'dmm.co.jp' => 'DMM', 'images-na.ssl-images-amazon.com' => 'Amazon', ]; foreach ($map as $needle => $brand) { if (stripos($host, $needle) !== false) return $brand; } // ドメイン末尾2ラベルをブランド化(例: example.co.jp -> example) $labels = explode('.', $host); if (count($labels) >= 2) { $core = $labels[count($labels)-2]; return strtoupper($core); } return ''; }