// 本文中の全画像を、ご提示の 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 '';
}