レ点腫瘍学ノート

Toppukiwikiカスタマイズ箇所2025

refプラグインにCloudflare Imagesの導入

f4bdbd96a0.png

Cloudflare Imagesとは

Cloudflare Imagesは、画像の管理や配信を効率化するサービスだ。画像の変換や圧縮を自動で行い、不要な負荷を減らしながら高速に配信できる。専用のストレージを使うことで、サーバーの管理を簡単にし、画像の最適化も自動化できる。世界各地にあるCloudflareのネットワークを利用するため、どこからアクセスしても安定した速度で画像を表示できる。

Cloudflare Images Transportations機能について

Cloudflare ImagesのTransportations機能を使えば、画像を移動させずにCloudflare経由で配信できる。ローカルサーバーに置いた画像のURLを書き換えるだけで、Cloudflareの画像最適化やキャッシュ機能を利用できる。既存の画像をそのまま使えるので、手間をかけずに配信を改善できる。サーバーの負担を減らしながら、画像の読み込みを速くするのに役立つ。

Workersを使ったURL転送の断念

Cloudflare Workersを使えば、リクエストの内容を動的に書き換えられる。そこで、画像URLをCloudflare Imagesのものに自動変換する処理を組み込めば、既存のシステムを変更せずに移行できると考えた。実際、このサイトを参考にして試したところ、画像の変換自体は問題なく動作した。

オリジンコンテンツに変更を加えず、Cloudflare Image Transformation の適用を自動化で対応 - Qiita
1. Image Transformationとは?Cloudflare Image Transformationは、リクエスト時に動的に画像を最適化し、サイズ変更や形式変換を行う強力なツールです。…
https://qiita.com/dd_cloudflare/items/9568203968f9b9597ec6

しかし、この方法には致命的な問題があった。Workersが画像URLを書き換えることで、Pukiwikiの各種プラグインも当該URLにアクセスできなくなり、キャッシュ機能などが正常に動作しなくなった。特に、PukiwikiのrefプラグインやOGP関連のプラグインがCloudflare ImagesのURLを認識できず、適切な動作をしなくなった。

Workersに投入したがうまく行かなかったコード

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  const userAgent = request.headers.get('user-agent') || '';
  const url = new URL(request.url);
  const cacheDirectoryPath = '/path/to/your/cache/directory/'; // キャッシュディレクトリのパス

  // 1. Cloudflare Images bot の判定 (User-Agentに基づく)
  if (userAgent.includes('Cloudflare-Images')) {
    // Cloudflare Images bot の場合はオリジンに転送
    return fetch(request);
  }

  // 2. OGP取得プラグインの判定 (URLに基づく)
  else if (url.pathname.startsWith(cacheDirectoryPath)) {
     // キャッシュディレクトリへのリクエストの場合はオリジンに転送
    return fetch(request);
  }

  // 3. それ以外 (来訪者) の場合は、Cloudflare Images のエッジに転送
  else {
     // Cloudflare ImagesのURL変換処理が必要であれば、ここに記述。
      const imageURL = 'https://your-domain.com/cdn-cgi/image/...' + url.pathname; //例
      return fetch(imageURL, {
        headers: request.headers // 元のリクエストヘッダーを保持(重要)
     });
  }
}

最終的には、Workersを使うのを諦め、PukiwikiのrefプラグインやOGPプラグインを改造し、強制的に画像URLをCloudflare Imagesのものに変換するという強引な解決策を講じた。システムの根本的な修正は避けたかったが、動作を確保するにはこの方法を選ばざるを得なかった。

改造後の各種プラグイン

PukiwikiのrefプラグインとOGP関連のプラグインを改造し、画像URLを強制的にCloudflare Imagesのものに変換するようにした。具体的には、プラグイン内で画像のURLを取得する処理に手を加え、Cloudflare Imagesの形式に自動で書き換えるようにした。

なお、このサイトは画像遅延読み込みのためのlazysizes.jsも併用している。

ref.inc.php

30行目付近に設定項目PLUGIN_REF_CLOUDFLARE_IMAGESを作成

// Cloudflare Imagesを契約しているか
define('PLUGIN_REF_CLOUDFLARE_IMAGES', TRUE); // FALSE, TRUE

350行目付近の、refプラグインに画像のURLを投入した場合の挙動を修正

