PHPでリンク切れを調べる

(1/1)
PHPでは、ローカルディスクにあるファイルとインターネット上のコンテンツを同様に扱うことができる。
この特徴を利用して、ホームページ内に書かれているリンク先が存在しているかどうか、存在する場合はコンテンツのサイズがゼロでないかどうかをチェックするプログラムを作ってみることにする。
あわせて、W3Cが提供するWebサービスを利用し、HTMLが文法的に正しいかをチェックする。

(2025年5月10日)getContext()--Referer生成機能追加, getHttpResponseCodeSize()--リクエストヘッダ改良
(2024年11月15日)プログラムを全面改訂:XPath式導入, アクセス回数低減

目次

サンプル・プログラムの実行例

PHPでリンク切れを調べる
このプログラムは、URLで指定したネット上のコンテンツに含まれるリンク先(<a>タグや<img>タグなどに書かれているURL)が存在するかどうかをチェックする。使い方は、実行画面下方に表示されているとおり。
存在チェックを実施したくないURLを、除外パターンとして正規表現で複数指定することができる。パターンはPHPの正規表現と同様、スラッシュで囲む /.../ こと。2つ以上のパターンを指定したいときは、改行で区切る。たとえば、"/e-soul/webtech/php02/" で始まるURLを除外したければ、
/https\:\/\/www\.pahoo\.org\/e\-soul\/webtech\/php02\//
と入力する。

サンプル・プログラム

圧縮ファイルの内容
check404.phpサンプル・プログラム本体。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
pahooScraping.phpスクレイピングS処理に関わるクラス pahooScraping。
スクレイピング処理に関わるクラスの使い方は「PHPで作るRSSビューア」を参照。include_path が通ったディレクトリに配置すること。
check404.php 更新履歴
バージョン 更新日 内容
5.2.0 2025/05/10 getHttpResponseCodeSize()-リクエストヘッダ改良
5.1.0 2025/03/09 getContext() -- Referer生成機能追加
5.0.0 2024/11/15 全面改訂:XPath式導入, アクセス回数低減
4.6.0 2023/03/21 htmlChecker→validateHTMLに変更
4.5 2022/01/10 コンテンツ・サイズ取得の例外に対応
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.8.1 2025/03/15 validRegexPattern() -- debug
1.8.0 2024/11/12 validRegexPattern() 追加
1.7.0 2024/10/09 validURL() validEmail() 追加
1.6.0 2024/10/07 isButton() -- buttonタグに対応
1.5.0 2024/01/28 exitIfExceedVersion() 追加
pahooScraping.php 更新履歴
バージョン 更新日 内容
1.2.1 2024/10/31 __construct() 文字化け対策
1.2.0 2024/09/29 getValueFistrXPath() 属性値でない指定に対応
1.1.0 2023/10/15 getValueFistrXPath() 追加
1.0.1 2023/09/29 __construct() bug-fix
1.0.0 2023/09/18 初版

準備

