itcore TOP小技ツールlink_check.php

階層をたどってHPのリンク切れを調べる。 | itcore 2017

チェックするURLを入力してください。


同じサーバ内のみリンクを辿ります。
MAX 100 件までチェックします。
コメント文字列の判定はしていません。(コメントもリンク対象となります)

テストリンク 外部、相対パス、エラーリンクなど

https://www.google.com
http://www.google.co.jp
../kowaza/link_check.php
./link_check.php
. Forbidden
../link_check.php NotFound

プログラムソース 100件を超えるチェックをしたい場合はこちらを自サーバへ設置してください、

<?php
ini_set( 'display_errors', 1 ); // エラーを画面に出す。
//--------------------
// 初期設定
//--------------------
$title = "階層をたどってHPのリンク切れを調べる。 | itcore 2017";
$ver = "ver0.1"; // 2017-12-26 初期開発
//--------------------
// 主要変数
//--------------------
$self_id = "link_check"; $self = basename($_SERVER['PHP_SELF']);
date_default_timezone_set('Asia/Tokyo'); $dt = date("Y-m-d H:i:s"); // 日時
$mode = uForm("mode");
$a_url = array(); // チェック済みURL
$max_url = 100; // チェックするURL数のMAX
if ("182.171.225.60" == $_SERVER['REMOTE_ADDR']) {
  echo "ITCORE MAX=1000<br>\n";
  $max_url = 1000; // チェックするURL数のMAX
}
$no_url = 1; // URL番号
//--------------------
// ヘッダ、スタイルシート
//--------------------
print <<<EOT
<!DOCTYPE html><html lang="ja">
<head>
<meta charset="utf-8">
<title>$title</title>
<link rel="/shortcut icon" href="../favicon.ico">
<style type="text/css">
a:hover {background-color: lightpink;} /* リンクの背景色を変える */
h1 {background: limegreen;}
h2 {background: aquamarine;}
h3 {background: pink;}
h4 {background: gold;}
img {border: dimgray solid 1px; max-width:100%; height:auto;}
.msg {color:darkgreen;} /* メッセージ */
/* tbl01 テーブル thに背景(緑) */
.tbl01 {border-collapse: collapse;margin:6px; }
.tbl01 th{padding: 6px;border: 1px solid maroon; font-size:medium; background-color:yellowgreen;}
.tbl01 td{padding: 6px;border: 1px solid maroon;}
/* tbl02 データ内テーブル thに背景(灰色) 枠線グレー */
.tbl02 {border-collapse: collapse;margin:6px; }
.tbl02 th{padding: 6px;border: 1px solid dimgray; font-size:medium; background-color:lightgray;}
.tbl02 td{padding: 6px;border: 1px solid dimgray;}
/* tbl03 データ内テーブル 枠なし 背景なし*/
.tbl03 {border-collapse: collapse;margin:6px; }
.tbl03 th{padding: 6px;border: 0px; background-color:white; text-align:left;}
.tbl03 td{padding: 6px;border: 0px;}
</style>
</head>
<body>
<SCRIPT>
//--------------------
// JavaScript関数
//--------------------
//初期クリア
function jClear(){
  document.f1.url.value = "";
}
</SCRIPT>
EOT;
include_once "../google.inc"; // Google Abalytics
//--------------------
// パンくずリスト
//--------------------
print <<<EOT
<a href=/>itcore TOP</a>
><a href=/#tool>小技ツール</a>
><a href=$self>$self</a>
<h2>$title</h2>
EOT;
//--------------------
// 初期画面
//--------------------
if ("" == $mode) {
  print "
  チェックするURLを入力してください。<br>
  <form method=post action=$self name=f1>
  <input type=hidden name=mode value=go>
  <input type=text name=url size=50 autofocus required value='https://www.itcore.jp/'>
  <select name=max_level>
  <option value=1 selected>1階層まで</option>
  <option value=2>2階層まで</option>
  <option value=3>3階層まで</option>
  <option value=>無制限</option>
  </select><br>
  <br>
  <input type=submit name=sub_check value='リンク切れチェック'>
  <input type=button value='クリア' onClick='jClear()'>
  </form>
  同じサーバ内のみリンクを辿ります。<br>
  MAX $max_url 件までチェックします。<br>
  コメント文字列の判定はしていません。(コメントもリンク対象となります)<br>

  <h3>テストリンク 外部、相対パス、エラーリンクなど</h3>
  <a href=https://www.google.com>https://www.google.com</a><br>
  <a href=http://www.google.co.jp>http://www.google.co.jp</a><br>
  <a href=../kowaza/link_check.php>../kowaza/link_check.php</a><br>
  <a href=./link_check.php>./link_check.php</a><br>
  <a href=.>.</a> Forbidden<br>
  <a href=../link_check.php>../link_check.php</a> NotFound<br>

  <h3>プログラムソース {$max_url}件を超えるチェックをしたい場合はこちらを自サーバへ設置してください、</h3>
  ";
  print nl2br(uHtmlspecialcharsIndent(file_get_contents($self)));
  print "<br><hr>\n";
  }
