tra('Wanted Pages'),
'documentation' => 'PluginWantedPages',
'description' => tra('Show location of links to pages not yet created'),
'prefs' => [ 'wikiplugin_wantedpages' ],
'body' => tr('Custom level regex. A custom filter for wanted pages to be listed (only used when %0). Possible
values: a valid regex-expression (PCRE).', 'level="custom"'),
'iconname' => 'search',
'introduced' => 1,
'tags' => [ 'basic' ],
'params' => [
'ignore' => [
'required' => false,
'name' => tra('Ignore'),
'description' => tra('A wildcard pattern of originating pages to be ignored. (refer to PHP function
fnmatch() for details)'),
'since' => '1',
'accepted' => tra('a valid regex-expression (PCRE)'),
'default' => '',
'advanced' => true,
],
'splitby' => [
'required' => false,
'name' => tra('Split By'),
'description' => tra('The character by which ignored patterns are separated.'),
'since' => '1',
'default' => '+',
'advanced' => true,
],
'skipalias' => [
'required' => false,
'name' => tra('Skip Alias'),
'description' => tra('Whether to skip wanted pages that have a defined alias (not skipped by default)'),
'since' => '12.1',
'default' => 0,
'filter' => 'digits',
'options' => [
['text' => '', 'value' => ''],
['text' => tra('Yes'), 'value' => 1],
['text' => tra('No'), 'value' => 0],
],
],
'skipext' => [
'required' => false,
'name' => tra('Skip Extension'),
'description' => tra('Whether to include external wikis in the list (not included by default)'),
'since' => '1',
'default' => 0,
'filter' => 'digits',
'options' => [
['text' => '', 'value' => ''],
['text' => tra('Yes'), 'value' => 1],
['text' => tra('No'), 'value' => 0],
],
],
'collect' => [
'required' => false,
'name' => tra('Collect'),
'description' => tra('Collect either originating (from) or wanted pages (to) in a cell and display them
in the second column.'),
'since' => '1',
'default' => 'from',
'filter' => 'alpha',
'options' => [
['text' => '', 'value' => ''],
['text' => tra('From'), 'value' => 'from'],
['text' => tra('To'), 'value' => 'to'],
],
],
'debug' => [
'required' => false,
'name' => tra('Debug'),
'description' => tra('Switch on debug output with details about the items (debug not on by default)'),
'since' => '1',
'default' => 0,
'filter' => 'digits',
'advanced' => true,
'options' => [
['text' => '', 'value' => ''],
['text' => tra('No'), 'value' => 0],
['text' => tra('Yes'), 'value' => 1],
['text' => tra('Memory Saver'), 'value' => 2],
],
],
'table' => [
'required' => false,
'name' => tra('Table'),
'description' => tra('Multiple collected items are separated in distinct table rows (default), or by
comma or line break in one cell.'),
'since' => '1',
'filter' => 'alpha',
'default' => 'sep',
'accepted' => 'sep, co, br',
'options' => [
['text' => '', 'value' => ''],
['text' => tra('Comma'), 'value' => 'co'],
['text' => tra('Line break'), 'value' => 'br'],
['text' => tra('Separate Row'), 'value' => 'sep'],
],
],
'level' => [
'required' => false,
'name' => tra('Level'),
'description' => tra('Filter the list of wanted pages according to page_regex or custom filter. The
default value is the site\'s __current__ page_regex.'),
'since' => '1',
'default' => '',
'filter' => 'alpha',
'advanced' => true,
'options' => [
['text' => '', 'value' => ''],
['text' => tra('Custom'), 'value' => 'custom'],
['text' => tra('Full'), 'value' => 'full'],
['text' => tra('Strict'), 'value' => 'strict'],
],
],
],
];
}
class WikiPluginWantedPages extends PluginsLib
{
function getDefaultArguments()
{
return [ 'ignore' => '', // originating pages to be ignored
'splitby' => '+', // split ignored pages by this character
'skipalias' => 0, // false, count a page alias as a wanted page
'skipext' => 0, // false, display external wiki links
'collect' => 'from', // display (and sort) wanted pages in the first column,
// collect originating pages in the second column (and separate them by table parameter)
'table' => 'sep', // show each line of output in a separate table row
'level' => '', // use current page_regex to filter output
'debug' => 0]; // false, no debug output; a value of 2
// tries to allocate as little memory as possible.
}
function getName()
{
return 'WantedPages';
}
function getDescription()
{
return wikiplugin_wantedpages_help();
}
function getVersion()
{
return preg_replace("/[Revision: $]/", '', "\$Revision: 1.7 $");
}
function run($data, $params)
{
global $prefs, $page_regex;
// Grab and handle our Tiki parameters...
extract($params, EXTR_SKIP);
if (! isset($ignore)) {
$ignore = '';
}
if (! isset($splitby)) {
$splitby = '+';
}
if (! isset($skipalias)) {
$skipalias = false;
}
if (! isset($skipext)) {
$skipext = false;
}
if (! isset($debug)) {
$debug = false;
}
if (! isset($collect)) {
$collect = 'from';
}
if (! isset($table)) {
$table = 'sep';
}
if (! isset($level)) {
$level = '';
}
// for regexes and external wiki details, see tikilib.php
if ($level == 'strict') {
$level_reg = '([A-Za-z0-9_])([\.: A-Za-z0-9_\-])*([A-Za-z0-9_])';
} elseif ($level == 'full') {
$level_reg = '([A-Za-z0-9_]|[\x80-\xFF])([\.: A-Za-z0-9_\-]|[\x80-\xFF])*([A-Za-z0-9_]|[\x80-\xFF])';
} elseif ($level == 'complete') {
$level_reg = '([^|\(\)])([^|\(\)](?!\)\)))*?([^|\(\)])';
} elseif (($level == 'custom') && ($data != '')) {
if (preg_ispreg($data)) { // custom regular expression
$level_reg = $data;
} elseif ($debug == 2) {
echo $data . ': ' . tra('non-valid custom regex') . '
';
}
} else { // default
$level_reg = $page_regex;
}
// define the separator
if ($table == 'br') {
$break = '
';
} elseif ($table == 'co') {
$break = tra(', ');
} else {
$break = 'sep';
}
// get array of fromPages to be ignored
$ignorepages = explode($splitby, $ignore);
// Currently we only look in wiki pages.
// Wiki links in articles, blogs, etc are ignored.
$query = 'select distinct tl.`toPage`, tl.`fromPage` from `tiki_links` tl';
$query .= ' left join `tiki_pages` tp on (tl.`toPage` = tp.`pageName`)';
if ($skipalias) {
$query .= ' left join `tiki_object_relations` tor on (tl.`toPage` = tor.`target_itemId`)';
}
$categories = $this->get_jail();
if ($categories) {
$query .= ' inner join `tiki_objects` as tob on (tob.`itemId`= tl.`fromPage` and tob.`type`= ?) inner join `tiki_category_objects` as tc on (tc.`catObjectId`=tob.`objectId` and tc.`categId` IN(' . implode(', ', array_fill(0, count($categories), '?')) . '))';
}
$query .= ' where tp.`pageName` is null';
if ($skipalias) {
$query .= ' and (tor.`relation` is null or tor.`relation` != \'tiki.link.alias\')';
}
$result = $this->query($query, $categories ? array_merge(['wiki page'], $categories) : []);
$tmp = [];
while ($row = $result->fetchRow()) {
foreach ($ignorepages as $ipage) {
// test whether a substring ignores this page, ignore case
if (fnmatch(TikiLib::strtolower($ipage), TikiLib::strtolower($row['fromPage'])) === true) {
if ($debug == 2) { // the "hardcore case"
echo $row['toPage'] . ' [from: ' . $row['fromPage'] . ']: ' . tra('ignored') . '
';
} elseif ($debug) { // add this page to the table
$tmp[] = [$row['toPage'], $row['fromPage'], 'ignored'];
}
continue 2; // no need to test other ignorepages or toPages
}
} // foreach ignorepage
// if toPage contains colon, and exloding yields two parts => external Wiki
if (($skipext) && (strstr($row['toPage'], ':') !== false)) {
$parts = explode(':', $row['toPage']);
if (count($parts) == 2) {
if ($debug == 2) {
echo $row['toPage'] . ' [from: ' . $row['fromPage'] . ']: ' . tra('External Wiki') . '
';
} elseif ($debug) {
$tmp[] = [$row['toPage'], $row['fromPage'], 'External Wiki'];
}
continue;
}
} // $skipext
$dashWikiWord = preg_match("/^(?<=[ \n\t\r\,\;]|^)([A-Z][a-z0-9_\-\x80-\xFF]+[A-Z][a-z0-9_\-\x80-\xFF]+[A-Za-z0-9\-_\x80-\xFF]*)(?=$|[ \n\t\r\,\;\.])$/", $row['toPage']);
$WikiWord = preg_match("/^(?<=[ \n\t\r\,\;]|^)([A-Z][a-z0-9\x80-\xFF]+[A-Z][a-z0-9\x80-\xFF]+[A-Za-z0-9\x80-\xFF]*)(?=$|[ \n\t\r\,\;\.])$/", $row['toPage']);
// test whether toPage is a valid wiki page under current syntax
if ($dashWikiWord && ! $WikiWord) { // a Dashed-WikiWord, can we allow this?
if (($prefs['feature_wikiwords'] != 'y') || ($prefs['feature_wikiwords_usedash'] != 'y')) {
$tmp = debug_print($row, $debug, tra('dash-WikiWord'));
continue;
}
} elseif ($WikiWord) { // a WikiWord, can we allow this?
if ($prefs['feature_wikiwords'] != 'y') {
$tmp = debug_print($row, $debug, tra('WikiWord'));
continue;
}
} else { // no WikiWord, we can now filter with the level parameter
if (! preg_match("/^($level_reg)$/", $row['toPage'])) {
$tmp = debug_print($row, $debug, tra('not in level'));
continue;
}
} // dashWikiWord, WikiWord, normal link
if (! $debug) { // a normal, valid WantedPage:
$tmp[] = [$row['toPage'], $row['fromPage']];
} elseif ($debug == 2) {
debug_print($row, $debug, tra('valid'));
} // in simple debug mode, valid links are ignored
} // while (each entry in tiki_links is handled)
unset($result); // free memory
if ($debug == 2) {
return(tra('End of debug output.'));
}
$out = [];
$linkin = (! $debug) ? '((' : '~np~'; // this is how toPages are handled
$linkout = (! $debug) ? '))' : '~/np~';
if (is_array($tmp)) {
foreach ($tmp as $row) { // row[toPage, fromPage, reason]
if ($debug) { // modified rejected toPages with reason
$row[0] = '' . tra($row[2]) . ': ' . $row[0];
}
$row[0] = $linkin . $row[0] . $linkout; // toPages
$row[1] = '((' . $row[1] . '))'; // fromPages
// two identical keys may exist, they can either be displayed
// each in its own table row, or be collected in one cell, separated by
// either comma or
if ($collect == 'from') {
if ($break == 'sep') {
// toPages separated in each row, there might be duplicates!!!
$out[] = [$row[0], $row[1]];
} elseif (! array_key_exists($row[0], $out)) {
// multiple fromPages (for one toPage) might be in one row, this is the first
$out[$row[0]] = $row[1];
} else {
// multiple fromPages might be in one row, this is a follow-up
$out[$row[0]] = $out[$row[0]] . $break . $row[1];
}
} else { // $collect == to
if ($break == 'sep') {
// fromPages separated in each row, there might be duplicates!!!
$out[] = [$row[1], $row[0]];
} elseif (! array_key_exists($row[1], $out)) {
// multiple toPages (for one fromPage) might be in one row, this is the first
$out[$row[1]] = $row[0];
} else { // multiple toPages might be in one row, this is a follow-up
$out[$row[1]] = $out[$row[1]] . $break . $row[0];
}
}
} // foreach (received row) is handled
unset($tmp); // free memory
}
// sort the entries
if ($break == 'sep') {
sort($out);
} else {
ksort($out);
}
$headerwant = tra('Wanted Page');
$headerref = tra('Referenced By Page');
$rowbreak = "\n";
$endtable = '||';
if ($prefs['feature_wiki_tables'] != 'new') {
$rowbreak = ' || ';
$endtable = '';
}
$sOutput = '||' . '__';
if ($collect == 'from') {
$sOutput .= $headerwant . '__|__' . $headerref . '__' . $rowbreak;
if ($break == 'sep') {
foreach ($out as $link) {
$sOutput .= $link[0] . ' | ' . $link[1] . $rowbreak;
}
} else {
foreach ($out as $to => $from) {
$sOutput .= $to . ' | ' . $from . $rowbreak;
}
}
} else { // $collect == 'to'
$sOutput .= $headerref . '__|__' . $headerwant . '__' . $rowbreak;
if ($break == 'sep') {
foreach ($out as $link) {
$sOutput .= $link[0] . ' | ' . $link[1] . $rowbreak;
}
} else {
foreach ($out as $from => $to) {
$sOutput .= $from . ' | ' . $to . $rowbreak;
}
}
}
$sOutput .= $endtable;
return $sOutput;
} // run()
} // class WikiPluginWantedPages
function wikiplugin_wantedpages($data, $params)
{
$plugin = new WikiPluginWantedPages();
return $plugin->run($data, $params);
}
// fnmatch() is not defined on windows or PHP < 4.3.0!!
// From php help "fnmatch", http://www.php.net/manual/de/function.fnmatch.php
// comment by "soywiz at gmail dot com 26-Jul-2005 07:07" (as of Jan. 21 2006)
if (! function_exists('fnmatch')) {
function fnmatch($pattern, $string)
{
for ($op = 0, $npattern = '', $n = 0, $l = strlen($pattern); $n < $l; $n++) {
switch ($c = $pattern[$n]) {
case '\\':
$npattern .= '\\' . @$pattern[++$n];
break;
case '.':
case '+':
case '^':
case '$':
case '(':
case ')':
case '{':
case '}':
case '=':
case '!':
case '<':
case '>':
case '|':
$npattern .= '\\' . $c;
break;
case '?':
case '*':
$npattern .= '.' . $c;
break;
case '[':
case ']':
default:
$npattern .= $c;
if ($c == '[') {
$op++;
} elseif ($c == ']') {
if ($op == 0) {
return false;
}
$op--;
}
break;
}
}
if ($op != 0) {
return false;
}
return preg_match('/' . $npattern . '/i', $string);
} // function fnmatch
} // !exists(fnmatch)
// A small function to determine whether a string is a [valid] preg expression.
// From php help "Regular Expression Functions (Perl-Compatible)", http://www.php.net/pcre/
// comment by "alexbodn at 012 dot n@t dot il 09-Jan-2006 11:45" (as of Jan. 21 2006)
if (! function_exists('preg_ispreg')) {
function preg_ispreg($str)
{
$prefix = '';
$sufix = '';
if ($str[0] != '^') {
$prefix = '^';
}
if ($str[strlen($str) - 1] != '$') {
$sufix = '$';
}
$estr = preg_replace("'^/'", "\\/", preg_replace("'([^/])/'", "\\1\\/", $str));
if (@preg_match("/" . $prefix . $estr . $sufix . "/", $str, $matches)) {
return strcmp($str, $matches[0]) != 0;
}
return true;
} // function preg_ispreg
} //!exists(preg_ispreg)
if (! function_exists('debug_print')) {
function debug_print($row, $debug, $message)
{
if ($debug == 2) {
echo $row['toPage'] . ' [from: ' . $row['fromPage'] . ']: ' . $message . '
';
return;
} elseif ($debug) {
$tmp[] = [$row['toPage'], $row['fromPage'], $message];
return $tmp;
}
}
}