PHPでリンク切れを調べる

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

(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.0.0 2024/11/15 全面改訂:XPath式導入, アクセス回数低減
4.6.0 2023/03/21 htmlChecker→validateHTMLに変更
4.5 2022/01/10 コンテンツ・サイズ取得の例外に対応
4.41 2021/06/12 スタイルシートへのリンク変更
4.4 2021/06/05 getContext()追加,USER_AGENT 変更
pahooInputData.php 更新履歴
バージョン 更新日 内容
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() 追加
1.4.2 2024/01/28 exitIfLessVersion() メッセージ修正
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

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

このプログラムの肝となるのはユーザー定義関数 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   なし
 243:  * @param   object コンテクスト
 244: */
 245: function getContext() {
 246:     //偽装ヘッダ
 247:     $header = array(
 248:         'Content-Type: text/html; charset=UTF-8',
 249:         'User-Agent: ' . USER_AGENT,
 250:         'Referer: https://www.yahoo.co.jp/'
 251:     );
 252:     $ssl = array(
 253:         'verify_peer' => FALSE,
 254:         'verify_peer_name' => FALSE
 255:     );
 256:     $opts = array(
 257:         'http' => array(
 258:             'method' => 'GET',
 259:             'header' => implode("\r\n", $header),
 260:             'ssl' => $ssl
 261:         )
 262:     );
 263: 
 264:     return stream_context_create($opts);
 265: }

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

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

check404.php

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

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

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

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

check404.php

 285: /**
 286:  * HTTPレスポンスコード、サイズを取得
 287:  * @param   string $url チェックするURL
 288:  * @return  array(HTTPステータスコード:200 正常/NULL 取得失敗, サイズ)
 289: */
 290: function getHttpResponseCodeSize($url) {
 291:     // ヘッダー設定
 292:     $header = array(
 293:         'Content-Type: text/html; charset=UTF-8',
 294:         'User-Agent: ' . USER_AGENT,
 295:         'Referer: https://www.yahoo.co.jp/'
 296:     );
 297: 
 298:     // cURL関数を使ってコンテンツをオープンする
 299:     $options = array(
 300:         CURLOPT_RETURNTRANSFER => TRUE,     // ホスト名の検証を無効化
 301:         CURLOPT_HEADER         => TRUE,     // HTTPステータスコードを返す
 302:         CURLOPT_HTTPHEADER     => $header,  // ヘッダ情報
 303:         CURLOPT_SSL_VERIFYPEER => FALSE,    // SSL証明書の検証を無効化
 304:         CURLOPT_SSL_VERIFYHOST => FALSE,    // ホスト名の検証を無効化
 305:         CURLOPT_FOLLOWLOCATION => TRUE,     // リダイレクト先に自動アクセス
 306:         CURLOPT_AUTOREFERER    => TRUE,     // リダイレクト先にReferer設定
 307:         CURLOPT_MAXREDIRS      => 10,       // リダイレクトを追跡する最大回数
 308:         CURLOPT_CONNECTTIMEOUT => 120,      // 接続開始までの最大待ち時間
 309:         CURLOPT_TIMEOUT        => 120,      // リクエスト終了までの最大時間
 310:     );
 311:     $ch = curl_init($url);
 312:     curl_setopt_array($ch, $options);
 313:     curl_exec($ch);
 314: 
 315:     // cURLエラーチェック
 316:     if (curl_errno($ch)) {
 317:         curl_close($ch);
 318:         return array(NULL, NULL);
 319:     }
 320:     $header = curl_getinfo($ch);
 321:     curl_close($ch);
 322: 
 323:     // コンテンツ・サイズを求める
 324:     $size = $header['download_content_length'];
 325:     // HTTPステータスが200でサイズが求められなかった場合の処置
 326:     if (($header['http_code'] == 200&& ($size < 0)) {
 327:         $size = getContentsSize($url);
 328:         if ($size < 0) {
 329:             $options[CURLOPT_HEADER] = FALSE;
 330:             $ch = curl_init($url);
 331:             curl_setopt_array($ch, $options);
 332:             $res = curl_exec($ch);
 333:             curl_close($ch);
 334:             $size = strlen($res);
 335:         }
 336:     }
 337: 
 338:     return array($header['http_code'], $size);
 339: }

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

cURL関数に設定するオプションは、コメントの通りである。
getContext 関数同様の偽装情報を変数 $header に格納し、HTTPヘッダとしてサーバに渡す。

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

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

check404.php

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

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

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

解説:URLを正規化する

XPath式 で得られたリンク先は相対アドレス(例:"./index.htm")のこともあるので、ステータスを調べるときに支障が出る。そこで、すべて絶対アドレスに正規化する。そのためのユーザー定義関数が [#:normalizeURLstitle=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

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

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

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

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

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

check404.php

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

pahooInputData.php

 480: /**
 481:  * 正規表現のバリデーションを行う.
 482:  * スラッシュ /.../ で囲まれているかどうかを照合する
 483:  * @参考URL https://www.pahoo.org/e-soul/webtech/php02/php02-16-01.shtm
 484:  * @param   string $reg    正規表現
 485:  * @return  bool TRUE:正規表現/FALSE:正規表現ではない
 486: */
 487: function validRegexPattern($reg) {
 488:     $pattern = '/^\/.*[^\\\\]\/$/';
 489:     return preg_match($pattern, $reg);
 490: }

参考サイト

(この項おわり)
header