はじまりの大地
This commit is contained in:
+23
@@ -0,0 +1,23 @@
|
||||
# Another repository
|
||||
/data
|
||||
|
||||
# やばいの
|
||||
/conf/users.auth.php
|
||||
/conf/local.php
|
||||
|
||||
# Ignore vendor directory
|
||||
vendor/
|
||||
|
||||
# DokuWiki upgrade/install files
|
||||
install.php
|
||||
VERSION
|
||||
CHANGES
|
||||
README
|
||||
COPYING
|
||||
SECURITY.md
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~
|
||||
@@ -0,0 +1,45 @@
|
||||
## You should disable Indexes and MultiViews either here or in the
|
||||
## global config. Symlinks maybe needed for URL rewriting.
|
||||
#Options -Indexes -MultiViews +FollowSymLinks
|
||||
|
||||
## make sure nobody gets the htaccess, README, COPYING or VERSION files
|
||||
<Files ~ "^([\._]ht|README$|VERSION$|COPYING$)">
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</IfModule>
|
||||
</Files>
|
||||
|
||||
## Don't allow access to git directories
|
||||
<IfModule alias_module>
|
||||
RedirectMatch 404 /\.git
|
||||
</IfModule>
|
||||
|
||||
## Uncomment these rules if you want to have nice URLs using
|
||||
## $conf['userewrite'] = 1 - not needed for rewrite mode 2
|
||||
RewriteEngine on
|
||||
|
||||
RewriteRule ^_media/(.*) lib/exe/fetch.php?media=$1 [QSA,L]
|
||||
RewriteRule ^_detail/(.*) lib/exe/detail.php?media=$1 [QSA,L]
|
||||
RewriteRule ^_export/([^/]+)/(.*) doku.php?do=export_$1&id=$2 [QSA,L]
|
||||
RewriteRule ^$ doku.php [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule (.*) doku.php?id=$1 [QSA,L]
|
||||
RewriteRule ^index.php$ doku.php
|
||||
|
||||
## Not all installations will require the following line. If you do,
|
||||
## change "/dokuwiki" to the path to your dokuwiki directory relative
|
||||
## to your document root.
|
||||
#RewriteBase /dokuwiki
|
||||
#
|
||||
## If you enable DokuWikis XML-RPC interface, you should consider to
|
||||
## restrict access to it over HTTPS only! Uncomment the following two
|
||||
## rules if your server setup allows HTTPS.
|
||||
#RewriteCond %{HTTPS} !=on
|
||||
#RewriteRule ^lib/exe/xmlrpc.php$ https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301]
|
||||
|
||||
# vim: set tabstop=2 softtabstop=2 expandtab :
|
||||
@@ -0,0 +1,7 @@
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</IfModule>
|
||||
Executable
+359
@@ -0,0 +1,359 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use splitbrain\phpcli\CLI;
|
||||
use splitbrain\phpcli\Options;
|
||||
use dokuwiki\Utf8\PhpString;
|
||||
|
||||
if (!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../') . '/');
|
||||
define('NOSESSION', 1);
|
||||
require_once(DOKU_INC . 'inc/init.php');
|
||||
|
||||
/**
|
||||
* Checkout and commit pages from the command line while maintaining the history
|
||||
*/
|
||||
class PageCLI extends CLI
|
||||
{
|
||||
protected $force = false;
|
||||
protected $username = '';
|
||||
|
||||
/**
|
||||
* Register options and arguments on the given $options object
|
||||
*
|
||||
* @param Options $options
|
||||
* @return void
|
||||
*/
|
||||
protected function setup(Options $options)
|
||||
{
|
||||
/* global */
|
||||
$options->registerOption(
|
||||
'force',
|
||||
'force obtaining a lock for the page (generally bad idea)',
|
||||
'f'
|
||||
);
|
||||
$options->registerOption(
|
||||
'user',
|
||||
'work as this user. defaults to current CLI user',
|
||||
'u',
|
||||
'username'
|
||||
);
|
||||
$options->setHelp(
|
||||
'Utility to help command line Dokuwiki page editing, allow ' .
|
||||
'pages to be checked out for editing then committed after changes'
|
||||
);
|
||||
|
||||
/* checkout command */
|
||||
$options->registerCommand(
|
||||
'checkout',
|
||||
'Checks out a file from the repository, using the wiki id and obtaining ' .
|
||||
'a lock for the page. ' . "\n" .
|
||||
'If a working_file is specified, this is where the page is copied to. ' .
|
||||
'Otherwise defaults to the same as the wiki page in the current ' .
|
||||
'working directory.'
|
||||
);
|
||||
$options->registerArgument(
|
||||
'wikipage',
|
||||
'The wiki page to checkout',
|
||||
true,
|
||||
'checkout'
|
||||
);
|
||||
$options->registerArgument(
|
||||
'workingfile',
|
||||
'How to name the local checkout',
|
||||
false,
|
||||
'checkout'
|
||||
);
|
||||
|
||||
/* commit command */
|
||||
$options->registerCommand(
|
||||
'commit',
|
||||
'Checks in the working_file into the repository using the specified ' .
|
||||
'wiki id, archiving the previous version.'
|
||||
);
|
||||
$options->registerArgument(
|
||||
'workingfile',
|
||||
'The local file to commit',
|
||||
true,
|
||||
'commit'
|
||||
);
|
||||
$options->registerArgument(
|
||||
'wikipage',
|
||||
'The wiki page to create or update',
|
||||
true,
|
||||
'commit'
|
||||
);
|
||||
$options->registerOption(
|
||||
'message',
|
||||
'Summary describing the change (required)',
|
||||
'm',
|
||||
'summary',
|
||||
'commit'
|
||||
);
|
||||
$options->registerOption(
|
||||
'trivial',
|
||||
'minor change',
|
||||
't',
|
||||
false,
|
||||
'commit'
|
||||
);
|
||||
|
||||
/* lock command */
|
||||
$options->registerCommand(
|
||||
'lock',
|
||||
'Obtains or updates a lock for a wiki page'
|
||||
);
|
||||
$options->registerArgument(
|
||||
'wikipage',
|
||||
'The wiki page to lock',
|
||||
true,
|
||||
'lock'
|
||||
);
|
||||
|
||||
/* unlock command */
|
||||
$options->registerCommand(
|
||||
'unlock',
|
||||
'Removes a lock for a wiki page.'
|
||||
);
|
||||
$options->registerArgument(
|
||||
'wikipage',
|
||||
'The wiki page to unlock',
|
||||
true,
|
||||
'unlock'
|
||||
);
|
||||
|
||||
/* gmeta command */
|
||||
$options->registerCommand(
|
||||
'getmeta',
|
||||
'Prints metadata value for a page to stdout.'
|
||||
);
|
||||
$options->registerArgument(
|
||||
'wikipage',
|
||||
'The wiki page to get the metadata for',
|
||||
true,
|
||||
'getmeta'
|
||||
);
|
||||
$options->registerArgument(
|
||||
'key',
|
||||
'The name of the metadata item to be retrieved.' . "\n" .
|
||||
'If empty, an array of all the metadata items is returned.' . "\n" .
|
||||
'For retrieving items that are stored in sub-arrays, separate the ' .
|
||||
'keys of the different levels by spaces, in quotes, eg "date modified".',
|
||||
false,
|
||||
'getmeta'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Your main program
|
||||
*
|
||||
* Arguments and options have been parsed when this is run
|
||||
*
|
||||
* @param Options $options
|
||||
* @return void
|
||||
*/
|
||||
protected function main(Options $options)
|
||||
{
|
||||
$this->force = $options->getOpt('force', false);
|
||||
$this->username = $options->getOpt('user', $this->getUser());
|
||||
|
||||
$command = $options->getCmd();
|
||||
$args = $options->getArgs();
|
||||
switch ($command) {
|
||||
case 'checkout':
|
||||
$wiki_id = array_shift($args);
|
||||
$localfile = array_shift($args);
|
||||
$this->commandCheckout($wiki_id, $localfile);
|
||||
break;
|
||||
case 'commit':
|
||||
$localfile = array_shift($args);
|
||||
$wiki_id = array_shift($args);
|
||||
$this->commandCommit(
|
||||
$localfile,
|
||||
$wiki_id,
|
||||
$options->getOpt('message', ''),
|
||||
$options->getOpt('trivial', false)
|
||||
);
|
||||
break;
|
||||
case 'lock':
|
||||
$wiki_id = array_shift($args);
|
||||
$this->obtainLock($wiki_id);
|
||||
$this->success("$wiki_id locked");
|
||||
break;
|
||||
case 'unlock':
|
||||
$wiki_id = array_shift($args);
|
||||
$this->clearLock($wiki_id);
|
||||
$this->success("$wiki_id unlocked");
|
||||
break;
|
||||
case 'getmeta':
|
||||
$wiki_id = array_shift($args);
|
||||
$key = trim(array_shift($args));
|
||||
$meta = p_get_metadata($wiki_id, $key, METADATA_RENDER_UNLIMITED);
|
||||
echo trim(json_encode($meta, JSON_PRETTY_PRINT));
|
||||
echo "\n";
|
||||
break;
|
||||
default:
|
||||
echo $options->help();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check out a file
|
||||
*
|
||||
* @param string $wiki_id
|
||||
* @param string $localfile
|
||||
*/
|
||||
protected function commandCheckout($wiki_id, $localfile)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$wiki_id = cleanID($wiki_id);
|
||||
$wiki_fn = wikiFN($wiki_id);
|
||||
|
||||
if (!file_exists($wiki_fn)) {
|
||||
$this->fatal("$wiki_id does not yet exist");
|
||||
}
|
||||
|
||||
if (empty($localfile)) {
|
||||
$localfile = getcwd() . '/' . PhpString::basename($wiki_fn);
|
||||
}
|
||||
|
||||
if (!file_exists(dirname($localfile))) {
|
||||
$this->fatal("Directory " . dirname($localfile) . " does not exist");
|
||||
}
|
||||
|
||||
if (stristr(realpath(dirname($localfile)), (string) realpath($conf['datadir'])) !== false) {
|
||||
$this->fatal("Attempt to check out file into data directory - not allowed");
|
||||
}
|
||||
|
||||
$this->obtainLock($wiki_id);
|
||||
|
||||
if (!copy($wiki_fn, $localfile)) {
|
||||
$this->clearLock($wiki_id);
|
||||
$this->fatal("Unable to copy $wiki_fn to $localfile");
|
||||
}
|
||||
|
||||
$this->success("$wiki_id > $localfile");
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a file as a new page revision
|
||||
*
|
||||
* @param string $localfile
|
||||
* @param string $wiki_id
|
||||
* @param string $message
|
||||
* @param bool $minor
|
||||
*/
|
||||
protected function commandCommit($localfile, $wiki_id, $message, $minor)
|
||||
{
|
||||
$wiki_id = cleanID($wiki_id);
|
||||
$message = trim($message);
|
||||
|
||||
if (!file_exists($localfile)) {
|
||||
$this->fatal("$localfile does not exist");
|
||||
}
|
||||
|
||||
if (!is_readable($localfile)) {
|
||||
$this->fatal("Cannot read from $localfile");
|
||||
}
|
||||
|
||||
if (!$message) {
|
||||
$this->fatal("Summary message required");
|
||||
}
|
||||
|
||||
$this->obtainLock($wiki_id);
|
||||
|
||||
saveWikiText($wiki_id, file_get_contents($localfile), $message, $minor);
|
||||
|
||||
$this->clearLock($wiki_id);
|
||||
|
||||
$this->success("$localfile > $wiki_id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock the given page or exit
|
||||
*
|
||||
* @param string $wiki_id
|
||||
*/
|
||||
protected function obtainLock($wiki_id)
|
||||
{
|
||||
if ($this->force) $this->deleteLock($wiki_id);
|
||||
|
||||
$_SERVER['REMOTE_USER'] = $this->username;
|
||||
|
||||
if (checklock($wiki_id)) {
|
||||
$this->error("Page $wiki_id is already locked by another user");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
lock($wiki_id);
|
||||
|
||||
if (checklock($wiki_id)) {
|
||||
$this->error("Unable to obtain lock for $wiki_id ");
|
||||
var_dump(checklock($wiki_id));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the lock on the given page
|
||||
*
|
||||
* @param string $wiki_id
|
||||
*/
|
||||
protected function clearLock($wiki_id)
|
||||
{
|
||||
if ($this->force) $this->deleteLock($wiki_id);
|
||||
|
||||
$_SERVER['REMOTE_USER'] = $this->username;
|
||||
if (checklock($wiki_id)) {
|
||||
$this->error("Page $wiki_id is locked by another user");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
unlock($wiki_id);
|
||||
|
||||
if (file_exists(wikiLockFN($wiki_id))) {
|
||||
$this->error("Unable to clear lock for $wiki_id");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcefully remove a lock on the page given
|
||||
*
|
||||
* @param string $wiki_id
|
||||
*/
|
||||
protected function deleteLock($wiki_id)
|
||||
{
|
||||
$wikiLockFN = wikiLockFN($wiki_id);
|
||||
|
||||
if (file_exists($wikiLockFN)) {
|
||||
if (!unlink($wikiLockFN)) {
|
||||
$this->error("Unable to delete $wikiLockFN");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user's username from the environment
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getUser()
|
||||
{
|
||||
$user = getenv('USER');
|
||||
if (empty($user)) {
|
||||
$user = getenv('USERNAME');
|
||||
} else {
|
||||
return $user;
|
||||
}
|
||||
if (empty($user)) {
|
||||
$user = 'admin';
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
$cli = new PageCLI();
|
||||
$cli->run();
|
||||
Executable
+346
@@ -0,0 +1,346 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
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');
|
||||
|
||||
/**
|
||||
* Easily manage DokuWiki git repositories
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
class GitToolCLI extends CLI
|
||||
{
|
||||
/**
|
||||
* Register options and arguments on the given $options object
|
||||
*
|
||||
* @param Options $options
|
||||
* @return void
|
||||
*/
|
||||
protected function setup(Options $options)
|
||||
{
|
||||
$options->setHelp(
|
||||
"Manage git repositories for DokuWiki and its plugins and templates.\n\n" .
|
||||
"$> ./bin/gittool.php clone gallery template:ach\n" .
|
||||
"$> ./bin/gittool.php repos\n" .
|
||||
"$> ./bin/gittool.php origin -v"
|
||||
);
|
||||
|
||||
$options->registerArgument(
|
||||
'command',
|
||||
'Command to execute. See below',
|
||||
true
|
||||
);
|
||||
|
||||
$options->registerCommand(
|
||||
'clone',
|
||||
'Tries to install a known plugin or template (prefix with template:) via git. Uses the DokuWiki.org ' .
|
||||
'plugin repository to find the proper git repository. Multiple extensions can be given as parameters'
|
||||
);
|
||||
$options->registerArgument(
|
||||
'extension',
|
||||
'name of the extension to install, prefix with \'template:\' for templates',
|
||||
true,
|
||||
'clone'
|
||||
);
|
||||
|
||||
$options->registerCommand(
|
||||
'install',
|
||||
'The same as clone, but when no git source repository can be found, the extension is installed via ' .
|
||||
'download'
|
||||
);
|
||||
$options->registerArgument(
|
||||
'extension',
|
||||
'name of the extension to install, prefix with \'template:\' for templates',
|
||||
true,
|
||||
'install'
|
||||
);
|
||||
|
||||
$options->registerCommand(
|
||||
'repos',
|
||||
'Lists all git repositories found in this DokuWiki installation'
|
||||
);
|
||||
|
||||
$options->registerCommand(
|
||||
'*',
|
||||
'Any unknown commands are assumed to be arguments to git and will be executed in all repositories ' .
|
||||
'found within this DokuWiki installation'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Your main program
|
||||
*
|
||||
* Arguments and options have been parsed when this is run
|
||||
*
|
||||
* @param Options $options
|
||||
* @return void
|
||||
*/
|
||||
protected function main(Options $options)
|
||||
{
|
||||
$command = $options->getCmd();
|
||||
$args = $options->getArgs();
|
||||
if (!$command) $command = array_shift($args);
|
||||
|
||||
switch ($command) {
|
||||
case '':
|
||||
echo $options->help();
|
||||
break;
|
||||
case 'clone':
|
||||
$this->cmdClone($args);
|
||||
break;
|
||||
case 'install':
|
||||
$this->cmdInstall($args);
|
||||
break;
|
||||
case 'repo':
|
||||
case 'repos':
|
||||
$this->cmdRepos();
|
||||
break;
|
||||
default:
|
||||
$this->cmdGit($command, $args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to install the given extensions using git clone
|
||||
*
|
||||
* @param array $extensions
|
||||
*/
|
||||
public function cmdClone($extensions)
|
||||
{
|
||||
$errors = [];
|
||||
$succeeded = [];
|
||||
|
||||
foreach ($extensions as $ext) {
|
||||
$repo = $this->getSourceRepo($ext);
|
||||
|
||||
if (!$repo) {
|
||||
$this->error("could not find a repository for $ext");
|
||||
$errors[] = $ext;
|
||||
} elseif ($this->cloneExtension($ext, $repo)) {
|
||||
$succeeded[] = $ext;
|
||||
} else {
|
||||
$errors[] = $ext;
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
if ($succeeded) $this->success('successfully cloned the following extensions: ' . implode(', ', $succeeded));
|
||||
if ($errors) $this->error('failed to clone the following extensions: ' . implode(', ', $errors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to install the given extensions using git clone with fallback to install
|
||||
*
|
||||
* @param array $extensions
|
||||
*/
|
||||
public function cmdInstall($extensions)
|
||||
{
|
||||
$errors = [];
|
||||
$succeeded = [];
|
||||
|
||||
foreach ($extensions as $ext) {
|
||||
$repo = $this->getSourceRepo($ext);
|
||||
|
||||
if (!$repo) {
|
||||
$this->info("could not find a repository for $ext");
|
||||
if ($this->downloadExtension($ext)) {
|
||||
$succeeded[] = $ext;
|
||||
} else {
|
||||
$errors[] = $ext;
|
||||
}
|
||||
} elseif ($this->cloneExtension($ext, $repo)) {
|
||||
$succeeded[] = $ext;
|
||||
} else {
|
||||
$errors[] = $ext;
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
if ($succeeded) $this->success('successfully installed the following extensions: ' . implode(', ', $succeeded));
|
||||
if ($errors) $this->error('failed to install the following extensions: ' . implode(', ', $errors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given git command in every repository
|
||||
*
|
||||
* @param $cmd
|
||||
* @param $arg
|
||||
*/
|
||||
public function cmdGit($cmd, $arg)
|
||||
{
|
||||
$repos = $this->findRepos();
|
||||
|
||||
$shell = array_merge(['git', $cmd], $arg);
|
||||
$shell = array_map('escapeshellarg', $shell);
|
||||
$shell = implode(' ', $shell);
|
||||
|
||||
foreach ($repos as $repo) {
|
||||
if (!@chdir($repo)) {
|
||||
$this->error("Could not change into $repo");
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->info("executing $shell in $repo");
|
||||
$ret = 0;
|
||||
system($shell, $ret);
|
||||
|
||||
if ($ret == 0) {
|
||||
$this->success("git succeeded in $repo");
|
||||
} else {
|
||||
$this->error("git failed in $repo");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply lists the repositories
|
||||
*/
|
||||
public function cmdRepos()
|
||||
{
|
||||
$repos = $this->findRepos();
|
||||
foreach ($repos as $repo) {
|
||||
echo "$repo\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install extension from the given download URL
|
||||
*
|
||||
* @param string $ext
|
||||
* @return bool|null
|
||||
*/
|
||||
private function downloadExtension($ext)
|
||||
{
|
||||
/** @var helper_plugin_extension_extension $plugin */
|
||||
$plugin = plugin_load('helper', 'extension_extension');
|
||||
if (!$ext) die("extension plugin not available, can't continue");
|
||||
|
||||
$plugin->setExtension($ext);
|
||||
|
||||
$url = $plugin->getDownloadURL();
|
||||
if (!$url) {
|
||||
$this->error("no download URL for $ext");
|
||||
return false;
|
||||
}
|
||||
|
||||
$ok = false;
|
||||
try {
|
||||
$this->info("installing $ext via download from $url");
|
||||
$ok = $plugin->installFromURL($url);
|
||||
} catch (Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
$this->success("installed $ext via download");
|
||||
return true;
|
||||
} else {
|
||||
$this->success("failed to install $ext via download");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the extension from the given repository
|
||||
*
|
||||
* @param string $ext
|
||||
* @param string $repo
|
||||
* @return bool
|
||||
*/
|
||||
private function cloneExtension($ext, $repo)
|
||||
{
|
||||
if (str_starts_with($ext, 'template:')) {
|
||||
$target = fullpath(tpl_incdir() . '../' . substr($ext, 9));
|
||||
} else {
|
||||
$target = DOKU_PLUGIN . $ext;
|
||||
}
|
||||
|
||||
$this->info("cloning $ext from $repo to $target");
|
||||
$ret = 0;
|
||||
system("git clone $repo $target", $ret);
|
||||
if ($ret === 0) {
|
||||
$this->success("cloning of $ext succeeded");
|
||||
return true;
|
||||
} else {
|
||||
$this->error("cloning of $ext failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all git repositories in this DokuWiki install
|
||||
*
|
||||
* Looks in root, template and plugin directories only.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function findRepos()
|
||||
{
|
||||
$this->info('Looking for .git directories');
|
||||
$data = array_merge(
|
||||
glob(DOKU_INC . '.git', GLOB_ONLYDIR),
|
||||
glob(DOKU_PLUGIN . '*/.git', GLOB_ONLYDIR),
|
||||
glob(fullpath(tpl_incdir() . '../') . '/*/.git', GLOB_ONLYDIR)
|
||||
);
|
||||
|
||||
if (!$data) {
|
||||
$this->error('Found no .git directories');
|
||||
} else {
|
||||
$this->success('Found ' . count($data) . ' .git directories');
|
||||
}
|
||||
$data = array_map('fullpath', array_map('dirname', $data));
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repository for the given extension
|
||||
*
|
||||
* @param $extension
|
||||
* @return false|string
|
||||
*/
|
||||
private function getSourceRepo($extension)
|
||||
{
|
||||
/** @var helper_plugin_extension_extension $ext */
|
||||
$ext = plugin_load('helper', 'extension_extension');
|
||||
if (!$ext) die("extension plugin not available, can't continue");
|
||||
|
||||
$ext->setExtension($extension);
|
||||
|
||||
$repourl = $ext->getSourcerepoURL();
|
||||
if (!$repourl) return false;
|
||||
|
||||
// match github repos
|
||||
if (preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
|
||||
$user = $m[1];
|
||||
$repo = $m[2];
|
||||
return 'https://github.com/' . $user . '/' . $repo . '.git';
|
||||
}
|
||||
|
||||
// match gitorious repos
|
||||
if (preg_match('/gitorious.org\/([^\/]+)\/([^\/]+)?/i', $repourl, $m)) {
|
||||
$user = $m[1];
|
||||
$repo = $m[2];
|
||||
if (!$repo) $repo = $user;
|
||||
|
||||
return 'https://git.gitorious.org/' . $user . '/' . $repo . '.git';
|
||||
}
|
||||
|
||||
// match bitbucket repos - most people seem to use mercurial there though
|
||||
if (preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
|
||||
$user = $m[1];
|
||||
$repo = $m[2];
|
||||
return 'https://bitbucket.org/' . $user . '/' . $repo . '.git';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
$cli = new GitToolCLI();
|
||||
$cli->run();
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
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');
|
||||
|
||||
/**
|
||||
* Update the Search Index from command line
|
||||
*/
|
||||
class IndexerCLI extends CLI
|
||||
{
|
||||
private $quiet = false;
|
||||
private $clear = false;
|
||||
|
||||
/**
|
||||
* Register options and arguments on the given $options object
|
||||
*
|
||||
* @param Options $options
|
||||
* @return void
|
||||
*/
|
||||
protected function setup(Options $options)
|
||||
{
|
||||
$options->setHelp(
|
||||
'Updates the searchindex by indexing all new or changed pages. When the -c option is ' .
|
||||
'given the index is cleared first.'
|
||||
);
|
||||
|
||||
$options->registerOption(
|
||||
'clear',
|
||||
'clear the index before updating',
|
||||
'c'
|
||||
);
|
||||
$options->registerOption(
|
||||
'quiet',
|
||||
'don\'t produce any output',
|
||||
'q'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Your main program
|
||||
*
|
||||
* Arguments and options have been parsed when this is run
|
||||
*
|
||||
* @param Options $options
|
||||
* @return void
|
||||
*/
|
||||
protected function main(Options $options)
|
||||
{
|
||||
$this->clear = $options->getOpt('clear');
|
||||
$this->quiet = $options->getOpt('quiet');
|
||||
|
||||
if ($this->clear) $this->clearindex();
|
||||
|
||||
$this->update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the index
|
||||
*/
|
||||
protected function update()
|
||||
{
|
||||
global $conf;
|
||||
$data = [];
|
||||
$this->quietecho("Searching pages... ");
|
||||
search($data, $conf['datadir'], 'search_allpages', ['skipacl' => true]);
|
||||
$this->quietecho(count($data) . " pages found.\n");
|
||||
|
||||
foreach ($data as $val) {
|
||||
$this->index($val['id']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Index the given page
|
||||
*
|
||||
* @param string $id
|
||||
*/
|
||||
protected function index($id)
|
||||
{
|
||||
$this->quietecho("$id... ");
|
||||
idx_addPage($id, !$this->quiet, $this->clear);
|
||||
$this->quietecho("done.\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all index files
|
||||
*/
|
||||
protected function clearindex()
|
||||
{
|
||||
$this->quietecho("Clearing index... ");
|
||||
idx_get_indexer()->clear();
|
||||
$this->quietecho("done.\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Print message if not supressed
|
||||
*
|
||||
* @param string $msg
|
||||
*/
|
||||
protected function quietecho($msg)
|
||||
{
|
||||
if (!$this->quiet) echo $msg;
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
$cli = new IndexerCLI();
|
||||
$cli->run();
|
||||
Executable
+110
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use dokuwiki\Extension\PluginController;
|
||||
use splitbrain\phpcli\CLI;
|
||||
use splitbrain\phpcli\Colors;
|
||||
use splitbrain\phpcli\Options;
|
||||
use dokuwiki\Extension\CLIPlugin;
|
||||
use splitbrain\phpcli\TableFormatter;
|
||||
|
||||
if (!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../') . '/');
|
||||
define('NOSESSION', 1);
|
||||
require_once(DOKU_INC . 'inc/init.php');
|
||||
|
||||
class PluginCLI extends CLI
|
||||
{
|
||||
/**
|
||||
* Register options and arguments on the given $options object
|
||||
*
|
||||
* @param Options $options
|
||||
* @return void
|
||||
*/
|
||||
protected function setup(Options $options)
|
||||
{
|
||||
$options->setHelp('Excecutes Plugin command line tools');
|
||||
$options->registerArgument('plugin', 'The plugin CLI you want to run. Leave off to see list', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Your main program
|
||||
*
|
||||
* Arguments and options have been parsed when this is run
|
||||
*
|
||||
* @param Options $options
|
||||
* @return void
|
||||
*/
|
||||
protected function main(Options $options)
|
||||
{
|
||||
global $argv;
|
||||
$argv = $options->getArgs();
|
||||
|
||||
if ($argv) {
|
||||
$plugin = $this->loadPlugin($argv[0]);
|
||||
if ($plugin instanceof CLIPlugin) {
|
||||
$plugin->run();
|
||||
} else {
|
||||
$this->fatal('Command {cmd} not found.', ['cmd' => $argv[0]]);
|
||||
}
|
||||
} else {
|
||||
echo $options->help();
|
||||
$this->listPlugins();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List available plugins
|
||||
*/
|
||||
protected function listPlugins()
|
||||
{
|
||||
/** @var PluginController $plugin_controller */
|
||||
global $plugin_controller;
|
||||
|
||||
echo "\n";
|
||||
echo "\n";
|
||||
echo $this->colors->wrap('AVAILABLE PLUGINS:', Colors::C_BROWN);
|
||||
echo "\n";
|
||||
|
||||
$list = $plugin_controller->getList('cli');
|
||||
sort($list);
|
||||
if ($list === []) {
|
||||
echo $this->colors->wrap(" No plugins providing CLI components available\n", Colors::C_RED);
|
||||
} else {
|
||||
$tf = new TableFormatter($this->colors);
|
||||
|
||||
foreach ($list as $name) {
|
||||
$plugin = $this->loadPlugin($name);
|
||||
if (!$plugin instanceof CLIPlugin) continue;
|
||||
$info = $plugin->getInfo();
|
||||
|
||||
echo $tf->format(
|
||||
[2, '30%', '*'],
|
||||
['', $name, $info['desc']],
|
||||
['', Colors::C_CYAN, '']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a CLI plugin
|
||||
*
|
||||
* @param string $name
|
||||
* @return CLIPlugin|null
|
||||
*/
|
||||
protected function loadPlugin($name)
|
||||
{
|
||||
if (plugin_isdisabled($name)) return null;
|
||||
|
||||
// execute the plugin CLI
|
||||
$class = "cli_plugin_$name";
|
||||
if (class_exists($class)) {
|
||||
return new $class();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
$cli = new PluginCLI();
|
||||
$cli->run();
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
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');
|
||||
|
||||
/**
|
||||
* A simple commandline tool to render some DokuWiki syntax with a given
|
||||
* renderer.
|
||||
*
|
||||
* This may not work for plugins that expect a certain environment to be
|
||||
* set up before rendering, but should work for most or even all standard
|
||||
* DokuWiki markup
|
||||
*
|
||||
* @license GPL2
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
class RenderCLI extends CLI
|
||||
{
|
||||
/**
|
||||
* Register options and arguments on the given $options object
|
||||
*
|
||||
* @param Options $options
|
||||
* @return void
|
||||
*/
|
||||
protected function setup(Options $options)
|
||||
{
|
||||
$options->setHelp(
|
||||
'A simple commandline tool to render some DokuWiki syntax with a given renderer.' .
|
||||
"\n\n" .
|
||||
'This may not work for plugins that expect a certain environment to be ' .
|
||||
'set up before rendering, but should work for most or even all standard ' .
|
||||
'DokuWiki markup'
|
||||
);
|
||||
$options->registerOption('renderer', 'The renderer mode to use. Defaults to xhtml', 'r', 'mode');
|
||||
}
|
||||
|
||||
/**
|
||||
* Your main program
|
||||
*
|
||||
* Arguments and options have been parsed when this is run
|
||||
*
|
||||
* @param Options $options
|
||||
* @throws DokuCLI_Exception
|
||||
* @return void
|
||||
*/
|
||||
protected function main(Options $options)
|
||||
{
|
||||
$renderer = $options->getOpt('renderer', 'xhtml');
|
||||
|
||||
// do the action
|
||||
$source = stream_get_contents(STDIN);
|
||||
$info = [];
|
||||
$result = p_render($renderer, p_get_instructions($source), $info);
|
||||
if (is_null($result)) throw new DokuCLI_Exception("No such renderer $renderer");
|
||||
echo $result;
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
$cli = new RenderCLI();
|
||||
$cli->run();
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
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');
|
||||
|
||||
/**
|
||||
* Remove unwanted languages from a DokuWiki install
|
||||
*/
|
||||
class StripLangsCLI extends CLI
|
||||
{
|
||||
/**
|
||||
* Register options and arguments on the given $options object
|
||||
*
|
||||
* @param Options $options
|
||||
* @return void
|
||||
*/
|
||||
protected function setup(Options $options)
|
||||
{
|
||||
|
||||
$options->setHelp(
|
||||
'Remove all languages from the installation, besides the ones specified. English language ' .
|
||||
'is never removed!'
|
||||
);
|
||||
|
||||
$options->registerOption(
|
||||
'keep',
|
||||
'Comma separated list of languages to keep in addition to English.',
|
||||
'k',
|
||||
'langcodes'
|
||||
);
|
||||
$options->registerOption(
|
||||
'english-only',
|
||||
'Remove all languages except English',
|
||||
'e'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Your main program
|
||||
*
|
||||
* Arguments and options have been parsed when this is run
|
||||
*
|
||||
* @param Options $options
|
||||
* @return void
|
||||
*/
|
||||
protected function main(Options $options)
|
||||
{
|
||||
if ($options->getOpt('keep')) {
|
||||
$keep = explode(',', $options->getOpt('keep'));
|
||||
if (!in_array('en', $keep)) $keep[] = 'en';
|
||||
} elseif ($options->getOpt('english-only')) {
|
||||
$keep = ['en'];
|
||||
} else {
|
||||
echo $options->help();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Kill all language directories in /inc/lang and /lib/plugins besides those in $langs array
|
||||
$this->stripDirLangs(realpath(__DIR__ . '/../inc/lang'), $keep);
|
||||
$this->processExtensions(realpath(__DIR__ . '/../lib/plugins'), $keep);
|
||||
$this->processExtensions(realpath(__DIR__ . '/../lib/tpl'), $keep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip languages from extensions
|
||||
*
|
||||
* @param string $path path to plugin or template dir
|
||||
* @param array $keep_langs languages to keep
|
||||
*/
|
||||
protected function processExtensions($path, $keep_langs)
|
||||
{
|
||||
if (is_dir($path)) {
|
||||
$entries = scandir($path);
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if ($entry != "." && $entry != "..") {
|
||||
if (is_dir($path . '/' . $entry)) {
|
||||
$plugin_langs = $path . '/' . $entry . '/lang';
|
||||
|
||||
if (is_dir($plugin_langs)) {
|
||||
$this->stripDirLangs($plugin_langs, $keep_langs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip languages from path
|
||||
*
|
||||
* @param string $path path to lang dir
|
||||
* @param array $keep_langs languages to keep
|
||||
*/
|
||||
protected function stripDirLangs($path, $keep_langs)
|
||||
{
|
||||
$dir = dir($path);
|
||||
|
||||
while (($cur_dir = $dir->read()) !== false) {
|
||||
if ($cur_dir != '.' && $cur_dir != '..' && is_dir($path . '/' . $cur_dir)) {
|
||||
if (!in_array($cur_dir, $keep_langs, true)) {
|
||||
io_rmdir($path . '/' . $cur_dir, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
$dir->close();
|
||||
}
|
||||
}
|
||||
|
||||
$cli = new StripLangsCLI();
|
||||
$cli->run();
|
||||
Executable
+188
@@ -0,0 +1,188 @@
|
||||
#!/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();
|
||||
@@ -0,0 +1,8 @@
|
||||
## no access to the conf directory
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</IfModule>
|
||||
@@ -0,0 +1,9 @@
|
||||
# acl.auth.php
|
||||
# <?php exit()?>
|
||||
# Don't modify the lines above
|
||||
#
|
||||
# Access Control Lists
|
||||
#
|
||||
# Auto-generated by install script
|
||||
# Date: Sun, 07 Jul 2024 09:48:21 +0000
|
||||
* @ALL 8
|
||||
@@ -0,0 +1,21 @@
|
||||
# acl.auth.php
|
||||
# <?php exit()?>
|
||||
# Don't modify the lines above
|
||||
#
|
||||
# Access Control Lists
|
||||
#
|
||||
# Editing this file by hand shouldn't be necessary. Use the ACL
|
||||
# Manager interface instead.
|
||||
#
|
||||
# If your auth backend allows special char like spaces in groups
|
||||
# or user names you need to urlencode them (only chars <128, leave
|
||||
# UTF-8 multibyte chars as is)
|
||||
#
|
||||
# none 0
|
||||
# read 1
|
||||
# edit 2
|
||||
# create 4
|
||||
# upload 8
|
||||
# delete 16
|
||||
|
||||
* @ALL 8
|
||||
@@ -0,0 +1,62 @@
|
||||
# Acronyms.
|
||||
|
||||
ACL Access Control List
|
||||
AFAICS As far as I can see
|
||||
AFAIK As far as I know
|
||||
AFAIR As far as I remember
|
||||
API Application Programming Interface
|
||||
ASAP As soon as possible
|
||||
ASCII American Standard Code for Information Interchange
|
||||
BTW By the way
|
||||
CMS Content Management System
|
||||
CSS Cascading Style Sheets
|
||||
DNS Domain Name System
|
||||
EOF End of file
|
||||
EOL End of line
|
||||
EOM End of message
|
||||
EOT End of text
|
||||
FAQ Frequently Asked Questions
|
||||
FTP File Transfer Protocol
|
||||
FOSS Free & Open-Source Software
|
||||
FLOSS Free/Libre and Open Source Software
|
||||
FUD Fear, Uncertainty, and Doubt
|
||||
FYI For your information
|
||||
GB Gigabyte
|
||||
GHz Gigahertz
|
||||
GPL GNU General Public License
|
||||
GUI Graphical User Interface
|
||||
HTML HyperText Markup Language
|
||||
IANAL I am not a lawyer (but)
|
||||
IE Internet Explorer
|
||||
IIRC If I remember correctly
|
||||
IMHO In my humble opinion
|
||||
IMO In my opinion
|
||||
IOW In other words
|
||||
IRC Internet Relay Chat
|
||||
IRL In real life
|
||||
KISS Keep it simple stupid
|
||||
LAN Local Area Network
|
||||
LGPL GNU Lesser General Public License
|
||||
LOL Laughing out loud
|
||||
MathML Mathematical Markup Language
|
||||
MB Megabyte
|
||||
MHz Megahertz
|
||||
MSIE Microsoft Internet Explorer
|
||||
OMG Oh my God
|
||||
OS Operating System
|
||||
OSS Open Source Software
|
||||
OTOH On the other hand
|
||||
PITA Pain in the Ass
|
||||
RFC Request for Comments
|
||||
ROTFL Rolling on the floor laughing
|
||||
RTFM Read The Fine Manual
|
||||
spec specification
|
||||
TIA Thanks in advance
|
||||
TL;DR Too long; didn't read
|
||||
TOC Table of Contents
|
||||
URI Uniform Resource Identifier
|
||||
URL Uniform Resource Locator
|
||||
W3C World Wide Web Consortium
|
||||
WTF? What the f***
|
||||
WYSIWYG What You See Is What You Get
|
||||
YMMV Your mileage may vary
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
/**
|
||||
* This is DokuWiki's Main Configuration file
|
||||
*
|
||||
* All the default values are kept here, you should not modify it but use
|
||||
* a local.php file instead to override the settings from here.
|
||||
*
|
||||
* This is a piece of PHP code so PHP syntax applies!
|
||||
*
|
||||
* For help with the configuration and a more detailed explanation of the various options
|
||||
* see https://www.dokuwiki.org/config
|
||||
*/
|
||||
|
||||
|
||||
/* Basic Settings */
|
||||
$conf['title'] = 'DokuWiki'; //what to show in the title
|
||||
$conf['start'] = 'start'; //name of start page
|
||||
$conf['lang'] = 'en'; //your language
|
||||
$conf['template'] = 'dokuwiki'; //see lib/tpl directory
|
||||
$conf['tagline'] = ''; //tagline in header (if template supports it)
|
||||
$conf['sidebar'] = 'sidebar'; //name of sidebar in root namespace (if template supports it)
|
||||
$conf['license'] = 'cc-by-nc-sa'; //see conf/license.php
|
||||
$conf['savedir'] = './data'; //where to store all the files
|
||||
$conf['basedir'] = ''; //absolute dir from serveroot - blank for autodetection
|
||||
$conf['baseurl'] = ''; //URL to server including protocol - blank for autodetect
|
||||
$conf['cookiedir'] = ''; //path to use in cookies - blank for basedir
|
||||
$conf['dmode'] = 0755; //set directory creation mode
|
||||
$conf['fmode'] = 0644; //set file creation mode
|
||||
$conf['allowdebug'] = 0; //allow debug output, enable if needed 0|1
|
||||
|
||||
/* Display Settings */
|
||||
$conf['recent'] = 20; //how many entries to show in recent
|
||||
$conf['recent_days'] = 7; //How many days of recent changes to keep. (days)
|
||||
$conf['breadcrumbs'] = 10; //how many recent visited pages to show
|
||||
$conf['youarehere'] = 0; //show "You are here" navigation? 0|1
|
||||
$conf['fullpath'] = 0; //show full path of the document or relative to datadir only? 0|1
|
||||
$conf['typography'] = 1; //smartquote conversion 0=off, 1=doublequotes, 2=all quotes
|
||||
$conf['dformat'] = '%Y/%m/%d %H:%M'; //dateformat accepted by PHPs strftime() function
|
||||
$conf['signature'] = ' --- //[[@MAIL@|@NAME@]] @DATE@//'; //signature see wiki page for details
|
||||
$conf['showuseras'] = 'loginname'; // 'loginname' users login name
|
||||
// 'username' users full name
|
||||
// 'email' e-mail address (will be obfuscated as per mailguard)
|
||||
// 'email_link' e-mail address as a mailto: link (obfuscated)
|
||||
$conf['toptoclevel'] = 1; //Level starting with and below to include in AutoTOC (max. 5)
|
||||
$conf['tocminheads'] = 3; //Minimum amount of headlines that determines if a TOC is built
|
||||
$conf['maxtoclevel'] = 3; //Up to which level include into AutoTOC (max. 5)
|
||||
$conf['maxseclevel'] = 3; //Up to which level create editable sections (max. 5)
|
||||
$conf['camelcase'] = 0; //Use CamelCase for linking? (I don't like it) 0|1
|
||||
$conf['deaccent'] = 1; //deaccented chars in pagenames (1) or romanize (2) or keep (0)?
|
||||
$conf['useheading'] = 0; //use the first heading in a page as its name
|
||||
$conf['sneaky_index']= 0; //check for namespace read permission in index view (0|1) (1 might cause unexpected behavior)
|
||||
$conf['hidepages'] = ''; //Regexp for pages to be skipped from RSS, Search and Recent Changes
|
||||
|
||||
/* Authentication Settings */
|
||||
$conf['useacl'] = 0; //Use Access Control Lists to restrict access?
|
||||
$conf['autopasswd'] = 1; //autogenerate passwords and email them to user
|
||||
$conf['authtype'] = 'authplain'; //which authentication backend should be used
|
||||
$conf['passcrypt'] = 'bcrypt'; //Used crypt method (smd5,md5,sha1,ssha,crypt,mysql,my411,bcrypt)
|
||||
$conf['defaultgroup']= 'user'; //Default groups new Users are added to
|
||||
$conf['superuser'] = '!!not set!!'; //The admin can be user or @group or comma separated list user1,@group1,user2
|
||||
$conf['manager'] = '!!not set!!'; //The manager can be user or @group or comma separated list user1,@group1,user2
|
||||
$conf['profileconfirm'] = 1; //Require current password to confirm changes to user profile
|
||||
$conf['rememberme'] = 1; //Enable/disable remember me on login
|
||||
$conf['disableactions'] = ''; //comma separated list of actions to disable
|
||||
$conf['auth_security_timeout'] = 900; //time (seconds) auth data is considered valid, set to 0 to recheck on every page view
|
||||
$conf['securecookie'] = 1; //never send HTTPS cookies via HTTP
|
||||
$conf['samesitecookie'] = 'Lax'; //SameSite attribute for cookies (Lax|Strict|None|Empty)
|
||||
$conf['remote'] = 0; //Enable/disable remote interfaces
|
||||
$conf['remoteuser'] = '!!not set!!'; //user/groups that have access to remote interface (comma separated). leave empty to allow all users
|
||||
$conf['remotecors'] = ''; //enable Cross-Origin Resource Sharing (CORS) for the remote interfaces. Asterisk (*) to allow all origins. leave empty to deny.
|
||||
|
||||
/* Antispam Features */
|
||||
$conf['usewordblock']= 1; //block spam based on words? 0|1
|
||||
$conf['relnofollow'] = 1; //use rel="ugc nofollow" for external links?
|
||||
$conf['indexdelay'] = 60*60*24*5; //allow indexing after this time (seconds) default is 5 days
|
||||
$conf['mailguard'] = 'hex'; //obfuscate email addresses against spam harvesters?
|
||||
//valid entries are:
|
||||
// 'visible' - replace @ with [at], . with [dot] and - with [dash]
|
||||
// 'hex' - use hex entities to encode the mail address
|
||||
// 'none' - do not obfuscate addresses
|
||||
$conf['iexssprotect']= 1; // check for JavaScript and HTML in uploaded files 0|1
|
||||
|
||||
/* Editing Settings */
|
||||
$conf['usedraft'] = 1; //automatically save a draft while editing (0|1)
|
||||
$conf['locktime'] = 15*60; //maximum age for lockfiles (defaults to 15 minutes)
|
||||
$conf['cachetime'] = 60*60*24; //maximum age for cachefile in seconds (defaults to a day)
|
||||
|
||||
/* Link Settings */
|
||||
// Set target to use when creating links - leave empty for same window
|
||||
$conf['target']['wiki'] = '';
|
||||
$conf['target']['interwiki'] = '';
|
||||
$conf['target']['extern'] = '';
|
||||
$conf['target']['media'] = '';
|
||||
$conf['target']['windows'] = '';
|
||||
|
||||
/* Media Settings */
|
||||
$conf['mediarevisions'] = 1; //enable/disable media revisions
|
||||
$conf['refcheck'] = 1; //check for references before deleting media files
|
||||
$conf['gdlib'] = 2; //the GDlib version (0, 1 or 2) 2 tries to autodetect
|
||||
$conf['im_convert'] = ''; //path to ImageMagicks convert (will be used instead of GD)
|
||||
$conf['jpg_quality'] = '70'; //quality of compression when scaling jpg images (0-100)
|
||||
$conf['fetchsize'] = 0; //maximum size (bytes) fetch.php may download from extern, disabled by default
|
||||
|
||||
/* Notification Settings */
|
||||
$conf['subscribers'] = 0; //enable change notice subscription support
|
||||
$conf['subscribe_time'] = 24*60*60; //Time after which digests / lists are sent (in sec, default 1 day)
|
||||
//Should be smaller than the time specified in recent_days
|
||||
$conf['notify'] = ''; //send change info to this email (leave blank for nobody)
|
||||
$conf['registernotify'] = ''; //send info about newly registered users to this email (leave blank for nobody)
|
||||
$conf['mailfrom'] = ''; //use this email when sending mails
|
||||
$conf['mailreturnpath'] = ''; //use this email as returnpath for bounce mails
|
||||
$conf['mailprefix'] = ''; //use this as prefix of outgoing mails
|
||||
$conf['htmlmail'] = 1; //send HTML multipart mails
|
||||
$conf['dontlog'] = 'debug'; //logging facilities that should be disabled
|
||||
$conf['logretain'] = 3; //how many days of logs to keep
|
||||
|
||||
/* Syndication Settings */
|
||||
$conf['sitemap'] = 0; //Create a Google sitemap? How often? In days.
|
||||
$conf['rss_type'] = 'rss1'; //type of RSS feed to provide, by default:
|
||||
// 'rss' - RSS 0.91
|
||||
// 'rss1' - RSS 1.0
|
||||
// 'rss2' - RSS 2.0
|
||||
// 'atom' - Atom 0.3
|
||||
// 'atom1' - Atom 1.0
|
||||
$conf['rss_linkto'] = 'diff'; //what page RSS entries link to:
|
||||
// 'diff' - page showing revision differences
|
||||
// 'page' - the revised page itself
|
||||
// 'rev' - page showing all revisions
|
||||
// 'current' - most recent revision of page
|
||||
$conf['rss_content'] = 'abstract'; //what to put in the items by default?
|
||||
// 'abstract' - plain text, first paragraph or so
|
||||
// 'diff' - plain text unified diff wrapped in <pre> tags
|
||||
// 'htmldiff' - diff as HTML table
|
||||
// 'html' - the full page rendered in XHTML
|
||||
$conf['rss_media'] = 'both'; //what should be listed?
|
||||
// 'both' - page and media changes
|
||||
// 'pages' - page changes only
|
||||
// 'media' - media changes only
|
||||
$conf['rss_update'] = 5*60; //Update the RSS feed every n seconds (defaults to 5 minutes)
|
||||
$conf['rss_show_summary'] = 1; //Add revision summary to title? 0|1
|
||||
$conf['rss_show_deleted'] = 1; //Show deleted items 0|1
|
||||
|
||||
/* Advanced Settings */
|
||||
$conf['updatecheck'] = 1; //automatically check for new releases?
|
||||
$conf['userewrite'] = 0; //this makes nice URLs: 0: off 1: .htaccess 2: internal
|
||||
$conf['useslash'] = 0; //use slash instead of colon? only when rewrite is on
|
||||
$conf['sepchar'] = '_'; //word separator character in page names; may be a
|
||||
// letter, a digit, '_', '-', or '.'.
|
||||
$conf['canonical'] = 0; //Should all URLs use full canonical http://... style?
|
||||
$conf['fnencode'] = 'url'; //encode filenames (url|safe|utf-8)
|
||||
$conf['autoplural'] = 0; //try (non)plural form of nonexistent files?
|
||||
$conf['compression'] = 'gz'; //compress old revisions: (0: off) ('gz': gnuzip) ('bz2': bzip)
|
||||
// bz2 generates smaller files, but needs more cpu-power
|
||||
$conf['gzip_output'] = 0; //use gzip content encoding for the output xhtml (if allowed by browser)
|
||||
$conf['compress'] = 1; //Strip whitespaces and comments from Styles and JavaScript? 1|0
|
||||
$conf['cssdatauri'] = 512; //Maximum byte size of small images to embed into CSS, won't work on IE<8
|
||||
$conf['send404'] = 0; //Send an HTTP 404 status for nonexistent pages?
|
||||
$conf['broken_iua'] = 0; //Platform with broken ignore_user_abort (IIS+CGI) 0|1
|
||||
$conf['xsendfile'] = 0; //Use X-Sendfile (1 = lighttpd, 2 = standard)
|
||||
$conf['renderer_xhtml'] = 'xhtml'; //renderer to use for main page generation
|
||||
$conf['readdircache'] = 0; //time cache in second for the readdir operation, 0 to deactivate.
|
||||
$conf['search_nslimit'] = 0; //limit the search to the current X namespaces
|
||||
$conf['search_fragment'] = 'exact'; //specify the default fragment search behavior
|
||||
$conf['trustedproxy'] = '^(::1|[fF][eE]80:|127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)';
|
||||
//Regexp of trusted proxy address when reading IP using HTTP header
|
||||
// if blank, do not trust any proxy (including local IP)
|
||||
|
||||
/* Feature Flags */
|
||||
$conf['defer_js'] = 1; // Defer javascript to be executed after the page's HTML has been parsed. Setting will be removed in the next release.
|
||||
$conf['hidewarnings'] = 0; // Hide warnings
|
||||
|
||||
/* Network Settings */
|
||||
$conf['dnslookups'] = 1; //disable to disallow IP to hostname lookups
|
||||
$conf['jquerycdn'] = 0; //use a CDN for delivering jQuery?
|
||||
// Proxy setup - if your Server needs a proxy to access the web set these
|
||||
$conf['proxy']['host'] = '';
|
||||
$conf['proxy']['port'] = '';
|
||||
$conf['proxy']['user'] = '';
|
||||
$conf['proxy']['pass'] = '';
|
||||
$conf['proxy']['ssl'] = 0;
|
||||
$conf['proxy']['except'] = '';
|
||||
@@ -0,0 +1,22 @@
|
||||
# Typography replacements
|
||||
#
|
||||
# Order does matter!
|
||||
#
|
||||
# You can use HTML entities here, but it is not recommended because it may break
|
||||
# non-HTML renderers. Use UTF-8 chars directly instead.
|
||||
|
||||
<-> ↔
|
||||
-> →
|
||||
<- ←
|
||||
<=> ⇔
|
||||
=> ⇒
|
||||
<= ⇐
|
||||
>> »
|
||||
<< «
|
||||
--- —
|
||||
-- –
|
||||
(c) ©
|
||||
(tm) ™
|
||||
(r) ®
|
||||
... …
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Each URL may contain one of these placeholders
|
||||
# {URL} is replaced by the URL encoded representation of the wikiname
|
||||
# this is the right thing to do in most cases
|
||||
# {NAME} this is replaced by the wikiname as given in the document
|
||||
# only mandatory encoded is done, urlencoding if the link
|
||||
# is an external URL, or encoding as a wikiname if it is an
|
||||
# internal link (begins with a colon)
|
||||
# {SCHEME}
|
||||
# {HOST}
|
||||
# {PORT}
|
||||
# {PATH}
|
||||
# {QUERY} these placeholders will be replaced with the appropriate part
|
||||
# of the link when parsed as a URL
|
||||
# If no placeholder is defined the urlencoded name is appended to the URL
|
||||
|
||||
# To prevent losing your added InterWiki shortcuts after an upgrade,
|
||||
# you should add new ones to interwiki.local.conf
|
||||
|
||||
wp https://en.wikipedia.org/wiki/{NAME}
|
||||
wpfr https://fr.wikipedia.org/wiki/{NAME}
|
||||
wpde https://de.wikipedia.org/wiki/{NAME}
|
||||
wpes https://es.wikipedia.org/wiki/{NAME}
|
||||
wppl https://pl.wikipedia.org/wiki/{NAME}
|
||||
wpjp https://ja.wikipedia.org/wiki/{NAME}
|
||||
wpru https://ru.wikipedia.org/wiki/{NAME}
|
||||
wpmeta https://meta.wikipedia.org/wiki/{NAME}
|
||||
doku https://www.dokuwiki.org/
|
||||
rfc https://tools.ietf.org/html/rfc
|
||||
man http://man.cx/
|
||||
amazon https://www.amazon.com/dp/{URL}?tag=splitbrain-20
|
||||
amazon.de https://www.amazon.de/dp/{URL}?tag=splitbrain-21
|
||||
amazon.uk https://www.amazon.co.uk/dp/{URL}
|
||||
paypal https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=
|
||||
phpfn https://secure.php.net/{NAME}
|
||||
skype skype:{NAME}
|
||||
google https://www.google.com/search?q=
|
||||
google.de https://www.google.de/search?q=
|
||||
go https://www.google.com/search?q={URL}&btnI=lucky
|
||||
user :user:{NAME}
|
||||
|
||||
# To support VoIP/SIP/TEL links
|
||||
callto callto://{NAME}
|
||||
tel tel:{NAME}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* This file defines multiple available licenses you can license your
|
||||
* wiki contents under. Do not change this file, but create a
|
||||
* license.local.php instead.
|
||||
*/
|
||||
|
||||
if(empty($LC)) $LC = empty($conf['lang']) ? 'en' : $conf['lang'];
|
||||
|
||||
$license['cc-zero'] = array(
|
||||
'name' => 'CC0 1.0 Universal',
|
||||
'url' => 'https://creativecommons.org/publicdomain/zero/1.0/deed.'.$LC,
|
||||
);
|
||||
$license['publicdomain'] = array(
|
||||
'name' => 'Public Domain',
|
||||
'url' => 'https://creativecommons.org/licenses/publicdomain/deed.'.$LC,
|
||||
);
|
||||
$license['cc-by'] = array(
|
||||
'name' => 'CC Attribution 4.0 International',
|
||||
'url' => 'https://creativecommons.org/licenses/by/4.0/deed.'.$LC,
|
||||
);
|
||||
$license['cc-by-sa'] = array(
|
||||
'name' => 'CC Attribution-Share Alike 4.0 International',
|
||||
'url' => 'https://creativecommons.org/licenses/by-sa/4.0/deed.'.$LC,
|
||||
);
|
||||
$license['gnufdl'] = array(
|
||||
'name' => 'GNU Free Documentation License 1.3',
|
||||
'url' => 'https://www.gnu.org/licenses/fdl-1.3.html',
|
||||
);
|
||||
$license['cc-by-nc'] = array(
|
||||
'name' => 'CC Attribution-Noncommercial 4.0 International',
|
||||
'url' => 'https://creativecommons.org/licenses/by-nc/4.0/deed.'.$LC,
|
||||
);
|
||||
$license['cc-by-nc-sa'] = array(
|
||||
'name' => 'CC Attribution-Noncommercial-Share Alike 4.0 International',
|
||||
'url' => 'https://creativecommons.org/licenses/by-nc-sa/4.0/deed.'.$LC,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/*
|
||||
* Dokuwiki's Main Configuration File - Local Settings
|
||||
* Auto-generated by config plugin
|
||||
* Run for user: miteruzo
|
||||
* Date: Mon, 08 Jul 2024 01:50:32 +0900
|
||||
*/
|
||||
|
||||
$conf['title'] = 'Mr.伝説 Wiki';
|
||||
$conf['lang'] = 'ja';
|
||||
$conf['template'] = 'vector';
|
||||
$conf['license'] = 'cc-zero';
|
||||
$conf['useacl'] = 1;
|
||||
$conf['superuser'] = '@admin';
|
||||
$conf['target']['interwiki'] = '_blank';
|
||||
$conf['target']['extern'] = '_blank';
|
||||
$conf['userewrite'] = '1';
|
||||
$conf['plugin']['ckgedit']['scayt_lang'] = 'British English/en_GB';
|
||||
$conf['plugin']['ckgedit']['other_lang'] = 'ja';
|
||||
$conf['tpl']['bootstrap3']['socialShareProviders'] = 'facebook,linkedin,microsoft-teams,pinterest,whatsapp,reddit,twitter,telegram,yammer,google-plus';
|
||||
$conf['tpl']['flat']['topSidebar'] = 'sidebar';
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* This is an example of how a local.php could look like.
|
||||
* Simply copy the options you want to change from dokuwiki.php
|
||||
* to this file and change them.
|
||||
*
|
||||
* When using the installer, a correct local.php file be generated for
|
||||
* you automatically.
|
||||
*/
|
||||
|
||||
|
||||
//$conf['title'] = 'My Wiki'; //what to show in the title
|
||||
|
||||
//$conf['useacl'] = 1; //Use Access Control Lists to restrict access?
|
||||
//$conf['superuser'] = 'joe';
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"display": "standalone"
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* This configures which metadata will be editable through
|
||||
* the media manager. Each field of the array is an array with the
|
||||
* following contents:
|
||||
* fieldname - Where data will be saved (EXIF or IPTC field)
|
||||
* label - key to lookup in the $lang var, if not found printed as is
|
||||
* htmltype - 'text', 'textarea' or 'date'
|
||||
* lookups - array additional fields to look up the data (EXIF or IPTC fields)
|
||||
*
|
||||
* The fields are not ordered continuously to make inserting additional items
|
||||
* in between simpler.
|
||||
*
|
||||
* This is a PHP snippet, so PHP syntax applies.
|
||||
*
|
||||
* Note: $fields is not a global variable and will not be available to any
|
||||
* other functions or templates later
|
||||
*
|
||||
* You may extend or overwrite this variable in an optional
|
||||
* conf/mediameta.local.php file
|
||||
*
|
||||
* For a list of available EXIF/IPTC fields refer to
|
||||
* http://www.dokuwiki.org/devel:templates:detail.php
|
||||
*/
|
||||
|
||||
|
||||
$fields = array(
|
||||
10 => array('Iptc.Headline',
|
||||
'img_title',
|
||||
'text'),
|
||||
|
||||
20 => array('',
|
||||
'img_date',
|
||||
'date',
|
||||
array('Date.EarliestTime')),
|
||||
|
||||
30 => array('',
|
||||
'img_fname',
|
||||
'text',
|
||||
array('File.Name')),
|
||||
|
||||
40 => array('Iptc.Caption',
|
||||
'img_caption',
|
||||
'textarea',
|
||||
array('Exif.UserComment',
|
||||
'Exif.TIFFImageDescription',
|
||||
'Exif.TIFFUserComment')),
|
||||
|
||||
50 => array('Iptc.Byline',
|
||||
'img_artist',
|
||||
'text',
|
||||
array('Exif.TIFFArtist',
|
||||
'Exif.Artist',
|
||||
'Iptc.Credit')),
|
||||
|
||||
60 => array('Iptc.CopyrightNotice',
|
||||
'img_copyr',
|
||||
'text',
|
||||
array('Exif.TIFFCopyright',
|
||||
'Exif.Copyright')),
|
||||
|
||||
70 => array('',
|
||||
'img_format',
|
||||
'text',
|
||||
array('File.Format')),
|
||||
|
||||
80 => array('',
|
||||
'img_fsize',
|
||||
'text',
|
||||
array('File.NiceSize')),
|
||||
|
||||
90 => array('',
|
||||
'img_width',
|
||||
'text',
|
||||
array('File.Width')),
|
||||
|
||||
100 => array('',
|
||||
'img_height',
|
||||
'text',
|
||||
array('File.Height')),
|
||||
|
||||
110 => array('',
|
||||
'img_camera',
|
||||
'text',
|
||||
array('Simple.Camera')),
|
||||
|
||||
120 => array('Iptc.Keywords',
|
||||
'img_keywords',
|
||||
'text',
|
||||
array('Exif.Category')),
|
||||
);
|
||||
@@ -0,0 +1,75 @@
|
||||
# Allowed uploadable file extensions and mimetypes are defined here.
|
||||
# To extend this file it is recommended to create a mime.local.conf
|
||||
# file. Mimetypes that should be downloadable and not be opened in the
|
||||
# should be prefixed with a !
|
||||
|
||||
jpg image/jpeg
|
||||
jpeg image/jpeg
|
||||
gif image/gif
|
||||
png image/png
|
||||
webp image/webp
|
||||
ico image/vnd.microsoft.icon
|
||||
|
||||
mp3 audio/mpeg
|
||||
ogg audio/ogg
|
||||
wav audio/wav
|
||||
webm video/webm
|
||||
ogv video/ogg
|
||||
mp4 video/mp4
|
||||
vtt text/vtt
|
||||
|
||||
tgz !application/octet-stream
|
||||
tar !application/x-gtar
|
||||
gz !application/octet-stream
|
||||
bz2 !application/octet-stream
|
||||
zip !application/zip
|
||||
rar !application/rar
|
||||
7z !application/x-7z-compressed
|
||||
|
||||
pdf application/pdf
|
||||
ps !application/postscript
|
||||
|
||||
rpm !application/octet-stream
|
||||
deb !application/octet-stream
|
||||
|
||||
doc !application/msword
|
||||
xls !application/msexcel
|
||||
ppt !application/mspowerpoint
|
||||
rtf !application/msword
|
||||
|
||||
docx !application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
xlsx !application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
pptx !application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
|
||||
sxw !application/soffice
|
||||
sxc !application/soffice
|
||||
sxi !application/soffice
|
||||
sxd !application/soffice
|
||||
|
||||
odc !application/vnd.oasis.opendocument.chart
|
||||
odf !application/vnd.oasis.opendocument.formula
|
||||
odg !application/vnd.oasis.opendocument.graphics
|
||||
odi !application/vnd.oasis.opendocument.image
|
||||
odp !application/vnd.oasis.opendocument.presentation
|
||||
ods !application/vnd.oasis.opendocument.spreadsheet
|
||||
odt !application/vnd.oasis.opendocument.text
|
||||
|
||||
svg image/svg+xml
|
||||
|
||||
# You should enable HTML and Text uploads only for restricted Wikis.
|
||||
# Spammers are known to upload spam pages through unprotected Wikis.
|
||||
# Note: Enabling HTML opens Cross Site Scripting vulnerabilities
|
||||
# through JavaScript. Only enable this with trusted users. You
|
||||
# need to disable the iexssprotect option additionally to
|
||||
# adding the mime type here
|
||||
#html text/html
|
||||
#htm text/html
|
||||
#txt text/plain
|
||||
#conf text/plain
|
||||
#xml text/xml
|
||||
#csv text/csv
|
||||
|
||||
# Also flash may be able to execute arbitrary scripts in the website's
|
||||
# context
|
||||
#swf application/x-shockwave-flash
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
/*
|
||||
* This is an example configuration for the mysql auth plugin.
|
||||
*
|
||||
* This SQL statements are optimized for following table structure.
|
||||
* If you use a different one you have to change them accordingly.
|
||||
* See comments of every statement for details.
|
||||
*
|
||||
* TABLE users
|
||||
* uid login pass firstname lastname email
|
||||
*
|
||||
* TABLE groups
|
||||
* gid name
|
||||
*
|
||||
* TABLE usergroup
|
||||
* uid gid
|
||||
*
|
||||
* To use this configuration you have to copy them to local.protected.php
|
||||
* or at least include this file in local.protected.php.
|
||||
*/
|
||||
|
||||
/* Options to configure database access. You need to set up this
|
||||
* options carefully, otherwise you won't be able to access you
|
||||
* database.
|
||||
*/
|
||||
$conf['plugin']['authmysql']['server'] = '';
|
||||
$conf['plugin']['authmysql']['user'] = '';
|
||||
$conf['plugin']['authmysql']['password'] = '';
|
||||
$conf['plugin']['authmysql']['database'] = '';
|
||||
|
||||
/* This option enables debug messages in the mysql plugin. It is
|
||||
* mostly useful for system admins.
|
||||
*/
|
||||
$conf['plugin']['authmysql']['debug'] = 0;
|
||||
|
||||
/* Normally password encryption is done by DokuWiki (recommended) but for
|
||||
* some reasons it might be useful to let the database do the encryption.
|
||||
* Set 'forwardClearPass' to '1' and the cleartext password is forwarded to
|
||||
* the database, otherwise the encrypted one.
|
||||
*/
|
||||
$conf['plugin']['authmysql']['forwardClearPass'] = 0;
|
||||
|
||||
/* Multiple table operations will be protected by locks. This array tells
|
||||
* the plugin which tables to lock. If you use any aliases for table names
|
||||
* these array must also contain these aliases. Any unnamed alias will cause
|
||||
* a warning during operation. See the example below.
|
||||
*/
|
||||
$conf['plugin']['authmysql']['TablesToLock']= array("users", "users AS u","groups", "groups AS g", "usergroup", "usergroup AS ug");
|
||||
|
||||
/***********************************************************************/
|
||||
/* Basic SQL statements for user authentication (required) */
|
||||
/***********************************************************************/
|
||||
|
||||
/* This statement is used to grant or deny access to the wiki. The result
|
||||
* should be a table with exact one line containing at least the password
|
||||
* of the user. If the result table is empty or contains more than one
|
||||
* row, access will be denied.
|
||||
*
|
||||
* The plugin accesses the password as 'pass' so an alias might be necessary.
|
||||
*
|
||||
* Following patters will be replaced:
|
||||
* %{user} user name
|
||||
* %{pass} encrypted or clear text password (depends on 'encryptPass')
|
||||
* %{dgroup} default group name
|
||||
*/
|
||||
$conf['plugin']['authmysql']['checkPass'] = "SELECT pass
|
||||
FROM usergroup AS ug
|
||||
JOIN users AS u ON u.uid=ug.uid
|
||||
JOIN groups AS g ON g.gid=ug.gid
|
||||
WHERE login='%{user}'
|
||||
AND name='%{dgroup}'";
|
||||
|
||||
/* This statement should return a table with exact one row containing
|
||||
* information about one user. The field needed are:
|
||||
* 'pass' containing the encrypted or clear text password
|
||||
* 'name' the user's full name
|
||||
* 'mail' the user's email address
|
||||
*
|
||||
* Keep in mind that Dokuwiki will access this information through the
|
||||
* names listed above so aliases might be necessary.
|
||||
*
|
||||
* Following patters will be replaced:
|
||||
* %{user} user name
|
||||
*/
|
||||
$conf['plugin']['authmysql']['getUserInfo'] = "SELECT pass, CONCAT(firstname,' ',lastname) AS name, email AS mail
|
||||
FROM users
|
||||
WHERE login='%{user}'";
|
||||
|
||||
/* This statement is used to get all groups a user is member of. The
|
||||
* result should be a table containing all groups the given user is
|
||||
* member of. The plugin accesses the group name as 'group' so an alias
|
||||
* might be necessary.
|
||||
*
|
||||
* Following patters will be replaced:
|
||||
* %{user} user name
|
||||
*/
|
||||
$conf['plugin']['authmysql']['getGroups'] = "SELECT name as `group`
|
||||
FROM groups g, users u, usergroup ug
|
||||
WHERE u.uid = ug.uid
|
||||
AND g.gid = ug.gid
|
||||
AND u.login='%{user}'";
|
||||
|
||||
/***********************************************************************/
|
||||
/* Additional minimum SQL statements to use the user manager */
|
||||
/***********************************************************************/
|
||||
|
||||
/* This statement should return a table containing all user login names
|
||||
* that meet certain filter criteria. The filter expressions will be added
|
||||
* case dependent by the plugin. At the end a sort expression will be added.
|
||||
* Important is that this list contains no double entries for a user. Each
|
||||
* user name is only allowed once in the table.
|
||||
*
|
||||
* The login name will be accessed as 'user' to an alias might be necessary.
|
||||
* No patterns will be replaced in this statement but following patters
|
||||
* will be replaced in the filter expressions:
|
||||
* %{user} in FilterLogin user's login name
|
||||
* %{name} in FilterName user's full name
|
||||
* %{email} in FilterEmail user's email address
|
||||
* %{group} in FilterGroup group name
|
||||
*/
|
||||
$conf['plugin']['authmysql']['getUsers'] = "SELECT DISTINCT login AS user
|
||||
FROM users AS u
|
||||
LEFT JOIN usergroup AS ug ON u.uid=ug.uid
|
||||
LEFT JOIN groups AS g ON ug.gid=g.gid";
|
||||
$conf['plugin']['authmysql']['FilterLogin'] = "login LIKE '%{user}'";
|
||||
$conf['plugin']['authmysql']['FilterName'] = "CONCAT(firstname,' ',lastname) LIKE '%{name}'";
|
||||
$conf['plugin']['authmysql']['FilterEmail'] = "email LIKE '%{email}'";
|
||||
$conf['plugin']['authmysql']['FilterGroup'] = "name LIKE '%{group}'";
|
||||
$conf['plugin']['authmysql']['SortOrder'] = "ORDER BY login";
|
||||
|
||||
/***********************************************************************/
|
||||
/* Additional SQL statements to add new users with the user manager */
|
||||
/***********************************************************************/
|
||||
|
||||
/* This statement should add a user to the database. Minimum information
|
||||
* to store are: login name, password, email address and full name.
|
||||
*
|
||||
* Following patterns will be replaced:
|
||||
* %{user} user's login name
|
||||
* %{pass} password (encrypted or clear text, depends on 'encryptPass')
|
||||
* %{email} email address
|
||||
* %{name} user's full name
|
||||
*/
|
||||
$conf['plugin']['authmysql']['addUser'] = "INSERT INTO users
|
||||
(login, pass, email, firstname, lastname)
|
||||
VALUES ('%{user}', '%{pass}', '%{email}',
|
||||
SUBSTRING_INDEX('%{name}',' ', 1),
|
||||
SUBSTRING_INDEX('%{name}',' ', -1))";
|
||||
|
||||
/* This statement should add a group to the database.
|
||||
* Following patterns will be replaced:
|
||||
* %{group} group name
|
||||
*/
|
||||
$conf['plugin']['authmysql']['addGroup'] = "INSERT INTO groups (name)
|
||||
VALUES ('%{group}')";
|
||||
|
||||
/* This statement should connect a user to a group (a user become member
|
||||
* of that group).
|
||||
* Following patterns will be replaced:
|
||||
* %{user} user's login name
|
||||
* %{uid} id of a user dataset
|
||||
* %{group} group name
|
||||
* %{gid} id of a group dataset
|
||||
*/
|
||||
$conf['plugin']['authmysql']['addUserGroup']= "INSERT INTO usergroup (uid, gid)
|
||||
VALUES ('%{uid}', '%{gid}')";
|
||||
|
||||
/* This statement should remove a group fom the database.
|
||||
* Following patterns will be replaced:
|
||||
* %{group} group name
|
||||
* %{gid} id of a group dataset
|
||||
*/
|
||||
$conf['plugin']['authmysql']['delGroup'] = "DELETE FROM groups
|
||||
WHERE gid='%{gid}'";
|
||||
|
||||
/* This statement should return the database index of a given user name.
|
||||
* The plugin will access the index with the name 'id' so an alias might be
|
||||
* necessary.
|
||||
* following patters will be replaced:
|
||||
* %{user} user name
|
||||
*/
|
||||
$conf['plugin']['authmysql']['getUserID'] = "SELECT uid AS id
|
||||
FROM users
|
||||
WHERE login='%{user}'";
|
||||
|
||||
/***********************************************************************/
|
||||
/* Additional SQL statements to delete users with the user manager */
|
||||
/***********************************************************************/
|
||||
|
||||
/* This statement should remove a user fom the database.
|
||||
* Following patterns will be replaced:
|
||||
* %{user} user's login name
|
||||
* %{uid} id of a user dataset
|
||||
*/
|
||||
$conf['plugin']['authmysql']['delUser'] = "DELETE FROM users
|
||||
WHERE uid='%{uid}'";
|
||||
|
||||
/* This statement should remove all connections from a user to any group
|
||||
* (a user quits membership of all groups).
|
||||
* Following patterns will be replaced:
|
||||
* %{uid} id of a user dataset
|
||||
*/
|
||||
$conf['plugin']['authmysql']['delUserRefs'] = "DELETE FROM usergroup
|
||||
WHERE uid='%{uid}'";
|
||||
|
||||
/***********************************************************************/
|
||||
/* Additional SQL statements to modify users with the user manager */
|
||||
/***********************************************************************/
|
||||
|
||||
/* This statements should modify a user entry in the database. The
|
||||
* statements UpdateLogin, UpdatePass, UpdateEmail and UpdateName will be
|
||||
* added to updateUser on demand. Only changed parameters will be used.
|
||||
*
|
||||
* Following patterns will be replaced:
|
||||
* %{user} user's login name
|
||||
* %{pass} password (encrypted or clear text, depends on 'encryptPass')
|
||||
* %{email} email address
|
||||
* %{name} user's full name
|
||||
* %{uid} user id that should be updated
|
||||
*/
|
||||
$conf['plugin']['authmysql']['updateUser'] = "UPDATE users SET";
|
||||
$conf['plugin']['authmysql']['UpdateLogin'] = "login='%{user}'";
|
||||
$conf['plugin']['authmysql']['UpdatePass'] = "pass='%{pass}'";
|
||||
$conf['plugin']['authmysql']['UpdateEmail'] = "email='%{email}'";
|
||||
$conf['plugin']['authmysql']['UpdateName'] = "firstname=SUBSTRING_INDEX('%{name}',' ', 1),
|
||||
lastname=SUBSTRING_INDEX('%{name}',' ', -1)";
|
||||
$conf['plugin']['authmysql']['UpdateTarget']= "WHERE uid=%{uid}";
|
||||
|
||||
/* This statement should remove a single connection from a user to a
|
||||
* group (a user quits membership of that group).
|
||||
*
|
||||
* Following patterns will be replaced:
|
||||
* %{user} user's login name
|
||||
* %{uid} id of a user dataset
|
||||
* %{group} group name
|
||||
* %{gid} id of a group dataset
|
||||
*/
|
||||
$conf['plugin']['authmysql']['delUserGroup']= "DELETE FROM usergroup
|
||||
WHERE uid='%{uid}'
|
||||
AND gid='%{gid}'";
|
||||
|
||||
/* This statement should return the database index of a given group name.
|
||||
* The plugin will access the index with the name 'id' so an alias might
|
||||
* be necessary.
|
||||
*
|
||||
* Following patters will be replaced:
|
||||
* %{group} group name
|
||||
*/
|
||||
$conf['plugin']['authmysql']['getGroupID'] = "SELECT gid AS id
|
||||
FROM groups
|
||||
WHERE name='%{group}'";
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
/*
|
||||
* Local plugin enable/disable settings
|
||||
*
|
||||
* Auto-generated by install script
|
||||
* Date: Sun, 07 Jul 2024 09:48:21 +0000
|
||||
*/
|
||||
|
||||
$plugins['authad'] = 0;
|
||||
$plugins['authldap'] = 0;
|
||||
$plugins['authmysql'] = 0;
|
||||
$plugins['authpgsql'] = 0;
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* This file configures the default states of available plugins. All settings in
|
||||
* the plugins.*.php files will override those here.
|
||||
*/
|
||||
$plugins['testing'] = 0;
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* This file configures the enabled/disabled status of plugins, which are also protected
|
||||
* from changes by the extension manager. These settings will override any local settings.
|
||||
* It is not recommended to change this file, as it is overwritten on DokuWiki upgrades.
|
||||
*/
|
||||
$plugins['acl'] = 1;
|
||||
$plugins['authplain'] = 1;
|
||||
$plugins['extension'] = 1;
|
||||
$plugins['config'] = 1;
|
||||
$plugins['usermanager'] = 1;
|
||||
$plugins['template:dokuwiki'] = 1; // not a plugin, but this should not be uninstalled either
|
||||
@@ -0,0 +1,11 @@
|
||||
#Add URL schemes you want to be recognized as links here
|
||||
|
||||
http
|
||||
https
|
||||
telnet
|
||||
gopher
|
||||
wais
|
||||
ftp
|
||||
ed2k
|
||||
irc
|
||||
ldap
|
||||
@@ -0,0 +1,28 @@
|
||||
# Smileys configured here will be replaced by the
|
||||
# configured images in the smiley directory
|
||||
|
||||
8-) cool.svg
|
||||
8-O eek.svg
|
||||
8-o eek.svg
|
||||
:-( sad.svg
|
||||
:-) smile.svg
|
||||
=) smile2.svg
|
||||
:-/ doubt.svg
|
||||
:-\ doubt2.svg
|
||||
:-? confused.svg
|
||||
:-D biggrin.svg
|
||||
:-P razz.svg
|
||||
:-o surprised.svg
|
||||
:-O surprised.svg
|
||||
:-x silenced.svg
|
||||
:-X silenced.svg
|
||||
:-| neutral.svg
|
||||
;-) wink.svg
|
||||
m( facepalm.svg
|
||||
^_^ fun.svg
|
||||
:?: question.svg
|
||||
:!: exclaim.svg
|
||||
LOL lol.svg
|
||||
FIXME fixme.svg
|
||||
DELETEME deleteme.svg
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# users.auth.php
|
||||
# <?php exit()?>
|
||||
# Don't modify the lines above
|
||||
#
|
||||
# Userfile
|
||||
#
|
||||
# Format:
|
||||
#
|
||||
# login:passwordhash:Real Name:email:groups,comma,separated
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# This blacklist is maintained by the DokuWiki community
|
||||
# patches welcome
|
||||
#
|
||||
https?:\/\/(\S*?)(-side-effects|top|pharm|pill|discount|discount-|deal|price|order|now|best|cheap|cheap-|online|buy|buy-|sale|sell)(\S*?)(cialis|viagra|prazolam|xanax|zanax|soma|vicodin|zenical|xenical|meridia|paxil|prozac|claritin|allegra|lexapro|wellbutrin|zoloft|retin|valium|levitra|phentermine)
|
||||
https?:\/\/(\S*?)(bi\s*sex|gay\s*sex|fetish|incest|penis|\brape\b)
|
||||
zoosex
|
||||
gang\s*bang
|
||||
facials
|
||||
ladyboy
|
||||
\btits\b
|
||||
bolea\.com
|
||||
52crystal
|
||||
baida\.org
|
||||
web-directory\.awardspace\.us
|
||||
korsan-team\.com
|
||||
BUDA TAMAMDIR
|
||||
wow-powerleveling-wow\.com
|
||||
wow gold
|
||||
wow-gold\.dinmo\.cn
|
||||
downgrade-vista\.com
|
||||
downgradetowindowsxp\.com
|
||||
elegantugg\.com
|
||||
classicedhardy\.com
|
||||
research-service\.com
|
||||
https?:\/\/(\S*?)(2-pay-secure|911essay|academia-research|anypapers|applicationessay|bestbuyessay|bestdissertation|bestessay|bestresume|besttermpaper|businessessay|college-paper|customessay|custom-made-paper|custom-writing|degree-?result|dissertationblog|dissertation-service|dissertations?expert|essaybank|essay-?blog|essaycapital|essaylogic|essaymill|essayontime|essaypaper|essays?land|essaytownsucks|essay-?writ|fastessays|freelancercareers|genuinecontent|genuineessay|genuinepaper|goessay|grandresume|killer-content|ma-dissertation|managementessay|masterpaper|mightystudent|needessay|researchedge|researchpaper-blog|resumecvservice|resumesexperts|resumesplanet|rushessay|samedayessay|superiorcontent|superiorpaper|superiorthesis|term-paper|termpaper-blog|term-paper-research|thesisblog|universalresearch|valwriting|vdwriters|wisetranslation|writersassembly|writers\.com\.ph|writers\.ph)
|
||||
flatsinmumbai\.co\.in
|
||||
https?:\/\/(\S*?)penny-?stock
|
||||
mattressreview\.biz
|
||||
(just|simply) (my|a) profile (site|webpage|page)
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* DokuWiki mainscript
|
||||
*
|
||||
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
* @global Input $INPUT
|
||||
*/
|
||||
|
||||
use dokuwiki\ChangeLog\PageChangeLog;
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
// update message version - always use a string to avoid localized floats!
|
||||
$updateVersion = "55.1";
|
||||
|
||||
// xdebug_start_profiling();
|
||||
|
||||
if (!defined('DOKU_INC')) define('DOKU_INC', __DIR__ . '/');
|
||||
|
||||
// define all DokuWiki globals here (needed within test requests but also helps to keep track)
|
||||
global $ACT, $INPUT, $QUERY, $ID, $REV, $DATE_AT, $IDX,
|
||||
$DATE, $RANGE, $HIGH, $TEXT, $PRE, $SUF, $SUM, $INFO, $JSINFO;
|
||||
|
||||
|
||||
if (isset($_SERVER['HTTP_X_DOKUWIKI_DO'])) {
|
||||
$ACT = trim(strtolower($_SERVER['HTTP_X_DOKUWIKI_DO']));
|
||||
} elseif (!empty($_REQUEST['idx'])) {
|
||||
$ACT = 'index';
|
||||
} elseif (isset($_REQUEST['do'])) {
|
||||
$ACT = $_REQUEST['do'];
|
||||
} else {
|
||||
$ACT = 'show';
|
||||
}
|
||||
|
||||
// load and initialize the core system
|
||||
require_once(DOKU_INC . 'inc/init.php');
|
||||
|
||||
//import variables
|
||||
$INPUT->set('id', str_replace("\xC2\xAD", '', $INPUT->str('id'))); //soft-hyphen
|
||||
$QUERY = trim($INPUT->str('q'));
|
||||
$ID = getID();
|
||||
|
||||
$REV = $INPUT->int('rev');
|
||||
$DATE_AT = $INPUT->str('at');
|
||||
$IDX = $INPUT->str('idx');
|
||||
$DATE = $INPUT->int('date');
|
||||
$RANGE = $INPUT->str('range');
|
||||
$HIGH = $INPUT->param('s');
|
||||
if (empty($HIGH)) $HIGH = getGoogleQuery();
|
||||
|
||||
if ($INPUT->post->has('wikitext')) {
|
||||
$TEXT = cleanText($INPUT->post->str('wikitext'));
|
||||
}
|
||||
$PRE = cleanText(substr($INPUT->post->str('prefix'), 0, -1));
|
||||
$SUF = cleanText($INPUT->post->str('suffix'));
|
||||
$SUM = $INPUT->post->str('summary');
|
||||
|
||||
|
||||
//parse DATE_AT
|
||||
if ($DATE_AT) {
|
||||
$date_parse = strtotime($DATE_AT);
|
||||
if ($date_parse) {
|
||||
$DATE_AT = $date_parse;
|
||||
} else { // check for UNIX Timestamp
|
||||
$date_parse = @date('Ymd', $DATE_AT);
|
||||
if (!$date_parse || $date_parse === '19700101') {
|
||||
msg(sprintf($lang['unable_to_parse_date'], hsc($DATE_AT)));
|
||||
$DATE_AT = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check for existing $REV related to $DATE_AT
|
||||
if ($DATE_AT) {
|
||||
$pagelog = new PageChangeLog($ID);
|
||||
$rev_t = $pagelog->getLastRevisionAt($DATE_AT);
|
||||
if ($rev_t === '') {
|
||||
//current revision
|
||||
$REV = null;
|
||||
$DATE_AT = null;
|
||||
} elseif ($rev_t === false) {
|
||||
//page did not exist
|
||||
$rev_n = $pagelog->getRelativeRevision($DATE_AT, +1);
|
||||
msg(
|
||||
sprintf(
|
||||
$lang['page_nonexist_rev'],
|
||||
dformat($DATE_AT),
|
||||
wl($ID, ['rev' => $rev_n]),
|
||||
dformat($rev_n)
|
||||
)
|
||||
);
|
||||
$REV = $DATE_AT; //will result in a page not exists message
|
||||
} else {
|
||||
$REV = $rev_t;
|
||||
}
|
||||
}
|
||||
|
||||
//make infos about the selected page available
|
||||
$INFO = pageinfo();
|
||||
|
||||
// handle debugging
|
||||
if ($conf['allowdebug'] && $ACT == 'debug') {
|
||||
html_debug();
|
||||
exit;
|
||||
}
|
||||
|
||||
//send 404 for missing pages if configured or ID has special meaning to bots
|
||||
if (
|
||||
!$INFO['exists'] &&
|
||||
($conf['send404'] || preg_match('/^(robots\.txt|sitemap\.xml(\.gz)?|favicon\.ico|crossdomain\.xml)$/', $ID)) &&
|
||||
($ACT == 'show' || (!is_array($ACT) && str_starts_with($ACT, 'export_')))
|
||||
) {
|
||||
header('HTTP/1.0 404 Not Found');
|
||||
}
|
||||
|
||||
//prepare breadcrumbs (initialize a static var)
|
||||
if ($conf['breadcrumbs']) breadcrumbs();
|
||||
|
||||
// check upstream
|
||||
checkUpdateMessages();
|
||||
|
||||
$tmp = []; // No event data
|
||||
Event::createAndTrigger('DOKUWIKI_STARTED', $tmp);
|
||||
|
||||
//close session
|
||||
session_write_close();
|
||||
|
||||
//do the work (picks up what to do from global env)
|
||||
act_dispatch();
|
||||
|
||||
$tmp = []; // No event data
|
||||
Event::createAndTrigger('DOKUWIKI_DONE', $tmp);
|
||||
|
||||
// xdebug_dump_function_profile(1);
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* XML feed export
|
||||
*
|
||||
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
* @global array $conf
|
||||
* @global Input $INPUT
|
||||
*/
|
||||
|
||||
use dokuwiki\Feed\FeedCreator;
|
||||
use dokuwiki\Feed\FeedCreatorOptions;
|
||||
use dokuwiki\Cache\Cache;
|
||||
use dokuwiki\ChangeLog\MediaChangeLog;
|
||||
use dokuwiki\ChangeLog\PageChangeLog;
|
||||
use dokuwiki\Extension\AuthPlugin;
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
if (!defined('DOKU_INC')) define('DOKU_INC', __DIR__ . '/');
|
||||
require_once(DOKU_INC . 'inc/init.php');
|
||||
|
||||
//close session
|
||||
session_write_close();
|
||||
|
||||
//feed disabled?
|
||||
if (!actionOK('rss')) {
|
||||
http_status(404);
|
||||
echo '<error>RSS feed is disabled.</error>';
|
||||
exit;
|
||||
}
|
||||
|
||||
$options = new FeedCreatorOptions();
|
||||
|
||||
// the feed is dynamic - we need a cache for each combo
|
||||
// (but most people just use the default feed so it's still effective)
|
||||
$key = implode('$', [
|
||||
$options->getCacheKey(),
|
||||
$INPUT->server->str('REMOTE_USER'),
|
||||
$INPUT->server->str('HTTP_HOST'),
|
||||
$INPUT->server->str('SERVER_PORT')
|
||||
]);
|
||||
$cache = new Cache($key, '.feed');
|
||||
|
||||
// prepare cache depends
|
||||
$depends['files'] = getConfigFiles('main');
|
||||
$depends['age'] = $conf['rss_update'];
|
||||
$depends['purge'] = $INPUT->bool('purge');
|
||||
|
||||
// check cacheage and deliver if nothing has changed since last
|
||||
// time or the update interval has not passed, also handles conditional requests
|
||||
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
|
||||
header('Pragma: public');
|
||||
header('Content-Type: ' . $options->getMimeType());
|
||||
header('X-Robots-Tag: noindex');
|
||||
if ($cache->useCache($depends)) {
|
||||
http_conditionalRequest($cache->getTime());
|
||||
if ($conf['allowdebug']) header("X-CacheUsed: $cache->cache");
|
||||
echo $cache->retrieveCache();
|
||||
exit;
|
||||
} else {
|
||||
http_conditionalRequest(time());
|
||||
}
|
||||
|
||||
// create new feed
|
||||
try {
|
||||
$feed = (new FeedCreator($options))->build();
|
||||
$cache->storeCache($feed);
|
||||
echo $feed;
|
||||
} catch (Exception $e) {
|
||||
http_status(500);
|
||||
echo '<error>' . hsc($e->getMessage()) . '</error>';
|
||||
exit;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
## no access to the inc directory
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</IfModule>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAclRequiredException;
|
||||
use dokuwiki\Extension\AuthPlugin;
|
||||
|
||||
/**
|
||||
* Class AbstractAclAction
|
||||
*
|
||||
* An action that requires the ACL subsystem to be enabled (eg. useacl=1)
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
abstract class AbstractAclAction extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions()
|
||||
{
|
||||
parent::checkPreconditions();
|
||||
global $conf;
|
||||
global $auth;
|
||||
if (!$conf['useacl']) throw new ActionAclRequiredException();
|
||||
if (!$auth instanceof AuthPlugin) throw new ActionAclRequiredException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
use dokuwiki\Action\Exception\FatalException;
|
||||
|
||||
/**
|
||||
* Class AbstractAction
|
||||
*
|
||||
* Base class for all actions
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
abstract class AbstractAction
|
||||
{
|
||||
/** @var string holds the name of the action (lowercase class name, no namespace) */
|
||||
protected $actionname;
|
||||
|
||||
/**
|
||||
* AbstractAction constructor.
|
||||
*
|
||||
* @param string $actionname the name of this action (see getActionName() for caveats)
|
||||
*/
|
||||
public function __construct($actionname = '')
|
||||
{
|
||||
if ($actionname !== '') {
|
||||
$this->actionname = $actionname;
|
||||
} else {
|
||||
// http://stackoverflow.com/a/27457689/172068
|
||||
$this->actionname = strtolower(substr(strrchr(get_class($this), '\\'), 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the minimum permission needed
|
||||
*
|
||||
* This needs to return one of the AUTH_* constants. It will be checked against
|
||||
* the current user and page after checkPermissions() ran through. If it fails,
|
||||
* the user will be shown the Denied action.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function minimumPermission();
|
||||
|
||||
/**
|
||||
* Check conditions are met to run this action
|
||||
*
|
||||
* @throws ActionException
|
||||
* @return void
|
||||
*/
|
||||
public function checkPreconditions()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Process data
|
||||
*
|
||||
* This runs before any output is sent to the browser.
|
||||
*
|
||||
* Throw an Exception if a different action should be run after this step.
|
||||
*
|
||||
* @throws ActionException
|
||||
* @return void
|
||||
*/
|
||||
public function preProcess()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Output whatever content is wanted within tpl_content();
|
||||
*
|
||||
* @fixme we may want to return a Ui class here
|
||||
* @throws FatalException
|
||||
*/
|
||||
public function tplContent()
|
||||
{
|
||||
throw new FatalException('No content for Action ' . $this->actionname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this action
|
||||
*
|
||||
* This is usually the lowercased class name, but may differ for some actions.
|
||||
* eg. the export_ modes or for the Plugin action.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getActionName()
|
||||
{
|
||||
return $this->actionname;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\FatalException;
|
||||
|
||||
/**
|
||||
* Class AbstractAliasAction
|
||||
*
|
||||
* An action that is an alias for another action. Skips the minimumPermission check
|
||||
*
|
||||
* Be sure to implement preProcess() and throw an ActionAbort exception
|
||||
* with the proper action.
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
abstract class AbstractAliasAction extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FatalException
|
||||
*/
|
||||
public function preProcess()
|
||||
{
|
||||
throw new FatalException('Alias Actions need to implement preProcess to load the aliased action');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionUserRequiredException;
|
||||
|
||||
/**
|
||||
* Class AbstractUserAction
|
||||
*
|
||||
* An action that requires a logged in user
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
abstract class AbstractUserAction extends AbstractAclAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions()
|
||||
{
|
||||
parent::checkPreconditions();
|
||||
global $INPUT;
|
||||
if ($INPUT->server->str('REMOTE_USER') === '') {
|
||||
throw new ActionUserRequiredException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
use dokuwiki\Extension\AdminPlugin;
|
||||
|
||||
/**
|
||||
* Class Admin
|
||||
*
|
||||
* Action to show the admin interface or admin plugins
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Admin extends AbstractUserAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_READ; // let in check later
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function preProcess()
|
||||
{
|
||||
global $INPUT;
|
||||
|
||||
// retrieve admin plugin name from $_REQUEST['page']
|
||||
if ($INPUT->str('page', '', true) != '') {
|
||||
/** @var AdminPlugin $plugin */
|
||||
if ($plugin = plugin_getRequestAdminPlugin()) { // FIXME this method does also permission checking
|
||||
if (!$plugin->isAccessibleByCurrentUser()) {
|
||||
throw new ActionException('denied');
|
||||
}
|
||||
$plugin->handle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function tplContent()
|
||||
{
|
||||
tpl_admin();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
use dokuwiki\JWT;
|
||||
|
||||
class Authtoken extends AbstractUserAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions()
|
||||
{
|
||||
parent::checkPreconditions();
|
||||
|
||||
if (!checkSecurityToken()) throw new ActionException('profile');
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
global $INPUT;
|
||||
parent::preProcess();
|
||||
$token = JWT::fromUser($INPUT->server->str('REMOTE_USER'));
|
||||
$token->save();
|
||||
throw new ActionAbort('profile');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\Backlinks;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Backlink
|
||||
*
|
||||
* Shows which pages link to the current page
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Backlink extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
(new Backlinks())->show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
|
||||
/**
|
||||
* Class Cancel
|
||||
*
|
||||
* Alias for show. Aborts editing
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Cancel extends AbstractAliasAction
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws ActionAbort
|
||||
*/
|
||||
public function preProcess()
|
||||
{
|
||||
global $ID;
|
||||
unlock($ID);
|
||||
|
||||
// continue with draftdel -> redirect -> show
|
||||
throw new ActionAbort('draftdel');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
|
||||
/**
|
||||
* Class Check
|
||||
*
|
||||
* Adds some debugging info before aborting to show
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Check extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
public function preProcess()
|
||||
{
|
||||
check();
|
||||
throw new ActionAbort();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\PageConflict;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Conflict
|
||||
*
|
||||
* Show the conflict resolution screen
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Conflict extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
global $INFO;
|
||||
if ($INFO['exists']) {
|
||||
return AUTH_EDIT;
|
||||
} else {
|
||||
return AUTH_CREATE;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
global $PRE;
|
||||
global $TEXT;
|
||||
global $SUF;
|
||||
global $SUM;
|
||||
|
||||
$text = con($PRE, $TEXT, $SUF);
|
||||
(new PageConflict($text, $SUM))->show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\Login;
|
||||
use dokuwiki\Extension\Event;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Denied
|
||||
*
|
||||
* Show the access denied screen
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Denied extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
$this->showBanner();
|
||||
|
||||
$data = null;
|
||||
$event = new Event('ACTION_DENIED_TPLCONTENT', $data);
|
||||
if ($event->advise_before()) {
|
||||
global $INPUT;
|
||||
if (empty($INPUT->server->str('REMOTE_USER')) && actionOK('login')) {
|
||||
(new Login())->show();
|
||||
}
|
||||
}
|
||||
$event->advise_after();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display error on denied pages
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function showBanner()
|
||||
{
|
||||
// print intro
|
||||
echo p_locale_xhtml('denied');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\PageDiff;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Diff
|
||||
*
|
||||
* Show the differences between two revisions
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Diff extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
global $INPUT;
|
||||
|
||||
// store the selected diff type in cookie
|
||||
$difftype = $INPUT->str('difftype');
|
||||
if (!empty($difftype)) {
|
||||
set_doku_pref('difftype', $difftype);
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
global $INFO;
|
||||
(new PageDiff($INFO['id']))->preference('showIntro', true)->show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\PageDraft;
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Draft
|
||||
*
|
||||
* Screen to see and recover a draft
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
* @fixme combine with Recover?
|
||||
*/
|
||||
class Draft extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
global $INFO;
|
||||
if ($INFO['exists']) {
|
||||
return AUTH_EDIT;
|
||||
} else {
|
||||
return AUTH_CREATE;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions()
|
||||
{
|
||||
parent::checkPreconditions();
|
||||
global $INFO;
|
||||
if (!isset($INFO['draft']) || !file_exists($INFO['draft'])) throw new ActionException('edit');
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
(new PageDraft())->show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Draft;
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
|
||||
/**
|
||||
* Class Draftdel
|
||||
*
|
||||
* Delete a draft
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Draftdel extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_EDIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing draft for the current page and user if any
|
||||
*
|
||||
* Redirects to show, afterwards.
|
||||
*
|
||||
* @throws ActionAbort
|
||||
*/
|
||||
public function preProcess()
|
||||
{
|
||||
global $INFO, $ID;
|
||||
$draft = new Draft($ID, $INFO['client']);
|
||||
if ($draft->isDraftAvailable() && checkSecurityToken()) {
|
||||
$draft->deleteDraft();
|
||||
}
|
||||
|
||||
throw new ActionAbort('redirect');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\Editor;
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Edit
|
||||
*
|
||||
* Handle editing
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Edit extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
global $INFO;
|
||||
if ($INFO['exists']) {
|
||||
return AUTH_READ; // we check again below
|
||||
} else {
|
||||
return AUTH_CREATE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc falls back to 'source' if page not writable
|
||||
*/
|
||||
public function checkPreconditions()
|
||||
{
|
||||
parent::checkPreconditions();
|
||||
global $INFO;
|
||||
|
||||
// no edit permission? view source
|
||||
if ($INFO['exists'] && !$INFO['writable']) {
|
||||
throw new ActionAbort('source');
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
global $ID;
|
||||
global $INFO;
|
||||
|
||||
global $TEXT;
|
||||
global $RANGE;
|
||||
global $PRE;
|
||||
global $SUF;
|
||||
global $REV;
|
||||
global $SUM;
|
||||
global $lang;
|
||||
global $DATE;
|
||||
|
||||
if (!isset($TEXT)) {
|
||||
if ($INFO['exists']) {
|
||||
if ($RANGE) {
|
||||
[$PRE, $TEXT, $SUF] = rawWikiSlices($RANGE, $ID, $REV);
|
||||
} else {
|
||||
$TEXT = rawWiki($ID, $REV);
|
||||
}
|
||||
} else {
|
||||
$TEXT = pageTemplate($ID);
|
||||
}
|
||||
}
|
||||
|
||||
//set summary default
|
||||
if (!$SUM) {
|
||||
if ($REV) {
|
||||
$SUM = sprintf($lang['restored'], dformat($REV));
|
||||
} elseif (!$INFO['exists']) {
|
||||
$SUM = $lang['created'];
|
||||
}
|
||||
}
|
||||
|
||||
// Use the date of the newest revision, not of the revision we edit
|
||||
// This is used for conflict detection
|
||||
if (!$DATE) $DATE = @filemtime(wikiFN($ID));
|
||||
|
||||
//check if locked by anyone - if not lock for my self
|
||||
$lockedby = checklock($ID);
|
||||
if ($lockedby) {
|
||||
throw new ActionAbort('locked');
|
||||
}
|
||||
lock($ID);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
(new Editor())->show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class ActionAbort
|
||||
*
|
||||
* Strictly speaking not an Exception but an expected execution path. Used to
|
||||
* signal when one action is done and another should take over.
|
||||
*
|
||||
* If you want to signal the same but under some error condition use ActionException
|
||||
* or one of it's decendants.
|
||||
*
|
||||
* The message will NOT be shown to the enduser
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class ActionAbort extends ActionException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class ActionAclRequiredException
|
||||
*
|
||||
* Thrown by AbstractACLAction when an action requires that the ACL subsystem is
|
||||
* enabled but it isn't. You should not use it
|
||||
*
|
||||
* The message will NOT be shown to the enduser
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class ActionAclRequiredException extends ActionException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class ActionDisabledException
|
||||
*
|
||||
* Thrown when the requested action has been disabled. Eg. through the 'disableactions'
|
||||
* config setting. You should probably not use it.
|
||||
*
|
||||
* The message will NOT be shown to the enduser, but a generic information will be shown.
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class ActionDisabledException extends ActionException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class ActionException
|
||||
*
|
||||
* This exception and its subclasses signal that the current action should be
|
||||
* aborted and a different action should be used instead. The new action can
|
||||
* be given as parameter in the constructor. Defaults to 'show'
|
||||
*
|
||||
* The message will NOT be shown to the enduser
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class ActionException extends \Exception
|
||||
{
|
||||
/** @var string the new action */
|
||||
protected $newaction;
|
||||
|
||||
/** @var bool should the exception's message be shown to the user? */
|
||||
protected $displayToUser = false;
|
||||
|
||||
/**
|
||||
* ActionException constructor.
|
||||
*
|
||||
* When no new action is given 'show' is assumed. For requests that originated in a POST,
|
||||
* a 'redirect' is used which will cause a redirect to the 'show' action.
|
||||
*
|
||||
* @param string|null $newaction the action that should be used next
|
||||
* @param string $message optional message, will not be shown except for some dub classes
|
||||
*/
|
||||
public function __construct($newaction = null, $message = '')
|
||||
{
|
||||
global $INPUT;
|
||||
parent::__construct($message);
|
||||
if (is_null($newaction)) {
|
||||
if (strtolower($INPUT->server->str('REQUEST_METHOD')) == 'post') {
|
||||
$newaction = 'redirect';
|
||||
} else {
|
||||
$newaction = 'show';
|
||||
}
|
||||
}
|
||||
|
||||
$this->newaction = $newaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action to use next
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNewAction()
|
||||
{
|
||||
return $this->newaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this Exception's message be shown to the user?
|
||||
*
|
||||
* @param null|bool $set when null is given, the current setting is not changed
|
||||
* @return bool
|
||||
*/
|
||||
public function displayToUser($set = null)
|
||||
{
|
||||
if (!is_null($set)) $this->displayToUser = $set;
|
||||
return $set;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class ActionUserRequiredException
|
||||
*
|
||||
* Thrown by AbstractUserAction when an action requires that a user is logged
|
||||
* in but it isn't. You should not use it.
|
||||
*
|
||||
* The message will NOT be shown to the enduser
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class ActionUserRequiredException extends ActionException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class FatalException
|
||||
*
|
||||
* A fatal exception during handling the action
|
||||
*
|
||||
* Will abort all handling and display some info to the user. The HTTP status code
|
||||
* can be defined.
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class FatalException extends \Exception
|
||||
{
|
||||
/**
|
||||
* FatalException constructor.
|
||||
*
|
||||
* @param string $message the message to send
|
||||
* @param int $status the HTTP status to send
|
||||
* @param null|\Exception $previous previous exception
|
||||
*/
|
||||
public function __construct($message = 'A fatal error occured', $status = 500, $previous = null)
|
||||
{
|
||||
parent::__construct($message, $status, $previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class NoActionException
|
||||
*
|
||||
* Thrown in the ActionRouter when a wanted action can not be found. Triggers
|
||||
* the unknown action event
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class NoActionException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
/**
|
||||
* Class Export
|
||||
*
|
||||
* Handle exporting by calling the appropriate renderer
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Export extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a wiki page for various formats
|
||||
*
|
||||
* Triggers ACTION_EXPORT_POSTPROCESS
|
||||
*
|
||||
* Event data:
|
||||
* data['id'] -- page id
|
||||
* data['mode'] -- requested export mode
|
||||
* data['headers'] -- export headers
|
||||
* data['output'] -- export output
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @author Michael Klier <chi@chimeric.de>
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function preProcess()
|
||||
{
|
||||
global $ID;
|
||||
global $REV;
|
||||
global $conf;
|
||||
global $lang;
|
||||
|
||||
$pre = '';
|
||||
$post = '';
|
||||
$headers = [];
|
||||
|
||||
// search engines: never cache exported docs! (Google only currently)
|
||||
$headers['X-Robots-Tag'] = 'noindex';
|
||||
|
||||
$mode = substr($this->actionname, 7);
|
||||
switch ($mode) {
|
||||
case 'raw':
|
||||
$headers['Content-Type'] = 'text/plain; charset=utf-8';
|
||||
$headers['Content-Disposition'] = 'attachment; filename=' . noNS($ID) . '.txt';
|
||||
$output = rawWiki($ID, $REV);
|
||||
break;
|
||||
case 'xhtml':
|
||||
$pre .= '<!DOCTYPE html>' . DOKU_LF;
|
||||
$pre .= '<html lang="' . $conf['lang'] . '" dir="' . $lang['direction'] . '">' . DOKU_LF;
|
||||
$pre .= '<head>' . DOKU_LF;
|
||||
$pre .= ' <meta charset="utf-8" />' . DOKU_LF; // FIXME improve wrapper
|
||||
$pre .= ' <title>' . $ID . '</title>' . DOKU_LF;
|
||||
|
||||
// get metaheaders
|
||||
ob_start();
|
||||
tpl_metaheaders();
|
||||
$pre .= ob_get_clean();
|
||||
|
||||
$pre .= '</head>' . DOKU_LF;
|
||||
$pre .= '<body>' . DOKU_LF;
|
||||
$pre .= '<div class="dokuwiki export">' . DOKU_LF;
|
||||
|
||||
// get toc
|
||||
$pre .= tpl_toc(true);
|
||||
|
||||
$headers['Content-Type'] = 'text/html; charset=utf-8';
|
||||
$output = p_wiki_xhtml($ID, $REV, false);
|
||||
|
||||
$post .= '</div>' . DOKU_LF;
|
||||
$post .= '</body>' . DOKU_LF;
|
||||
$post .= '</html>' . DOKU_LF;
|
||||
break;
|
||||
case 'xhtmlbody':
|
||||
$headers['Content-Type'] = 'text/html; charset=utf-8';
|
||||
$output = p_wiki_xhtml($ID, $REV, false);
|
||||
break;
|
||||
default:
|
||||
$output = p_cached_output(wikiFN($ID, $REV), $mode, $ID);
|
||||
$headers = p_get_metadata($ID, "format $mode");
|
||||
break;
|
||||
}
|
||||
|
||||
// prepare event data
|
||||
$data = [];
|
||||
$data['id'] = $ID;
|
||||
$data['mode'] = $mode;
|
||||
$data['headers'] = $headers;
|
||||
$data['output'] =& $output;
|
||||
|
||||
Event::createAndTrigger('ACTION_EXPORT_POSTPROCESS', $data);
|
||||
|
||||
if (!empty($data['output'])) {
|
||||
if (is_array($data['headers'])) foreach ($data['headers'] as $key => $val) {
|
||||
header("$key: $val");
|
||||
}
|
||||
echo $pre . $data['output'] . $post;
|
||||
exit;
|
||||
}
|
||||
|
||||
throw new ActionAbort();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Index
|
||||
*
|
||||
* Show the human readable sitemap. Do not confuse with Sitemap
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Index extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
global $IDX;
|
||||
(new Ui\Index($IDX))->show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\Editor;
|
||||
|
||||
/**
|
||||
* Class Locked
|
||||
*
|
||||
* Show a locked screen when a page is locked
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Locked extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
$this->showBanner();
|
||||
(new Editor())->show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display error on locked pages
|
||||
*
|
||||
* @return void
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
*/
|
||||
public function showBanner()
|
||||
{
|
||||
global $ID;
|
||||
global $conf;
|
||||
global $lang;
|
||||
global $INFO;
|
||||
|
||||
$locktime = filemtime(wikiLockFN($ID));
|
||||
$expire = dformat($locktime + $conf['locktime']);
|
||||
$min = round(($conf['locktime'] - (time() - $locktime)) / 60);
|
||||
|
||||
// print intro
|
||||
echo p_locale_xhtml('locked');
|
||||
|
||||
echo '<ul>';
|
||||
echo '<li><div class="li"><strong>' . $lang['lockedby'] . '</strong> ' .
|
||||
editorinfo($INFO['locked']) . '</div></li>';
|
||||
echo '<li><div class="li"><strong>' . $lang['lockexpire'] . '</strong> ' .
|
||||
$expire . ' (' . $min . ' min)</div></li>';
|
||||
echo '</ul>' . DOKU_LF;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Login
|
||||
*
|
||||
* The login form. Actual logins are handled in inc/auth.php
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Login extends AbstractAclAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions()
|
||||
{
|
||||
global $INPUT;
|
||||
parent::checkPreconditions();
|
||||
if ($INPUT->server->has('REMOTE_USER')) {
|
||||
// nothing to do
|
||||
throw new ActionException();
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
(new Ui\Login())->show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
use dokuwiki\Extension\AuthPlugin;
|
||||
|
||||
/**
|
||||
* Class Logout
|
||||
*
|
||||
* Log out a user
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Logout extends AbstractUserAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions()
|
||||
{
|
||||
parent::checkPreconditions();
|
||||
|
||||
/** @var AuthPlugin $auth */
|
||||
global $auth;
|
||||
if (!$auth->canDo('logout')) throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
global $ID;
|
||||
global $INPUT;
|
||||
|
||||
if (!checkSecurityToken()) throw new ActionException();
|
||||
|
||||
// when logging out during an edit session, unlock the page
|
||||
$lockedby = checklock($ID);
|
||||
if ($lockedby == $INPUT->server->str('REMOTE_USER')) {
|
||||
unlock($ID);
|
||||
}
|
||||
|
||||
// do the logout stuff and redirect to login
|
||||
auth_logoff();
|
||||
send_redirect(wl($ID, ['do' => 'login'], true, '&'));
|
||||
|
||||
// should never be reached
|
||||
throw new ActionException('login');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Media
|
||||
*
|
||||
* The full screen media manager
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Media extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
tpl_media();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
/**
|
||||
* Class Plugin
|
||||
*
|
||||
* Used to run action plugins
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Plugin extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs nothing but a warning unless an action plugin overwrites it
|
||||
*
|
||||
* @inheritdoc
|
||||
* @triggers TPL_ACT_UNKNOWN
|
||||
*/
|
||||
public function tplContent()
|
||||
{
|
||||
$evt = new Event('TPL_ACT_UNKNOWN', $this->actionname);
|
||||
if ($evt->advise_before()) {
|
||||
msg('Failed to handle action: ' . hsc($this->actionname), -1);
|
||||
}
|
||||
$evt->advise_after();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\Editor;
|
||||
use dokuwiki\Ui\PageView;
|
||||
use dokuwiki\Draft;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Preview
|
||||
*
|
||||
* preview during editing
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Preview extends Edit
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
header('X-XSS-Protection: 0');
|
||||
$this->savedraft();
|
||||
parent::preProcess();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
global $TEXT;
|
||||
(new Editor())->show();
|
||||
(new PageView($TEXT))->show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a draft on preview
|
||||
*/
|
||||
protected function savedraft()
|
||||
{
|
||||
global $ID, $INFO;
|
||||
$draft = new Draft($ID, $INFO['client']);
|
||||
if (!$draft->saveDraft()) {
|
||||
$errors = $draft->getErrors();
|
||||
foreach ($errors as $error) {
|
||||
msg(hsc($error), -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\UserProfile;
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
use dokuwiki\Extension\AuthPlugin;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Profile
|
||||
*
|
||||
* Handle the profile form
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Profile extends AbstractUserAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions()
|
||||
{
|
||||
parent::checkPreconditions();
|
||||
|
||||
/** @var AuthPlugin $auth */
|
||||
global $auth;
|
||||
if (!$auth->canDo('Profile')) throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
global $lang;
|
||||
if (updateprofile()) {
|
||||
msg($lang['profchanged'], 1);
|
||||
throw new ActionAbort('show');
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
(new UserProfile())->show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
use dokuwiki\Extension\AuthPlugin;
|
||||
|
||||
/**
|
||||
* Class ProfileDelete
|
||||
*
|
||||
* Delete a user account
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class ProfileDelete extends AbstractUserAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions()
|
||||
{
|
||||
parent::checkPreconditions();
|
||||
|
||||
/** @var AuthPlugin $auth */
|
||||
global $auth;
|
||||
if (!$auth->canDo('delUser')) throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
global $lang;
|
||||
if (auth_deleteprofile()) {
|
||||
msg($lang['profdeleted'], 1);
|
||||
throw new ActionAbort('show');
|
||||
} else {
|
||||
throw new ActionAbort('profile');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Recent
|
||||
*
|
||||
* The recent changes view
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Recent extends AbstractAction
|
||||
{
|
||||
/** @var string what type of changes to show */
|
||||
protected $showType = 'both';
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
global $INPUT;
|
||||
$show_changes = $INPUT->str('show_changes');
|
||||
if (!empty($show_changes)) {
|
||||
set_doku_pref('show_changes', $show_changes);
|
||||
$this->showType = $show_changes;
|
||||
} else {
|
||||
$this->showType = get_doku_pref('show_changes', 'both');
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
global $INPUT;
|
||||
(new Ui\Recent($INPUT->extract('first')->int('first'), $this->showType))->show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
|
||||
/**
|
||||
* Class Recover
|
||||
*
|
||||
* Recover a draft
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Recover extends AbstractAliasAction
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws ActionAbort
|
||||
*/
|
||||
public function preProcess()
|
||||
{
|
||||
throw new ActionAbort('edit');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
/**
|
||||
* Class Redirect
|
||||
*
|
||||
* Used to redirect to the current page with the last edited section as a target if found
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Redirect extends AbstractAliasAction
|
||||
{
|
||||
/**
|
||||
* Redirect to the show action, trying to jump to the previously edited section
|
||||
*
|
||||
* @triggers ACTION_SHOW_REDIRECT
|
||||
* @throws ActionAbort
|
||||
*/
|
||||
public function preProcess()
|
||||
{
|
||||
global $PRE;
|
||||
global $TEXT;
|
||||
global $INPUT;
|
||||
global $ID;
|
||||
global $ACT;
|
||||
|
||||
$opts = ['id' => $ID, 'preact' => $ACT];
|
||||
//get section name when coming from section edit
|
||||
if ($INPUT->has('hid')) {
|
||||
// Use explicitly transmitted header id
|
||||
$opts['fragment'] = $INPUT->str('hid');
|
||||
} elseif ($PRE && preg_match('/^\s*==+([^=\n]+)/', $TEXT, $match)) {
|
||||
// Fallback to old mechanism
|
||||
$check = false; //Byref
|
||||
$opts['fragment'] = sectionID($match[0], $check);
|
||||
}
|
||||
|
||||
// execute the redirect
|
||||
Event::createAndTrigger('ACTION_SHOW_REDIRECT', $opts, [$this, 'redirect']);
|
||||
|
||||
// should never be reached
|
||||
throw new ActionAbort('show');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the redirect
|
||||
*
|
||||
* Default action for ACTION_SHOW_REDIRECT
|
||||
*
|
||||
* @param array $opts id and fragment for the redirect and the preact
|
||||
*/
|
||||
public function redirect($opts)
|
||||
{
|
||||
$go = wl($opts['id'], '', true, '&');
|
||||
if (isset($opts['fragment'])) $go .= '#' . $opts['fragment'];
|
||||
|
||||
//show it
|
||||
send_redirect($go);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\UserRegister;
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
use dokuwiki\Extension\AuthPlugin;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Register
|
||||
*
|
||||
* Self registering a new user
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Register extends AbstractAclAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions()
|
||||
{
|
||||
parent::checkPreconditions();
|
||||
|
||||
/** @var AuthPlugin $auth */
|
||||
global $auth;
|
||||
global $conf;
|
||||
if (isset($conf['openregister']) && !$conf['openregister']) throw new ActionDisabledException();
|
||||
if (!$auth->canDo('addUser')) throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
if (register()) { // FIXME could be moved from auth to here
|
||||
throw new ActionAbort('login');
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
(new UserRegister())->show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\UserResendPwd;
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
use dokuwiki\Extension\AuthPlugin;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Resendpwd
|
||||
*
|
||||
* Handle password recovery
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Resendpwd extends AbstractAclAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions()
|
||||
{
|
||||
parent::checkPreconditions();
|
||||
|
||||
/** @var AuthPlugin $auth */
|
||||
global $auth;
|
||||
global $conf;
|
||||
if (isset($conf['resendpasswd']) && !$conf['resendpasswd'])
|
||||
throw new ActionDisabledException(); //legacy option
|
||||
if (!$auth->canDo('modPass')) throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
if ($this->resendpwd()) {
|
||||
throw new ActionAbort('login');
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
(new UserResendPwd())->show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a new password
|
||||
*
|
||||
* This function handles both phases of the password reset:
|
||||
*
|
||||
* - handling the first request of password reset
|
||||
* - validating the password reset auth token
|
||||
*
|
||||
* @author Benoit Chesneau <benoit@bchesneau.info>
|
||||
* @author Chris Smith <chris@jalakai.co.uk>
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @fixme this should be split up into multiple methods
|
||||
* @return bool true on success, false on any error
|
||||
*/
|
||||
protected function resendpwd()
|
||||
{
|
||||
global $lang;
|
||||
global $conf;
|
||||
/* @var AuthPlugin $auth */
|
||||
global $auth;
|
||||
global $INPUT;
|
||||
|
||||
if (!actionOK('resendpwd')) {
|
||||
msg($lang['resendna'], -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
$token = preg_replace('/[^a-f0-9]+/', '', $INPUT->str('pwauth'));
|
||||
|
||||
if ($token) {
|
||||
// we're in token phase - get user info from token
|
||||
|
||||
$tfile = $conf['cachedir'] . '/' . $token[0] . '/' . $token . '.pwauth';
|
||||
if (!file_exists($tfile)) {
|
||||
msg($lang['resendpwdbadauth'], -1);
|
||||
$INPUT->remove('pwauth');
|
||||
return false;
|
||||
}
|
||||
// token is only valid for 3 days
|
||||
if ((time() - filemtime($tfile)) > (3 * 60 * 60 * 24)) {
|
||||
msg($lang['resendpwdbadauth'], -1);
|
||||
$INPUT->remove('pwauth');
|
||||
@unlink($tfile);
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = io_readfile($tfile);
|
||||
$userinfo = $auth->getUserData($user, $requireGroups = false);
|
||||
if (empty($userinfo['mail'])) {
|
||||
msg($lang['resendpwdnouser'], -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$conf['autopasswd']) { // we let the user choose a password
|
||||
$pass = $INPUT->str('pass');
|
||||
|
||||
// password given correctly?
|
||||
if (!$pass) return false;
|
||||
if ($pass != $INPUT->str('passchk')) {
|
||||
msg($lang['regbadpass'], -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
// change it
|
||||
if (!$auth->triggerUserMod('modify', [$user, ['pass' => $pass]])) {
|
||||
msg($lang['proffail'], -1);
|
||||
return false;
|
||||
}
|
||||
} else { // autogenerate the password and send by mail
|
||||
$pass = auth_pwgen($user);
|
||||
if (!$auth->triggerUserMod('modify', [$user, ['pass' => $pass]])) {
|
||||
msg($lang['proffail'], -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auth_sendPassword($user, $pass)) {
|
||||
msg($lang['resendpwdsuccess'], 1);
|
||||
} else {
|
||||
msg($lang['regmailfail'], -1);
|
||||
}
|
||||
}
|
||||
|
||||
@unlink($tfile);
|
||||
return true;
|
||||
} else {
|
||||
// we're in request phase
|
||||
|
||||
if (!$INPUT->post->bool('save')) return false;
|
||||
|
||||
if (!$INPUT->post->str('login')) {
|
||||
msg($lang['resendpwdmissing'], -1);
|
||||
return false;
|
||||
} else {
|
||||
$user = trim($auth->cleanUser($INPUT->post->str('login')));
|
||||
}
|
||||
|
||||
$userinfo = $auth->getUserData($user, $requireGroups = false);
|
||||
if (empty($userinfo['mail'])) {
|
||||
msg($lang['resendpwdnouser'], -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
// generate auth token
|
||||
$token = md5(auth_randombytes(16)); // random secret
|
||||
$tfile = $conf['cachedir'] . '/' . $token[0] . '/' . $token . '.pwauth';
|
||||
$url = wl('', ['do' => 'resendpwd', 'pwauth' => $token], true, '&');
|
||||
|
||||
io_saveFile($tfile, $user);
|
||||
|
||||
$text = rawLocale('pwconfirm');
|
||||
$trep = [
|
||||
'FULLNAME' => $userinfo['name'],
|
||||
'LOGIN' => $user,
|
||||
'CONFIRM' => $url
|
||||
];
|
||||
|
||||
$mail = new \Mailer();
|
||||
$mail->to($userinfo['name'] . ' <' . $userinfo['mail'] . '>');
|
||||
$mail->subject($lang['regpwmail']);
|
||||
$mail->setBody($text, $trep);
|
||||
if ($mail->send()) {
|
||||
msg($lang['resendpwdconfirm'], 1);
|
||||
} else {
|
||||
msg($lang['regmailfail'], -1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// never reached
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
|
||||
/**
|
||||
* Class Revert
|
||||
*
|
||||
* Quick revert to an old revision
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Revert extends AbstractUserAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_EDIT;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @inheritdoc
|
||||
* @throws ActionAbort
|
||||
* @throws ActionException
|
||||
* @todo check for writability of the current page ($INFO might do it wrong and check the attic version)
|
||||
*/
|
||||
public function preProcess()
|
||||
{
|
||||
if (!checkSecurityToken()) throw new ActionException();
|
||||
|
||||
global $ID;
|
||||
global $REV;
|
||||
global $lang;
|
||||
|
||||
// when no revision is given, delete current one
|
||||
// FIXME this feature is not exposed in the GUI currently
|
||||
$text = '';
|
||||
$sum = $lang['deleted'];
|
||||
if ($REV) {
|
||||
$text = rawWiki($ID, $REV);
|
||||
if (!$text) throw new ActionException(); //something went wrong
|
||||
$sum = sprintf($lang['restored'], dformat($REV));
|
||||
}
|
||||
|
||||
// spam check
|
||||
if (checkwordblock($text)) {
|
||||
msg($lang['wordblock'], -1);
|
||||
throw new ActionException('edit');
|
||||
}
|
||||
|
||||
saveWikiText($ID, $text, $sum, false);
|
||||
msg($sum, 1);
|
||||
$REV = '';
|
||||
|
||||
// continue with draftdel -> redirect -> show
|
||||
throw new ActionAbort('draftdel');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\PageRevisions;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Revisions
|
||||
*
|
||||
* Show the list of old revisions of the current page
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Revisions extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
global $INFO, $INPUT;
|
||||
(new PageRevisions($INFO['id']))->show($INPUT->int('first', -1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
|
||||
/**
|
||||
* Class Save
|
||||
*
|
||||
* Save at the end of an edit session
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Save extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
global $INFO;
|
||||
if ($INFO['exists']) {
|
||||
return AUTH_EDIT;
|
||||
} else {
|
||||
return AUTH_CREATE;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
if (!checkSecurityToken()) throw new ActionException('preview');
|
||||
|
||||
global $ID;
|
||||
global $DATE;
|
||||
global $PRE;
|
||||
global $TEXT;
|
||||
global $SUF;
|
||||
global $SUM;
|
||||
global $lang;
|
||||
global $INFO;
|
||||
global $INPUT;
|
||||
|
||||
//spam check
|
||||
if (checkwordblock()) {
|
||||
msg($lang['wordblock'], -1);
|
||||
throw new ActionException('edit');
|
||||
}
|
||||
//conflict check
|
||||
if (
|
||||
$DATE != 0
|
||||
&& isset($INFO['meta']['date']['modified'])
|
||||
&& $INFO['meta']['date']['modified'] > $DATE
|
||||
) {
|
||||
throw new ActionException('conflict');
|
||||
}
|
||||
|
||||
//save it
|
||||
saveWikiText($ID, con($PRE, $TEXT, $SUF, true), $SUM, $INPUT->bool('minor')); //use pretty mode for con
|
||||
//unlock it
|
||||
unlock($ID);
|
||||
|
||||
// continue with draftdel -> redirect -> show
|
||||
throw new ActionAbort('draftdel');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
|
||||
/**
|
||||
* Class Search
|
||||
*
|
||||
* Search for pages and content
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Search extends AbstractAction
|
||||
{
|
||||
protected $pageLookupResults = [];
|
||||
protected $fullTextResults = [];
|
||||
protected $highlight = [];
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* we only search if a search word was given
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function checkPreconditions()
|
||||
{
|
||||
parent::checkPreconditions();
|
||||
}
|
||||
|
||||
public function preProcess()
|
||||
{
|
||||
global $QUERY, $ID, $conf, $INPUT;
|
||||
$s = cleanID($QUERY);
|
||||
|
||||
if ($ID !== $conf['start'] && !$INPUT->has('q')) {
|
||||
parse_str($INPUT->server->str('QUERY_STRING'), $urlParts);
|
||||
$urlParts['q'] = $urlParts['id'];
|
||||
unset($urlParts['id']);
|
||||
$url = wl($ID, $urlParts, true, '&');
|
||||
send_redirect($url);
|
||||
}
|
||||
|
||||
if ($s === '') throw new ActionAbort();
|
||||
$this->adjustGlobalQuery();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
$this->execute();
|
||||
|
||||
$search = new \dokuwiki\Ui\Search($this->pageLookupResults, $this->fullTextResults, $this->highlight);
|
||||
$search->show();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* run the search
|
||||
*/
|
||||
protected function execute()
|
||||
{
|
||||
global $INPUT, $QUERY;
|
||||
$after = $INPUT->str('min');
|
||||
$before = $INPUT->str('max');
|
||||
$this->pageLookupResults = ft_pageLookup($QUERY, true, useHeading('navigation'), $after, $before);
|
||||
$this->fullTextResults = ft_pageSearch($QUERY, $highlight, $INPUT->str('srt'), $after, $before);
|
||||
$this->highlight = $highlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the global query accordingly to the config search_nslimit and search_fragment
|
||||
*
|
||||
* This will only do something if the search didn't originate from the form on the searchpage itself
|
||||
*/
|
||||
protected function adjustGlobalQuery()
|
||||
{
|
||||
global $conf, $INPUT, $QUERY, $ID;
|
||||
|
||||
if ($INPUT->bool('sf')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$Indexer = idx_get_indexer();
|
||||
$parsedQuery = ft_queryParser($Indexer, $QUERY);
|
||||
|
||||
if (empty($parsedQuery['ns']) && empty($parsedQuery['notns'])) {
|
||||
if ($conf['search_nslimit'] > 0) {
|
||||
if (getNS($ID) !== false) {
|
||||
$nsParts = explode(':', getNS($ID));
|
||||
$ns = implode(':', array_slice($nsParts, 0, $conf['search_nslimit']));
|
||||
$QUERY .= " @$ns";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($conf['search_fragment'] !== 'exact') {
|
||||
if (empty(array_diff($parsedQuery['words'], $parsedQuery['and']))) {
|
||||
if (strpos($QUERY, '*') === false) {
|
||||
$queryParts = explode(' ', $QUERY);
|
||||
$queryParts = array_map(function ($part) {
|
||||
if (strpos($part, '@') === 0) {
|
||||
return $part;
|
||||
}
|
||||
if (strpos($part, 'ns:') === 0) {
|
||||
return $part;
|
||||
}
|
||||
if (strpos($part, '^') === 0) {
|
||||
return $part;
|
||||
}
|
||||
if (strpos($part, '-ns:') === 0) {
|
||||
return $part;
|
||||
}
|
||||
|
||||
global $conf;
|
||||
|
||||
if ($conf['search_fragment'] === 'starts_with') {
|
||||
return $part . '*';
|
||||
}
|
||||
if ($conf['search_fragment'] === 'ends_with') {
|
||||
return '*' . $part;
|
||||
}
|
||||
|
||||
return '*' . $part . '*';
|
||||
}, $queryParts);
|
||||
$QUERY = implode(' ', $queryParts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Created by IntelliJ IDEA.
|
||||
* User: andi
|
||||
* Date: 2/10/17
|
||||
* Time: 4:32 PM
|
||||
*/
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\PageView;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Show
|
||||
*
|
||||
* The default action of showing a page
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Show extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
global $ID;
|
||||
unlock($ID);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
(new PageView())->show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\FatalException;
|
||||
use dokuwiki\Sitemap\Mapper;
|
||||
use dokuwiki\Utf8\PhpString;
|
||||
|
||||
/**
|
||||
* Class Sitemap
|
||||
*
|
||||
* Generate an XML sitemap for search engines. Do not confuse with Index
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Sitemap extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle sitemap delivery
|
||||
*
|
||||
* @author Michael Hamann <michael@content-space.de>
|
||||
* @throws FatalException
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function preProcess()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if ($conf['sitemap'] < 1 || !is_numeric($conf['sitemap'])) {
|
||||
throw new FatalException('Sitemap generation is disabled', 404);
|
||||
}
|
||||
|
||||
$sitemap = Mapper::getFilePath();
|
||||
if (Mapper::sitemapIsCompressed()) {
|
||||
$mime = 'application/x-gzip';
|
||||
} else {
|
||||
$mime = 'application/xml; charset=utf-8';
|
||||
}
|
||||
|
||||
// Check if sitemap file exists, otherwise create it
|
||||
if (!is_readable($sitemap)) {
|
||||
Mapper::generate();
|
||||
}
|
||||
|
||||
if (is_readable($sitemap)) {
|
||||
// Send headers
|
||||
header('Content-Type: ' . $mime);
|
||||
header('Content-Disposition: attachment; filename=' . PhpString::basename($sitemap));
|
||||
|
||||
http_conditionalRequest(filemtime($sitemap));
|
||||
|
||||
// Send file
|
||||
//use x-sendfile header to pass the delivery to compatible webservers
|
||||
http_sendfile($sitemap);
|
||||
|
||||
readfile($sitemap);
|
||||
exit;
|
||||
}
|
||||
|
||||
throw new FatalException('Could not read the sitemap file - bad permissions?');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Ui\Editor;
|
||||
use dokuwiki\Ui;
|
||||
|
||||
/**
|
||||
* Class Source
|
||||
*
|
||||
* Show the source of a page
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Source extends AbstractAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
global $TEXT;
|
||||
global $INFO;
|
||||
global $ID;
|
||||
global $REV;
|
||||
|
||||
if ($INFO['exists']) {
|
||||
$TEXT = rawWiki($ID, $REV);
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
(new Editor())->show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
use dokuwiki\Subscriptions\SubscriberManager;
|
||||
use dokuwiki\Extension\Event;
|
||||
use dokuwiki\Ui;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class Subscribe
|
||||
*
|
||||
* E-Mail subscription handling
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Subscribe extends AbstractUserAction
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission()
|
||||
{
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions()
|
||||
{
|
||||
parent::checkPreconditions();
|
||||
|
||||
global $conf;
|
||||
if (isset($conf['subscribers']) && !$conf['subscribers']) throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess()
|
||||
{
|
||||
try {
|
||||
$this->handleSubscribeData();
|
||||
} catch (ActionAbort $e) {
|
||||
throw $e;
|
||||
} catch (Exception $e) {
|
||||
msg($e->getMessage(), -1);
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
(new Ui\Subscribe())->show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle page 'subscribe'
|
||||
*
|
||||
* @author Adrian Lang <lang@cosmocode.de>
|
||||
* @throws Exception if (un)subscribing fails
|
||||
* @throws ActionAbort when (un)subscribing worked
|
||||
*/
|
||||
protected function handleSubscribeData()
|
||||
{
|
||||
global $lang;
|
||||
global $INFO;
|
||||
global $INPUT;
|
||||
|
||||
// get and preprocess data.
|
||||
$params = [];
|
||||
foreach (['target', 'style', 'action'] as $param) {
|
||||
if ($INPUT->has("sub_$param")) {
|
||||
$params[$param] = $INPUT->str("sub_$param");
|
||||
}
|
||||
}
|
||||
|
||||
// any action given? if not just return and show the subscription page
|
||||
if (empty($params['action']) || !checkSecurityToken()) return;
|
||||
|
||||
// Handle POST data, may throw exception.
|
||||
Event::createAndTrigger('ACTION_HANDLE_SUBSCRIBE', $params, [$this, 'handlePostData']);
|
||||
|
||||
$target = $params['target'];
|
||||
$style = $params['style'];
|
||||
$action = $params['action'];
|
||||
|
||||
// Perform action.
|
||||
$subManager = new SubscriberManager();
|
||||
if ($action === 'unsubscribe') {
|
||||
$ok = $subManager->remove($target, $INPUT->server->str('REMOTE_USER'), $style);
|
||||
} else {
|
||||
$ok = $subManager->add($target, $INPUT->server->str('REMOTE_USER'), $style);
|
||||
}
|
||||
|
||||
if ($ok) {
|
||||
msg(
|
||||
sprintf(
|
||||
$lang["subscr_{$action}_success"],
|
||||
hsc($INFO['userinfo']['name']),
|
||||
prettyprint_id($target)
|
||||
),
|
||||
1
|
||||
);
|
||||
throw new ActionAbort('redirect');
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
$lang["subscr_{$action}_error"],
|
||||
hsc($INFO['userinfo']['name']),
|
||||
prettyprint_id($target)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate POST data
|
||||
*
|
||||
* Validates POST data for a subscribe or unsubscribe request. This is the
|
||||
* default action for the event ACTION_HANDLE_SUBSCRIBE.
|
||||
*
|
||||
* @author Adrian Lang <lang@cosmocode.de>
|
||||
*
|
||||
* @param array &$params the parameters: target, style and action
|
||||
* @throws Exception
|
||||
*/
|
||||
public function handlePostData(&$params)
|
||||
{
|
||||
global $INFO;
|
||||
global $lang;
|
||||
global $INPUT;
|
||||
|
||||
// Get and validate parameters.
|
||||
if (!isset($params['target'])) {
|
||||
throw new Exception('no subscription target given');
|
||||
}
|
||||
$target = $params['target'];
|
||||
$valid_styles = ['every', 'digest'];
|
||||
if (str_ends_with($target, ':')) {
|
||||
// Allow “list” subscribe style since the target is a namespace.
|
||||
$valid_styles[] = 'list';
|
||||
}
|
||||
$style = valid_input_set(
|
||||
'style',
|
||||
$valid_styles,
|
||||
$params,
|
||||
'invalid subscription style given'
|
||||
);
|
||||
$action = valid_input_set(
|
||||
'action',
|
||||
['subscribe', 'unsubscribe'],
|
||||
$params,
|
||||
'invalid subscription action given'
|
||||
);
|
||||
|
||||
// Check other conditions.
|
||||
if ($action === 'subscribe') {
|
||||
if ($INFO['userinfo']['mail'] === '') {
|
||||
throw new Exception($lang['subscr_subscribe_noaddress']);
|
||||
}
|
||||
} elseif ($action === 'unsubscribe') {
|
||||
$is = false;
|
||||
foreach ($INFO['subscribed'] as $subscr) {
|
||||
if ($subscr['target'] === $target) {
|
||||
$is = true;
|
||||
}
|
||||
}
|
||||
if ($is === false) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
$lang['subscr_not_subscribed'],
|
||||
$INPUT->server->str('REMOTE_USER'),
|
||||
prettyprint_id($target)
|
||||
)
|
||||
);
|
||||
}
|
||||
// subscription_set deletes a subscription if style = null.
|
||||
$style = null;
|
||||
}
|
||||
|
||||
$params = ['target' => $target, 'style' => $style, 'action' => $action];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki;
|
||||
|
||||
use dokuwiki\Extension\Event;
|
||||
use dokuwiki\Action\AbstractAction;
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
use dokuwiki\Action\Exception\FatalException;
|
||||
use dokuwiki\Action\Exception\NoActionException;
|
||||
use dokuwiki\Action\Plugin;
|
||||
|
||||
/**
|
||||
* Class ActionRouter
|
||||
* @package dokuwiki
|
||||
*/
|
||||
class ActionRouter
|
||||
{
|
||||
/** @var AbstractAction */
|
||||
protected $action;
|
||||
|
||||
/** @var ActionRouter */
|
||||
protected static $instance;
|
||||
|
||||
/** @var int transition counter */
|
||||
protected $transitions = 0;
|
||||
|
||||
/** maximum loop */
|
||||
protected const MAX_TRANSITIONS = 5;
|
||||
|
||||
/** @var string[] the actions disabled in the configuration */
|
||||
protected $disabled;
|
||||
|
||||
/**
|
||||
* ActionRouter constructor. Singleton, thus protected!
|
||||
*
|
||||
* Sets up the correct action based on the $ACT global. Writes back
|
||||
* the selected action to $ACT
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
global $ACT;
|
||||
global $conf;
|
||||
|
||||
$this->disabled = explode(',', $conf['disableactions']);
|
||||
$this->disabled = array_map('trim', $this->disabled);
|
||||
|
||||
$ACT = act_clean($ACT);
|
||||
$this->setupAction($ACT);
|
||||
$ACT = $this->action->getActionName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance
|
||||
*
|
||||
* @param bool $reinit
|
||||
* @return ActionRouter
|
||||
*/
|
||||
public static function getInstance($reinit = false)
|
||||
{
|
||||
if ((!self::$instance instanceof \dokuwiki\ActionRouter) || $reinit) {
|
||||
self::$instance = new ActionRouter();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the given action
|
||||
*
|
||||
* Instantiates the right class, runs permission checks and pre-processing and
|
||||
* sets $action
|
||||
*
|
||||
* @param string $actionname this is passed as a reference to $ACT, for plugin backward compatibility
|
||||
* @triggers ACTION_ACT_PREPROCESS
|
||||
*/
|
||||
protected function setupAction(&$actionname)
|
||||
{
|
||||
$presetup = $actionname;
|
||||
|
||||
try {
|
||||
// give plugins an opportunity to process the actionname
|
||||
$evt = new Event('ACTION_ACT_PREPROCESS', $actionname);
|
||||
if ($evt->advise_before()) {
|
||||
$this->action = $this->loadAction($actionname);
|
||||
$this->checkAction($this->action);
|
||||
$this->action->preProcess();
|
||||
} else {
|
||||
// event said the action should be kept, assume action plugin will handle it later
|
||||
$this->action = new Plugin($actionname);
|
||||
}
|
||||
$evt->advise_after();
|
||||
} catch (ActionException $e) {
|
||||
// we should have gotten a new action
|
||||
$actionname = $e->getNewAction();
|
||||
|
||||
// this one should trigger a user message
|
||||
if ($e instanceof ActionDisabledException) {
|
||||
msg('Action disabled: ' . hsc($presetup), -1);
|
||||
}
|
||||
|
||||
// some actions may request the display of a message
|
||||
if ($e->displayToUser()) {
|
||||
msg(hsc($e->getMessage()), -1);
|
||||
}
|
||||
|
||||
// do setup for new action
|
||||
$this->transitionAction($presetup, $actionname);
|
||||
} catch (NoActionException $e) {
|
||||
msg('Action unknown: ' . hsc($actionname), -1);
|
||||
$actionname = 'show';
|
||||
$this->transitionAction($presetup, $actionname);
|
||||
} catch (\Exception $e) {
|
||||
$this->handleFatalException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transitions from one action to another
|
||||
*
|
||||
* Basically just calls setupAction() again but does some checks before.
|
||||
*
|
||||
* @param string $from current action name
|
||||
* @param string $to new action name
|
||||
* @param null|ActionException $e any previous exception that caused the transition
|
||||
*/
|
||||
protected function transitionAction($from, $to, $e = null)
|
||||
{
|
||||
$this->transitions++;
|
||||
|
||||
// no infinite recursion
|
||||
if ($from == $to) {
|
||||
$this->handleFatalException(new FatalException('Infinite loop in actions', 500, $e));
|
||||
}
|
||||
|
||||
// larger loops will be caught here
|
||||
if ($this->transitions >= self::MAX_TRANSITIONS) {
|
||||
$this->handleFatalException(new FatalException('Maximum action transitions reached', 500, $e));
|
||||
}
|
||||
|
||||
// do the recursion
|
||||
$this->setupAction($to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts all processing with a message
|
||||
*
|
||||
* When a FataException instanc is passed, the code is treated as Status code
|
||||
*
|
||||
* @param \Exception|FatalException $e
|
||||
* @throws FatalException during unit testing
|
||||
*/
|
||||
protected function handleFatalException(\Throwable $e)
|
||||
{
|
||||
if ($e instanceof FatalException) {
|
||||
http_status($e->getCode());
|
||||
} else {
|
||||
http_status(500);
|
||||
}
|
||||
if (defined('DOKU_UNITTEST')) {
|
||||
throw $e;
|
||||
}
|
||||
ErrorHandler::logException($e);
|
||||
$msg = 'Something unforeseen has happened: ' . $e->getMessage();
|
||||
nice_die(hsc($msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the given action
|
||||
*
|
||||
* This translates the given name to a class name by uppercasing the first letter.
|
||||
* Underscores translate to camelcase names. For actions with underscores, the different
|
||||
* parts are removed beginning from the end until a matching class is found. The instatiated
|
||||
* Action will always have the full original action set as Name
|
||||
*
|
||||
* Example: 'export_raw' -> ExportRaw then 'export' -> 'Export'
|
||||
*
|
||||
* @param $actionname
|
||||
* @return AbstractAction
|
||||
* @throws NoActionException
|
||||
*/
|
||||
public function loadAction($actionname)
|
||||
{
|
||||
$actionname = strtolower($actionname); // FIXME is this needed here? should we run a cleanup somewhere else?
|
||||
$parts = explode('_', $actionname);
|
||||
while ($parts !== []) {
|
||||
$load = implode('_', $parts);
|
||||
$class = 'dokuwiki\\Action\\' . str_replace('_', '', ucwords($load, '_'));
|
||||
if (class_exists($class)) {
|
||||
return new $class($actionname);
|
||||
}
|
||||
array_pop($parts);
|
||||
}
|
||||
|
||||
throw new NoActionException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the checks to see if this action can be executed
|
||||
*
|
||||
* @param AbstractAction $action
|
||||
* @throws ActionDisabledException
|
||||
* @throws ActionException
|
||||
*/
|
||||
public function checkAction(AbstractAction $action)
|
||||
{
|
||||
global $INFO;
|
||||
global $ID;
|
||||
|
||||
if (in_array($action->getActionName(), $this->disabled)) {
|
||||
throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
$action->checkPreconditions();
|
||||
|
||||
if (isset($INFO)) {
|
||||
$perm = $INFO['perm'];
|
||||
} else {
|
||||
$perm = auth_quickaclcheck($ID);
|
||||
}
|
||||
|
||||
if ($perm < $action->minimumPermission()) {
|
||||
throw new ActionException('denied');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action handling the current request
|
||||
*
|
||||
* @return AbstractAction
|
||||
*/
|
||||
public function getAction()
|
||||
{
|
||||
return $this->action;
|
||||
}
|
||||
}
|
||||
+447
@@ -0,0 +1,447 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki;
|
||||
|
||||
use dokuwiki\Extension\Event;
|
||||
use dokuwiki\Ui\MediaDiff;
|
||||
use dokuwiki\Ui\Index;
|
||||
use dokuwiki\Ui;
|
||||
use dokuwiki\Utf8\Sort;
|
||||
|
||||
/**
|
||||
* Manage all builtin AJAX calls
|
||||
*
|
||||
* @todo The calls should be refactored out to their own proper classes
|
||||
* @package dokuwiki
|
||||
*/
|
||||
class Ajax
|
||||
{
|
||||
/**
|
||||
* Execute the given call
|
||||
*
|
||||
* @param string $call name of the ajax call
|
||||
*/
|
||||
public function __construct($call)
|
||||
{
|
||||
$callfn = 'call' . ucfirst($call);
|
||||
if (method_exists($this, $callfn)) {
|
||||
$this->$callfn();
|
||||
} else {
|
||||
$evt = new Event('AJAX_CALL_UNKNOWN', $call);
|
||||
if ($evt->advise_before()) {
|
||||
echo "AJAX call '" . hsc($call) . "' unknown!\n";
|
||||
} else {
|
||||
$evt->advise_after();
|
||||
unset($evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for matching pagenames
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
protected function callQsearch()
|
||||
{
|
||||
global $lang;
|
||||
global $INPUT;
|
||||
|
||||
$maxnumbersuggestions = 50;
|
||||
|
||||
$query = $INPUT->post->str('q');
|
||||
if (empty($query)) $query = $INPUT->get->str('q');
|
||||
if (empty($query)) return;
|
||||
|
||||
$query = urldecode($query);
|
||||
|
||||
$data = ft_pageLookup($query, true, useHeading('navigation'));
|
||||
|
||||
if ($data === []) return;
|
||||
|
||||
echo '<strong>' . $lang['quickhits'] . '</strong>';
|
||||
echo '<ul>';
|
||||
$counter = 0;
|
||||
foreach ($data as $id => $title) {
|
||||
if (useHeading('navigation')) {
|
||||
$name = $title;
|
||||
} else {
|
||||
$ns = getNS($id);
|
||||
if ($ns) {
|
||||
$name = noNS($id) . ' (' . $ns . ')';
|
||||
} else {
|
||||
$name = $id;
|
||||
}
|
||||
}
|
||||
echo '<li>' . html_wikilink(':' . $id, $name) . '</li>';
|
||||
|
||||
$counter++;
|
||||
if ($counter > $maxnumbersuggestions) {
|
||||
echo '<li>...</li>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Support OpenSearch suggestions
|
||||
*
|
||||
* @link http://www.opensearch.org/Specifications/OpenSearch/Extensions/Suggestions/1.0
|
||||
* @author Mike Frysinger <vapier@gentoo.org>
|
||||
*/
|
||||
protected function callSuggestions()
|
||||
{
|
||||
global $INPUT;
|
||||
|
||||
$query = cleanID($INPUT->post->str('q'));
|
||||
if (empty($query)) $query = cleanID($INPUT->get->str('q'));
|
||||
if (empty($query)) return;
|
||||
|
||||
$data = ft_pageLookup($query);
|
||||
if ($data === []) return;
|
||||
$data = array_keys($data);
|
||||
|
||||
// limit results to 15 hits
|
||||
$data = array_slice($data, 0, 15);
|
||||
$data = array_map('trim', $data);
|
||||
$data = array_map('noNS', $data);
|
||||
$data = array_unique($data);
|
||||
Sort::sort($data);
|
||||
|
||||
/* now construct a json */
|
||||
$suggestions = [
|
||||
$query, // the original query
|
||||
$data, // some suggestions
|
||||
[], // no description
|
||||
[], // no urls
|
||||
];
|
||||
|
||||
header('Content-Type: application/x-suggestions+json');
|
||||
echo json_encode($suggestions, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh a page lock and save draft
|
||||
*
|
||||
* Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
protected function callLock()
|
||||
{
|
||||
global $ID;
|
||||
global $INFO;
|
||||
global $INPUT;
|
||||
|
||||
$ID = cleanID($INPUT->post->str('id'));
|
||||
if (empty($ID)) return;
|
||||
|
||||
$INFO = pageinfo();
|
||||
|
||||
$response = [
|
||||
'errors' => [],
|
||||
'lock' => '0',
|
||||
'draft' => '',
|
||||
];
|
||||
if (!$INFO['writable']) {
|
||||
$response['errors'][] = 'Permission to write this page has been denied.';
|
||||
echo json_encode($response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checklock($ID)) {
|
||||
lock($ID);
|
||||
$response['lock'] = '1';
|
||||
}
|
||||
|
||||
$draft = new Draft($ID, $INFO['client']);
|
||||
if ($draft->saveDraft()) {
|
||||
$response['draft'] = $draft->getDraftMessage();
|
||||
} else {
|
||||
$response['errors'] = array_merge($response['errors'], $draft->getErrors());
|
||||
}
|
||||
echo json_encode($response, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a draft
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
protected function callDraftdel()
|
||||
{
|
||||
global $INPUT;
|
||||
$id = cleanID($INPUT->str('id'));
|
||||
if (empty($id)) return;
|
||||
|
||||
$client = $INPUT->server->str('REMOTE_USER');
|
||||
if (!$client) $client = clientIP(true);
|
||||
|
||||
$draft = new Draft($id, $client);
|
||||
if ($draft->isDraftAvailable() && checkSecurityToken()) {
|
||||
$draft->deleteDraft();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return subnamespaces for the Mediamanager
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
protected function callMedians()
|
||||
{
|
||||
global $conf;
|
||||
global $INPUT;
|
||||
|
||||
// wanted namespace
|
||||
$ns = cleanID($INPUT->post->str('ns'));
|
||||
$dir = utf8_encodeFN(str_replace(':', '/', $ns));
|
||||
|
||||
$lvl = count(explode(':', $ns));
|
||||
|
||||
$data = [];
|
||||
search($data, $conf['mediadir'], 'search_index', ['nofiles' => true], $dir);
|
||||
foreach (array_keys($data) as $item) {
|
||||
$data[$item]['level'] = $lvl + 1;
|
||||
}
|
||||
echo html_buildlist($data, 'idx', 'media_nstree_item', 'media_nstree_li');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of files for the Mediamanager
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
protected function callMedialist()
|
||||
{
|
||||
global $NS;
|
||||
global $INPUT;
|
||||
|
||||
$NS = cleanID($INPUT->post->str('ns'));
|
||||
$sort = $INPUT->post->bool('recent') ? 'date' : 'natural';
|
||||
if ($INPUT->post->str('do') == 'media') {
|
||||
tpl_mediaFileList();
|
||||
} else {
|
||||
tpl_mediaContent(true, $sort);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content of the right column
|
||||
* (image details) for the Mediamanager
|
||||
*
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
protected function callMediadetails()
|
||||
{
|
||||
global $IMG, $JUMPTO, $REV, $fullscreen, $INPUT;
|
||||
$fullscreen = true;
|
||||
require_once(DOKU_INC . 'lib/exe/mediamanager.php');
|
||||
|
||||
$image = '';
|
||||
if ($INPUT->has('image')) $image = cleanID($INPUT->str('image'));
|
||||
if (isset($IMG)) $image = $IMG;
|
||||
if (isset($JUMPTO)) $image = $JUMPTO;
|
||||
$rev = false;
|
||||
if (isset($REV) && !$JUMPTO) $rev = $REV;
|
||||
|
||||
html_msgarea();
|
||||
tpl_mediaFileDetails($image, $rev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns image diff representation for mediamanager
|
||||
*
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
protected function callMediadiff()
|
||||
{
|
||||
global $INPUT;
|
||||
|
||||
$image = '';
|
||||
if ($INPUT->has('image')) $image = cleanID($INPUT->str('image'));
|
||||
(new MediaDiff($image))->preference('fromAjax', true)->show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages file uploads
|
||||
*
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
protected function callMediaupload()
|
||||
{
|
||||
global $NS, $MSG, $INPUT;
|
||||
|
||||
$id = '';
|
||||
if (isset($_FILES['qqfile']['tmp_name'])) {
|
||||
$id = $INPUT->post->str('mediaid', $_FILES['qqfile']['name']);
|
||||
} elseif ($INPUT->get->has('qqfile')) {
|
||||
$id = $INPUT->get->str('qqfile');
|
||||
}
|
||||
|
||||
$id = cleanID($id);
|
||||
|
||||
$NS = $INPUT->str('ns');
|
||||
$ns = $NS . ':' . getNS($id);
|
||||
|
||||
$AUTH = auth_quickaclcheck("$ns:*");
|
||||
if ($AUTH >= AUTH_UPLOAD) {
|
||||
io_createNamespace("$ns:xxx", 'media');
|
||||
}
|
||||
|
||||
if (isset($_FILES['qqfile']['error']) && $_FILES['qqfile']['error']) unset($_FILES['qqfile']);
|
||||
|
||||
$res = false;
|
||||
if (isset($_FILES['qqfile']['tmp_name'])) $res = media_upload($NS, $AUTH, $_FILES['qqfile']);
|
||||
if ($INPUT->get->has('qqfile')) $res = media_upload_xhr($NS, $AUTH);
|
||||
|
||||
if ($res) {
|
||||
$result = [
|
||||
'success' => true,
|
||||
'link' => media_managerURL(['ns' => $ns, 'image' => $NS . ':' . $id], '&'),
|
||||
'id' => $NS . ':' . $id,
|
||||
'ns' => $NS
|
||||
];
|
||||
} else {
|
||||
$error = '';
|
||||
if (isset($MSG)) {
|
||||
foreach ($MSG as $msg) {
|
||||
$error .= $msg['msg'];
|
||||
}
|
||||
}
|
||||
$result = ['error' => $error, 'ns' => $NS];
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($result, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return sub index for index view
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
protected function callIndex()
|
||||
{
|
||||
global $conf;
|
||||
global $INPUT;
|
||||
|
||||
// wanted namespace
|
||||
$ns = cleanID($INPUT->post->str('idx'));
|
||||
$dir = utf8_encodeFN(str_replace(':', '/', $ns));
|
||||
|
||||
$lvl = count(explode(':', $ns));
|
||||
|
||||
$data = [];
|
||||
search($data, $conf['datadir'], 'search_index', ['ns' => $ns], $dir);
|
||||
foreach (array_keys($data) as $item) {
|
||||
$data[$item]['level'] = $lvl + 1;
|
||||
}
|
||||
$idx = new Index();
|
||||
echo html_buildlist($data, 'idx', [$idx,'formatListItem'], [$idx,'tagListItem']);
|
||||
}
|
||||
|
||||
/**
|
||||
* List matching namespaces and pages for the link wizard
|
||||
*
|
||||
* @author Andreas Gohr <gohr@cosmocode.de>
|
||||
*/
|
||||
protected function callLinkwiz()
|
||||
{
|
||||
global $conf;
|
||||
global $lang;
|
||||
global $INPUT;
|
||||
|
||||
$q = ltrim(trim($INPUT->post->str('q')), ':');
|
||||
$id = noNS($q);
|
||||
$ns = getNS($q);
|
||||
|
||||
$ns = cleanID($ns);
|
||||
|
||||
$id = cleanID($id);
|
||||
|
||||
$nsd = utf8_encodeFN(str_replace(':', '/', $ns));
|
||||
|
||||
$data = [];
|
||||
if ($q !== '' && $ns === '') {
|
||||
// use index to lookup matching pages
|
||||
$pages = ft_pageLookup($id, true);
|
||||
|
||||
// If 'useheading' option is 'always' or 'content',
|
||||
// search page titles with original query as well.
|
||||
if ($conf['useheading'] == '1' || $conf['useheading'] == 'content') {
|
||||
$pages = array_merge($pages, ft_pageLookup($q, true, true));
|
||||
asort($pages, SORT_STRING);
|
||||
}
|
||||
|
||||
// result contains matches in pages and namespaces
|
||||
// we now extract the matching namespaces to show
|
||||
// them seperately
|
||||
$dirs = [];
|
||||
|
||||
foreach ($pages as $pid => $title) {
|
||||
if (strpos(getNS($pid), $id) !== false) {
|
||||
// match was in the namespace
|
||||
$dirs[getNS($pid)] = 1; // assoc array avoids dupes
|
||||
} else {
|
||||
// it is a matching page, add it to the result
|
||||
$data[] = ['id' => $pid, 'title' => $title, 'type' => 'f'];
|
||||
}
|
||||
unset($pages[$pid]);
|
||||
}
|
||||
foreach (array_keys($dirs) as $dir) {
|
||||
$data[] = ['id' => $dir, 'type' => 'd'];
|
||||
}
|
||||
} else {
|
||||
$opts = [
|
||||
'depth' => 1,
|
||||
'listfiles' => true,
|
||||
'listdirs' => true,
|
||||
'pagesonly' => true,
|
||||
'firsthead' => true,
|
||||
'sneakyacl' => $conf['sneaky_index']
|
||||
];
|
||||
if ($id) $opts['filematch'] = '^.*\/' . $id;
|
||||
if ($id) $opts['dirmatch'] = '^.*\/' . $id;
|
||||
search($data, $conf['datadir'], 'search_universal', $opts, $nsd);
|
||||
|
||||
// add back to upper
|
||||
if ($ns) {
|
||||
array_unshift(
|
||||
$data,
|
||||
['id' => getNS($ns), 'type' => 'u']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// fixme sort results in a useful way ?
|
||||
|
||||
if (!count($data)) {
|
||||
echo $lang['nothingfound'];
|
||||
exit;
|
||||
}
|
||||
|
||||
// output the found data
|
||||
$even = 1;
|
||||
foreach ($data as $item) {
|
||||
$even *= -1; //zebra
|
||||
|
||||
if (($item['type'] == 'd' || $item['type'] == 'u') && $item['id'] !== '') $item['id'] .= ':';
|
||||
$link = wl($item['id']);
|
||||
|
||||
echo '<div class="' . (($even > 0) ? 'even' : 'odd') . ' type_' . $item['type'] . '">';
|
||||
|
||||
if ($item['type'] == 'u') {
|
||||
$name = $lang['upperns'];
|
||||
} else {
|
||||
$name = hsc($item['id']);
|
||||
}
|
||||
|
||||
echo '<a href="' . $link . '" title="' . hsc($item['id']) . '" class="wikilink1">' . $name . '</a>';
|
||||
|
||||
if (!blank($item['title'])) {
|
||||
echo '<span>' . hsc($item['title']) . '</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Cache;
|
||||
|
||||
use dokuwiki\Debug\PropertyDeprecationHelper;
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
/**
|
||||
* Generic handling of caching
|
||||
*/
|
||||
class Cache
|
||||
{
|
||||
use PropertyDeprecationHelper;
|
||||
|
||||
public $key = ''; // primary identifier for this item
|
||||
public $ext = ''; // file ext for cache data, secondary identifier for this item
|
||||
public $cache = ''; // cache file name
|
||||
public $depends = []; // array containing cache dependency information,
|
||||
// used by makeDefaultCacheDecision to determine cache validity
|
||||
|
||||
// phpcs:disable
|
||||
/**
|
||||
* @deprecated since 2019-02-02 use the respective getters instead!
|
||||
*/
|
||||
protected $_event = ''; // event to be triggered during useCache
|
||||
protected $_time;
|
||||
protected $_nocache = false; // if set to true, cache will not be used or stored
|
||||
// phpcs:enable
|
||||
|
||||
/**
|
||||
* @param string $key primary identifier
|
||||
* @param string $ext file extension
|
||||
*/
|
||||
public function __construct($key, $ext)
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->ext = $ext;
|
||||
$this->cache = getCacheName($key, $ext);
|
||||
|
||||
/**
|
||||
* @deprecated since 2019-02-02 use the respective getters instead!
|
||||
*/
|
||||
$this->deprecatePublicProperty('_event');
|
||||
$this->deprecatePublicProperty('_time');
|
||||
$this->deprecatePublicProperty('_nocache');
|
||||
}
|
||||
|
||||
public function getTime()
|
||||
{
|
||||
return $this->_time;
|
||||
}
|
||||
|
||||
public function getEvent()
|
||||
{
|
||||
return $this->_event;
|
||||
}
|
||||
|
||||
public function setEvent($event)
|
||||
{
|
||||
$this->_event = $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* public method to determine whether the cache can be used
|
||||
*
|
||||
* to assist in centralisation of event triggering and calculation of cache statistics,
|
||||
* don't override this function override makeDefaultCacheDecision()
|
||||
*
|
||||
* @param array $depends array of cache dependencies, support dependecies:
|
||||
* 'age' => max age of the cache in seconds
|
||||
* 'files' => cache must be younger than mtime of each file
|
||||
* (nb. dependency passes if file doesn't exist)
|
||||
*
|
||||
* @return bool true if cache can be used, false otherwise
|
||||
*/
|
||||
public function useCache($depends = [])
|
||||
{
|
||||
$this->depends = $depends;
|
||||
$this->addDependencies();
|
||||
|
||||
if ($this->getEvent()) {
|
||||
return $this->stats(
|
||||
Event::createAndTrigger(
|
||||
$this->getEvent(),
|
||||
$this,
|
||||
[$this, 'makeDefaultCacheDecision']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->stats($this->makeDefaultCacheDecision());
|
||||
}
|
||||
|
||||
/**
|
||||
* internal method containing cache use decision logic
|
||||
*
|
||||
* this function processes the following keys in the depends array
|
||||
* purge - force a purge on any non empty value
|
||||
* age - expire cache if older than age (seconds)
|
||||
* files - expire cache if any file in this array was updated more recently than the cache
|
||||
*
|
||||
* Note that this function needs to be public as it is used as callback for the event handler
|
||||
*
|
||||
* can be overridden
|
||||
*
|
||||
* @internal This method may only be called by the event handler! Call \dokuwiki\Cache\Cache::useCache instead!
|
||||
*
|
||||
* @return bool see useCache()
|
||||
*/
|
||||
public function makeDefaultCacheDecision()
|
||||
{
|
||||
if ($this->_nocache) {
|
||||
return false;
|
||||
} // caching turned off
|
||||
if (!empty($this->depends['purge'])) {
|
||||
return false;
|
||||
} // purge requested?
|
||||
if (!($this->_time = @filemtime($this->cache))) {
|
||||
return false;
|
||||
} // cache exists?
|
||||
|
||||
// cache too old?
|
||||
if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($this->depends['files'])) {
|
||||
foreach ($this->depends['files'] as $file) {
|
||||
if ($this->_time <= @filemtime($file)) {
|
||||
return false;
|
||||
} // cache older than files it depends on?
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* add dependencies to the depends array
|
||||
*
|
||||
* this method should only add dependencies,
|
||||
* it should not remove any existing dependencies and
|
||||
* it should only overwrite a dependency when the new value is more stringent than the old
|
||||
*/
|
||||
protected function addDependencies()
|
||||
{
|
||||
global $INPUT;
|
||||
if ($INPUT->has('purge')) {
|
||||
$this->depends['purge'] = true;
|
||||
} // purge requested
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve the cached data
|
||||
*
|
||||
* @param bool $clean true to clean line endings, false to leave line endings alone
|
||||
* @return string cache contents
|
||||
*/
|
||||
public function retrieveCache($clean = true)
|
||||
{
|
||||
return io_readFile($this->cache, $clean);
|
||||
}
|
||||
|
||||
/**
|
||||
* cache $data
|
||||
*
|
||||
* @param string $data the data to be cached
|
||||
* @return bool true on success, false otherwise
|
||||
*/
|
||||
public function storeCache($data)
|
||||
{
|
||||
if ($this->_nocache) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return io_saveFile($this->cache, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* remove any cached data associated with this cache instance
|
||||
*/
|
||||
public function removeCache()
|
||||
{
|
||||
@unlink($this->cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record cache hits statistics.
|
||||
* (Only when debugging allowed, to reduce overhead.)
|
||||
*
|
||||
* @param bool $success result of this cache use attempt
|
||||
* @return bool pass-thru $success value
|
||||
*/
|
||||
protected function stats($success)
|
||||
{
|
||||
global $conf;
|
||||
static $stats = null;
|
||||
static $file;
|
||||
|
||||
if (!$conf['allowdebug']) {
|
||||
return $success;
|
||||
}
|
||||
|
||||
if (is_null($stats)) {
|
||||
$file = $conf['cachedir'] . '/cache_stats.txt';
|
||||
$lines = explode("\n", io_readFile($file));
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$i = strpos($line, ',');
|
||||
$stats[substr($line, 0, $i)] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($stats[$this->ext])) {
|
||||
[$ext, $count, $hits] = explode(',', $stats[$this->ext]);
|
||||
} else {
|
||||
$ext = $this->ext;
|
||||
$count = 0;
|
||||
$hits = 0;
|
||||
}
|
||||
|
||||
$count++;
|
||||
if ($success) {
|
||||
$hits++;
|
||||
}
|
||||
$stats[$this->ext] = "$ext,$count,$hits";
|
||||
|
||||
io_saveFile($file, implode("\n", $stats));
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isNoCache()
|
||||
{
|
||||
return $this->_nocache;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Cache;
|
||||
|
||||
/**
|
||||
* Handle the caching of modified (resized/cropped) images
|
||||
*/
|
||||
class CacheImageMod extends Cache
|
||||
{
|
||||
/** @var string source file */
|
||||
protected $file;
|
||||
|
||||
/**
|
||||
* @param string $file Original source file
|
||||
* @param int $w new width in pixel
|
||||
* @param int $h new height in pixel
|
||||
* @param string $ext Image extension - no leading dot
|
||||
* @param bool $crop Is this a crop?
|
||||
*/
|
||||
public function __construct($file, $w, $h, $ext, $crop)
|
||||
{
|
||||
$fullext = '.media.' . $w . 'x' . $h;
|
||||
$fullext .= $crop ? '.crop' : '';
|
||||
$fullext .= ".$ext";
|
||||
|
||||
$this->file = $file;
|
||||
|
||||
$this->setEvent('IMAGEMOD_CACHE_USE');
|
||||
parent::__construct($file, $fullext);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function makeDefaultCacheDecision()
|
||||
{
|
||||
if (!file_exists($this->file)) {
|
||||
return false;
|
||||
}
|
||||
return parent::makeDefaultCacheDecision();
|
||||
}
|
||||
|
||||
/**
|
||||
* Caching depends on the source and the wiki config
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function addDependencies()
|
||||
{
|
||||
parent::addDependencies();
|
||||
|
||||
$this->depends['files'] = array_merge(
|
||||
[$this->file],
|
||||
getConfigFiles('main')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Cache;
|
||||
|
||||
/**
|
||||
* Caching of parser instructions
|
||||
*/
|
||||
class CacheInstructions extends CacheParser
|
||||
{
|
||||
/**
|
||||
* @param string $id page id
|
||||
* @param string $file source file for cache
|
||||
*/
|
||||
public function __construct($id, $file)
|
||||
{
|
||||
parent::__construct($id, $file, 'i');
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve the cached data
|
||||
*
|
||||
* @param bool $clean true to clean line endings, false to leave line endings alone
|
||||
* @return array cache contents
|
||||
*/
|
||||
public function retrieveCache($clean = true)
|
||||
{
|
||||
$contents = io_readFile($this->cache, false);
|
||||
return empty($contents) ? [] : unserialize($contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* cache $instructions
|
||||
*
|
||||
* @param array $instructions the instruction to be cached
|
||||
* @return bool true on success, false otherwise
|
||||
*/
|
||||
public function storeCache($instructions)
|
||||
{
|
||||
if ($this->_nocache) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return io_saveFile($this->cache, serialize($instructions));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Cache;
|
||||
|
||||
/**
|
||||
* Parser caching
|
||||
*/
|
||||
class CacheParser extends Cache
|
||||
{
|
||||
public $file = ''; // source file for cache
|
||||
public $mode = ''; // input mode (represents the processing the input file will undergo)
|
||||
public $page = '';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $id page id
|
||||
* @param string $file source file for cache
|
||||
* @param string $mode input mode
|
||||
*/
|
||||
public function __construct($id, $file, $mode)
|
||||
{
|
||||
global $INPUT;
|
||||
|
||||
if ($id) {
|
||||
$this->page = $id;
|
||||
}
|
||||
$this->file = $file;
|
||||
$this->mode = $mode;
|
||||
|
||||
$this->setEvent('PARSER_CACHE_USE');
|
||||
parent::__construct($file . $INPUT->server->str('HTTP_HOST') . $INPUT->server->str('SERVER_PORT'), '.' . $mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* method contains cache use decision logic
|
||||
*
|
||||
* @return bool see useCache()
|
||||
*/
|
||||
public function makeDefaultCacheDecision()
|
||||
{
|
||||
if (!file_exists($this->file)) {
|
||||
// source doesn't exist
|
||||
return false;
|
||||
}
|
||||
return parent::makeDefaultCacheDecision();
|
||||
}
|
||||
|
||||
protected function addDependencies()
|
||||
{
|
||||
// parser cache file dependencies ...
|
||||
$files = [
|
||||
$this->file, // source
|
||||
DOKU_INC . 'inc/Parsing/Parser.php', // parser
|
||||
DOKU_INC . 'inc/parser/handler.php', // handler
|
||||
];
|
||||
$files = array_merge($files, getConfigFiles('main')); // wiki settings
|
||||
|
||||
$this->depends['files'] = empty($this->depends['files']) ?
|
||||
$files :
|
||||
array_merge($files, $this->depends['files']);
|
||||
parent::addDependencies();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Cache;
|
||||
|
||||
/**
|
||||
* Caching of data of renderer
|
||||
*/
|
||||
class CacheRenderer extends CacheParser
|
||||
{
|
||||
/**
|
||||
* method contains cache use decision logic
|
||||
*
|
||||
* @return bool see useCache()
|
||||
*/
|
||||
public function makeDefaultCacheDecision()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (!parent::makeDefaultCacheDecision()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($this->page)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// meta cache older than file it depends on?
|
||||
if ($this->_time < @filemtime(metaFN($this->page, '.meta'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check current link existence is consistent with cache version
|
||||
// first check the purgefile
|
||||
// - if the cache is more recent than the purgefile we know no links can have been updated
|
||||
if ($this->_time >= @filemtime($conf['cachedir'] . '/purgefile')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// for wiki pages, check metadata dependencies
|
||||
$metadata = p_get_metadata($this->page);
|
||||
|
||||
if (
|
||||
!isset($metadata['relation']['references']) ||
|
||||
empty($metadata['relation']['references'])
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($metadata['relation']['references'] as $id => $exists) {
|
||||
if ($exists != page_exists($id, '', false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function addDependencies()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
// default renderer cache file 'age' is dependent on 'cachetime' setting, two special values:
|
||||
// -1 : do not cache (should not be overridden)
|
||||
// 0 : cache never expires (can be overridden) - no need to set depends['age']
|
||||
if ($conf['cachetime'] == -1) {
|
||||
$this->_nocache = true;
|
||||
return;
|
||||
} elseif ($conf['cachetime'] > 0) {
|
||||
$this->depends['age'] = isset($this->depends['age']) ?
|
||||
min($this->depends['age'], $conf['cachetime']) : $conf['cachetime'];
|
||||
}
|
||||
|
||||
// renderer cache file dependencies ...
|
||||
$files = [DOKU_INC . 'inc/parser/' . $this->mode . '.php'];
|
||||
|
||||
// page implies metadata and possibly some other dependencies
|
||||
if (isset($this->page)) {
|
||||
// for xhtml this will render the metadata if needed
|
||||
$valid = p_get_metadata($this->page, 'date valid');
|
||||
if (!empty($valid['age'])) {
|
||||
$this->depends['age'] = isset($this->depends['age']) ?
|
||||
min($this->depends['age'], $valid['age']) : $valid['age'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->depends['files'] = empty($this->depends['files']) ?
|
||||
$files :
|
||||
array_merge($files, $this->depends['files']);
|
||||
|
||||
parent::addDependencies();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,700 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\ChangeLog;
|
||||
|
||||
use dokuwiki\Logger;
|
||||
|
||||
/**
|
||||
* ChangeLog Prototype; methods for handling changelog
|
||||
*/
|
||||
abstract class ChangeLog
|
||||
{
|
||||
use ChangeLogTrait;
|
||||
|
||||
/** @var string */
|
||||
protected $id;
|
||||
/** @var false|int */
|
||||
protected $currentRevision;
|
||||
/** @var array */
|
||||
protected $cache = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $id page id
|
||||
* @param int $chunk_size maximum block size read from file
|
||||
*/
|
||||
public function __construct($id, $chunk_size = 8192)
|
||||
{
|
||||
global $cache_revinfo;
|
||||
|
||||
$this->cache =& $cache_revinfo;
|
||||
if (!isset($this->cache[$id])) {
|
||||
$this->cache[$id] = [];
|
||||
}
|
||||
|
||||
$this->id = $id;
|
||||
$this->setChunkSize($chunk_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to current page/media
|
||||
*
|
||||
* @param string|int $rev empty string or revision timestamp
|
||||
* @return string path to file
|
||||
*/
|
||||
abstract protected function getFilename($rev = '');
|
||||
|
||||
/**
|
||||
* Returns mode
|
||||
*
|
||||
* @return string RevisionInfo::MODE_MEDIA or RevisionInfo::MODE_PAGE
|
||||
*/
|
||||
abstract protected function getMode();
|
||||
|
||||
/**
|
||||
* Check whether given revision is the current page
|
||||
*
|
||||
* @param int $rev timestamp of current page
|
||||
* @return bool true if $rev is current revision, otherwise false
|
||||
*/
|
||||
public function isCurrentRevision($rev)
|
||||
{
|
||||
return $rev == $this->currentRevision();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the revision is last revision
|
||||
*
|
||||
* @param int $rev revision timestamp
|
||||
* @return bool true if $rev is last revision, otherwise false
|
||||
*/
|
||||
public function isLastRevision($rev = null)
|
||||
{
|
||||
return $rev === $this->lastRevision();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current revision identifier
|
||||
*
|
||||
* The "current" revision means current version of the page or media file. It is either
|
||||
* identical with or newer than the "last" revision, that depends on whether the file
|
||||
* has modified, created or deleted outside of DokuWiki.
|
||||
* The value of identifier can be determined by timestamp as far as the file exists,
|
||||
* otherwise it must be assigned larger than any other revisions to keep them sortable.
|
||||
*
|
||||
* @return int|false revision timestamp
|
||||
*/
|
||||
public function currentRevision()
|
||||
{
|
||||
if (!isset($this->currentRevision)) {
|
||||
// set ChangeLog::currentRevision property
|
||||
$this->getCurrentRevisionInfo();
|
||||
}
|
||||
return $this->currentRevision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last revision identifier, date value of the last entry of the changelog
|
||||
*
|
||||
* @return int|false revision timestamp
|
||||
*/
|
||||
public function lastRevision()
|
||||
{
|
||||
$revs = $this->getRevisions(-1, 1);
|
||||
return empty($revs) ? false : $revs[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a changelog line into its components and save revision info to the cache pool
|
||||
*
|
||||
* @param string $value changelog line
|
||||
* @return array|bool parsed line or false
|
||||
*/
|
||||
protected function parseAndCacheLogLine($value)
|
||||
{
|
||||
$info = static::parseLogLine($value);
|
||||
if (is_array($info)) {
|
||||
$info['mode'] = $this->getMode();
|
||||
$this->cache[$this->id][$info['date']] ??= $info;
|
||||
return $info;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the changelog information for a specific revision (timestamp)
|
||||
*
|
||||
* Adjacent changelog lines are optimistically parsed and cached to speed up
|
||||
* consecutive calls to getRevisionInfo. For large changelog files, only the chunk
|
||||
* containing the requested changelog line is read.
|
||||
*
|
||||
* @param int $rev revision timestamp
|
||||
* @param bool $retrieveCurrentRevInfo allows to skip for getting other revision info in the
|
||||
* getCurrentRevisionInfo() where $currentRevision is not yet determined
|
||||
* @return bool|array false or array with entries:
|
||||
* - date: unix timestamp
|
||||
* - ip: IPv4 address (127.0.0.1)
|
||||
* - type: log line type
|
||||
* - id: page id
|
||||
* - user: user name
|
||||
* - sum: edit summary (or action reason)
|
||||
* - extra: extra data (varies by line type)
|
||||
* - sizechange: change of filesize
|
||||
* additional:
|
||||
* - mode: page or media
|
||||
*
|
||||
* @author Ben Coburn <btcoburn@silicodon.net>
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
public function getRevisionInfo($rev, $retrieveCurrentRevInfo = true)
|
||||
{
|
||||
$rev = max(0, $rev);
|
||||
if (!$rev) return false;
|
||||
|
||||
//ensure the external edits are cached as well
|
||||
if (!isset($this->currentRevision) && $retrieveCurrentRevInfo) {
|
||||
$this->getCurrentRevisionInfo();
|
||||
}
|
||||
|
||||
// check if it's already in the memory cache
|
||||
if (isset($this->cache[$this->id][$rev])) {
|
||||
return $this->cache[$this->id][$rev];
|
||||
}
|
||||
|
||||
//read lines from changelog
|
||||
[$fp, $lines] = $this->readloglines($rev);
|
||||
if ($fp) {
|
||||
fclose($fp);
|
||||
}
|
||||
if (empty($lines)) return false;
|
||||
|
||||
// parse and cache changelog lines
|
||||
foreach ($lines as $line) {
|
||||
$this->parseAndCacheLogLine($line);
|
||||
}
|
||||
|
||||
return $this->cache[$this->id][$rev] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of page revisions numbers
|
||||
*
|
||||
* Does not guarantee that the revision exists in the attic,
|
||||
* only that a line with the date exists in the changelog.
|
||||
* By default the current revision is skipped.
|
||||
*
|
||||
* The current revision is automatically skipped when the page exists.
|
||||
* See $INFO['meta']['last_change'] for the current revision.
|
||||
* A negative $first let read the current revision too.
|
||||
*
|
||||
* For efficiency, the log lines are parsed and cached for later
|
||||
* calls to getRevisionInfo. Large changelog files are read
|
||||
* backwards in chunks until the requested number of changelog
|
||||
* lines are received.
|
||||
*
|
||||
* @param int $first skip the first n changelog lines
|
||||
* @param int $num number of revisions to return
|
||||
* @return array with the revision timestamps
|
||||
*
|
||||
* @author Ben Coburn <btcoburn@silicodon.net>
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
public function getRevisions($first, $num)
|
||||
{
|
||||
$revs = [];
|
||||
$lines = [];
|
||||
$count = 0;
|
||||
|
||||
$logfile = $this->getChangelogFilename();
|
||||
if (!file_exists($logfile)) return $revs;
|
||||
|
||||
$num = max($num, 0);
|
||||
if ($num == 0) {
|
||||
return $revs;
|
||||
}
|
||||
|
||||
if ($first < 0) {
|
||||
$first = 0;
|
||||
} else {
|
||||
$fileLastMod = $this->getFilename();
|
||||
if (file_exists($fileLastMod) && $this->isLastRevision(filemtime($fileLastMod))) {
|
||||
// skip last revision if the page exists
|
||||
$first = max($first + 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (filesize($logfile) < $this->chunk_size || $this->chunk_size == 0) {
|
||||
// read whole file
|
||||
$lines = file($logfile);
|
||||
if ($lines === false) {
|
||||
return $revs;
|
||||
}
|
||||
} else {
|
||||
// read chunks backwards
|
||||
$fp = fopen($logfile, 'rb'); // "file pointer"
|
||||
if ($fp === false) {
|
||||
return $revs;
|
||||
}
|
||||
fseek($fp, 0, SEEK_END);
|
||||
$tail = ftell($fp);
|
||||
|
||||
// chunk backwards
|
||||
$finger = max($tail - $this->chunk_size, 0);
|
||||
while ($count < $num + $first) {
|
||||
$nl = $this->getNewlinepointer($fp, $finger);
|
||||
|
||||
// was the chunk big enough? if not, take another bite
|
||||
if ($nl > 0 && $tail <= $nl) {
|
||||
$finger = max($finger - $this->chunk_size, 0);
|
||||
continue;
|
||||
} else {
|
||||
$finger = $nl;
|
||||
}
|
||||
|
||||
// read chunk
|
||||
$chunk = '';
|
||||
$read_size = max($tail - $finger, 0); // found chunk size
|
||||
$got = 0;
|
||||
while ($got < $read_size && !feof($fp)) {
|
||||
$tmp = @fread($fp, max(min($this->chunk_size, $read_size - $got), 0));
|
||||
if ($tmp === false) {
|
||||
break;
|
||||
} //error state
|
||||
$got += strlen($tmp);
|
||||
$chunk .= $tmp;
|
||||
}
|
||||
$tmp = explode("\n", $chunk);
|
||||
array_pop($tmp); // remove trailing newline
|
||||
|
||||
// combine with previous chunk
|
||||
$count += count($tmp);
|
||||
$lines = [...$tmp, ...$lines];
|
||||
|
||||
// next chunk
|
||||
if ($finger == 0) {
|
||||
break;
|
||||
} else { // already read all the lines
|
||||
$tail = $finger;
|
||||
$finger = max($tail - $this->chunk_size, 0);
|
||||
}
|
||||
}
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
// skip parsing extra lines
|
||||
$num = max(min(count($lines) - $first, $num), 0);
|
||||
if ($first > 0 && $num > 0) {
|
||||
$lines = array_slice($lines, max(count($lines) - $first - $num, 0), $num);
|
||||
} elseif ($first > 0 && $num == 0) {
|
||||
$lines = array_slice($lines, 0, max(count($lines) - $first, 0));
|
||||
} elseif ($first == 0 && $num > 0) {
|
||||
$lines = array_slice($lines, max(count($lines) - $num, 0));
|
||||
}
|
||||
|
||||
// handle lines in reverse order
|
||||
for ($i = count($lines) - 1; $i >= 0; $i--) {
|
||||
$info = $this->parseAndCacheLogLine($lines[$i]);
|
||||
if (is_array($info)) {
|
||||
$revs[] = $info['date'];
|
||||
}
|
||||
}
|
||||
|
||||
return $revs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the nth revision left or right-hand side for a specific page id and revision (timestamp)
|
||||
*
|
||||
* For large changelog files, only the chunk containing the
|
||||
* reference revision $rev is read and sometimes a next chunk.
|
||||
*
|
||||
* Adjacent changelog lines are optimistically parsed and cached to speed up
|
||||
* consecutive calls to getRevisionInfo.
|
||||
*
|
||||
* @param int $rev revision timestamp used as start date
|
||||
* (doesn't need to be exact revision number)
|
||||
* @param int $direction give position of returned revision with respect to $rev;
|
||||
positive=next, negative=prev
|
||||
* @return bool|int
|
||||
* timestamp of the requested revision
|
||||
* otherwise false
|
||||
*/
|
||||
public function getRelativeRevision($rev, $direction)
|
||||
{
|
||||
$rev = max($rev, 0);
|
||||
$direction = (int)$direction;
|
||||
|
||||
//no direction given or last rev, so no follow-up
|
||||
if (!$direction || ($direction > 0 && $this->isCurrentRevision($rev))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//get lines from changelog
|
||||
[$fp, $lines, $head, $tail, $eof] = $this->readloglines($rev);
|
||||
if (empty($lines)) return false;
|
||||
|
||||
// look for revisions later/earlier than $rev, when founded count till the wanted revision is reached
|
||||
// also parse and cache changelog lines for getRevisionInfo().
|
||||
$revCounter = 0;
|
||||
$relativeRev = false;
|
||||
$checkOtherChunk = true; //always runs once
|
||||
while (!$relativeRev && $checkOtherChunk) {
|
||||
$info = [];
|
||||
//parse in normal or reverse order
|
||||
$count = count($lines);
|
||||
if ($direction > 0) {
|
||||
$start = 0;
|
||||
$step = 1;
|
||||
} else {
|
||||
$start = $count - 1;
|
||||
$step = -1;
|
||||
}
|
||||
for ($i = $start; $i >= 0 && $i < $count; $i += $step) {
|
||||
$info = $this->parseAndCacheLogLine($lines[$i]);
|
||||
if (is_array($info)) {
|
||||
//look for revs older/earlier then reference $rev and select $direction-th one
|
||||
if (($direction > 0 && $info['date'] > $rev) || ($direction < 0 && $info['date'] < $rev)) {
|
||||
$revCounter++;
|
||||
if ($revCounter == abs($direction)) {
|
||||
$relativeRev = $info['date'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//true when $rev is found, but not the wanted follow-up.
|
||||
$checkOtherChunk = $fp
|
||||
&& ($info['date'] == $rev || ($revCounter > 0 && !$relativeRev))
|
||||
&& (!($tail == $eof && $direction > 0) && !($head == 0 && $direction < 0));
|
||||
|
||||
if ($checkOtherChunk) {
|
||||
[$lines, $head, $tail] = $this->readAdjacentChunk($fp, $head, $tail, $direction);
|
||||
|
||||
if (empty($lines)) break;
|
||||
}
|
||||
}
|
||||
if ($fp) {
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
return $relativeRev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns revisions around rev1 and rev2
|
||||
* When available it returns $max entries for each revision
|
||||
*
|
||||
* @param int $rev1 oldest revision timestamp
|
||||
* @param int $rev2 newest revision timestamp (0 looks up last revision)
|
||||
* @param int $max maximum number of revisions returned
|
||||
* @return array with two arrays with revisions surrounding rev1 respectively rev2
|
||||
*/
|
||||
public function getRevisionsAround($rev1, $rev2, $max = 50)
|
||||
{
|
||||
$max = (int) (abs($max) / 2) * 2 + 1;
|
||||
$rev1 = max($rev1, 0);
|
||||
$rev2 = max($rev2, 0);
|
||||
|
||||
if ($rev2) {
|
||||
if ($rev2 < $rev1) {
|
||||
$rev = $rev2;
|
||||
$rev2 = $rev1;
|
||||
$rev1 = $rev;
|
||||
}
|
||||
} else {
|
||||
//empty right side means a removed page. Look up last revision.
|
||||
$rev2 = $this->currentRevision();
|
||||
}
|
||||
//collect revisions around rev2
|
||||
[$revs2, $allRevs, $fp, $lines, $head, $tail] = $this->retrieveRevisionsAround($rev2, $max);
|
||||
|
||||
if (empty($revs2)) return [[], []];
|
||||
|
||||
//collect revisions around rev1
|
||||
$index = array_search($rev1, $allRevs);
|
||||
if ($index === false) {
|
||||
//no overlapping revisions
|
||||
[$revs1, , , , , ] = $this->retrieveRevisionsAround($rev1, $max);
|
||||
if (empty($revs1)) $revs1 = [];
|
||||
} else {
|
||||
//revisions overlaps, reuse revisions around rev2
|
||||
$lastRev = array_pop($allRevs); //keep last entry that could be external edit
|
||||
$revs1 = $allRevs;
|
||||
while ($head > 0) {
|
||||
for ($i = count($lines) - 1; $i >= 0; $i--) {
|
||||
$info = $this->parseAndCacheLogLine($lines[$i]);
|
||||
if (is_array($info)) {
|
||||
$revs1[] = $info['date'];
|
||||
$index++;
|
||||
|
||||
if ($index > (int) ($max / 2)) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[$lines, $head, $tail] = $this->readAdjacentChunk($fp, $head, $tail, -1);
|
||||
}
|
||||
sort($revs1);
|
||||
$revs1[] = $lastRev; //push back last entry
|
||||
|
||||
//return wanted selection
|
||||
$revs1 = array_slice($revs1, max($index - (int) ($max / 2), 0), $max);
|
||||
}
|
||||
|
||||
return [array_reverse($revs1), array_reverse($revs2)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an existing revision for a specific date which is
|
||||
* the current one or younger or equal then the date
|
||||
*
|
||||
* @param number $date_at timestamp
|
||||
* @return string revision ('' for current)
|
||||
*/
|
||||
public function getLastRevisionAt($date_at)
|
||||
{
|
||||
$fileLastMod = $this->getFilename();
|
||||
//requested date_at(timestamp) younger or equal then modified_time($this->id) => load current
|
||||
if (file_exists($fileLastMod) && $date_at >= @filemtime($fileLastMod)) {
|
||||
return '';
|
||||
} elseif ($rev = $this->getRelativeRevision($date_at + 1, -1)) {
|
||||
//+1 to get also the requested date revision
|
||||
return $rev;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the $max revisions near to the timestamp $rev
|
||||
*
|
||||
* Ideally, half of retrieved timestamps are older than $rev, another half are newer.
|
||||
* The returned array $requestedRevs may not contain the reference timestamp $rev
|
||||
* when it does not match any revision value recorded in changelog.
|
||||
*
|
||||
* @param int $rev revision timestamp
|
||||
* @param int $max maximum number of revisions to be returned
|
||||
* @return bool|array
|
||||
* return array with entries:
|
||||
* - $requestedRevs: array of with $max revision timestamps
|
||||
* - $revs: all parsed revision timestamps
|
||||
* - $fp: file pointer only defined for chuck reading, needs closing.
|
||||
* - $lines: non-parsed changelog lines before the parsed revisions
|
||||
* - $head: position of first read changelog line
|
||||
* - $lastTail: position of end of last read changelog line
|
||||
* otherwise false
|
||||
*/
|
||||
protected function retrieveRevisionsAround($rev, $max)
|
||||
{
|
||||
$revs = [];
|
||||
$afterCount = 0;
|
||||
$beforeCount = 0;
|
||||
|
||||
//get lines from changelog
|
||||
[$fp, $lines, $startHead, $startTail, $eof] = $this->readloglines($rev);
|
||||
if (empty($lines)) return false;
|
||||
|
||||
//parse changelog lines in chunk, and read forward more chunks until $max/2 is reached
|
||||
$head = $startHead;
|
||||
$tail = $startTail;
|
||||
while (count($lines) > 0) {
|
||||
foreach ($lines as $line) {
|
||||
$info = $this->parseAndCacheLogLine($line);
|
||||
if (is_array($info)) {
|
||||
$revs[] = $info['date'];
|
||||
if ($info['date'] >= $rev) {
|
||||
//count revs after reference $rev
|
||||
$afterCount++;
|
||||
if ($afterCount == 1) {
|
||||
$beforeCount = count($revs);
|
||||
}
|
||||
}
|
||||
//enough revs after reference $rev?
|
||||
if ($afterCount > (int) ($max / 2)) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
//retrieve next chunk
|
||||
[$lines, $head, $tail] = $this->readAdjacentChunk($fp, $head, $tail, 1);
|
||||
}
|
||||
$lastTail = $tail;
|
||||
|
||||
// add a possible revision of external edit, create or deletion
|
||||
if (
|
||||
$lastTail == $eof && $afterCount <= (int) ($max / 2) &&
|
||||
count($revs) && !$this->isCurrentRevision($revs[count($revs) - 1])
|
||||
) {
|
||||
$revs[] = $this->currentRevision;
|
||||
$afterCount++;
|
||||
}
|
||||
|
||||
if ($afterCount == 0) {
|
||||
//given timestamp $rev is newer than the most recent line in chunk
|
||||
return false; //FIXME: or proceed to collect older revisions?
|
||||
}
|
||||
|
||||
//read more chunks backward until $max/2 is reached and total number of revs is equal to $max
|
||||
$lines = [];
|
||||
$i = 0;
|
||||
$head = $startHead;
|
||||
$tail = $startTail;
|
||||
while ($head > 0) {
|
||||
[$lines, $head, $tail] = $this->readAdjacentChunk($fp, $head, $tail, -1);
|
||||
|
||||
for ($i = count($lines) - 1; $i >= 0; $i--) {
|
||||
$info = $this->parseAndCacheLogLine($lines[$i]);
|
||||
if (is_array($info)) {
|
||||
$revs[] = $info['date'];
|
||||
$beforeCount++;
|
||||
//enough revs before reference $rev?
|
||||
if ($beforeCount > max((int) ($max / 2), $max - $afterCount)) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//keep only non-parsed lines
|
||||
$lines = array_slice($lines, 0, $i);
|
||||
|
||||
sort($revs);
|
||||
|
||||
//trunk desired selection
|
||||
$requestedRevs = array_slice($revs, -$max, $max);
|
||||
|
||||
return [$requestedRevs, $revs, $fp, $lines, $head, $lastTail];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current revision information, considering external edit, create or deletion
|
||||
*
|
||||
* When the file has not modified since its last revision, the information of the last
|
||||
* change that had already recorded in the changelog is returned as current change info.
|
||||
* Otherwise, the change information since the last revision caused outside DokuWiki
|
||||
* should be returned, which is referred as "external revision".
|
||||
*
|
||||
* The change date of the file can be determined by timestamp as far as the file exists,
|
||||
* however this is not possible when the file has already deleted outside of DokuWiki.
|
||||
* In such case we assign 1 sec before current time() for the external deletion.
|
||||
* As a result, the value of current revision identifier may change each time because:
|
||||
* 1) the file has again modified outside of DokuWiki, or
|
||||
* 2) the value is essentially volatile for deleted but once existed files.
|
||||
*
|
||||
* @return bool|array false when page had never existed or array with entries:
|
||||
* - date: revision identifier (timestamp or last revision +1)
|
||||
* - ip: IPv4 address (127.0.0.1)
|
||||
* - type: log line type
|
||||
* - id: id of page or media
|
||||
* - user: user name
|
||||
* - sum: edit summary (or action reason)
|
||||
* - extra: extra data (varies by line type)
|
||||
* - sizechange: change of filesize
|
||||
* - timestamp: unix timestamp or false (key set only for external edit occurred)
|
||||
* additional:
|
||||
* - mode: page or media
|
||||
*
|
||||
* @author Satoshi Sahara <sahara.satoshi@gmail.com>
|
||||
*/
|
||||
public function getCurrentRevisionInfo()
|
||||
{
|
||||
global $lang;
|
||||
|
||||
if (isset($this->currentRevision)) {
|
||||
return $this->getRevisionInfo($this->currentRevision);
|
||||
}
|
||||
|
||||
// get revision id from the item file timestamp and changelog
|
||||
$fileLastMod = $this->getFilename();
|
||||
$fileRev = @filemtime($fileLastMod); // false when the file not exist
|
||||
$lastRev = $this->lastRevision(); // false when no changelog
|
||||
|
||||
if (!$fileRev && !$lastRev) { // has never existed
|
||||
$this->currentRevision = false;
|
||||
return false;
|
||||
} elseif ($fileRev === $lastRev) { // not external edit
|
||||
$this->currentRevision = $lastRev;
|
||||
return $this->getRevisionInfo($lastRev);
|
||||
}
|
||||
|
||||
if (!$fileRev && $lastRev) { // item file does not exist
|
||||
// check consistency against changelog
|
||||
$revInfo = $this->getRevisionInfo($lastRev, false);
|
||||
if ($revInfo['type'] == DOKU_CHANGE_TYPE_DELETE) {
|
||||
$this->currentRevision = $lastRev;
|
||||
return $revInfo;
|
||||
}
|
||||
|
||||
// externally deleted, set revision date as late as possible
|
||||
$revInfo = [
|
||||
'date' => max($lastRev + 1, time() - 1), // 1 sec before now or new page save
|
||||
'ip' => '127.0.0.1',
|
||||
'type' => DOKU_CHANGE_TYPE_DELETE,
|
||||
'id' => $this->id,
|
||||
'user' => '',
|
||||
'sum' => $lang['deleted'] . ' - ' . $lang['external_edit'] . ' (' . $lang['unknowndate'] . ')',
|
||||
'extra' => '',
|
||||
'sizechange' => -io_getSizeFile($this->getFilename($lastRev)),
|
||||
'timestamp' => false,
|
||||
'mode' => $this->getMode()
|
||||
];
|
||||
} else { // item file exists, with timestamp $fileRev
|
||||
// here, file timestamp $fileRev is different with last revision timestamp $lastRev in changelog
|
||||
$isJustCreated = $lastRev === false || (
|
||||
$fileRev > $lastRev &&
|
||||
$this->getRevisionInfo($lastRev, false)['type'] == DOKU_CHANGE_TYPE_DELETE
|
||||
);
|
||||
$filesize_new = filesize($this->getFilename());
|
||||
$filesize_old = $isJustCreated ? 0 : io_getSizeFile($this->getFilename($lastRev));
|
||||
$sizechange = $filesize_new - $filesize_old;
|
||||
|
||||
if ($isJustCreated) {
|
||||
$timestamp = $fileRev;
|
||||
$sum = $lang['created'] . ' - ' . $lang['external_edit'];
|
||||
} elseif ($fileRev > $lastRev) {
|
||||
$timestamp = $fileRev;
|
||||
$sum = $lang['external_edit'];
|
||||
} else {
|
||||
// $fileRev is older than $lastRev, that is erroneous/incorrect occurrence.
|
||||
$msg = "Warning: current file modification time is older than last revision date";
|
||||
$details = 'File revision: ' . $fileRev . ' ' . dformat($fileRev, "%Y-%m-%d %H:%M:%S") . "\n"
|
||||
. 'Last revision: ' . $lastRev . ' ' . dformat($lastRev, "%Y-%m-%d %H:%M:%S");
|
||||
Logger::error($msg, $details, $this->getFilename());
|
||||
$timestamp = false;
|
||||
$sum = $lang['external_edit'] . ' (' . $lang['unknowndate'] . ')';
|
||||
}
|
||||
|
||||
// externally created or edited
|
||||
$revInfo = [
|
||||
'date' => $timestamp ?: $lastRev + 1,
|
||||
'ip' => '127.0.0.1',
|
||||
'type' => $isJustCreated ? DOKU_CHANGE_TYPE_CREATE : DOKU_CHANGE_TYPE_EDIT,
|
||||
'id' => $this->id,
|
||||
'user' => '',
|
||||
'sum' => $sum,
|
||||
'extra' => '',
|
||||
'sizechange' => $sizechange,
|
||||
'timestamp' => $timestamp,
|
||||
'mode' => $this->getMode()
|
||||
];
|
||||
}
|
||||
|
||||
// cache current revision information of external edition
|
||||
$this->currentRevision = $revInfo['date'];
|
||||
$this->cache[$this->id][$this->currentRevision] = $revInfo;
|
||||
return $this->getRevisionInfo($this->currentRevision);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mechanism to trace no-actual external current revision
|
||||
* @param int $rev
|
||||
*/
|
||||
public function traceCurrentRevision($rev)
|
||||
{
|
||||
if ($rev > $this->lastRevision()) {
|
||||
$rev = $this->currentRevision();
|
||||
}
|
||||
return $rev;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\ChangeLog;
|
||||
|
||||
use dokuwiki\Utf8\PhpString;
|
||||
|
||||
/**
|
||||
* Provides methods for handling of changelog
|
||||
*/
|
||||
trait ChangeLogTrait
|
||||
{
|
||||
/**
|
||||
* Adds an entry to the changelog file
|
||||
*
|
||||
* @return array added log line as revision info
|
||||
*/
|
||||
abstract public function addLogEntry(array $info, $timestamp = null);
|
||||
|
||||
/**
|
||||
* Parses a changelog line into its components
|
||||
*
|
||||
* @param string $line changelog line
|
||||
* @return array|bool parsed line or false
|
||||
* @author Ben Coburn <btcoburn@silicodon.net>
|
||||
*
|
||||
*/
|
||||
public static function parseLogLine($line)
|
||||
{
|
||||
$info = sexplode("\t", rtrim($line, "\n"), 8);
|
||||
if ($info[3]) { // we need at least the page id to consider it a valid line
|
||||
return [
|
||||
'date' => (int)$info[0], // unix timestamp
|
||||
'ip' => $info[1], // IP address (127.0.0.1)
|
||||
'type' => $info[2], // log line type
|
||||
'id' => $info[3], // page id
|
||||
'user' => $info[4], // user name
|
||||
'sum' => $info[5], // edit summary (or action reason)
|
||||
'extra' => $info[6], // extra data (varies by line type)
|
||||
'sizechange' => ($info[7] != '') ? (int)$info[7] : null, // size difference in bytes
|
||||
];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a changelog line from its components
|
||||
*
|
||||
* @param array $info Revision info structure
|
||||
* @param int $timestamp log line date (optional)
|
||||
* @return string changelog line
|
||||
*/
|
||||
public static function buildLogLine(array &$info, $timestamp = null)
|
||||
{
|
||||
$strip = ["\t", "\n"];
|
||||
$entry = [
|
||||
'date' => $timestamp ?? $info['date'],
|
||||
'ip' => $info['ip'],
|
||||
'type' => str_replace($strip, '', $info['type']),
|
||||
'id' => $info['id'],
|
||||
'user' => $info['user'],
|
||||
'sum' => PhpString::substr(str_replace($strip, '', $info['sum'] ?? ''), 0, 255),
|
||||
'extra' => str_replace($strip, '', $info['extra']),
|
||||
'sizechange' => $info['sizechange']
|
||||
];
|
||||
$info = $entry;
|
||||
return implode("\t", $entry) . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to changelog
|
||||
*
|
||||
* @return string path to file
|
||||
*/
|
||||
abstract protected function getChangelogFilename();
|
||||
|
||||
/**
|
||||
* Checks if the ID has old revisions
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasRevisions()
|
||||
{
|
||||
$logfile = $this->getChangelogFilename();
|
||||
return file_exists($logfile);
|
||||
}
|
||||
|
||||
|
||||
/** @var int */
|
||||
protected $chunk_size;
|
||||
|
||||
/**
|
||||
* Set chunk size for file reading
|
||||
* Chunk size zero let read whole file at once
|
||||
*
|
||||
* @param int $chunk_size maximum block size read from file
|
||||
*/
|
||||
public function setChunkSize($chunk_size)
|
||||
{
|
||||
if (!is_numeric($chunk_size)) $chunk_size = 0;
|
||||
|
||||
$this->chunk_size = max($chunk_size, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns lines from changelog.
|
||||
* If file larger than $chunk_size, only chunk is read that could contain $rev.
|
||||
*
|
||||
* When reference timestamp $rev is outside time range of changelog, readloglines() will return
|
||||
* lines in first or last chunk, but they obviously does not contain $rev.
|
||||
*
|
||||
* @param int $rev revision timestamp
|
||||
* @return array|false
|
||||
* if success returns array(fp, array(changeloglines), $head, $tail, $eof)
|
||||
* where fp only defined for chuck reading, needs closing.
|
||||
* otherwise false
|
||||
*/
|
||||
protected function readloglines($rev)
|
||||
{
|
||||
$file = $this->getChangelogFilename();
|
||||
|
||||
if (!file_exists($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fp = null;
|
||||
$head = 0;
|
||||
$tail = 0;
|
||||
$eof = 0;
|
||||
|
||||
if (filesize($file) < $this->chunk_size || $this->chunk_size == 0) {
|
||||
// read whole file
|
||||
$lines = file($file);
|
||||
if ($lines === false) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// read by chunk
|
||||
$fp = fopen($file, 'rb'); // "file pointer"
|
||||
if ($fp === false) {
|
||||
return false;
|
||||
}
|
||||
fseek($fp, 0, SEEK_END);
|
||||
$eof = ftell($fp);
|
||||
$tail = $eof;
|
||||
|
||||
// find chunk
|
||||
while ($tail - $head > $this->chunk_size) {
|
||||
$finger = $head + (int)(($tail - $head) / 2);
|
||||
$finger = $this->getNewlinepointer($fp, $finger);
|
||||
$tmp = fgets($fp);
|
||||
if ($finger == $head || $finger == $tail) {
|
||||
break;
|
||||
}
|
||||
$info = $this->parseLogLine($tmp);
|
||||
$finger_rev = $info['date'];
|
||||
|
||||
if ($finger_rev > $rev) {
|
||||
$tail = $finger;
|
||||
} else {
|
||||
$head = $finger;
|
||||
}
|
||||
}
|
||||
|
||||
if ($tail - $head < 1) {
|
||||
// could not find chunk, assume requested rev is missing
|
||||
fclose($fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
$lines = $this->readChunk($fp, $head, $tail);
|
||||
}
|
||||
return [$fp, $lines, $head, $tail, $eof];
|
||||
}
|
||||
|
||||
/**
|
||||
* Read chunk and return array with lines of given chunk.
|
||||
* Has no check if $head and $tail are really at a new line
|
||||
*
|
||||
* @param resource $fp resource file pointer
|
||||
* @param int $head start point chunk
|
||||
* @param int $tail end point chunk
|
||||
* @return array lines read from chunk
|
||||
*/
|
||||
protected function readChunk($fp, $head, $tail)
|
||||
{
|
||||
$chunk = '';
|
||||
$chunk_size = max($tail - $head, 0); // found chunk size
|
||||
$got = 0;
|
||||
fseek($fp, $head);
|
||||
while ($got < $chunk_size && !feof($fp)) {
|
||||
$tmp = @fread($fp, max(min($this->chunk_size, $chunk_size - $got), 0));
|
||||
if ($tmp === false) { //error state
|
||||
break;
|
||||
}
|
||||
$got += strlen($tmp);
|
||||
$chunk .= $tmp;
|
||||
}
|
||||
$lines = explode("\n", $chunk);
|
||||
array_pop($lines); // remove trailing newline
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set pointer to first new line after $finger and return its position
|
||||
*
|
||||
* @param resource $fp file pointer
|
||||
* @param int $finger a pointer
|
||||
* @return int pointer
|
||||
*/
|
||||
protected function getNewlinepointer($fp, $finger)
|
||||
{
|
||||
fseek($fp, $finger);
|
||||
$nl = $finger;
|
||||
if ($finger > 0) {
|
||||
fgets($fp); // slip the finger forward to a new line
|
||||
$nl = ftell($fp);
|
||||
}
|
||||
return $nl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next lines of the changelog of the chunk before head or after tail
|
||||
*
|
||||
* @param resource $fp file pointer
|
||||
* @param int $head position head of last chunk
|
||||
* @param int $tail position tail of last chunk
|
||||
* @param int $direction positive forward, negative backward
|
||||
* @return array with entries:
|
||||
* - $lines: changelog lines of read chunk
|
||||
* - $head: head of chunk
|
||||
* - $tail: tail of chunk
|
||||
*/
|
||||
protected function readAdjacentChunk($fp, $head, $tail, $direction)
|
||||
{
|
||||
if (!$fp) return [[], $head, $tail];
|
||||
|
||||
if ($direction > 0) {
|
||||
//read forward
|
||||
$head = $tail;
|
||||
$tail = $head + (int)($this->chunk_size * (2 / 3));
|
||||
$tail = $this->getNewlinepointer($fp, $tail);
|
||||
} else {
|
||||
//read backward
|
||||
$tail = $head;
|
||||
$head = max($tail - $this->chunk_size, 0);
|
||||
while (true) {
|
||||
$nl = $this->getNewlinepointer($fp, $head);
|
||||
// was the chunk big enough? if not, take another bite
|
||||
if ($nl > 0 && $tail <= $nl) {
|
||||
$head = max($head - $this->chunk_size, 0);
|
||||
} else {
|
||||
$head = $nl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//load next chunk
|
||||
$lines = $this->readChunk($fp, $head, $tail);
|
||||
return [$lines, $head, $tail];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\ChangeLog;
|
||||
|
||||
/**
|
||||
* Class MediaChangeLog; handles changelog of a media file
|
||||
*/
|
||||
class MediaChangeLog extends ChangeLog
|
||||
{
|
||||
/**
|
||||
* Returns path to changelog
|
||||
*
|
||||
* @return string path to file
|
||||
*/
|
||||
protected function getChangelogFilename()
|
||||
{
|
||||
return mediaMetaFN($this->id, '.changes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to current page/media
|
||||
*
|
||||
* @param string|int $rev empty string or revision timestamp
|
||||
* @return string path to file
|
||||
*/
|
||||
protected function getFilename($rev = '')
|
||||
{
|
||||
return mediaFN($this->id, $rev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns mode
|
||||
*
|
||||
* @return string RevisionInfo::MODE_PAGE
|
||||
*/
|
||||
protected function getMode()
|
||||
{
|
||||
return RevisionInfo::MODE_MEDIA;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an entry to the changelog
|
||||
*
|
||||
* @param array $info Revision info structure of a media file
|
||||
* @param int $timestamp log line date (optional)
|
||||
* @return array revision info of added log line
|
||||
*
|
||||
* @see also addMediaLogEntry() in inc/changelog.php file
|
||||
*/
|
||||
public function addLogEntry(array $info, $timestamp = null)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (isset($timestamp)) unset($this->cache[$this->id][$info['date']]);
|
||||
|
||||
// add changelog lines
|
||||
$logline = static::buildLogLine($info, $timestamp);
|
||||
io_saveFile(mediaMetaFN($this->id, '.changes'), $logline, $append = true);
|
||||
io_saveFile($conf['media_changelog'], $logline, $append = true); //global changelog cache
|
||||
|
||||
// update cache
|
||||
$this->currentRevision = $info['date'];
|
||||
$info['mode'] = $this->getMode();
|
||||
$this->cache[$this->id][$this->currentRevision] = $info;
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\ChangeLog;
|
||||
|
||||
/**
|
||||
* Class PageChangeLog; handles changelog of a wiki page
|
||||
*/
|
||||
class PageChangeLog extends ChangeLog
|
||||
{
|
||||
/**
|
||||
* Returns path to changelog
|
||||
*
|
||||
* @return string path to file
|
||||
*/
|
||||
protected function getChangelogFilename()
|
||||
{
|
||||
return metaFN($this->id, '.changes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to current page/media
|
||||
*
|
||||
* @param string|int $rev empty string or revision timestamp
|
||||
* @return string path to file
|
||||
*/
|
||||
protected function getFilename($rev = '')
|
||||
{
|
||||
return wikiFN($this->id, $rev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns mode
|
||||
*
|
||||
* @return string RevisionInfo::MODE_PAGE
|
||||
*/
|
||||
protected function getMode()
|
||||
{
|
||||
return RevisionInfo::MODE_PAGE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an entry to the changelog
|
||||
*
|
||||
* @param array $info Revision info structure of a page
|
||||
* @param int $timestamp log line date (optional)
|
||||
* @return array revision info of added log line
|
||||
*
|
||||
* @see also addLogEntry() in inc/changelog.php file
|
||||
*/
|
||||
public function addLogEntry(array $info, $timestamp = null)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (isset($timestamp)) unset($this->cache[$this->id][$info['date']]);
|
||||
|
||||
// add changelog lines
|
||||
$logline = static::buildLogLine($info, $timestamp);
|
||||
io_saveFile(metaFN($this->id, '.changes'), $logline, true);
|
||||
io_saveFile($conf['changelog'], $logline, true); //global changelog cache
|
||||
|
||||
// update cache
|
||||
$this->currentRevision = $info['date'];
|
||||
$info['mode'] = $this->getMode();
|
||||
$this->cache[$this->id][$this->currentRevision] = $info;
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\ChangeLog;
|
||||
|
||||
/**
|
||||
* Class RevisionInfo
|
||||
*
|
||||
* Provides methods to show Revision Information in DokuWiki Ui components:
|
||||
* - Ui\Recent
|
||||
* - Ui\PageRevisions
|
||||
* - Ui\MediaRevisions
|
||||
* - Ui\PageDiff
|
||||
* - Ui\MediaDiff
|
||||
*/
|
||||
class RevisionInfo
|
||||
{
|
||||
public const MODE_PAGE = 'page';
|
||||
public const MODE_MEDIA = 'media';
|
||||
|
||||
/* @var array */
|
||||
protected $info;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $info Revision Information structure with entries:
|
||||
* - date: unix timestamp
|
||||
* - ip: IPv4 or IPv6 address
|
||||
* - type: change type (log line type)
|
||||
* - id: page id
|
||||
* - user: user name
|
||||
* - sum: edit summary (or action reason)
|
||||
* - extra: extra data (varies by line type)
|
||||
* - sizechange: change of filesize
|
||||
* additionally,
|
||||
* - current: (optional) whether current revision or not
|
||||
* - timestamp: (optional) set only when external edits occurred
|
||||
* - mode: (internal use) ether "media" or "page"
|
||||
*/
|
||||
public function __construct($info = null)
|
||||
{
|
||||
if (!is_array($info) || !isset($info['id'])) {
|
||||
$info = [
|
||||
'mode' => self::MODE_PAGE,
|
||||
'date' => false,
|
||||
];
|
||||
}
|
||||
$this->info = $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or return whether this revision is current page or media file
|
||||
*
|
||||
* This method does not check exactly whether the revision is current or not. Instead,
|
||||
* set value of associated "current" key for internal use. Some UI element like diff
|
||||
* link button depend on relation to current page or media file. A changelog line does
|
||||
* not indicate whether it corresponds to current page or media file.
|
||||
*
|
||||
* @param bool $value true if the revision is current, otherwise false
|
||||
* @return bool
|
||||
*/
|
||||
public function isCurrent($value = null)
|
||||
{
|
||||
return (bool) $this->val('current', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return or set a value of associated key of revision information
|
||||
* but does not allow to change values of existing keys
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return string|null
|
||||
*/
|
||||
public function val($key, $value = null)
|
||||
{
|
||||
if (isset($value) && !array_key_exists($key, $this->info)) {
|
||||
// setter, only for new keys
|
||||
$this->info[$key] = $value;
|
||||
}
|
||||
if (array_key_exists($key, $this->info)) {
|
||||
// getter
|
||||
return $this->info[$key];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set extra key-value to the revision information
|
||||
* but does not allow to change values of existing keys
|
||||
* @param array $info
|
||||
* @return void
|
||||
*/
|
||||
public function append(array $info)
|
||||
{
|
||||
foreach ($info as $key => $value) {
|
||||
$this->val($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* file icon of the page or media file
|
||||
* used in [Ui\recent]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function showFileIcon()
|
||||
{
|
||||
$id = $this->val('id');
|
||||
if ($this->val('mode') == self::MODE_MEDIA) {
|
||||
// media file revision
|
||||
return media_printicon($id);
|
||||
} elseif ($this->val('mode') == self::MODE_PAGE) {
|
||||
// page revision
|
||||
return '<img class="icon" src="' . DOKU_BASE . 'lib/images/fileicons/file.png" alt="' . $id . '" />';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* edit date and time of the page or media file
|
||||
* used in [Ui\recent, Ui\Revisions]
|
||||
*
|
||||
* @param bool $checkTimestamp enable timestamp check, alter formatted string when timestamp is false
|
||||
* @return string
|
||||
*/
|
||||
public function showEditDate($checkTimestamp = false)
|
||||
{
|
||||
$formatted = dformat($this->val('date'));
|
||||
if ($checkTimestamp && $this->val('timestamp') === false) {
|
||||
// exact date is unknown for externally deleted file
|
||||
// when unknown, alter formatted string "YYYY-mm-DD HH:MM" to "____-__-__ __:__"
|
||||
$formatted = preg_replace('/[0-9a-zA-Z]/', '_', $formatted);
|
||||
}
|
||||
return '<span class="date">' . $formatted . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* edit summary
|
||||
* used in [Ui\recent, Ui\Revisions]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function showEditSummary()
|
||||
{
|
||||
return '<span class="sum">' . ' – ' . hsc($this->val('sum')) . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* editor of the page or media file
|
||||
* used in [Ui\recent, Ui\Revisions]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function showEditor()
|
||||
{
|
||||
if ($this->val('user')) {
|
||||
$html = '<bdi>' . editorinfo($this->val('user')) . '</bdi>';
|
||||
if (auth_ismanager()) {
|
||||
$html .= ' <bdo dir="ltr">(' . $this->val('ip') . ')</bdo>';
|
||||
}
|
||||
} else {
|
||||
$html = '<bdo dir="ltr">' . $this->val('ip') . '</bdo>';
|
||||
}
|
||||
return '<span class="user">' . $html . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* name of the page or media file
|
||||
* used in [Ui\recent, Ui\Revisions]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function showFileName()
|
||||
{
|
||||
$id = $this->val('id');
|
||||
$rev = $this->isCurrent() ? '' : $this->val('date');
|
||||
|
||||
if ($this->val('mode') == self::MODE_MEDIA) {
|
||||
// media file revision
|
||||
$params = ['tab_details' => 'view', 'ns' => getNS($id), 'image' => $id];
|
||||
if ($rev) $params += ['rev' => $rev];
|
||||
$href = media_managerURL($params, '&');
|
||||
$display_name = $id;
|
||||
$exists = file_exists(mediaFN($id, $rev));
|
||||
} elseif ($this->val('mode') == self::MODE_PAGE) {
|
||||
// page revision
|
||||
$params = $rev ? ['rev' => $rev] : [];
|
||||
$href = wl($id, $params, false, '&');
|
||||
$display_name = useHeading('navigation') ? hsc(p_get_first_heading($id)) : $id;
|
||||
if (!$display_name) $display_name = $id;
|
||||
$exists = page_exists($id, $rev);
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
$class = 'wikilink1';
|
||||
} elseif ($this->isCurrent()) {
|
||||
//show only not-existing link for current page, which allows for directly create a new page/upload
|
||||
$class = 'wikilink2';
|
||||
} else {
|
||||
//revision is not in attic
|
||||
return $display_name;
|
||||
}
|
||||
if ($this->val('type') == DOKU_CHANGE_TYPE_DELETE) {
|
||||
$class = 'wikilink2';
|
||||
}
|
||||
return '<a href="' . $href . '" class="' . $class . '">' . $display_name . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Revision Title for PageDiff table headline
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function showRevisionTitle()
|
||||
{
|
||||
global $lang;
|
||||
|
||||
if (!$this->val('date')) return '—';
|
||||
|
||||
$id = $this->val('id');
|
||||
$rev = $this->isCurrent() ? '' : $this->val('date');
|
||||
$params = ($rev) ? ['rev' => $rev] : [];
|
||||
|
||||
// revision info may have timestamp key when external edits occurred
|
||||
$date = ($this->val('timestamp') === false)
|
||||
? $lang['unknowndate']
|
||||
: dformat($this->val('date'));
|
||||
|
||||
|
||||
if ($this->val('mode') == self::MODE_MEDIA) {
|
||||
// media file revision
|
||||
$href = ml($id, $params, false, '&');
|
||||
$exists = file_exists(mediaFN($id, $rev));
|
||||
} elseif ($this->val('mode') == self::MODE_PAGE) {
|
||||
// page revision
|
||||
$href = wl($id, $params, false, '&');
|
||||
$exists = page_exists($id, $rev);
|
||||
}
|
||||
if ($exists) {
|
||||
$class = 'wikilink1';
|
||||
} elseif ($this->isCurrent()) {
|
||||
//show only not-existing link for current page, which allows for directly create a new page/upload
|
||||
$class = 'wikilink2';
|
||||
} else {
|
||||
//revision is not in attic
|
||||
return $id . ' [' . $date . ']';
|
||||
}
|
||||
if ($this->val('type') == DOKU_CHANGE_TYPE_DELETE) {
|
||||
$class = 'wikilink2';
|
||||
}
|
||||
return '<bdi><a class="' . $class . '" href="' . $href . '">' . $id . ' [' . $date . ']' . '</a></bdi>';
|
||||
}
|
||||
|
||||
/**
|
||||
* diff link icon in recent changes list, to compare (this) current revision with previous one
|
||||
* all items in "recent changes" are current revision of the page or media
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function showIconCompareWithPrevious()
|
||||
{
|
||||
global $lang;
|
||||
$id = $this->val('id');
|
||||
|
||||
$href = '';
|
||||
if ($this->val('mode') == self::MODE_MEDIA) {
|
||||
// media file revision
|
||||
// unlike page, media file does not copied to media_attic when uploaded.
|
||||
// diff icon will not be shown when external edit occurred
|
||||
// because no attic file to be compared with current.
|
||||
$revs = (new MediaChangeLog($id))->getRevisions(0, 1);
|
||||
$showLink = (count($revs) && file_exists(mediaFN($id, $revs[0])) && file_exists(mediaFN($id)));
|
||||
if ($showLink) {
|
||||
$param = ['tab_details' => 'history', 'mediado' => 'diff', 'ns' => getNS($id), 'image' => $id];
|
||||
$href = media_managerURL($param, '&');
|
||||
}
|
||||
} elseif ($this->val('mode') == self::MODE_PAGE) {
|
||||
// page revision
|
||||
// when a page just created anyway, it is natural to expect no older revisions
|
||||
// even if it had once existed but deleted before. Simply ignore to check changelog.
|
||||
if ($this->val('type') !== DOKU_CHANGE_TYPE_CREATE) {
|
||||
$href = wl($id, ['do' => 'diff'], false, '&');
|
||||
}
|
||||
}
|
||||
|
||||
if ($href) {
|
||||
return '<a href="' . $href . '" class="diff_link">'
|
||||
. '<img src="' . DOKU_BASE . 'lib/images/diff.png" width="15" height="11"'
|
||||
. ' title="' . $lang['diff'] . '" alt="' . $lang['diff'] . '" />'
|
||||
. '</a>';
|
||||
} else {
|
||||
return '<img src="' . DOKU_BASE . 'lib/images/blank.gif" width="15" height="11" alt="" />';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* diff link icon in revisions list, compare this revision with current one
|
||||
* the icon does not displayed for the current revision
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function showIconCompareWithCurrent()
|
||||
{
|
||||
global $lang;
|
||||
$id = $this->val('id');
|
||||
$rev = $this->isCurrent() ? '' : $this->val('date');
|
||||
|
||||
$href = '';
|
||||
if ($this->val('mode') == self::MODE_MEDIA) {
|
||||
// media file revision
|
||||
if (!$this->isCurrent() && file_exists(mediaFN($id, $rev))) {
|
||||
$param = ['mediado' => 'diff', 'image' => $id, 'rev' => $rev];
|
||||
$href = media_managerURL($param, '&');
|
||||
}
|
||||
} elseif ($this->val('mode') == self::MODE_PAGE) {
|
||||
// page revision
|
||||
if (!$this->isCurrent()) {
|
||||
$href = wl($id, ['rev' => $rev, 'do' => 'diff'], false, '&');
|
||||
}
|
||||
}
|
||||
|
||||
if ($href) {
|
||||
return '<a href="' . $href . '" class="diff_link">'
|
||||
. '<img src="' . DOKU_BASE . 'lib/images/diff.png" width="15" height="11"'
|
||||
. ' title="' . $lang['diff'] . '" alt="' . $lang['diff'] . '" />'
|
||||
. '</a>';
|
||||
} else {
|
||||
return '<img src="' . DOKU_BASE . 'lib/images/blank.gif" width="15" height="11" alt="" />';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* icon for revision action
|
||||
* used in [Ui\recent]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function showIconRevisions()
|
||||
{
|
||||
global $lang;
|
||||
|
||||
if (!actionOK('revisions')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$id = $this->val('id');
|
||||
if ($this->val('mode') == self::MODE_MEDIA) {
|
||||
// media file revision
|
||||
$param = ['tab_details' => 'history', 'ns' => getNS($id), 'image' => $id];
|
||||
$href = media_managerURL($param, '&');
|
||||
} elseif ($this->val('mode') == self::MODE_PAGE) {
|
||||
// page revision
|
||||
$href = wl($id, ['do' => 'revisions'], false, '&');
|
||||
}
|
||||
return '<a href="' . $href . '" class="revisions_link">'
|
||||
. '<img src="' . DOKU_BASE . 'lib/images/history.png" width="12" height="14"'
|
||||
. ' title="' . $lang['btn_revs'] . '" alt="' . $lang['btn_revs'] . '" />'
|
||||
. '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* size change
|
||||
* used in [Ui\recent, Ui\Revisions]
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function showSizeChange()
|
||||
{
|
||||
$class = 'sizechange';
|
||||
$value = filesize_h(abs($this->val('sizechange')));
|
||||
if ($this->val('sizechange') > 0) {
|
||||
$class .= ' positive';
|
||||
$value = '+' . $value;
|
||||
} elseif ($this->val('sizechange') < 0) {
|
||||
$class .= ' negative';
|
||||
$value = '-' . $value;
|
||||
} else {
|
||||
$value = '±' . $value;
|
||||
}
|
||||
return '<span class="' . $class . '">' . $value . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* current indicator, used in revision list
|
||||
* not used in Ui\Recent because recent files are always current one
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function showCurrentIndicator()
|
||||
{
|
||||
global $lang;
|
||||
return $this->isCurrent() ? '(' . $lang['current'] . ')' : '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Debug;
|
||||
|
||||
use dokuwiki\Extension\Event;
|
||||
use dokuwiki\Extension\EventHandler;
|
||||
use dokuwiki\Logger;
|
||||
|
||||
class DebugHelper
|
||||
{
|
||||
protected const INFO_DEPRECATION_LOG_EVENT = 'INFO_DEPRECATION_LOG';
|
||||
|
||||
/**
|
||||
* Check if deprecation messages shall be handled
|
||||
*
|
||||
* This is either because its logging is not disabled or a deprecation handler was registered
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isEnabled()
|
||||
{
|
||||
/** @var EventHandler $EVENT_HANDLER */
|
||||
global $EVENT_HANDLER;
|
||||
if (
|
||||
!Logger::getInstance(Logger::LOG_DEPRECATED)->isLogging() &&
|
||||
(!$EVENT_HANDLER instanceof EventHandler || !$EVENT_HANDLER->hasHandlerForEvent('INFO_DEPRECATION_LOG'))
|
||||
) {
|
||||
// avoid any work if no one cares
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log accesses to deprecated fucntions to the debug log
|
||||
*
|
||||
* @param string $alternative (optional) The function or method that should be used instead
|
||||
* @param int $callerOffset (optional) How far the deprecated method is removed from this one
|
||||
* @param string $thing (optional) The deprecated thing, defaults to the calling method
|
||||
* @triggers \dokuwiki\Debug::INFO_DEPRECATION_LOG_EVENT
|
||||
*/
|
||||
public static function dbgDeprecatedFunction($alternative = '', $callerOffset = 1, $thing = '')
|
||||
{
|
||||
if (!self::isEnabled()) return;
|
||||
|
||||
$backtrace = debug_backtrace();
|
||||
for ($i = 0; $i < $callerOffset; ++$i) {
|
||||
if (count($backtrace) > 1) array_shift($backtrace);
|
||||
}
|
||||
|
||||
[$self, $call] = $backtrace;
|
||||
|
||||
self::triggerDeprecationEvent(
|
||||
$backtrace,
|
||||
$alternative,
|
||||
self::formatCall($self),
|
||||
self::formatCall($call),
|
||||
$self['file'] ?? $call['file'] ?? '',
|
||||
$self['line'] ?? $call['line'] ?? 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given backtrace info into a proper function/method call string
|
||||
* @param array $call
|
||||
* @return string
|
||||
*/
|
||||
protected static function formatCall($call)
|
||||
{
|
||||
$thing = '';
|
||||
if (!empty($call['class'])) {
|
||||
$thing .= $call['class'] . '::';
|
||||
}
|
||||
$thing .= $call['function'] . '()';
|
||||
return trim($thing, ':');
|
||||
}
|
||||
|
||||
/**
|
||||
* This marks logs a deprecation warning for a property that should no longer be used
|
||||
*
|
||||
* This is usually called withing a magic getter or setter.
|
||||
* For logging deprecated functions or methods see dbgDeprecatedFunction()
|
||||
*
|
||||
* @param string $class The class with the deprecated property
|
||||
* @param string $propertyName The name of the deprecated property
|
||||
*
|
||||
* @triggers \dokuwiki\Debug::INFO_DEPRECATION_LOG_EVENT
|
||||
*/
|
||||
public static function dbgDeprecatedProperty($class, $propertyName)
|
||||
{
|
||||
if (!self::isEnabled()) return;
|
||||
|
||||
$backtrace = debug_backtrace();
|
||||
array_shift($backtrace);
|
||||
$call = $backtrace[1];
|
||||
$caller = trim($call['class'] . '::' . $call['function'] . '()', ':');
|
||||
$qualifiedName = $class . '::$' . $propertyName;
|
||||
self::triggerDeprecationEvent(
|
||||
$backtrace,
|
||||
'',
|
||||
$qualifiedName,
|
||||
$caller,
|
||||
$backtrace[0]['file'],
|
||||
$backtrace[0]['line']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a custom deprecation event
|
||||
*
|
||||
* Usually dbgDeprecatedFunction() or dbgDeprecatedProperty() should be used instead.
|
||||
* This method is intended only for those situation where they are not applicable.
|
||||
*
|
||||
* @param string $alternative
|
||||
* @param string $deprecatedThing
|
||||
* @param string $caller
|
||||
* @param string $file
|
||||
* @param int $line
|
||||
* @param int $callerOffset How many lines should be removed from the beginning of the backtrace
|
||||
*/
|
||||
public static function dbgCustomDeprecationEvent(
|
||||
$alternative,
|
||||
$deprecatedThing,
|
||||
$caller,
|
||||
$file,
|
||||
$line,
|
||||
$callerOffset = 1
|
||||
) {
|
||||
if (!self::isEnabled()) return;
|
||||
|
||||
$backtrace = array_slice(debug_backtrace(), $callerOffset);
|
||||
|
||||
self::triggerDeprecationEvent(
|
||||
$backtrace,
|
||||
$alternative,
|
||||
$deprecatedThing,
|
||||
$caller,
|
||||
$file,
|
||||
$line
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $backtrace
|
||||
* @param string $alternative
|
||||
* @param string $deprecatedThing
|
||||
* @param string $caller
|
||||
* @param string $file
|
||||
* @param int $line
|
||||
*/
|
||||
private static function triggerDeprecationEvent(
|
||||
array $backtrace,
|
||||
$alternative,
|
||||
$deprecatedThing,
|
||||
$caller,
|
||||
$file,
|
||||
$line
|
||||
) {
|
||||
$data = [
|
||||
'trace' => $backtrace,
|
||||
'alternative' => $alternative,
|
||||
'called' => $deprecatedThing,
|
||||
'caller' => $caller,
|
||||
'file' => $file,
|
||||
'line' => $line,
|
||||
];
|
||||
$event = new Event(self::INFO_DEPRECATION_LOG_EVENT, $data);
|
||||
if ($event->advise_before()) {
|
||||
$msg = $event->data['called'] . ' is deprecated. It was called from ';
|
||||
$msg .= $event->data['caller'] . ' in ' . $event->data['file'] . ':' . $event->data['line'];
|
||||
if ($event->data['alternative']) {
|
||||
$msg .= ' ' . $event->data['alternative'] . ' should be used instead!';
|
||||
}
|
||||
Logger::getInstance(Logger::LOG_DEPRECATED)->log($msg);
|
||||
}
|
||||
$event->advise_after();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Trait for issuing warnings on deprecated access.
|
||||
*
|
||||
* Adapted from https://github.com/wikimedia/mediawiki/blob/4aedefdbfd193f323097354bf581de1c93f02715/includes/debug/DeprecationHelper.php
|
||||
*
|
||||
*/
|
||||
|
||||
namespace dokuwiki\Debug;
|
||||
|
||||
/**
|
||||
* Use this trait in classes which have properties for which public access
|
||||
* is deprecated. Set the list of properties in $deprecatedPublicProperties
|
||||
* and make the properties non-public. The trait will preserve public access
|
||||
* but issue deprecation warnings when it is needed.
|
||||
*
|
||||
* Example usage:
|
||||
* class Foo {
|
||||
* use DeprecationHelper;
|
||||
* protected $bar;
|
||||
* public function __construct() {
|
||||
* $this->deprecatePublicProperty( 'bar', '1.21', __CLASS__ );
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* $foo = new Foo;
|
||||
* $foo->bar; // works but logs a warning
|
||||
*
|
||||
* Cannot be used with classes that have their own __get/__set methods.
|
||||
*
|
||||
*/
|
||||
trait PropertyDeprecationHelper
|
||||
{
|
||||
/**
|
||||
* List of deprecated properties, in <property name> => <class> format
|
||||
* where <class> is the the name of the class defining the property
|
||||
*
|
||||
* E.g. [ '_event' => '\dokuwiki\Cache\Cache' ]
|
||||
* @var string[]
|
||||
*/
|
||||
protected $deprecatedPublicProperties = [];
|
||||
|
||||
/**
|
||||
* Mark a property as deprecated. Only use this for properties that used to be public and only
|
||||
* call it in the constructor.
|
||||
*
|
||||
* @param string $property The name of the property.
|
||||
* @param null $class name of the class defining the property
|
||||
* @see DebugHelper::dbgDeprecatedProperty
|
||||
*/
|
||||
protected function deprecatePublicProperty(
|
||||
$property,
|
||||
$class = null
|
||||
) {
|
||||
$this->deprecatedPublicProperties[$property] = $class ?: get_class($this);
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
if (isset($this->deprecatedPublicProperties[$name])) {
|
||||
$class = $this->deprecatedPublicProperties[$name];
|
||||
DebugHelper::dbgDeprecatedProperty($class, $name);
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
$qualifiedName = get_class() . '::$' . $name;
|
||||
if ($this->deprecationHelperGetPropertyOwner($name)) {
|
||||
// Someone tried to access a normal non-public property. Try to behave like PHP would.
|
||||
trigger_error("Cannot access non-public property $qualifiedName", E_USER_ERROR);
|
||||
} else {
|
||||
// Non-existing property. Try to behave like PHP would.
|
||||
trigger_error("Undefined property: $qualifiedName", E_USER_NOTICE);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function __set($name, $value)
|
||||
{
|
||||
if (isset($this->deprecatedPublicProperties[$name])) {
|
||||
$class = $this->deprecatedPublicProperties[$name];
|
||||
DebugHelper::dbgDeprecatedProperty($class, $name);
|
||||
$this->$name = $value;
|
||||
return;
|
||||
}
|
||||
|
||||
$qualifiedName = get_class() . '::$' . $name;
|
||||
if ($this->deprecationHelperGetPropertyOwner($name)) {
|
||||
// Someone tried to access a normal non-public property. Try to behave like PHP would.
|
||||
trigger_error("Cannot access non-public property $qualifiedName", E_USER_ERROR);
|
||||
} else {
|
||||
// Non-existing property. Try to behave like PHP would.
|
||||
$this->$name = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like property_exists but also check for non-visible private properties and returns which
|
||||
* class in the inheritance chain declared the property.
|
||||
* @param string $property
|
||||
* @return string|bool Best guess for the class in which the property is defined.
|
||||
*/
|
||||
private function deprecationHelperGetPropertyOwner($property)
|
||||
{
|
||||
// Easy branch: check for protected property / private property of the current class.
|
||||
if (property_exists($this, $property)) {
|
||||
// The class name is not necessarily correct here but getting the correct class
|
||||
// name would be expensive, this will work most of the time and getting it
|
||||
// wrong is not a big deal.
|
||||
return self::class;
|
||||
}
|
||||
// property_exists() returns false when the property does exist but is private (and not
|
||||
// defined by the current class, for some value of "current" that differs slightly
|
||||
// between engines).
|
||||
// Since PHP triggers an error on public access of non-public properties but happily
|
||||
// allows public access to undefined properties, we need to detect this case as well.
|
||||
// Reflection is slow so use array cast hack to check for that:
|
||||
$obfuscatedProps = array_keys((array)$this);
|
||||
$obfuscatedPropTail = "\0$property";
|
||||
foreach ($obfuscatedProps as $obfuscatedProp) {
|
||||
// private props are in the form \0<classname>\0<propname>
|
||||
if (strpos($obfuscatedProp, $obfuscatedPropTail, 1) !== false) {
|
||||
$classname = substr($obfuscatedProp, 1, -strlen($obfuscatedPropTail));
|
||||
if ($classname === '*') {
|
||||
// sanity; this shouldn't be possible as protected properties were handled earlier
|
||||
$classname = self::class;
|
||||
}
|
||||
return $classname;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+168
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki;
|
||||
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
/**
|
||||
* Class Draft
|
||||
*
|
||||
* @package dokuwiki
|
||||
*/
|
||||
class Draft
|
||||
{
|
||||
protected $errors = [];
|
||||
protected $cname;
|
||||
protected $id;
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* Draft constructor.
|
||||
*
|
||||
* @param string $ID the page id for this draft
|
||||
* @param string $client the client identification (username or ip or similar) for this draft
|
||||
*/
|
||||
public function __construct($ID, $client)
|
||||
{
|
||||
$this->id = $ID;
|
||||
$this->client = $client;
|
||||
$this->cname = getCacheName("$client\n$ID", '.draft');
|
||||
if (file_exists($this->cname) && file_exists(wikiFN($ID))) {
|
||||
if (filemtime($this->cname) < filemtime(wikiFN($ID))) {
|
||||
// remove stale draft
|
||||
$this->deleteDraft();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename for this draft (whether or not it exists)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDraftFilename()
|
||||
{
|
||||
return $this->cname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this draft exists on the filesystem
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDraftAvailable()
|
||||
{
|
||||
return file_exists($this->cname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a draft of a current edit session
|
||||
*
|
||||
* The draft will not be saved if
|
||||
* - drafts are deactivated in the config
|
||||
* - or the editarea is empty and there are no event handlers registered
|
||||
* - or the event is prevented
|
||||
*
|
||||
* @triggers DRAFT_SAVE
|
||||
*
|
||||
* @return bool whether has the draft been saved
|
||||
*/
|
||||
public function saveDraft()
|
||||
{
|
||||
global $INPUT, $INFO, $EVENT_HANDLER, $conf;
|
||||
if (!$conf['usedraft']) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!$INPUT->post->has('wikitext') &&
|
||||
!$EVENT_HANDLER->hasHandlerForEvent('DRAFT_SAVE')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
$draft = [
|
||||
'id' => $this->id,
|
||||
'prefix' => substr($INPUT->post->str('prefix'), 0, -1),
|
||||
'text' => $INPUT->post->str('wikitext'),
|
||||
'suffix' => $INPUT->post->str('suffix'),
|
||||
'date' => $INPUT->post->int('date'),
|
||||
'client' => $this->client,
|
||||
'cname' => $this->cname,
|
||||
'errors' => [],
|
||||
];
|
||||
$event = new Event('DRAFT_SAVE', $draft);
|
||||
if ($event->advise_before()) {
|
||||
$draft['hasBeenSaved'] = io_saveFile($draft['cname'], serialize($draft));
|
||||
if ($draft['hasBeenSaved']) {
|
||||
$INFO['draft'] = $draft['cname'];
|
||||
}
|
||||
} else {
|
||||
$draft['hasBeenSaved'] = false;
|
||||
}
|
||||
$event->advise_after();
|
||||
|
||||
$this->errors = $draft['errors'];
|
||||
|
||||
return $draft['hasBeenSaved'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text from the draft file
|
||||
*
|
||||
* @throws \RuntimeException if the draft file doesn't exist
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDraftText()
|
||||
{
|
||||
if (!file_exists($this->cname)) {
|
||||
throw new \RuntimeException(
|
||||
"Draft for page $this->id and user $this->client doesn't exist at $this->cname."
|
||||
);
|
||||
}
|
||||
$draft = unserialize(io_readFile($this->cname, false));
|
||||
return cleanText(con($draft['prefix'], $draft['text'], $draft['suffix'], true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the draft from the filesystem
|
||||
*
|
||||
* Also sets $INFO['draft'] to null
|
||||
*/
|
||||
public function deleteDraft()
|
||||
{
|
||||
global $INFO;
|
||||
@unlink($this->cname);
|
||||
$INFO['draft'] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted message stating when the draft was saved
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDraftMessage()
|
||||
{
|
||||
global $lang;
|
||||
return $lang['draftdate'] . ' ' . dformat(filemtime($this->cname));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the errors that occured when saving the draft
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getErrors()
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp when this draft was saved
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDraftDate()
|
||||
{
|
||||
return filemtime($this->cname);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki;
|
||||
|
||||
use dokuwiki\Exception\FatalException;
|
||||
|
||||
/**
|
||||
* Manage the global handling of errors and exceptions
|
||||
*
|
||||
* Developer may use this to log and display exceptions themselves
|
||||
*/
|
||||
class ErrorHandler
|
||||
{
|
||||
/**
|
||||
* Standard error codes used in PHP errors
|
||||
* @see https://www.php.net/manual/en/errorfunc.constants.php
|
||||
*/
|
||||
protected const ERRORCODES = [
|
||||
1 => 'E_ERROR',
|
||||
2 => 'E_WARNING',
|
||||
4 => 'E_PARSE',
|
||||
8 => 'E_NOTICE',
|
||||
16 => 'E_CORE_ERROR',
|
||||
32 => 'E_CORE_WARNING',
|
||||
64 => 'E_COMPILE_ERROR',
|
||||
128 => 'E_COMPILE_WARNING',
|
||||
256 => 'E_USER_ERROR',
|
||||
512 => 'E_USER_WARNING',
|
||||
1024 => 'E_USER_NOTICE',
|
||||
2048 => 'E_STRICT',
|
||||
4096 => 'E_RECOVERABLE_ERROR',
|
||||
8192 => 'E_DEPRECATED',
|
||||
16384 => 'E_USER_DEPRECATED',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register the default error handling
|
||||
*/
|
||||
public static function register()
|
||||
{
|
||||
if (!defined('DOKU_UNITTEST')) {
|
||||
set_exception_handler([ErrorHandler::class, 'fatalException']);
|
||||
register_shutdown_function([ErrorHandler::class, 'fatalShutdown']);
|
||||
set_error_handler(
|
||||
[ErrorHandler::class, 'errorHandler'],
|
||||
E_WARNING | E_USER_ERROR | E_USER_WARNING | E_RECOVERABLE_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default Exception handler to show a nice user message before dieing
|
||||
*
|
||||
* The exception is logged to the error log
|
||||
*
|
||||
* @param \Throwable $e
|
||||
*/
|
||||
public static function fatalException($e)
|
||||
{
|
||||
$plugin = self::guessPlugin($e);
|
||||
$title = hsc(get_class($e) . ': ' . $e->getMessage());
|
||||
$msg = 'An unforeseen error has occured. This is most likely a bug somewhere.';
|
||||
if ($plugin) $msg .= ' It might be a problem in the ' . $plugin . ' plugin.';
|
||||
$logged = self::logException($e)
|
||||
? 'More info has been written to the DokuWiki error log.'
|
||||
: $e->getFile() . ':' . $e->getLine();
|
||||
|
||||
echo <<<EOT
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>$title</title></head>
|
||||
<body style="font-family: Arial, sans-serif">
|
||||
<div style="width:60%; margin: auto; background-color: #fcc;
|
||||
border: 1px solid #faa; padding: 0.5em 1em;">
|
||||
<h1 style="font-size: 120%">$title</h1>
|
||||
<p>$msg</p>
|
||||
<p>$logged</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to display an error message for the given Exception
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @param string $intro
|
||||
*/
|
||||
public static function showExceptionMsg($e, $intro = 'Error!')
|
||||
{
|
||||
$msg = hsc($intro) . '<br />' . hsc(get_class($e) . ': ' . $e->getMessage());
|
||||
if (self::logException($e)) $msg .= '<br />More info is available in the error log.';
|
||||
msg($msg, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Last resort to handle fatal errors that still can't be caught
|
||||
*/
|
||||
public static function fatalShutdown()
|
||||
{
|
||||
$error = error_get_last();
|
||||
// Check if it's a core/fatal error, otherwise it's a normal shutdown
|
||||
if (
|
||||
$error !== null &&
|
||||
in_array(
|
||||
$error['type'],
|
||||
[
|
||||
E_ERROR,
|
||||
E_CORE_ERROR,
|
||||
E_COMPILE_ERROR,
|
||||
]
|
||||
)
|
||||
) {
|
||||
self::fatalException(
|
||||
new FatalException($error['message'], 0, $error['type'], $error['file'], $error['line'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the given exception to the error log
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return bool false if the logging failed
|
||||
*/
|
||||
public static function logException($e)
|
||||
{
|
||||
if ($e instanceof \ErrorException) {
|
||||
$prefix = self::ERRORCODES[$e->getSeverity()];
|
||||
} else {
|
||||
$prefix = get_class($e);
|
||||
}
|
||||
|
||||
return Logger::getInstance()->log(
|
||||
$prefix . ': ' . $e->getMessage(),
|
||||
$e->getTraceAsString(),
|
||||
$e->getFile(),
|
||||
$e->getLine()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error handler to log non-exception errors
|
||||
*
|
||||
* @param int $errno
|
||||
* @param string $errstr
|
||||
* @param string $errfile
|
||||
* @param int $errline
|
||||
* @return bool
|
||||
*/
|
||||
public static function errorHandler($errno, $errstr, $errfile, $errline)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
// ignore supressed warnings
|
||||
if (!(error_reporting() & $errno)) return false;
|
||||
|
||||
$ex = new \ErrorException(
|
||||
$errstr,
|
||||
0,
|
||||
$errno,
|
||||
$errfile,
|
||||
$errline
|
||||
);
|
||||
self::logException($ex);
|
||||
|
||||
if ($ex->getSeverity() === E_WARNING && $conf['hidewarnings']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the the stacktrace for plugin files
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return false|string
|
||||
*/
|
||||
protected static function guessPlugin($e)
|
||||
{
|
||||
if (preg_match('/lib\/plugins\/(\w+)\//', str_replace('\\', '/', $e->getFile()), $match)) {
|
||||
return $match[1];
|
||||
}
|
||||
|
||||
foreach ($e->getTrace() as $line) {
|
||||
if (
|
||||
isset($line['class']) &&
|
||||
preg_match('/\w+?_plugin_(\w+)/', $line['class'], $match)
|
||||
) {
|
||||
return $match[1];
|
||||
}
|
||||
|
||||
if (
|
||||
isset($line['file']) &&
|
||||
preg_match('/lib\/plugins\/(\w+)\//', str_replace('\\', '/', $line['file']), $match)
|
||||
) {
|
||||
return $match[1];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Exception;
|
||||
|
||||
/**
|
||||
* Fatal Errors are converted into this Exception in out Shutdown handler
|
||||
*/
|
||||
class FatalException extends \ErrorException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Extension;
|
||||
|
||||
/**
|
||||
* Action Plugin Prototype
|
||||
*
|
||||
* Handles action hooks within a plugin
|
||||
*
|
||||
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||||
* @author Christopher Smith <chris@jalakai.co.uk>
|
||||
*/
|
||||
abstract class ActionPlugin extends Plugin
|
||||
{
|
||||
/**
|
||||
* Registers a callback function for a given event
|
||||
*
|
||||
* @param EventHandler $controller
|
||||
*/
|
||||
abstract public function register(EventHandler $controller);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Extension;
|
||||
|
||||
/**
|
||||
* Admin Plugin Prototype
|
||||
*
|
||||
* Implements an admin interface in a plugin
|
||||
*
|
||||
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||||
* @author Christopher Smith <chris@jalakai.co.uk>
|
||||
*/
|
||||
abstract class AdminPlugin extends Plugin
|
||||
{
|
||||
/**
|
||||
* Return the text that is displayed at the main admin menu
|
||||
* (Default localized language string 'menu' is returned, override this function for setting another name)
|
||||
*
|
||||
* @param string $language language code
|
||||
* @return string menu string
|
||||
*/
|
||||
public function getMenuText($language)
|
||||
{
|
||||
$menutext = $this->getLang('menu');
|
||||
if (!$menutext) {
|
||||
$info = $this->getInfo();
|
||||
$menutext = $info['name'] . ' ...';
|
||||
}
|
||||
return $menutext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path to the icon being displayed in the main admin menu.
|
||||
* By default it tries to find an 'admin.svg' file in the plugin directory.
|
||||
* (Override this function for setting another image)
|
||||
*
|
||||
* Important: you have to return a single path, monochrome SVG icon! It has to be
|
||||
* under 2 Kilobytes!
|
||||
*
|
||||
* We recommend icons from https://materialdesignicons.com/ or to use a matching
|
||||
* style.
|
||||
*
|
||||
* @return string full path to the icon file
|
||||
*/
|
||||
public function getMenuIcon()
|
||||
{
|
||||
$plugin = $this->getPluginName();
|
||||
return DOKU_PLUGIN . $plugin . '/admin.svg';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine position in list in admin window
|
||||
* Lower values are sorted up
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMenuSort()
|
||||
{
|
||||
return 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Carry out required processing
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// some plugins might not need this
|
||||
}
|
||||
|
||||
/**
|
||||
* Output html of the admin page
|
||||
*/
|
||||
abstract public function html();
|
||||
|
||||
/**
|
||||
* Checks if access should be granted to this admin plugin
|
||||
*
|
||||
* @return bool true if the current user may access this admin plugin
|
||||
*/
|
||||
public function isAccessibleByCurrentUser()
|
||||
{
|
||||
$data = [];
|
||||
$data['instance'] = $this;
|
||||
$data['hasAccess'] = false;
|
||||
|
||||
$event = new Event('ADMINPLUGIN_ACCESS_CHECK', $data);
|
||||
if ($event->advise_before()) {
|
||||
if ($this->forAdminOnly()) {
|
||||
$data['hasAccess'] = auth_isadmin();
|
||||
} else {
|
||||
$data['hasAccess'] = auth_ismanager();
|
||||
}
|
||||
}
|
||||
$event->advise_after();
|
||||
|
||||
return $data['hasAccess'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true for access only by admins (config:superuser) or false if managers are allowed as well
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function forAdminOnly()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array with ToC items. Items can be created with the html_mktocitem()
|
||||
*
|
||||
* @see html_mktocitem()
|
||||
* @see tpl_toc()
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTOC()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Extension;
|
||||
|
||||
/**
|
||||
* Auth Plugin Prototype
|
||||
*
|
||||
* allows to authenticate users in a plugin
|
||||
*
|
||||
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||||
* @author Chris Smith <chris@jalakai.co.uk>
|
||||
* @author Jan Schumann <js@jschumann-it.com>
|
||||
*/
|
||||
abstract class AuthPlugin extends Plugin
|
||||
{
|
||||
public $success = true;
|
||||
|
||||
/**
|
||||
* Possible things an auth backend module may be able to
|
||||
* do. The things a backend can do need to be set to true
|
||||
* in the constructor.
|
||||
*/
|
||||
protected $cando = [
|
||||
'addUser' => false, // can Users be created?
|
||||
'delUser' => false, // can Users be deleted?
|
||||
'modLogin' => false, // can login names be changed?
|
||||
'modPass' => false, // can passwords be changed?
|
||||
'modName' => false, // can real names be changed?
|
||||
'modMail' => false, // can emails be changed?
|
||||
'modGroups' => false, // can groups be changed?
|
||||
'getUsers' => false, // can a (filtered) list of users be retrieved?
|
||||
'getUserCount' => false, // can the number of users be retrieved?
|
||||
'getGroups' => false, // can a list of available groups be retrieved?
|
||||
'external' => false, // does the module do external auth checking?
|
||||
'logout' => true, // can the user logout again? (eg. not possible with HTTP auth)
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Carry out sanity checks to ensure the object is
|
||||
* able to operate. Set capabilities in $this->cando
|
||||
* array here
|
||||
*
|
||||
* For future compatibility, sub classes should always include a call
|
||||
* to parent::__constructor() in their constructors!
|
||||
*
|
||||
* Set $this->success to false if checks fail
|
||||
*
|
||||
* @author Christopher Smith <chris@jalakai.co.uk>
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// the base class constructor does nothing, derived class
|
||||
// constructors do the real work
|
||||
}
|
||||
|
||||
/**
|
||||
* Available Capabilities. [ DO NOT OVERRIDE ]
|
||||
*
|
||||
* For introspection/debugging
|
||||
*
|
||||
* @author Christopher Smith <chris@jalakai.co.uk>
|
||||
* @return array
|
||||
*/
|
||||
public function getCapabilities()
|
||||
{
|
||||
return array_keys($this->cando);
|
||||
}
|
||||
|
||||
/**
|
||||
* Capability check. [ DO NOT OVERRIDE ]
|
||||
*
|
||||
* Checks the capabilities set in the $this->cando array and
|
||||
* some pseudo capabilities (shortcutting access to multiple
|
||||
* ones)
|
||||
*
|
||||
* ususal capabilities start with lowercase letter
|
||||
* shortcut capabilities start with uppercase letter
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @param string $cap the capability to check
|
||||
* @return bool
|
||||
*/
|
||||
public function canDo($cap)
|
||||
{
|
||||
switch ($cap) {
|
||||
case 'Profile':
|
||||
// can at least one of the user's properties be changed?
|
||||
return ($this->cando['modPass'] ||
|
||||
$this->cando['modName'] ||
|
||||
$this->cando['modMail']);
|
||||
case 'UserMod':
|
||||
// can at least anything be changed?
|
||||
return ($this->cando['modPass'] ||
|
||||
$this->cando['modName'] ||
|
||||
$this->cando['modMail'] ||
|
||||
$this->cando['modLogin'] ||
|
||||
$this->cando['modGroups'] ||
|
||||
$this->cando['modMail']);
|
||||
default:
|
||||
// print a helping message for developers
|
||||
if (!isset($this->cando[$cap])) {
|
||||
msg("Check for unknown capability '$cap' - Do you use an outdated Plugin?", -1);
|
||||
}
|
||||
return $this->cando[$cap];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger the AUTH_USERDATA_CHANGE event and call the modification function. [ DO NOT OVERRIDE ]
|
||||
*
|
||||
* You should use this function instead of calling createUser, modifyUser or
|
||||
* deleteUsers directly. The event handlers can prevent the modification, for
|
||||
* example for enforcing a user name schema.
|
||||
*
|
||||
* @author Gabriel Birke <birke@d-scribe.de>
|
||||
* @param string $type Modification type ('create', 'modify', 'delete')
|
||||
* @param array $params Parameters for the createUser, modifyUser or deleteUsers method.
|
||||
* The content of this array depends on the modification type
|
||||
* @return bool|null|int Result from the modification function or false if an event handler has canceled the action
|
||||
*/
|
||||
public function triggerUserMod($type, $params)
|
||||
{
|
||||
$validTypes = [
|
||||
'create' => 'createUser',
|
||||
'modify' => 'modifyUser',
|
||||
'delete' => 'deleteUsers'
|
||||
];
|
||||
if (empty($validTypes[$type])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = false;
|
||||
$eventdata = ['type' => $type, 'params' => $params, 'modification_result' => null];
|
||||
$evt = new Event('AUTH_USER_CHANGE', $eventdata);
|
||||
if ($evt->advise_before(true)) {
|
||||
$result = call_user_func_array([$this, $validTypes[$type]], $evt->data['params']);
|
||||
$evt->data['modification_result'] = $result;
|
||||
}
|
||||
$evt->advise_after();
|
||||
unset($evt);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log off the current user [ OPTIONAL ]
|
||||
*
|
||||
* Is run in addition to the ususal logoff method. Should
|
||||
* only be needed when trustExternal is implemented.
|
||||
*
|
||||
* @see auth_logoff()
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
public function logOff()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Do all authentication [ OPTIONAL ]
|
||||
*
|
||||
* Set $this->cando['external'] = true when implemented
|
||||
*
|
||||
* If this function is implemented it will be used to
|
||||
* authenticate a user - all other DokuWiki internals
|
||||
* will not be used for authenticating (except this
|
||||
* function returns null, in which case, DokuWiki will
|
||||
* still run auth_login as a fallback, which may call
|
||||
* checkPass()). If this function is not returning null,
|
||||
* implementing checkPass() is not needed here anymore.
|
||||
*
|
||||
* The function can be used to authenticate against third
|
||||
* party cookies or Apache auth mechanisms and replaces
|
||||
* the auth_login() function
|
||||
*
|
||||
* The function will be called with or without a set
|
||||
* username. If the Username is given it was called
|
||||
* from the login form and the given credentials might
|
||||
* need to be checked. If no username was given it
|
||||
* the function needs to check if the user is logged in
|
||||
* by other means (cookie, environment).
|
||||
*
|
||||
* The function needs to set some globals needed by
|
||||
* DokuWiki like auth_login() does.
|
||||
*
|
||||
* @see auth_login()
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
* @param string $user Username
|
||||
* @param string $pass Cleartext Password
|
||||
* @param bool $sticky Cookie should not expire
|
||||
* @return bool true on successful auth,
|
||||
* null on unknown result (fallback to checkPass)
|
||||
*/
|
||||
public function trustExternal($user, $pass, $sticky = false)
|
||||
{
|
||||
/* some example:
|
||||
|
||||
global $USERINFO;
|
||||
global $conf;
|
||||
$sticky ? $sticky = true : $sticky = false; //sanity check
|
||||
|
||||
// do the checking here
|
||||
|
||||
// set the globals if authed
|
||||
$USERINFO['name'] = 'FIXME';
|
||||
$USERINFO['mail'] = 'FIXME';
|
||||
$USERINFO['grps'] = array('FIXME');
|
||||
$_SERVER['REMOTE_USER'] = $user;
|
||||
$_SESSION[DOKU_COOKIE]['auth']['user'] = $user;
|
||||
$_SESSION[DOKU_COOKIE]['auth']['pass'] = $pass;
|
||||
$_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO;
|
||||
return true;
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user+password [ MUST BE OVERRIDDEN ]
|
||||
*
|
||||
* Checks if the given user exists and the given
|
||||
* plaintext password is correct
|
||||
*
|
||||
* May be ommited if trustExternal is used.
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @param string $user the user name
|
||||
* @param string $pass the clear text password
|
||||
* @return bool
|
||||
*/
|
||||
public function checkPass($user, $pass)
|
||||
{
|
||||
msg("no valid authorisation system in use", -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return user info [ MUST BE OVERRIDDEN ]
|
||||
*
|
||||
* Returns info about the given user needs to contain
|
||||
* at least these fields:
|
||||
*
|
||||
* name string full name of the user
|
||||
* mail string email address of the user
|
||||
* grps array list of groups the user is in
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @param string $user the user name
|
||||
* @param bool $requireGroups whether or not the returned data must include groups
|
||||
* @return false|array containing user data or false
|
||||
*/
|
||||
public function getUserData($user, $requireGroups = true)
|
||||
{
|
||||
if (!$this->cando['external']) msg("no valid authorisation system in use", -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new User [implement only where required/possible]
|
||||
*
|
||||
* Returns false if the user already exists, null when an error
|
||||
* occurred and true if everything went well.
|
||||
*
|
||||
* The new user HAS TO be added to the default group by this
|
||||
* function!
|
||||
*
|
||||
* Set addUser capability when implemented
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @param string $user
|
||||
* @param string $pass
|
||||
* @param string $name
|
||||
* @param string $mail
|
||||
* @param null|array $grps
|
||||
* @return bool|null
|
||||
*/
|
||||
public function createUser($user, $pass, $name, $mail, $grps = null)
|
||||
{
|
||||
msg("authorisation method does not allow creation of new users", -1);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify user data [implement only where required/possible]
|
||||
*
|
||||
* Set the mod* capabilities according to the implemented features
|
||||
*
|
||||
* @author Chris Smith <chris@jalakai.co.uk>
|
||||
* @param string $user nick of the user to be changed
|
||||
* @param array $changes array of field/value pairs to be changed (password will be clear text)
|
||||
* @return bool
|
||||
*/
|
||||
public function modifyUser($user, $changes)
|
||||
{
|
||||
msg("authorisation method does not allow modifying of user data", -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete one or more users [implement only where required/possible]
|
||||
*
|
||||
* Set delUser capability when implemented
|
||||
*
|
||||
* @author Chris Smith <chris@jalakai.co.uk>
|
||||
* @param array $users
|
||||
* @return int number of users deleted
|
||||
*/
|
||||
public function deleteUsers($users)
|
||||
{
|
||||
msg("authorisation method does not allow deleting of users", -1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a count of the number of user which meet $filter criteria
|
||||
* [should be implemented whenever retrieveUsers is implemented]
|
||||
*
|
||||
* Set getUserCount capability when implemented
|
||||
*
|
||||
* @author Chris Smith <chris@jalakai.co.uk>
|
||||
* @param array $filter array of field/pattern pairs, empty array for no filter
|
||||
* @return int
|
||||
*/
|
||||
public function getUserCount($filter = [])
|
||||
{
|
||||
msg("authorisation method does not provide user counts", -1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk retrieval of user data [implement only where required/possible]
|
||||
*
|
||||
* Set getUsers capability when implemented
|
||||
*
|
||||
* @author Chris Smith <chris@jalakai.co.uk>
|
||||
* @param int $start index of first user to be returned
|
||||
* @param int $limit max number of users to be returned, 0 for unlimited
|
||||
* @param array $filter array of field/pattern pairs, null for no filter
|
||||
* @return array list of userinfo (refer getUserData for internal userinfo details)
|
||||
*/
|
||||
public function retrieveUsers($start = 0, $limit = 0, $filter = null)
|
||||
{
|
||||
msg("authorisation method does not support mass retrieval of user data", -1);
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a group [implement only where required/possible]
|
||||
*
|
||||
* Set addGroup capability when implemented
|
||||
*
|
||||
* @author Chris Smith <chris@jalakai.co.uk>
|
||||
* @param string $group
|
||||
* @return bool
|
||||
*/
|
||||
public function addGroup($group)
|
||||
{
|
||||
msg("authorisation method does not support independent group creation", -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve groups [implement only where required/possible]
|
||||
*
|
||||
* Set getGroups capability when implemented
|
||||
*
|
||||
* @author Chris Smith <chris@jalakai.co.uk>
|
||||
* @param int $start
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function retrieveGroups($start = 0, $limit = 0)
|
||||
{
|
||||
msg("authorisation method does not support group list retrieval", -1);
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return case sensitivity of the backend [OPTIONAL]
|
||||
*
|
||||
* When your backend is caseinsensitive (eg. you can login with USER and
|
||||
* user) then you need to overwrite this method and return false
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCaseSensitive()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a given username [OPTIONAL]
|
||||
*
|
||||
* This function is applied to any user name that is given to
|
||||
* the backend and should also be applied to any user name within
|
||||
* the backend before returning it somewhere.
|
||||
*
|
||||
* This should be used to enforce username restrictions.
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @param string $user username
|
||||
* @return string the cleaned username
|
||||
*/
|
||||
public function cleanUser($user)
|
||||
{
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a given groupname [OPTIONAL]
|
||||
*
|
||||
* This function is applied to any groupname that is given to
|
||||
* the backend and should also be applied to any groupname within
|
||||
* the backend before returning it somewhere.
|
||||
*
|
||||
* This should be used to enforce groupname restrictions.
|
||||
*
|
||||
* Groupnames are to be passed without a leading '@' here.
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @param string $group groupname
|
||||
* @return string the cleaned groupname
|
||||
*/
|
||||
public function cleanGroup($group)
|
||||
{
|
||||
return $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Session Cache validity [implement only where required/possible]
|
||||
*
|
||||
* DokuWiki caches user info in the user's session for the timespan defined
|
||||
* in $conf['auth_security_timeout'].
|
||||
*
|
||||
* This makes sure slow authentication backends do not slow down DokuWiki.
|
||||
* This also means that changes to the user database will not be reflected
|
||||
* on currently logged in users.
|
||||
*
|
||||
* To accommodate for this, the user manager plugin will touch a reference
|
||||
* file whenever a change is submitted. This function compares the filetime
|
||||
* of this reference file with the time stored in the session.
|
||||
*
|
||||
* This reference file mechanism does not reflect changes done directly in
|
||||
* the backend's database through other means than the user manager plugin.
|
||||
*
|
||||
* Fast backends might want to return always false, to force rechecks on
|
||||
* each page load. Others might want to use their own checking here. If
|
||||
* unsure, do not override.
|
||||
*
|
||||
* @param string $user - The username
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @return bool
|
||||
*/
|
||||
public function useSessionCache($user)
|
||||
{
|
||||
global $conf;
|
||||
return ($_SESSION[DOKU_COOKIE]['auth']['time'] >= @filemtime($conf['cachedir'] . '/sessionpurge'));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user