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