if ($is_image) { // 画像
	if(strpos($url,$script) !== false){
		$url = str_replace($script, '', $url); //ローカルサーバーでは相対パスにする
	}
	if ( $params['eager']) {
		$lazy = "eager";
	} else {
		$lazy = "lazy";
	}
	if ( PLUGIN_REF_CLOUDFLARE_IMAGES ) {
		$params['_body'] = "<img class=\"lazyload\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" data-src=\"/cdn-cgi/image/q=70,f=auto/$url\" data-srcset=\"/cdn-cgi/image/q=70%2Cw=860%2Cf=auto%2Cfit=scale-down/$url 860w, /cdn-cgi/image/q=70%2Cw=1290%2Cf=auto%2Cfit=scale-down/$url 1290w, /cdn-cgi/image/q=70%2Cw=1920%2Cf=auto%2Cfit=scale-down/$url 1920w\" alt=\"$title\" data-aspectratio=\"$width/$height\" $info>"; //Cloudflare ImagesとLazysizesを使用する場合
	} else {
		$params['_body'] = "<img class=\"lazyload\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" data-src=\"$url\" alt=\"$title\" data-aspectratio=\"$width/$height\" $info>"; //Lazysizesを使用する場合
	}
	if (! $params['nolink'] && $url2) {
		$params['_body'] = "<a href=\"$url2\" title=\"$title\">{$params['_body']}</a>";
	}
} else {
	$icon = $params['noicon'] ? '' : FILE_ICON;
	$params['_body'] = "<a href=\"$url\" title=\"$info\">$icon$title</a>";
}

ogp.inc.php

<?php

// 'ogp' plugin for PukiWiki
// author: m0370
// Twitter: @m0370

// ver1.0 (2019.9.10)
// ひとまずOGPを取得して表示する機能を実装しました。

// ver1.1 (2019.9.17)
// Cache機能を実装しました。CACHE.DIRのogpというサブフォルダにキャッシュを配置します。

// ver1.2 (2020.5.1)
// 第2引数をスタイルシートとして引用

// ver1.3 (2020.5.2)
// ファイル形式(GIF・PNGなど)を反映したキャッシュファイル名になるようにしました。従来のキャッシュも利用できます。

// ver 1.4 (2020.7.16)
// HTMLパース,noimg対応
// OGP画像がjpg拡張子になっていても中身がwebPやgzipの場合に画像が表示できない問題がある

// ver 1.5 (2021.6.14)
// キャッシュをJSON形式で保存するように変更

// ver 1.6 (2021.12.23)
// PHP8.0でエラーが出ないよう微修正

// ver 1.7 (2023.01.13)
// 無限ループにならないようにUser Agentでの読み込み防止を実装

// ver 1.8 (2025.02.09)
// Cloudflare images transformationに対応する分岐を作成

/////////////////////////////////////////////////

// 画像サイズ
define('PLUGIN_OGP_SIZE', 100); // TRUE, FALSE

// Cloudflare Imagesによるキャッシュを利用する
define('PLUGIN_OGP_CLOUDFLAREIMAGE', TRUE); // TRUE, FALSE

// WEBPファイルがあるときWEBP表示を試みる fallback
define('PLUGIN_OGP_WEBP_FALLBACK', FALSE); // TRUE, FALSE. Cloudflare imagesを使う場合はFALSE推奨

/////////////////////////////////////////////////

