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