dokuwiki/bin/wantedpages.php

189 lines
5.1 KiB
PHP
Executable File

#!/usr/bin/env php
<?php
use dokuwiki\Utf8\Sort;
use dokuwiki\File\PageResolver;
use splitbrain\phpcli\CLI;
use splitbrain\phpcli\Options;
if (!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../') . '/');
define('NOSESSION', 1);
require_once(DOKU_INC . 'inc/init.php');
/**
* Find wanted pages
*/
class WantedPagesCLI extends CLI
{
protected const DIR_CONTINUE = 1;
protected const DIR_NS = 2;
protected const DIR_PAGE = 3;
private $skip = false;
private $sort = 'wanted';
private $result = [];
/**
* Register options and arguments on the given $options object
*
* @param Options $options
* @return void
*/
protected function setup(Options $options)
{
$options->setHelp(
'Outputs a list of wanted pages (pages that do not exist yet) and their origin pages ' .
' (the pages that are linkin to these missing pages).'
);
$options->registerArgument(
'namespace',
'The namespace to lookup. Defaults to root namespace',
false
);
$options->registerOption(
'sort',
'Sort by wanted or origin page',
's',
'(wanted|origin)'
);
$options->registerOption(
'skip',
'Do not show the second dimension',
'k'
);
}
/**
* Your main program
*
* Arguments and options have been parsed when this is run
*
* @param Options $options
* @return void
*/
protected function main(Options $options)
{
$args = $options->getArgs();
if ($args) {
$startdir = dirname(wikiFN($args[0] . ':xxx'));
} else {
$startdir = dirname(wikiFN('xxx'));
}
$this->skip = $options->getOpt('skip');
$this->sort = $options->getOpt('sort');
$this->info("searching $startdir");
foreach ($this->getPages($startdir) as $page) {
$this->internalLinks($page);
}
Sort::ksort($this->result);
foreach ($this->result as $main => $subs) {
if ($this->skip) {
echo "$main\n";
} else {
$subs = array_unique($subs);
Sort::sort($subs);
foreach ($subs as $sub) {
printf("%-40s %s\n", $main, $sub);
}
}
}
}
/**
* Determine directions of the search loop
*
* @param string $entry
* @param string $basepath
* @return int
*/
protected function dirFilter($entry, $basepath)
{
if ($entry == '.' || $entry == '..') {
return WantedPagesCLI::DIR_CONTINUE;
}
if (is_dir($basepath . '/' . $entry)) {
if (strpos($entry, '_') === 0) {
return WantedPagesCLI::DIR_CONTINUE;
}
return WantedPagesCLI::DIR_NS;
}
if (preg_match('/\.txt$/', $entry)) {
return WantedPagesCLI::DIR_PAGE;
}
return WantedPagesCLI::DIR_CONTINUE;
}
/**
* Collects recursively the pages in a namespace
*
* @param string $dir
* @return array
* @throws DokuCLI_Exception
*/
protected function getPages($dir)
{
static $trunclen = null;
if (!$trunclen) {
global $conf;
$trunclen = strlen($conf['datadir'] . ':');
}
if (!is_dir($dir)) {
throw new DokuCLI_Exception("Unable to read directory $dir");
}
$pages = [];
$dh = opendir($dir);
while (false !== ($entry = readdir($dh))) {
$status = $this->dirFilter($entry, $dir);
if ($status == WantedPagesCLI::DIR_CONTINUE) {
continue;
} elseif ($status == WantedPagesCLI::DIR_NS) {
$pages = array_merge($pages, $this->getPages($dir . '/' . $entry));
} else {
$page = ['id' => pathID(substr($dir . '/' . $entry, $trunclen)), 'file' => $dir . '/' . $entry];
$pages[] = $page;
}
}
closedir($dh);
return $pages;
}
/**
* Parse instructions and add the non-existing links to the result array
*
* @param array $page array with page id and file path
*/
protected function internalLinks($page)
{
global $conf;
$instructions = p_get_instructions(file_get_contents($page['file']));
$resolver = new PageResolver($page['id']);
$pid = $page['id'];
foreach ($instructions as $ins) {
if ($ins[0] == 'internallink' || ($conf['camelcase'] && $ins[0] == 'camelcaselink')) {
$mid = $resolver->resolveId($ins[1][0]);
if (!page_exists($mid)) {
[$mid] = explode('#', $mid); //record pages without hashes
if ($this->sort == 'origin') {
$this->result[$pid][] = $mid;
} else {
$this->result[$mid][] = $pid;
}
}
}
}
}
}
// Main
$cli = new WantedPagesCLI();
$cli->run();