//--------------------
// 実行
//--------------------
if ("go" == $mode) {
  $url = trim(uForm("url"));
  //$url = strtolower($url); // 小文字にする。
  if ("http://" != strtolower(substr($url, 0, 7)) && "https://" != strtolower(substr($url, 0, 8))) {
    print "URLはhttp://又はhttps://で始めてください。<br>\n";
    exit;
  }
  $level = 0; // 階層
  $url_p = ""; //呼び出し元
  $a_url[$url] = 1; // 呼び出し回数
  print "$no_url $url<br>\n";
  u1CheckUrl($level, $url_p, $url, $a_url, $max_url, $no_url); // URLを辿ってチェックする。
  print "<br>" . uJsBack();
}

//--------------------
// 終了
//--------------------
print "</body></html>\n";
exit; // ここで終了
?>

<?php
//-------------------------------------------------------------------
// 個別関数
//-------------------------------------------------------------------
// URLを辿ってチェックする。
function u1CheckUrl($level, $url_p, $url, &$a_url, $max_url, &$no_url) {
  // 階層を+1
  $level++;

  $no_url_p = $no_url; // このページのURL番号
  // 画面を途中で表示させる。
  flush(); ob_flush();
  //echo "debug level=$level url_p=$url_p url=$url<br>\n";
  // URLチェック
  $arr_p = parse_url($url_p); // 呼び出し元
  $arr = parse_url($url);
  if (false === $arr) {
    print "URLの形式が正しくありません。url=$url<br>\n";
    return false;
  }
  // var_dump($arr);echo "<br>\n";
  if ("" == $url_p) {
    // 始めのURL
    $url_p = $url;
  } elseif (!isset($arr["host"])) {
    // host部分なし 自分のサーバ
  } elseif ($arr["host"] != $arr_p["host"]) {
    //echo "debug 自分以外のリンク先<br>\n";
    return true;
  }

  // コンテンツ取得
  //echo "debug url_p=$url_p url=$url";
  $url = uUrlSeikika($url_p, $url); // http[s]://[host]/[page]の形式にする。
  //echo "seikika_url=$url<br>\n";
    $data = @file_get_contents($url); // @ エラーメッセージを抑止。
  if (false === $data) {
    print "<font color=red>URLがオープン出来ません。url=$url</font></font><br>\n";
    return false;
  }
  $mojisu = mb_strlen($data);
  //print "階層$level $url 文字数:$mojisu<br>\n";
  //print nl2br(htmlspecialchars(shell_exec("echo \"$data\" | head -3")));
  //$arr = explode(" ", $http_response_header[0]); // "HTTP/1.1 200 OK"
  //$status = $arr[1];
  //echo "status=$status<br>\n";
  //var_dump($http_response_header);echo "<br>\n";
  //if ("301" == $status) {
  // $arr = explode(" ", $http_response_header[3]); // "Location: https://www.itcore.jp/"
  // $url2 = $arr[1];
  // echo "url2=$url2<br>\n";
  //}

  // 指定階層まで
  $max_level = uForm("max_level");
  if ("" != $max_level && $max_level < $level) return;

  // 同じページ内のリンクの場合は辿らない。
  $path_p = ""; if (isset($arr_p["path"])) $path_p = $arr_p["path"];
  $path = ""; if (isset($arr["path"])) $path = $arr["path"];
  $pos_p = strpos($path_p, "#");
  $pos = strpos($path, "#");
  if (false !==$pos_p) $path_p = substr($path_p, 0, $pos_p);
  if (false !==$pos) $path = substr($path, 0, $pos);
  if ($path_p == $path) return true;
  //echo "debug path_p=$path_p path=$path<br>\n";
    // リンクを抽出する。
  //$data = strtolower($data); // 小文字へ
  $data = str_replace(array("'", '"'), "", $data); // クオーテーションを削除
  $href = " href="; // 検索文字
  $data = str_ireplace(" action=", $href, $data); // form
  $data = str_replace(">", " ", $data); // タグとじ>を空白区切り文字へ
  $data .= " "; // 念のため 最後にリンクがあった時のため
  $cnt = 0;
  $a_url_wait2 = array(); // このページの待ちリスト
  //while (false !== ($pos = stripos($data, $href))) {
  while (false !== ($data = stristr($data, "<a "))) {
    $pos = stripos($data, $href);
    //echo "debug pos=$pos<br>\n";
    $data = substr($data, $pos + strlen($href)); // URL以降
    $pos2 = strpos($data, " ");
    if (false === $pos2) break;
    //echo "debug pos2=$pos2<br>\n";
    $url2 = substr($data, 0, $pos2);
    //echo "debug url2=$url2<br>\n";
    $data = substr($data, $pos2); // 次のデータ
    //------------------------
    // 見つかったURLへの処理
    //------------------------
    if ("#" == $url) continue; // href=# は無視
    $url3 = uUrlSeikika($url, $url2); // http[s]://[host]/[page]の形式にする。
    if (isset($a_url[$url3])) {
      $a_url[$url3] ++;
    } else {
      $a_url[$url3] = 1;
    }
    $num = $a_url[$url3];
    $indent = "";
    for ($i = 0; $i < $level; $i++) {
      $indent .= "&nbsp;&nbsp;";
    }
    //print "$indent$url3 {$num}回目<br>\n";
    $cnt++;
    if (100 < $cnt) {
      print "<font color=red>ページ内リンク数が100を超えましたのでスキップします。</font></font><br>\n";
      break; // 1ページ内リンク数MAX
    }
    // 下の階層へ 1回目のみ
    if (1 == $num) {
      // 待ちリストに追加する。
      $a_url_wait2[] = $url2; // このページの待ちリスト
    }
  }
  // このページのすべてのリンクを待ちリストに入れてから、順番に下へ探索する。
  foreach ($a_url_wait2 as $url3) {
    $no_url++; // URL番号
    if ($no_url > $max_url) {
      print "<font color=red>MAX URL数 $max_url を超えました。</font></font><br>\n";
      exit;
    }
    $url3_seiki = uUrlSeikika($url, $url3); // http[s]://[host]/[page]の形式にする。
    $disp = "";
    if ($url3 != $url3_seiki) $disp = " ($url3_seiki)";
    print "$indent$no_url_p" . "->" . "$no_url $url3$disp<br>\n";
    if (".exe" == strtolower(substr($url3, -4))) continue; // .exeファイルは辿らない。
    if (".pdf" == strtolower(substr($url3, -4))) continue; // .pdfファイルは辿らない。
    u1CheckUrl($level, $url, $url3, $a_url, $max_url, $no_url); // 再帰呼び出し
  }
  return true;
}
//-------------------------------------------------------------------
// 共通関数
//-------------------------------------------------------------------
// フォーム変数の受取
function uForm($var) {
  if (isset($_POST[$var])) return $_POST[$var];
  if (isset($_GET[$var])) return $_GET[$var];
  return "";
}
// 1つ前の画面に戻る。
function uJsBack() {
  return "<input type=button value=\"戻る\" onClick=\"history.go(-1);\">\n";
}
// $urlをベースに$url2をhttp[s]://[host]/[page]の形式にする。
function uUrlSeikika($url, $url2) {
  // $urlが正規化されていなければエラー
  if ("http://" != substr($url, 0, 7) && "https://" != substr($url, 0, 8)) {
    print "URLはhttp://又はhttps://で始めてください。<br>\n";
    return $url2;
  }
  // $url2が既に正規化されている場合はそのまま返す。
  if ("http://" == substr($url2, 0, 7) || "https://" == substr($url2, 0, 8)) {
    return $url2;
  }
   // $urlの分解
  $arr = parse_url($url);
  $scheme = $arr["scheme"]; // http[s]
  $host = $arr["host"];
  $path = ""; if (isset($arr["path"])) $path = $arr["path"];
  if ("" == $path) $path = "/";
  // $url2の加工 パスの途中に../などがある場合には未対応(普通はないはず)
  if ("/" == substr($url2, 0, 1)) {
    $url3 = "$scheme://$host$url2";
  } elseif ("./" == substr($url2, 0, 2)) {
    $dir_current = dirname2($path);
    if ("/" == $dir_current) $dir_current = "";
    $url3 = "$scheme://$host$dir_current" . substr($url2, 1);
  } elseif ("../" == substr($url2, 0, 3)) {
    $dir_parent = dirname2(dirname2($path));
    if ("/" == $dir_parent) $dir_parent = "";
    $url3 = "$scheme://$host$dir_parent" . substr($url2, 2);
    //echo "debug path=$path dirname2=" . dirname2(dirname2($path)) . "<br>\n";
  } elseif ("." == $url2) {
    $dir_current = dirname2($path);
    if ("/" == $dir_current) $dir_current = "";
    $url3 = "$scheme://$host$dir_current/";
  // ページ内リンク
  } elseif ("#" == substr($url2, 0, 1)) {
    $url3 = uRmPLink($url) . $url2;
  } else {
    $dir_current = dirname2($path);
    if ("/" == $dir_current) $dir_current = "";
    $url3 = "$scheme://$host$dir_current/$url2";
  }
  //echo "debug url3=$url3<br>\n";
  return $url3;
}
// 最後が/のpathに対応
function dirname2($path) {
  return dirname($path . "x"); // "/abc/x"
}
//ページ内リンクの部分を削除する。 /page.html#1 -> /page.html
function uRmPLink($url) {
  $pos = strpos($url, "#");
  if (false !==$pos) return substr($url, 0, $pos);
  return $url;
}
// インデント付きのhtmlspecialchars 行頭の空白を&nbsp;に変換する。
function uHtmlspecialcharsIndent($text) {
  $a_line = explode("\n", $text); // 行に分割
  $text1 = "";
  for($i2 = 0; $i2 < count($a_line); $i2++) {
    $line = $a_line[$i2];
    for ($i = 0; $i < strlen($line); $i++) {
    $s1 = substr($line, $i, 1);
      if (" " == $s1) {
        $text1 .= "&nbsp;";
      } else if ("\t" == $s1) {
        $text1 .= "&nbsp;&nbsp;&nbsp;&nbsp;";
      } else {
        $text1 .= htmlspecialchars(substr($line, $i)) . "\n";
        break;
      }
    }
    if (count($a_line) -1 != $i2 && 0 == strlen($line)) $text1 .= "\n"; // 最終行以外の空行
  }
  return $text1;
}
?>