check404.php

  59: // 初期値(START) ============================================================
  60: // 表示幅(ピクセル)
  61: define('WIDTH', 600);
  62: 
  63: // 偽装ユーザー
  64: define('USER_AGENT', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36');
  65: 
  66: // スクレイピング処理に関わるクラス:include_pathが通ったディレクトリに配置
  67: require_once('pahooScraping.php');
  68: 
  69: // デフォルトのスクレイピング対象クエリ
  70: $text = [
  71:     "//a/@href",
  72:     "//img/@src",
  73:     "//link/@href",
  74:     "//script/@src",
  75:     "//meta[@name='og:url']/@content",
  76:     "//meta[@name='og:image']/@content",
  77:     "//meta[@name='twitter:image']/@content",
  78:     "//link[@rel='canonical']/@href",
  79: ];
  80: define('XPATH_LIST', $text);
  81: 
  82: // デフォルトの除外パターン(チェック対象外URL)リスト
  83: define('DEF_EXCLUDE', '');
  84: 
  85: // 初期値(END) ==============================================================

初期値は、【変更不可】の記載の無いものは自由に変更できる。
後述するユーザー関数の幾つかで、ブラウザからアクセスが来ているように見せかけるために、USER_AGENT の偽装値を設定しておく。

リンク先を検出するのに、XPath式を利用する。XPath式 およびユーザー定義クラス pahooScraping については「PHPでDOMDocumentを使ってスクレイピング」をご覧いただきたい。
下表に示すタグにあるリンク先をデフォルトでチェックするよう、定数 XPATH_LIST に代入してある。過不足があれば、適宜編集してほしい。
XPath式意味
//a/@hrefaタグのhref属性にあるリンク
//img/@srcimgタグのsec属性にあるリンク
//link/@hreflinkタグのhref属性にあるリンク
//script/@srcscriptaタグのsrc属性にあるリンク
//meta[@name='og:url']/@contentmetaタグのname属性がog:urlのものでcontent属性にあるリンク
//meta[@name='og:image']/@contentmetaタグのname属性がog:imageのものでcontent属性にあるリンク
//meta[@name='twitter:image']/@contentmetaタグのname属性がtwitter:imageのものでcontent属性にあるリンク
//link[@rel='canonical']/@hreflinkタグのrel属性がcanonicalのものでhref属性にあるリンク

解説:404エラーのチェック

check404.php

 409: /**
 410:  * 指定コンテンツに含まれるリンク先の有無を配列に格納する
 411:  * @param   string $sour      コンテンツURL
 412:  * @param   array  $items     チェック結果を格納する配列
 413:  * @param   string $errmsg    エラーメッセージを格納
 414:  * @param   string $queryList チェック対象のXPath式の配列
 415:  * @param   string $exclude   チェック除外するURLパターン
 416:  *                              (正規表現の配列;省略時はNULL)
 417:  * @return  bool TRUE:処理成功/FALSE:失敗(引数のエラーなど)
 418: */
 419: function check404($sour, &$items, &$errmsg, $queryList=XPATH_LIST, $excludes=NULL) {
 420:     $context = getContext($sour);
 421:     $contents = @file_get_contents($sour, FALSE, $context);
 422:     if ($contents == FALSE) {
 423:         $errmsg = $url . ' は存在しない.';
 424:         return FALSE;
 425:     }
 426: 
 427:     // スクレイピング開始
 428:     $pcr = new pahooScraping($contents);
 429:     $index = 0;
 430: 
 431:     // チェック対象のXPath式を1つずつ調べていく
 432:     foreach ($queryList as $query) {
 433:         $imageNode = $pcr->queryXPath($query);
 434:         // XPath式に合致するノードを1つずつ調べていく
 435:         foreach ($imageNode as $node) {
 436:             // URLチェックを行うフラグを立てる
 437:             $flagCheck = TRUE;
 438:             // ノードにあるURLを正規化する
 439:             $url = normalizeURLs((string)$node->nodeValue, $sour);
 440:             // それがURLでなければスキップ
 441:             if ($url == FALSE) {
 442:                 continue;
 443:             }
 444:             // そのURLが除外URL配列に含まれていたら
 445:             // この後の処理をスキップするようフラグを降ろす
 446:             if ($excludes !NULL) {
 447:                 foreach ($excludes as $exclude) {
 448:                     if (preg_match($exclude, $url)) {
 449:                         $flagCheck = FALSE;
 450:                         break;
 451:                     }
 452:                 }
 453:             }
 454:             if ($flagCheck) {
 455:                 // そのURLが存在するかどうか、サイズなどの情報を配列に格納
 456:                 checkLink($url, (int)$node->getLineNo(), $items);
 457:                 $index++;
 458:             }
 459:         }
 460:     }
 461:     // スクレイピング終了
 462:     $pcr = NULL;
 463: 
 464:     // 行番号の小さい順に並び替える
 465:     usort($items, function ($a, $b) {
 466:         return $a['ln'> $b['ln'? (+1: (-01);
 467:     });
 468: }

このプログラムの肝となるのはユーザー定義関数 check404 である。
調査するコンテンツのURLを引数 $sour に渡し、与えられた XPath式(複数)を使って、そのコンテンツの中に含まれているリンク先URを取りだし、それがが存在するかどうかを、後述するユーザー関数 checkLink を使って調べる。

まず、調査対象の $sour が存在するかどうかを、組み込み関数  file_get_contents  を使って検証する。このとき、第三引数に後述するユーザー関数 getContext で生成したストリームコンテキストを渡すことで、ブラウザからアクセスしているように見せかける。

次に、ユーザー定義クラス pahooScraping を使ってスクレイピングを開始する。
for文を使い、引数で渡された XPath式 の連想配列を1つずつ調べていく。XPath式 に合致するパターンがあれば、そのノードを配列 $imageNode に代入する。

for文を使い、ノード配列 $imageNode を1つずつ調べていく。
まず、URLチェックを行うフラグ変数 $flagCheck を立てる。
次に、XPath式を使って得られるリンク先は "http(s)://" ではじまらない相対リンクのこともあるので、後述するユーザー関数 normalizeURLs を使って "http(s)://" ではじまるように正規化してから変数 $url に代入する。
$url が引数として渡された除外URL配列 $excludes に合致したら、これ以降の処理をスキップするよう、フラグ変数 $flackCheck を降ろす。

最後に、フラグ変数 $flagCheck が立っていれば、得られたURLが存在するかどうかを、後述するユーザー関数 checkLink を使ってチェックし、結果を連想配列 $items に格納する。

以上の二重ループを抜けたら、結果を格納している連想配列 $items を、行番号の小さい順に並び替える。

解説:ストリームコンテキスト作成

check404.php

 240: /**
 241:  * 偽装用のcontextを返す
 242:  * @param   string $url 対象URL
 243:  * @param   object コンテクスト
 244: */
 245: function getContext($url) {
 246:     // Refererを生成する
 247:     $parsedUrl = parse_url($url);
 248:     $referer = $parsedUrl['scheme'. '://' . $parsedUrl['host'. '/';
 249: 
 250:     //偽装ヘッダ
 251:     $header = array(
 252:         'Content-Type: text/html; charset=UTF-8',
 253:         'User-Agent: ' . USER_AGENT,
 254:         'Referer: ' . $referer
 255:     );
 256:     $ssl = array(
 257:         'verify_peer' => FALSE,
 258:         'verify_peer_name' => FALSE
 259:     );
 260:     $opts = array(
 261:         'http' => array(
 262:             'method' => 'GET',
 263:             'header' => implode("\r\n", $header),
 264:             'ssl' => $ssl,
 265:             'follow_location' => 1,     // リダイレクトを追跡する
 266:             'max_redirects' => 10,      // 最大リダイレクト回数
 267:         )
 268:     );
 269: 
 270:     return stream_context_create($opts);
 271: }

ブラウザはhttpサーバと通信を行うとき、さまざまなヘッダ情報を付加している。
本プログラムでは、ブラウザがサーバと通信している状態を再現して404エラーが起きるかどうかをチェックするために、これらの情報を組み込み関数  stream_context_creat  を使って用意する。用意する情報は、getContext に書かれているとおりだ。
Referer として、対象URLのホスト名を利用する。
これらはアクセス偽装と呼ばれる手法だが、別に悪い意味はないので、念のため。

解説:指定したURLが存在するかどうかを連想配列に格納

check404.php

 354: /**
 355:  * 指定したURLが存在するかどうかを配列に格納する
 356:  * @param   string $url     チェックするURL(正規化済)
 357:  * @param   int    $ln      解析中の行番号
 358:  * @param   array  $items   チェック結果を格納する配列
 359: */
 360: function checkLink($url, $ln, &$items) {
 361:     $arr = array();
 362: 
 363:     // URLが $items に含まれていたら行番号以外をコピーして終了
 364:     // ネットへの余計なアクセスを減らすため
 365:     foreach ($items as $item) {
 366:         if ($item['url'] == $url) {
 367:             $arr['ln']   = $ln;
 368:             $arr['url']  = $url;
 369:             $arr['res']  = $item['res'];
 370:             $arr['size'] = $item['size'];
 371:             $items[] = $arr;
 372:             return;
 373:         }
 374:     }
 375: 
 376:     // cURL関数を使って存在チェックする
 377:     if (function_exists('curl_exec')) {
 378:         list($code, $size) = getHttpResponseCodeSize($url);
 379:         if (($code !NULL&& ($code !200)) {
 380:             $arr['res'] = 'なし';
 381:             $size = (-1);
 382:         //サイズ>0 または パス指定
 383:         } else if (($size > 0|| (preg_match('/\/$/ui', $url> 0)) {
 384:             $arr['res'] = 'あり';
 385:         } else {
 386:             $arr['res'] = 'ゼロ';
 387:             $size = 0;
 388:         }
 389: 
 390:     // getContentsSize関数を使って存在チェックする
 391:     } else {
 392:         $size = getContentsSize($url);
 393:         if ($size < 0) {
 394:             $arr['res'] = 'なし';
 395:         } else if ($size == 0) {
 396:             $arr['res'] = 'ゼロ';
 397:         } else {
 398:             $arr['res'] = 'あり';
 399:         }
 400:     }
 401: 
 402:     // 配列に追加する
 403:     $arr['url']  = $url;        // 対象URL
 404:     $arr['ln']   = $ln;         // 対象URLがある行番号
 405:     $arr['size'] = $size;       // コンテンツ・サイズ(バイト)
 406:     $items[] = $arr;
 407: }

ユーザー定義関数 checkLink は、指定したURL(1つ)が存在するかどうかをチェックし、連想配列 $items に追加格納する。結果出力の都合で、指定したURLが含まれている解析中コンテンツの行番号も引数として渡す。

存在するかどうかをチェックするのに、3つの場合分けをしている。
  1. すでに連想配列 $items に登録されていれば、その内容をコピーする(行番号だけ解析中のものにする)。これは余計なネットワークアクセスを減らすための工夫だ。
  2. PHP処理系にcURL関数が実装されていれば、cURL関数を使って存在チェックする。
  3. それ以外の場合は、ユーザー関数 getContentsSize を使って存在チェックする。
結果は、次の4つの要素に格納する。
  • url‥‥対象URL
  • ln‥‥対象URLがある行番号(表示用)
  • size‥‥コンテンツ・サイズ(バイト)
  • res‥‥あり/なし/ゼロ(表示用)

解説:HTTPレスポンスコード、サイズを取得

check404.php

 291: /**
 292:  * HTTPレスポンスコード、サイズを取得
 293:  * @param   string $url チェックするURL
 294:  * @return  array(HTTPステータスコード:200 正常/NULL 取得失敗, サイズ)
 295: */
 296: function getHttpResponseCodeSize($url) {
 297:     // Refererを生成する
 298:     $parsedUrl = parse_url($url);
 299:     $referer = $parsedUrl['scheme'. '://' . $parsedUrl['host'. '/';
 300: 
 301:     // ヘッダー設定
 302:     $header = array(
 303:         'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
 304:         'Accept-Language: ja,en-US;q=0.9,en;q=0.8',
 305:         'Cache-Control: max-age=0',
 306:         'User-Agent: ' . USER_AGENT,
 307:         'Referer: ' . $referer,
 308:     );
 309: 
 310:     // cURL関数を使ってコンテンツをオープンする
 311:     $options = array(
 312:         CURLOPT_RETURNTRANSFER => TRUE,     // ホスト名の検証を無効化
 313:         CURLOPT_HEADER         => TRUE,     // HTTPステータスコードを返す
 314:         CURLOPT_HTTPHEADER     => $header,  // ヘッダ情報
 315:         CURLOPT_SSL_VERIFYPEER => FALSE,    // SSL証明書の検証を無効化
 316:         CURLOPT_SSL_VERIFYHOST => FALSE,    // ホスト名の検証を無効化
 317:         CURLOPT_FOLLOWLOCATION => TRUE,     // リダイレクト先に自動アクセス
 318:         CURLOPT_AUTOREFERER    => TRUE,     // リダイレクト先にReferer設定
 319:         CURLOPT_MAXREDIRS      => 10,       // リダイレクトを追跡する最大回数
 320:         CURLOPT_CONNECTTIMEOUT => 120,      // 接続開始までの最大待ち時間
 321:         CURLOPT_TIMEOUT        => 120,      // リクエスト終了までの最大時間
 322:         CURLOPT_ENCODING       => '',       // ヘッダのエンコーディンング
 323:     );
 324:     $ch = curl_init($url);
 325:     curl_setopt_array($ch, $options);
 326:     curl_exec($ch);
 327: 
 328:     // cURLエラーチェック
 329:     if (curl_errno($ch)) {
 330:         curl_close($ch);
 331:         return array(NULL, NULL);
 332:     }
 333:     $header = curl_getinfo($ch);
 334:     curl_close($ch);
 335: 
 336:     // コンテンツ・サイズを求める
 337:     $size = $header['download_content_length'];
 338:     // HTTPステータスが200でサイズが求められなかった場合の処置
 339:     if (($header['http_code'] == 200&& ($size < 0)) {
 340:         $size = getContentsSize($url);
 341:         if ($size < 0) {
 342:             $options[CURLOPT_HEADER] = FALSE;
 343:             $ch = curl_init($url);
 344:             curl_setopt_array($ch, $options);
 345:             $res = curl_exec($ch);
 346:             curl_close($ch);
 347:             $size = strlen($res);
 348:         }
 349:     }
 350: 
 351:     return array($header['http_code'], $size);
 352: }

PHPにcURL関数が実装されている場合は、ユーザー定義関数 getHttpResponseCodeSize を使って、指定したURLが存在するかどうかをチェックする。戻り値は、HTTPレスポンスコードとコンテンツ・サイズ(バイト)である。

httpヘッダ は、配列 $http に格納しておく。getContext 関数同様、User-AgentReferを偽装する。

cURL関数に設定するオプションは、コメントの通りである。CURLOPT_ENCODING は、コンテンツがgzipで圧縮されている場合に備えての指定である。

アクセスに成功したら、 curl_getinfo  関数で得られる download_content_length をコンテンツ・サイズとして返す。
もし、HTTPステータスコードが正常(200)なのに、この値が取得できなかった場合は、後述するユーザー定義関数 getContentsSize を使ってサイズを取得する。

解説:http上のファイルサイズ取得

check404.php

 273: /**
 274:  * http上のファイルサイズ取得
 275:  * @param   string $url 対象コンテンツ
 276:  * @param   long   ファイルサイズ
 277: */
 278: function getContentsSize($url) {
 279:     $data = '';
 280:     $context = getContext($url);
 281:     $fp = @fopen($url, 'r', FALSE, $context);
 282:     if (! $fp)      return (-1);
 283:     while (! feof($fp)) {
 284:         $data .fread($fp, 1024);
 285:     }
 286:     fclose($fp);
 287: 
 288:     return strlen($data);
 289: }

ユーザー定義関数 getContentsSize は、cURL関数が使えないときにURLの存在チェックを行う。

ユーザー関数 getContext で生成したストリームコンテキストを使って、ブラウザからアクセスしているように見せかけて  fopen  関数を実行し、[fread:phph_function] 関数を使ってコンテンツを読み込み、そのサイズを返す。

解説:URLを正規化する

XPath式 で得られたリンク先は相対アドレス(例:"./index.htm")のこともあるので、ステータスを調べるときに支障が出る。そこで、すべて絶対アドレスに正規化する。そのためのユーザー定義関数が normalizeURLs である。引数として、正規化する対象URLと、読み込んだコンテンツのURL(基準となる絶対アドレス)の2つを渡す。

まず、httpではじまるアドレスであれば絶対アドレスなので、即リターンする。

次に、基準となる絶対アドレス $sour と正規化するURLを結合し、変数 $url に再代入する。このとき、パス分離記号のスラッシュ "/" が二重にならないよう調整している。

この段階では、変数 $url の中に、相対パス指定を意味するドット . または .. が含まれている。続く処理で、スラッシュ "/" を目印にパスを分離し、ドット標記を消し込んでいく。

check404.php

 175: /**
 176:  * URLを正規化する
 177:  * 相対アドレスを絶対アドレスに変換する
 178:  * @param   string $url  正規化するURL
 179:  * @param   string $sour 読み込んだコンテンツのURL(絶対アドレス)
 180:  * @return  string 正規化したURL / FALSE(正規化に失敗;URLではない)
 181: */
 182: function normalizeURLs($url, $sour) {
 183:     // 前後の空白を除く
 184:     $url = trim($url);
 185:     // httpで始まればそのまま返す
 186:     if (preg_match('/^http/', $url!FALSE) {
 187:         return $url;
 188:     }
 189: 
 190:     // 末尾のスラッシュを除く
 191:     $sour = rtrim($sour, '/');
 192: 
 193:     // 基準となる絶対アドレスと正規化するURLを結合する
 194:     if (preg_match('/\:\/\//', $url) == FALSE) {
 195:         $regs = parse_url($sour);
 196:         // URLとして分離できなければFALSE
 197:         if (!isset($regs['scheme']) || !isset($regs['host'])) {
 198:             return FALSE;
 199:         }
 200:         $dirname = isset($regs['path']) ? dirname($regs['path']) : '';
 201:         if (preg_match('/^\//', $url> 0) {
 202:             $url = ltrim($url, '/');        // 冒頭のスラッシュは除く
 203:             $dirname = '';
 204:         }
 205:         $url = $regs['scheme'. '://' . $regs['host'. $dirname . '/' . $url;
 206:     }
 207: 
 208:     // ドット . または .. を置換する
 209:     $regs = parse_url($url);
 210:     // URLとして分離できなければFALSE
 211:     if (!isset($regs['scheme']) || !isset($regs['host'])) {
 212:         return FALSE;
 213:     }
 214:     $aa = preg_split("/[\/\\\]/", $regs['path']);
 215:     $an = count($aa);
 216:     $bb = array();
 217:     $bn = 0;
 218:     for ($i = 1$i < $an$i++) {
 219:         switch ($aa[$i]) {
 220:             case '.':
 221:                 break;
 222:             case '..':
 223:                 $bn--;
 224:                 if ($bn < 0return FALSE;
 225:                 break;
 226:             default:
 227:                 $bb[$bn] = $aa[$i];
 228:                 $bn++;
 229:                 break;
 230:         }
 231:     }
 232:     $ss = '';
 233:     for ($i = 0$i < $bn$i++) {
 234:         $ss = $ss . '/' . $bb[$i];
 235:     }
 236: 
 237:     return $regs['scheme'. '://' . $regs['host'. $ss;
 238: }

解説:HTMLバリーデーション

指定したHTMLが文法的に正しいかどうか、W3Cが用意するクラウドサービス「Markup Validation Service」と連携してチェックする。
Markup Validation Service
URL
https://validator.w3.org/nu/
リクエスト・データ(http) header Content-Type "text/html; charset=UTF-8" User-Agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36" get doc 検査するURL out "json"
レスポンス・データ(json) url 検査したURL messages type info(情報)|warning(警告)|error(エラー) lastLine エラーや警告が発生した行番号 firstColumn エラーや警告が発生した列の開始位置 lastColumn エラーや警告が発生した列の終了位置 message エラーメッセージの内容 extract 問題のある箇所のHTML抜粋 hiliteStart exgtract内の強調開始位置 hiliteLength extract内の強調長さ type info(情報)|warning(警告)|error(エラー) lastLine エラーや警告が発生した行番号 firstColumn エラーや警告が発生した列の開始位置 lastColumn エラーや警告が発生した列の終了位置 message エラーメッセージの内容 extract 問題のある箇所のHTML抜粋 hiliteStart exgtract内の強調開始位置 hiliteLength extract内の強調長さ language 検査したHTMLの言語
これをユーザー定義関数として実装したものが validateHTML である。

check404.php

 470: /**
 471:  * 指定したURLをHTMLバリーデーションする.
 472:  * W3C Markup Validator Web Service APIを利用する.
 473:  * @param   string $url バリデーションするURL
 474:  * @return  array(エラー数,警告数,情報数)/FALSE:APIコール失敗
 475: */
 476: function validateHTML($url) {
 477:     //W3C Markup Validator Web Service API
 478:     $webapi = 'https://validator.w3.org/nu/?';
 479:     //API引数
 480:     $params = array(
 481:         'doc'   => $url,
 482:         'out'   => 'json',
 483:     );
 484:     //HTTPヘッダ
 485:     $headers = array(
 486:         'Content-Type: text/html; charset=UTF-8',
 487:         'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36',
 488:     );
 489: 
 490:     //W3C Markup Validator Web Service APIを呼び出す.
 491:     $options = array(
 492:         CURLOPT_URL            => $webapi . http_build_query($params),
 493:         CURLOPT_RETURNTRANSFER => TRUE,     // ホスト名の検証を無効化
 494:         CURLOPT_HTTPHEADER     => $headers, // ヘッダ情報
 495:         CURLOPT_SSL_VERIFYPEER => FALSE,    // SSL証明書の検証を無効化
 496:         CURLOPT_SSL_VERIFYHOST => FALSE,    // ホスト名の検証を無効化
 497:         CURLOPT_FOLLOWLOCATION => TRUE,     // リダイレクト先に自動アクセス
 498:         CURLOPT_AUTOREFERER    => TRUE,     // リダイレクト先にReferer設定
 499:         CURLOPT_MAXREDIRS      => 10,       // リダイレクトを追跡する最大回数
 500:         CURLOPT_CONNECTTIMEOUT => 120,      // 接続開始までの最大待ち時間
 501:         CURLOPT_TIMEOUT        => 120,      // リクエスト終了までの最大時間
 502:     );
 503:     $ch = curl_init();
 504:     curl_setopt_array($ch, $options);
 505:     $response = curl_exec($ch);
 506:     curl_close($ch);
 507: 
 508:     // エラーチェック
 509:     if ($response == FALSE) {
 510:         return FALSE;
 511:     }
 512:     // JSONデコード
 513:     $json = json_decode($response);
 514: 
 515:     // エラーチェック
 516:     if (! isset($json->messages)) {
 517:         return FALSE;
 518:     } else if (count($json->messages) == 0) {
 519:         return array(0, 0, 0);
 520:     }
 521: 
 522:     // エラー、警告、情報をカウントする.
 523:     $error = $warning = $info = 0;
 524:     foreach ($json->messages as $message) {
 525:         // エラー
 526:         if (preg_match('/error/ui', (string)$message->type> 0) {
 527:             $error++;
 528:         // 警告
 529:         } else if (preg_match('/warning/ui', (string)$message->type> 0) {
 530:             $warning++;
 531:         // 情報
 532:         } else if (preg_match('/info/ui', (string)$message->type> 0) {
 533:             $info++;
 534:         }
 535:     }
 536: 
 537:     return array($error, $info, $warning);
 538: }

バリデーション結果は、messagesノードに格納される。messagesノードは1つまたは複数あり、その下のtypeノードが "error" ならエラーを、"warning" なら警告を、"info" なら情報を、それぞれカウントする。

解説:メイン・プログラム

メイン・プログラムは、FORMタグに入力されているパラメータを受け取り、上述の check404関数を使ってリンク先のチェックを行い、必要に応じて上述の validateHTML 関数を使ってHTMLバリデーションを行う。

なお、パラメータ取得時に、対象URLがURL文字列であるかどうか、"pahooInputData.php" にある validURL関数を使ってバリデーションする。
また、除外パターンを行分割して配列 $excludes に格納し、各要素に対して正規表現であるかどうかのバリデーションを行う。バリデーションを行うのは、以下に示すユーザー定義関数 validRegexPattern である。

check404.php

 661: // メイン・プログラム ======================================================
 662: // パラメータを取得する
 663: $items = array();
 664: $errmsg = '';
 665: $htmlerror = $htmlwarning = $htmlinfo = '?';
 666: 
 667: $flag = getParam('flag', FALSE, '');
 668: $flag = ($flag == '1'? FALSE : TRUE;
 669: $checkhtml = getParam('checkhtml', FALSE, '');
 670: $checkhtml = ($checkhtml == '1'? TRUE : FALSE;
 671: // URLのバリデーション
 672: $url = getParam('url', FALSE, '');
 673: if (isButton('exec'&& (validURL($url, $errmsg) == FALSE)) {
 674:     $errmsg = $url . ' はURLではありません';
 675: }
 676: 
 677: // 除外パターンのバリデーション
 678: $excludeList = NULL;
 679: $exclude = getParam('exclude', FALSE, DEF_EXCLUDE);
 680: if (isButton('exec'&& ($exclude !'')) {
 681:     $excludes = explode("\n", $exclude);
 682:     foreach ($excludes as $key=>$val) {
 683:         $val = trim($val);
 684:         if ($val !'') {
 685:             if (validRegexPattern($val)) {
 686:                 $excludeList[$key] = $val;
 687:             } else {
 688:                 $errmsg = $val . ' は正規表現ではありません';
 689:                 break;
 690:             }
 691:         }
 692:     }
 693:     if (count($excludeList) == 0) {
 694:         $excludeList = NULL;
 695:     }
 696: }
 697: 
 698: // 404チェックを実行する
 699: if ($errmsg == '') {
 700:     $errmsg= ($url !''? check404($url, $items, $errmsg, XPATH_LIST, $excludeList: '';
 701:     // HTMLバリデーションを行う。
 702:     if (($url !''&& ($errmsg == ''&& $checkhtml) {
 703:         $arr = validateHTML($url);
 704:         if ($arr !FALSE) {
 705:             $htmlerror   = $arr[0];
 706:             $htmlinfo    = $arr[1];
 707:             $htmlwarning = $arr[2];
 708:         }
 709:     }
 710: }
 711: 
 712: // 表示HTMLを作成する
 713: $HtmlBody = makeCommonBody($url, $items, $htmlerror, $htmlwarning, $htmlinfo, $errmsg, $flag, $checkhtml, $exclude);
 714: 
 715: // 画面に表示する
 716: echo $HtmlHeader;
 717: echo $HtmlBody;
 718: echo $HtmlFooter;
 719: 
 720: /*

pahooInputData.php

 481: /**
 482:  * 正規表現のバリデーションを行う.
 483:  * @参考URL https://www.pahoo.org/e-soul/webtech/php06/php05-23-01.shtm
 484:  * @param   string $reg  正規表現;スラッシュ /.../ で囲むこと.
 485:  * @param   string $text マッチングさせる文字列;デフォルトでは空文字
 486:  * @return  bool TRUE:正規表現/FALSE:正規表現ではない
 487: */
 488: function validRegexPattern($reg, $text='') {
 489:     $res = @preg_match($reg, $text);
 490:     return ($res === FALSE? FALSE : TRUE;
 491: }

参考サイト

(この項おわり)
header