@@ -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> |
@@ -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(); |
@@ -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(); |
@@ -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(); |
@@ -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(); |
@@ -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(); |
@@ -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(); |
@@ -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; | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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')); | |||||
} | |||||
} |