* @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
*/
class admin_plugin_xtern extends DokuWiki_Admin_Plugin {
private $dnld = false;
private $check = false;
private $wikiRoot;
private $dir = NULL;
private $accumulator = null;
private $broken = array();
private $review = false;
private $headers;
private $debug_handle = null;
function __construct() {
$this->wikiRoot = realpath (DOKU_INC. 'data/pages');
$this->accumulator = metaFN('xtern:accumulator','.ser');
// $this->debug_handle=fopen(DOKU_INC.'xtern.txt', 'wb');
}
function handle() {
if (!isset($_REQUEST['cmd'])) return; // first time - nothing to do
$this->output = 'invalid';
if (!checkSecurityToken()) return;
if (!is_array($_REQUEST['cmd'])) return;
switch (key($_REQUEST['cmd'])) {
case 'check_links' :
$this->output = 'check_links';
$this->check = true;
if(!empty($_REQUEST['dir'])) {
$this->dir = $_REQUEST['dir'];
}
break;
case 'download' :
$this->output = 'download';
$this->dnld = true;
break;
case 'review_links' :
$this->output = 'reviews';
$this->review = true;
}
//msg(__DIR__);
}
/**
* output appropriate html
*/
function html() {
$max_time = $this->getConf('max_time');
$ini_max = ini_get('max_execution_time');
$max_time = $max_time > $ini_max ? $max_time : $ini_max;
$this->buttons($max_time);
if($this->check) {
$this->check_links($max_time);
}
else if ($this->dnld) {
$this->downloadPem();
}
else if($this->review ) {
$this->review_links();
}
}
function check_links($max_time) {
set_time_limit($max_time);
$this->disable_ob();
$this->headers = array();
$this->buttons($max_time,$this->dir);
if(isset($this->dir)){
$dir = trim($this->dir,':');
$dir = str_replace(':', '/', $dir);
$dir = $this->wikiRoot . '/' . $dir;
}
else $dir = $this->wikiRoot;
ptln('
');
echo "Checking: $dir
";
usleep(300000);
$site = $this->scanDirectories($dir);
echo "Checking links\n
";
echo "
\n";
usleep(300000);
foreach($site AS $entry=>$data) {
$handle = fopen($data['path'], "r");
if ($handle) {
$this->parse_dwfile($handle,$data['id'],$data['path']);
fclose($handle);
}
}
ptln("
DONE");
ptln('
' . NL);
io_saveFile($this->accumulator,serialize($this->broken)) ;
// fclose($this->debug_handle);
}
function review_links() {
$reviews_ar = unserialize(io_readFile($this->accumulator ,false)) ;
set_time_limit($max_time);
$this->disable_ob();
$this->buttons($max_timer);
ptln('
');
ptln('
');
foreach($reviews_ar as $id=>$errors) {
ptln("$id |
");
foreach($errors as $error) {
$this->do_check($error, "", $id);
}
}
ptln("
DONE");
ptln('
' . NL);
}
function buttons($max_time = "",$ns="") {
ptln('');
echo $this->locale_xhtml('header');
ptln('
');
//$ns = isset($this->dir) ? $this->dir : "";
ptln('' .NL);
ptln('');
if($max_time) {
ptln('
' . $this->getLang('max_time') . ": $max_time");
}
ptln('
');
}
/**
* @ $id wiki page
* @ $url broken link address
*/
function local_url($id,$url) {
$id = trim($id,':');
$url = rawurlencode($url);
$id = str_replace(array('"', "'"),array(""),$id);
return " $id";
}
function add_broken($id,$url) {
$id = trim($id,':');
if(!isset($this->broken[$id])) {
$this->broken[$id] = array();
}
$this->broken[$id][] = $url;
}
function parse_dwfile($handle="",$id, $path) {
$in_code = false;
$in_file = false;
$lineno = 0;
while (!feof($handle)) {
$lineno++;
$buffer = fgets($handle);
if($in_code) {
if(preg_match("#<\/code>#",$buffer)) {
$in_code = false;
}
else continue;
}
if($in_file) {
if(preg_match("#\<\/file>#",$buffer)) {
$in_file = false;
}
else continue;
}
if(preg_match("#^\s*\#",$buffer)) {
$in_code=true;
continue;
}
if(preg_match("#^\s*\#",$buffer)) {
$in_file=true;
continue;
}
if(preg_match("#\#",$buffer)) {
if(preg_match('#\.*?https?:\/\/.*?\<\/nowiki\>#', $buffer)) {
continue;
}
}
if(preg_match("#\[?(https?://\S+)\]?#",$buffer,$matches)) {
preg_match_all("#https?://\S+#",$buffer,$submatches);
$num_urls = count($submatches[0]);
if($num_urls > 1) {
foreach($submatches[0] as $link) {
$link = preg_replace("#[^\w\#\?\/]+$#m","",$link);
$this->do_check($link,$lineno,$id);
}
}
else {
$this->do_check($matches[1],$lineno,$id);
}
}
}
}
function do_check($url, $lineno = "",$id = "") {
$url = trim($url,' )(\\;:-!"\'.,');
list($url,$rest) = explode('|',$url);
$header = $id ? "$id |
" : "";
if(strpos($url, '{{') !== false || strpos($url, '}}') !== false) {
if(preg_match("#\{\{https?://(.*?)\}\}#", $url,$submatches)) {
$url = $submatches[1];
$url = "submatches: $url";
}
else return "";
}
$url = trim($url,']');
$status = $this->link_check($url);
if($status !="200" && $status !="300" && $status != "301" && $status != "0") {
$link =$this->local_url($id,$url);
$len = strlen($url);
if(strlen($url) > 1024) {
$status = "414";
}
if($lineno) {
$this->add_broken($id,$url);
if(!isset($this->headers[$id])) {
ptln($header);
$this->headers[$id] = 1;
}
}
$trunc = substr($url,0,512);
if(strlen($trunc) > strlen($url)) {
$url .= '. . .';
}
ptln('');
echo $status .": $link:\n ";
usleep(300000);
if($lineno) {
echo ' line' . " $lineno: $url" . "\n";
}
else {
echo " ". $this->getLang('bad_link') . ": $url\n";
}
ptln(' |
');
usleep(300000);
}
}
function link_check($url) {
$url = trim($url, ' "\'' );
$url=html_entity_decode($url);
$ch = curl_init($url);
// curl --remote-name --time-cond cacert.pem https://curl.haxx.se/ca/cacert.pem
if($this->getConf('ca_required')) {
curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . "/ca/cacert.pem");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
}
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_FOLLOWLOCATION,1);
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
curl_setopt($ch,CURLOPT_TIMEOUT,15);
curl_setopt($ch, CURLOPT_NOBODY, 1); //just fetch headers
$output = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_errno = curl_errno($ch);
if($curl_errno) {
return "Curl Erro: " .curl_errno($ch) . "--" . curl_error($ch);
}
curl_close($ch);
return trim("$httpcode");
}
/*https://stackoverflow.com/questions/1281140/run-process-with-realtime-output-in-php/5956708#5956708 */
function disable_ob() {
// Turn off output buffering
ini_set('output_buffering', 'off');
// Turn off PHP output compression
// ini_set('zlib.output_compression', false);
// Implicitly flush the buffer(s)
ini_set('implicit_flush', true);
ob_implicit_flush(true);
// Clear, and turn off output buffering
while (ob_get_level() > 0) {
// Get the curent level
$level = ob_get_level();
// End the buffering
ob_end_clean();
// If the current level has not changed, abort
if (ob_get_level() == $level) break;
}
// Disable apache output buffering/compression
if (function_exists('apache_setenv')) {
apache_setenv('no-gzip', '1');
apache_setenv('dont-vary', '1');
}
}
/* http://php.net/manual/en/function.scandir.php#80057 */
function scanDirectories($rootDir, $allData=array()) {
// set filenames invisible if you want
$invisibleFileNames = array(".", "..", ".htaccess", ".htpasswd");
// run through content of root directory
$dirContent = scandir($rootDir);
foreach($dirContent as $key => $content) {
// filter all files not accessible
$path = $rootDir.'/'.$content;
// echo "$content\n
";
if(!is_dir($path)) {
$ext = pathinfo ( $path,PATHINFO_EXTENSION);
if($ext !='txt') continue;
}
if(!in_array($content, $invisibleFileNames) ) {
// if content is file & readable, add to array
if(is_file($path) && is_readable($path)) {
// save file name with path
$ns = preg_replace('#' . preg_quote($this->wikiRoot) . '#', "", $path);
$ns = str_replace(array('/','\\\\','.txt'), array(':',':'), $ns);
$allData[] = array('path'=>$path,'file'=>$content, 'id'=>$ns);
// if content is a directory and readable, add path and name
}elseif(is_dir($path) && is_readable($path)) {
// recursive callback to open new directory
$allData = $this->scanDirectories($path, $allData);
}
}
}
return $allData;
}
function downloadPem() {
@set_time_limit(60);
$SavePath = DOKU_INC . 'lib/plugins/xtern/ca/cacert.pem';
$url = "https://curl.haxx.se/ca/cacert.pem";
io_makeFileDir($SavePath);
$http = new DokuHTTPClient();
$http->max_bodysize = 32777216;
$http->timeout = 120;
$http->keep_alive = false;
$data = $http->get($url);
if(!$data) {
$this->say('download failed', $url);
return;
}
$fp = @fopen($SavePath,'wb');
if($fp === false) {
$this->say('write_fail', $SavePath);
return;
}
if(!fwrite($fp,$data)) {
$this->say('write_fail', $SavePath);
return;
}
fclose($fp);
$this->say('file_saved', $SavePath);
}
function say(){
$args = func_get_args();
echo vsprintf("%s: %s\n",$args);
ob_flush();
}
function write_debug($data) {
return;
if(!$this->debug_handle) return;
fwrite($this->debug_handle, "$data\n");
}
}