function plugin_ogp_convert()
{
$args = func_get_args();
$uri = get_script_uri();
$ogpsize = PLUGIN_OGP_SIZE;
$ogpurl = (explode('://', $args[0]));
$ogpurlmd = md5($ogpurl[1]);
$datcache = CACHE_DIR . 'ogp/' . $ogpurlmd . '.txt';
$gifcache = CACHE_DIR . 'ogp/' . $ogpurlmd . '.gif';
$jpgcache = CACHE_DIR . 'ogp/' . $ogpurlmd . '.jpg';
$pngcache = CACHE_DIR . 'ogp/' . $ogpurlmd . '.png';
if(PLUGIN_OGP_WEBP_FALLBACK) {
$webpcache = CACHE_DIR . 'ogp/' . $ogpurlmd . '.webp'; //webp対応
}
$browser = $_SERVER['HTTP_USER_AGENT'];

if(file_exists($pngcache)) { $imgcache = $pngcache ; }
else if(file_exists($gifcache)) { $imgcache = $gifcache ; }
else { $imgcache = $jpgcache ; }

if(file_exists($datcache) && file_exists($imgcache)) {
	$ogpcache = json_decode(file_get_contents($datcache), true);
	$title = $ogpcache['title'];
	$description = $ogpcache['description'];
	$src = $imgcache ;
} else {
    require_once(PLUGIN_DIR.'opengraph.php');
    $graph = OpenGraph::fetch($args[0]);
    if ($graph) {
        $title = $graph->title;
        $url = $graph->url;
        $description = $graph->description;
        if( isset($graph->{'image:secure_url'}) ){
		 	$src = $graph->{'image:secure_url'};
		} else {
			$src = $graph->image;
		}

	    $title_check = mb_convert_encoding($title, 'ISO-8859-1', 'UTF-8');
	    $description_check = mb_convert_encoding($description, 'ISO-8859-1', 'UTF-8');
	    if(mb_detect_encoding($title_check) == 'UTF-8'){
	        $title = $title_check; // 文字化け解消
	    }
	    if(mb_detect_encoding($description_check) == 'UTF-8'){
	        $description = $description_check; // 文字化け解消
	    }
	    
	    $detects = array('ASCII','EUC-JP','SJIS','JIS','CP51932','UTF-16','ISO-8859-1');
	    
	    // 上記以外でもUTF-8以外の文字コードが渡ってきてた場合、UTF-8に変換する
	    if(mb_detect_encoding($title) != 'UTF-8'){
	        $title = mb_convert_encoding($title, 'UTF-8', mb_detect_encoding($title, $detects, true));
	    }
	    if(mb_detect_encoding($description) != 'UTF-8'){
	        $description = mb_convert_encoding($description, 'UTF-8', mb_detect_encoding($description, $detects, true));
	    }

	    $grapharray = array('title' => $title, 'description' => $description, 'src' => $src, 'url' => $args[0], 'date' => date("Y-m-d H:i:s"));
		file_put_contents($datcache, json_encode($grapharray, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));

		if(file_exists($imgcache)) {
			$src = $imgcache ;
		} else if($src == '') {
			$is_noimg = TRUE ;
			$imgfile = touch('imgfile.jpg');
			file_put_contents($jpgcache, $imgfile) ;
		} else {
			$imgfile = file_get_contents($src);
			$filetype = exif_imagetype($src);
			if( $filetype == IMAGETYPE_GIF ){
				file_put_contents($gifcache, $imgfile) ;
			} else if ( $filetype == IMAGETYPE_PNG ){
				file_put_contents($pngcache, $imgfile) ;
			} else {
				file_put_contents($jpgcache, $imgfile) ;
			} //どの拡張子でもない場合、ダミーjpgファイルを作る
		}
	} else {
		error_log("OGP fetch failed for URL: " . $args[0], 3, CACHE_DIR . 'ogp/error-fetch.log');
		return '#ogp Error: Failed to fetch OGP data.';
	}
}

if($is_noimg != TRUE){
	$is_noimg = (in_array('noimg', $args) || ( file_exists($imgcache) && filesize($imgcache) <= 1 ));
}
if($is_noimg) {$noimgclass = "ogp-noimg" ;}
	if(in_array('ogp2', $args)) {$ogpclass = 'ogp2';} //ogp2

//XSS回避
$description = htmlspecialchars($description);
$args[0] = htmlspecialchars($args[0]);

//WEBP表示のfallback
if ( PLUGIN_OGP_CLOUDFLAREIMAGE) {
	$src = '/cdn-cgi/image/q=60,w=300,f=auto,fit=scale-down/' . $src ;
	$fallback1 = '';
	$fallback2 = '';
} else if( PLUGIN_OGP_WEBP_FALLBACK && file_exists($webpcache) ) {
	$fallback1 = '<picture><source type="image/webp" srcset="' . $webpcache . '"/>';
	$fallback2 = '</picture>';
} else {
	$fallback1 = '';
	$fallback2 = '';
}



return <<<EOD
<div class="ogp $ogpclass">
<div class="ogp-img-box $ogpclass $noimgclass">$fallback1<img class="ogp-img $ogpclass lazyload fadein" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-src="$src" alt="$title" width="$ogpsize" height="$ogpsize">$fallback2</div>
<div class="ogp-title $ogpclass"><a href="$args[0]" target=”_blank” rel="noreferrer">$title<span class="overlink"></span></a></div>
<div class="ogp-description $ogpclass">$description</div>
<div class="ogp-url $ogpclass">$args[0]</div>
</div>
EOD;
}

?>

refプラグインの場合、通常はローカルサーバーの画像パスを出力するが、改造後はこのパスをCloudflare ImagesのURLに変換する処理を挟んだ。OGPプラグインについても同様に、OGP画像を取得する部分でCloudflare ImagesのURLを生成するように変更した。これにより、Pukiwikiの画像関連機能がCloudflare Images経由で動作するようになった。

srcsetとCloudflare Imagesの問題

imgタグにはsrcsetを使うことで、画面サイズに応じた適切な画像を指定できる。しかし、srcsetの仕様上、複数の画像URLを指定するためにコンマ(,)を使う必要がある。一方、Cloudflare ImagesのURLもパラメータの区切りにコンマを使用するため、この競合によってリンクが正しく認識されず、画像が表示されない問題が発生した。

解決策として、URL内のコンマをエンコード(%2C に置換)してみたが、Cloudflare Images側がこの形式を受け付けないのか、画像が正しく表示されなかった。ただし、Cloudflareの公式文書を見るとコンマ付きのURLでも実際の使用は問題なく行えるらしいので、何らかの設定間違いであったよう。(→現在修正を検討中)

Make responsive images · Cloudflare Images docs
You can serve responsive images in two different ways:
https://developers.cloudflare.com/images/transform-images/make-responsive-images/#srcset-for-responsive-images

この記事に対するコメント

このページには、まだコメントはありません。

お名前:

更新日:2025-02-11 閲覧数:840 views.