/** * Normalize relative and legacy asset URLs within HTML / HTML内の相対パスと旧URLを正規化する * * 処理対象: * - src, href, poster, srcset * - data-*, data-src, data-srcset, data-bg, data-background 等 * - style 属性内の url(...) * - (picture/Video) の srcset, src * * @param string $html Raw HTML string to process. * @return string Normalized HTML. */ function etude_normalize_product_html( string $html ) : string { if ( trim( $html ) === '' ) { return $html; } $previous = libxml_use_internal_errors( true ); $dom = new DOMDocument( '1.0', 'UTF-8' ); $html_utf8 = mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' ); $dom->loadHTML( '' . $html_utf8, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ); $xpath = new DOMXPath( $dom ); // 1) 代表的なURL属性(単一URL) $single_url_attrs = [ 'src', 'href', 'poster' ]; foreach ( $single_url_attrs as $attr ) { foreach ( $xpath->query( '//*[@' . $attr . ']' ) as $el ) { /** @var DOMElement $el */ $el->setAttribute( $attr, etude_map_asset_url( $el->getAttribute( $attr ) ) ); } } // 2) srcset foreach ( $xpath->query( '//*[@srcset]' ) as $el ) { $el->setAttribute( 'srcset', etude_normalize_attribute_urls( $el->getAttribute( 'srcset' ), 'srcset' ) ); } // 3) style (background: url(...)) foreach ( $xpath->query( '//*[@style]' ) as $el ) { $el->setAttribute( 'style', etude_normalize_attribute_urls( $el->getAttribute( 'style' ), 'style' ) ); } // 4) data-* 系(data-src, data-srcset, data-bg, data-background など)を一括 foreach ( $xpath->query( '//*' ) as $el ) { /** @var DOMElement $el */ if ( ! $el->hasAttributes() ) continue; $to_set = []; foreach ( $el->attributes as $attr ) { $name = strtolower( $attr->name ); if ( $name === 'src' || $name === 'href' || $name === 'poster' || $name === 'style' || $name === 'srcset' ) { continue; // 既に処理済み } if ( strpos( $name, 'data-' ) === 0 || preg_match( '/(?:background|bg|src|srcset)/i', $name ) ) { $to_set[ $attr->name ] = etude_normalize_attribute_urls( $attr->value, $attr->name ); } } foreach ( $to_set as $n => $v ) { $el->setAttribute( $n, $v ); } } // 5) (video/picture)で src も拾う foreach ( $xpath->query( '//source[@src]' ) as $el ) { $el->setAttribute( 'src', etude_map_asset_url( $el->getAttribute( 'src' ) ) ); } foreach ( $xpath->query( '//source[@srcset]' ) as $el ) { $el->setAttribute( 'srcset', etude_normalize_attribute_urls( $el->getAttribute( 'srcset' ), 'srcset' ) ); } $output = $dom->saveHTML(); $output = preg_replace( '#^]+?>#', '', $output ?? '' ); libxml_clear_errors(); libxml_use_internal_errors( $previous ); return $output !== '' ? $output : $html; } /** * Normalize URLs embedded in arbitrary attribute values / 任意属性値内のURLを正規化する * * @param string $value Attribute value string. * @param string $attr_name (optional) 現在処理中の属性名ヒント(srcset/style等) * @return string Normalized attribute value. */ function etude_normalize_attribute_urls( string $value, string $attr_name = '' ) : string { if ( trim( $value ) === '' ) { return $value; } // srcset は「url<空白>descriptor」のカンマ区切り if ( strtolower( $attr_name ) === 'srcset' ) { $parts = array_map( 'trim', explode( ',', $value ) ); foreach ( $parts as &$part ) { if ( $part === '' ) continue; $segments = preg_split( '/\s+/', $part, 2 ); $url_part = etude_map_asset_url( $segments[0] ?? '' ); $descriptor = $segments[1] ?? ''; $part = trim( $url_part . ( $descriptor ? ' ' . $descriptor : '' ) ); } unset( $part ); return implode( ', ', $parts ); } // style="background: url(...)" 等: url(...) をすべて拾う if ( strtolower( $attr_name ) === 'style' ) { return preg_replace_callback( // 区切りを ~ にすることで / や # を多用しても衝突しない '~url\(\s*(["\']?)([^)]+?)\1\s*\)~i', function( $m ) { $mapped = etude_map_asset_url( trim( $m[2] ) ); return 'url(' . $mapped . ')'; }, $value ); } // data-* など任意属性:複合文字列中の URL トークンだけを置換 // - http(s)://... もしくは //...(プロトコル相対) // - / から始まるルート相対 return preg_replace_callback( // 1 個の安全なパターンで抽出(否定の後読みは \w と @ のみ) // () 全体が URL 本体。) や ] は文字クラス外に置き、デリミタは ~ を使用 '~(? Warning: Cannot modify header information - headers already sent by (output started at /home/etudejapan/etude.jp/public_html/wp-content/themes/etudehouse/inc/product-importer.php:1) in /home/etudejapan/etude.jp/public_html/wp-includes/sitemaps/class-wp-sitemaps-renderer.php on line 190
https://etude.jp/category/uncategorized/