Upgrade to 2022-07-31a Igor

This commit is contained in:
Niklas Keller
2023-03-28 21:15:28 +02:00
parent d19fe6497b
commit e74503e863
814 changed files with 24732 additions and 9800 deletions

16
dokuwiki/SECURITY.md Normal file
View File

@@ -0,0 +1,16 @@
# Security Policy
Security vulnerabilities can be reported for the current stable release (branch `stable`) and the `master` branch.
We try to fix vulnerabilites as fast as possible, but please keep in mind that this is a project run by volunteers. Depending on the severity we may release hotfixes for the current stable release or may simply incorporate the fix in the next proper release.
**This policy only applies to DokuWiki and the bundled plugins. Do not report issues about 3rd party plugins here.**
## Reporting a Vulnerability
You have multiple options on reporting vulnerabilities
* Use [huntr.dev](https://www.huntr.dev/bounties/disclose/?target=https%3A%2F%2Fgithub.com%2Fsplitbrain%2Fdokuwiki%2F)
* Send an e-mail to [Andi](mailto:andi@splitbrain.org)
* Open a [Github Issue](https://github.com/splitbrain/dokuwiki/issues)
* Send a mail to the [Mailing List](https://www.dokuwiki.org/mailinglist)

View File

@@ -1 +1 @@
2020-07-29a "Hogfather"
2022-07-31a "Igor"

View File

@@ -118,6 +118,27 @@ class PageCLI extends CLI {
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'
);
}
/**
@@ -160,6 +181,13 @@ class PageCLI extends CLI {
$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();
}

View File

@@ -1,6 +1,8 @@
#!/usr/bin/env php
<?php
use dokuwiki\Utf8\Sort;
use dokuwiki\File\PageResolver;
use splitbrain\phpcli\CLI;
use splitbrain\phpcli\Options;
@@ -77,13 +79,13 @@ class WantedPagesCLI extends CLI {
foreach($this->getPages($startdir) as $page) {
$this->internalLinks($page);
}
ksort($this->result);
Sort::ksort($this->result);
foreach($this->result as $main => $subs) {
if($this->skip) {
print "$main\n";
} else {
$subs = array_unique($subs);
sort($subs);
Sort::sort($subs);
foreach($subs as $sub) {
printf("%-40s %s\n", $main, $sub);
}
@@ -160,14 +162,12 @@ class WantedPagesCLI extends CLI {
protected function internalLinks($page) {
global $conf;
$instructions = p_get_instructions(file_get_contents($page['file']));
$cns = getNS($page['id']);
$exists = false;
$resolver = new PageResolver($page['id']);
$pid = $page['id'];
foreach($instructions as $ins) {
if($ins[0] == 'internallink' || ($conf['camelcase'] && $ins[0] == 'camelcaselink')) {
$mid = $ins[1][0];
resolve_pageid($cns, $mid, $exists);
if(!$exists) {
$mid = $resolver->resolveId($ins[1][0]);
if(!page_exists($mid)) {
list($mid) = explode('#', $mid); //record pages without hashes
if($this->sort == 'origin') {

33
dokuwiki/composer.json Normal file
View File

@@ -0,0 +1,33 @@
{
"name": "splitbrain/dokuwiki",
"description": "DokuWiki is a simple to use and highly versatile Open Source wiki software that doesn't require a database",
"homepage": "https://www.dokuwiki.org",
"type": "project",
"license": "GPL v2",
"require": {
"php": ">=7.2",
"ext-json": "*",
"splitbrain/php-archive": "~1.0",
"phpseclib/phpseclib": "~2.0",
"simplepie/simplepie": "^1.4",
"geshi/geshi": "dev-master as 1.0.x-dev",
"openpsa/universalfeedcreator": "^1.8",
"aziraphale/email-address-validator": "^2",
"marcusschwarz/lesserphp": "^0.5.1",
"splitbrain/php-cli": "^1.1",
"splitbrain/slika": "^1.0",
"kissifrot/php-ixr": "^1.8"
},
"config": {
"platform": {
"php": "7.2"
}
},
"suggest": {
"ext-mbstring": "The mbstring extension is highly reccomended to speed up all UTF-8 handling",
"ext-intl": "The intl extension allows for better locale specific sorting",
"squizlabs/php_codesniffer": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
"phpunit/phpunit": "Allows automated tests to be run without system-wide install (only version 4-7 are supported)."
},
"prefer-stable": true
}

621
dokuwiki/composer.lock generated Normal file
View File

@@ -0,0 +1,621 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "09f5f3726c3993d80980df99ab2a59dc",
"packages": [
{
"name": "aziraphale/email-address-validator",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/aziraphale/email-address-validator.git",
"reference": "fa25bc22c1c0b6491657c91473fae3e40719a650"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aziraphale/email-address-validator/zipball/fa25bc22c1c0b6491657c91473fae3e40719a650",
"reference": "fa25bc22c1c0b6491657c91473fae3e40719a650",
"shasum": ""
},
"require-dev": {
"phpunit/phpunit": "^5.7"
},
"type": "library",
"autoload": {
"psr-0": {
"EmailAddressValidator": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Dave Child",
"email": "dave@addedbytes.com"
},
{
"name": "Andrew Gillard",
"email": "andrew@lorddeath.net"
}
],
"description": "Fork of AddedBytes' PHP EmailAddressValidator script, now with Composer support!",
"homepage": "https://github.com/aziraphale/email-address-validator",
"support": {
"issues": "https://github.com/aziraphale/email-address-validator/issues",
"source": "https://github.com/aziraphale/email-address-validator/tree/master"
},
"time": "2017-05-22T14:05:57+00:00"
},
{
"name": "geshi/geshi",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/GeSHi/geshi-1.0.git",
"reference": "3c12a7931d509c5e3557c5ed44c9a32e9c917c7d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GeSHi/geshi-1.0/zipball/3c12a7931d509c5e3557c5ed44c9a32e9c917c7d",
"reference": "3c12a7931d509c5e3557c5ed44c9a32e9c917c7d",
"shasum": ""
},
"require-dev": {
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.2"
},
"default-branch": true,
"type": "library",
"autoload": {
"classmap": [
"src/geshi/",
"src/geshi.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0+"
],
"authors": [
{
"name": "Benny Baumann",
"email": "BenBE@geshi.org",
"homepage": "http://blog.benny-baumann.de/",
"role": "Developer"
}
],
"description": "Generic Syntax Highlighter",
"homepage": "http://qbnz.com/highlighter/",
"support": {
"forum": "https://lists.sourceforge.net/lists/listinfo/geshi-users",
"irc": "irc://irc.freenode.org/geshi",
"issues": "https://sourceforge.net/p/geshi/feature-requests/",
"source": "https://github.com/GeSHi/geshi-1.0/tree/master"
},
"time": "2020-06-22T15:46:04+00:00"
},
{
"name": "kissifrot/php-ixr",
"version": "1.8.3",
"source": {
"type": "git",
"url": "https://github.com/kissifrot/php-ixr.git",
"reference": "4477cd1a67416ce5b6a2080f9a79d9eb50a965c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kissifrot/php-ixr/zipball/4477cd1a67416ce5b6a2080f9a79d9eb50a965c1",
"reference": "4477cd1a67416ce5b6a2080f9a79d9eb50a965c1",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"type": "library",
"autoload": {
"psr-4": {
"IXR\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Incutio Ltd 2010 - Simon Willison",
"homepage": "http://scripts.incutio.com/xmlrpc/"
}
],
"description": "Incutio XML-RPC library (IXR)",
"homepage": "http://scripts.incutio.com/xmlrpc/",
"keywords": [
"remote procedure call",
"rpc",
"xlm-rpc",
"xmlrpc"
],
"support": {
"issues": "https://github.com/kissifrot/php-ixr/issues",
"source": "https://github.com/kissifrot/php-ixr/tree/master"
},
"time": "2016-11-17T12:00:18+00:00"
},
{
"name": "marcusschwarz/lesserphp",
"version": "v0.5.5",
"source": {
"type": "git",
"url": "https://github.com/MarcusSchwarz/lesserphp.git",
"reference": "77ba82b5218ff228267d3b0e5ec8697be75e86a7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarcusSchwarz/lesserphp/zipball/77ba82b5218ff228267d3b0e5ec8697be75e86a7",
"reference": "77ba82b5218ff228267d3b0e5ec8697be75e86a7",
"shasum": ""
},
"require-dev": {
"phpunit/phpunit": ">=4.8.35 <8"
},
"bin": [
"plessc"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.5.1-dev"
}
},
"autoload": {
"classmap": [
"lessc.inc.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT",
"GPL-3.0"
],
"authors": [
{
"name": "Leaf Corcoran",
"email": "leafot@gmail.com",
"homepage": "http://leafo.net"
},
{
"name": "Marcus Schwarz",
"email": "github@maswaba.de",
"homepage": "https://www.maswaba.de"
}
],
"description": "lesserphp is a compiler for LESS written in PHP based on leafo's lessphp.",
"homepage": "http://leafo.net/lessphp/",
"support": {
"issues": "https://github.com/MarcusSchwarz/lesserphp/issues",
"source": "https://github.com/MarcusSchwarz/lesserphp/tree/v0.5.5"
},
"time": "2021-03-10T17:56:57+00:00"
},
{
"name": "openpsa/universalfeedcreator",
"version": "v1.8.4.1",
"source": {
"type": "git",
"url": "https://github.com/flack/UniversalFeedCreator.git",
"reference": "e4736a68eef454a83acd100230a2e15b424f899f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/flack/UniversalFeedCreator/zipball/e4736a68eef454a83acd100230a2e15b424f899f",
"reference": "e4736a68eef454a83acd100230a2e15b424f899f",
"shasum": ""
},
"require": {
"php": ">=5.0"
},
"require-dev": {
"phpunit/phpunit": "*"
},
"type": "library",
"autoload": {
"files": [
"lib/constants.php"
],
"classmap": [
"lib"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "Andreas Flack",
"email": "flack@contentcontrol-berlin.de",
"homepage": "http://www.contentcontrol-berlin.de/"
}
],
"description": "RSS and Atom feed generator by Kai Blankenhorn",
"keywords": [
"atom",
"georss",
"gpx",
"opml",
"pie",
"rss"
],
"support": {
"issues": "https://github.com/flack/UniversalFeedCreator/issues",
"source": "https://github.com/flack/UniversalFeedCreator/tree/v1.8.4.1"
},
"time": "2022-04-04T10:02:43+00:00"
},
{
"name": "phpseclib/phpseclib",
"version": "2.0.31",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "233a920cb38636a43b18d428f9a8db1f0a1a08f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/233a920cb38636a43b18d428f9a8db1f0a1a08f4",
"reference": "233a920cb38636a43b18d428f9a8db1f0a1a08f4",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phing/phing": "~2.7",
"phpunit/phpunit": "^4.8.35|^5.7|^6.0|^9.4",
"squizlabs/php_codesniffer": "~2.0"
},
"suggest": {
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
},
"type": "library",
"autoload": {
"files": [
"phpseclib/bootstrap.php"
],
"psr-4": {
"phpseclib\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jim Wigginton",
"email": "terrafrost@php.net",
"role": "Lead Developer"
},
{
"name": "Patrick Monnerat",
"email": "pm@datasphere.ch",
"role": "Developer"
},
{
"name": "Andreas Fischer",
"email": "bantu@phpbb.com",
"role": "Developer"
},
{
"name": "Hans-Jürgen Petrich",
"email": "petrich@tronic-media.com",
"role": "Developer"
},
{
"name": "Graham Campbell",
"email": "graham@alt-three.com",
"role": "Developer"
}
],
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
"homepage": "http://phpseclib.sourceforge.net",
"keywords": [
"BigInteger",
"aes",
"asn.1",
"asn1",
"blowfish",
"crypto",
"cryptography",
"encryption",
"rsa",
"security",
"sftp",
"signature",
"signing",
"ssh",
"twofish",
"x.509",
"x509"
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/2.0.31"
},
"funding": [
{
"url": "https://github.com/terrafrost",
"type": "github"
},
{
"url": "https://www.patreon.com/phpseclib",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
"type": "tidelift"
}
],
"time": "2021-04-06T13:56:45+00:00"
},
{
"name": "simplepie/simplepie",
"version": "1.5.6",
"source": {
"type": "git",
"url": "https://github.com/simplepie/simplepie.git",
"reference": "1c68e14ca3ac84346b6e6fe3c5eedf725d0f92c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/simplepie/simplepie/zipball/1c68e14ca3ac84346b6e6fe3c5eedf725d0f92c6",
"reference": "1c68e14ca3ac84346b6e6fe3c5eedf725d0f92c6",
"shasum": ""
},
"require": {
"ext-pcre": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"php": ">=5.6.0"
},
"require-dev": {
"phpunit/phpunit": "~5.4.3 || ~6.5"
},
"suggest": {
"ext-curl": "",
"ext-iconv": "",
"ext-intl": "",
"ext-mbstring": "",
"mf2/mf2": "Microformat module that allows for parsing HTML for microformats"
},
"type": "library",
"autoload": {
"psr-0": {
"SimplePie": "library"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Ryan Parman",
"homepage": "http://ryanparman.com/",
"role": "Creator, alumnus developer"
},
{
"name": "Sam Sneddon",
"homepage": "https://gsnedders.com/",
"role": "Alumnus developer"
},
{
"name": "Ryan McCue",
"email": "me@ryanmccue.info",
"homepage": "http://ryanmccue.info/",
"role": "Developer"
}
],
"description": "A simple Atom/RSS parsing library for PHP",
"homepage": "http://simplepie.org/",
"keywords": [
"atom",
"feeds",
"rss"
],
"support": {
"issues": "https://github.com/simplepie/simplepie/issues",
"source": "https://github.com/simplepie/simplepie/tree/1.5.6"
},
"time": "2020-10-14T07:17:22+00:00"
},
{
"name": "splitbrain/php-archive",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/splitbrain/php-archive.git",
"reference": "211a2198b73b233d7d2b6159462e11cd9a91348a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/splitbrain/php-archive/zipball/211a2198b73b233d7d2b6159462e11cd9a91348a",
"reference": "211a2198b73b233d7d2b6159462e11cd9a91348a",
"shasum": ""
},
"require": {
"php": ">=5.4"
},
"require-dev": {
"ext-bz2": "*",
"ext-zip": "*",
"mikey179/vfsstream": "^1.6",
"phpunit/phpunit": "^8"
},
"suggest": {
"ext-iconv": "Used for proper filename encode handling",
"ext-mbstring": "Can be used alternatively for handling filename encoding"
},
"type": "library",
"autoload": {
"psr-4": {
"splitbrain\\PHPArchive\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Andreas Gohr",
"email": "andi@splitbrain.org"
}
],
"description": "Pure-PHP implementation to read and write TAR and ZIP archives",
"keywords": [
"archive",
"extract",
"tar",
"unpack",
"unzip",
"zip"
],
"support": {
"issues": "https://github.com/splitbrain/php-archive/issues",
"source": "https://github.com/splitbrain/php-archive/tree/1.2.1"
},
"time": "2021-02-22T17:59:24+00:00"
},
{
"name": "splitbrain/php-cli",
"version": "1.1.8",
"source": {
"type": "git",
"url": "https://github.com/splitbrain/php-cli.git",
"reference": "8c2c001b1b55d194402cf18aad2757049ac6d575"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/splitbrain/php-cli/zipball/8c2c001b1b55d194402cf18aad2757049ac6d575",
"reference": "8c2c001b1b55d194402cf18aad2757049ac6d575",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^8"
},
"suggest": {
"psr/log": "Allows you to make the CLI available as PSR-3 logger"
},
"type": "library",
"autoload": {
"psr-4": {
"splitbrain\\phpcli\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Andreas Gohr",
"email": "andi@splitbrain.org"
}
],
"description": "Easy command line scripts for PHP with opt parsing and color output. No dependencies",
"keywords": [
"argparse",
"cli",
"command line",
"console",
"getopt",
"optparse",
"terminal"
],
"support": {
"issues": "https://github.com/splitbrain/php-cli/issues",
"source": "https://github.com/splitbrain/php-cli/tree/1.1.8"
},
"time": "2021-02-05T12:02:46+00:00"
},
{
"name": "splitbrain/slika",
"version": "1.0.5",
"source": {
"type": "git",
"url": "https://github.com/splitbrain/slika.git",
"reference": "be0785cb6b7def847df5d92e0e5fde57def7220f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/splitbrain/slika/zipball/be0785cb6b7def847df5d92e0e5fde57def7220f",
"reference": "be0785cb6b7def847df5d92e0e5fde57def7220f",
"shasum": ""
},
"require-dev": {
"phpunit/phpunit": "^8.0"
},
"suggest": {
"ext-gd": "PHP's builtin image manipulation library. Alternatively use an installation of ImageMagick"
},
"type": "library",
"autoload": {
"psr-4": {
"splitbrain\\slika\\": "src",
"splitbrain\\slika\\tests\\": "tests"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Andreas Gohr",
"email": "andi@splitbrain.org"
}
],
"description": "Simple image resizing",
"support": {
"issues": "https://github.com/splitbrain/slika/issues",
"source": "https://github.com/splitbrain/slika/tree/1.0.5"
},
"time": "2022-02-04T22:41:26+00:00"
}
],
"packages-dev": [],
"aliases": [
{
"package": "geshi/geshi",
"version": "9999999-dev",
"alias": "1.0.x-dev",
"alias_normalized": "1.0.9999999.9999999-dev"
}
],
"minimum-stability": "stable",
"stability-flags": {
"geshi/geshi": 20
},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": ">=7.2",
"ext-json": "*"
},
"platform-dev": [],
"platform-overrides": {
"php": "7.2"
},
"plugin-api-version": "2.3.0"
}

View File

@@ -66,6 +66,7 @@ $conf['auth_security_timeout'] = 900; //time (seconds) auth data is considere
$conf['securecookie'] = 1; //never send HTTPS cookies via HTTP
$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
@@ -111,6 +112,7 @@ $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 facilites that should be disabled
/* Syndication Settings */
$conf['sitemap'] = 0; //Create a google sitemap? How often? In days.

View File

@@ -33,6 +33,7 @@ amazon.uk https://www.amazon.co.uk/dp/{URL}
paypal https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&amp;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}&amp;btnI=lucky
user :user:{NAME}

View File

@@ -53,6 +53,8 @@ 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

View File

@@ -1,28 +1,28 @@
# Smileys configured here will be replaced by the
# configured images in the smiley directory
8-) icon_cool.gif
8-O icon_eek.gif
8-o icon_eek.gif
:-( icon_sad.gif
:-) icon_smile.gif
=) icon_smile2.gif
:-/ icon_doubt.gif
:-\ icon_doubt2.gif
:-? icon_confused.gif
:-D icon_biggrin.gif
:-P icon_razz.gif
:-o icon_surprised.gif
:-O icon_surprised.gif
:-x icon_silenced.gif
:-X icon_silenced.gif
:-| icon_neutral.gif
;-) icon_wink.gif
m( facepalm.gif
^_^ icon_fun.gif
:?: icon_question.gif
:!: icon_exclaim.gif
LOL icon_lol.gif
FIXME fixme.gif
DELETEME delete.gif
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

View File

@@ -2,6 +2,73 @@
# but were removed later. An up to date DokuWiki should not have any of
# the files installed
# removed in 2022-06-26
.travis.yml
appveyor.yml
inc/IXR_Library.php
inc/cli.php
lib/images/interwiki/amazon.de.gif
lib/images/interwiki/amazon.gif
lib/images/interwiki/amazon.uk.gif
lib/images/interwiki/callto.gif
lib/images/interwiki/doku.gif
lib/images/interwiki/google.gif
lib/images/interwiki/paypal.gif
lib/images/interwiki/phpfn.gif
lib/images/interwiki/skype.gif
lib/images/interwiki/tel.gif
lib/images/interwiki/user.png
lib/images/interwiki/wp.gif
lib/images/interwiki/wpde.gif
lib/images/interwiki/wpes.gif
lib/images/interwiki/wpfr.gif
lib/images/interwiki/wpjp.gif
lib/images/interwiki/wpmeta.gif
lib/images/interwiki/wppl.gif
lib/images/smileys/delete.gif
lib/images/smileys/facepalm.gif
lib/images/smileys/fixme.gif
lib/images/smileys/icon_arrow.gif
lib/images/smileys/icon_biggrin.gif
lib/images/smileys/icon_confused.gif
lib/images/smileys/icon_cool.gif
lib/images/smileys/icon_cry.gif
lib/images/smileys/icon_doubt.gif
lib/images/smileys/icon_doubt2.gif
lib/images/smileys/icon_eek.gif
lib/images/smileys/icon_evil.gif
lib/images/smileys/icon_exclaim.gif
lib/images/smileys/icon_frown.gif
lib/images/smileys/icon_fun.gif
lib/images/smileys/icon_idea.gif
lib/images/smileys/icon_kaddi.gif
lib/images/smileys/icon_lol.gif
lib/images/smileys/icon_mrgreen.gif
lib/images/smileys/icon_neutral.gif
lib/images/smileys/icon_question.gif
lib/images/smileys/icon_razz.gif
lib/images/smileys/icon_redface.gif
lib/images/smileys/icon_rolleyes.gif
lib/images/smileys/icon_sad.gif
lib/images/smileys/icon_silenced.gif
lib/images/smileys/icon_smile.gif
lib/images/smileys/icon_smile2.gif
lib/images/smileys/icon_surprised.gif
lib/images/smileys/icon_twisted.gif
lib/images/smileys/icon_wink.gif
vendor/paragonie/random_compat/LICENSE
vendor/paragonie/random_compat/composer.json
vendor/paragonie/random_compat/lib/byte_safe_strings.php
vendor/paragonie/random_compat/lib/cast_to_int.php
vendor/paragonie/random_compat/lib/error_polyfill.php
vendor/paragonie/random_compat/lib/random.php
vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php
vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php
vendor/paragonie/random_compat/lib/random_bytes_libsodium.php
vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php
vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php
vendor/paragonie/random_compat/lib/random_int.php
# removed in 2020-06-01
inc/PluginInterface.php
inc/PluginTrait.php

View File

@@ -1,586 +1,84 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="128.17094"
height="128.03864"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.48.1 "
sodipodi:docname="dokuwiki-logo.svg"
version="1.1">
<title
id="title3181">DokuWiki Logo</title>
<defs
id="defs4">
<linearGradient
id="linearGradient2624">
<stop
style="stop-color:#3a9030;stop-opacity:0.83673471;"
offset="0"
id="stop2626" />
<stop
style="stop-color:#3d9c32;stop-opacity:0.79591835;"
offset="1"
id="stop2628" />
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 128.17 128.04">
<defs>
<linearGradient id="a">
<stop stop-color="#d69c00" offset="0"/>
<stop stop-color="#ffe658" offset="1"/>
</linearGradient>
<linearGradient
id="linearGradient2612">
<stop
style="stop-color:#25901b;stop-opacity:0.83673471;"
offset="0"
id="stop2614" />
<stop
style="stop-color:#25901b;stop-opacity:0.37755102;"
offset="1"
id="stop2616" />
<linearGradient id="n" x1="192.04" x2="263.67" y1="262.26" y2="262.26" gradientUnits="userSpaceOnUse">
<stop stop-color="#00a423" offset="0"/>
<stop stop-color="#00b427" offset="1"/>
</linearGradient>
<linearGradient
id="linearGradient2600">
<stop
style="stop-color:#e32525;stop-opacity:0.81632656;"
offset="0"
id="stop2602" />
<stop
style="stop-color:#e32525;stop-opacity:0.5714286;"
offset="1"
id="stop2604" />
<linearGradient id="p" x1="191.75" x2="255.66" y1="258.92" y2="258.92" gradientUnits="userSpaceOnUse">
<stop stop-color="#00b62b" offset="0"/>
<stop stop-color="#a1d784" offset="1"/>
</linearGradient>
<marker
inkscape:stockid="TriangleOutL"
orient="auto"
refY="0"
refX="0"
id="TriangleOutL"
style="overflow:visible">
<path
id="path2488"
d="m 5.77,0 -8.65,5 0,-10 8.65,5 z"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
transform="scale(0.8,0.8)"
inkscape:connector-curvature="0" />
</marker>
<marker
inkscape:stockid="Arrow2Lstart"
orient="auto"
refY="0"
refX="0"
id="Arrow2Lstart"
style="overflow:visible">
<path
id="path2571"
style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
transform="matrix(1.1,0,0,1.1,-5.5,0)"
inkscape:connector-curvature="0" />
</marker>
<linearGradient
id="linearGradient2408">
<stop
id="stop2410"
offset="0"
style="stop-color:#000000;stop-opacity:0.17346939;" />
<stop
id="stop2412"
offset="1"
style="stop-color:#c7cec2;stop-opacity:0;" />
<linearGradient id="m" x1="184.07" x2="201.41" y1="246.36" y2="246.36" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
<linearGradient id="d" x1="162.76" x2="240.85" y1="184.99" y2="289.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#ede1ae" offset="0"/>
<stop stop-color="#fefdfa" offset="1"/>
</linearGradient>
<linearGradient
id="linearGradient2389">
<stop
style="stop-color:#000000;stop-opacity:0.17346939;"
offset="0"
id="stop2391" />
<stop
style="stop-color:#c7cec2;stop-opacity:0;"
offset="1"
id="stop2393" />
<linearGradient id="b" x1="140.16" x2="136.14" y1="303.79" y2="195.87" gradientUnits="userSpaceOnUse">
<stop stop-color="#fbf6f0" offset="0"/>
<stop stop-color="#e9dac7" offset="1"/>
</linearGradient>
<linearGradient
id="linearGradient2370">
<stop
style="stop-color:#fbfaf9;stop-opacity:1;"
offset="0"
id="stop2372" />
<stop
style="stop-color:#e9dac7;stop-opacity:1;"
offset="1"
id="stop2374" />
<linearGradient id="c" x1="286.16" x2="185.81" y1="262.29" y2="172.32" gradientUnits="userSpaceOnUse">
<stop stop-color="#fbfaf9" offset="0"/>
<stop stop-color="#e9dac7" offset="1"/>
</linearGradient>
<linearGradient
id="linearGradient2364">
<stop
id="stop2366"
offset="0"
style="stop-color:#fbf6f0;stop-opacity:1;" />
<stop
id="stop2368"
offset="1"
style="stop-color:#e9dac7;stop-opacity:1;" />
<linearGradient id="h" x1="213.97" x2="244.79" y1="220.07" y2="265.4" gradientUnits="userSpaceOnUse">
<stop stop-opacity=".173" offset="0"/>
<stop stop-color="#c7cec2" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient
id="linearGradient2348">
<stop
style="stop-color:#fbf6f0;stop-opacity:1;"
offset="0"
id="stop2350" />
<stop
style="stop-color:#e9dac7;stop-opacity:1;"
offset="1"
id="stop2352" />
<linearGradient id="l" x1="184.31" x2="224.67" y1="241.53" y2="307.53" gradientUnits="userSpaceOnUse">
<stop stop-opacity=".173" offset="0"/>
<stop stop-color="#c7cec2" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient
id="linearGradient2332">
<stop
style="stop-color:#ede1ae;stop-opacity:1;"
offset="0"
id="stop2334" />
<stop
style="stop-color:#fefdfa;stop-opacity:1;"
offset="1"
id="stop2336" />
<linearGradient id="e" x1="202.42" x2="206.06" y1="222.05" y2="210.36" gradientUnits="userSpaceOnUse">
<stop stop-color="#e32525" stop-opacity=".816" offset="0"/>
<stop stop-color="#e32525" stop-opacity=".571" offset="1"/>
</linearGradient>
<linearGradient
id="linearGradient2249">
<stop
style="stop-color:#00a423;stop-opacity:1;"
offset="0"
id="stop2251" />
<stop
style="stop-color:#00b427;stop-opacity:1;"
offset="1"
id="stop2253" />
<linearGradient id="f" x1="248.62" x2="251.64" y1="234.52" y2="213.12" gradientUnits="userSpaceOnUse">
<stop stop-color="#25901b" stop-opacity=".837" offset="0"/>
<stop stop-color="#25901b" stop-opacity=".378" offset="1"/>
</linearGradient>
<linearGradient
id="linearGradient2229">
<stop
id="stop2231"
offset="0"
style="stop-color:#00b62b;stop-opacity:1;" />
<stop
id="stop2233"
offset="1"
style="stop-color:#a1d784;stop-opacity:1;" />
<linearGradient id="g" x1="275.72" x2="255.68" y1="251.56" y2="217.94" gradientUnits="userSpaceOnUse">
<stop stop-color="#3a9030" stop-opacity=".837" offset="0"/>
<stop stop-color="#3d9c32" stop-opacity=".796" offset="1"/>
</linearGradient>
<linearGradient
id="linearGradient2213">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop2215" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop2217" />
<linearGradient id="k" x1="219.66" x2="277.88" y1="192.73" y2="192.73" gradientUnits="userSpaceOnUse">
<stop stop-color="#ce411e" offset="0"/>
<stop stop-color="#ecad8d" offset="1"/>
</linearGradient>
<linearGradient
id="linearGradient2360">
<stop
style="stop-color:#d69c00;stop-opacity:1;"
offset="0"
id="stop2362" />
<stop
style="stop-color:#ffe658;stop-opacity:1;"
offset="1"
id="stop2364" />
<linearGradient id="j" x1="219.21" x2="286.23" y1="189.02" y2="189.02" gradientUnits="userSpaceOnUse">
<stop stop-color="#8f2a15" offset="0"/>
<stop stop-color="#c8381b" offset="1"/>
</linearGradient>
<linearGradient
id="linearGradient2352">
<stop
id="stop2354"
offset="0"
style="stop-color:#ce411e;stop-opacity:1;" />
<stop
id="stop2356"
offset="1"
style="stop-color:#ecad8d;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient2336">
<stop
style="stop-color:#8f2a15;stop-opacity:1;"
offset="0"
id="stop2338" />
<stop
style="stop-color:#c8381b;stop-opacity:1;"
offset="1"
id="stop2340" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2336"
id="linearGradient2342"
x1="219.21262"
y1="189.01556"
x2="286.22665"
y2="189.01556"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2352"
id="linearGradient2350"
x1="219.66267"
y1="192.73286"
x2="277.8761"
y2="192.73286"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2360"
id="radialGradient2366"
cx="224.41418"
cy="212.80016"
fx="224.41418"
fy="212.80016"
r="8.6813803"
gradientTransform="matrix(1,0,0,0.984179,0,3.366635)"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2249"
id="linearGradient2227"
x1="192.03938"
y1="262.25757"
x2="263.67093"
y2="262.25757"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2229"
id="linearGradient2247"
x1="191.75092"
y1="258.91571"
x2="255.6561"
y2="258.91571"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2360"
id="radialGradient2317"
cx="257.41144"
cy="274.64203"
fx="257.41144"
fy="274.64203"
r="7.1440549"
gradientTransform="matrix(1,0,0,1.631384,0,-173.4045)"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2360"
id="linearGradient2325"
x1="184.07063"
y1="246.35907"
x2="201.40646"
y2="246.35907"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2332"
id="linearGradient2346"
x1="162.76369"
y1="184.99277"
x2="240.84924"
y2="289.50323"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2348"
id="linearGradient2354"
x1="140.15784"
y1="303.78967"
x2="136.14151"
y2="195.87151"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2370"
id="linearGradient2362"
x1="286.15598"
y1="262.28729"
x2="185.81258"
y2="172.32423"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2389"
id="linearGradient2395"
x1="213.96568"
y1="220.07191"
x2="244.79126"
y2="265.40363"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2408"
id="linearGradient2406"
x1="184.30582"
y1="241.52789"
x2="224.67441"
y2="307.52844"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2600"
id="linearGradient2606"
x1="202.41772"
y1="222.05145"
x2="206.06017"
y2="210.3558"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2612"
id="linearGradient2618"
x1="248.62152"
y1="234.52202"
x2="251.64362"
y2="213.12164"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2624"
id="linearGradient2630"
x1="275.71765"
y1="251.56442"
x2="255.68353"
y2="217.94008"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2352"
id="linearGradient2640"
gradientUnits="userSpaceOnUse"
x1="219.66267"
y1="192.73286"
x2="277.8761"
y2="192.73286" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2336"
id="linearGradient2643"
gradientUnits="userSpaceOnUse"
x1="219.21262"
y1="189.01556"
x2="286.22665"
y2="189.01556" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2360"
id="radialGradient2647"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.984179,0,3.366635)"
cx="224.41418"
cy="212.80016"
fx="224.41418"
fy="212.80016"
r="8.6813803" />
<radialGradient id="o" cx="257.41" cy="274.64" r="7.144" gradientTransform="matrix(1 0 0 1.6314 0 -173.4)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
<radialGradient id="i" cx="224.41" cy="212.8" r="8.681" gradientTransform="matrix(1 0 0 .98418 0 3.367)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.03"
inkscape:cx="35.144424"
inkscape:cy="83.160427"
inkscape:document-units="px"
inkscape:current-layer="layer3"
inkscape:window-width="1366"
inkscape:window-height="716"
inkscape:window-x="-8"
inkscape:window-y="-8"
showguides="true"
inkscape:guide-bbox="true"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-maximized="1"
inkscape:showpageshadow="false"
showborder="true"
borderlayer="false" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>DokuWiki Logo</dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Esther Brunner</dc:title>
</cc:Agent>
</dc:creator>
<cc:license
rdf:resource="http://www.gnu.org/licenses/gpl-2.0.html" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="paper"
style="display:inline"
transform="translate(-158.10602,-158.67323)">
<g
id="g1419"
transform="matrix(0.99993322,0,0,0.9959778,0.01483419,0.8957919)">
<g
id="g2376">
<path
transform="matrix(0.989976,-0.141236,0.201069,0.979577,0,0)"
style="fill:url(#linearGradient2354);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.7216621px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline"
d="m 120.21543,196.43769 70.90655,-0.79226 -2.40261,109.05308 -71.71761,0.37344 3.21367,-108.63426 z"
id="rect1422"
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0" />
<path
style="fill:url(#linearGradient2362);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline"
d="m 179.20033,182.08731 79.84173,-19.51687 26.61391,101.72428 -82.50312,21.58684 -23.95252,-103.79425 z"
id="rect1425"
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0" />
<path
transform="matrix(0.995676,-0.09289891,0.08102261,0.996712,0,0)"
style="fill:url(#linearGradient2346);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00418305px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline"
d="m 159.01353,181.74387 85.58587,0.53396 0,110.47429 -84.53387,-2.5127 -1.052,-108.49555 z"
id="rect1419"
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0" />
</g>
<path
id="text2382"
d="m 167.55116,214.00773 0,-20.1846 5.34962,0 0,2.37403 -2.48145,0 0,15.43654 2.48145,0 0,2.37403 -5.34962,0 m 7.34767,0 0,-20.1846 5.34961,0 0,2.37403 -2.48144,0 0,15.43654 2.48144,0 0,2.37403 -5.34961,0 m 7.36915,-20.1846 5.81153,0 c 1.31054,2e-5 2.30956,0.10028 2.99707,0.30078 0.92382,0.27216 1.71516,0.75555 2.37403,1.4502 0.65884,0.69468 1.16014,1.54689 1.50391,2.55664 0.34373,1.00262 0.51561,2.24155 0.51562,3.71681 -10e-6,1.29623 -0.16115,2.41342 -0.4834,3.35156 -0.39389,1.14584 -0.95607,2.07325 -1.68652,2.78223 -0.55145,0.53711 -1.29624,0.95606 -2.23438,1.25684 -0.70183,0.222 -1.63999,0.33301 -2.81446,0.33301 l -5.9834,0 0,-15.74807 m 3.17969,2.66407 0,10.43067 2.37402,0 c 0.88802,1e-5 1.52897,-0.0501 1.92286,-0.15039 0.51561,-0.1289 0.94172,-0.34732 1.27832,-0.65527 0.34374,-0.30794 0.62304,-0.81282 0.83789,-1.51465 0.21483,-0.70898 0.32226,-1.6722 0.32227,-2.88965 -1e-5,-1.21744 -0.10744,-2.15201 -0.32227,-2.80372 -0.21485,-0.65168 -0.51563,-1.16014 -0.90234,-1.52539 -0.38673,-0.36522 -0.87729,-0.61229 -1.47168,-0.74121 -0.44402,-0.10025 -1.31414,-0.15038 -2.61036,-0.15039 l -1.42871,0 m 14.96388,13.084 -3.75977,-15.74807 3.25489,0 2.37403,10.8174 2.87891,-10.8174 3.78125,0 2.76074,11.00002 2.417,-11.00002 3.20118,0 -3.82423,15.74807 -3.37305,0 -3.13672,-11.77345 -3.12598,11.77345 -3.44825,0 m 22.76272,-15.74807 0,20.1846 -5.34961,0 0,-2.37403 2.48145,0 0,-15.45803 -2.48145,0 0,-2.35254 5.34961,0 m 7.34767,0 0,20.1846 -5.34962,0 0,-2.37403 2.48145,0 0,-15.45803 -2.48145,0 0,-2.35254 5.34962,0"
style="font-size:12.0000124px;font-style:normal;font-weight:normal;line-height:125%;fill:#6184a3;fill-opacity:1;stroke:none;display:inline;font-family:Bitstream Vera Sans"
transform="matrix(0.995433,-0.09546066,0.09546066,0.995433,0,0)"
inkscape:connector-curvature="0" />
<g
id="g2632"
style="display:inline">
<path
style="fill:url(#linearGradient2606);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;marker-end:none"
d="m 174.75585,201.60224 c -6.04576,2.46667 -10.16789,4.4194 -12.88454,6.35064 -2.71665,1.93124 -3.19257,4.60007 -3.24631,6.26587 -0.0269,0.8329 0.0809,1.77774 0.63189,2.44014 0.55103,0.6624 1.80769,1.87421 2.75794,2.38558 1.90049,1.02274 7.5417,2.42901 10.51899,3.07308 11.90917,2.57627 26.80568,1.68117 26.80568,1.68117 1.69307,1.2452 2.83283,2.82434 3.269,4.26902 4.5766,-1.88674 11.81084,-6.58439 13.15657,-8.57706 -5.45142,-4.19955 -10.79692,-6.33346 -16.51317,-8.30847 -1.59867,-0.71918 -2.87956,-1.22649 -0.71773,2.55635 0.98506,2.47275 0.85786,5.05143 0.57176,7.41825 0,0 -16.52749,0.40678 -28.23838,-2.1266 -2.92772,-0.63334 -5.46627,-0.95523 -7.21875,-1.89832 -0.87624,-0.47154 -1.48296,-0.8208 -1.91578,-1.3411 -0.43282,-0.5203 -0.2196,-1.29055 -0.20128,-1.85858 0.0366,-1.13607 0.25336,-1.67063 2.86177,-3.52492 2.60841,-1.85429 5.65407,-3.36195 11.65936,-5.81211 -0.0877,-1.29125 -0.29025,-2.5059 -1.29702,-2.99294 z"
id="path2414"
sodipodi:nodetypes="csssssccccccssssscc"
inkscape:connector-curvature="0" />
<path
style="fill:url(#linearGradient2618);fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 269.62539,220.7482 c -1.43576,-0.13963 -2.58044,0.30288 -2.56084,1.50218 0.94391,0.85652 1.34942,2.43518 1.48562,3.14008 0.1362,0.7049 0.0359,1.21914 -0.48562,1.89004 -1.043,1.3418 -3.12498,1.56875 -6.5006,2.72063 -6.75124,2.30377 -16.89306,2.52561 -27.90689,3.84639 -22.02767,2.64157 -39.03164,3.76107 -39.03164,3.76107 1.98346,-4.64758 6.32828,-4.41197 6.34903,-8.20969 0.27376,-0.89755 -3.14597,-1.31638 -5.09943,-0.10731 -4.26694,3.70137 -7.59152,6.75353 -10.69418,10.51311 l 1.88795,3.08438 c 0,0 26.13006,-2.88973 48.19776,-5.5361 11.03385,-1.32318 20.95601,-1.99856 27.80968,-4.33728 3.42683,-1.16936 5.95975,-1.49022 7.6409,-3.51958 0.63172,-0.76256 1.35238,-3.04699 1.06804,-4.73369 -0.21951,-1.30213 -1.14979,-3.09774 -2.15978,-4.01423 z"
id="path2608"
sodipodi:nodetypes="ccsssscccccssssc"
inkscape:connector-curvature="0" />
<path
style="fill:url(#linearGradient2630);fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 254.36185,220.33948 c -6.84997,3.24198 -7.15311,8.60912 -5.95953,12.79884 1.19358,4.18972 5.26293,8.75677 9.32121,12.40608 8.11656,7.29861 12.06046,9.33163 12.06046,9.33163 -3.71515,-0.10342 -7.89887,-1.41174 -8.13315,0.49304 -0.9483,2.97582 11.49137,3.47486 17.43787,2.70205 -1.39456,-7.57836 -3.79323,-13.21546 -7.73151,-14.90312 -1.68464,-0.14804 0.31242,4.72441 0.76985,9.39604 0,0 -3.62454,-1.73122 -11.60519,-8.90762 -3.99032,-3.5882 -7.37386,-7.3421 -8.47319,-11.20099 -1.09933,-3.85889 0.0776,-6.1205 4.95082,-9.53176 0.92816,-0.99528 -1.28985,-2.45913 -2.63764,-2.58419 z"
id="path2620"
sodipodi:nodetypes="csscccccsscc"
inkscape:connector-curvature="0" />
</g>
<path
sodipodi:nodetypes="cccccc"
id="rect2386"
d="m 213.96569,234.57806 2.18756,-14.42897 15.21982,6.08793 21.49387,29.94828 -20.40591,9.21832 -18.49534,-30.82556 z"
style="fill:url(#linearGradient2395);fill-opacity:1;stroke:none;display:inline"
inkscape:connector-curvature="0" />
<g
id="g2649"
style="display:inline">
<path
style="fill:url(#radialGradient2647);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
d="m 232.55816,219.5295 -15.92827,0.32199 3.08809,-15.15716 12.84018,14.83517 z"
id="path1443"
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0" />
<path
style="fill:#812310;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 221.60041,219.29315 -4.41205,0.0782 0.85429,-3.98263 3.55776,3.90445 z"
id="path1452"
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0" />
<path
style="fill:url(#linearGradient2643);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
d="m 269.44172,159.27421 0.098,8.91471 8.0581,8.72344 7.75906,0.7992 -52.80669,41.84092 -6.66532,-3.30696 -5.08243,-5.618 -1.08987,-5.91194 49.72911,-45.44137 z"
id="rect1437"
sodipodi:nodetypes="ccccccccc"
inkscape:connector-curvature="0" />
<path
style="fill:url(#linearGradient2640);fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 268.94766,168.32844 8.3426,8.82719 -51.1007,38.68262 -4.9197,-5.4436 47.6778,-42.06621 z"
id="rect1446"
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0" />
<path
style="fill:#ffe965;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;display:inline"
d="m 285.33776,177.73216 -8.16219,-0.86619 -7.7518,-8.67862 0.0132,-9.14293 8.36213,0.75209 7.18862,9.57682 0.35007,8.35883 z"
id="path1440"
sodipodi:nodetypes="ccccccc"
inkscape:connector-curvature="0" />
<path
style="fill:#cb391c;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 280.72049,168.46367 0.1644,4.05654 -3.81335,-0.71676 -2.87504,-3.18901 -0.28089,-3.53393 3.85447,-0.16637 2.95041,3.54953 z"
id="path1449"
sodipodi:nodetypes="ccccccc"
inkscape:connector-curvature="0" />
</g>
<g
id="g2657"
style="display:inline">
<path
style="fill:url(#linearGradient2406);fill-opacity:1;stroke:none"
d="m 183.88617,256.82796 0.99991,-16.30721 17.2878,8.44012 26.05488,38.00946 -29.28095,-1.13363 -15.06164,-29.00874 z"
id="rect2397"
sodipodi:nodetypes="cccccc"
inkscape:connector-curvature="0" />
<path
style="fill:url(#linearGradient2325);fill-opacity:1;stroke:#000000;stroke-linejoin:round;stroke-opacity:1;display:inline"
d="m 200.90647,238.44836 -8.04601,15.77386 -7.05577,-13.57337 15.10178,-2.20049 z"
id="rect2207"
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0" />
<path
style="fill:url(#linearGradient2227);fill-opacity:1;stroke:#000000;stroke-linejoin:round;stroke-opacity:1"
d="m 201.05389,238.55401 62.11704,24.91912 -7.88689,3.21429 -4.35152,9.30976 1.1716,9.96396 -59.31453,-31.72759 -0.49402,-7.36382 3.09592,-5.82826 5.6624,-2.48746 z"
id="rect1328"
sodipodi:nodetypes="ccccccccc"
inkscape:connector-curvature="0" />
<path
style="fill:url(#radialGradient2317);fill-opacity:1;stroke:#000000;stroke-linejoin:round;stroke-opacity:1;display:inline"
d="m 255.27801,266.53504 7.9241,-3.04772 0.85337,10.24037 -3.9011,8.28983 -8.04601,3.77919 -1.341,-9.63083 4.51064,-9.63084 z"
id="rect2204"
sodipodi:nodetypes="ccccccc"
inkscape:connector-curvature="0" />
<path
style="fill:url(#linearGradient2247);fill-opacity:1;stroke:none;display:inline"
d="m 195.7549,241.421 59.13059,24.7962 -4.5917,9.76614 -57.48995,-29.00967 2.95106,-5.55267 z"
id="rect2210"
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0" />
<path
style="fill:#00b527;fill-opacity:1;stroke:none"
d="m 255.02263,275.21029 2.08411,-4.1069 2.96459,-1.06995 0.69433,3.37197 -1.76759,3.85723 -3.15516,1.38315 -0.82028,-3.4355 z"
id="rect2308"
sodipodi:nodetypes="ccccccc"
inkscape:connector-curvature="0" />
<path
style="fill:#258209;fill-opacity:1;stroke:none;display:inline"
d="m 186.56849,241.00362 3.54963,-0.47312 -2.02297,3.53926 -1.52666,-3.06614 z"
id="rect2327"
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0" />
</g>
</g>
<g fill-rule="evenodd" stroke="#000">
<path transform="matrix(.98991 -.14067 .20106 .97564 -158.095 -157.774)" d="m120.22 196.44 70.907-.792-2.403 109.05-71.718.373 3.214-108.63z" fill="url(#b)" stroke-width=".722"/>
<path d="m179.2 182.09 79.842-19.517 26.614 101.72-82.503 21.587L179.2 182.09z" fill="url(#c)" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
<path transform="matrix(.99561 -.09253 .08102 .9927 -158.095 -157.774)" d="m159.01 181.74 85.586.534v110.47l-84.534-2.513-1.052-108.5z" fill="url(#d)" stroke-width="1.004"/>
</g>
<path d="M29.106 38.471 27.179 18.46l5.325-.508.226 2.353-2.47.236 1.474 15.305 2.47-.236.226 2.354-5.324.508m7.313-.698L34.492 17.76l5.325-.509.227 2.354-2.47.236 1.474 15.304 2.47-.236.226 2.354-5.325.509m5.409-20.713 5.784-.553c1.304-.124 2.308-.12 3.012.014.945.182 1.78.586 2.501 1.212.722.626 1.303 1.423 1.741 2.391.438.962.728 2.174.868 3.636.124 1.285.07 2.408-.16 3.37-.284 1.173-.755 2.145-1.414 2.918-.498.585-1.199 1.071-2.104 1.458-.677.287-1.6.486-2.77.598l-5.955.569-1.504-15.613m3.42 2.339.995 10.341 2.363-.225c.884-.085 1.517-.195 1.9-.332.5-.177.904-.434 1.21-.771.313-.338.542-.865.69-1.582.145-.723.16-1.688.044-2.895s-.312-2.123-.588-2.75c-.276-.625-.624-1.1-1.044-1.426-.42-.325-.932-.523-1.536-.595-.451-.057-1.322-.024-2.612.1l-1.422.135M61.39 30.95l-5.245-15.256 3.24-.31 3.395 10.499 1.833-10.998 3.764-.36 3.797 10.644 1.356-11.136 3.187-.304-2.304 15.976-3.357.321-4.246-11.374-1.988 11.97-3.432.327m21.154-17.777 1.927 20.012-5.325.509-.226-2.354 2.47-.236-1.476-15.325-2.47.236-.224-2.333 5.324-.508m7.314-.699 1.927 20.012-5.325.509-.227-2.354 2.47-.236-1.475-15.325-2.47.236-.225-2.333 5.325-.508" fill="#6184a3"/>
<g fill-rule="evenodd">
<path d="M174.76 201.6c-6.046 2.467-10.168 4.42-12.885 6.35s-3.193 4.6-3.246 6.267c-.027.832.08 1.777.632 2.44.55.662 1.807 1.874 2.757 2.385 1.901 1.023 7.542 2.43 10.52 3.073 11.908 2.577 26.805 1.682 26.805 1.682 1.694 1.245 2.833 2.824 3.27 4.269 4.576-1.887 11.81-6.585 13.156-8.578-5.45-4.2-10.797-6.333-16.513-8.308-1.598-.72-2.88-1.227-.717 2.556.985 2.473.858 5.052.572 7.419 0 0-16.527.406-28.238-2.127-2.928-.633-5.467-.955-7.22-1.898-.876-.472-1.482-.821-1.915-1.341s-.22-1.291-.201-1.86c.036-1.135.253-1.67 2.861-3.524s5.655-3.362 11.66-5.812c-.088-1.291-.29-2.506-1.298-2.993z" fill="url(#e)" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
<path d="M269.63 220.75c-1.436-.14-2.58.303-2.56 1.502.943.857 1.349 2.435 1.485 3.14s.036 1.22-.486 1.89c-1.043 1.342-3.125 1.57-6.5 2.72-6.752 2.305-16.893 2.526-27.907 3.847-22.028 2.642-39.032 3.761-39.032 3.761 1.983-4.647 6.328-4.412 6.349-8.21.273-.897-3.146-1.316-5.1-.107-4.267 3.702-7.591 6.754-10.694 10.513l1.888 3.085s26.13-2.89 48.198-5.536c11.034-1.324 20.956-1.999 27.81-4.338 3.427-1.169 5.96-1.49 7.641-3.52.632-.762 1.352-3.046 1.068-4.733-.22-1.302-1.15-3.098-2.16-4.014z" fill="url(#f)" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
<path d="M254.36 220.34c-6.85 3.242-7.153 8.61-5.96 12.799s5.263 8.757 9.322 12.406c8.116 7.299 12.06 9.332 12.06 9.332-3.715-.104-7.9-1.412-8.133.493-.949 2.975 11.49 3.475 17.438 2.702-1.395-7.579-3.794-13.215-7.732-14.903-1.685-.148.312 4.724.77 9.396 0 0-3.625-1.732-11.605-8.908-3.99-3.588-7.374-7.342-8.473-11.201s.077-6.12 4.95-9.532c.929-.995-1.29-2.459-2.637-2.584z" fill="url(#g)" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
</g>
<path d="m213.97 234.58 2.188-14.429 15.22 6.088 21.494 29.948-20.406 9.218-18.495-30.826z" fill="url(#h)" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
<path d="m232.56 219.53-15.928.322 3.088-15.157 12.84 14.835z" fill="url(#i)" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
<path d="m63.49 60.634-4.412.078.854-3.966 3.557 3.888z" fill="#812310" fill-rule="evenodd"/>
<path d="m269.44 159.27.098 8.915 8.058 8.723 7.76.8-52.808 41.84-6.665-3.307-5.083-5.618-1.09-5.912 49.73-45.44z" fill="url(#j)" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
<path d="m268.95 168.33 8.343 8.827-51.101 38.683-4.92-5.443 47.678-42.066z" fill="url(#k)" fill-rule="evenodd" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
<path d="m285.34 177.73-8.162-.866-7.752-8.679.013-9.143 8.362.752 7.189 9.577.35 8.359z" fill="#ffe965" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
<path d="m122.605 10.009.165 4.04-3.814-.714-2.874-3.176-.281-3.52 3.854-.166 2.95 3.536z" fill="#cb391c" fill-rule="evenodd"/>
<path d="m183.89 256.83 1-16.307 17.288 8.44 26.055 38.01-29.281-1.135-15.062-29.009z" fill="url(#l)" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
<path d="m200.91 238.45-8.046 15.774-7.056-13.573 15.102-2.2z" fill="url(#m)" stroke="#000" stroke-linejoin="round" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
<path d="m201.05 238.55 62.117 24.919-7.887 3.214-4.351 9.31 1.171 9.964-59.315-31.728-.494-7.364 3.096-5.828 5.662-2.488z" fill="url(#n)" stroke="#000" stroke-linejoin="round" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
<path d="m255.28 266.54 7.924-3.048.853 10.24-3.9 8.29-8.047 3.78-1.34-9.631 4.51-9.631z" fill="url(#o)" stroke="#000" stroke-linejoin="round" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
<path d="m195.75 241.42 59.131 24.796-4.592 9.766-57.49-29.01 2.951-5.553z" fill="url(#p)" transform="matrix(.99993 0 0 .99598 -158.095 -157.774)"/>
<path d="m96.907 116.33 2.084-4.09 2.964-1.067.695 3.359-1.768 3.841-3.155 1.378-.82-3.422z" fill="#00b527"/>
<path d="m28.462 82.257 3.55-.471-2.024 3.525-1.526-3.054z" fill="#258209"/>
</svg>

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -246,6 +246,7 @@ DokuWiki converts commonly used [[wp>emoticon]]s to their graphical equivalents.
* :-| %% :-| %%
* ;-) %% ;-) %%
* ^_^ %% ^_^ %%
* m( %% m( %%
* :?: %% :?: %%
* :!: %% :!: %%
* LOL %% LOL %%

View File

@@ -11,7 +11,7 @@
// update message version - always use a string to avoid localized floats!
use dokuwiki\Extension\Event;
$updateVersion = "51.4";
$updateVersion = "53";
// xdebug_start_profiling();
@@ -79,10 +79,14 @@ if($DATE_AT) {
$DATE_AT = null;
} else if ($rev_t === false) { //page did not exist
$rev_n = $pagelog->getRelativeRevision($DATE_AT,+1);
msg(sprintf($lang['page_nonexist_rev'],
strftime($conf['dformat'],$DATE_AT),
wl($ID, array('rev' => $rev_n)),
strftime($conf['dformat'],$rev_n)));
msg(
sprintf(
$lang['page_nonexist_rev'],
dformat($DATE_AT),
wl($ID, array('rev' => $rev_n)),
dformat($rev_n)
)
);
$REV = $DATE_AT; //will result in a page not exists message
} else {
$REV = $rev_t;

View File

@@ -1,4 +1,5 @@
<?php
/**
* XML feed export
*
@@ -15,14 +16,14 @@ use dokuwiki\ChangeLog\PageChangeLog;
use dokuwiki\Extension\AuthPlugin;
use dokuwiki\Extension\Event;
if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__).'/');
require_once(DOKU_INC.'inc/init.php');
if (!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__) . '/');
require_once(DOKU_INC . 'inc/init.php');
//close session
session_write_close();
//feed disabled?
if(!actionOK('rss')) {
if (!actionOK('rss')) {
http_status(404);
echo '<error>RSS feed is disabled.</error>';
exit;
@@ -33,7 +34,8 @@ $opt = rss_parseOptions();
// 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 = join('', array_values($opt)).'$'.$_SERVER['REMOTE_USER'].'$'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'];
$key = join('', array_values($opt)) . '$' . $_SERVER['REMOTE_USER']
. '$' . $_SERVER['HTTP_HOST'] . $_SERVER['SERVER_PORT'];
$cache = new Cache($key, '.feed');
// prepare cache depends
@@ -47,9 +49,9 @@ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Type: application/xml; charset=utf-8');
header('X-Robots-Tag: noindex');
if($cache->useCache($depends)) {
if ($cache->useCache($depends)) {
http_conditionalRequest($cache->getTime());
if($conf['allowdebug']) header("X-CacheUsed: $cache->cache");
if ($conf['allowdebug']) header("X-CacheUsed: $cache->cache");
print $cache->retrieveCache();
exit;
} else {
@@ -58,32 +60,33 @@ if($cache->useCache($depends)) {
// create new feed
$rss = new UniversalFeedCreator();
$rss->title = $conf['title'].(($opt['namespace']) ? ' '.$opt['namespace'] : '');
$rss->title = $conf['title'] . (($opt['namespace']) ? ' ' . $opt['namespace'] : '');
$rss->link = DOKU_URL;
$rss->syndicationURL = DOKU_URL.'feed.php';
$rss->cssStyleSheet = DOKU_URL.'lib/exe/css.php?s=feed';
$rss->syndicationURL = DOKU_URL . 'feed.php';
$rss->cssStyleSheet = DOKU_URL . 'lib/exe/css.php?s=feed';
$image = new FeedImage();
$image->title = $conf['title'];
$image->url = tpl_getMediaFile(array(':wiki:favicon.ico', ':favicon.ico', 'images/favicon.ico'), true);
$image->url = tpl_getMediaFile([':wiki:favicon.ico', ':favicon.ico', 'images/favicon.ico'], true);
$image->link = DOKU_URL;
$rss->image = $image;
$data = null;
$modes = array(
$modes = [
'list' => 'rssListNamespace',
'search' => 'rssSearch',
'recent' => 'rssRecentChanges'
);
if(isset($modes[$opt['feed_mode']])) {
];
if (isset($modes[$opt['feed_mode']])) {
$data = $modes[$opt['feed_mode']]($opt);
} else {
$eventData = array(
$eventData = [
'opt' => &$opt,
'data' => &$data,
);
];
$event = new Event('FEED_MODE_UNKNOWN', $eventData);
if($event->advise_before(true)) {
if ($event->advise_before(true)) {
echo sprintf('<error>Unknown feed mode %s</error>', hsc($opt['feed_mode']));
exit;
}
@@ -106,59 +109,62 @@ print $feed;
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
function rss_parseOptions() {
function rss_parseOptions()
{
global $conf;
global $INPUT;
$opt = array();
$opt = [];
foreach(array(
// Basic feed properties
// Plugins may probably want to add new values to these
// properties for implementing own feeds
foreach (
[
// Basic feed properties
// Plugins may probably want to add new values to these
// properties for implementing own feeds
// One of: list, search, recent
'feed_mode' => array('str', 'mode', 'recent'),
// One of: diff, page, rev, current
'link_to' => array('str', 'linkto', $conf['rss_linkto']),
// One of: abstract, diff, htmldiff, html
'item_content' => array('str', 'content', $conf['rss_content']),
// One of: list, search, recent
'feed_mode' => ['str', 'mode', 'recent'],
// One of: diff, page, rev, current
'link_to' => ['str', 'linkto', $conf['rss_linkto']],
// One of: abstract, diff, htmldiff, html
'item_content' => ['str', 'content', $conf['rss_content']],
// Special feed properties
// These are only used by certain feed_modes
// Special feed properties
// These are only used by certain feed_modes
// String, used for feed title, in list and rc mode
'namespace' => array('str', 'ns', null),
// Positive integer, only used in rc mode
'items' => array('int', 'num', $conf['recent']),
// Boolean, only used in rc mode
'show_minor' => array('bool', 'minor', false),
// Boolean, only used in rc mode
'only_new' => array('bool', 'onlynewpages', false),
// String, only used in list mode
'sort' => array('str', 'sort', 'natural'),
// String, only used in search mode
'search_query' => array('str', 'q', null),
// One of: pages, media, both
'content_type' => array('str', 'view', $conf['rss_media'])
// String, used for feed title, in list and rc mode
'namespace' => ['str', 'ns', null],
// Positive integer, only used in rc mode
'items' => ['int', 'num', $conf['recent']],
// Boolean, only used in rc mode
'show_minor' => ['bool', 'minor', false],
// Boolean, only used in rc mode
'only_new' => ['bool', 'onlynewpages', false],
// String, only used in list mode
'sort' => ['str', 'sort', 'natural'],
// String, only used in search mode
'search_query' => ['str', 'q', null],
// One of: pages, media, both
'content_type' => ['str', 'view', $conf['rss_media']]
) as $name => $val) {
] as $name => $val
) {
$opt[$name] = $INPUT->{$val[0]}($val[1], $val[2], true);
}
$opt['items'] = max(0, (int) $opt['items']);
$opt['show_minor'] = (bool) $opt['show_minor'];
$opt['only_new'] = (bool) $opt['only_new'];
$opt['sort'] = valid_input_set('sort', array('default' => 'natural', 'date'), $opt);
$opt['sort'] = valid_input_set('sort', ['default' => 'natural', 'date'], $opt);
$opt['guardmail'] = ($conf['mailguard'] != '' && $conf['mailguard'] != 'none');
$type = $INPUT->valid(
'type',
array( 'rss', 'rss2', 'atom', 'atom1', 'rss1'),
['rss', 'rss2', 'atom', 'atom1', 'rss1'],
$conf['rss_type']
);
switch($type) {
switch ($type) {
case 'rss':
$opt['feed_type'] = 'RSS0.91';
$opt['mime_type'] = 'text/xml';
@@ -180,9 +186,9 @@ function rss_parseOptions() {
$opt['mime_type'] = 'application/xml';
}
$eventData = array(
$eventData = [
'opt' => &$opt,
);
];
Event::createAndTrigger('FEED_OPTS_POSTPROCESS', $eventData);
return $opt;
}
@@ -190,98 +196,105 @@ function rss_parseOptions() {
/**
* Add recent changed pages to a feed object
*
* @param FeedCreator $rss the FeedCreator Object
* @param array $data the items to add
* @param array $opt the feed options
* @author Andreas Gohr <andi@splitbrain.org>
* @param FeedCreator $rss the FeedCreator Object
* @param array $data the items to add
* @param array $opt the feed options
*/
function rss_buildItems(&$rss, &$data, $opt) {
function rss_buildItems(&$rss, &$data, $opt)
{
global $conf;
global $lang;
/* @var AuthPlugin $auth */
global $auth;
$eventData = array(
$eventData = [
'rss' => &$rss,
'data' => &$data,
'opt' => &$opt,
);
];
$event = new Event('FEED_DATA_PROCESS', $eventData);
if($event->advise_before(false)) {
foreach($data as $ditem) {
if(!is_array($ditem)) {
if ($event->advise_before(false)) {
foreach ($data as $ditem) {
if (!is_array($ditem)) {
// not an array? then only a list of IDs was given
$ditem = array('id' => $ditem);
$ditem = ['id' => $ditem];
}
$item = new FeedItem();
$id = $ditem['id'];
if(!$ditem['media']) {
if (!$ditem['media']) {
$meta = p_get_metadata($id);
} else {
$meta = array();
$meta = [];
}
// add date
if($ditem['date']) {
if ($ditem['date']) {
$date = $ditem['date'];
} elseif ($ditem['media']) {
$date = @filemtime(mediaFN($id));
} elseif (file_exists(wikiFN($id))) {
$date = @filemtime(wikiFN($id));
} elseif($meta['date']['modified']) {
} elseif ($meta['date']['modified']) {
$date = $meta['date']['modified'];
} else {
$date = 0;
}
if($date) $item->date = date('r', $date);
if ($date) $item->date = date('r', $date);
// add title
if($conf['useheading'] && $meta['title']) {
if ($conf['useheading'] && $meta['title']) {
$item->title = $meta['title'];
} else {
$item->title = $ditem['id'];
}
if($conf['rss_show_summary'] && !empty($ditem['sum'])) {
$item->title .= ' - '.strip_tags($ditem['sum']);
if ($conf['rss_show_summary'] && !empty($ditem['sum'])) {
$item->title .= ' - ' . strip_tags($ditem['sum']);
}
// add item link
switch($opt['link_to']) {
switch ($opt['link_to']) {
case 'page':
if($ditem['media']) {
if ($ditem['media']) {
$item->link = media_managerURL(
array(
'image' => $id,
'ns' => getNS($id),
'rev' => $date
), '&', true
[
'image' => $id,
'ns' => getNS($id),
'rev' => $date
],
'&',
true
);
} else {
$item->link = wl($id, 'rev='.$date, true, '&');
$item->link = wl($id, 'rev=' . $date, true, '&');
}
break;
case 'rev':
if($ditem['media']) {
if ($ditem['media']) {
$item->link = media_managerURL(
array(
'image' => $id,
'ns' => getNS($id),
'rev' => $date,
'tab_details' => 'history'
), '&', true
[
'image' => $id,
'ns' => getNS($id),
'rev' => $date,
'tab_details' => 'history'
],
'&',
true
);
} else {
$item->link = wl($id, 'do=revisions&rev='.$date, true, '&');
$item->link = wl($id, 'do=revisions&rev=' . $date, true, '&');
}
break;
case 'current':
if($ditem['media']) {
if ($ditem['media']) {
$item->link = media_managerURL(
array(
'image' => $id,
'ns' => getNS($id)
), '&', true
[
'image' => $id,
'ns' => getNS($id)
],
'&',
true
);
} else {
$item->link = wl($id, '', true, '&');
@@ -289,85 +302,91 @@ function rss_buildItems(&$rss, &$data, $opt) {
break;
case 'diff':
default:
if($ditem['media']) {
if ($ditem['media']) {
$item->link = media_managerURL(
array(
'image' => $id,
'ns' => getNS($id),
'rev' => $date,
'tab_details' => 'history',
'mediado' => 'diff'
), '&', true
[
'image' => $id,
'ns' => getNS($id),
'rev' => $date,
'tab_details' => 'history',
'mediado' => 'diff'
],
'&',
true
);
} else {
$item->link = wl($id, 'rev='.$date.'&do=diff', true, '&');
$item->link = wl($id, 'rev=' . $date . '&do=diff', true, '&');
}
}
// add item content
switch($opt['item_content']) {
switch ($opt['item_content']) {
case 'diff':
case 'htmldiff':
if($ditem['media']) {
if ($ditem['media']) {
$medialog = new MediaChangeLog($id);
$revs = $medialog->getRevisions(0, 1);
$rev = $revs[0];
$src_r = '';
$src_l = '';
$revs = $medialog->getRevisions(0, 1);
$rev = $revs[0];
$src_r = '';
$src_l = '';
if($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)), 300)) {
$more = 'w='.$size[0].'&h='.$size[1].'&t='.@filemtime(mediaFN($id));
if ($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)), 300)) {
$more = 'w=' . $size[0] . '&h=' . $size[1] . '&t=' . @filemtime(mediaFN($id));
$src_r = ml($id, $more, true, '&amp;', true);
}
if($rev && $size = media_image_preview_size($id, $rev, new JpegMeta(mediaFN($id, $rev)), 300)) {
$more = 'rev='.$rev.'&w='.$size[0].'&h='.$size[1];
if ($rev && $size = media_image_preview_size($id, $rev, new JpegMeta(mediaFN($id, $rev)),
300)) {
$more = 'rev=' . $rev . '&w=' . $size[0] . '&h=' . $size[1];
$src_l = ml($id, $more, true, '&amp;', true);
}
$content = '';
if($src_r) {
if ($src_r) {
$content = '<table>';
$content .= '<tr><th width="50%">'.$rev.'</th>';
$content .= '<th width="50%">'.$lang['current'].'</th></tr>';
$content .= '<tr align="center"><td><img src="'.$src_l.'" alt="" /></td><td>';
$content .= '<img src="'.$src_r.'" alt="'.$id.'" /></td></tr>';
$content .= '<tr><th width="50%">' . $rev . '</th>';
$content .= '<th width="50%">' . $lang['current'] . '</th></tr>';
$content .= '<tr align="center"><td><img src="' . $src_l . '" alt="" /></td><td>';
$content .= '<img src="' . $src_r . '" alt="' . $id . '" /></td></tr>';
$content .= '</table>';
}
} else {
require_once(DOKU_INC.'inc/DifferenceEngine.php');
require_once(DOKU_INC . 'inc/DifferenceEngine.php');
$pagelog = new PageChangeLog($id);
$revs = $pagelog->getRevisions(0, 1);
$rev = $revs[0];
$revs = $pagelog->getRevisions(0, 1);
$rev = $revs[0];
if($rev) {
$df = new Diff(explode("\n", rawWiki($id, $rev)),
explode("\n", rawWiki($id, '')));
if ($rev) {
$df = new Diff(
explode("\n", rawWiki($id, $rev)),
explode("\n", rawWiki($id, ''))
);
} else {
$df = new Diff(array(''),
explode("\n", rawWiki($id, '')));
$df = new Diff(
[''],
explode("\n", rawWiki($id, ''))
);
}
if($opt['item_content'] == 'htmldiff') {
if ($opt['item_content'] == 'htmldiff') {
// note: no need to escape diff output, TableDiffFormatter provides 'safe' html
$tdf = new TableDiffFormatter();
$content = '<table>';
$content .= '<tr><th colspan="2" width="50%">'.$rev.'</th>';
$content .= '<th colspan="2" width="50%">'.$lang['current'].'</th></tr>';
$content .= '<tr><th colspan="2" width="50%">' . $rev . '</th>';
$content .= '<th colspan="2" width="50%">' . $lang['current'] . '</th></tr>';
$content .= $tdf->format($df);
$content .= '</table>';
} else {
// note: diff output must be escaped, UnifiedDiffFormatter provides plain text
$udf = new UnifiedDiffFormatter();
$content = "<pre>\n".hsc($udf->format($df))."\n</pre>";
$content = "<pre>\n" . hsc($udf->format($df)) . "\n</pre>";
}
}
break;
case 'html':
if($ditem['media']) {
if($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)))) {
$more = 'w='.$size[0].'&h='.$size[1].'&t='.@filemtime(mediaFN($id));
$src = ml($id, $more, true, '&amp;', true);
$content = '<img src="'.$src.'" alt="'.$id.'" />';
if ($ditem['media']) {
if ($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)))) {
$more = 'w=' . $size[0] . '&h=' . $size[1] . '&t=' . @filemtime(mediaFN($id));
$src = ml($id, $more, true, '&amp;', true);
$content = '<img src="' . $src . '" alt="' . $id . '" />';
} else {
$content = '';
}
@@ -385,20 +404,23 @@ function rss_buildItems(&$rss, &$data, $opt) {
$content = preg_replace('/(<img .*?class="mediaright")/s', '\\1 align="right"', $content);
// make URLs work when canonical is not set, regexp instead of rerendering!
if(!$conf['canonical']) {
if (!$conf['canonical']) {
$base = preg_quote(DOKU_REL, '/');
$content = preg_replace('/(<a href|<img src)="('.$base.')/s', '$1="'.DOKU_URL, $content);
$content = preg_replace(
'/(<a href|<img src)="(' . $base . ')/s', '$1="' . DOKU_URL,
$content
);
}
}
break;
case 'abstract':
default:
if($ditem['media']) {
if($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)))) {
$more = 'w='.$size[0].'&h='.$size[1].'&t='.@filemtime(mediaFN($id));
$src = ml($id, $more, true, '&amp;', true);
$content = '<img src="'.$src.'" alt="'.$id.'" />';
if ($ditem['media']) {
if ($size = media_image_preview_size($id, '', new JpegMeta(mediaFN($id)))) {
$more = 'w=' . $size[0] . '&h=' . $size[1] . '&t=' . @filemtime(mediaFN($id));
$src = ml($id, $more, true, '&amp;', true);
$content = '<img src="' . $src . '" alt="' . $id . '" />';
} else {
$content = '';
}
@@ -410,19 +432,19 @@ function rss_buildItems(&$rss, &$data, $opt) {
// add user
# FIXME should the user be pulled from metadata as well?
$user = @$ditem['user']; // the @ spares time repeating lookup
if(blank($user)) {
$item->author = 'Anonymous';
$user = @$ditem['user']; // the @ spares time repeating lookup
if (blank($user)) {
$item->author = 'Anonymous';
$item->authorEmail = 'anonymous@undisclosed.example.com';
} else {
$item->author = $user;
$item->author = $user;
$item->authorEmail = $user . '@undisclosed.example.com';
// get real user name if configured
if($conf['useacl'] && $auth) {
if ($conf['useacl'] && $auth) {
$userInfo = $auth->getUserData($user);
if($userInfo) {
switch($conf['showuseras']) {
if ($userInfo) {
switch ($conf['showuseras']) {
case 'username':
case 'username_link':
$item->author = $userInfo['name'];
@@ -438,22 +460,22 @@ function rss_buildItems(&$rss, &$data, $opt) {
}
// add category
if(isset($meta['subject'])) {
if (isset($meta['subject'])) {
$item->category = $meta['subject'];
} else {
$cat = getNS($id);
if($cat) $item->category = $cat;
if ($cat) $item->category = $cat;
}
// finally add the item to the feed object, after handing it to registered plugins
$evdata = array(
$evdata = [
'item' => &$item,
'opt' => &$opt,
'ditem' => &$ditem,
'rss' => &$rss
);
];
$evt = new Event('FEED_ITEM_ADD', $evdata);
if($evt->advise_before()) {
if ($evt->advise_before()) {
$rss->addItem($item);
}
$evt->advise_after(); // for completeness
@@ -467,14 +489,15 @@ function rss_buildItems(&$rss, &$data, $opt) {
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
function rssRecentChanges($opt) {
function rssRecentChanges($opt)
{
global $conf;
$flags = 0;
if(!$conf['rss_show_deleted']) $flags += RECENTS_SKIP_DELETED;
if(!$opt['show_minor']) $flags += RECENTS_SKIP_MINORS;
if($opt['only_new']) $flags += RECENTS_ONLY_CREATION;
if($opt['content_type'] == 'media' && $conf['mediarevisions']) $flags += RECENTS_MEDIA_CHANGES;
if($opt['content_type'] == 'both' && $conf['mediarevisions']) $flags += RECENTS_MEDIA_PAGES_MIXED;
if (!$conf['rss_show_deleted']) $flags += RECENTS_SKIP_DELETED;
if (!$opt['show_minor']) $flags += RECENTS_SKIP_MINORS;
if ($opt['only_new']) $flags += RECENTS_ONLY_CREATION;
if ($opt['content_type'] == 'media' && $conf['mediarevisions']) $flags += RECENTS_MEDIA_CHANGES;
if ($opt['content_type'] == 'both' && $conf['mediarevisions']) $flags += RECENTS_MEDIA_PAGES_MIXED;
$recents = getRecents(0, $opt['items'], $opt['namespace'], $flags);
return $recents;
@@ -485,19 +508,20 @@ function rssRecentChanges($opt) {
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
function rssListNamespace($opt) {
require_once(DOKU_INC.'inc/search.php');
function rssListNamespace($opt)
{
require_once(DOKU_INC . 'inc/search.php');
global $conf;
$ns = ':'.cleanID($opt['namespace']);
$ns = ':' . cleanID($opt['namespace']);
$ns = utf8_encodeFN(str_replace(':', '/', $ns));
$data = array();
$search_opts = array(
'depth' => 1,
$data = [];
$search_opts = [
'depth' => 1,
'pagesonly' => true,
'listfiles' => true
);
];
search($data, $conf['datadir'], 'search_universal', $search_opts, $ns, $lvl = 1, $opt['sort']);
return $data;
@@ -508,10 +532,11 @@ function rssListNamespace($opt) {
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
function rssSearch($opt) {
if(!$opt['search_query']) return array();
function rssSearch($opt)
{
if (!$opt['search_query'] || !actionOK('search')) return [];
require_once(DOKU_INC.'inc/fulltext.php');
require_once(DOKU_INC . 'inc/fulltext.php');
$data = ft_pageSearch($opt['search_query'], $poswords);
$data = array_keys($data);

View File

@@ -2,7 +2,6 @@
namespace dokuwiki\Action;
use dokuwiki\Action\Exception\ActionDisabledException;
use dokuwiki\Action\Exception\ActionException;
use dokuwiki\Action\Exception\FatalException;
@@ -69,6 +68,7 @@ abstract class AbstractAction {
* 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);

View File

@@ -21,6 +21,9 @@ abstract class AbstractAliasAction extends AbstractAction {
return AUTH_NONE;
}
/**
* @throws FatalException
*/
public function preProcess() {
throw new FatalException('Alias Actions need to implement preProcess to load the aliased action');
}

View File

@@ -17,7 +17,7 @@ abstract class AbstractUserAction extends AbstractAclAction {
public function checkPreconditions() {
parent::checkPreconditions();
global $INPUT;
if(!$INPUT->server->str('REMOTE_USER')) {
if($INPUT->server->str('REMOTE_USER') === '') {
throw new ActionUserRequiredException();
}
}

View File

@@ -3,6 +3,7 @@
namespace dokuwiki\Action;
use dokuwiki\Action\Exception\ActionException;
use dokuwiki\Extension\AdminPlugin;
/**
* Class Admin
@@ -18,17 +19,13 @@ class Admin extends AbstractUserAction {
return AUTH_READ; // let in check later
}
public function checkPreconditions() {
parent::checkPreconditions();
}
/** @inheritDoc */
public function preProcess() {
global $INPUT;
global $INFO;
// retrieve admin plugin name from $_REQUEST['page']
if(($page = $INPUT->str('page', '', true)) != '') {
/** @var $plugin \dokuwiki\Extension\AdminPlugin */
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');
@@ -38,8 +35,8 @@ class Admin extends AbstractUserAction {
}
}
/** @inheritDoc */
public function tplContent() {
tpl_admin();
}
}

View File

@@ -2,6 +2,8 @@
namespace dokuwiki\Action;
use dokuwiki\Ui;
/**
* Class Backlink
*
@@ -9,16 +11,18 @@ namespace dokuwiki\Action;
*
* @package dokuwiki\Action
*/
class Backlink extends AbstractAction {
class Backlink extends AbstractAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_NONE;
}
/** @inheritdoc */
public function tplContent() {
html_backlinks();
public function tplContent()
{
(new Ui\Backlinks)->show();
}
}

View File

@@ -13,7 +13,10 @@ use dokuwiki\Action\Exception\ActionAbort;
*/
class Cancel extends AbstractAliasAction {
/** @inheritdoc */
/**
* @inheritdoc
* @throws ActionAbort
*/
public function preProcess() {
global $ID;
unlock($ID);

View File

@@ -2,6 +2,8 @@
namespace dokuwiki\Action;
use dokuwiki\Ui;
/**
* Class Conflict
*
@@ -9,26 +11,29 @@ namespace dokuwiki\Action;
*
* @package dokuwiki\Action
*/
class Conflict extends AbstractAction {
class Conflict extends AbstractAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
global $INFO;
if($INFO['exists']) {
if ($INFO['exists']) {
return AUTH_EDIT;
} else {
return AUTH_CREATE;
}
}
public function tplContent() {
/** @inheritdoc */
public function tplContent()
{
global $PRE;
global $TEXT;
global $SUF;
global $SUM;
html_conflict(con($PRE, $TEXT, $SUF), $SUM);
html_diff(con($PRE, $TEXT, $SUF), false);
$text = con($PRE, $TEXT, $SUF);
(new Ui\PageConflict($text, $SUM))->show();
}
}

View File

@@ -2,6 +2,9 @@
namespace dokuwiki\Action;
use dokuwiki\Extension\Event;
use dokuwiki\Ui;
/**
* Class Denied
*
@@ -9,15 +12,41 @@ namespace dokuwiki\Action;
*
* @package dokuwiki\Action
*/
class Denied extends AbstractAclAction {
class Denied extends AbstractAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_NONE;
}
public function tplContent() {
html_denied();
/** @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 Ui\Login)->show();
}
}
$event->advise_after();
}
/**
* Display error on denied pages
*
* @author Andreas Gohr <andi@splitbrain.org>
*
* @return void
*/
public function showBanner()
{
// print intro
print p_locale_xhtml('denied');
}
}

View File

@@ -2,6 +2,8 @@
namespace dokuwiki\Action;
use dokuwiki\Ui;
/**
* Class Diff
*
@@ -9,27 +11,31 @@ namespace dokuwiki\Action;
*
* @package dokuwiki\Action
*/
class Diff extends AbstractAction {
class Diff extends AbstractAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_READ;
}
/** @inheritdoc */
public function preProcess() {
public function preProcess()
{
global $INPUT;
// store the selected diff type in cookie
$difftype = $INPUT->str('difftype');
if(!empty($difftype)) {
if (!empty($difftype)) {
set_doku_pref('difftype', $difftype);
}
}
/** @inheritdoc */
public function tplContent() {
html_diff();
public function tplContent()
{
global $INFO;
(new Ui\PageDiff($INFO['id']))->preference('showIntro', true)->show();
}
}

View File

@@ -3,6 +3,7 @@
namespace dokuwiki\Action;
use dokuwiki\Action\Exception\ActionException;
use dokuwiki\Ui;
/**
* Class Draft
@@ -12,12 +13,13 @@ use dokuwiki\Action\Exception\ActionException;
* @package dokuwiki\Action
* @fixme combine with Recover?
*/
class Draft extends AbstractAction {
class Draft extends AbstractAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
global $INFO;
if($INFO['exists']) {
if ($INFO['exists']) {
return AUTH_EDIT;
} else {
return AUTH_CREATE;
@@ -25,15 +27,17 @@ class Draft extends AbstractAction {
}
/** @inheritdoc */
public function checkPreconditions() {
public function checkPreconditions()
{
parent::checkPreconditions();
global $INFO;
if(!file_exists($INFO['draft'])) throw new ActionException('edit');
if (!file_exists($INFO['draft'])) throw new ActionException('edit');
}
/** @inheritdoc */
public function tplContent() {
html_draft();
public function tplContent()
{
(new Ui\PageDraft)->show();
}
}

View File

@@ -28,7 +28,7 @@ class Draftdel extends AbstractAction {
public function preProcess() {
global $INFO, $ID;
$draft = new \dokuwiki\Draft($ID, $INFO['client']);
if ($draft->isDraftAvailable()) {
if ($draft->isDraftAvailable() && checkSecurityToken()) {
$draft->deleteDraft();
}

View File

@@ -3,6 +3,7 @@
namespace dokuwiki\Action;
use dokuwiki\Action\Exception\ActionAbort;
use dokuwiki\Ui;
/**
* Class Edit
@@ -11,12 +12,13 @@ use dokuwiki\Action\Exception\ActionAbort;
*
* @package dokuwiki\Action
*/
class Edit extends AbstractAction {
class Edit extends AbstractAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
global $INFO;
if($INFO['exists']) {
if ($INFO['exists']) {
return AUTH_READ; // we check again below
} else {
return AUTH_CREATE;
@@ -26,18 +28,20 @@ class Edit extends AbstractAction {
/**
* @inheritdoc falls back to 'source' if page not writable
*/
public function checkPreconditions() {
public function checkPreconditions()
{
parent::checkPreconditions();
global $INFO;
// no edit permission? view source
if($INFO['exists'] && !$INFO['writable']) {
if ($INFO['exists'] && !$INFO['writable']) {
throw new ActionAbort('source');
}
}
/** @inheritdoc */
public function preProcess() {
public function preProcess()
{
global $ID;
global $INFO;
@@ -50,9 +54,9 @@ class Edit extends AbstractAction {
global $lang;
global $DATE;
if(!isset($TEXT)) {
if($INFO['exists']) {
if($RANGE) {
if (!isset($TEXT)) {
if ($INFO['exists']) {
if ($RANGE) {
list($PRE, $TEXT, $SUF) = rawWikiSlices($RANGE, $ID, $REV);
} else {
$TEXT = rawWiki($ID, $REV);
@@ -63,29 +67,30 @@ class Edit extends AbstractAction {
}
//set summary default
if(!$SUM) {
if($REV) {
if (!$SUM) {
if ($REV) {
$SUM = sprintf($lang['restored'], dformat($REV));
} elseif(!$INFO['exists']) {
} 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));
if (!$DATE) $DATE = @filemtime(wikiFN($ID));
//check if locked by anyone - if not lock for my self
$lockedby = checklock($ID);
if($lockedby) {
if ($lockedby) {
throw new ActionAbort('locked');
};
}
lock($ID);
}
/** @inheritdoc */
public function tplContent() {
html_edit();
public function tplContent()
{
(new Ui\Editor)->show();
}
}

View File

@@ -2,6 +2,8 @@
namespace dokuwiki\Action;
use dokuwiki\Ui;
/**
* Class Index
*
@@ -9,17 +11,19 @@ namespace dokuwiki\Action;
*
* @package dokuwiki\Action
*/
class Index extends AbstractAction {
class Index extends AbstractAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_NONE;
}
/** @inheritdoc */
public function tplContent() {
public function tplContent()
{
global $IDX;
html_index($IDX);
(new Ui\Index($IDX))->show();
}
}

View File

@@ -2,6 +2,8 @@
namespace dokuwiki\Action;
use dokuwiki\Ui;
/**
* Class Locked
*
@@ -9,17 +11,46 @@ namespace dokuwiki\Action;
*
* @package dokuwiki\Action
*/
class Locked extends AbstractAction {
class Locked extends AbstractAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_READ;
}
/** @inheritdoc */
public function tplContent() {
html_locked();
html_edit();
public function tplContent()
{
$this->showBanner();
(new Ui\Editor)->show();
}
/**
* Display error on locked pages
*
* @author Andreas Gohr <andi@splitbrain.org>
*
* @return void
*/
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
print p_locale_xhtml('locked');
print '<ul>';
print '<li><div class="li"><strong>'.$lang['lockedby'].'</strong> '.editorinfo($INFO['locked']).'</div></li>';
print '<li><div class="li"><strong>'.$lang['lockexpire'].'</strong> '.$expire.' ('.$min.' min)</div></li>';
print '</ul>'.DOKU_LF;
}
}

View File

@@ -3,6 +3,7 @@
namespace dokuwiki\Action;
use dokuwiki\Action\Exception\ActionException;
use dokuwiki\Ui;
/**
* Class Login
@@ -11,26 +12,29 @@ use dokuwiki\Action\Exception\ActionException;
*
* @package dokuwiki\Action
*/
class Login extends AbstractAclAction {
class Login extends AbstractAclAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_NONE;
}
/** @inheritdoc */
public function checkPreconditions() {
public function checkPreconditions()
{
global $INPUT;
parent::checkPreconditions();
if($INPUT->server->has('REMOTE_USER')) {
if ($INPUT->server->has('REMOTE_USER')) {
// nothing to do
throw new ActionException();
}
}
/** @inheritdoc */
public function tplContent() {
html_login();
public function tplContent()
{
(new Ui\Login)->show();
}
}

View File

@@ -4,6 +4,7 @@ namespace dokuwiki\Action;
use dokuwiki\Action\Exception\ActionDisabledException;
use dokuwiki\Action\Exception\ActionException;
use dokuwiki\Extension\AuthPlugin;
/**
* Class Logout
@@ -23,7 +24,7 @@ class Logout extends AbstractUserAction {
public function checkPreconditions() {
parent::checkPreconditions();
/** @var \dokuwiki\Extension\AuthPlugin $auth */
/** @var AuthPlugin $auth */
global $auth;
if(!$auth->canDo('logout')) throw new ActionDisabledException();
}
@@ -33,6 +34,8 @@ class Logout extends AbstractUserAction {
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')) {

View File

@@ -2,6 +2,8 @@
namespace dokuwiki\Action;
use dokuwiki\Extension\Event;
/**
* Class Plugin
*
@@ -23,7 +25,7 @@ class Plugin extends AbstractAction {
* @triggers TPL_ACT_UNKNOWN
*/
public function tplContent() {
$evt = new \dokuwiki\Extension\Event('TPL_ACT_UNKNOWN', $this->actionname);
$evt = new Event('TPL_ACT_UNKNOWN', $this->actionname);
if($evt->advise_before()) {
msg('Failed to handle action: ' . hsc($this->actionname), -1);
}

View File

@@ -2,6 +2,8 @@
namespace dokuwiki\Action;
use dokuwiki\Ui;
/**
* Class Preview
*
@@ -9,26 +11,29 @@ namespace dokuwiki\Action;
*
* @package dokuwiki\Action
*/
class Preview extends Edit {
class Preview extends Edit
{
/** @inheritdoc */
public function preProcess() {
public function preProcess()
{
header('X-XSS-Protection: 0');
$this->savedraft();
parent::preProcess();
}
/** @inheritdoc */
public function tplContent() {
public function tplContent()
{
global $TEXT;
html_edit();
html_show($TEXT);
(new Ui\Editor)->show();
(new Ui\PageView($TEXT))->show();
}
/**
* Saves a draft on preview
*/
protected function savedraft() {
protected function savedraft()
{
global $ID, $INFO;
$draft = new \dokuwiki\Draft($ID, $INFO['client']);
if (!$draft->saveDraft()) {

View File

@@ -4,6 +4,8 @@ namespace dokuwiki\Action;
use dokuwiki\Action\Exception\ActionAbort;
use dokuwiki\Action\Exception\ActionDisabledException;
use dokuwiki\Extension\AuthPlugin;
use dokuwiki\Ui;
/**
* Class Profile
@@ -12,34 +14,38 @@ use dokuwiki\Action\Exception\ActionDisabledException;
*
* @package dokuwiki\Action
*/
class Profile extends AbstractUserAction {
class Profile extends AbstractUserAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_NONE;
}
/** @inheritdoc */
public function checkPreconditions() {
public function checkPreconditions()
{
parent::checkPreconditions();
/** @var \dokuwiki\Extension\AuthPlugin $auth */
/** @var AuthPlugin $auth */
global $auth;
if(!$auth->canDo('Profile')) throw new ActionDisabledException();
}
/** @inheritdoc */
public function preProcess() {
public function preProcess()
{
global $lang;
if(updateprofile()) {
if (updateprofile()) {
msg($lang['profchanged'], 1);
throw new ActionAbort('show');
}
}
/** @inheritdoc */
public function tplContent() {
html_updateprofile();
public function tplContent()
{
(new Ui\UserProfile)->show();
}
}

View File

@@ -4,6 +4,7 @@ namespace dokuwiki\Action;
use dokuwiki\Action\Exception\ActionAbort;
use dokuwiki\Action\Exception\ActionDisabledException;
use dokuwiki\Extension\AuthPlugin;
/**
* Class ProfileDelete
@@ -23,7 +24,7 @@ class ProfileDelete extends AbstractUserAction {
public function checkPreconditions() {
parent::checkPreconditions();
/** @var \dokuwiki\Extension\AuthPlugin $auth */
/** @var AuthPlugin $auth */
global $auth;
if(!$auth->canDo('delUser')) throw new ActionDisabledException();
}

View File

@@ -2,6 +2,8 @@
namespace dokuwiki\Action;
use dokuwiki\Ui;
/**
* Class Recent
*
@@ -9,21 +11,23 @@ namespace dokuwiki\Action;
*
* @package dokuwiki\Action
*/
class Recent extends AbstractAction {
class Recent extends AbstractAction
{
/** @var string what type of changes to show */
protected $showType = 'both';
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_NONE;
}
/** @inheritdoc */
public function preProcess() {
public function preProcess()
{
global $INPUT;
$show_changes = $INPUT->str('show_changes');
if(!empty($show_changes)) {
if (!empty($show_changes)) {
set_doku_pref('show_changes', $show_changes);
$this->showType = $show_changes;
} else {
@@ -32,9 +36,10 @@ class Recent extends AbstractAction {
}
/** @inheritdoc */
public function tplContent() {
public function tplContent()
{
global $INPUT;
html_recent((int) $INPUT->extract('first')->int('first'), $this->showType);
(new Ui\Recent($INPUT->extract('first')->int('first'), $this->showType))->show();
}
}

View File

@@ -13,7 +13,10 @@ use dokuwiki\Action\Exception\ActionAbort;
*/
class Recover extends AbstractAliasAction {
/** @inheritdoc */
/**
* @inheritdoc
* @throws ActionAbort
*/
public function preProcess() {
throw new ActionAbort('edit');
}

View File

@@ -4,6 +4,8 @@ namespace dokuwiki\Action;
use dokuwiki\Action\Exception\ActionAbort;
use dokuwiki\Action\Exception\ActionDisabledException;
use dokuwiki\Extension\AuthPlugin;
use dokuwiki\Ui;
/**
* Class Register
@@ -12,34 +14,38 @@ use dokuwiki\Action\Exception\ActionDisabledException;
*
* @package dokuwiki\Action
*/
class Register extends AbstractAclAction {
class Register extends AbstractAclAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_NONE;
}
/** @inheritdoc */
public function checkPreconditions() {
public function checkPreconditions()
{
parent::checkPreconditions();
/** @var \dokuwiki\Extension\AuthPlugin $auth */
/** @var AuthPlugin $auth */
global $auth;
global $conf;
if(isset($conf['openregister']) && !$conf['openregister']) throw new ActionDisabledException();
if(!$auth->canDo('addUser')) throw new ActionDisabledException();
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
public function preProcess()
{
if (register()) { // FIXME could be moved from auth to here
throw new ActionAbort('login');
}
}
/** @inheritdoc */
public function tplContent() {
html_register();
public function tplContent()
{
(new Ui\UserRegister)->show();
}
}

View File

@@ -4,6 +4,8 @@ namespace dokuwiki\Action;
use dokuwiki\Action\Exception\ActionAbort;
use dokuwiki\Action\Exception\ActionDisabledException;
use dokuwiki\Extension\AuthPlugin;
use dokuwiki\Ui;
/**
* Class Resendpwd
@@ -12,34 +14,39 @@ use dokuwiki\Action\Exception\ActionDisabledException;
*
* @package dokuwiki\Action
*/
class Resendpwd extends AbstractAclAction {
class Resendpwd extends AbstractAclAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_NONE;
}
/** @inheritdoc */
public function checkPreconditions() {
public function checkPreconditions()
{
parent::checkPreconditions();
/** @var \dokuwiki\Extension\AuthPlugin $auth */
/** @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();
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()) {
public function preProcess()
{
if ($this->resendpwd()) {
throw new ActionAbort('login');
}
}
/** @inheritdoc */
public function tplContent() {
html_resendpwd();
public function tplContent()
{
(new Ui\UserResendPwd)->show();
}
/**
@@ -56,31 +63,32 @@ class Resendpwd extends AbstractAclAction {
* @fixme this should be split up into multiple methods
* @return bool true on success, false on any error
*/
protected function resendpwd() {
protected function resendpwd()
{
global $lang;
global $conf;
/* @var \dokuwiki\Extension\AuthPlugin $auth */
/* @var AuthPlugin $auth */
global $auth;
global $INPUT;
if(!actionOK('resendpwd')) {
if (!actionOK('resendpwd')) {
msg($lang['resendna'], -1);
return false;
}
$token = preg_replace('/[^a-f0-9]+/', '', $INPUT->str('pwauth'));
if($token) {
if ($token) {
// we're in token phase - get user info from token
$tfile = $conf['cachedir'] . '/' . $token[0] . '/' . $token . '.pwauth';
if(!file_exists($tfile)) {
$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)) {
if ((time() - filemtime($tfile)) > (3 * 60 * 60 * 24)) {
msg($lang['resendpwdbadauth'], -1);
$INPUT->remove('pwauth');
@unlink($tfile);
@@ -89,23 +97,23 @@ class Resendpwd extends AbstractAclAction {
$user = io_readfile($tfile);
$userinfo = $auth->getUserData($user, $requireGroups = false);
if(!$userinfo['mail']) {
if (!$userinfo['mail']) {
msg($lang['resendpwdnouser'], -1);
return false;
}
if(!$conf['autopasswd']) { // we let the user choose a password
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')) {
if (!$pass) return false;
if ($pass != $INPUT->str('passchk')) {
msg($lang['regbadpass'], -1);
return false;
}
// change it
if(!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) {
if (!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) {
msg($lang['proffail'], -1);
return false;
}
@@ -113,12 +121,12 @@ class Resendpwd extends AbstractAclAction {
} else { // autogenerate the password and send by mail
$pass = auth_pwgen($user);
if(!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) {
if (!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) {
msg($lang['proffail'], -1);
return false;
}
if(auth_sendPassword($user, $pass)) {
if (auth_sendPassword($user, $pass)) {
msg($lang['resendpwdsuccess'], 1);
} else {
msg($lang['regmailfail'], -1);
@@ -131,9 +139,9 @@ class Resendpwd extends AbstractAclAction {
} else {
// we're in request phase
if(!$INPUT->post->bool('save')) return false;
if (!$INPUT->post->bool('save')) return false;
if(!$INPUT->post->str('login')) {
if (!$INPUT->post->str('login')) {
msg($lang['resendpwdmissing'], -1);
return false;
} else {
@@ -141,14 +149,14 @@ class Resendpwd extends AbstractAclAction {
}
$userinfo = $auth->getUserData($user, $requireGroups = false);
if(!$userinfo['mail']) {
if (!$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';
$tfile = $conf['cachedir'] .'/'. $token[0] .'/'. $token .'.pwauth';
$url = wl('', array('do' => 'resendpwd', 'pwauth' => $token), true, '&');
io_saveFile($tfile, $user);
@@ -161,10 +169,10 @@ class Resendpwd extends AbstractAclAction {
);
$mail = new \Mailer();
$mail->to($userinfo['name'] . ' <' . $userinfo['mail'] . '>');
$mail->to($userinfo['name'] .' <'. $userinfo['mail'] .'>');
$mail->subject($lang['regpwmail']);
$mail->setBody($text, $trep);
if($mail->send()) {
if ($mail->send()) {
msg($lang['resendpwdconfirm'], 1);
} else {
msg($lang['regmailfail'], -1);

View File

@@ -12,10 +12,12 @@ use dokuwiki\Action\Exception\ActionException;
*
* @package dokuwiki\Action
*/
class Revert extends AbstractAction {
class Revert extends AbstractUserAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_EDIT;
}
@@ -26,8 +28,9 @@ class Revert extends AbstractAction {
* @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();
public function preProcess()
{
if (!checkSecurityToken()) throw new ActionException();
global $ID;
global $REV;
@@ -37,14 +40,14 @@ class Revert extends AbstractAction {
// FIXME this feature is not exposed in the GUI currently
$text = '';
$sum = $lang['deleted'];
if($REV) {
if ($REV) {
$text = rawWiki($ID, $REV);
if(!$text) throw new ActionException(); //something went wrong
if (!$text) throw new ActionException(); //something went wrong
$sum = sprintf($lang['restored'], dformat($REV));
}
// spam check
if(checkwordblock($text)) {
if (checkwordblock($text)) {
msg($lang['wordblock'], -1);
throw new ActionException('edit');
}

View File

@@ -2,6 +2,8 @@
namespace dokuwiki\Action;
use dokuwiki\Ui;
/**
* Class Revisions
*
@@ -9,16 +11,18 @@ namespace dokuwiki\Action;
*
* @package dokuwiki\Action
*/
class Revisions extends AbstractAction {
class Revisions extends AbstractAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_READ;
}
/** @inheritdoc */
public function tplContent() {
global $INPUT;
html_revisions($INPUT->int('first'));
public function tplContent()
{
global $INFO, $INPUT;
(new Ui\PageRevisions($INFO['id']))->show($INPUT->int('first'));
}
}

View File

@@ -44,7 +44,10 @@ class Save extends AbstractAction {
throw new ActionException('edit');
}
//conflict check
if($DATE != 0 && $INFO['meta']['date']['modified'] > $DATE) {
if($DATE != 0
&& isset($INFO['meta']['date']['modified'])
&& $INFO['meta']['date']['modified'] > $DATE
) {
throw new ActionException('conflict');
}

View File

@@ -8,6 +8,8 @@
namespace dokuwiki\Action;
use dokuwiki\Ui;
/**
* Class Show
*
@@ -15,22 +17,24 @@ namespace dokuwiki\Action;
*
* @package dokuwiki\Action
*/
class Show extends AbstractAction {
class Show extends AbstractAction
{
/** @inheritdoc */
public function minimumPermission() {
return AUTH_READ;
}
/** @inheritdoc */
public function preProcess() {
public function preProcess()
{
global $ID;
unlock($ID);
}
/** @inheritdoc */
public function tplContent() {
html_show();
public function tplContent()
{
(new Ui\PageView())->show();
}
}

View File

@@ -4,6 +4,7 @@ namespace dokuwiki\Action;
use dokuwiki\Action\Exception\FatalException;
use dokuwiki\Sitemap\Mapper;
use dokuwiki\Utf8\PhpString;
/**
* Class Sitemap
@@ -48,7 +49,7 @@ class Sitemap extends AbstractAction {
if(is_readable($sitemap)) {
// Send headers
header('Content-Type: ' . $mime);
header('Content-Disposition: attachment; filename=' . \dokuwiki\Utf8\PhpString::basename($sitemap));
header('Content-Disposition: attachment; filename=' . PhpString::basename($sitemap));
http_conditionalRequest(filemtime($sitemap));

View File

@@ -2,6 +2,8 @@
namespace dokuwiki\Action;
use dokuwiki\Ui;
/**
* Class Source
*
@@ -9,28 +11,31 @@ namespace dokuwiki\Action;
*
* @package dokuwiki\Action
*/
class Source extends AbstractAction {
class Source extends AbstractAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_READ;
}
/** @inheritdoc */
public function preProcess() {
public function preProcess()
{
global $TEXT;
global $INFO;
global $ID;
global $REV;
if($INFO['exists']) {
if ($INFO['exists']) {
$TEXT = rawWiki($ID, $REV);
}
}
/** @inheritdoc */
public function tplContent() {
html_edit();
public function tplContent()
{
(new Ui\Editor)->show();
}
}

View File

@@ -6,6 +6,8 @@ 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
@@ -14,15 +16,17 @@ use dokuwiki\Extension\Event;
*
* @package dokuwiki\Action
*/
class Subscribe extends AbstractUserAction {
class Subscribe extends AbstractUserAction
{
/** @inheritdoc */
public function minimumPermission() {
public function minimumPermission()
{
return AUTH_READ;
}
/** @inheritdoc */
public function checkPreconditions() {
public function checkPreconditions()
{
parent::checkPreconditions();
global $conf;
@@ -30,43 +34,46 @@ class Subscribe extends AbstractUserAction {
}
/** @inheritdoc */
public function preProcess() {
public function preProcess()
{
try {
$this->handleSubscribeData();
} catch(ActionAbort $e) {
} catch (ActionAbort $e) {
throw $e;
} catch(\Exception $e) {
} catch (Exception $e) {
msg($e->getMessage(), -1);
}
}
/** @inheritdoc */
public function tplContent() {
tpl_subscribe();
public function tplContent()
{
(new Ui\Subscribe)->show();
}
/**
* Handle page 'subscribe'
*
* @author Adrian Lang <lang@cosmocode.de>
* @throws \Exception if (un)subscribing fails
* @throws Exception if (un)subscribing fails
* @throws ActionAbort when (un)subscribing worked
*/
protected function handleSubscribeData() {
protected function handleSubscribeData()
{
global $lang;
global $INFO;
global $INPUT;
// get and preprocess data.
$params = array();
foreach(array('target', 'style', 'action') as $param) {
if($INPUT->has("sub_$param")) {
foreach (array('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;
if (empty($params['action']) || !checkSecurityToken()) return;
// Handle POST data, may throw exception.
Event::createAndTrigger('ACTION_HANDLE_SUBSCRIBE', $params, array($this, 'handlePostData'));
@@ -77,13 +84,13 @@ class Subscribe extends AbstractUserAction {
// Perform action.
$subManager = new SubscriberManager();
if($action === 'unsubscribe') {
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) {
if ($ok) {
msg(
sprintf(
$lang["subscr_{$action}_success"], hsc($INFO['userinfo']['name']),
@@ -93,7 +100,7 @@ class Subscribe extends AbstractUserAction {
throw new ActionAbort('redirect');
}
throw new \Exception(
throw new Exception(
sprintf(
$lang["subscr_{$action}_error"],
hsc($INFO['userinfo']['name']),
@@ -111,20 +118,21 @@ class Subscribe extends AbstractUserAction {
* @author Adrian Lang <lang@cosmocode.de>
*
* @param array &$params the parameters: target, style and action
* @throws \Exception
* @throws Exception
*/
public function handlePostData(&$params) {
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');
if (!isset($params['target'])) {
throw new Exception('no subscription target given');
}
$target = $params['target'];
$valid_styles = array('every', 'digest');
if(substr($target, -1, 1) === ':') {
if (substr($target, -1, 1) === ':') {
// Allow “list” subscribe style since the target is a namespace.
$valid_styles[] = 'list';
}
@@ -138,19 +146,19 @@ class Subscribe extends AbstractUserAction {
);
// Check other conditions.
if($action === 'subscribe') {
if($INFO['userinfo']['mail'] === '') {
throw new \Exception($lang['subscr_subscribe_noaddress']);
if ($action === 'subscribe') {
if ($INFO['userinfo']['mail'] === '') {
throw new Exception($lang['subscr_subscribe_noaddress']);
}
} elseif($action === 'unsubscribe') {
} elseif ($action === 'unsubscribe') {
$is = false;
foreach($INFO['subscribed'] as $subscr) {
if($subscr['target'] === $target) {
foreach ($INFO['subscribed'] as $subscr) {
if ($subscr['target'] === $target) {
$is = true;
}
}
if($is === false) {
throw new \Exception(
if ($is === false) {
throw new Exception(
sprintf(
$lang['subscr_not_subscribed'],
$INPUT->server->str('REMOTE_USER'),

View File

@@ -156,6 +156,7 @@ class ActionRouter {
if(defined('DOKU_UNITTEST')) {
throw $e;
}
ErrorHandler::logException($e);
$msg = 'Something unforeseen has happened: ' . $e->getMessage();
nice_die(hsc($msg));
}

View File

@@ -2,6 +2,9 @@
namespace dokuwiki;
use dokuwiki\Ui;
use dokuwiki\Utf8\Sort;
/**
* Manage all builtin AJAX calls
*
@@ -98,7 +101,7 @@ class Ajax {
$data = array_map('trim', $data);
$data = array_map('noNS', $data);
$data = array_unique($data);
sort($data);
Sort::sort($data);
/* now construct a json */
$suggestions = array(
@@ -165,8 +168,10 @@ class Ajax {
$client = $_SERVER['REMOTE_USER'];
if(!$client) $client = clientIP(true);
$cname = getCacheName($client . $id, '.draft');
@unlink($cname);
$draft = new Draft($id, $client);
if ($draft->isDraftAvailable() && checkSecurityToken()) {
$draft->deleteDraft();
}
}
/**
@@ -238,14 +243,11 @@ class Ajax {
* @author Kate Arzamastseva <pshns@ukr.net>
*/
protected function callMediadiff() {
global $NS;
global $INPUT;
$image = '';
if($INPUT->has('image')) $image = cleanID($INPUT->str('image'));
$NS = getNS($image);
$auth = auth_quickaclcheck("$NS:*");
media_diff($image, $NS, $auth, true);
(new Ui\MediaDiff($image))->preference('fromAjax', true)->show();
}
/**
@@ -320,10 +322,11 @@ class Ajax {
$data = array();
search($data, $conf['datadir'], 'search_index', array('ns' => $ns), $dir);
foreach(array_keys($data) as $item) {
foreach (array_keys($data) as $item) {
$data[$item]['level'] = $lvl + 1;
}
echo html_buildlist($data, 'idx', 'html_list_index', 'html_li_index');
$idx = new Ui\Index;
echo html_buildlist($data, 'idx', [$idx,'formatListItem'], [$idx,'tagListItem']);
}
/**
@@ -351,13 +354,20 @@ class Ajax {
// 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 = array();
foreach($pages as $pid => $title) {
if(strpos(noNS($pid), $id) === false) {
if(strpos(getNS($pid), $id) !== false) {
// match was in the namespace
$dirs[getNS($pid)] = 1; // assoc array avoids dupes
} else {

View File

@@ -0,0 +1,56 @@
<?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')
);
}
}

View File

@@ -2,16 +2,19 @@
namespace dokuwiki\ChangeLog;
use dokuwiki\Logger;
/**
* methods for handling of changelog of pages or media files
* ChangeLog Prototype; methods for handling changelog
*/
abstract class ChangeLog
{
use ChangeLogTrait;
/** @var string */
protected $id;
/** @var int */
protected $chunk_size;
/** @var false|int */
protected $currentRevision;
/** @var array */
protected $cache;
@@ -32,29 +35,8 @@ abstract class ChangeLog
$this->id = $id;
$this->setChunkSize($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 = (int)max($chunk_size, 0);
}
/**
* Returns path to changelog
*
* @return string path to file
*/
abstract protected function getChangelogFilename();
/**
* Returns path to current page/media
*
@@ -63,13 +45,82 @@ abstract class ChangeLog
abstract protected function getFilename();
/**
* Get the changelog information for a specific page id and revision (timestamp)
* 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];
}
/**
* Save revision info to the cache pool
*
* @param array $info Revision info structure
* @return bool
*/
protected function cacheRevisionInfo($info)
{
if (!is_array($info)) return false;
//$this->cache[$this->id][$info['date']] ??= $info; // since php 7.4
$this->cache[$this->id][$info['date']] = $this->cache[$this->id][$info['date']] ?? $info;
return true;
}
/**
* 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)
@@ -78,13 +129,20 @@ abstract class ChangeLog
* - user: user name
* - sum: edit summary (or action reason)
* - extra: extra data (varies by line type)
* - sizechange: change of filesize
*
* @author Ben Coburn <btcoburn@silicodon.net>
* @author Kate Arzamastseva <pshns@ukr.net>
*/
public function getRevisionInfo($rev)
public function getRevisionInfo($rev, $retrieveCurrentRevInfo = true)
{
$rev = max($rev, 0);
$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]) && isset($this->cache[$this->id][$rev])) {
@@ -100,10 +158,8 @@ abstract class ChangeLog
// parse and cache changelog lines
foreach ($lines as $value) {
$tmp = parseChangelogLine($value);
if ($tmp !== false) {
$this->cache[$this->id][$tmp['date']] = $tmp;
}
$info = $this->parseLogLine($value);
$this->cacheRevisionInfo($info);
}
if (!isset($this->cache[$this->id][$rev])) {
return false;
@@ -125,7 +181,7 @@ abstract class ChangeLog
* 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 recieved.
* lines are received.
*
* @param int $first skip the first n changelog lines
* @param int $num number of revisions to return
@@ -140,6 +196,9 @@ abstract class ChangeLog
$lines = array();
$count = 0;
$logfile = $this->getChangelogFilename();
if (!file_exists($logfile)) return $revs;
$num = max($num, 0);
if ($num == 0) {
return $revs;
@@ -148,26 +207,22 @@ abstract class ChangeLog
if ($first < 0) {
$first = 0;
} else {
if (file_exists($this->getFilename())) {
// skip current revision if the page exists
$fileLastMod = $this->getFilename();
if (file_exists($fileLastMod) && $this->isLastRevision(filemtime($fileLastMod))) {
// skip last revision if the page exists
$first = max($first + 1, 0);
}
}
$file = $this->getChangelogFilename();
if (!file_exists($file)) {
return $revs;
}
if (filesize($file) < $this->chunk_size || $this->chunk_size == 0) {
if (filesize($logfile) < $this->chunk_size || $this->chunk_size == 0) {
// read whole file
$lines = file($file);
$lines = file($logfile);
if ($lines === false) {
return $revs;
}
} else {
// read chunks backwards
$fp = fopen($file, 'rb'); // "file pointer"
$fp = fopen($logfile, 'rb'); // "file pointer"
if ($fp === false) {
return $revs;
}
@@ -221,20 +276,17 @@ abstract class ChangeLog
$num = max(min(count($lines) - $first, $num), 0);
if ($first > 0 && $num > 0) {
$lines = array_slice($lines, max(count($lines) - $first - $num, 0), $num);
} else {
if ($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));
}
} 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--) {
$tmp = parseChangelogLine($lines[$i]);
if ($tmp !== false) {
$this->cache[$this->id][$tmp['date']] = $tmp;
$revs[] = $tmp['date'];
$info = $this->parseLogLine($lines[$i]);
if ($this->cacheRevisionInfo($info)) {
$revs[] = $info['date'];
}
}
@@ -242,16 +294,18 @@ abstract class ChangeLog
}
/**
* Get the nth revision left or right handside for a specific page id and revision (timestamp)
* 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 chunck.
* 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 startdate (doesn't need to be revisionnumber)
* @param int $direction give position of returned revision with respect to $rev; positive=next, negative=prev
* @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
@@ -270,13 +324,13 @@ abstract class ChangeLog
list($fp, $lines, $head, $tail, $eof) = $this->readloglines($rev);
if (empty($lines)) return false;
// look for revisions later/earlier then $rev, when founded count till the wanted revision is reached
// 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;
$checkotherchunck = true; //always runs once
while (!$relativerev && $checkotherchunck) {
$tmp = array();
$revCounter = 0;
$relativeRev = false;
$checkOtherChunk = true; //always runs once
while (!$relativeRev && $checkOtherChunk) {
$info = array();
//parse in normal or reverse order
$count = count($lines);
if ($direction > 0) {
@@ -287,25 +341,24 @@ abstract class ChangeLog
$step = -1;
}
for ($i = $start; $i >= 0 && $i < $count; $i = $i + $step) {
$tmp = parseChangelogLine($lines[$i]);
if ($tmp !== false) {
$this->cache[$this->id][$tmp['date']] = $tmp;
$info = $this->parseLogLine($lines[$i]);
if ($this->cacheRevisionInfo($info)) {
//look for revs older/earlier then reference $rev and select $direction-th one
if (($direction > 0 && $tmp['date'] > $rev) || ($direction < 0 && $tmp['date'] < $rev)) {
$revcounter++;
if ($revcounter == abs($direction)) {
$relativerev = $tmp['date'];
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.
$checkotherchunck = $fp
&& ($tmp['date'] == $rev || ($revcounter > 0 && !$relativerev))
$checkOtherChunk = $fp
&& ($info['date'] == $rev || ($revCounter > 0 && !$relativeRev))
&& !(($tail == $eof && $direction > 0) || ($head == 0 && $direction < 0));
if ($checkotherchunck) {
if ($checkOtherChunk) {
list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, $direction);
if (empty($lines)) break;
@@ -315,7 +368,7 @@ abstract class ChangeLog
fclose($fp);
}
return $relativerev;
return $relativeRev;
}
/**
@@ -329,7 +382,7 @@ abstract class ChangeLog
*/
public function getRevisionsAround($rev1, $rev2, $max = 50)
{
$max = floor(abs($max) / 2) * 2 + 1;
$max = intval(abs($max) / 2) * 2 + 1;
$rev1 = max($rev1, 0);
$rev2 = max($rev2, 0);
@@ -341,187 +394,46 @@ abstract class ChangeLog
}
} else {
//empty right side means a removed page. Look up last revision.
$revs = $this->getRevisions(-1, 1);
$rev2 = $revs[0];
$rev2 = $this->currentRevision();
}
//collect revisions around rev2
list($revs2, $allrevs, $fp, $lines, $head, $tail) = $this->retrieveRevisionsAround($rev2, $max);
list($revs2, $allRevs, $fp, $lines, $head, $tail) = $this->retrieveRevisionsAround($rev2, $max);
if (empty($revs2)) return array(array(), array());
//collect revisions around rev1
$index = array_search($rev1, $allrevs);
$index = array_search($rev1, $allRevs);
if ($index === false) {
//no overlapping revisions
list($revs1, , , , ,) = $this->retrieveRevisionsAround($rev1, $max);
if (empty($revs1)) $revs1 = array();
} else {
//revisions overlaps, reuse revisions around rev2
$revs1 = $allrevs;
$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--) {
$tmp = parseChangelogLine($lines[$i]);
if ($tmp !== false) {
$this->cache[$this->id][$tmp['date']] = $tmp;
$revs1[] = $tmp['date'];
$info = $this->parseLogLine($lines[$i]);
if ($this->cacheRevisionInfo($info)) {
$revs1[] = $info['date'];
$index++;
if ($index > floor($max / 2)) break 2;
if ($index > intval($max / 2)) break 2;
}
}
list($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 - floor($max / 2), 0), $max);
$revs1 = array_slice($revs1, max($index - intval($max / 2), 0), $max);
}
return array(array_reverse($revs1), array_reverse($revs2));
}
/**
* Checks if the ID has old revisons
* @return boolean
*/
public function hasRevisions() {
$file = $this->getChangelogFilename();
return file_exists($file);
}
/**
* Returns lines from changelog.
* If file larger than $chuncksize, only chunck is read that could 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;
}
$head = 0;
fseek($fp, 0, SEEK_END);
$eof = ftell($fp);
$tail = $eof;
// find chunk
while ($tail - $head > $this->chunk_size) {
$finger = $head + floor(($tail - $head) / 2.0);
$finger = $this->getNewlinepointer($fp, $finger);
$tmp = fgets($fp);
if ($finger == $head || $finger == $tail) {
break;
}
$tmp = parseChangelogLine($tmp);
$finger_rev = $tmp['date'];
if ($finger_rev > $rev) {
$tail = $finger;
} else {
$head = $finger;
}
}
if ($tail - $head < 1) {
// cound not find chunk, assume requested rev is missing
fclose($fp);
return false;
}
$lines = $this->readChunk($fp, $head, $tail);
}
return array(
$fp,
$lines,
$head,
$tail,
$eof,
);
}
/**
* Read chunk and return array with lines of given chunck.
* Has no check if $head and $tail are really at a new line
*
* @param resource $fp resource filepointer
* @param int $head start point chunck
* @param int $tail end point chunck
* @return array lines read from chunck
*/
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 filepointer
* @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;
}
/**
* 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 == @filemtime($this->getFilename());
}
/**
* Return an existing revision for a specific date which is
* the current one or younger or equal then the date
@@ -531,8 +443,9 @@ abstract class ChangeLog
*/
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($this->getFilename()) && $date_at >= @filemtime($this->getFilename())) {
if (file_exists($fileLastMod) && $date_at >= @filemtime($fileLastMod)) {
return '';
} else {
if ($rev = $this->getRelativeRevision($date_at + 1, -1)) { //+1 to get also the requested date revision
@@ -543,124 +456,223 @@ abstract class ChangeLog
}
}
/**
* Returns the next lines of the changelog of the chunck before head or after tail
*
* @param resource $fp filepointer
* @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 readed chunk
* - $head: head of chunk
* - $tail: tail of chunk
*/
protected function readAdjacentChunk($fp, $head, $tail, $direction)
{
if (!$fp) return array(array(), $head, $tail);
if ($direction > 0) {
//read forward
$head = $tail;
$tail = $head + floor($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 chunck
$lines = $this->readChunk($fp, $head, $tail);
return array($lines, $head, $tail);
}
/**
* 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
* - $requestedRevs: array of with $max revision timestamps
* - $revs: all parsed revision timestamps
* - $fp: filepointer only defined for chuck reading, needs closing.
* - $fp: file pointer only defined for chuck reading, needs closing.
* - $lines: non-parsed changelog lines before the parsed revisions
* - $head: position of first readed changelogline
* - $lasttail: position of end of last readed changelogline
* - $head: position of first read changelog line
* - $lastTail: position of end of last read changelog line
* otherwise false
*/
protected function retrieveRevisionsAround($rev, $max)
{
$revs = array();
$afterCount = $beforeCount = 0;
//get lines from changelog
list($fp, $lines, $starthead, $starttail, /* $eof */) = $this->readloglines($rev);
list($fp, $lines, $startHead, $startTail, $eof) = $this->readloglines($rev);
if (empty($lines)) return false;
//parse chunk containing $rev, and read forward more chunks until $max/2 is reached
$head = $starthead;
$tail = $starttail;
$revs = array();
$aftercount = $beforecount = 0;
//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) {
$tmp = parseChangelogLine($line);
if ($tmp !== false) {
$this->cache[$this->id][$tmp['date']] = $tmp;
$revs[] = $tmp['date'];
if ($tmp['date'] >= $rev) {
$info = $this->parseLogLine($line);
if ($this->cacheRevisionInfo($info)) {
$revs[] = $info['date'];
if ($info['date'] >= $rev) {
//count revs after reference $rev
$aftercount++;
if ($aftercount == 1) $beforecount = count($revs);
$afterCount++;
if ($afterCount == 1) $beforeCount = count($revs);
}
//enough revs after reference $rev?
if ($aftercount > floor($max / 2)) break 2;
if ($afterCount > intval($max / 2)) break 2;
}
}
//retrieve next chunk
list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, 1);
}
if ($aftercount == 0) return false;
$lastTail = $tail;
$lasttail = $tail;
// add a possible revision of external edit, create or deletion
if ($lastTail == $eof && $afterCount <= intval($max / 2) &&
count($revs) && !$this->isCurrentRevision($revs[count($revs)-1])
) {
$revs[] = $this->currentRevision;
$afterCount++;
}
//read additional chuncks backward until $max/2 is reached and total number of revs is equal to $max
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 = array();
$i = 0;
if ($aftercount > 0) {
$head = $starthead;
$tail = $starttail;
if ($afterCount > 0) {
$head = $startHead;
$tail = $startTail;
while ($head > 0) {
list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, -1);
for ($i = count($lines) - 1; $i >= 0; $i--) {
$tmp = parseChangelogLine($lines[$i]);
if ($tmp !== false) {
$this->cache[$this->id][$tmp['date']] = $tmp;
$revs[] = $tmp['date'];
$beforecount++;
$info = $this->parseLogLine($lines[$i]);
if ($this->cacheRevisionInfo($info)) {
$revs[] = $info['date'];
$beforeCount++;
//enough revs before reference $rev?
if ($beforecount > max(floor($max / 2), $max - $aftercount)) break 2;
if ($beforeCount > max(intval($max / 2), $max - $afterCount)) break 2;
}
}
}
}
sort($revs);
//keep only non-parsed lines
$lines = array_slice($lines, 0, $i);
//trunk desired selection
$requestedrevs = array_slice($revs, -$max, $max);
return array($requestedrevs, $revs, $fp, $lines, $head, $lasttail);
sort($revs);
//trunk desired selection
$requestedRevs = array_slice($revs, -$max, $max);
return array($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)
*
* @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,
];
} 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,
];
}
// 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;
}
}

View File

@@ -0,0 +1,269 @@
<?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 it's components
*
* @author Ben Coburn <btcoburn@silicodon.net>
*
* @param string $line changelog line
* @return array|bool parsed line or false
*/
public static function parseLogLine($line)
{
$info = explode("\t", rtrim($line, "\n"));
if ($info !== false && count($info) > 1) {
return [
'date' => (int)$info[0], // unix timestamp
'ip' => $info[1], // IPv4 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' => (isset($info[7]) && $info[7] !== '') ? (int)$info[7] : null, //
];
} else {
return false;
}
}
/**
* Build a changelog line from it's 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 = array(
'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 = (int)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 + intval(($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 array(
$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 array(array(), $head, $tail);
if ($direction > 0) {
//read forward
$head = $tail;
$tail = $head + intval($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 array($lines, $head, $tail);
}
}

View File

@@ -3,7 +3,7 @@
namespace dokuwiki\ChangeLog;
/**
* handles changelog of a media file
* Class MediaChangeLog; handles changelog of a media file
*/
class MediaChangeLog extends ChangeLog
{
@@ -21,10 +21,40 @@ class MediaChangeLog extends ChangeLog
/**
* Returns path to current page/media
*
* @param string|int $rev empty string or revision timestamp
* @return string path to file
*/
protected function getFilename()
protected function getFilename($rev = '')
{
return mediaFN($this->id);
return mediaFN($this->id, $rev);
}
/**
* 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 = $this->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'];
$this->cache[$this->id][$this->currentRevision] = $info;
return $info;
}
}

View File

@@ -3,7 +3,7 @@
namespace dokuwiki\ChangeLog;
/**
* handles changelog of a wiki page
* Class PageChangeLog; handles changelog of a wiki page
*/
class PageChangeLog extends ChangeLog
{
@@ -21,10 +21,40 @@ class PageChangeLog extends ChangeLog
/**
* Returns path to current page/media
*
* @param string|int $rev empty string or revision timestamp
* @return string path to file
*/
protected function getFilename()
protected function getFilename($rev = '')
{
return wikiFN($this->id);
return wikiFN($this->id, $rev);
}
/**
* 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 = $this->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'];
$this->cache[$this->id][$this->currentRevision] = $info;
return $info;
}
}

View File

@@ -0,0 +1,396 @@
<?php
namespace dokuwiki\ChangeLog;
/**
* Class RevisionInfo
*
* Provides methods to show Revision Information in DokuWiki Ui components:
* - Ui\Recent
* - Ui\PageRevisions
* - Ui\MediaRevisions
*/
class RevisionInfo
{
/* @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'])) {
// define strategy context
$info['mode'] = strrpos($info['id'], '.') ? 'media' : 'page';
} else {
$info = [
'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');
switch ($this->val('mode')) {
case 'media': // media file revision
return media_printicon($id);
case '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');
switch ($this->val('mode')) {
case '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));
break;
case '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';
} else {
if($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 '&mdash;';
$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'));
switch ($this->val('mode')) {
case 'media': // media file revision
$href = ml($id, $params, false, '&');
$exists = file_exists(mediaFN($id, $rev));
break;
case 'page': // page revision
$href = wl($id, $params, false, '&');
$exists = page_exists($id, $rev);
}
if($exists) {
$class = 'wikilink1';
} else {
if($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 = '';
switch ($this->val('mode')) {
case '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, '&');
}
break;
case '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 = '';
switch ($this->val('mode')) {
case 'media': // media file revision
if (!$this->isCurrent() && file_exists(mediaFN($id, $rev))) {
$param = ['mediado'=>'diff', 'image'=> $id, 'rev'=> $rev];
$href = media_managerURL($param, '&');
}
break;
case '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');
switch ($this->val('mode')) {
case 'media': // media file revision
$param = ['tab_details'=>'history', 'ns'=> getNS($id), 'image'=> $id];
$href = media_managerURL($param, '&');
break;
case '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'].')' : '';
}
}

View File

@@ -5,6 +5,7 @@ namespace dokuwiki\Debug;
use Doku_Event;
use dokuwiki\Extension\EventHandler;
use dokuwiki\Logger;
class DebugHelper
{
@@ -13,12 +14,12 @@ class DebugHelper
/**
* 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 $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)
public static function dbgDeprecatedFunction($alternative = '', $callerOffset = 1, $thing = '')
{
global $conf;
/** @var EventHandler $EVENT_HANDLER */
@@ -38,17 +39,21 @@ class DebugHelper
list($self, $call) = $backtrace;
if (!$thing) {
$thing = trim(
(!empty($self['class']) ? ($self['class'] . '::') : '') .
$self['function'] . '()', ':');
}
self::triggerDeprecationEvent(
$backtrace,
$alternative,
trim(
(!empty($self['class']) ? ($self['class'] . '::') : '') .
$self['function'] . '()', ':'),
$thing,
trim(
(!empty($call['class']) ? ($call['class'] . '::') : '') .
$call['function'] . '()', ':'),
$call['file'],
$call['line']
$self['file'],
$self['line']
);
}
@@ -160,7 +165,7 @@ class DebugHelper
if ($event->data['alternative']) {
$msg .= ' ' . $event->data['alternative'] . ' should be used instead!';
}
dbglog($msg);
Logger::getInstance(Logger::LOG_DEPRECATED)->log($msg);
}
$event->advise_after();
}

View File

@@ -25,7 +25,7 @@ class Draft
{
$this->id = $ID;
$this->client = $client;
$this->cname = getCacheName($client.$ID, '.draft');
$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

View File

@@ -0,0 +1,141 @@
<?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
{
/**
* 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']);
}
}
/**
* 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)
{
return Logger::getInstance()->log(
get_class($e) . ': ' . $e->getMessage(),
$e->getTraceAsString(),
$e->getFile(),
$e->getLine()
);
}
/**
* 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;
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace dokuwiki\Exception;
/**
* Fatal Errors are converted into this Exception in out Shutdown handler
*/
class FatalException extends \ErrorException
{
}

View File

@@ -3,6 +3,8 @@
namespace dokuwiki\Extension;
use dokuwiki\Logger;
/**
* The Action plugin event
*/
@@ -71,7 +73,8 @@ class Event
if ($EVENT_HANDLER !== null) {
$EVENT_HANDLER->process_event($this, 'BEFORE');
} else {
dbglog($this->name . ':BEFORE event triggered before event system was initialized');
Logger::getInstance(Logger::LOG_DEBUG)
->log($this->name . ':BEFORE event triggered before event system was initialized');
}
return (!$enablePreventDefault || $this->runDefault);
@@ -92,7 +95,8 @@ class Event
if ($EVENT_HANDLER !== null) {
$EVENT_HANDLER->process_event($this, 'AFTER');
} else {
dbglog($this->name . ':AFTER event triggered before event system was initialized');
Logger::getInstance(Logger::LOG_DEBUG)->
log($this->name . ':AFTER event triggered before event system was initialized');
}
}

View File

@@ -2,6 +2,8 @@
namespace dokuwiki\Extension;
use dokuwiki\ErrorHandler;
/**
* Class to encapsulate access to dokuwiki plugins
*
@@ -90,42 +92,49 @@ class PluginController
$class = $type . '_plugin_' . $name;
//plugin already loaded?
if (!empty($DOKU_PLUGINS[$type][$name])) {
if ($new || !$DOKU_PLUGINS[$type][$name]->isSingleton()) {
return class_exists($class, true) ? new $class : null;
try {
//plugin already loaded?
if (!empty($DOKU_PLUGINS[$type][$name])) {
if ($new || !$DOKU_PLUGINS[$type][$name]->isSingleton()) {
return class_exists($class, true) ? new $class : null;
}
return $DOKU_PLUGINS[$type][$name];
}
return $DOKU_PLUGINS[$type][$name];
}
//construct class and instantiate
if (!class_exists($class, true)) {
# the plugin might be in the wrong directory
$inf = confToHash(DOKU_PLUGIN . "$plugin/plugin.info.txt");
if ($inf['base'] && $inf['base'] != $plugin) {
msg(
sprintf(
"Plugin installed incorrectly. Rename plugin directory '%s' to '%s'.",
hsc($plugin),
hsc(
$inf['base']
)
), -1
);
} elseif (preg_match('/^' . DOKU_PLUGIN_NAME_REGEX . '$/', $plugin) !== 1) {
msg(
sprintf(
"Plugin name '%s' is not a valid plugin name, only the characters a-z and 0-9 are allowed. " .
'Maybe the plugin has been installed in the wrong directory?', hsc($plugin)
), -1
);
//construct class and instantiate
if (!class_exists($class, true)) {
# the plugin might be in the wrong directory
$inf = confToHash(DOKU_PLUGIN . "$plugin/plugin.info.txt");
if ($inf['base'] && $inf['base'] != $plugin) {
msg(
sprintf(
"Plugin installed incorrectly. Rename plugin directory '%s' to '%s'.",
hsc($plugin),
hsc(
$inf['base']
)
), -1
);
} elseif (preg_match('/^' . DOKU_PLUGIN_NAME_REGEX . '$/', $plugin) !== 1) {
msg(
sprintf(
"Plugin name '%s' is not a valid plugin name, only the characters a-z ".
"and 0-9 are allowed. " .
'Maybe the plugin has been installed in the wrong directory?', hsc($plugin)
), -1
);
}
return null;
}
$DOKU_PLUGINS[$type][$name] = new $class;
} catch (\Throwable $e) {
ErrorHandler::showExceptionMsg($e, sprintf('Failed to load plugin %s', $plugin));
return null;
}
$DOKU_PLUGINS[$type][$name] = new $class;
return $DOKU_PLUGINS[$type][$name];
}
@@ -265,7 +274,7 @@ class PluginController
$backup = $file . '.bak';
if (file_exists($backup)) @unlink($backup);
if (!@copy($file, $backup)) return false;
if (!empty($conf['fperm'])) chmod($backup, $conf['fperm']);
if ($conf['fperm']) chmod($backup, $conf['fperm']);
}
//check if can open for writing, else restore
return io_saveFile($file, $out);

View File

@@ -0,0 +1,166 @@
<?php
namespace dokuwiki\File;
use JpegMeta;
class MediaFile
{
protected $id;
protected $path;
protected $mime;
protected $ext;
protected $downloadable;
protected $width;
protected $height;
protected $meta;
/**
* MediaFile constructor.
* @param string $id
* @param string|int $rev optional revision
*/
public function __construct($id, $rev = '')
{
$this->id = $id; //FIXME should it be cleaned?
$this->path = mediaFN($id, $rev);
list($this->ext, $this->mime, $this->downloadable) = mimetype($this->path, false);
}
/** @return string */
public function getId()
{
return $this->id;
}
/** @return string */
public function getPath()
{
return $this->path;
}
/**
* The ID without namespace, used for display purposes
*
* @return string
*/
public function getDisplayName()
{
return noNS($this->id);
}
/** @return string */
public function getMime()
{
if (!$this->mime) return 'application/octet-stream';
return $this->mime;
}
/** @return string */
public function getExtension()
{
return (string)$this->ext;
}
/**
* Similar to the extesion but does some clean up
*
* @return string
*/
public function getIcoClass()
{
$ext = $this->getExtension();
if ($ext === '') $ext = 'file';
return preg_replace('/[^_\-a-z0-9]+/i', '_', $ext);
}
/**
* Should this file be downloaded instead being displayed inline?
*
* @return bool
*/
public function isDownloadable()
{
return $this->downloadable;
}
/** @return int */
public function getFileSize()
{
return filesize($this->path);
}
/** @return int */
public function getLastModified()
{
return filemtime($this->path);
}
/** @return bool */
public function isWritable()
{
return is_writable($this->path);
}
/** @return bool */
public function isImage()
{
return (substr($this->mime, 0, 6) === 'image/');
}
/**
* initializes width and height for images when requested
*/
protected function initSizes()
{
$this->width = 0;
$this->height = 0;
if (!$this->isImage()) return;
$info = getimagesize($this->path);
if ($info === false) return;
list($this->width, $this->height) = $info;
}
/**
* Returns the width if this is a supported image, 0 otherwise
*
* @return int
*/
public function getWidth()
{
if ($this->width === null) $this->initSizes();
return $this->width;
}
/**
* Returns the height if this is a supported image, 0 otherwise
*
* @return int
*/
public function getHeight()
{
if ($this->height === null) $this->initSizes();
return $this->height;
}
/**
* Returns the permissions the current user has on the file
*
* @todo doing this for each file within a namespace is a waste, we need to cache this somehow
* @return int
*/
public function userPermission()
{
return auth_quickaclcheck(getNS($this->id).':*');
}
/** @return JpegMeta */
public function getMeta()
{
if($this->meta === null) $this->meta = new JpegMeta($this->path);
return $this->meta;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace dokuwiki\File;
/**
* Creates an absolute media ID from a relative one
*/
class MediaResolver extends Resolver {
/** @inheritDoc */
public function resolveId($id, $rev = '', $isDateAt = false)
{
return cleanID(parent::resolveId($id, $rev, $isDateAt));
}
}

View File

@@ -0,0 +1,334 @@
<?php
namespace dokuwiki\File;
use dokuwiki\Cache\CacheInstructions;
use dokuwiki\ChangeLog\PageChangeLog;
use dokuwiki\Extension\Event;
use dokuwiki\Input\Input;
use dokuwiki\Logger;
use RuntimeException;
/**
* Class PageFile : handles wiki text file and its change management for specific page
*/
class PageFile
{
protected $id;
/* @var PageChangeLog $changelog */
public $changelog;
/* @var array $data initial data when event COMMON_WIKIPAGE_SAVE triggered */
protected $data;
/**
* PageFile constructor.
*
* @param string $id
*/
public function __construct($id)
{
$this->id = $id;
$this->changelog = new PageChangeLog($this->id);
}
/** @return string */
public function getId()
{
return $this->id;
}
/** @return string */
public function getPath($rev = '')
{
return wikiFN($this->id, $rev);
}
/**
* Get raw WikiText of the page, considering change type at revision date
* similar to function rawWiki($id, $rev = '')
*
* @param int|false $rev timestamp when a revision of wikitext is desired
* @return string
*/
public function rawWikiText($rev = null)
{
if ($rev !== null) {
$revInfo = $rev ? $this->changelog->getRevisionInfo($rev) : false;
return (!$revInfo || $revInfo['type'] == DOKU_CHANGE_TYPE_DELETE)
? '' // attic stores complete last page version for a deleted page
: io_readWikiPage($this->getPath($rev), $this->id, $rev); // retrieve from attic
} else {
return io_readWikiPage($this->getPath(), $this->id, '');
}
}
/**
* Saves a wikitext by calling io_writeWikiPage.
* Also directs changelog and attic updates.
*
* @author Andreas Gohr <andi@splitbrain.org>
* @author Ben Coburn <btcoburn@silicodon.net>
*
* @param string $text wikitext being saved
* @param string $summary summary of text update
* @param bool $minor mark this saved version as minor update
* @return array|void data of event COMMON_WIKIPAGE_SAVE
*/
public function saveWikiText($text, $summary, $minor = false)
{
/* Note to developers:
This code is subtle and delicate. Test the behavior of
the attic and changelog with dokuwiki and external edits
after any changes. External edits change the wiki page
directly without using php or dokuwiki.
*/
global $conf;
global $lang;
global $REV;
/* @var Input $INPUT */
global $INPUT;
// prevent recursive call
if (isset($this->data)) return;
$pagefile = $this->getPath();
$currentRevision = @filemtime($pagefile); // int or false
$currentContent = $this->rawWikiText();
$currentSize = file_exists($pagefile) ? filesize($pagefile) : 0;
// prepare data for event COMMON_WIKIPAGE_SAVE
$data = array(
'id' => $this->id, // should not be altered by any handlers
'file' => $pagefile, // same above
'changeType' => null, // set prior to event, and confirm later
'revertFrom' => $REV,
'oldRevision' => $currentRevision,
'oldContent' => $currentContent,
'newRevision' => 0, // only available in the after hook
'newContent' => $text,
'summary' => $summary,
'contentChanged' => ($text != $currentContent), // confirm later
'changeInfo' => '', // automatically determined by revertFrom
'sizechange' => strlen($text) - strlen($currentContent), // TBD
);
// determine tentatively change type and relevant elements of event data
if ($data['revertFrom']) {
// new text may differ from exact revert revision
$data['changeType'] = DOKU_CHANGE_TYPE_REVERT;
$data['changeInfo'] = $REV;
} elseif (trim($data['newContent']) == '') {
// empty or whitespace only content deletes
$data['changeType'] = DOKU_CHANGE_TYPE_DELETE;
} elseif (!file_exists($pagefile)) {
$data['changeType'] = DOKU_CHANGE_TYPE_CREATE;
} else {
// minor edits allowable only for logged in users
$is_minor_change = ($minor && $conf['useacl'] && $INPUT->server->str('REMOTE_USER'));
$data['changeType'] = $is_minor_change
? DOKU_CHANGE_TYPE_MINOR_EDIT
: DOKU_CHANGE_TYPE_EDIT;
}
$this->data = $data;
$data['page'] = $this; // allow event handlers to use this class methods
$event = new Event('COMMON_WIKIPAGE_SAVE', $data);
if (!$event->advise_before()) return;
// if the content has not been changed, no save happens (plugins may override this)
if (!$data['contentChanged']) return;
// Check whether the pagefile has modified during $event->advise_before()
clearstatcache();
$fileRev = @filemtime($pagefile);
if ($fileRev === $currentRevision) {
// pagefile has not touched by plugin's event handler
// add a potential external edit entry to changelog and store it into attic
$this->detectExternalEdit();
$filesize_old = $currentSize;
} else {
// pagefile has modified by plugin's event handler, confirm sizechange
$filesize_old = (
$data['changeType'] == DOKU_CHANGE_TYPE_CREATE || (
$data['changeType'] == DOKU_CHANGE_TYPE_REVERT && !file_exists($pagefile))
) ? 0 : filesize($pagefile);
}
// make change to the current file
if ($data['changeType'] == DOKU_CHANGE_TYPE_DELETE) {
// nothing to do when the file has already deleted
if (!file_exists($pagefile)) return;
// autoset summary on deletion
if (blank($data['summary'])) {
$data['summary'] = $lang['deleted'];
}
// send "update" event with empty data, so plugins can react to page deletion
$ioData = array([$pagefile, '', false], getNS($this->id), noNS($this->id), false);
Event::createAndTrigger('IO_WIKIPAGE_WRITE', $ioData);
// pre-save deleted revision
@touch($pagefile);
clearstatcache();
$data['newRevision'] = $this->saveOldRevision();
// remove empty file
@unlink($pagefile);
$filesize_new = 0;
// don't remove old meta info as it should be saved, plugins can use
// IO_WIKIPAGE_WRITE for removing their metadata...
// purge non-persistant meta data
p_purge_metadata($this->id);
// remove empty namespaces
io_sweepNS($this->id, 'datadir');
io_sweepNS($this->id, 'mediadir');
} else {
// save file (namespace dir is created in io_writeWikiPage)
io_writeWikiPage($pagefile, $data['newContent'], $this->id);
// pre-save the revision, to keep the attic in sync
$data['newRevision'] = $this->saveOldRevision();
$filesize_new = filesize($pagefile);
}
$data['sizechange'] = $filesize_new - $filesize_old;
$event->advise_after();
unset($data['page']);
// adds an entry to the changelog and saves the metadata for the page
$logEntry = $this->changelog->addLogEntry([
'date' => $data['newRevision'],
'ip' => clientIP(true),
'type' => $data['changeType'],
'id' => $this->id,
'user' => $INPUT->server->str('REMOTE_USER'),
'sum' => $data['summary'],
'extra' => $data['changeInfo'],
'sizechange' => $data['sizechange'],
]);
// update metadata
$this->updateMetadata($logEntry);
// update the purgefile (timestamp of the last time anything within the wiki was changed)
io_saveFile($conf['cachedir'].'/purgefile', time());
return $data;
}
/**
* Checks if the current page version is newer than the last entry in the page's changelog.
* If so, we assume it has been an external edit and we create an attic copy and add a proper
* changelog line.
*
* This check is only executed when the page is about to be saved again from the wiki,
* triggered in @see saveWikiText()
*/
public function detectExternalEdit()
{
$revInfo = $this->changelog->getCurrentRevisionInfo();
// only interested in external revision
if (empty($revInfo) || !array_key_exists('timestamp', $revInfo)) return;
if ($revInfo['type'] != DOKU_CHANGE_TYPE_DELETE && !$revInfo['timestamp']) {
// file is older than last revision, that is erroneous/incorrect occurence.
// try to change file modification time
$fileLastMod = $this->getPath();
$wrong_timestamp = filemtime($fileLastMod);
if (touch($fileLastMod, $revInfo['date'])) {
clearstatcache();
$msg = "PageFile($this->id)::detectExternalEdit(): timestamp successfully modified";
$details = '('.$wrong_timestamp.' -> '.$revInfo['date'].')';
Logger::error($msg, $details, $fileLastMod);
} else {
// runtime error
$msg = "PageFile($this->id)::detectExternalEdit(): page file should be newer than last revision "
.'('.filemtime($fileLastMod).' < '. $this->changelog->lastRevision() .')';
throw new RuntimeException($msg);
}
}
// keep at least 1 sec before new page save
if ($revInfo['date'] == time()) sleep(1); // wait a tick
// store externally edited file to the attic folder
$this->saveOldRevision();
// add a changelog entry for externally edited file
$this->changelog->addLogEntry($revInfo);
// remove soon to be stale instructions
$cache = new CacheInstructions($this->id, $this->getPath());
$cache->removeCache();
}
/**
* Moves the current version to the attic and returns its revision date
*
* @author Andreas Gohr <andi@splitbrain.org>
*
* @return int|string revision timestamp
*/
public function saveOldRevision()
{
$oldfile = $this->getPath();
if (!file_exists($oldfile)) return '';
$date = filemtime($oldfile);
$newfile = $this->getPath($date);
io_writeWikiPage($newfile, $this->rawWikiText(), $this->id, $date);
return $date;
}
/**
* Update metadata of changed page
*
* @param array $logEntry changelog entry
*/
public function updateMetadata(array $logEntry)
{
global $INFO;
list(
'date' => $date,
'type' => $changeType,
'user' => $user,
) = $logEntry;
$wasRemoved = ($changeType === DOKU_CHANGE_TYPE_DELETE);
$wasCreated = ($changeType === DOKU_CHANGE_TYPE_CREATE);
$wasReverted = ($changeType === DOKU_CHANGE_TYPE_REVERT);
$wasMinorEdit = ($changeType === DOKU_CHANGE_TYPE_MINOR_EDIT);
$createdDate = @filectime($this->getPath());
if ($wasRemoved) return;
$oldmeta = p_read_metadata($this->id)['persistent'];
$meta = array();
if ($wasCreated &&
(empty($oldmeta['date']['created']) || $oldmeta['date']['created'] === $createdDate)
) {
// newly created
$meta['date']['created'] = $createdDate;
if ($user) {
$meta['creator'] = $INFO['userinfo']['name'] ?? null;
$meta['user'] = $user;
}
} elseif (($wasCreated || $wasReverted) && !empty($oldmeta['date']['created'])) {
// re-created / restored
$meta['date']['created'] = $oldmeta['date']['created'];
$meta['date']['modified'] = $createdDate; // use the files ctime here
$meta['creator'] = $oldmeta['creator'] ?? null;
if ($user) {
$meta['contributor'][$user] = $INFO['userinfo']['name'] ?? null;
}
} elseif (!$wasMinorEdit) { // non-minor modification
$meta['date']['modified'] = $date;
if ($user) {
$meta['contributor'][$user] = $INFO['userinfo']['name'] ?? null;
}
}
$meta['last_change'] = $logEntry;
p_set_metadata($this->id, $meta);
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace dokuwiki\File;
/**
* Creates an absolute page ID from a relative one
*/
class PageResolver extends Resolver
{
/**
* Resolves a given ID to be absolute
*
* This handles all kinds of relative shortcuts, startpages and autoplurals
* @inheritDoc
*/
public function resolveId($id, $rev = '', $isDateAt = false)
{
global $conf;
// pages may have a hash attached, we separate it on resolving
if (strpos($id, '#') !== false) {
list($id, $hash) = explode('#', $id, 2);
$hash = cleanID($hash);
} else {
$hash = '';
}
if ($id !== '') {
$id = parent::resolveId($id, $rev, $isDateAt);
$id = $this->resolveStartPage($id, $rev, $isDateAt);
if ($conf['autoplural']) {
$id = $this->resolveAutoPlural($id, $rev, $isDateAt);
}
} else {
$id = $this->contextID;
}
$id = cleanID($id); // FIXME always? or support parameter
// readd hash if any
if ($hash !== '') $id .= "#$hash";
return $id;
}
/**
* IDs ending in :
*
* @param string $id
* @param string|int|false $rev
* @param bool $isDateAt
* @return string
*/
protected function resolveStartPage($id, $rev, $isDateAt)
{
global $conf;
if ($id[-1] !== ':') return $id;
if (page_exists($id . $conf['start'], $rev, true, $isDateAt)) {
// start page inside namespace
return $id . $conf['start'];
} elseif (page_exists($id . noNS(cleanID($id)), $rev, true, $isDateAt)) {
// page named like the NS inside the NS
return $id . noNS(cleanID($id));
} elseif (page_exists(substr($id, 0, -1), $rev, true, $isDateAt)) {
// page named like the NS outside the NS
return substr($id, 0, -1);
}
// fall back to default start page
return $id . $conf['start'];
}
/**
* Try alternative plural/singular form
*
* @param string $id
* @param int $rev
* @param bool $isDateAt
* @return string
*/
protected function resolveAutoPlural($id, $rev, $isDateAt)
{
if (page_exists($id, $rev, $isDateAt)) return $id;
if ($id[-1] === 's') {
$try = substr($id, 0, -1);
} else {
$try = $id . 's';
}
if (page_exists($try, $rev, true, $isDateAt)) {
return $try;
}
return $id;
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace dokuwiki\File;
/**
* Resolving relative IDs to absolute ones
*/
abstract class Resolver
{
/** @var string context page ID */
protected $contextID;
/** @var string namespace of context page ID */
protected $contextNS;
/**
* @param string $contextID the current pageID that's the context to resolve relative IDs to
*/
public function __construct($contextID)
{
$this->contextID = $contextID;
$this->contextNS = (string)getNS($contextID);
}
/**
* Resolves a given ID to be absolute
*
* @param string $id The ID to resolve
* @param string|int|false $rev The revision time to use when resolving
* @param bool $isDateAt Is the given revision only a datetime hint not an exact revision?
* @return string
*/
public function resolveId($id, $rev = '', $isDateAt = false)
{
global $conf;
// some pre cleaning for useslash:
if ($conf['useslash']) $id = str_replace('/', ':', $id);
// on some systems, semicolons might be used instead of colons:
$id = str_replace(';', ':', $id);
$id = $this->resolvePrefix($id);
return $this->resolveRelatives($id);
}
/**
* Handle IDs starting with . or ~ and prepend the proper prefix
*
* @param string $id
* @return string
*/
protected function resolvePrefix($id)
{
// relative to current page (makes the current page a start page)
if ($id[0] === '~') {
$id = $this->contextID . ':' . substr($id, 1);
}
// relative to current namespace
if ($id[0] === '.') {
// normalize initial dots without a colon
$id = preg_replace('/^((\.+:)*)(\.+)(?=[^:\.])/', '\1\3:', $id);
$id = $this->contextNS . ':' . $id;
}
// auto-relative, because there is a context namespace but no namespace in the ID
if ($this->contextID !== '' && strpos($id, ':') === false) {
$id = $this->contextNS . ':' . $id;
}
return $id;
}
/**
* Handle . and .. within IDs
*
* @param string $id
* @return string
*/
protected function resolveRelatives($id)
{
if ($id === '') return '';
$trail = ($id[-1] === ':') ? ':' : ''; // keep trailing colon
$result = [];
$parts = explode(':', $id);
foreach ($parts as $dir) {
if ($dir === '.') continue;
if ($dir === '') continue;
if ($dir === '..') {
array_pop($result);
continue;
}
array_push($result, $dir);
}
$id = implode(':', $result);
$id .= $trail;
return $id;
}
}

View File

@@ -1,4 +1,5 @@
<?php
namespace dokuwiki\Form;
/**
@@ -8,8 +9,8 @@ namespace dokuwiki\Form;
*
* @package dokuwiki\Form
*/
class ButtonElement extends Element {
class ButtonElement extends Element
{
/** @var string HTML content */
protected $content = '';
@@ -17,7 +18,8 @@ class ButtonElement extends Element {
* @param string $name
* @param string $content HTML content of the button. You have to escape it yourself.
*/
public function __construct($name, $content = '') {
public function __construct($name, $content = '')
{
parent::__construct('button', array('name' => $name, 'value' => 1));
$this->content = $content;
}
@@ -27,7 +29,8 @@ class ButtonElement extends Element {
*
* @return string
*/
public function toHTML() {
public function toHTML()
{
return '<button ' . buildAttributes($this->attrs(), true) . '>'.$this->content.'</button>';
}

View File

@@ -1,4 +1,5 @@
<?php
namespace dokuwiki\Form;
/**
@@ -8,14 +9,15 @@ namespace dokuwiki\Form;
*
* @package dokuwiki\Form
*/
class CheckableElement extends InputElement {
class CheckableElement extends InputElement
{
/**
* @param string $type The type of this element
* @param string $name The name of this form element
* @param string $label The label text for this element
*/
public function __construct($type, $name, $label) {
public function __construct($type, $name, $label)
{
parent::__construct($type, $name, $label);
// default value is 1
$this->attr('value', 1);
@@ -24,17 +26,18 @@ class CheckableElement extends InputElement {
/**
* Handles the useInput flag and sets the checked attribute accordingly
*/
protected function prefillInput() {
protected function prefillInput()
{
global $INPUT;
list($name, $key) = $this->getInputName();
$myvalue = $this->val();
if(!$INPUT->has($name)) return;
if (!$INPUT->has($name)) return;
if($key === null) {
if ($key === null) {
// no key - single value
$value = $INPUT->str($name);
if($value == $myvalue) {
if ($value == $myvalue) {
$this->attr('checked', 'checked');
} else {
$this->rmattr('checked');
@@ -42,16 +45,16 @@ class CheckableElement extends InputElement {
} else {
// we have an array, there might be several values in it
$input = $INPUT->arr($name);
if(isset($input[$key])) {
if (isset($input[$key])) {
$this->rmattr('checked');
// values seem to be in another sub array
if(is_array($input[$key])) {
if (is_array($input[$key])) {
$input = $input[$key];
}
foreach($input as $value) {
if($value == $myvalue) {
foreach ($input as $value) {
if ($value == $myvalue) {
$this->attr('checked', 'checked');
}
}
@@ -59,4 +62,22 @@ class CheckableElement extends InputElement {
}
}
/**
* The HTML representation of this element wrapped in a label
* Note: allow HTML tags in label text
*
* @return string
*/
public function toHTML()
{
if ($this->label) {
return '<label '. buildAttributes($this->label->attrs()) .'>'.DOKU_LF
. $this->mainElementHTML() .DOKU_LF
.'<span>'. $this->label->val() .'</span>'.DOKU_LF
.'</label>';
} else {
return $this->mainElementHTML();
}
}
}

View File

@@ -1,4 +1,5 @@
<?php
namespace dokuwiki\Form;
/**
@@ -8,8 +9,8 @@ namespace dokuwiki\Form;
*
* @package dokuwiki\Form
*/
class DropdownElement extends InputElement {
class DropdownElement extends InputElement
{
/** @var array OptGroup[] */
protected $optGroups = array();
@@ -18,7 +19,8 @@ class DropdownElement extends InputElement {
* @param array $options The available options
* @param string $label The label text for this element (will be autoescaped)
*/
public function __construct($name, $options, $label = '') {
public function __construct($name, $options, $label = '')
{
parent::__construct('dropdown', $name, $label);
$this->rmattr('type');
$this->optGroups[''] = new OptGroup(null, $options);
@@ -33,7 +35,8 @@ class DropdownElement extends InputElement {
* @return OptGroup a reference to the added optgroup
* @throws \Exception
*/
public function addOptGroup($label, $options) {
public function addOptGroup($label, $options)
{
if (empty($label)) {
throw new \InvalidArgumentException(hsc('<optgroup> must have a label!'));
}
@@ -51,8 +54,9 @@ class DropdownElement extends InputElement {
* @param null|array $optGroups
* @return OptGroup[]|DropdownElement
*/
public function optGroups($optGroups = null) {
if($optGroups === null) {
public function optGroups($optGroups = null)
{
if ($optGroups === null) {
return $this->optGroups;
}
if (!is_array($optGroups)) {
@@ -81,7 +85,8 @@ class DropdownElement extends InputElement {
* @param null|array $options
* @return $this|array
*/
public function options($options = null) {
public function options($options = null)
{
if ($options === null) {
return $this->optGroups['']->options();
}
@@ -102,8 +107,9 @@ class DropdownElement extends InputElement {
* @param null|string $value New value to set
* @return string|$this
*/
public function attr($name, $value = null) {
if(strtolower($name) == 'multiple') {
public function attr($name, $value = null)
{
if (strtolower($name) == 'multiple') {
throw new \InvalidArgumentException(
'Sorry, the dropdown element does not support the "multiple" attribute'
);
@@ -120,12 +126,13 @@ class DropdownElement extends InputElement {
* @param null|string $value The value to set
* @return $this|string
*/
public function val($value = null) {
if($value === null) return $this->value;
public function val($value = null)
{
if ($value === null) return $this->value;
$value_exists = $this->setValueInOptGroups($value);
if($value_exists) {
if ($value_exists) {
$this->value = $value;
} else {
// unknown value set, select first option instead
@@ -141,7 +148,8 @@ class DropdownElement extends InputElement {
*
* @return string
*/
protected function getFirstOption() {
protected function getFirstOption()
{
$options = $this->options();
if (!empty($options)) {
$keys = array_keys($options);
@@ -162,7 +170,8 @@ class DropdownElement extends InputElement {
* @param string $value
* @return bool
*/
protected function setValueInOptGroups($value) {
protected function setValueInOptGroups($value)
{
$value_exists = false;
/** @var OptGroup $optGroup */
foreach ($this->optGroups as $optGroup) {
@@ -179,8 +188,9 @@ class DropdownElement extends InputElement {
*
* @return string
*/
protected function mainElementHTML() {
if($this->useInput) $this->prefillInput();
protected function mainElementHTML()
{
if ($this->useInput) $this->prefillInput();
$html = '<select ' . buildAttributes($this->attrs()) . '>';
$html = array_reduce(

View File

@@ -1,4 +1,5 @@
<?php
namespace dokuwiki\Form;
/**
@@ -8,8 +9,8 @@ namespace dokuwiki\Form;
*
* @package dokuwiki\Form
*/
abstract class Element {
abstract class Element
{
/**
* @var array the attributes of this element
*/
@@ -24,7 +25,8 @@ abstract class Element {
* @param string $type The type of this element
* @param array $attributes
*/
public function __construct($type, $attributes = array()) {
public function __construct($type, $attributes = array())
{
$this->type = $type;
$this->attributes = $attributes;
}
@@ -34,7 +36,8 @@ abstract class Element {
*
* @return string
*/
public function getType() {
public function getType()
{
return $this->type;
}
@@ -51,15 +54,16 @@ abstract class Element {
* @param null|string $value New value to set
* @return string|$this
*/
public function attr($name, $value = null) {
public function attr($name, $value = null)
{
// set
if($value !== null) {
if ($value !== null) {
$this->attributes[$name] = $value;
return $this;
}
// get
if(isset($this->attributes[$name])) {
if (isset($this->attributes[$name])) {
return $this->attributes[$name];
} else {
return '';
@@ -72,8 +76,9 @@ abstract class Element {
* @param string $name
* @return $this
*/
public function rmattr($name) {
if(isset($this->attributes[$name])) {
public function rmattr($name)
{
if (isset($this->attributes[$name])) {
unset($this->attributes[$name]);
}
return $this;
@@ -85,10 +90,11 @@ abstract class Element {
* @param array|null $attributes
* @return array|$this
*/
public function attrs($attributes = null) {
public function attrs($attributes = null)
{
// set
if($attributes) {
foreach((array) $attributes as $key => $val) {
if ($attributes) {
foreach ((array) $attributes as $key => $val) {
$this->attr($key, $val);
}
return $this;
@@ -105,7 +111,8 @@ abstract class Element {
* @param string $class the new class to add
* @return $this
*/
public function addClass($class) {
public function addClass($class)
{
$classes = explode(' ', $this->attr('class'));
$classes[] = $class;
$classes = array_unique($classes);
@@ -122,8 +129,9 @@ abstract class Element {
* @param null|string $id
* @return string|$this
*/
public function id($id = null) {
if(strpos($id, '__') === false) {
public function id($id = null)
{
if (strpos($id, '__') === false) {
throw new \InvalidArgumentException('IDs in DokuWiki have to contain two subsequent underscores');
}
@@ -138,7 +146,8 @@ abstract class Element {
* @param null|string $value
* @return string|$this
*/
public function val($value = null) {
public function val($value = null)
{
return $this->attr('value', $value);
}

View File

@@ -1,4 +1,5 @@
<?php
namespace dokuwiki\Form;
/**
@@ -8,12 +9,13 @@ namespace dokuwiki\Form;
*
* @package dokuwiki\Form
*/
class FieldsetCloseElement extends TagCloseElement {
class FieldsetCloseElement extends TagCloseElement
{
/**
* @param array $attributes
*/
public function __construct($attributes = array()) {
public function __construct($attributes = array())
{
parent::__construct('', $attributes);
$this->type = 'fieldsetclose';
}
@@ -24,7 +26,8 @@ class FieldsetCloseElement extends TagCloseElement {
*
* @return string
*/
public function toHTML() {
public function toHTML()
{
return '</fieldset>';
}
}

View File

@@ -1,4 +1,5 @@
<?php
namespace dokuwiki\Form;
/**
@@ -8,13 +9,15 @@ namespace dokuwiki\Form;
*
* @package dokuwiki\Form
*/
class FieldsetOpenElement extends TagOpenElement {
class FieldsetOpenElement extends TagOpenElement
{
/**
* @param string $legend
* @param array $attributes
*/
public function __construct($legend='', $attributes = array()) {
public function __construct($legend='', $attributes = array())
{
// this is a bit messy and we just do it for the nicer class hierarchy
// the parent would expect the tag in $value but we're storing the
// legend there, so we have to set the type manually
@@ -27,10 +30,11 @@ class FieldsetOpenElement extends TagOpenElement {
*
* @return string
*/
public function toHTML() {
public function toHTML()
{
$html = '<fieldset '.buildAttributes($this->attrs()).'>';
$legend = $this->val();
if($legend) $html .= DOKU_LF.'<legend>'.hsc($legend).'</legend>';
if ($legend) $html .= DOKU_LF.'<legend>'.hsc($legend).'</legend>';
return $html;
}
}

View File

@@ -1,6 +1,9 @@
<?php
namespace dokuwiki\Form;
use dokuwiki\Extension\Event;
/**
* Class Form
*
@@ -8,8 +11,8 @@ namespace dokuwiki\Form;
*
* @package dokuwiki\Form
*/
class Form extends Element {
class Form extends Element
{
/**
* @var array name value pairs for hidden values
*/
@@ -26,26 +29,27 @@ class Form extends Element {
* @param array $attributes
* @param bool $unsafe if true, then the security token is ommited
*/
public function __construct($attributes = array(), $unsafe = false) {
public function __construct($attributes = array(), $unsafe = false)
{
global $ID;
parent::__construct('form', $attributes);
// use the current URL as default action
if(!$this->attr('action')) {
if (!$this->attr('action')) {
$get = $_GET;
if(isset($get['id'])) unset($get['id']);
if (isset($get['id'])) unset($get['id']);
$self = wl($ID, $get, false, '&'); //attributes are escaped later
$this->attr('action', $self);
}
// post is default
if(!$this->attr('method')) {
if (!$this->attr('method')) {
$this->attr('method', 'post');
}
// we like UTF-8
if(!$this->attr('accept-charset')) {
if (!$this->attr('accept-charset')) {
$this->attr('accept-charset', 'utf-8');
}
@@ -65,7 +69,8 @@ class Form extends Element {
* @param string $value
* @return $this
*/
public function setHiddenField($name, $value) {
public function setHiddenField($name, $value)
{
$this->hidden[$name] = $value;
return $this;
}
@@ -77,7 +82,8 @@ class Form extends Element {
*
* @return int
*/
public function elementCount() {
public function elementCount()
{
return count($this->elements);
}
@@ -105,10 +111,11 @@ class Form extends Element {
* @param int $pos
* @return Element
*/
public function getElementAt($pos) {
if($pos < 0) $pos = count($this->elements) + $pos;
if($pos < 0) $pos = 0;
if($pos >= count($this->elements)) $pos = count($this->elements) - 1;
public function getElementAt($pos)
{
if ($pos < 0) $pos = count($this->elements) + $pos;
if ($pos < 0) $pos = 0;
if ($pos >= count($this->elements)) $pos = count($this->elements) - 1;
return $this->elements[$pos];
}
@@ -119,10 +126,11 @@ class Form extends Element {
* @param int $offset search from this position onward
* @return false|int position of element if found, otherwise false
*/
public function findPositionByType($type, $offset = 0) {
public function findPositionByType($type, $offset = 0)
{
$len = $this->elementCount();
for($pos = $offset; $pos < $len; $pos++) {
if($this->elements[$pos]->getType() == $type) {
for ($pos = $offset; $pos < $len; $pos++) {
if ($this->elements[$pos]->getType() == $type) {
return $pos;
}
}
@@ -137,10 +145,11 @@ class Form extends Element {
* @param int $offset search from this position onward
* @return false|int position of element if found, otherwise false
*/
public function findPositionByAttribute($name, $value, $offset = 0) {
public function findPositionByAttribute($name, $value, $offset = 0)
{
$len = $this->elementCount();
for($pos = $offset; $pos < $len; $pos++) {
if($this->elements[$pos]->attr($name) == $value) {
for ($pos = $offset; $pos < $len; $pos++) {
if ($this->elements[$pos]->attr($name) == $value) {
return $pos;
}
}
@@ -158,11 +167,12 @@ class Form extends Element {
* @param int $pos 0-based position in the form, -1 for at the end
* @return Element
*/
public function addElement(Element $element, $pos = -1) {
if(is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
public function addElement(Element $element, $pos = -1)
{
if (is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
'You can\'t add a form to a form'
);
if($pos < 0) {
if ($pos < 0) {
$this->elements[] = $element;
} else {
array_splice($this->elements, $pos, 0, array($element));
@@ -176,8 +186,9 @@ class Form extends Element {
* @param Element $element the new element
* @param int $pos 0-based position of the element to replace
*/
public function replaceElement(Element $element, $pos) {
if(is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
public function replaceElement(Element $element, $pos)
{
if (is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
'You can\'t add a form to a form'
);
array_splice($this->elements, $pos, 1, array($element));
@@ -188,7 +199,8 @@ class Form extends Element {
*
* @param int $pos 0-based position of the element to remove
*/
public function removeElement($pos) {
public function removeElement($pos)
{
array_splice($this->elements, $pos, 1);
}
@@ -204,7 +216,8 @@ class Form extends Element {
* @param int $pos
* @return InputElement
*/
public function addTextInput($name, $label = '', $pos = -1) {
public function addTextInput($name, $label = '', $pos = -1)
{
return $this->addElement(new InputElement('text', $name, $label), $pos);
}
@@ -216,7 +229,8 @@ class Form extends Element {
* @param int $pos
* @return InputElement
*/
public function addPasswordInput($name, $label = '', $pos = -1) {
public function addPasswordInput($name, $label = '', $pos = -1)
{
return $this->addElement(new InputElement('password', $name, $label), $pos);
}
@@ -228,7 +242,8 @@ class Form extends Element {
* @param int $pos
* @return CheckableElement
*/
public function addRadioButton($name, $label = '', $pos = -1) {
public function addRadioButton($name, $label = '', $pos = -1)
{
return $this->addElement(new CheckableElement('radio', $name, $label), $pos);
}
@@ -240,7 +255,8 @@ class Form extends Element {
* @param int $pos
* @return CheckableElement
*/
public function addCheckbox($name, $label = '', $pos = -1) {
public function addCheckbox($name, $label = '', $pos = -1)
{
return $this->addElement(new CheckableElement('checkbox', $name, $label), $pos);
}
@@ -253,7 +269,8 @@ class Form extends Element {
* @param int $pos
* @return DropdownElement
*/
public function addDropdown($name, $options, $label = '', $pos = -1) {
public function addDropdown($name, $options, $label = '', $pos = -1)
{
return $this->addElement(new DropdownElement($name, $options, $label), $pos);
}
@@ -265,7 +282,8 @@ class Form extends Element {
* @param int $pos
* @return TextareaElement
*/
public function addTextarea($name, $label = '', $pos = -1) {
public function addTextarea($name, $label = '', $pos = -1)
{
return $this->addElement(new TextareaElement($name, $label), $pos);
}
@@ -277,7 +295,8 @@ class Form extends Element {
* @param int $pos
* @return Element
*/
public function addButton($name, $content, $pos = -1) {
public function addButton($name, $content, $pos = -1)
{
return $this->addElement(new ButtonElement($name, hsc($content)), $pos);
}
@@ -289,7 +308,8 @@ class Form extends Element {
* @param int $pos
* @return Element
*/
public function addButtonHTML($name, $html, $pos = -1) {
public function addButtonHTML($name, $html, $pos = -1)
{
return $this->addElement(new ButtonElement($name, $html), $pos);
}
@@ -301,7 +321,8 @@ class Form extends Element {
* @param int $pos
* @return Element
*/
public function addLabel($label, $for='', $pos = -1) {
public function addLabel($label, $for='', $pos = -1)
{
return $this->addLabelHTML(hsc($label), $for, $pos);
}
@@ -313,15 +334,16 @@ class Form extends Element {
* @param int $pos
* @return Element
*/
public function addLabelHTML($content, $for='', $pos = -1) {
public function addLabelHTML($content, $for='', $pos = -1)
{
$element = new LabelElement(hsc($content));
if(is_a($for, '\dokuwiki\Form\Element')) {
if (is_a($for, '\dokuwiki\Form\Element')) {
/** @var Element $for */
$for = $for->id();
}
$for = (string) $for;
if($for !== '') {
if ($for !== '') {
$element->attr('for', $for);
}
@@ -335,7 +357,8 @@ class Form extends Element {
* @param int $pos
* @return HTMLElement
*/
public function addHTML($html, $pos = -1) {
public function addHTML($html, $pos = -1)
{
return $this->addElement(new HTMLElement($html), $pos);
}
@@ -346,7 +369,8 @@ class Form extends Element {
* @param int $pos
* @return TagElement
*/
public function addTag($tag, $pos = -1) {
public function addTag($tag, $pos = -1)
{
return $this->addElement(new TagElement($tag), $pos);
}
@@ -359,7 +383,8 @@ class Form extends Element {
* @param int $pos
* @return TagOpenElement
*/
public function addTagOpen($tag, $pos = -1) {
public function addTagOpen($tag, $pos = -1)
{
return $this->addElement(new TagOpenElement($tag), $pos);
}
@@ -372,7 +397,8 @@ class Form extends Element {
* @param int $pos
* @return TagCloseElement
*/
public function addTagClose($tag, $pos = -1) {
public function addTagClose($tag, $pos = -1)
{
return $this->addElement(new TagCloseElement($tag), $pos);
}
@@ -383,7 +409,8 @@ class Form extends Element {
* @param int $pos
* @return FieldsetOpenElement
*/
public function addFieldsetOpen($legend = '', $pos = -1) {
public function addFieldsetOpen($legend = '', $pos = -1)
{
return $this->addElement(new FieldsetOpenElement($legend), $pos);
}
@@ -393,7 +420,8 @@ class Form extends Element {
* @param int $pos
* @return TagCloseElement
*/
public function addFieldsetClose($pos = -1) {
public function addFieldsetClose($pos = -1)
{
return $this->addElement(new FieldsetCloseElement(), $pos);
}
@@ -402,15 +430,16 @@ class Form extends Element {
/**
* Adjust the elements so that fieldset open and closes are matching
*/
protected function balanceFieldsets() {
protected function balanceFieldsets()
{
$lastclose = 0;
$isopen = false;
$len = count($this->elements);
for($pos = 0; $pos < $len; $pos++) {
for ($pos = 0; $pos < $len; $pos++) {
$type = $this->elements[$pos]->getType();
if($type == 'fieldsetopen') {
if($isopen) {
if ($type == 'fieldsetopen') {
if ($isopen) {
//close previous fieldset
$this->addFieldsetClose($pos);
$lastclose = $pos + 1;
@@ -418,8 +447,8 @@ class Form extends Element {
$len++;
}
$isopen = true;
} else if($type == 'fieldsetclose') {
if(!$isopen) {
} elseif ($type == 'fieldsetclose') {
if (!$isopen) {
// make sure there was a fieldsetopen
// either right after the last close or at the begining
$this->addFieldsetOpen('', $lastclose);
@@ -432,7 +461,7 @@ class Form extends Element {
}
// close open fieldset at the end
if($isopen) {
if ($isopen) {
$this->addFieldsetClose();
}
}
@@ -440,18 +469,26 @@ class Form extends Element {
/**
* The HTML representation of the whole form
*
* @param string $eventName (optional) name of the event: FORM_{$name}_OUTPUT
* @return string
*/
public function toHTML() {
public function toHTML($eventName = null)
{
$this->balanceFieldsets();
$html = '<form ' . buildAttributes($this->attrs()) . '>';
foreach($this->hidden as $name => $value) {
$html .= '<input type="hidden" name="' . $name . '" value="' . formText($value) . '" />';
// trigger event to provide an opportunity to modify this form
if (isset($eventName)) {
$eventName = 'FORM_'.strtoupper($eventName).'_OUTPUT';
Event::createAndTrigger($eventName, $this, null, false);
}
foreach($this->elements as $element) {
$html = '<form '. buildAttributes($this->attrs()) .'>';
foreach ($this->hidden as $name => $value) {
$html .= '<input type="hidden" name="'. $name .'" value="'. formText($value) .'" />';
}
foreach ($this->elements as $element) {
$html .= $element->toHTML();
}

View File

@@ -1,4 +1,5 @@
<?php
namespace dokuwiki\Form;
/**
@@ -8,13 +9,13 @@ namespace dokuwiki\Form;
*
* @package dokuwiki\Form
*/
class HTMLElement extends ValueElement {
class HTMLElement extends ValueElement
{
/**
* @param string $html
*/
public function __construct($html) {
public function __construct($html)
{
parent::__construct('html', $html);
}
@@ -23,7 +24,8 @@ class HTMLElement extends ValueElement {
*
* @return string
*/
public function toHTML() {
public function toHTML()
{
return $this->val();
}
}

View File

@@ -1,4 +1,5 @@
<?php
namespace dokuwiki\Form;
/**
@@ -10,7 +11,8 @@ namespace dokuwiki\Form;
* @todo figure out how to make wrapping or related label configurable
* @package dokuwiki\Form
*/
class InputElement extends Element {
class InputElement extends Element
{
/**
* @var LabelElement
*/
@@ -26,11 +28,12 @@ class InputElement extends Element {
* @param string $name The name of this form element
* @param string $label The label text for this element (will be autoescaped)
*/
public function __construct($type, $name, $label = '') {
public function __construct($type, $name, $label = '')
{
parent::__construct($type, array('name' => $name));
$this->attr('name', $name);
$this->attr('type', $type);
if($label) $this->label = new LabelElement($label);
if ($label) $this->label = new LabelElement($label);
}
/**
@@ -38,7 +41,8 @@ class InputElement extends Element {
*
* @return LabelElement|null
*/
public function getLabel() {
public function getLabel()
{
return $this->label;
}
@@ -51,7 +55,8 @@ class InputElement extends Element {
* @param bool $useinput
* @return $this
*/
public function useInput($useinput) {
public function useInput($useinput)
{
$this->useInput = (bool) $useinput;
return $this;
}
@@ -62,8 +67,9 @@ class InputElement extends Element {
* @param null|string $id
* @return string|$this
*/
public function id($id = null) {
if($this->label) $this->label->attr('for', $id);
public function id($id = null)
{
if ($this->label) $this->label->attr('for', $id);
return parent::id($id);
}
@@ -75,8 +81,9 @@ class InputElement extends Element {
* @param string $class the new class to add
* @return $this
*/
public function addClass($class) {
if($this->label) $this->label->addClass($class);
public function addClass($class)
{
if ($this->label) $this->label->addClass($class);
return parent::addClass($class);
}
@@ -92,14 +99,15 @@ class InputElement extends Element {
*
* @return array name and array key (null if not an array)
*/
protected function getInputName() {
protected function getInputName()
{
$name = $this->attr('name');
parse_str("$name=1", $parsed);
$name = array_keys($parsed);
$name = array_shift($name);
if(is_array($parsed[$name])) {
if (isset($parsed[$name]) && is_array($parsed[$name])) {
$key = array_keys($parsed[$name]);
$key = array_shift($key);
} else {
@@ -112,17 +120,18 @@ class InputElement extends Element {
/**
* Handles the useInput flag and set the value attribute accordingly
*/
protected function prefillInput() {
protected function prefillInput()
{
global $INPUT;
list($name, $key) = $this->getInputName();
if(!$INPUT->has($name)) return;
if (!$INPUT->has($name)) return;
if($key === null) {
if ($key === null) {
$value = $INPUT->str($name);
} else {
$value = $INPUT->arr($name);
if(isset($value[$key])) {
if (isset($value[$key])) {
$value = $value[$key];
} else {
$value = '';
@@ -136,9 +145,10 @@ class InputElement extends Element {
*
* @return string
*/
protected function mainElementHTML() {
if($this->useInput) $this->prefillInput();
return '<input ' . buildAttributes($this->attrs()) . ' />';
protected function mainElementHTML()
{
if ($this->useInput) $this->prefillInput();
return '<input '. buildAttributes($this->attrs()) .' />';
}
/**
@@ -146,12 +156,13 @@ class InputElement extends Element {
*
* @return string
*/
public function toHTML() {
if($this->label) {
return '<label ' . buildAttributes($this->label->attrs()) . '>' . DOKU_LF .
'<span>' . hsc($this->label->val()) . '</span>' . DOKU_LF .
$this->mainElementHTML() . DOKU_LF .
'</label>';
public function toHTML()
{
if ($this->label) {
return '<label '. buildAttributes($this->label->attrs()) .'>'.DOKU_LF
.'<span>'. hsc($this->label->val()) .'</span>'.DOKU_LF
. $this->mainElementHTML() .DOKU_LF
.'</label>';
} else {
return $this->mainElementHTML();
}

View File

@@ -1,18 +1,20 @@
<?php
namespace dokuwiki\Form;
/**
* Class Label
* @package dokuwiki\Form
*/
class LabelElement extends ValueElement {
class LabelElement extends ValueElement
{
/**
* Creates a new Label
*
* @param string $label This is is raw HTML and will not be escaped
*/
public function __construct($label) {
public function __construct($label)
{
parent::__construct('label', $label);
}
@@ -21,7 +23,8 @@ class LabelElement extends ValueElement {
*
* @return string
*/
public function toHTML() {
public function toHTML()
{
return '<label ' . buildAttributes($this->attrs()) . '>' . $this->val() . '</label>';
}
}

View File

@@ -1,4 +1,5 @@
<?php
namespace dokuwiki\Form;
/**
@@ -11,23 +12,24 @@ namespace dokuwiki\Form;
*
* @package dokuwiki\Form
*/
class LegacyForm extends Form {
class LegacyForm extends Form
{
/**
* Creates a new modern form from an old legacy Doku_Form
*
* @param \Doku_Form $oldform
*/
public function __construct(\Doku_Form $oldform) {
public function __construct(\Doku_Form $oldform)
{
parent::__construct($oldform->params);
$this->hidden = $oldform->_hidden;
foreach($oldform->_content as $element) {
foreach ($oldform->_content as $element) {
list($ctl, $attr) = $this->parseLegacyAttr($element);
if(is_array($element)) {
switch($ctl['elem']) {
if (is_array($element)) {
switch ($ctl['elem']) {
case 'wikitext':
$this->addTextarea('wikitext')
->attrs($attr)
@@ -111,12 +113,13 @@ class LegacyForm extends Form {
* @param array $legacy
* @return array
*/
protected function parseLegacyAttr($legacy) {
protected function parseLegacyAttr($legacy)
{
$attributes = array();
$control = array();
foreach($legacy as $key => $val) {
if($key[0] == '_') {
foreach ($legacy as $key => $val) {
if ($key[0] == '_') {
$control[substr($key, 1)] = $val;
} elseif($key == 'name') {
$control[$key] = $val;
@@ -136,7 +139,8 @@ class LegacyForm extends Form {
* @param string $type
* @return string
*/
protected function legacyType($type) {
protected function legacyType($type)
{
static $types = array(
'text' => 'textfield',
'password' => 'passwordfield',
@@ -147,7 +151,7 @@ class LegacyForm extends Form {
'fieldsetopen' => 'openfieldset',
'fieldsetclose' => 'closefieldset',
);
if(isset($types[$type])) return $types[$type];
if (isset($types[$type])) return $types[$type];
return $type;
}
@@ -156,20 +160,21 @@ class LegacyForm extends Form {
*
* @return \Doku_Form
*/
public function toLegacy() {
public function toLegacy()
{
$this->balanceFieldsets();
$legacy = new \Doku_Form($this->attrs());
$legacy->_hidden = $this->hidden;
foreach($this->elements as $element) {
if(is_a($element, 'dokuwiki\Form\HTMLElement')) {
foreach ($this->elements as $element) {
if (is_a($element, 'dokuwiki\Form\HTMLElement')) {
$legacy->_content[] = $element->toHTML();
} elseif(is_a($element, 'dokuwiki\Form\InputElement')) {
} elseif (is_a($element, 'dokuwiki\Form\InputElement')) {
/** @var InputElement $element */
$data = $element->attrs();
$data['_elem'] = $this->legacyType($element->getType());
$label = $element->getLabel();
if($label) {
if ($label) {
$data['_class'] = $label->attr('class');
}
$legacy->_content[] = $data;

View File

@@ -3,7 +3,8 @@
namespace dokuwiki\Form;
class OptGroup extends Element {
class OptGroup extends Element
{
protected $options = array();
protected $value;
@@ -11,7 +12,8 @@ class OptGroup extends Element {
* @param string $label The label text for this element (will be autoescaped)
* @param array $options The available options
*/
public function __construct($label, $options) {
public function __construct($label, $options)
{
parent::__construct('optGroup', array('label' => $label));
$this->options($options);
}
@@ -24,7 +26,8 @@ class OptGroup extends Element {
* @param string $value
* @return bool true if an option with the given value exists, false otherwise
*/
public function storeValue($value) {
public function storeValue($value)
{
$this->value = $value;
return isset($this->options[$value]);
}
@@ -45,11 +48,12 @@ class OptGroup extends Element {
* @param null|array $options
* @return $this|array
*/
public function options($options = null) {
if($options === null) return $this->options;
if(!is_array($options)) throw new \InvalidArgumentException('Options have to be an array');
public function options($options = null)
{
if ($options === null) return $this->options;
if (!is_array($options)) throw new \InvalidArgumentException('Options have to be an array');
$this->options = array();
foreach($options as $key => $val) {
foreach ($options as $key => $val) {
if (is_array($val)) {
if (!key_exists('label', $val)) throw new \InvalidArgumentException(
'If option is given as array, it has to have a "label"-key!'
@@ -60,7 +64,7 @@ class OptGroup extends Element {
);
}
$this->options[$key] = $val;
} elseif(is_int($key)) {
} elseif (is_int($key)) {
$this->options[$val] = array('label' => (string) $val);
} else {
$this->options[$key] = array('label' => (string) $val);
@@ -69,13 +73,13 @@ class OptGroup extends Element {
return $this;
}
/**
* The HTML representation of this element
*
* @return string
*/
public function toHTML() {
public function toHTML()
{
if ($this->attributes['label'] === null) {
return $this->renderOptions();
}
@@ -89,9 +93,10 @@ class OptGroup extends Element {
/**
* @return string
*/
protected function renderOptions() {
protected function renderOptions()
{
$html = '';
foreach($this->options as $key => $val) {
foreach ($this->options as $key => $val) {
$selected = ((string)$key === (string)$this->value) ? ' selected="selected"' : '';
$attrs = '';
if (!empty($val['attrs']) && is_array($val['attrs'])) {

View File

@@ -1,4 +1,5 @@
<?php
namespace dokuwiki\Form;
/**
@@ -9,13 +10,14 @@ namespace dokuwiki\Form;
*
* @package dokuwiki\Form
*/
class TagCloseElement extends ValueElement {
class TagCloseElement extends ValueElement
{
/**
* @param string $tag
* @param array $attributes
*/
public function __construct($tag, $attributes = array()) {
public function __construct($tag, $attributes = array())
{
parent::__construct('tagclose', $tag, $attributes);
}
@@ -26,7 +28,8 @@ class TagCloseElement extends ValueElement {
* @return void
* @throws \BadMethodCallException
*/
public function addClass($class) {
public function addClass($class)
{
throw new \BadMethodCallException('You can\t add classes to closing tag');
}
@@ -37,7 +40,8 @@ class TagCloseElement extends ValueElement {
* @return string
* @throws \BadMethodCallException
*/
public function id($id = null) {
public function id($id = null)
{
if ($id === null) {
return '';
} else {
@@ -53,7 +57,8 @@ class TagCloseElement extends ValueElement {
* @return string
* @throws \BadMethodCallException
*/
public function attr($name, $value = null) {
public function attr($name, $value = null)
{
if ($value === null) {
return '';
} else {
@@ -68,7 +73,8 @@ class TagCloseElement extends ValueElement {
* @return array
* @throws \BadMethodCallException
*/
public function attrs($attributes = null) {
public function attrs($attributes = null)
{
if ($attributes === null) {
return array();
} else {
@@ -81,7 +87,8 @@ class TagCloseElement extends ValueElement {
*
* @return string
*/
public function toHTML() {
public function toHTML()
{
return '</'.$this->val().'>';
}

View File

@@ -1,4 +1,5 @@
<?php
namespace dokuwiki\Form;
/**
@@ -8,13 +9,14 @@ namespace dokuwiki\Form;
*
* @package dokuwiki\Form
*/
class TagElement extends ValueElement {
class TagElement extends ValueElement
{
/**
* @param string $tag
* @param array $attributes
*/
public function __construct($tag, $attributes = array()) {
public function __construct($tag, $attributes = array())
{
parent::__construct('tag', $tag, $attributes);
}
@@ -23,7 +25,8 @@ class TagElement extends ValueElement {
*
* @return string
*/
public function toHTML() {
public function toHTML()
{
return '<'.$this->val().' '.buildAttributes($this->attrs()).' />';
}
}

View File

@@ -1,4 +1,5 @@
<?php
namespace dokuwiki\Form;
/**
@@ -9,13 +10,14 @@ namespace dokuwiki\Form;
*
* @package dokuwiki\Form
*/
class TagOpenElement extends ValueElement {
class TagOpenElement extends ValueElement
{
/**
* @param string $tag
* @param array $attributes
*/
public function __construct($tag, $attributes = array()) {
public function __construct($tag, $attributes = array())
{
parent::__construct('tagopen', $tag, $attributes);
}
@@ -24,7 +26,8 @@ class TagOpenElement extends ValueElement {
*
* @return string
*/
public function toHTML() {
public function toHTML()
{
return '<'.$this->val().' '.buildAttributes($this->attrs()).'>';
}
}

View File

@@ -1,12 +1,13 @@
<?php
namespace dokuwiki\Form;
/**
* Class TextareaElement
* @package dokuwiki\Form
*/
class TextareaElement extends InputElement {
class TextareaElement extends InputElement
{
/**
* @var string the actual text within the area
*/
@@ -16,7 +17,8 @@ class TextareaElement extends InputElement {
* @param string $name The name of this form element
* @param string $label The label text for this element
*/
public function __construct($name, $label) {
public function __construct($name, $label)
{
parent::__construct('textarea', $name, $label);
$this->attr('dir', 'auto');
}
@@ -29,8 +31,9 @@ class TextareaElement extends InputElement {
* @param null|string $value
* @return string|$this
*/
public function val($value = null) {
if($value !== null) {
public function val($value = null)
{
if ($value !== null) {
$this->text = cleanText($value);
return $this;
}
@@ -42,10 +45,11 @@ class TextareaElement extends InputElement {
*
* @return string
*/
protected function mainElementHTML() {
if($this->useInput) $this->prefillInput();
protected function mainElementHTML()
{
if ($this->useInput) $this->prefillInput();
return '<textarea ' . buildAttributes($this->attrs()) . '>' .
formText($this->val()) . '</textarea>';
formText($this->val()) . '</textarea>';
}
}

View File

@@ -11,8 +11,8 @@ namespace dokuwiki\Form;
*
* @package dokuwiki\Form
*/
abstract class ValueElement extends Element {
abstract class ValueElement extends Element
{
/**
* @var string holds the element's value
*/
@@ -23,7 +23,8 @@ abstract class ValueElement extends Element {
* @param string $value
* @param array $attributes
*/
public function __construct($type, $value, $attributes = array()) {
public function __construct($type, $value, $attributes = array())
{
parent::__construct($type, $attributes);
$this->val($value);
}
@@ -34,8 +35,9 @@ abstract class ValueElement extends Element {
* @param null|string $value
* @return string|$this
*/
public function val($value = null) {
if($value !== null) {
public function val($value = null)
{
if ($value !== null) {
$this->value = $value;
return $this;
}

View File

@@ -168,6 +168,9 @@ class HTTPClient {
$this->resp_body = '';
$this->resp_headers = array();
// save unencoded data for recursive call
$unencodedData = $data;
// don't accept gzip if truncated bodies might occur
if($this->max_bodysize &&
!$this->max_bodysize_abort &&
@@ -178,10 +181,9 @@ class HTTPClient {
// parse URL into bits
$uri = parse_url($url);
$server = $uri['host'];
$path = $uri['path'];
if(empty($path)) $path = '/';
$path = !empty($uri['path']) ? $uri['path'] : '/';
$uriPort = !empty($uri['port']) ? $uri['port'] : null;
if(!empty($uri['query'])) $path .= '?'.$uri['query'];
if(!empty($uri['port'])) $port = $uri['port'];
if(isset($uri['user'])) $this->user = $uri['user'];
if(isset($uri['pass'])) $this->pass = $uri['pass'];
@@ -194,7 +196,7 @@ class HTTPClient {
$use_tls = $this->proxy_ssl;
}else{
$request_url = $path;
if (!isset($port)) $port = ($uri['scheme'] == 'https') ? 443 : 80;
$port = $uriPort ?: ($uri['scheme'] == 'https' ? 443 : 80);
$use_tls = ($uri['scheme'] == 'https');
}
@@ -209,8 +211,8 @@ class HTTPClient {
// prepare headers
$headers = $this->headers;
$headers['Host'] = $uri['host'];
if(!empty($uri['port'])) $headers['Host'].= ':'.$uri['port'];
$headers['Host'] = $uri['host']
. ($uriPort ? ':' . $uriPort : '');
$headers['User-Agent'] = $this->agent;
$headers['Referer'] = $this->referer;
@@ -354,7 +356,7 @@ class HTTPClient {
$this->debug('Object headers',$this->resp_headers);
// check server status code to follow redirect
if($this->status == 301 || $this->status == 302 ){
if(in_array($this->status, [301, 302, 303, 307, 308])){
if (empty($this->resp_headers['location'])){
throw new HTTPClientException('Redirect but no Location Header found');
}elseif($this->redirect_count == $this->max_redirect){
@@ -370,15 +372,20 @@ class HTTPClient {
// handle non-RFC-compliant relative redirects
if (!preg_match('/^http/i', $this->resp_headers['location'])){
if($this->resp_headers['location'][0] != '/'){
$this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].':'.$uri['port'].
dirname($uri['path']).'/'.$this->resp_headers['location'];
$this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].':'.$uriPort.
dirname($path).'/'.$this->resp_headers['location'];
}else{
$this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].':'.$uri['port'].
$this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].':'.$uriPort.
$this->resp_headers['location'];
}
}
// perform redirected request, always via GET (required by RFC)
return $this->sendRequest($this->resp_headers['location'],array(),'GET');
if($this->status == 307 || $this->status == 308) {
// perform redirected request, same method as before (required by RFC)
return $this->sendRequest($this->resp_headers['location'],$unencodedData,$method);
}else{
// perform redirected request, always via GET (required by RFC)
return $this->sendRequest($this->resp_headers['location'],array(),'GET');
}
}
}
@@ -511,7 +518,7 @@ class HTTPClient {
if(!$this->useProxyForUrl($requesturl)) return false;
$requestinfo = parse_url($requesturl);
if($requestinfo['scheme'] != 'https') return false;
if(!$requestinfo['port']) $requestinfo['port'] = 443;
if(empty($requestinfo['port'])) $requestinfo['port'] = 443;
// build request
$request = "CONNECT {$requestinfo['host']}:{$requestinfo['port']} HTTP/1.0".HTTP_NL;
@@ -765,7 +772,7 @@ class HTTPClient {
foreach($lines as $line){
@list($key, $val) = explode(':',$line,2);
$key = trim($key);
$val = trim($val);
$val = trim($val ?? '');
$key = strtolower($key);
if(!$key) continue;
if(isset($headers[$key])){

View File

@@ -0,0 +1,39 @@
<?php
namespace dokuwiki\HTTP;
/**
* Utilities to send HTTP Headers
*/
class Headers
{
/**
* Send a Content-Security-Polica Header
*
* Expects an associative array with individual policies and their values
*
* @param array $policy
*/
static public function contentSecurityPolicy($policy)
{
foreach ($policy as $key => $values) {
// if the value is not an array, we also accept newline terminated strings
if (!is_array($values)) $values = explode("\n", $values);
$values = array_map('trim', $values);
$values = array_unique($values);
$values = array_filter($values);
$policy[$key] = $values;
}
$cspheader = 'Content-Security-Policy:';
foreach ($policy as $key => $values) {
if ($values) {
$cspheader .= " $key " . join(' ', $values) . ';';
} else {
$cspheader .= " $key;";
}
}
header($cspheader);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1317,79 +1317,81 @@ class JpegMeta {
$this->_info['file']['UnixTime'] = filemtime($this->_fileName);
// get image size directly from file
$size = getimagesize($this->_fileName);
$this->_info['file']['Width'] = $size[0];
$this->_info['file']['Height'] = $size[1];
// set mime types and formats
// http://php.net/manual/en/function.getimagesize.php
// http://php.net/manual/en/function.image-type-to-mime-type.php
switch ($size[2]){
case 1:
$this->_info['file']['Mime'] = 'image/gif';
$this->_info['file']['Format'] = 'GIF';
break;
case 2:
$this->_info['file']['Mime'] = 'image/jpeg';
$this->_info['file']['Format'] = 'JPEG';
break;
case 3:
$this->_info['file']['Mime'] = 'image/png';
$this->_info['file']['Format'] = 'PNG';
break;
case 4:
$this->_info['file']['Mime'] = 'application/x-shockwave-flash';
$this->_info['file']['Format'] = 'SWF';
break;
case 5:
$this->_info['file']['Mime'] = 'image/psd';
$this->_info['file']['Format'] = 'PSD';
break;
case 6:
$this->_info['file']['Mime'] = 'image/bmp';
$this->_info['file']['Format'] = 'BMP';
break;
case 7:
$this->_info['file']['Mime'] = 'image/tiff';
$this->_info['file']['Format'] = 'TIFF (Intel)';
break;
case 8:
$this->_info['file']['Mime'] = 'image/tiff';
$this->_info['file']['Format'] = 'TIFF (Motorola)';
break;
case 9:
$this->_info['file']['Mime'] = 'application/octet-stream';
$this->_info['file']['Format'] = 'JPC';
break;
case 10:
$this->_info['file']['Mime'] = 'image/jp2';
$this->_info['file']['Format'] = 'JP2';
break;
case 11:
$this->_info['file']['Mime'] = 'application/octet-stream';
$this->_info['file']['Format'] = 'JPX';
break;
case 12:
$this->_info['file']['Mime'] = 'application/octet-stream';
$this->_info['file']['Format'] = 'JB2';
break;
case 13:
$this->_info['file']['Mime'] = 'application/x-shockwave-flash';
$this->_info['file']['Format'] = 'SWC';
break;
case 14:
$this->_info['file']['Mime'] = 'image/iff';
$this->_info['file']['Format'] = 'IFF';
break;
case 15:
$this->_info['file']['Mime'] = 'image/vnd.wap.wbmp';
$this->_info['file']['Format'] = 'WBMP';
break;
case 16:
$this->_info['file']['Mime'] = 'image/xbm';
$this->_info['file']['Format'] = 'XBM';
break;
default:
$this->_info['file']['Mime'] = 'image/unknown';
if ($size = getimagesize($this->_fileName)) {
$this->_info['file']['Width'] = $size[0];
$this->_info['file']['Height'] = $size[1];
// set mime types and formats
// http://php.net/manual/en/function.getimagesize.php
// http://php.net/manual/en/function.image-type-to-mime-type.php
switch ($size[2]) {
case 1:
$this->_info['file']['Mime'] = 'image/gif';
$this->_info['file']['Format'] = 'GIF';
break;
case 2:
$this->_info['file']['Mime'] = 'image/jpeg';
$this->_info['file']['Format'] = 'JPEG';
break;
case 3:
$this->_info['file']['Mime'] = 'image/png';
$this->_info['file']['Format'] = 'PNG';
break;
case 4:
$this->_info['file']['Mime'] = 'application/x-shockwave-flash';
$this->_info['file']['Format'] = 'SWF';
break;
case 5:
$this->_info['file']['Mime'] = 'image/psd';
$this->_info['file']['Format'] = 'PSD';
break;
case 6:
$this->_info['file']['Mime'] = 'image/bmp';
$this->_info['file']['Format'] = 'BMP';
break;
case 7:
$this->_info['file']['Mime'] = 'image/tiff';
$this->_info['file']['Format'] = 'TIFF (Intel)';
break;
case 8:
$this->_info['file']['Mime'] = 'image/tiff';
$this->_info['file']['Format'] = 'TIFF (Motorola)';
break;
case 9:
$this->_info['file']['Mime'] = 'application/octet-stream';
$this->_info['file']['Format'] = 'JPC';
break;
case 10:
$this->_info['file']['Mime'] = 'image/jp2';
$this->_info['file']['Format'] = 'JP2';
break;
case 11:
$this->_info['file']['Mime'] = 'application/octet-stream';
$this->_info['file']['Format'] = 'JPX';
break;
case 12:
$this->_info['file']['Mime'] = 'application/octet-stream';
$this->_info['file']['Format'] = 'JB2';
break;
case 13:
$this->_info['file']['Mime'] = 'application/x-shockwave-flash';
$this->_info['file']['Format'] = 'SWC';
break;
case 14:
$this->_info['file']['Mime'] = 'image/iff';
$this->_info['file']['Format'] = 'IFF';
break;
case 15:
$this->_info['file']['Mime'] = 'image/vnd.wap.wbmp';
$this->_info['file']['Format'] = 'WBMP';
break;
case 16:
$this->_info['file']['Mime'] = 'image/xbm';
$this->_info['file']['Format'] = 'XBM';
break;
default:
$this->_info['file']['Mime'] = 'image/unknown';
}
}
} else {
$this->_info['file'] = array();

215
dokuwiki/inc/Logger.php Normal file
View File

@@ -0,0 +1,215 @@
<?php
namespace dokuwiki;
use dokuwiki\Extension\Event;
/**
* Log messages to a daily log file
*/
class Logger
{
const LOG_ERROR = 'error';
const LOG_DEPRECATED = 'deprecated';
const LOG_DEBUG = 'debug';
/** @var Logger[] */
static protected $instances;
/** @var string what kind of log is this */
protected $facility;
protected $isLogging = true;
/**
* Logger constructor.
*
* @param string $facility The type of log
*/
protected function __construct($facility)
{
global $conf;
$this->facility = $facility;
// Should logging be disabled for this facility?
$dontlog = explode(',', $conf['dontlog']);
$dontlog = array_map('trim', $dontlog);
if (in_array($facility, $dontlog)) $this->isLogging = false;
}
/**
* Return a Logger instance for the given facility
*
* @param string $facility The type of log
* @return Logger
*/
static public function getInstance($facility = self::LOG_ERROR)
{
if (empty(self::$instances[$facility])) {
self::$instances[$facility] = new Logger($facility);
}
return self::$instances[$facility];
}
/**
* Convenience method to directly log to the error log
*
* @param string $message The log message
* @param mixed $details Any details that should be added to the log entry
* @param string $file A source filename if this is related to a source position
* @param int $line A line number for the above file
* @return bool has a log been written?
*/
static public function error($message, $details = null, $file = '', $line = 0)
{
return self::getInstance(self::LOG_ERROR)->log(
$message, $details, $file, $line
);
}
/**
* Convenience method to directly log to the debug log
*
* @param string $message The log message
* @param mixed $details Any details that should be added to the log entry
* @param string $file A source filename if this is related to a source position
* @param int $line A line number for the above file
* @return bool has a log been written?
*/
static public function debug($message, $details = null, $file = '', $line = 0)
{
return self::getInstance(self::LOG_DEBUG)->log(
$message, $details, $file, $line
);
}
/**
* Convenience method to directly log to the deprecation log
*
* @param string $message The log message
* @param mixed $details Any details that should be added to the log entry
* @param string $file A source filename if this is related to a source position
* @param int $line A line number for the above file
* @return bool has a log been written?
*/
static public function deprecated($message, $details = null, $file = '', $line = 0)
{
return self::getInstance(self::LOG_DEPRECATED)->log(
$message, $details, $file, $line
);
}
/**
* Log a message to the facility log
*
* @param string $message The log message
* @param mixed $details Any details that should be added to the log entry
* @param string $file A source filename if this is related to a source position
* @param int $line A line number for the above file
* @triggers LOGGER_DATA_FORMAT can be used to change the logged data or intercept it
* @return bool has a log been written?
*/
public function log($message, $details = null, $file = '', $line = 0)
{
global $EVENT_HANDLER;
if (!$this->isLogging) return false;
$datetime = time();
$data = [
'facility' => $this->facility,
'datetime' => $datetime,
'message' => $message,
'details' => $details,
'file' => $file,
'line' => $line,
'loglines' => [],
'logfile' => $this->getLogfile($datetime),
];
if ($EVENT_HANDLER !== null) {
$event = new Event('LOGGER_DATA_FORMAT', $data);
if ($event->advise_before()) {
$data['loglines'] = $this->formatLogLines($data);
}
$event->advise_after();
} else {
// The event system is not yet available, to ensure the log isn't lost even on
// fatal errors, the default action is executed
$data['loglines'] = $this->formatLogLines($data);
}
// only log when any data available
if (count($data['loglines'])) {
return $this->writeLogLines($data['loglines'], $data['logfile']);
} else {
return false;
}
}
/**
* Formats the given data as loglines
*
* @param array $data Event data from LOGGER_DATA_FORMAT
* @return string[] the lines to log
*/
protected function formatLogLines($data)
{
extract($data);
// details are logged indented
if ($details) {
if (!is_string($details)) {
$details = json_encode($details, JSON_PRETTY_PRINT);
}
$details = explode("\n", $details);
$loglines = array_map(function ($line) {
return ' ' . $line;
}, $details);
} elseif ($details) {
$loglines = [$details];
} else {
$loglines = [];
}
// datetime, fileline, message
$logline = gmdate('Y-m-d H:i:s', $datetime) . "\t";
if ($file) {
$logline .= $file;
if ($line) $logline .= "($line)";
}
$logline .= "\t" . $message;
array_unshift($loglines, $logline);
return $loglines;
}
/**
* Construct the log file for the given day
*
* @param false|string|int $date Date to access, false for today
* @return string
*/
public function getLogfile($date = false)
{
global $conf;
if($date !== null && !is_numeric($date)) {
$date = strtotime($date);
}
if (!$date) $date = time();
return $conf['logdir'] . '/' . $this->facility . '/' . date('Y-m-d', $date) . '.log';
}
/**
* Write the given lines to today's facility log
*
* @param string[] $lines the raw lines to append to the log
* @param string $logfile where to write to
* @return bool true if the log was written
*/
protected function writeLogLines($lines, $logfile)
{
return io_saveFile($logfile, join("\n", $lines) . "\n", true);
}
}

View File

@@ -7,15 +7,18 @@ namespace dokuwiki\Menu\Item;
*
* Quick revert to the currently shown page revision
*/
class Revert extends AbstractItem {
class Revert extends AbstractItem
{
/** @inheritdoc */
public function __construct() {
public function __construct()
{
global $REV;
global $INFO;
global $INPUT;
parent::__construct();
if(!$REV || !$INFO['writable']) {
if (!$REV || !$INFO['writable'] || $INPUT->server->str('REMOTE_USER') === '') {
throw new \RuntimeException('revert not available');
}
$this->params['rev'] = $REV;

View File

@@ -66,7 +66,7 @@ class ParallelRegex
* subject.
* @return bool|string False if no match found, label if label exists, true if not
*/
public function match($subject, &$match)
public function apply($subject, &$match)
{
if (count($this->patterns) == 0) {
return false;

View File

@@ -23,8 +23,10 @@ class Externallink extends AbstractMode
$this->patterns[] = '\b(?i)'.$scheme.'(?-i)://['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
}
$this->patterns[] = '(?<=\s)(?i)www?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
$this->patterns[] = '(?<=\s)(?i)ftp?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
$this->patterns[] = '(?<![/\\\\])\b(?i)www?(?-i)\.['.$host.']+?\.'.
'['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
$this->patterns[] = '(?<![/\\\\])\b(?i)ftp?(?-i)\.['.$host.']+?\.'.
'['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
}
/** @inheritdoc */
@@ -41,4 +43,12 @@ class Externallink extends AbstractMode
{
return 330;
}
/**
* @return array
*/
public function getPatterns()
{
return $this->patterns;
}
}

View File

@@ -92,10 +92,13 @@ class PassHash {
} elseif(preg_match('/^:B:(.+?):.{32}$/', $hash, $m)) {
$method = 'mediawiki';
$salt = $m[1];
} elseif(preg_match('/^\$6\$(rounds=\d+)?\$?(.+?)\$/', $hash, $m)) {
$method = 'sha512';
$salt = $m[2];
$magic = $m[1];
} elseif(preg_match('/^\$(5|6)\$(rounds=\d+)?\$?(.+?)\$/', $hash, $m)) {
$method = 'sha2';
$salt = $m[3];
$magic = array(
'prefix' => $m[1],
'rounds' => $m[2],
);
} elseif(preg_match('/^\$(argon2id?)/', $hash, $m)) {
if(!defined('PASSWORD_'.strtoupper($m[1]))) {
throw new \Exception('This PHP installation has no '.strtoupper($m[1]).' support');
@@ -666,29 +669,58 @@ class PassHash {
}
/**
* Password hashing method SHA512
* Password hashing method SHA-2
*
* This is only supported on PHP 5.3.2 or higher and will throw an exception if
* the needed crypt support is not available
*
* Uses:
* - SHA-2 with 256-bit output for prefix $5$
* - SHA-2 with 512-bit output for prefix $6$ (default)
*
* @param string $clear The clear text to hash
* @param string $salt The salt to use, null for random
* @param string $magic The rounds for sha512 (for example "rounds=3000"), null for default value
* @param array $opts ('rounds' => rounds for sha256/sha512, 'prefix' => selected method from SHA-2 family)
* @return string Hashed password
* @throws \Exception
*/
public function hash_sha512($clear, $salt = null, $magic = null) {
if(!defined('CRYPT_SHA512') || CRYPT_SHA512 != 1) {
public function hash_sha2($clear, $salt = null, $opts = array()) {
if(empty($opts['prefix'])) {
$prefix = '6';
} else {
$prefix = $opts['prefix'];
}
if(empty($opts['rounds'])) {
$rounds = null;
} else {
$rounds = $opts['rounds'];
}
if($prefix == '5' && (!defined('CRYPT_SHA256') || CRYPT_SHA256 != 1)) {
throw new \Exception('This PHP installation has no SHA256 support');
}
if($prefix == '6' && (!defined('CRYPT_SHA512') || CRYPT_SHA512 != 1)) {
throw new \Exception('This PHP installation has no SHA512 support');
}
$this->init_salt($salt, 8, false);
if(empty($magic)) {
return crypt($clear, '$6$'.$salt.'$');
if(empty($rounds)) {
return crypt($clear, '$'.$prefix.'$'.$salt.'$');
}else{
return crypt($clear, '$6$'.$magic.'$'.$salt.'$');
return crypt($clear, '$'.$prefix.'$'.$rounds.'$'.$salt.'$');
}
}
/** @see sha2 */
public function hash_sha512($clear, $salt = null, $opts=[]) {
$opts['prefix'] = 6;
return $this->hash_sha2($clear, $salt, $opts);
}
/** @see sha2 */
public function hash_sha256($clear, $salt = null, $opts=[]) {
$opts['prefix'] = 5;
return $this->hash_sha2($clear, $salt, $opts);
}
/**
* Password hashing method 'mediawiki'
*
@@ -722,7 +754,7 @@ class PassHash {
if(!defined('PASSWORD_ARGON2I')) {
throw new \Exception('This PHP installation has no ARGON2I support');
}
return password_hash($clear,PASSWORD_ARGON2I);
return password_hash($clear,PASSWORD_ARGON2I);
}
/**
@@ -740,7 +772,7 @@ class PassHash {
if(!defined('PASSWORD_ARGON2ID')) {
throw new \Exception('This PHP installation has no ARGON2ID support');
}
return password_hash($clear,PASSWORD_ARGON2ID);
return password_hash($clear,PASSWORD_ARGON2ID);
}
/**

View File

@@ -92,7 +92,8 @@ class Api
if ($args === null) {
$args = array();
}
list($type, $pluginName, /* $call */) = explode('.', $method, 3);
// Ensure we have at least one '.' in $method
list($type, $pluginName, /* $call */) = explode('.', $method . '.', 3);
if ($type === 'plugin') {
return $this->callPlugin($pluginName, $method, $args);
}
@@ -128,8 +129,9 @@ class Api
if (!array_key_exists($method, $customCalls)) {
throw new RemoteException('Method does not exist', -32603);
}
$customCall = $customCalls[$method];
return $this->callPlugin($customCall[0], $customCall[1], $args);
list($plugin, $method) = $customCalls[$method];
$fullMethod = "plugin.$plugin.$method";
return $this->callPlugin($plugin, $fullMethod, $args);
}
/**
@@ -342,7 +344,7 @@ class Api
$this->coreMethods = $apiCore;
}
}
return $this->coreMethods->__getRemoteInfo();
return $this->coreMethods->getRemoteInfo();
}
/**

View File

@@ -6,8 +6,9 @@ use Doku_Renderer_xhtml;
use dokuwiki\ChangeLog\MediaChangeLog;
use dokuwiki\ChangeLog\PageChangeLog;
use dokuwiki\Extension\Event;
use dokuwiki\Utf8\Sort;
define('DOKU_API_VERSION', 10);
define('DOKU_API_VERSION', 11);
/**
* Provides the core methods for the remote API.
@@ -16,7 +17,7 @@ define('DOKU_API_VERSION', 10);
class ApiCore
{
/** @var int Increased whenever the API is changed */
const API_VERSION = 10;
const API_VERSION = 11;
/** @var Api */
@@ -35,7 +36,7 @@ class ApiCore
*
* @return array
*/
public function __getRemoteInfo()
public function getRemoteInfo()
{
return array(
'dokuwiki.getVersion' => array(
@@ -77,7 +78,11 @@ class ApiCore
'args' => array('string', 'string', 'array'),
'return' => 'bool',
'doc' => 'Append text to a wiki page.'
), 'dokuwiki.deleteUsers' => array(
), 'dokuwiki.createUser' => array(
'args' => array('struct'),
'return' => 'bool',
'doc' => 'Create a user. The result is boolean'
),'dokuwiki.deleteUsers' => array(
'args' => array('array'),
'return' => 'bool',
'doc' => 'Remove one or more users from the list of registered users.'
@@ -142,11 +147,11 @@ class ApiCore
), 'wiki.getRecentChanges' => array(
'args' => array('int'),
'return' => 'array',
'Returns a struct about all recent changes since given timestamp.'
'doc' => 'Returns a struct about all recent changes since given timestamp.'
), 'wiki.getRecentMediaChanges' => array(
'args' => array('int'),
'return' => 'array',
'Returns a struct about all recent media changes since given timestamp.'
'doc' => 'Returns a struct about all recent media changes since given timestamp.'
), 'wiki.aclCheck' => array(
'args' => array('string', 'string', 'array'),
'return' => 'int',
@@ -310,6 +315,7 @@ class ApiCore
$list = array();
$pages = idx_get_indexer()->getPages();
$pages = array_filter(array_filter($pages, 'isVisiblePage'), 'page_exists');
Sort::ksort($pages);
foreach (array_keys($pages) as $idx) {
$perm = auth_quickaclcheck($pages[$idx]);
@@ -580,6 +586,68 @@ class ApiCore
return $this->putPage($id, $currentpage . $text, $params);
}
/**
* Create one or more users
*
* @param array[] $userStruct User struct
*
* @return boolean Create state
*
* @throws AccessDeniedException
* @throws RemoteException
*/
public function createUser($userStruct)
{
if (!auth_isadmin()) {
throw new AccessDeniedException('Only admins are allowed to create users', 114);
}
/** @var \dokuwiki\Extension\AuthPlugin $auth */
global $auth;
if(!$auth->canDo('addUser')) {
throw new AccessDeniedException(
sprintf('Authentication backend %s can\'t do addUser', $auth->getPluginName()),
114
);
}
$user = trim($auth->cleanUser($userStruct['user'] ?? ''));
$password = $userStruct['password'] ?? '';
$name = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $userStruct['name'] ?? ''));
$mail = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $userStruct['mail'] ?? ''));
$groups = $userStruct['groups'] ?? [];
$notify = (bool)$userStruct['notify'] ?? false;
if ($user === '') throw new RemoteException('empty or invalid user', 401);
if ($name === '') throw new RemoteException('empty or invalid user name', 402);
if (!mail_isvalid($mail)) throw new RemoteException('empty or invalid mail address', 403);
if(strlen($password) === 0) {
$password = auth_pwgen($user);
}
if (!is_array($groups) || count($groups) === 0) {
$groups = null;
}
$ok = $auth->triggerUserMod('create', array($user, $password, $name, $mail, $groups));
if ($ok !== false && $ok !== null) {
$ok = true;
}
if($ok) {
if($notify) {
auth_sendPassword($user, $password);
}
}
return $ok;
}
/**
* Remove one or more users from the list of registered users
*

View File

@@ -0,0 +1,88 @@
<?php
namespace dokuwiki\Remote\IXR;
use dokuwiki\HTTP\HTTPClient;
use IXR\Message\Message;
use IXR\Request\Request;
/**
* This implements a XML-RPC client using our own HTTPClient
*
* Note: this now inherits from the IXR library's client and no longer from HTTPClient. Instead composition
* is used to add the HTTP client.
*/
class Client extends \IXR\Client\Client
{
/** @var HTTPClient */
protected $httpClient;
/** @var string */
protected $posturl = '';
/** @inheritdoc */
public function __construct($server, $path = false, $port = 80, $timeout = 15, $timeout_io = null)
{
parent::__construct($server, $path, $port, $timeout, $timeout_io);
if (!$path) {
// Assume we have been given an URL instead
$this->posturl = $server;
} else {
$this->posturl = 'http://' . $server . ':' . $port . $path;
}
$this->httpClient = new HTTPClient();
$this->httpClient->timeout = $timeout;
}
/** @inheritdoc */
public function query()
{
$args = func_get_args();
$method = array_shift($args);
$request = new Request($method, $args);
$length = $request->getLength();
$xml = $request->getXml();
$this->headers['Content-Type'] = 'text/xml';
$this->headers['Content-Length'] = $length;
$this->httpClient->headers = array_merge($this->httpClient->headers, $this->headers);
if (!$this->httpClient->sendRequest($this->posturl, $xml, 'POST')) {
$this->handleError(-32300, 'transport error - ' . $this->httpClient->error);
return false;
}
// Check HTTP Response code
if ($this->httpClient->status < 200 || $this->httpClient->status > 206) {
$this->handleError(-32300, 'transport error - HTTP status ' . $this->httpClient->status);
return false;
}
// Now parse what we've got back
$this->message = new Message($this->httpClient->resp_body);
if (!$this->message->parse()) {
// XML error
return $this->handleError(-32700, 'Parse error. Message not well formed');
}
// Is the message a fault?
if ($this->message->messageType == 'fault') {
return $this->handleError($this->message->faultCode, $this->message->faultString);
}
// Message must be OK
return true;
}
/**
* Direct access to the underlying HTTP client if needed
*
* @return HTTPClient
*/
public function getHTTPClient()
{
return $this->httpClient;
}
}

View File

@@ -2,10 +2,15 @@
namespace dokuwiki\Remote;
use IXR\DataType\Base64;
use IXR\DataType\Date;
use IXR\Exception\ServerException;
use IXR\Server\Server;
/**
* Contains needed wrapper functions and registers all available XMLRPC functions.
*/
class XmlRpcServer extends \IXR_Server
class XmlRpcServer extends Server
{
protected $remote;
@@ -20,6 +25,20 @@ class XmlRpcServer extends \IXR_Server
parent::__construct(false, false, $wait);
}
/** @inheritdoc */
public function serve($data = false)
{
global $conf;
if (!$conf['remote']) {
throw new ServerException("XML-RPC server not enabled.", -32605);
}
if (!empty($conf['remotecors'])) {
header('Access-Control-Allow-Origin: ' . $conf['remotecors']);
}
parent::serve($data);
}
/**
* @inheritdoc
*/
@@ -28,34 +47,34 @@ class XmlRpcServer extends \IXR_Server
try {
$result = $this->remote->call($methodname, $args);
return $result;
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (AccessDeniedException $e) {
} catch (AccessDeniedException $e) {
if (!isset($_SERVER['REMOTE_USER'])) {
http_status(401);
return new \IXR_Error(-32603, "server error. not authorized to call method $methodname");
return new ServerException("server error. not authorized to call method $methodname", -32603);
} else {
http_status(403);
return new \IXR_Error(-32604, "server error. forbidden to call the method $methodname");
return new ServerException("server error. forbidden to call the method $methodname", -32604);
}
} catch (RemoteException $e) {
return new \IXR_Error($e->getCode(), $e->getMessage());
return new ServerException($e->getMessage(), $e->getCode());
}
}
/**
* @param string|int $data iso date(yyyy[-]mm[-]dd[ hh:mm[:ss]]) or timestamp
* @return \IXR_Date
* @return Date
*/
public function toDate($data)
{
return new \IXR_Date($data);
return new Date($data);
}
/**
* @param string $data
* @return \IXR_Base64
* @return Base64
*/
public function toFile($data)
{
return new \IXR_Base64($data);
return new Base64($data);
}
}

View File

@@ -927,7 +927,7 @@ class Indexer {
$status = true;
$run = 0;
$lock = $conf['lockdir'].'/_indexer.lock';
while (!@mkdir($lock, $conf['dmode'])) {
while (!@mkdir($lock)) {
usleep(50);
if(is_dir($lock) && time()-@filemtime($lock) > 60*5){
// looks like a stale lock - remove it
@@ -942,7 +942,7 @@ class Indexer {
return false;
}
}
if (!empty($conf['dperm'])) {
if ($conf['dperm']) {
chmod($lock, $conf['dperm']);
}
return $status;
@@ -1186,8 +1186,10 @@ class Indexer {
if ($tuple === '') continue;
list($key, $cnt) = explode('*', $tuple);
if (!$cnt) continue;
$key = $keys[$key];
if ($key === false || is_null($key)) continue;
if (isset($keys[$key])) {
$key = $keys[$key];
if ($key === false || is_null($key)) continue;
}
$result[$key] = $cnt;
}
return $result;

View File

@@ -9,6 +9,7 @@
namespace dokuwiki\Sitemap;
use dokuwiki\HTTP\DokuHTTPClient;
use dokuwiki\Logger;
/**
* A class for building sitemaps and pinging search engines with the sitemap URL.
@@ -43,14 +44,14 @@ class Mapper {
if(@filesize($sitemap) &&
@filemtime($sitemap) > (time()-($conf['sitemap']*86400))){ // 60*60*24=86400
dbglog('Sitemapper::generate(): Sitemap up to date');
Logger::debug('Sitemapper::generate(): Sitemap up to date');
return false;
}
dbglog("Sitemapper::generate(): using $sitemap");
Logger::debug("Sitemapper::generate(): using $sitemap");
$pages = idx_get_indexer()->getPages();
dbglog('Sitemapper::generate(): creating sitemap using '.count($pages).' pages');
Logger::debug('Sitemapper::generate(): creating sitemap using '.count($pages).' pages');
$items = array();
// build the sitemap items
@@ -139,9 +140,9 @@ class Mapper {
$encoded_sitemap_url = urlencode(wl('', array('do' => 'sitemap'), true, '&'));
$ping_urls = array(
'google' => 'http://www.google.com/webmasters/sitemaps/ping?sitemap='.$encoded_sitemap_url,
'google' => 'https://www.google.com/ping?sitemap='.$encoded_sitemap_url,
'microsoft' => 'http://www.bing.com/webmaster/ping.aspx?siteMap='.$encoded_sitemap_url,
'yandex' => 'http://blogs.yandex.ru/pings/?status=success&url='.$encoded_sitemap_url
'yandex' => 'https://webmaster.yandex.com/ping?sitemap='.$encoded_sitemap_url
);
$data = array('ping_urls' => $ping_urls,
@@ -150,10 +151,11 @@ class Mapper {
$event = new \dokuwiki\Extension\Event('SITEMAP_PING', $data);
if ($event->advise_before(true)) {
foreach ($data['ping_urls'] as $name => $url) {
dbglog("Sitemapper::PingSearchEngines(): pinging $name");
Logger::debug("Sitemapper::PingSearchEngines(): pinging $name");
$resp = $http->get($url);
if($http->error) dbglog("Sitemapper:pingSearchengines(): $http->error");
dbglog('Sitemapper:pingSearchengines(): '.preg_replace('/[\n\r]/',' ',strip_tags($resp)));
if($http->error) {
Logger::debug("Sitemapper:pingSearchengines(): $http->error", $resp);
}
}
}
$event->advise_after();

View File

@@ -99,7 +99,7 @@ class StyleUtils
if (file_exists($inifile)) {
$config = parse_ini_file($inifile, true);
if (is_array($config['stylesheets'])) {
if (isset($config['stylesheets']) && is_array($config['stylesheets'])) {
foreach ($config['stylesheets'] as $inifile => $mode) {
// validate and include style files
$stylesheets = array_merge(
@@ -110,7 +110,7 @@ class StyleUtils
}
}
if (is_array($config['replacements'])) {
if (isset($config['replacements']) && is_array($config['replacements'])) {
$replacements = array_replace(
$replacements,
$this->cssFixreplacementurls($config['replacements'], $webbase)

View File

@@ -87,7 +87,8 @@ class BulkSubscriptionSender extends SubscriptionSender
$n = 0;
while (!is_null($rev) && $rev['date'] >= $lastupdate &&
($INPUT->server->str('REMOTE_USER') === $rev['user'] ||
$rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)) {
$rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)
) {
$pagelog = new PageChangeLog($rev['id']);
$rev = $pagelog->getRevisions($n++, 1);
$rev = (count($rev) > 0) ? $rev[0] : null;
@@ -152,11 +153,11 @@ class BulkSubscriptionSender extends SubscriptionSender
}
// try creating the lock directory
if (!@mkdir($lock, $conf['dmode'])) {
if (!@mkdir($lock)) {
return false;
}
if (!empty($conf['dperm'])) {
if ($conf['dperm']) {
chmod($lock, $conf['dperm']);
}
return true;

View File

@@ -5,6 +5,7 @@ namespace dokuwiki;
use dokuwiki\Extension\Event;
use dokuwiki\Sitemap\Mapper;
use dokuwiki\Subscriptions\BulkSubscriptionSender;
use dokuwiki\ChangeLog\ChangeLog;
/**
* Class TaskRunner
@@ -120,7 +121,7 @@ class TaskRunner
$out_lines = [];
$old_lines = [];
for ($i = 0; $i < count($lines); $i++) {
$log = parseChangelogLine($lines[$i]);
$log = ChangeLog::parseLogLine($lines[$i]);
if ($log === false) {
continue; // discard junk
}

Some files were not shown because too many files have changed in this diff Show More