diff options
351 files changed, 21379 insertions, 16420 deletions
diff --git a/_cs/DokuWiki/Sniffs/Functions/OpeningFunctionBraceSniff.php b/_cs/DokuWiki/Sniffs/Functions/OpeningFunctionBraceSniff.php deleted file mode 100644 index 6c582b3af..000000000 --- a/_cs/DokuWiki/Sniffs/Functions/OpeningFunctionBraceSniff.php +++ /dev/null @@ -1,81 +0,0 @@ -<?php -/** - * Generic_Sniffs_Functions_OpeningFunctionBraceKernighanRitchieSniff. - */ - -class DokuWiki_Sniffs_Functions_OpeningFunctionBraceSniff implements PHP_CodeSniffer_Sniff { - - - /** - * Registers the tokens that this sniff wants to listen for. - * - * @return void - */ - public function register() - { - return array(T_FUNCTION); - - }//end register() - - - /** - * Processes this test, when one of its tokens is encountered. - * - * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token in the - * stack passed in $tokens. - * - * @return void - */ - public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) - { - $tokens = $phpcsFile->getTokens(); - - if (isset($tokens[$stackPtr]['scope_opener']) === false) { - return; - } - - $openingBrace = $tokens[$stackPtr]['scope_opener']; - - // The end of the function occurs at the end of the argument list. Its - // like this because some people like to break long function declarations - // over multiple lines. - $functionLine = $tokens[$tokens[$stackPtr]['parenthesis_closer']]['line']; - $braceLine = $tokens[$openingBrace]['line']; - - $lineDifference = ($braceLine - $functionLine); - - if ($lineDifference > 0) { - $error = 'Opening brace should be on the same line as the declaration'; - $phpcsFile->addError($error, $openingBrace); - return; - } - - // Checks that the closing parenthesis and the opening brace are - // separated by a whitespace character. - $closerColumn = $tokens[$tokens[$stackPtr]['parenthesis_closer']]['column']; - $braceColumn = $tokens[$openingBrace]['column']; - - $columnDifference = ($braceColumn - $closerColumn); - - if ($columnDifference > 2) { - $error = 'Expected 0 or 1 space between the closing parenthesis and the opening brace; found '.($columnDifference - 1).'.'; - $phpcsFile->addError($error, $openingBrace); - return; - } - - // Check that a tab was not used instead of a space. - $spaceTokenPtr = ($tokens[$stackPtr]['parenthesis_closer'] + 1); - $spaceContent = $tokens[$spaceTokenPtr]['content']; - if ($columnDifference == 2 && $spaceContent !== ' ') { - $error = 'Expected a none or a single space character between closing parenthesis and opening brace; found "'.$spaceContent.'".'; - $phpcsFile->addError($error, $openingBrace); - return; - } - - }//end process() - - -}//end class - -?> diff --git a/_cs/DokuWiki/Sniffs/NamingConventions/ConstructorNameSniff.php b/_cs/DokuWiki/Sniffs/NamingConventions/ConstructorNameSniff.php deleted file mode 100644 index 7dd6d9366..000000000 --- a/_cs/DokuWiki/Sniffs/NamingConventions/ConstructorNameSniff.php +++ /dev/null @@ -1,85 +0,0 @@ -<?php -/** - * Generic_Sniffs_NamingConventions_ConstructorNameSniff. - * - * PHP version 5 - * - * @category PHP - * @package PHP_CodeSniffer - * @author Greg Sherwood <gsherwood@squiz.net> - * @author Leif Wickland <lwickland@rightnow.com> - * @copyright 2006-2011 Squiz Pty Ltd (ABN 77 084 670 600) - * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence - * @link http://pear.php.net/package/PHP_CodeSniffer - */ - -if (class_exists('PHP_CodeSniffer_Standards_AbstractScopeSniff', true) === false) { - $error = 'Class PHP_CodeSniffer_Standards_AbstractScopeSniff not found'; - throw new PHP_CodeSniffer_Exception($error); -} - -/** - * Generic_Sniffs_NamingConventions_ConstructorNameSniff. - * - * Favor PHP 5 constructor syntax, which uses "function __construct()". - * Avoid PHP 4 constructor syntax, which uses "function ClassName()". - * - * @category PHP - * @package PHP_CodeSniffer - * @author Leif Wickland <lwickland@rightnow.com> - * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence - * @version Release: 1.3.3 - * @link http://pear.php.net/package/PHP_CodeSniffer - */ -class DokuWiki_Sniffs_NamingConventions_ConstructorNameSniff extends Generic_Sniffs_NamingConventions_ConstructorNameSniff -{ - /** - * Processes this test when one of its tokens is encountered. - * - * @param PHP_CodeSniffer_File $phpcsFile The current file being scanned. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * @param int $currScope A pointer to the start of the scope. - * - * @return void - */ - protected function processTokenWithinScope( - PHP_CodeSniffer_File $phpcsFile, - $stackPtr, - $currScope - ) { - $className = $phpcsFile->getDeclarationName($currScope); - $methodName = $phpcsFile->getDeclarationName($stackPtr); - - if (strcasecmp($methodName, $className) === 0) { - $error = 'PHP4 style constructors are discouraged; use "__construct()" instead'; - $phpcsFile->addWarning($error, $stackPtr, 'OldStyle'); - } else if (strcasecmp($methodName, '__construct') !== 0) { - // Not a constructor. - return; - } - - $tokens = $phpcsFile->getTokens(); - - $parentClassName = $phpcsFile->findExtendedClassName($currScope); - if ($parentClassName === false) { - return; - } - - $endFunctionIndex = $tokens[$stackPtr]['scope_closer']; - $startIndex = $stackPtr; - while ($doubleColonIndex = $phpcsFile->findNext(array(T_DOUBLE_COLON), $startIndex, $endFunctionIndex)) { - if ($tokens[($doubleColonIndex + 1)]['code'] === T_STRING - && $tokens[($doubleColonIndex + 1)]['content'] === $parentClassName - ) { - $error = 'PHP4 style calls to parent constructors are discouraged; use "parent::__construct()" instead'; - $phpcsFile->addWarning($error, ($doubleColonIndex + 1), 'OldStyleCall'); - } - - $startIndex = ($doubleColonIndex + 1); - } - - }//end processTokenWithinScope() - - -}//end class diff --git a/_cs/DokuWiki/Sniffs/PHP/DeprecatedFunctionsSniff.php b/_cs/DokuWiki/Sniffs/PHP/DeprecatedFunctionsSniff.php deleted file mode 100644 index c15a5be02..000000000 --- a/_cs/DokuWiki/Sniffs/PHP/DeprecatedFunctionsSniff.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php -/** - * DokuWiki_Sniffs_PHP_DiscouragedFunctionsSniff. - * - * PHP version 5 - * - * @category PHP - * @package PHP_CodeSniffer - * @author Greg Sherwood <gsherwood@squiz.net> - * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) - * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence - * @version CVS: $Id: DiscouragedFunctionsSniff.php 265110 2008-08-19 06:36:11Z squiz $ - * @link http://pear.php.net/package/PHP_CodeSniffer - */ - -if (class_exists('Generic_Sniffs_PHP_ForbiddenFunctionsSniff', true) === false) { - throw new PHP_CodeSniffer_Exception('Class Generic_Sniffs_PHP_ForbiddenFunctionsSniff not found'); -} - -/** - * DokuWiki_Sniffs_PHP_DiscouragedFunctionsSniff. - * - * @category PHP - * @package PHP_CodeSniffer - * @author Greg Sherwood <gsherwood@squiz.net> - * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) - * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence - * @version Release: 1.2.2 - * @link http://pear.php.net/package/PHP_CodeSniffer - */ -class DokuWiki_Sniffs_PHP_DeprecatedFunctionsSniff extends Generic_Sniffs_PHP_ForbiddenFunctionsSniff -{ - - /** - * A list of forbidden functions with their alternatives. - * - * The value is NULL if no alternative exists. IE, the - * function should just not be used. - * - * @var array(string => string|null) - */ - public $forbiddenFunctions = array( - 'setCorrectLocale' => null, - 'html_attbuild' => 'buildAttributes', - 'io_runcmd' => null, - 'p_wiki_xhtml_summary' => 'p_cached_output', - 'search_callback' => 'call_user_func_array', - 'search_backlinks' => 'ft_backlinks', - 'search_fulltext' => 'Fulltext Indexer', - 'search_regex' => 'Fulltext Indexer', - 'tpl_getFavicon' => 'tpl_getMediaFile', - 'p_cached_xhtml' => 'p_cached_output', - ); - - /** - * If true, an error will be thrown; otherwise a warning. - * - * @var bool - */ - public $error = true; - -}//end class diff --git a/_cs/DokuWiki/Sniffs/PHP/DiscouragedFunctionsSniff.php b/_cs/DokuWiki/Sniffs/PHP/DiscouragedFunctionsSniff.php deleted file mode 100644 index bd51b1166..000000000 --- a/_cs/DokuWiki/Sniffs/PHP/DiscouragedFunctionsSniff.php +++ /dev/null @@ -1,54 +0,0 @@ -<?php -/** - * DokuWiki_Sniffs_PHP_DiscouragedFunctionsSniff. - * - * PHP version 5 - * - * @category PHP - * @package PHP_CodeSniffer - * @author Greg Sherwood <gsherwood@squiz.net> - * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) - * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence - * @version CVS: $Id: DiscouragedFunctionsSniff.php 265110 2008-08-19 06:36:11Z squiz $ - * @link http://pear.php.net/package/PHP_CodeSniffer - */ - -if (class_exists('Generic_Sniffs_PHP_ForbiddenFunctionsSniff', true) === false) { - throw new PHP_CodeSniffer_Exception('Class Generic_Sniffs_PHP_ForbiddenFunctionsSniff not found'); -} - -/** - * DokuWiki_Sniffs_PHP_DiscouragedFunctionsSniff. - * - * @category PHP - * @package PHP_CodeSniffer - * @author Greg Sherwood <gsherwood@squiz.net> - * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) - * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence - * @version Release: 1.2.2 - * @link http://pear.php.net/package/PHP_CodeSniffer - */ -class DokuWiki_Sniffs_PHP_DiscouragedFunctionsSniff extends Generic_Sniffs_PHP_ForbiddenFunctionsSniff -{ - - /** - * A list of forbidden functions with their alternatives. - * - * The value is NULL if no alternative exists. IE, the - * function should just not be used. - * - * @var array(string => string|null) - */ - public $forbiddenFunctions = array( - 'date' => 'dformat', - 'strftime' => 'dformat', - ); - - /** - * If true, an error will be thrown; otherwise a warning. - * - * @var bool - */ - public $error = false; - -}//end class diff --git a/_cs/DokuWiki/Sniffs/WhiteSpace/ScopeIndentSniff.php b/_cs/DokuWiki/Sniffs/WhiteSpace/ScopeIndentSniff.php deleted file mode 100644 index 72064bda0..000000000 --- a/_cs/DokuWiki/Sniffs/WhiteSpace/ScopeIndentSniff.php +++ /dev/null @@ -1,319 +0,0 @@ -<?php -/** - * DokuWiki_Sniffs_Whitespace_ScopeIndentSniff based on - * Generic_Sniffs_Whitespace_ScopeIndentSniff. - * - * PHP version 5 - * - * @category PHP - * @package PHP_CodeSniffer - * @author Andreas Gohr <andi@splitbrain.org> - * @author Greg Sherwood <gsherwood@squiz.net> - * @author Marc McIntyre <mmcintyre@squiz.net> - * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) - * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence - * @version CVS: $Id: ScopeIndentSniff.php 270281 2008-12-02 02:38:34Z squiz $ - * @link http://pear.php.net/package/PHP_CodeSniffer - */ - -/** - * Generic_Sniffs_Whitespace_ScopeIndentSniff. - * - * Checks that control structures are structured correctly, and their content - * is indented correctly. - * - * @category PHP - * @package PHP_CodeSniffer - * @author Greg Sherwood <gsherwood@squiz.net> - * @author Marc McIntyre <mmcintyre@squiz.net> - * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) - * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence - * @version Release: 1.2.0 - * @link http://pear.php.net/package/PHP_CodeSniffer - */ -class DokuWiki_Sniffs_WhiteSpace_ScopeIndentSniff implements PHP_CodeSniffer_Sniff -{ - - /** - * The number of spaces code should be indented. - * - * @var int - */ - protected $indent = 4; - - /** - * Does the indent need to be exactly right. - * - * If TRUE, indent needs to be exactly $ident spaces. If FALSE, - * indent needs to be at least $ident spaces (but can be more). - * - * @var bool - */ - protected $exact = false; - - /** - * Any scope openers that should not cause an indent. - * - * @var array(int) - */ - protected $nonIndentingScopes = array(); - - - /** - * Returns an array of tokens this test wants to listen for. - * - * @return array - */ - public function register() - { - return PHP_CodeSniffer_Tokens::$scopeOpeners; - - }//end register() - - - /** - * Processes this test, when one of its tokens is encountered. - * - * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document. - * @param int $stackPtr The position of the current token - * in the stack passed in $tokens. - * - * @return void - */ - public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) - { - $tokens = $phpcsFile->getTokens(); - - // If this is an inline condition (ie. there is no scope opener), then - // return, as this is not a new scope. - if (isset($tokens[$stackPtr]['scope_opener']) === false) { - return; - } - - if ($tokens[$stackPtr]['code'] === T_ELSE) { - $next = $phpcsFile->findNext( - PHP_CodeSniffer_Tokens::$emptyTokens, - ($stackPtr + 1), - null, - true - ); - - // We will handle the T_IF token in another call to process. - if ($tokens[$next]['code'] === T_IF) { - return; - } - } - - // Find the first token on this line. - $firstToken = $stackPtr; - for ($i = $stackPtr; $i >= 0; $i--) { - // Record the first code token on the line. - if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === false) { - $firstToken = $i; - } - - // It's the start of the line, so we've found our first php token. - if ($tokens[$i]['column'] === 1) { - break; - } - } - - // Based on the conditions that surround this token, determine the - // indent that we expect this current content to be. - $expectedIndent = $this->calculateExpectedIndent($tokens, $firstToken); - - if ($tokens[$firstToken]['column'] !== $expectedIndent) { - if($this->exact || $tokens[$firstToken]['column'] < $expectedIndent){ - $error = 'Line indented incorrectly; expected '; - $error .= ($expectedIndent - 1).' spaces, found '; - $error .= ($tokens[$firstToken]['column'] - 1); - $phpcsFile->addError($error, $stackPtr); - }elseif((($tokens[$firstToken]['column'] - 1) % $this->indent)){ - $error = 'Line indented not by multiple of '.$this->indent.'; expected '; - $error .= ($expectedIndent - 1).' spaces, found '; - $error .= ($tokens[$firstToken]['column'] - 1); - $phpcsFile->addError($error, $stackPtr); - } - } - - $scopeOpener = $tokens[$stackPtr]['scope_opener']; - $scopeCloser = $tokens[$stackPtr]['scope_closer']; - - // Some scopes are expected not to have indents. - if (in_array($tokens[$firstToken]['code'], $this->nonIndentingScopes) === false) { - $indent = ($expectedIndent + $this->indent); - } else { - $indent = $expectedIndent; - } - - $newline = false; - $commentOpen = false; - $inHereDoc = false; - - // Only loop over the content beween the opening and closing brace, not - // the braces themselves. - for ($i = ($scopeOpener + 1); $i < $scopeCloser; $i++) { - - // If this token is another scope, skip it as it will be handled by - // another call to this sniff. - if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$scopeOpeners) === true) { - if (isset($tokens[$i]['scope_opener']) === true) { - $i = $tokens[$i]['scope_closer']; - } else { - // If this token does not have a scope_opener indice, then - // it's probably an inline scope, so let's skip to the next - // semicolon. Inline scopes include inline if's, abstract - // methods etc. - $nextToken = $phpcsFile->findNext(T_SEMICOLON, $i, $scopeCloser); - if ($nextToken !== false) { - $i = $nextToken; - } - } - - continue; - } - - // If this is a HEREDOC then we need to ignore it as the - // whitespace before the contents within the HEREDOC are - // considered part of the content. - if ($tokens[$i]['code'] === T_START_HEREDOC) { - $inHereDoc = true; - continue; - } else if ($inHereDoc === true) { - if ($tokens[$i]['code'] === T_END_HEREDOC) { - $inHereDoc = false; - } - - continue; - } - - if ($tokens[$i]['column'] === 1) { - // We started a newline. - $newline = true; - } - - if ($newline === true && $tokens[$i]['code'] !== T_WHITESPACE) { - // If we started a newline and we find a token that is not - // whitespace, then this must be the first token on the line that - // must be indented. - $newline = false; - $firstToken = $i; - - $column = $tokens[$firstToken]['column']; - - // Special case for non-PHP code. - if ($tokens[$firstToken]['code'] === T_INLINE_HTML) { - $trimmedContentLength - = strlen(ltrim($tokens[$firstToken]['content'])); - if ($trimmedContentLength === 0) { - continue; - } - - $contentLength = strlen($tokens[$firstToken]['content']); - $column = ($contentLength - $trimmedContentLength + 1); - } - - // Check to see if this constant string spans multiple lines. - // If so, then make sure that the strings on lines other than the - // first line are indented appropriately, based on their whitespace. - if (in_array($tokens[$firstToken]['code'], PHP_CodeSniffer_Tokens::$stringTokens) === true) { - if (in_array($tokens[($firstToken - 1)]['code'], PHP_CodeSniffer_Tokens::$stringTokens) === true) { - // If we find a string that directly follows another string - // then its just a string that spans multiple lines, so we - // don't need to check for indenting. - continue; - } - } - - // This is a special condition for T_DOC_COMMENT and C-style - // comments, which contain whitespace between each line. - $comments = array( - T_COMMENT, - T_DOC_COMMENT - ); - - if (in_array($tokens[$firstToken]['code'], $comments) === true) { - $content = trim($tokens[$firstToken]['content']); - if (preg_match('|^/\*|', $content) !== 0) { - // Check to see if the end of the comment is on the same line - // as the start of the comment. If it is, then we don't - // have to worry about opening a comment. - if (preg_match('|\*/$|', $content) === 0) { - // We don't have to calculate the column for the - // start of the comment as there is a whitespace - // token before it. - $commentOpen = true; - } - } else if ($commentOpen === true) { - if ($content === '') { - // We are in a comment, but this line has nothing on it - // so let's skip it. - continue; - } - - $contentLength = strlen($tokens[$firstToken]['content']); - $trimmedContentLength - = strlen(ltrim($tokens[$firstToken]['content'])); - - $column = ($contentLength - $trimmedContentLength + 1); - if (preg_match('|\*/$|', $content) !== 0) { - $commentOpen = false; - } - }//end if - }//end if - - // The token at the start of the line, needs to have its' column - // greater than the relative indent we set above. If it is less, - // an error should be shown. - if ($column !== $indent) { - if ($this->exact === true || $column < $indent) { - $error = 'Line indented incorrectly; expected '; - if ($this->exact === false) { - $error .= 'at least '; - } - - $error .= ($indent - 1).' spaces, found '; - $error .= ($column - 1); - $phpcsFile->addError($error, $firstToken); - } - } - }//end if - }//end for - - }//end process() - - - /** - * Calculates the expected indent of a token. - * - * @param array $tokens The stack of tokens for this file. - * @param int $stackPtr The position of the token to get indent for. - * - * @return int - */ - protected function calculateExpectedIndent(array $tokens, $stackPtr) - { - $conditionStack = array(); - - // Empty conditions array (top level structure). - if (empty($tokens[$stackPtr]['conditions']) === true) { - return 1; - } - - $tokenConditions = $tokens[$stackPtr]['conditions']; - foreach ($tokenConditions as $id => $condition) { - // If it's an indenting scope ie. it's not in our array of - // scopes that don't indent, add it to our condition stack. - if (in_array($condition, $this->nonIndentingScopes) === false) { - $conditionStack[$id] = $condition; - } - } - - return ((count($conditionStack) * $this->indent) + 1); - - }//end calculateExpectedIndent() - - -}//end class - -?> diff --git a/_cs/DokuWiki/ruleset.xml b/_cs/DokuWiki/ruleset.xml deleted file mode 100644 index 3ee7fb667..000000000 --- a/_cs/DokuWiki/ruleset.xml +++ /dev/null @@ -1,69 +0,0 @@ -<?xml version="1.0"?> -<ruleset name="DokuWiki"> - <description>DokuWiki Coding Standard</description> - - <!-- ignore 3rd party libraries (that we haven't adopted) --> - <exclude-pattern>*/inc/blowfish.php</exclude-pattern> - <exclude-pattern>*/inc/lessc.inc.php</exclude-pattern> - <exclude-pattern>*/inc/phpseclib/*</exclude-pattern> - <exclude-pattern>*/lib/plugins/authad/adLDAP/*</exclude-pattern> - <exclude-pattern>*/lib/scripts/fileuploader.js</exclude-pattern> - <exclude-pattern>*/lib/scripts/jquery/*</exclude-pattern> - <exclude-pattern>*/EmailAddressValidator.php</exclude-pattern> - <exclude-pattern>*/feedcreator.class.php</exclude-pattern> - <exclude-pattern>*/SimplePie.php</exclude-pattern> - <exclude-pattern>*/geshi.php</exclude-pattern> - <exclude-pattern>*/geshi/*</exclude-pattern> - <exclude-pattern>*/JSON.php</exclude-pattern> - - <!-- ignore devel only parts --> - <exclude-pattern>*/_test/*</exclude-pattern> - <exclude-pattern>*/_cs/*</exclude-pattern> - - <rule ref="Generic.Classes.DuplicateClassName" /> - <rule ref="Generic.CodeAnalysis.JumbledIncrementer" /> - <rule ref="Generic.CodeAnalysis.UnnecessaryFinalModifier" /> - <rule ref="Generic.CodeAnalysis.UnconditionalIfStatement" /> - <rule ref="Generic.CodeAnalysis.ForLoopShouldBeWhileLoop" /> - <rule ref="Generic.CodeAnalysis.ForLoopWithTestFunctionCall" /> - <rule ref="Generic.CodeAnalysis.UnusedFunctionParameter" /> - <rule ref="Generic.CodeAnalysis.EmptyStatement" /> - <rule ref="Generic.CodeAnalysis.UselessOverridingMethod" /> - <rule ref="Generic.Commenting.Todo" /> - <rule ref="Generic.Files.ByteOrderMark" /> - <rule ref="Generic.Files.LineEndings" /> - <rule ref="Generic.Formatting.DisallowMultipleStatements" /> - <rule ref="Generic.Metrics.NestingLevel"> - <properties> - <property name="nestingLevel" value="6" /> - </properties> - </rule> - <rule ref="Generic.NamingConventions.UpperCaseConstantName" /> - <rule ref="Generic.PHP.LowerCaseConstant" /> - <rule ref="Generic.PHP.DeprecatedFunctions.php" /> - <rule ref="Generic.PHP.DisallowShortOpenTag" /> - <rule ref="Generic.PHP.ForbiddenFunctions" /> - <rule ref="Generic.WhiteSpace.DisallowTabIndent" /> - <rule ref="Generic.Classes.DuplicateClassName" /> - <rule ref="Generic.Functions.CallTimePassByReference" /> - <rule ref="Zend.Files.ClosingTag" /> - <rule ref="PEAR.Functions.ValidDefaultValue" /> - <rule ref="Squiz.PHP.Eval" /> - <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace" /> - <rule ref="Squiz.CSS.LowercaseStyleDefinition" /> - <rule ref="Squiz.CSS.MissingColon" /> - <rule ref="Squiz.CSS.DisallowMultipleStyleDefinitions" /> - <rule ref="Squiz.CSS.ColonSpacing" /> - <rule ref="Squiz.CSS.ClassDefinitionClosingBraceSpace" /> - <rule ref="Squiz.CSS.SemicolonSpacing" /> - <rule ref="Squiz.CSS.Indentation" /> - <rule ref="Squiz.CSS.EmptyClassDefinition" /> - <rule ref="Squiz.CSS.ClassDefinitionNameSpacing" /> - <rule ref="Squiz.CSS.EmptyStyleDefinition" /> - <rule ref="Squiz.CSS.Opacity" /> - <rule ref="Squiz.CSS.ColourDefinition" /> - <rule ref="Squiz.CSS.DuplicateClassDefinition" /> - <rule ref="Squiz.CSS.ClassDefinitionOpeningBraceSpace" /> - <rule ref="Squiz.Commenting.DocCommentAlignment" /> - -</ruleset> diff --git a/_cs/README b/_cs/README deleted file mode 100644 index 7aac73161..000000000 --- a/_cs/README +++ /dev/null @@ -1,18 +0,0 @@ -This directory contains the Coding Standard tests to be used with PHP -CodeSniffer on DokuWiki's code. - -1. Install PHP CodeSniffer: - - #> pear install PHP_CodeSniffer - -2. Link the Coding Standard to the CodeSniffer directory: - - #> ln -s /path/to/dokuwiki/_cs/DokuWiki /usr/share/pear/PHP/CodeSniffer/Standards/DokuWiki - -3. Set DokuWiki to be the default standard: - - #> phpcs --config-set default_standard DokuWiki - - - -The coding standard is work in progress. diff --git a/_test/core/DokuWikiTest.php b/_test/core/DokuWikiTest.php index 80fdb4ac0..6e8049450 100644 --- a/_test/core/DokuWikiTest.php +++ b/_test/core/DokuWikiTest.php @@ -1,4 +1,9 @@ <?php + +use dokuwiki\Extension\PluginController; +use dokuwiki\Extension\Event; +use dokuwiki\Extension\EventHandler; + if(!class_exists('PHPUnit_Framework_TestCase')) { /** * phpunit 5/6 compatibility @@ -103,7 +108,7 @@ abstract class DokuWikiTest extends PHPUnit_Framework_TestCase { // reset loaded plugins global $plugin_controller_class, $plugin_controller; - /** @var Doku_Plugin_Controller $plugin_controller */ + /** @var PluginController $plugin_controller */ $plugin_controller = new $plugin_controller_class(); // disable all non-default plugins @@ -133,14 +138,14 @@ abstract class DokuWikiTest extends PHPUnit_Framework_TestCase { // reset event handler global $EVENT_HANDLER; - $EVENT_HANDLER = new Doku_Event_Handler(); + $EVENT_HANDLER = new EventHandler(); // reload language $local = $conf['lang']; - trigger_event('INIT_LANG_LOAD', $local, 'init_lang', true); + Event::createAndTrigger('INIT_LANG_LOAD', $local, 'init_lang', true); global $INPUT; - $INPUT = new Input(); + $INPUT = new \dokuwiki\Input\Input(); } /** @@ -234,4 +239,41 @@ abstract class DokuWikiTest extends PHPUnit_Framework_TestCase { $method->setAccessible(true); return $method->invokeArgs($obj, $args); } + + /** + * Allow for reading inaccessible properties (private or protected) + * + * This makes it easier to check internals of tested objects. This should generally + * be avoided. + * + * @param object $obj Object on which to access the property + * @param string $prop name of the property to access + * @return mixed + * @throws ReflectionException when the given obj/prop does not exist + */ + protected static function getInaccessibleProperty($obj, $prop) { + $class = new \ReflectionClass($obj); + $property = $class->getProperty($prop); + $property->setAccessible(true); + return $property->getValue($obj); + } + + /** + * Allow for reading inaccessible properties (private or protected) + * + * This makes it easier to set internals of tested objects. This should generally + * be avoided. + * + * @param object $obj Object on which to access the property + * @param string $prop name of the property to access + * @param mixed $value new value to set the property to + * @return void + * @throws ReflectionException when the given obj/prop does not exist + */ + protected static function setInaccessibleProperty($obj, $prop, $value) { + $class = new \ReflectionClass($obj); + $property = $class->getProperty($prop); + $property->setAccessible(true); + $property->setValue($obj, $value); + } } diff --git a/_test/core/TestRequest.php b/_test/core/TestRequest.php index fcc80328c..23ca76eec 100644 --- a/_test/core/TestRequest.php +++ b/_test/core/TestRequest.php @@ -4,6 +4,8 @@ * runtime inspection. */ +use dokuwiki\Input\Input; + /** * Helper class to execute a fake request */ diff --git a/_test/mock/AuthPlugin.php b/_test/mock/AuthPlugin.php new file mode 100644 index 000000000..53edb2fe1 --- /dev/null +++ b/_test/mock/AuthPlugin.php @@ -0,0 +1,10 @@ +<?php + +namespace dokuwiki\test\mock; + +/** + * Class dokuwiki\Plugin\DokuWiki_Auth_Plugin + */ +class AuthPlugin extends \dokuwiki\Extension\AuthPlugin { + +} diff --git a/_test/mock/Doku_Renderer.php b/_test/mock/Doku_Renderer.php new file mode 100644 index 000000000..350346101 --- /dev/null +++ b/_test/mock/Doku_Renderer.php @@ -0,0 +1,11 @@ +<?php + +namespace dokuwiki\test\mock; + +class Doku_Renderer extends \Doku_Renderer { + + /** @inheritdoc */ + public function getFormat() { + return 'none'; + } +} diff --git a/_test/mock/MailerMock.php b/_test/mock/MailerMock.php new file mode 100644 index 000000000..9d93bc25a --- /dev/null +++ b/_test/mock/MailerMock.php @@ -0,0 +1,16 @@ +<?php + +namespace dokuwiki\test\mock; + +class MailerMock extends \Mailer +{ + + public $mails = []; + + public function send() + { + $this->mails[] = $this->headers; + return true; + } + +} diff --git a/_test/phpcs.xml b/_test/phpcs.xml new file mode 100644 index 000000000..4f38195a3 --- /dev/null +++ b/_test/phpcs.xml @@ -0,0 +1,98 @@ +<?xml version="1.0"?> +<ruleset name="DokuWiki Coding Standard Standard" namespace="DokuWiki\CS\Standard"> + <description>Coding Standard used for DokuWiki</description> + + <!-- default config --> + <arg name="colors"/> + <arg value="sp"/> + <arg name="extensions" value="php"/> + + <ini name="memory_limit" value="-1"/> + + <!-- where to look --> + <file>../inc</file> + <file>../lib</file> + <file>../bin</file> + <file>../doku.php</file> + <file>../index.php</file> + <file>../feed.php</file> + <file>../install.php</file> + + <!-- skip these completely --> + <exclude-pattern>*/lang/*/lang.php</exclude-pattern> + <exclude-pattern>*/lang/*/settings.php</exclude-pattern> + <exclude-pattern>*/_test/*</exclude-pattern> + + <!-- 3rd party libs, these should be moved to composer some day --> + <exclude-pattern>*/inc/DifferenceEngine.php</exclude-pattern> + <exclude-pattern>*/inc/IXR_Library.php</exclude-pattern> + <exclude-pattern>*/inc/JSON.php</exclude-pattern> + <exclude-pattern>*/inc/JpegMeta.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/authad/adLDAP</exclude-pattern> + + <!-- deprecated files to be removed soon --> + <exclude-pattern>*/inc/cli.php</exclude-pattern> + <exclude-pattern>*/inc/parser/*</exclude-pattern> + + <!-- rules on top of PSR-2 --> + <rule ref="PSR2"> + <!-- the following rule is not in PSR-2 and breaks the guardian pattern --> + <exclude name="Generic.ControlStructures.InlineControlStructure.NotAllowed"/> + + <!-- we have lots of legacy classes without name spaces --> + <exclude name="PSR1.Classes.ClassDeclaration.MissingNamespace"/> + </rule> + + <!-- disable some rules for certain paths, for legacy support --> + <rule ref="Squiz.Classes.ValidClassName.NotCamelCaps"> + <exclude-pattern>*/inc/Plugin.php</exclude-pattern> + <exclude-pattern>*/inc/PluginInterface.php</exclude-pattern> + <exclude-pattern>*/inc/PluginTrait.php</exclude-pattern> + + <exclude-pattern>*/lib/plugins/*.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/action.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/action/*.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/admin.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/admin/*.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/auth.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/auth/*.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/cli.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/cli/*.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/helper.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/helper/*.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/remote.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/remote/*.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/syntax.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/syntax/*.php</exclude-pattern> + </rule> + + <!-- underscore skips exposing public methods to remote api --> + <rule ref="PSR2.Methods.MethodDeclaration.Underscore"> + <exclude-pattern>*/inc/Extension/RemotePlugin.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/remote.php</exclude-pattern> + <exclude-pattern>*/lib/plugins/*/remote/*.php</exclude-pattern> + </rule> + + <rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps"> + <exclude-pattern>*/inc/Extension/PluginInterface.php</exclude-pattern> + <exclude-pattern>*/inc/Extension/PluginTrait.php</exclude-pattern> + </rule> + + <!-- for now we mix declarations and execution here (mostly for defines) --> + <rule ref="PSR1.Files.SideEffects"> + <exclude-pattern>*/index.php</exclude-pattern> + <exclude-pattern>*/inc/parserutils.php</exclude-pattern> + <exclude-pattern>*/inc/mail.php</exclude-pattern> + <exclude-pattern>*/inc/init.php</exclude-pattern> + <exclude-pattern>*/inc/fulltext.php</exclude-pattern> + <exclude-pattern>*/inc/Mailer.class.php</exclude-pattern> + <exclude-pattern>*/doku.php</exclude-pattern> + <exclude-pattern>*/install.php</exclude-pattern> + <exclude-pattern>*/inc/utf8.php</exclude-pattern> + <exclude-pattern>*/feed.php</exclude-pattern> + <exclude-pattern>*/inc/load.php</exclude-pattern> + <exclude-pattern>*/bin/*.php</exclude-pattern> + <exclude-pattern>*/lib/exe/*.php</exclude-pattern> + </rule> + +</ruleset> diff --git a/_test/phpunit.xml b/_test/phpunit.xml index f4b88be9c..c450cea95 100644 --- a/_test/phpunit.xml +++ b/_test/phpunit.xml @@ -10,12 +10,15 @@ <testsuites> <testsuite name="DokuWiki Tests"> <directory suffix=".test.php">tests/</directory> + <directory suffix="Test.php">tests/</directory> </testsuite> <testsuite name="Plugin Tests"> - <directory suffix=".test.php">../lib/plugins/*/_test</directory> + <directory suffix=".test.php">../lib/plugins/*/_test/</directory> + <directory suffix="Test.php">../lib/plugins/*/_test/</directory> </testsuite> <testsuite name="Template Tests"> - <directory suffix=".test.php">../lib/tpl/*/_test</directory> + <directory suffix=".test.php">../lib/tpl/*/_test/</directory> + <directory suffix="Test.php">../lib/tpl/*/_test/</directory> </testsuite> </testsuites> diff --git a/_test/tests/inc/PageUtilsIsHiddenPage.test.php b/_test/tests/inc/PageUtilsIsHiddenPage.test.php index a7077862e..09ab78e1b 100644 --- a/_test/tests/inc/PageUtilsIsHiddenPage.test.php +++ b/_test/tests/inc/PageUtilsIsHiddenPage.test.php @@ -41,7 +41,7 @@ class PageUtilsIsHiddenPageTest extends DokuWikiTest { $this->assertTrue(isHiddenPage('another')); } - function alwaysHide(Doku_Event &$event, $params) { + function alwaysHide(Doku_Event $event, $params) { $event->data['hidden'] = true; } @@ -53,7 +53,7 @@ class PageUtilsIsHiddenPageTest extends DokuWikiTest { $this->assertFalse(isHiddenPage('test')); } - function showBefore(Doku_Event &$event, $params) { + function showBefore(Doku_Event $event, $params) { $event->data['hidden'] = false; $event->preventDefault(); $event->stopPropagation(); @@ -75,7 +75,7 @@ class PageUtilsIsHiddenPageTest extends DokuWikiTest { $this->assertTrue(isHiddenPage('another')); } - function hideBeforeWithoutPrevent(Doku_Event &$event, $params) { + function hideBeforeWithoutPrevent(Doku_Event $event, $params) { $event->data['hidden'] = true; } @@ -87,7 +87,7 @@ class PageUtilsIsHiddenPageTest extends DokuWikiTest { $this->assertFalse(isHiddenPage('test')); } - function showAfter(Doku_Event &$event, $params) { + function showAfter(Doku_Event $event, $params) { $event->data['hidden'] = false; } diff --git a/_test/tests/inc/PassHash.test.php b/_test/tests/inc/PassHash.test.php index 1bc4b95bc..2c8dc1c39 100644 --- a/_test/tests/inc/PassHash.test.php +++ b/_test/tests/inc/PassHash.test.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\PassHash; + /** * Class PassHash_test * diff --git a/_test/tests/inc/Subscriptions/BulkSubscriptionsSenderTest.php b/_test/tests/inc/Subscriptions/BulkSubscriptionsSenderTest.php new file mode 100644 index 000000000..3bc6a9c2f --- /dev/null +++ b/_test/tests/inc/Subscriptions/BulkSubscriptionsSenderTest.php @@ -0,0 +1,110 @@ +<?php + +namespace tests\inc\Subscriptions; + +use dokuwiki\Subscriptions\BulkSubscriptionSender; +use dokuwiki\Subscriptions\SubscriberManager; +use dokuwiki\test\mock\MailerMock; +use DokuWikiTest; + +class BulkSubscriptionsSenderTest extends DokuWikiTest +{ + + private $originalSubscriptionConfig; + + public function setUp() + { + parent::setUp(); + global $conf; + $this->originalSubscriptionConfig = $conf['subscribers']; + $conf['subscribers'] = true; + } + + protected function tearDown() + { + global $conf; + $conf['subscribers'] = $this->originalSubscriptionConfig; + parent::tearDown(); + } + + public function testBulkdigest() + { + $mailerMock = new MailerMock(); + $sub = new BulkSubscriptionSender($mailerMock); + $manager = new SubscriberManager(); + + // let's start with nothing + $this->assertEquals(0, $sub->sendBulk('sub1:test')); + + // create a subscription + $manager->add('sub1:', 'testuser', 'digest', '978328800'); // last mod 2001-01-01 + + // now create change + $_SERVER['REMOTE_USER'] = 'someguy'; + saveWikiText('sub1:test', 'foo bar', 'a subscription change', false); + + // should trigger a mail + $this->assertEquals(1, $sub->sendBulk('sub1:test')); + $this->assertEquals(['arthur@example.com'], array_column($mailerMock->mails, 'Bcc')); + + $mailerMock->mails = []; + + // now create more changes + $_SERVER['REMOTE_USER'] = 'someguy'; + saveWikiText('sub1:sub2:test', 'foo bar', 'a subscription change', false); + saveWikiText('sub1:another_test', 'foo bar', 'a subscription change', false); + + // should not trigger a mail, because the subscription time has not been reached, yet + $this->assertEquals(0, $sub->sendBulk('sub1:test')); + $this->assertEquals([], array_column($mailerMock->mails, 'Bcc')); + + // reset the subscription time + $manager->add('sub1:', 'testuser', 'digest', '978328800'); // last mod 2001-01-01 + + // we now should get mails for three changes + $this->assertEquals(3, $sub->sendBulk('sub1:test')); + $this->assertEquals( + ['arthur@example.com', 'arthur@example.com', 'arthur@example.com'], + array_column($mailerMock->mails, 'Bcc') + ); + } + + public function testBulklist() + { + $mailerMock = new MailerMock(); + $sub = new BulkSubscriptionSender($mailerMock); + $manager = new SubscriberManager(); + + // let's start with nothing + $this->assertEquals(0, $sub->sendBulk('sub1:test')); + + // create a subscription + $manager->add('sub1:', 'testuser', 'list', '978328800'); // last mod 2001-01-01 + + // now create change + $_SERVER['REMOTE_USER'] = 'someguy'; + saveWikiText('sub1:test', 'foo bar', 'a subscription change', false); + + // should trigger a mail + $this->assertEquals(1, $sub->sendBulk('sub1:test')); + $this->assertEquals(['arthur@example.com'], array_column($mailerMock->mails, 'Bcc')); + + $mailerMock->mails = []; + + // now create more changes + $_SERVER['REMOTE_USER'] = 'someguy'; + saveWikiText('sub1:sub2:test', 'foo bar', 'a subscription change', false); + saveWikiText('sub1:another_test', 'foo bar', 'a subscription change', false); + + // should not trigger a mail, because the subscription time has not been reached, yet + $this->assertEquals(0, $sub->sendBulk('sub1:test')); + $this->assertEquals([], array_column($mailerMock->mails, 'Bcc')); + + // reset the subscription time + $manager->add('sub1:', 'testuser', 'list', '978328800'); // last mod 2001-01-01 + + // we now should get a single mail for all three changes + $this->assertEquals(1, $sub->sendBulk('sub1:test')); + $this->assertEquals(['arthur@example.com'], array_column($mailerMock->mails, 'Bcc')); + } +} diff --git a/_test/tests/inc/Subscriptions/SubscriberManagerTest.php b/_test/tests/inc/Subscriptions/SubscriberManagerTest.php new file mode 100644 index 000000000..1fcd7c84b --- /dev/null +++ b/_test/tests/inc/Subscriptions/SubscriberManagerTest.php @@ -0,0 +1,131 @@ +<?php + +namespace tests\inc\Subscriptions; + +use dokuwiki\Subscriptions\SubscriberManager; +use DokuWikiTest; + +class SubscriberManagerTest extends DokuWikiTest +{ + private $originalSubscriptionConfig; + + public function setUp() + { + parent::setUp(); + global $conf; + $this->originalSubscriptionConfig = $conf['subscribers']; + $conf['subscribers'] = true; + } + + protected function tearDown() + { + global $conf; + $conf['subscribers'] = $this->originalSubscriptionConfig; + parent::tearDown(); + } + + public function testAddremove() + { + $sub = new SubscriberManager(); + + // no subscriptions + $this->assertArrayNotHasKey( + 'wiki:dokuwiki', + $sub->subscribers('wiki:dokuwiki', null, ['every', 'list', 'digest']) + ); + + // add page subscription + $sub->add('wiki:dokuwiki', 'testuser', 'every'); + + // one subscription + $this->assertArrayHasKey( + 'wiki:dokuwiki', + $sub->subscribers('wiki:dokuwiki', null, ['every', 'list', 'digest']) + ); + + // remove page subscription + $sub->remove('wiki:dokuwiki', 'testuser'); + + // no subscription + $this->assertArrayNotHasKey( + 'wiki:dokuwiki', + $sub->subscribers('wiki:dokuwiki', null, ['every', 'list', 'digest']) + ); + + // add namespace subscription + $sub->add('wiki:', 'testuser', 'every'); + + // one subscription + $this->assertArrayHasKey( + 'wiki:', + $sub->subscribers('wiki:dokuwiki', null, ['every', 'list', 'digest']) + ); + + // remove (non existing) page subscription + $sub->remove('wiki:dokuwiki', 'testuser'); + + // still one subscription + $this->assertArrayHasKey( + 'wiki:', + $sub->subscribers('wiki:dokuwiki', null, ['every', 'list', 'digest']) + ); + + // change namespace subscription + $sub->add('wiki:', 'testuser', 'digest', '1234567'); + + // still one subscription + $this->assertArrayHasKey( + 'wiki:', + $sub->subscribers('wiki:dokuwiki', null, ['every', 'list', 'digest']) + ); + + // check contents + $this->assertEquals( + ['wiki:' => ['testuser' => ['digest', '1234567']]], + $sub->subscribers('wiki:dokuwiki', null, ['every', 'list', 'digest']) + ); + + // change subscription data + $sub->add('wiki:', 'testuser', 'digest', '7654321'); + + // still one subscription + $this->assertArrayHasKey( + 'wiki:', + $sub->subscribers('wiki:dokuwiki', null, ['every', 'list', 'digest']) + ); + + // check contents + $this->assertEquals( + ['wiki:' => ['testuser' => ['digest', '7654321']]], + $sub->subscribers('wiki:dokuwiki', null, ['every', 'list', 'digest']) + ); + } + + /** + * Tests, if overwriting subscriptions works even when subscriptions for the same + * user exist for two nested namespaces, this is a test for the bug described in FS#2580 + */ + public function testOverwrite() + { + $sub = new SubscriberManager(); + + $sub->add(':', 'admin', 'digest', '123456789'); + $sub->add(':wiki:', 'admin', 'digest', '123456789'); + $sub->add(':', 'admin', 'digest', '1234'); + $sub->add(':wiki:', 'admin', 'digest', '1234'); + + $subscriptions = $sub->subscribers(':wiki:', 'admin'); + + $this->assertCount( + 1, + $subscriptions[':'], + 'More than one subscription saved for the root namespace even though the old one should have been overwritten.' + ); + $this->assertCount( + 1, + $subscriptions[':wiki:'], + 'More than one subscription saved for the wiki namespace even though the old one should have been overwritten.' + ); + $this->assertCount(2, $subscriptions, 'Didn\'t find the expected two subscriptions'); + } +} diff --git a/_test/tests/inc/Subscriptions/SubscriberRegexBuilderTest.php b/_test/tests/inc/Subscriptions/SubscriberRegexBuilderTest.php new file mode 100644 index 000000000..f9b22724d --- /dev/null +++ b/_test/tests/inc/Subscriptions/SubscriberRegexBuilderTest.php @@ -0,0 +1,52 @@ +<?php + +namespace tests\inc\Subscriptions; + +use dokuwiki\Subscriptions\SubscriberRegexBuilder; +use DokuWikiTest; + +class SubscriberRegexBuilderTest extends DokuWikiTest +{ + + public function regexTestdataProvider() + { + return [ + ['Cold Fusion', null, null, 1], + ['casper', null, null, 1], + ['nope', null, null, 0], + ['lights', null, null, 0], + [['Cold Fusion', 'casper', 'nope'], null, null, 2], + [null, 'list', null, 1], + [null, 'every', null, 2], + [null, 'digest', null, 3], + [null, ['list', 'every'], null, 3], + ['casper', 'digest', null, 0], + ['casper', ['digest', 'every'], null, 1], + ['zioth', 'list', '1344691369', 1], + ['zioth', null, '1344691369', 1], + ['zioth', 'digest', '1344691369', 0], + ]; + } + + /** + * @dataProvider regexTestdataProvider + */ + public function testRegexp($user, $style, $inputData, $expectedNumResults) + { + // data to test against + $data = [ + "casper every\n", + "Andreas digest 1344689733", + "Cold%20Fusion every", + "zioth list 1344691369\n", + "nlights digest", + "rikblok\tdigest \t 1344716803", + ]; + + $sub = new SubscriberRegexBuilder(); + $re = $sub->buildRegex($user, $style, $inputData); + $this->assertFalse(empty($re)); + $result = preg_grep($re, $data); + $this->assertEquals($expectedNumResults, count($result)); + } +} diff --git a/_test/tests/inc/auth_aclcheck.test.php b/_test/tests/inc/auth_aclcheck.test.php index 8c9b37536..2b5342e43 100644 --- a/_test/tests/inc/auth_aclcheck.test.php +++ b/_test/tests/inc/auth_aclcheck.test.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\test\mock\AuthPlugin; + class auth_acl_test extends DokuWikiTest { protected $oldAuthAcl; @@ -9,7 +11,7 @@ class auth_acl_test extends DokuWikiTest { global $AUTH_ACL; global $auth; $this->oldAuthAcl = $AUTH_ACL; - $auth = new DokuWiki_Auth_Plugin(); + $auth = new AuthPlugin(); } function tearDown() { diff --git a/_test/tests/inc/auth_aclcheck_caseinsensitive.test.php b/_test/tests/inc/auth_aclcheck_caseinsensitive.test.php index 21b2cfdb0..644675de4 100644 --- a/_test/tests/inc/auth_aclcheck_caseinsensitive.test.php +++ b/_test/tests/inc/auth_aclcheck_caseinsensitive.test.php @@ -1,6 +1,8 @@ <?php -class auth_acl_caseinsensitive_auth extends DokuWiki_Auth_Plugin { +use dokuwiki\Extension\AuthPlugin; + +class auth_acl_caseinsensitive_auth extends AuthPlugin { function isCaseSensitive() { return false; } diff --git a/_test/tests/inc/auth_admincheck.test.php b/_test/tests/inc/auth_admincheck.test.php index 087be3810..82ddafcff 100644 --- a/_test/tests/inc/auth_admincheck.test.php +++ b/_test/tests/inc/auth_admincheck.test.php @@ -1,6 +1,8 @@ <?php -class auth_admin_test_AuthInSensitive extends DokuWiki_Auth_Plugin { +use dokuwiki\test\mock\AuthPlugin; + +class auth_admin_test_AuthInSensitive extends AuthPlugin { function isCaseSensitive(){ return false; } @@ -18,7 +20,7 @@ class auth_admin_test extends DokuWikiTest { function setSensitive() { global $auth; - $auth = new DokuWiki_Auth_Plugin(); + $auth = new AuthPlugin(); } function setInSensitive() { diff --git a/_test/tests/inc/auth_deleteprofile.test.php b/_test/tests/inc/auth_deleteprofile.test.php index dc38fcd16..2195ee97d 100644 --- a/_test/tests/inc/auth_deleteprofile.test.php +++ b/_test/tests/inc/auth_deleteprofile.test.php @@ -1,11 +1,14 @@ <?php -class Mock_Auth_Plugin extends DokuWiki_Auth_Plugin { +use dokuwiki\Input\Input; +use dokuwiki\Extension\AuthPlugin; - public $loggedOff = false; +class Mock_Auth_Plugin extends AuthPlugin { + + public $loggedOff = false; public function __construct($canDeleteUser = true) { - $this->cando['delUser'] = $canDeleteUser; + $this->cando['delUser'] = $canDeleteUser; } public function checkPass($user, $pass) { @@ -13,11 +16,11 @@ class Mock_Auth_Plugin extends DokuWiki_Auth_Plugin { } public function deleteUsers($users) { - return in_array($_SERVER['REMOTE_USER'], $users); + return in_array($_SERVER['REMOTE_USER'], $users); } public function logoff() { - $this->loggedOff = true; + $this->loggedOff = true; } } @@ -27,7 +30,7 @@ class auth_deleteprofile_test extends DokuWikiTest { /* * Tests: * - * 1. It works and the user is logged off + * 1. It works and the user is logged off * 2. Password matches when config requires it * 3,4. Auth plugin can prevent & wiki config can prevent * 5. Any of invalid security token, missing/not set 'delete' flag, missing/unchecked 'confirm_delete' @@ -176,4 +179,4 @@ class auth_deleteprofile_test extends DokuWikiTest { $INPUT->remove('confirm_delete'); $this->assertFalse(auth_deleteprofile()); } -}
\ No newline at end of file +} diff --git a/_test/tests/inc/cache_stalecheck.test.php b/_test/tests/inc/cache_stalecheck.test.php index 93f44a55c..2172d9930 100644 --- a/_test/tests/inc/cache_stalecheck.test.php +++ b/_test/tests/inc/cache_stalecheck.test.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\Cache\CacheRenderer; + class cache_stalecheck_test extends DokuWikiTest { function test_staleness() { global $ID; @@ -11,7 +13,7 @@ class cache_stalecheck_test extends DokuWikiTest { saveWikiText($ID, 'Fresh', 'Created'); # Create stale cache - $cache = new cache_renderer($ID, $file, 'xhtml'); + $cache = new CacheRenderer($ID, $file, 'xhtml'); $cache->storeCache('Stale'); $stale = $cache->retrieveCache(); diff --git a/_test/tests/inc/cache_use.test.php b/_test/tests/inc/cache_use.test.php index c0c12580a..3a1028d13 100644 --- a/_test/tests/inc/cache_use.test.php +++ b/_test/tests/inc/cache_use.test.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\Cache\CacheRenderer; + /** * Class cache_use_test * @@ -8,7 +10,7 @@ * @todo tests marked as flaky until Ticket #694 has been fixed */ class cache_use_test extends DokuWikiTest { - /** @var cache_renderer $cache */ + /** @var CacheRenderer $cache */ private $cache; function setUp() { @@ -21,7 +23,7 @@ class cache_use_test extends DokuWikiTest { saveWikiText($ID, 'Content', 'Created'); - $this->cache = new cache_renderer($ID, $file, 'xhtml'); + $this->cache = new CacheRenderer($ID, $file, 'xhtml'); $this->cache->storeCache('Test'); // set the modification times explicitly (overcome Issue #694) @@ -76,6 +78,6 @@ class cache_use_test extends DokuWikiTest { $conf['cachetime'] = -1; // disables renderer caching $this->assertFalse($this->cache->useCache()); - $this->assertNotEmpty($this->cache->_nocache); + $this->assertNotEmpty($this->cache->isNoCache()); } } diff --git a/_test/tests/inc/changelog_getRevisionsAround.test.php b/_test/tests/inc/changelog_getRevisionsAround.test.php index 2a5cb849e..ad6d3afcf 100644 --- a/_test/tests/inc/changelog_getRevisionsAround.test.php +++ b/_test/tests/inc/changelog_getRevisionsAround.test.php @@ -1,4 +1,7 @@ <?php + +use dokuwiki\ChangeLog\PageChangeLog; + /** * Tests for requesting revisions of a page with getRevisions() * @@ -185,4 +188,4 @@ class changelog_getrevisionsaround_test extends DokuWikiTest { $this->assertEquals($revsexpected, $revs); } -}
\ No newline at end of file +} diff --git a/_test/tests/inc/changelog_getlastrevisionat.test.php b/_test/tests/inc/changelog_getlastrevisionat.test.php index 4ab7900b6..cec54f6db 100644 --- a/_test/tests/inc/changelog_getlastrevisionat.test.php +++ b/_test/tests/inc/changelog_getlastrevisionat.test.php @@ -1,5 +1,8 @@ <?php +use dokuwiki\ChangeLog\MediaChangeLog; +use dokuwiki\ChangeLog\PageChangeLog; + /** * Tests for requesting revisioninfo of a revision of a page with getRevisionInfo() * @@ -10,7 +13,7 @@ class changelog_getlastrevisionat_test extends DokuWikiTest { private $pageid = 'mailinglist'; - + function setup() { parent::setup(); global $cache_revinfo; @@ -22,7 +25,7 @@ class changelog_getlastrevisionat_test extends DokuWikiTest { unset($cache['mailinglist']); } } - + /** * no nonexist.changes meta file available @@ -53,7 +56,7 @@ class changelog_getlastrevisionat_test extends DokuWikiTest { $this->assertEquals($revsexpected, $revs); } - + /** * test a future revision * @@ -64,7 +67,7 @@ class changelog_getlastrevisionat_test extends DokuWikiTest { //set a known timestamp touch(wikiFN($this->pageid), $rev); - + $rev +=1; $pagelog = new PageChangeLog($this->pageid, $chunk_size = 8192); @@ -89,7 +92,7 @@ class changelog_getlastrevisionat_test extends DokuWikiTest { /** * Request not existing revision - * + * */ function test_olderrev() { $rev = 1; @@ -124,7 +127,7 @@ class changelog_getlastrevisionat_test extends DokuWikiTest { $current = $pagelog->getLastRevisionAt($rev); $this->assertEquals($currentexpected, $current); } - + /** * test get correct revision on deleted media * @@ -154,13 +157,13 @@ class changelog_getlastrevisionat_test extends DokuWikiTest { $ret = media_delete($image, 0); - $medialog = new MediaChangelog($image); + $medialog = new MediaChangeLog($image); $current = $medialog->getLastRevisionAt($rev); // as we wait for a tick, we should get something greater than the timestamp $this->assertGreaterThan($revexpected, $current); // however, it should be less than the current time or equal to it $this->assertLessThanOrEqual(time(), $current); - + //restore settings $_SERVER['REMOTE_USER'] = $oldRemoteUser; $conf['superuser'] = $oldSuperUser; diff --git a/_test/tests/inc/changelog_getrelativerevision.test.php b/_test/tests/inc/changelog_getrelativerevision.test.php index f9962066a..49d7a0915 100644 --- a/_test/tests/inc/changelog_getrelativerevision.test.php +++ b/_test/tests/inc/changelog_getrelativerevision.test.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\ChangeLog\PageChangeLog; + /** * Tests for requesting revisioninfo of a revision of a page with getRevisionInfo() * @@ -415,4 +417,4 @@ class changelog_getrelativerevision_test extends DokuWikiTest { $current = $pagelog->isCurrentRevision($rev); $this->assertEquals($currentexpected, $current); } -}
\ No newline at end of file +} diff --git a/_test/tests/inc/changelog_getrevisioninfo.test.php b/_test/tests/inc/changelog_getrevisioninfo.test.php index 79b31d68e..2d7ad131a 100644 --- a/_test/tests/inc/changelog_getrevisioninfo.test.php +++ b/_test/tests/inc/changelog_getrevisioninfo.test.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\ChangeLog\PageChangeLog; + /** * Tests for requesting revisioninfo of a revision of a page with getRevisionInfo() * @@ -137,4 +139,4 @@ class changelog_getrevisionsinfo_test extends DokuWikiTest { $info = $pagelog->getRevisionInfo($rev); $this->assertEquals($infoexpected, $info); } -}
\ No newline at end of file +} diff --git a/_test/tests/inc/changelog_getrevisions.test.php b/_test/tests/inc/changelog_getrevisions.test.php index b247ce3d6..e29b80997 100644 --- a/_test/tests/inc/changelog_getrevisions.test.php +++ b/_test/tests/inc/changelog_getrevisions.test.php @@ -1,4 +1,7 @@ <?php + +use dokuwiki\ChangeLog\PageChangeLog; + /** * Tests for requesting revisions of a page with getRevisions() * @@ -227,4 +230,4 @@ class changelog_getrevisions_test extends DokuWikiTest { $this->assertEquals($revsexpected, $revs); } -}
\ No newline at end of file +} diff --git a/_test/tests/inc/common_ml.test.php b/_test/tests/inc/common_ml.test.php index 027dcaef2..853c634b4 100644 --- a/_test/tests/inc/common_ml.test.php +++ b/_test/tests/inc/common_ml.test.php @@ -124,7 +124,7 @@ class common_ml_test extends DokuWikiTest { foreach($ids as $id) { $tok = media_get_token($id, $w, 0); - $hash = substr(PassHash::hmac('md5', $id, auth_cookiesalt()), 0, 6); + $hash = substr(\dokuwiki\PassHash::hmac('md5', $id, auth_cookiesalt()), 0, 6); $expect = DOKU_BASE.$this->script.'?w='.$w.'&tok='.$tok.'&media='.rawurlencode($id); $this->assertEquals($expect, ml($id, $args)); diff --git a/_test/tests/inc/common_saveWikiText.test.php b/_test/tests/inc/common_saveWikiText.test.php index fe83caabd..7ae12ca27 100644 --- a/_test/tests/inc/common_saveWikiText.test.php +++ b/_test/tests/inc/common_saveWikiText.test.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\ChangeLog\PageChangeLog; + class common_saveWikiText_test extends DokuWikiTest { /** Delay writes of old revisions by a second. */ public function handle_write(Doku_Event $event, $param) { @@ -166,7 +168,7 @@ class common_saveWikiText_test extends DokuWikiTest { function test_savesequencedeleteexternalrevision() { // add an additional delay when saving files to make sure // nobody relies on the saving happening in the same second - /** @var $EVENT_HANDLER Doku_Event_Handler */ + /** @var $EVENT_HANDLER \dokuwiki\Extension\EventHandler */ global $EVENT_HANDLER; $EVENT_HANDLER->register_hook('IO_WIKIPAGE_WRITE', 'BEFORE', $this, 'handle_write'); diff --git a/_test/tests/inc/events_nested.test.php b/_test/tests/inc/events_nested.test.php index fe5e395bb..3ed2fcf34 100644 --- a/_test/tests/inc/events_nested.test.php +++ b/_test/tests/inc/events_nested.test.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\Extension\Event; + /** * This tests if event handlers can trigger the same event again. * This is used by plugins that modify cache handling and use metadata @@ -16,7 +18,7 @@ class events_nested_test extends DokuWikiTest { $firstcount++; if ($firstcount == 1) { $param = array(); - trigger_event('NESTED_EVENT', $param); + Event::createAndTrigger('NESTED_EVENT', $param); } } ); @@ -28,7 +30,7 @@ class events_nested_test extends DokuWikiTest { ); $param = array(); - trigger_event('NESTED_EVENT', $param); + Event::createAndTrigger('NESTED_EVENT', $param); $this->assertEquals(2, $firstcount); $this->assertEquals(2, $secondcount); diff --git a/_test/tests/inc/httpclient_http.test.php b/_test/tests/inc/httpclient_http.test.php index 3061f7e33..f3afdce66 100644 --- a/_test/tests/inc/httpclient_http.test.php +++ b/_test/tests/inc/httpclient_http.test.php @@ -301,6 +301,9 @@ class httpclient_http_test extends DokuWikiTest { $this->assertTrue($data !== false, $http->errorInfo()); } + /** + * @throws ReflectionException + */ function test_postencode(){ $http = new HTTPMockClient(); @@ -312,7 +315,7 @@ class httpclient_http_test extends DokuWikiTest { ); $this->assertEquals( '%C3%B6%C3%A4%3F=%C3%B6%C3%A4%3F&foo=bang', - $http->_postEncode($data), + $this->callInaccessibleMethod($http, 'postEncode', [$data]), 'simple' ); @@ -323,7 +326,7 @@ class httpclient_http_test extends DokuWikiTest { ); $this->assertEquals( 'foo=bang&%C3%A4rr%5B0%5D=%C3%B6&%C3%A4rr%5B1%5D=b&%C3%A4rr%5B2%5D=c', - $http->_postEncode($data), + $this->callInaccessibleMethod($http, 'postEncode', [$data]), 'onelevelnum' ); @@ -334,7 +337,7 @@ class httpclient_http_test extends DokuWikiTest { ); $this->assertEquals( 'foo=bang&%C3%A4rr%5B%C3%B6%5D=%C3%A4&%C3%A4rr%5Bb%5D=c', - $http->_postEncode($data), + $this->callInaccessibleMethod($http, 'postEncode', [$data]), 'onelevelassoc' ); @@ -346,7 +349,7 @@ class httpclient_http_test extends DokuWikiTest { ); $this->assertEquals( 'foo=bang&%C3%A4rr%5B%C3%B6%5D=%C3%A4&%C3%A4rr%5B%C3%A4%5D%5B%C3%B6%5D=%C3%A4', - $http->_postEncode($data), + $this->callInaccessibleMethod($http, 'postEncode', [$data]), 'twolevelassoc' ); } diff --git a/_test/tests/inc/httpclient_mock.php b/_test/tests/inc/httpclient_mock.php index b66b90775..56b99b7a2 100644 --- a/_test/tests/inc/httpclient_mock.php +++ b/_test/tests/inc/httpclient_mock.php @@ -1,4 +1,7 @@ <?php + +use dokuwiki\HTTP\HTTPClient; + /** * Class HTTPMockClient * diff --git a/_test/tests/inc/input.test.php b/_test/tests/inc/input.test.php index 4a8fb8d71..099a8eb81 100644 --- a/_test/tests/inc/input.test.php +++ b/_test/tests/inc/input.test.php @@ -1,7 +1,9 @@ <?php +use dokuwiki\Input\Input; + /** - * Tests for the Input class + * Tests for the dokuwiki\Input\Input class */ class input_test extends DokuWikiTest { diff --git a/_test/tests/inc/json.test.php b/_test/tests/inc/json.test.php deleted file mode 100644 index 03a76a260..000000000 --- a/_test/tests/inc/json.test.php +++ /dev/null @@ -1,420 +0,0 @@ -<?php -/** - * Unit tests for JSON. - * - * @author Michal Migurski <mike-json@teczno.com> - * @author Matt Knapp <mdknapp[at]gmail[dot]com> - * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> - * @copyright 2005 Michal Migurski - * @version CVS: $Id: Test-JSON.php,v 1.28 2006/06/28 05:54:17 migurski Exp $ - * @license http://www.opensource.org/licenses/bsd-license.php - * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 - * @link http://mike.teczno.com/JSON/Test-JSON.phps - */ - -class JSON_EncDec_TestCase extends DokuWikiTest { - - protected $json; - - function setUp() { - parent::setUp(); - - $this->json = new JSON(); - $this->json->skipnative = true; - - $obj = new stdClass(); - $obj->a_string = '"he":llo}:{world'; - $obj->an_array = array(1, 2, 3); - $obj->obj = new stdClass(); - $obj->obj->a_number = 123; - - $this->obj = $obj; - $this->obj_j = '{"a_string":"\"he\":llo}:{world","an_array":[1,2,3],"obj":{"a_number":123}}'; - $this->obj_d = 'object with properties, nested object and arrays'; - - $this->arr = array(null, true, array(1, 2, 3), "hello\"],[world!"); - $this->arr_j = '[null,true,[1,2,3],"hello\"],[world!"]'; - $this->arr_d = 'array with elements and nested arrays'; - - $this->str1 = 'hello world'; - $this->str1_j = '"hello world"'; - $this->str1_j_ = "'hello world'"; - $this->str1_d = 'hello world'; - $this->str1_d_ = 'hello world, double quotes'; - - $this->str2 = "hello\t\"world\""; - $this->str2_j = '"hello\\t\\"world\\""'; - $this->str2_d = 'hello world, with tab, double-quotes'; - - $this->str3 = "\\\r\n\t\"/"; - $this->str3_j = '"\\\\\\r\\n\\t\\"\\/"'; - $this->str3_d = 'backslash, return, newline, tab, double-quote'; - - $this->str4 = 'héllö wørłd'; - $this->str4_j = '"h\u00e9ll\u00f6 w\u00f8r\u0142d"'; - $this->str4_j_ = '"héllö wørłd"'; - $this->str4_d = 'hello world, with unicode'; - } - - function test_to_JSON() { - $this->assertEquals('null', $this->json->encode(null), 'type case: null'); - $this->assertEquals('true', $this->json->encode(true), 'type case: boolean true'); - $this->assertEquals('false', $this->json->encode(false), 'type case: boolean false'); - - $this->assertEquals('1', $this->json->encode(1), 'numeric case: 1'); - $this->assertEquals('-1', $this->json->encode(-1), 'numeric case: -1'); - $this->assertEquals('1.000000', $this->json->encode(1.0), 'numeric case: 1.0'); - $this->assertEquals('1.100000', $this->json->encode(1.1), 'numeric case: 1.1'); - - $this->assertEquals($this->str1_j, $this->json->encode($this->str1), "string case: {$this->str1_d}"); - $this->assertEquals($this->str2_j, $this->json->encode($this->str2), "string case: {$this->str2_d}"); - $this->assertEquals($this->str3_j, $this->json->encode($this->str3), "string case: {$this->str3_d}"); - $this->assertEquals($this->str4_j, $this->json->encode($this->str4), "string case: {$this->str4_d}"); - - $this->assertEquals($this->arr_j, $this->json->encode($this->arr), "array case: {$this->arr_d}"); - $this->assertEquals($this->obj_j, $this->json->encode($this->obj), "object case: {$this->obj_d}"); - } - - function test_from_JSON() { - $this->assertEquals(null, $this->json->decode('null'), 'type case: null'); - $this->assertEquals(true, $this->json->decode('true'), 'type case: boolean true'); - $this->assertEquals(false, $this->json->decode('false'), 'type case: boolean false'); - - $this->assertEquals(1, $this->json->decode('1'), 'numeric case: 1'); - $this->assertEquals(-1, $this->json->decode('-1'), 'numeric case: -1'); - $this->assertEquals(1.0, $this->json->decode('1.0'), 'numeric case: 1.0'); - $this->assertEquals(1.1, $this->json->decode('1.1'), 'numeric case: 1.1'); - - $this->assertEquals(11.0, $this->json->decode('1.1e1'), 'numeric case: 1.1e1'); - $this->assertEquals(11.0, $this->json->decode('1.10e+1'), 'numeric case: 1.10e+1'); - $this->assertEquals(0.11, $this->json->decode('1.1e-1'), 'numeric case: 1.1e-1'); - $this->assertEquals(-0.11, $this->json->decode('-1.1e-1'), 'numeric case: -1.1e-1'); - - $this->assertEquals($this->str1, $this->json->decode($this->str1_j), "string case: {$this->str1_d}"); - $this->assertEquals($this->str1, $this->json->decode($this->str1_j_), "string case: {$this->str1_d_}"); - $this->assertEquals($this->str2, $this->json->decode($this->str2_j), "string case: {$this->str2_d}"); - $this->assertEquals($this->str3, $this->json->decode($this->str3_j), "string case: {$this->str3_d}"); - $this->assertEquals($this->str4, $this->json->decode($this->str4_j), "string case: {$this->str4_d}"); - $this->assertEquals($this->str4, $this->json->decode($this->str4_j_), "string case: {$this->str4_d}"); - - $this->assertEquals($this->arr, $this->json->decode($this->arr_j), "array case: {$this->arr_d}"); - $this->assertEquals($this->obj, $this->json->decode($this->obj_j), "object case: {$this->obj_d}"); - } - - function test_to_then_from_JSON() { - $this->assertEquals(null, $this->json->decode($this->json->encode(null)), 'type case: null'); - $this->assertEquals(true, $this->json->decode($this->json->encode(true)), 'type case: boolean true'); - $this->assertEquals(false, $this->json->decode($this->json->encode(false)), 'type case: boolean false'); - - $this->assertEquals(1, $this->json->decode($this->json->encode(1)), 'numeric case: 1'); - $this->assertEquals(-1, $this->json->decode($this->json->encode(-1)), 'numeric case: -1'); - $this->assertEquals(1.0, $this->json->decode($this->json->encode(1.0)), 'numeric case: 1.0'); - $this->assertEquals(1.1, $this->json->decode($this->json->encode(1.1)), 'numeric case: 1.1'); - - $this->assertEquals($this->str1, $this->json->decode($this->json->encode($this->str1)), "string case: {$this->str1_d}"); - $this->assertEquals($this->str2, $this->json->decode($this->json->encode($this->str2)), "string case: {$this->str2_d}"); - $this->assertEquals($this->str3, $this->json->decode($this->json->encode($this->str3)), "string case: {$this->str3_d}"); - $this->assertEquals($this->str4, $this->json->decode($this->json->encode($this->str4)), "string case: {$this->str4_d}"); - - $this->assertEquals($this->arr, $this->json->decode($this->json->encode($this->arr)), "array case: {$this->arr_d}"); - $this->assertEquals($this->obj, $this->json->decode($this->json->encode($this->obj)), "object case: {$this->obj_d}"); - } - - function test_from_then_to_JSON() { - $this->assertEquals('null', $this->json->encode($this->json->decode('null')), 'type case: null'); - $this->assertEquals('true', $this->json->encode($this->json->decode('true')), 'type case: boolean true'); - $this->assertEquals('false', $this->json->encode($this->json->decode('false')), 'type case: boolean false'); - - $this->assertEquals('1', $this->json->encode($this->json->decode('1')), 'numeric case: 1'); - $this->assertEquals('-1', $this->json->encode($this->json->decode('-1')), 'numeric case: -1'); - $this->assertEquals('1.0', $this->json->encode($this->json->decode('1.0')), 'numeric case: 1.0'); - $this->assertEquals('1.1', $this->json->encode($this->json->decode('1.1')), 'numeric case: 1.1'); - - $this->assertEquals($this->str1_j, $this->json->encode($this->json->decode($this->str1_j)), "string case: {$this->str1_d}"); - $this->assertEquals($this->str2_j, $this->json->encode($this->json->decode($this->str2_j)), "string case: {$this->str2_d}"); - $this->assertEquals($this->str3_j, $this->json->encode($this->json->decode($this->str3_j)), "string case: {$this->str3_d}"); - $this->assertEquals($this->str4_j, $this->json->encode($this->json->decode($this->str4_j)), "string case: {$this->str4_d}"); - $this->assertEquals($this->str4_j, $this->json->encode($this->json->decode($this->str4_j_)), "string case: {$this->str4_d}"); - - $this->assertEquals($this->arr_j, $this->json->encode($this->json->decode($this->arr_j)), "array case: {$this->arr_d}"); - $this->assertEquals($this->obj_j, $this->json->encode($this->json->decode($this->obj_j)), "object case: {$this->obj_d}"); - } -} - -class JSON_AssocArray_TestCase extends DokuWikiTest { - - function setUp() { - parent::setUp(); - - $this->json_l = new JSON(JSON_LOOSE_TYPE); - $this->json_l->skipnative = true; - $this->json_s = new JSON(); - $this->json_s->skipnative = true; - - $this->arr = array('car1'=> array('color'=> 'tan', 'model' => 'sedan'), - 'car2' => array('color' => 'red', 'model' => 'sports')); - $this->arr_jo = '{"car1":{"color":"tan","model":"sedan"},"car2":{"color":"red","model":"sports"}}'; - $this->arr_d = 'associative array with nested associative arrays'; - - $this->arn = array(0=> array(0=> 'tan\\', 'model\\' => 'sedan'), 1 => array(0 => 'red', 'model' => 'sports')); - $this->arn_ja = '[{"0":"tan\\\\","model\\\\":"sedan"},{"0":"red","model":"sports"}]'; - $this->arn_d = 'associative array with nested associative arrays, and some numeric keys thrown in'; - - $this->arrs = array (1 => 'one', 2 => 'two', 5 => 'five'); - $this->arrs_jo = '{"1":"one","2":"two","5":"five"}'; - $this->arrs_d = 'associative array numeric keys which are not fully populated in a range of 0 to length-1'; - } - - function test_type() { - $this->assertEquals('array', gettype($this->json_l->decode($this->arn_ja)), "loose type should be array"); - $this->assertEquals('array', gettype($this->json_s->decode($this->arn_ja)), "strict type should be array"); - } - - function test_to_JSON() { - // both strict and loose JSON should result in an object - $this->assertEquals($this->arr_jo, $this->json_l->encode($this->arr), "array case - loose: {$this->arr_d}"); - $this->assertEquals($this->arr_jo, $this->json_s->encode($this->arr), "array case - strict: {$this->arr_d}"); - - // ...unless the input array has some numeric indeces, in which case the behavior is to degrade to a regular array - $this->assertEquals($this->arn_ja, $this->json_s->encode($this->arn), "array case - strict: {$this->arn_d}"); - - // Test a sparsely populated numerically indexed associative array - $this->assertEquals($this->arrs_jo, $this->json_l->encode($this->arrs), "sparse numeric assoc array: {$this->arrs_d}"); - } - - function test_to_then_from_JSON() { - // these tests motivated by a bug in which strings that end - // with backslashes followed by quotes were incorrectly decoded. - - foreach(array('\\"', '\\\\"', '\\"\\"', '\\""\\""', '\\\\"\\\\"') as $v) { - $this->assertEquals(array($v), $this->json_l->decode($this->json_l->encode(array($v)))); - $this->assertEquals(array('a' => $v), $this->json_l->decode($this->json_l->encode(array('a' => $v)))); - } - } -} - -class JSON_NestedArray_TestCase extends DokuWikiTest { - - function setUp() { - parent::setUp(); - - $this->json = new JSON(JSON_LOOSE_TYPE); - $this->json->skipnative = true; - - $this->str1 = '[{"this":"that"}]'; - $this->arr1 = array(array('this' => 'that')); - - $this->str2 = '{"this":["that"]}'; - $this->arr2 = array('this' => array('that')); - - $this->str3 = '{"params":[{"foo":["1"],"bar":"1"}]}'; - $this->arr3 = array('params' => array(array('foo' => array('1'), 'bar' => '1'))); - - $this->str4 = '{"0": {"foo": "bar", "baz": "winkle"}}'; - $this->arr4 = array('0' => array('foo' => 'bar', 'baz' => 'winkle')); - - $this->str5 = '{"params":[{"options": {"old": [ ], "new": {"0": {"elements": {"old": [], "new": {"0": {"elementName": "aa", "isDefault": false, "elementRank": "0", "priceAdjust": "0", "partNumber": ""}}}, "optionName": "aa", "isRequired": false, "optionDesc": null}}}}]}'; - $this->arr5 = array ( - 'params' => array ( - 0 => array ( - 'options' => - array ( - 'old' => array(), - 'new' => array ( - 0 => array ( - 'elements' => array ( - 'old' => array(), - 'new' => array ( - 0 => array ( - 'elementName' => 'aa', - 'isDefault' => false, - 'elementRank' => '0', - 'priceAdjust' => '0', - 'partNumber' => '', - ), - ), - ), - 'optionName' => 'aa', - 'isRequired' => false, - 'optionDesc' => NULL, - ), - ), - ), - ), - ), - ); - } - - function test_type() { - $this->assertEquals('array', gettype($this->json->decode($this->str1)), "loose type should be array"); - $this->assertEquals('array', gettype($this->json->decode($this->str2)), "loose type should be array"); - $this->assertEquals('array', gettype($this->json->decode($this->str3)), "loose type should be array"); - } - - function test_from_JSON() { - $this->assertEquals($this->arr1, $this->json->decode($this->str1), "simple compactly-nested array"); - $this->assertEquals($this->arr2, $this->json->decode($this->str2), "simple compactly-nested array"); - $this->assertEquals($this->arr3, $this->json->decode($this->str3), "complex compactly nested array"); - $this->assertEquals($this->arr4, $this->json->decode($this->str4), "complex compactly nested array"); - $this->assertEquals($this->arr5, $this->json->decode($this->str5), "super complex compactly nested array"); - } - - function _test_from_JSON() { - $super = '{"params":[{"options": {"old": {}, "new": {"0": {"elements": {"old": {}, "new": {"0": {"elementName": "aa", "isDefault": false, "elementRank": "0", "priceAdjust": "0", "partNumber": ""}}}, "optionName": "aa", "isRequired": false, "optionDesc": ""}}}}]}'; - print("trying {$super}...\n"); - print var_export($this->json->decode($super)); - } -} - -class JSON_Object_TestCase extends DokuWikiTest { - - function setUp() { - parent::setUp(); - - $this->json_l = new JSON(JSON_LOOSE_TYPE); - $this->json_l->skipnative = true; - $this->json_s = new JSON(); - $this->json_s->skipnative = true; - - $this->obj_j = '{"a_string":"\"he\":llo}:{world","an_array":[1,2,3],"obj":{"a_number":123}}'; - - $this->obj1 = new stdClass(); - $this->obj1->car1 = new stdClass(); - $this->obj1->car1->color = 'tan'; - $this->obj1->car1->model = 'sedan'; - $this->obj1->car2 = new stdClass(); - $this->obj1->car2->color = 'red'; - $this->obj1->car2->model = 'sports'; - $this->obj1_j = '{"car1":{"color":"tan","model":"sedan"},"car2":{"color":"red","model":"sports"}}'; - $this->obj1_d = 'Object with nested objects'; - } - - function test_type() { - $this->assertEquals('object', gettype($this->json_s->decode($this->obj_j)), "checking whether decoded type is object"); - $this->assertEquals('array', gettype($this->json_l->decode($this->obj_j)), "checking whether decoded type is array"); - } - - function test_to_JSON() { - $this->assertEquals($this->obj1_j, $this->json_s->encode($this->obj1), "object - strict: {$this->obj1_d}"); - $this->assertEquals($this->obj1_j, $this->json_l->encode($this->obj1), "object - loose: {$this->obj1_d}"); - } - - function test_from_then_to_JSON() { - $this->assertEquals($this->obj_j, $this->json_s->encode($this->json_s->decode($this->obj_j)), "object case"); - $this->assertEquals($this->obj_j, $this->json_l->encode($this->json_l->decode($this->obj_j)), "array case"); - } -} - -class JSON_Spaces_Comments_TestCase extends DokuWikiTest { - - function setUp() { - parent::setUp(); - - $this->json = new JSON(JSON_LOOSE_TYPE); - $this->json->skipnative = true; - - $this->obj_j = '{"a_string":"\"he\":llo}:{world","an_array":[1,2,3],"obj":{"a_number":123}}'; - - $this->obj_js = '{"a_string": "\"he\":llo}:{world", - "an_array":[1, 2, 3], - "obj": {"a_number":123}}'; - - $this->obj_jc1 = '{"a_string": "\"he\":llo}:{world", - // here is a comment, hoorah - "an_array":[1, 2, 3], - "obj": {"a_number":123}}'; - - $this->obj_jc2 = '/* this here is the sneetch */ "the sneetch" - // this has been the sneetch.'; - - $this->obj_jc3 = '{"a_string": "\"he\":llo}:{world", - /* here is a comment, hoorah */ - "an_array":[1, 2, 3 /* and here is another */], - "obj": {"a_number":123}}'; - - $this->obj_jc4 = '{\'a_string\': "\"he\":llo}:{world", - /* here is a comment, hoorah */ - \'an_array\':[1, 2, 3 /* and here is another */], - "obj": {"a_number":123}}'; - } - - function test_spaces() { - $this->assertEquals($this->json->decode($this->obj_j), $this->json->decode($this->obj_js), "checking whether notation with spaces works"); - } - - function test_comments() { - $this->assertEquals($this->json->decode($this->obj_j), $this->json->decode($this->obj_jc1), "checking whether notation with single line comments works"); - $this->assertEquals('the sneetch', $this->json->decode($this->obj_jc2), "checking whether notation with multiline comments works"); - $this->assertEquals($this->json->decode($this->obj_j), $this->json->decode($this->obj_jc3), "checking whether notation with multiline comments works"); - $this->assertEquals($this->json->decode($this->obj_j), $this->json->decode($this->obj_jc4), "checking whether notation with single-quotes and multiline comments works"); - } -} - -class JSON_Empties_TestCase extends DokuWikiTest { - - function setUp() { - parent::setUp(); - - $this->json_l = new JSON(JSON_LOOSE_TYPE); - $this->json_l->skipnative = true; - $this->json_l->skipnative = true; - $this->json_s = new JSON(); - $this->json_s->skipnative = true; - - $this->obj0_j = '{}'; - $this->arr0_j = '[]'; - - $this->obj1_j = '{ }'; - $this->arr1_j = '[ ]'; - - $this->obj2_j = '{ /* comment inside */ }'; - $this->arr2_j = '[ /* comment inside */ ]'; - } - - function test_type() { - $this->assertEquals('array', gettype($this->json_l->decode($this->arr0_j)), "should be array"); - $this->assertEquals('object', gettype($this->json_s->decode($this->obj0_j)), "should be object"); - - $this->assertEquals(0, count($this->json_l->decode($this->arr0_j)), "should be empty array"); - $this->assertEquals(0, count(get_object_vars($this->json_s->decode($this->obj0_j))), "should be empty object"); - - $this->assertEquals('array', gettype($this->json_l->decode($this->arr1_j)), "should be array, even with space"); - $this->assertEquals('object', gettype($this->json_s->decode($this->obj1_j)), "should be object, even with space"); - - $this->assertEquals(0, count($this->json_l->decode($this->arr1_j)), "should be empty array, even with space"); - $this->assertEquals(0, count(get_object_vars($this->json_s->decode($this->obj1_j))), "should be empty object, even with space"); - - $this->assertEquals('array', gettype($this->json_l->decode($this->arr2_j)), "should be array, despite comment"); - $this->assertEquals('object', gettype($this->json_s->decode($this->obj2_j)), "should be object, despite comment"); - - $this->assertEquals(0, count($this->json_l->decode($this->arr2_j)), "should be empty array, despite comment"); - $this->assertEquals(0, count(get_object_vars($this->json_s->decode($this->obj2_j))), "should be empty object, despite commentt"); - } -} - -class JSON_UnquotedKeys_TestCase extends DokuWikiTest { - - function setUp() { - parent::setUp(); - - $this->json = new JSON(JSON_LOOSE_TYPE); - $this->json->skipnative = true; - - $this->arn = array(0=> array(0=> 'tan', 'model' => 'sedan'), 1 => array(0 => 'red', 'model' => 'sports')); - $this->arn_ja = '[{0:"tan","model":"sedan"},{"0":"red",model:"sports"}]'; - $this->arn_d = 'associative array with unquoted keys, nested associative arrays, and some numeric keys thrown in'; - - $this->arrs = array (1 => 'one', 2 => 'two', 5 => 'fi"ve'); - $this->arrs_jo = '{"1":"one",2:"two","5":\'fi"ve\'}'; - $this->arrs_d = 'associative array with unquoted keys, single-quoted values, numeric keys which are not fully populated in a range of 0 to length-1'; - } - - function test_from_JSON() { - // ...unless the input array has some numeric indeces, in which case the behavior is to degrade to a regular array - $this->assertEquals($this->arn, $this->json->decode($this->arn_ja), "array case - strict: {$this->arn_d}"); - - // Test a sparsely populated numerically indexed associative array - $this->assertEquals($this->arrs, $this->json->decode($this->arrs_jo), "sparse numeric assoc array: {$this->arrs_d}"); - } -} - diff --git a/_test/tests/inc/mailer.test.php b/_test/tests/inc/mailer.test.php index f1b579759..2c8f142d1 100755 --- a/_test/tests/inc/mailer.test.php +++ b/_test/tests/inc/mailer.test.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\HTTP\HTTPClient; + /** * Extends the mailer class to expose internal variables for testing */ diff --git a/_test/tests/inc/media_searchlist.test.php b/_test/tests/inc/media_searchlist.test.php index c038d3a20..b881ccc95 100644 --- a/_test/tests/inc/media_searchlist.test.php +++ b/_test/tests/inc/media_searchlist.test.php @@ -124,7 +124,7 @@ class media_searchlist_test extends DokuWikiTest { $info = array(); $info['id'] = $this->upload_ns . ':' . $rel_id; $info['perm'] = auth_quickaclcheck(getNS($info['id']).':*'); - $info['file'] = utf8_basename($file); + $info['file'] = \dokuwiki\Utf8\PhpString::basename($file); $info['size'] = filesize($file); $info['mtime'] = filemtime($file); $info['writable'] = is_writable($file); diff --git a/_test/tests/inc/pageutils_findnearest.test.php b/_test/tests/inc/pageutils_findnearest.test.php index 1bed5bb04..55db44afa 100644 --- a/_test/tests/inc/pageutils_findnearest.test.php +++ b/_test/tests/inc/pageutils_findnearest.test.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\test\mock\AuthPlugin; + class pageutils_findnearest_test extends DokuWikiTest { protected $oldAuthAcl; @@ -13,7 +15,7 @@ class pageutils_findnearest_test extends DokuWikiTest { $conf['useacl'] = 1; $this->oldAuthAcl = $AUTH_ACL; - $auth = new DokuWiki_Auth_Plugin(); + $auth = new AuthPlugin(); $AUTH_ACL = array( '* @ALL 1', diff --git a/_test/tests/inc/parser/lexer.test.php b/_test/tests/inc/parser/lexer.test.php index 50b6548a4..412bee75b 100644 --- a/_test/tests/inc/parser/lexer.test.php +++ b/_test/tests/inc/parser/lexer.test.php @@ -5,10 +5,9 @@ * @subpackage Tests */ -/** -* Includes -*/ -require_once DOKU_INC . 'inc/parser/lexer.php'; +use dokuwiki\Parsing\Lexer\Lexer; +use dokuwiki\Parsing\Lexer\ParallelRegex; +use dokuwiki\Parsing\Lexer\StateStack; /** * @package Doku @@ -17,24 +16,24 @@ require_once DOKU_INC . 'inc/parser/lexer.php'; class TestOfLexerParallelRegex extends DokuWikiTest { function testNoPatterns() { - $regex = new Doku_LexerParallelRegex(false); + $regex = new ParallelRegex(false); $this->assertFalse($regex->match("Hello", $match)); $this->assertEquals($match, ""); } function testNoSubject() { - $regex = new Doku_LexerParallelRegex(false); + $regex = new ParallelRegex(false); $regex->addPattern(".*"); $this->assertTrue($regex->match("", $match)); $this->assertEquals($match, ""); } function testMatchAll() { - $regex = new Doku_LexerParallelRegex(false); + $regex = new ParallelRegex(false); $regex->addPattern(".*"); $this->assertTrue($regex->match("Hello", $match)); $this->assertEquals($match, "Hello"); } function testCaseSensitive() { - $regex = new Doku_LexerParallelRegex(true); + $regex = new ParallelRegex(true); $regex->addPattern("abc"); $this->assertTrue($regex->match("abcdef", $match)); $this->assertEquals($match, "abc"); @@ -42,7 +41,7 @@ class TestOfLexerParallelRegex extends DokuWikiTest { $this->assertEquals($match, "abc"); } function testCaseInsensitive() { - $regex = new Doku_LexerParallelRegex(false); + $regex = new ParallelRegex(false); $regex->addPattern("abc"); $this->assertTrue($regex->match("abcdef", $match)); $this->assertEquals($match, "abc"); @@ -50,7 +49,7 @@ class TestOfLexerParallelRegex extends DokuWikiTest { $this->assertEquals($match, "ABC"); } function testMatchMultiple() { - $regex = new Doku_LexerParallelRegex(true); + $regex = new ParallelRegex(true); $regex->addPattern("abc"); $regex->addPattern("ABC"); $this->assertTrue($regex->match("abcdef", $match)); @@ -60,7 +59,7 @@ class TestOfLexerParallelRegex extends DokuWikiTest { $this->assertFalse($regex->match("Hello", $match)); } function testPatternLabels() { - $regex = new Doku_LexerParallelRegex(false); + $regex = new ParallelRegex(false); $regex->addPattern("abc", "letter"); $regex->addPattern("123", "number"); $this->assertEquals($regex->match("abcdef", $match), "letter"); @@ -69,7 +68,7 @@ class TestOfLexerParallelRegex extends DokuWikiTest { $this->assertEquals($match, "123"); } function testMatchMultipleWithLookaheadNot() { - $regex = new Doku_LexerParallelRegex(true); + $regex = new ParallelRegex(true); $regex->addPattern("abc"); $regex->addPattern("ABC"); $regex->addPattern("a(?!\n).{1}"); @@ -82,37 +81,37 @@ class TestOfLexerParallelRegex extends DokuWikiTest { $this->assertFalse($regex->match("Hello", $match)); } function testMatchSetOptionCaseless() { - $regex = new Doku_LexerParallelRegex(true); + $regex = new ParallelRegex(true); $regex->addPattern("a(?i)b(?i)c"); $this->assertTrue($regex->match("aBc", $match)); $this->assertEquals($match, "aBc"); } function testMatchSetOptionUngreedy() { - $regex = new Doku_LexerParallelRegex(true); + $regex = new ParallelRegex(true); $regex->addPattern("(?U)\w+"); $this->assertTrue($regex->match("aaaaaa", $match)); $this->assertEquals($match, "a"); } function testMatchLookaheadEqual() { - $regex = new Doku_LexerParallelRegex(true); + $regex = new ParallelRegex(true); $regex->addPattern("\w(?=c)"); $this->assertTrue($regex->match("xbyczd", $match)); $this->assertEquals($match, "y"); } function testMatchLookaheadNot() { - $regex = new Doku_LexerParallelRegex(true); + $regex = new ParallelRegex(true); $regex->addPattern("\w(?!b|c)"); $this->assertTrue($regex->match("xbyczd", $match)); $this->assertEquals($match, "b"); } function testMatchLookbehindEqual() { - $regex = new Doku_LexerParallelRegex(true); + $regex = new ParallelRegex(true); $regex->addPattern("(?<=c)\w"); $this->assertTrue($regex->match("xbyczd", $match)); $this->assertEquals($match, "z"); } function testMatchLookbehindNot() { - $regex = new Doku_LexerParallelRegex(true); + $regex = new ParallelRegex(true); $regex->addPattern("(?<!\A|x|b)\w"); $this->assertTrue($regex->match("xbyczd", $match)); $this->assertEquals($match, "c"); @@ -122,15 +121,15 @@ class TestOfLexerParallelRegex extends DokuWikiTest { class TestOfLexerStateStack extends DokuWikiTest { function testStartState() { - $stack = new Doku_LexerStateStack("one"); + $stack = new StateStack("one"); $this->assertEquals($stack->getCurrent(), "one"); } function testExhaustion() { - $stack = new Doku_LexerStateStack("one"); + $stack = new StateStack("one"); $this->assertFalse($stack->leave()); } function testStateMoves() { - $stack = new Doku_LexerStateStack("one"); + $stack = new StateStack("one"); $stack->enter("two"); $this->assertEquals($stack->getCurrent(), "two"); $stack->enter("three"); @@ -160,13 +159,13 @@ class TestOfLexer extends DokuWikiTest { function testNoPatterns() { $handler = $this->createMock('TestParser'); $handler->expects($this->never())->method('accept'); - $lexer = new Doku_Lexer($handler); + $lexer = new Lexer($handler); $this->assertFalse($lexer->parse("abcdef")); } function testEmptyPage() { $handler = $this->createMock('TestParser'); $handler->expects($this->never())->method('accept'); - $lexer = new Doku_Lexer($handler); + $lexer = new Lexer($handler); $lexer->addPattern("a+"); $this->assertTrue($lexer->parse("")); } @@ -189,7 +188,7 @@ class TestOfLexer extends DokuWikiTest { $handler->expects($this->at(7))->method('accept') ->with("z", DOKU_LEXER_UNMATCHED, 13)->will($this->returnValue(true)); - $lexer = new Doku_Lexer($handler); + $lexer = new Lexer($handler); $lexer->addPattern("a+"); $this->assertTrue($lexer->parse("aaaxayyyaxaaaz")); } @@ -201,7 +200,7 @@ class TestOfLexer extends DokuWikiTest { $handler->expects($this->at($i))->method('accept') ->with($target[$i], $this->anything(), $positions[$i])->will($this->returnValue(true)); } - $lexer = new Doku_Lexer($handler); + $lexer = new Lexer($handler); $lexer->addPattern("a+"); $lexer->addPattern("b+"); $this->assertTrue($lexer->parse("ababbxbaxxxxxxax")); @@ -227,7 +226,7 @@ class TestOfLexerModes extends DokuWikiTest { ->with("aaaa", DOKU_LEXER_MATCHED,11)->will($this->returnValue(true)); $handler->expects($this->at(7))->method('a') ->with("x", DOKU_LEXER_UNMATCHED,15)->will($this->returnValue(true)); - $lexer = new Doku_Lexer($handler, "a"); + $lexer = new Lexer($handler, "a"); $lexer->addPattern("a+", "a"); $lexer->addPattern("b+", "b"); $this->assertTrue($lexer->parse("abaabxbaaaxaaaax")); @@ -261,7 +260,7 @@ class TestOfLexerModes extends DokuWikiTest { $handler->expects($this->at(12))->method('b') ->with("a", DOKU_LEXER_UNMATCHED,18)->will($this->returnValue(true)); - $lexer = new Doku_Lexer($handler, "a"); + $lexer = new Lexer($handler, "a"); $lexer->addPattern("a+", "a"); $lexer->addEntryPattern(":", "a", "b"); $lexer->addPattern("b+", "b"); @@ -293,7 +292,7 @@ class TestOfLexerModes extends DokuWikiTest { ->with("b", DOKU_LEXER_UNMATCHED,15)->will($this->returnValue(true)); - $lexer = new Doku_Lexer($handler, "a"); + $lexer = new Lexer($handler, "a"); $lexer->addPattern("a+", "a"); $lexer->addEntryPattern("(", "a", "b"); $lexer->addPattern("b+", "b"); @@ -314,7 +313,7 @@ class TestOfLexerModes extends DokuWikiTest { ->with("bbb", DOKU_LEXER_SPECIAL,7)->will($this->returnValue(true)); $handler->expects($this->at(5))->method('a') ->with("xx", DOKU_LEXER_UNMATCHED,10)->will($this->returnValue(true)); - $lexer = new Doku_Lexer($handler, "a"); + $lexer = new Lexer($handler, "a"); $lexer->addPattern("a+", "a"); $lexer->addSpecialPattern("b+", "a", "b"); $this->assertTrue($lexer->parse("aabaaxxbbbxx")); @@ -326,7 +325,7 @@ class TestOfLexerModes extends DokuWikiTest { $handler->expects($this->at(1))->method('a') ->with(")", DOKU_LEXER_EXIT,2)->will($this->returnValue(true)); - $lexer = new Doku_Lexer($handler, "a"); + $lexer = new Lexer($handler, "a"); $lexer->addPattern("a+", "a"); $lexer->addExitPattern(")", "a"); $this->assertFalse($lexer->parse("aa)aa")); @@ -351,7 +350,7 @@ class TestOfLexerHandlers extends DokuWikiTest { $handler->expects($this->at(6))->method('a') ->with("b", DOKU_LEXER_UNMATCHED,9)->will($this->returnValue(true)); - $lexer = new Doku_Lexer($handler, "mode_a"); + $lexer = new Lexer($handler, "mode_a"); $lexer->addPattern("a+", "mode_a"); $lexer->addEntryPattern("(", "mode_a", "mode_b"); $lexer->addPattern("b+", "mode_b"); @@ -389,7 +388,7 @@ class TestOfLexerByteIndices extends DokuWikiTest { $handler->expects($this->at(5))->method('caught') ->with("</file>", DOKU_LEXER_EXIT, strpos($doc,'</file>'))->will($this->returnValue(true)); - $lexer = new Doku_Lexer($handler, "ignore"); + $lexer = new Lexer($handler, "ignore"); $lexer->addEntryPattern("<file>", "ignore", "caught"); $lexer->addExitPattern("</file>", "caught"); $lexer->addSpecialPattern('b','caught','special'); @@ -415,7 +414,7 @@ class TestOfLexerByteIndices extends DokuWikiTest { $handler->expects($this->at(5))->method('caught') ->with("</file>", DOKU_LEXER_EXIT, strpos($doc,'</file>'))->will($this->returnValue(true)); - $lexer = new Doku_Lexer($handler, "ignore"); + $lexer = new Lexer($handler, "ignore"); $lexer->addEntryPattern('<file>(?=.*</file>)', "ignore", "caught"); $lexer->addExitPattern("</file>", "caught"); $lexer->addSpecialPattern('b','caught','special'); @@ -441,7 +440,7 @@ class TestOfLexerByteIndices extends DokuWikiTest { $handler->expects($this->at(5))->method('caught') ->with("</file>", DOKU_LEXER_EXIT, strpos($doc,'</file>'))->will($this->returnValue(true)); - $lexer = new Doku_Lexer($handler, "ignore"); + $lexer = new Lexer($handler, "ignore"); $lexer->addEntryPattern('<file>(?!foo)', "ignore", "caught"); $lexer->addExitPattern("</file>", "caught"); $lexer->addSpecialPattern('b','caught','special'); @@ -467,7 +466,7 @@ class TestOfLexerByteIndices extends DokuWikiTest { $handler->expects($this->at(5))->method('caught') ->with("</file>", DOKU_LEXER_EXIT, strpos($doc,'</file>'))->will($this->returnValue(true)); - $lexer = new Doku_Lexer($handler, "ignore"); + $lexer = new Lexer($handler, "ignore"); $lexer->addEntryPattern('<file>', "ignore", "caught"); $lexer->addExitPattern("(?<=d)</file>", "caught"); $lexer->addSpecialPattern('b','caught','special'); @@ -493,7 +492,7 @@ class TestOfLexerByteIndices extends DokuWikiTest { $handler->expects($this->at(5))->method('caught') ->with("</file>", DOKU_LEXER_EXIT, strpos($doc,'</file>'))->will($this->returnValue(true)); - $lexer = new Doku_Lexer($handler, 'ignore'); + $lexer = new Lexer($handler, 'ignore'); $lexer->addEntryPattern('<file>', 'ignore', 'caught'); $lexer->addExitPattern('(?<!c)</file>', 'caught'); $lexer->addSpecialPattern('b','caught','special'); @@ -520,7 +519,7 @@ class TestOfLexerByteIndices extends DokuWikiTest { $handler->expects($this->once())->method('caught') ->with("FOO", DOKU_LEXER_SPECIAL, $matches[0][1])->will($this->returnValue(true)); - $lexer = new Doku_Lexer($handler, "ignore"); + $lexer = new Lexer($handler, "ignore"); $lexer->addSpecialPattern($pattern,'ignore','caught'); $this->assertTrue($lexer->parse($doc)); diff --git a/_test/tests/inc/parser/parser.inc.php b/_test/tests/inc/parser/parser.inc.php index c73f8d137..153f67b26 100644 --- a/_test/tests/inc/parser/parser.inc.php +++ b/_test/tests/inc/parser/parser.inc.php @@ -1,20 +1,22 @@ <?php +use dokuwiki\Parsing\Parser; + require_once DOKU_INC . 'inc/parser/parser.php'; require_once DOKU_INC . 'inc/parser/handler.php'; +if (!defined('DOKU_PARSER_EOL')) define('DOKU_PARSER_EOL', "\n"); // add this to make handling test cases simpler abstract class TestOfDoku_Parser extends DokuWikiTest { - /** @var Doku_Parser */ + /** @var Parser */ protected $P; /** @var Doku_Handler */ protected $H; function setUp() { parent::setUp(); - $this->P = new Doku_Parser(); $this->H = new Doku_Handler(); - $this->P->Handler = $this->H; + $this->P = new Parser($this->H); } function tearDown() { diff --git a/_test/tests/inc/parser/parser_code.test.php b/_test/tests/inc/parser/parser_code.test.php index df8225f4e..961db7dd2 100644 --- a/_test/tests/inc/parser/parser_code.test.php +++ b/_test/tests/inc/parser/parser_code.test.php @@ -1,4 +1,7 @@ <?php + +use dokuwiki\Parsing\ParserMode\Code; + require_once 'parser.inc.php'; /** @@ -10,7 +13,7 @@ class TestOfDoku_Parser_Code extends TestOfDoku_Parser { function setUp() { parent::setUp(); - $this->P->addMode('code',new Doku_Parser_Mode_Code()); + $this->P->addMode('code',new Code()); } function testCode() { diff --git a/_test/tests/inc/parser/parser_eol.test.php b/_test/tests/inc/parser/parser_eol.test.php index 6264f8b55..ae5e9cce5 100644 --- a/_test/tests/inc/parser/parser_eol.test.php +++ b/_test/tests/inc/parser/parser_eol.test.php @@ -1,10 +1,14 @@ <?php + +use dokuwiki\Parsing\ParserMode\Eol; +use dokuwiki\Parsing\ParserMode\Linebreak; + require_once 'parser.inc.php'; class TestOfDoku_Parser_Eol extends TestOfDoku_Parser { function testEol() { - $this->P->addMode('eol',new Doku_Parser_Mode_Eol()); + $this->P->addMode('eol',new Eol()); $this->P->parse("Foo\nBar"); $calls = array ( array('document_start',array()), @@ -17,7 +21,7 @@ class TestOfDoku_Parser_Eol extends TestOfDoku_Parser { } function testEolMultiple() { - $this->P->addMode('eol',new Doku_Parser_Mode_Eol()); + $this->P->addMode('eol',new Eol()); $this->P->parse("Foo\n\nbar\nFoo"); $calls = array ( array('document_start',array()), @@ -33,7 +37,7 @@ class TestOfDoku_Parser_Eol extends TestOfDoku_Parser { } function testWinEol() { - $this->P->addMode('eol',new Doku_Parser_Mode_Eol()); + $this->P->addMode('eol',new Eol()); $this->P->parse("Foo\r\nBar"); $calls = array ( array('document_start',array()), @@ -46,7 +50,7 @@ class TestOfDoku_Parser_Eol extends TestOfDoku_Parser { } function testLinebreak() { - $this->P->addMode('linebreak',new Doku_Parser_Mode_Linebreak()); + $this->P->addMode('linebreak',new Linebreak()); $this->P->parse('Foo\\\\ Bar'); $calls = array ( array('document_start',array()), @@ -61,8 +65,8 @@ class TestOfDoku_Parser_Eol extends TestOfDoku_Parser { } function testLinebreakPlusEol() { - $this->P->addMode('linebreak',new Doku_Parser_Mode_Linebreak()); - $this->P->addMode('eol',new Doku_Parser_Mode_Eol()); + $this->P->addMode('linebreak',new Linebreak()); + $this->P->addMode('eol',new Eol()); $this->P->parse('Foo\\\\'."\n\n".'Bar'); $calls = array ( @@ -80,7 +84,7 @@ class TestOfDoku_Parser_Eol extends TestOfDoku_Parser { } function testLinebreakInvalid() { - $this->P->addMode('linebreak',new Doku_Parser_Mode_Linebreak()); + $this->P->addMode('linebreak',new Linebreak()); $this->P->parse('Foo\\\\Bar'); $calls = array ( array('document_start',array()), diff --git a/_test/tests/inc/parser/parser_file.test.php b/_test/tests/inc/parser/parser_file.test.php index 39bda8a58..407b04a48 100644 --- a/_test/tests/inc/parser/parser_file.test.php +++ b/_test/tests/inc/parser/parser_file.test.php @@ -1,11 +1,14 @@ <?php + +use dokuwiki\Parsing\ParserMode\File; + require_once 'parser.inc.php'; class TestOfDoku_Parser_File extends TestOfDoku_Parser { function setUp() { parent::setUp(); - $this->P->addMode('file',new Doku_Parser_Mode_File()); + $this->P->addMode('file',new File()); } function testFile() { diff --git a/_test/tests/inc/parser/parser_footnote.test.php b/_test/tests/inc/parser/parser_footnote.test.php index 2457fb031..96d7a8407 100644 --- a/_test/tests/inc/parser/parser_footnote.test.php +++ b/_test/tests/inc/parser/parser_footnote.test.php @@ -1,11 +1,24 @@ <?php + +use dokuwiki\Parsing\Handler\Lists; +use dokuwiki\Parsing\ParserMode\Code; +use dokuwiki\Parsing\ParserMode\Eol; +use dokuwiki\Parsing\ParserMode\Footnote; +use dokuwiki\Parsing\ParserMode\Formatting; +use dokuwiki\Parsing\ParserMode\Hr; +use dokuwiki\Parsing\ParserMode\Listblock; +use dokuwiki\Parsing\ParserMode\Preformatted; +use dokuwiki\Parsing\ParserMode\Quote; +use dokuwiki\Parsing\ParserMode\Table; +use dokuwiki\Parsing\ParserMode\Unformatted; + require_once 'parser.inc.php'; class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { function setUp() { parent::setUp(); - $this->P->addMode('footnote',new Doku_Parser_Mode_Footnote()); + $this->P->addMode('footnote',new Footnote()); } function testFootnote() { @@ -39,7 +52,7 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { } function testFootnoteLinefeed() { - $this->P->addMode('eol',new Doku_Parser_Mode_Eol()); + $this->P->addMode('eol',new Eol()); $this->P->parse("Foo (( testing\ntesting )) Bar"); $calls = array ( array('document_start',array()), @@ -76,7 +89,7 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { } function testFootnoteEol() { - $this->P->addMode('eol',new Doku_Parser_Mode_Eol()); + $this->P->addMode('eol',new Eol()); $this->P->parse("Foo \nX(( test\ning ))Y\n Bar"); $calls = array ( array('document_start',array()), @@ -95,7 +108,7 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { } function testFootnoteStrong() { - $this->P->addMode('strong',new Doku_Parser_Mode_Formatting('strong')); + $this->P->addMode('strong',new Formatting('strong')); $this->P->parse('Foo (( **testing** )) Bar'); $calls = array ( array('document_start',array()), @@ -118,7 +131,7 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { } function testFootnoteHr() { - $this->P->addMode('hr',new Doku_Parser_Mode_HR()); + $this->P->addMode('hr',new Hr()); $this->P->parse("Foo (( \n ---- \n )) Bar"); $calls = array ( array('document_start',array()), @@ -139,7 +152,7 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { } function testFootnoteCode() { - $this->P->addMode('code',new Doku_Parser_Mode_Code()); + $this->P->addMode('code',new Code()); $this->P->parse("Foo (( <code>Test</code> )) Bar"); $calls = array ( array('document_start',array()), @@ -160,7 +173,7 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { } function testFootnotePreformatted() { - $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted()); + $this->P->addMode('preformatted',new Preformatted()); $this->P->parse("Foo (( \n Test\n )) Bar"); $calls = array ( array('document_start',array()), @@ -181,8 +194,8 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { } function testFootnotePreformattedEol() { - $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted()); - $this->P->addMode('eol',new Doku_Parser_Mode_Eol()); + $this->P->addMode('preformatted',new Preformatted()); + $this->P->addMode('eol',new Eol()); $this->P->parse("Foo (( \n Test\n )) Bar"); $calls = array ( array('document_start',array()), @@ -204,7 +217,7 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { } function testFootnoteUnformatted() { - $this->P->addMode('unformatted',new Doku_Parser_Mode_Unformatted()); + $this->P->addMode('unformatted',new Unformatted()); $this->P->parse("Foo (( <nowiki>Test</nowiki> )) Bar"); $calls = array ( array('document_start',array()), @@ -225,7 +238,7 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { } function testFootnoteNotHeader() { - $this->P->addMode('unformatted',new Doku_Parser_Mode_Unformatted()); + $this->P->addMode('unformatted',new Unformatted()); $this->P->parse("Foo (( \n====Test====\n )) Bar"); $calls = array ( array('document_start',array()), @@ -244,7 +257,7 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { } function testFootnoteTable() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse("Foo (( | Row 0 Col 1 | Row 0 Col 2 | Row 0 Col 3 | | Row 1 Col 1 | Row 1 Col 2 | Row 1 Col 3 | @@ -290,7 +303,7 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { } function testFootnoteList() { - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); + $this->P->addMode('listblock',new ListBlock()); $this->P->parse("Foo (( *A * B @@ -303,7 +316,7 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { array('nest', array ( array ( array('footnote_open',array()), array('listu_open',array()), - array('listitem_open',array(1,Doku_Handler_List::NODE)), + array('listitem_open',array(1,Lists::NODE)), array('listcontent_open',array()), array('cdata',array("A")), array('listcontent_close',array()), @@ -332,7 +345,7 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { } function testFootnoteQuote() { - $this->P->addMode('quote',new Doku_Parser_Mode_Quote()); + $this->P->addMode('quote',new Quote()); $this->P->parse("Foo (( > def >>ghi @@ -361,7 +374,7 @@ class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser { } function testFootnoteNesting() { - $this->P->addMode('strong',new Doku_Parser_Mode_Formatting('strong')); + $this->P->addMode('strong',new Formatting('strong')); $this->P->parse("(( a ** (( b )) ** c ))"); $calls = array( diff --git a/_test/tests/inc/parser/parser_headers.test.php b/_test/tests/inc/parser/parser_headers.test.php index a1bf7d2ba..d061899dd 100644 --- a/_test/tests/inc/parser/parser_headers.test.php +++ b/_test/tests/inc/parser/parser_headers.test.php @@ -1,10 +1,14 @@ <?php + +use dokuwiki\Parsing\ParserMode\Eol; +use dokuwiki\Parsing\ParserMode\Header; + require_once 'parser.inc.php'; class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { function testHeader1() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("abc \n ====== Header ====== \n def"); $calls = array ( array('document_start',array()), @@ -23,7 +27,7 @@ class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { } function testHeader2() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("abc \n ===== Header ===== \n def"); $calls = array ( array('document_start',array()), @@ -42,7 +46,7 @@ class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { } function testHeader3() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("abc \n ==== Header ==== \n def"); $calls = array ( array('document_start',array()), @@ -61,7 +65,7 @@ class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { } function testHeader4() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("abc \n === Header === \n def"); $calls = array ( array('document_start',array()), @@ -80,7 +84,7 @@ class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { } function testHeader5() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("abc \n == Header == \n def"); $calls = array ( array('document_start',array()), @@ -99,7 +103,7 @@ class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { } function testHeader2UnevenSmaller() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("abc \n ===== Header == \n def"); $calls = array ( array('document_start',array()), @@ -118,7 +122,7 @@ class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { } function testHeader2UnevenBigger() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("abc \n ===== Header =========== \n def"); $calls = array ( array('document_start',array()), @@ -137,7 +141,7 @@ class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { } function testHeaderLarge() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("abc \n ======= Header ======= \n def"); $calls = array ( array('document_start',array()), @@ -156,7 +160,7 @@ class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { } function testHeaderSmall() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("abc \n= Header =\n def"); $calls = array ( array('document_start',array()), @@ -170,7 +174,7 @@ class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { function testHeader1Mixed() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("abc \n====== == Header == ======\n def"); $calls = array ( array('document_start',array()), @@ -189,7 +193,7 @@ class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { } function testHeader5Mixed() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("abc \n== ====== Header ====== ==\n def"); $calls = array ( array('document_start',array()), @@ -208,7 +212,7 @@ class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { } function testHeaderMultiline() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("abc \n== ====== Header\n ====== ==\n def"); $calls = array ( array('document_start',array()), @@ -227,14 +231,14 @@ class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { } # function testNoToc() { -# $this->P->addMode('notoc',new Doku_Parser_Mode_NoToc()); +# $this->P->addMode('notoc',new NoToc()); # $this->P->parse('abc ~~NOTOC~~ def'); # $this->assertFalse($this->H->meta['toc']); # } function testHeader1Eol() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); - $this->P->addMode('eol',new Doku_Parser_Mode_Eol()); + $this->P->addMode('header',new Header()); + $this->P->addMode('eol',new Eol()); $this->P->parse("abc \n ====== Header ====== \n def"); $calls = array ( array('document_start',array()), @@ -254,7 +258,7 @@ class TestOfDoku_Parser_Headers extends TestOfDoku_Parser { } function testHeaderMulti2() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("abc \n ====== Header ====== \n def abc \n ===== Header2 ===== \n def"); $calls = array ( array('document_start',array()), diff --git a/_test/tests/inc/parser/parser_i18n.test.php b/_test/tests/inc/parser/parser_i18n.test.php index 096f2e227..b10bd9f3e 100644 --- a/_test/tests/inc/parser/parser_i18n.test.php +++ b/_test/tests/inc/parser/parser_i18n.test.php @@ -1,4 +1,11 @@ <?php + +use dokuwiki\Parsing\ParserMode\Acronym; +use dokuwiki\Parsing\ParserMode\Formatting; +use dokuwiki\Parsing\ParserMode\Header; +use dokuwiki\Parsing\ParserMode\Internallink; +use dokuwiki\Parsing\ParserMode\Table; + require_once 'parser.inc.php'; class TestOfDoku_Parser_i18n extends TestOfDoku_Parser { @@ -9,7 +16,7 @@ class TestOfDoku_Parser_i18n extends TestOfDoku_Parser { 'subscript', 'superscript', 'deleted', ); foreach ( $formats as $format ) { - $this->P->addMode($format,new Doku_Parser_Mode_Formatting($format)); + $this->P->addMode($format,new Formatting($format)); } $this->P->parse("I**ñ**t__ë__r//n//â<sup>t</sup>i<sub>ô</sub>n''à''liz<del>æ</del>tiøn"); $calls = array ( @@ -51,7 +58,7 @@ class TestOfDoku_Parser_i18n extends TestOfDoku_Parser { } function testHeader() { - $this->P->addMode('header',new Doku_Parser_Mode_Header()); + $this->P->addMode('header',new Header()); $this->P->parse("Foo\n ==== Iñtërnâtiônàlizætiøn ==== \n Bar"); $calls = array ( array('document_start',array()), @@ -70,7 +77,7 @@ class TestOfDoku_Parser_i18n extends TestOfDoku_Parser { } function testTable() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse(' abc | Row 0 Col 1 | Iñtërnâtiônàlizætiøn | Row 0 Col 3 | @@ -115,7 +122,7 @@ def'); function testAcronym() { $t = array('Iñtërnâtiônàlizætiøn'); - $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym($t)); + $this->P->addMode('acronym',new Acronym($t)); $this->P->parse("Foo Iñtërnâtiônàlizætiøn Bar"); $calls = array ( array('document_start',array()), @@ -130,7 +137,7 @@ def'); } function testInterwiki() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new InternalLink()); $this->P->parse("Foo [[wp>Iñtërnâtiônàlizætiøn|Iñtërnâtiônàlizætiøn]] Bar"); $calls = array ( array('document_start',array()), @@ -145,7 +152,7 @@ def'); } function testInternalLink() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new InternalLink()); $this->P->parse("Foo [[x:Iñtërnâtiônàlizætiøn:y:foo_bar:z|Iñtërnâtiônàlizætiøn]] Bar"); $calls = array ( array('document_start',array()), diff --git a/_test/tests/inc/parser/parser_links.test.php b/_test/tests/inc/parser/parser_links.test.php index ee001e73a..cbcfcb87b 100644 --- a/_test/tests/inc/parser/parser_links.test.php +++ b/_test/tests/inc/parser/parser_links.test.php @@ -1,4 +1,13 @@ <?php + +use dokuwiki\Parsing\ParserMode\Camelcaselink; +use dokuwiki\Parsing\ParserMode\Emaillink; +use dokuwiki\Parsing\ParserMode\Externallink; +use dokuwiki\Parsing\ParserMode\Filelink; +use dokuwiki\Parsing\ParserMode\Internallink; +use dokuwiki\Parsing\ParserMode\Media; +use dokuwiki\Parsing\ParserMode\Windowssharelink; + require_once 'parser.inc.php'; /** @@ -9,7 +18,7 @@ require_once 'parser.inc.php'; class TestOfDoku_Parser_Links extends TestOfDoku_Parser { function testExternalLinkSimple() { - $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink()); + $this->P->addMode('externallink',new Externallink()); $this->P->parse("Foo http://www.google.com Bar"); $calls = array ( array('document_start',array()), @@ -24,7 +33,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testExternalLinkCase() { - $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink()); + $this->P->addMode('externallink',new Externallink()); $this->P->parse("Foo HTTP://WWW.GOOGLE.COM Bar"); $calls = array ( array('document_start',array()), @@ -39,7 +48,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testExternalIPv4() { - $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink()); + $this->P->addMode('externallink',new Externallink()); $this->P->parse("Foo http://123.123.3.21/foo Bar"); $calls = array ( array('document_start',array()), @@ -54,7 +63,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testExternalIPv6() { - $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink()); + $this->P->addMode('externallink',new Externallink()); $this->P->parse("Foo http://[3ffe:2a00:100:7031::1]/foo Bar"); $calls = array ( array('document_start',array()), @@ -96,8 +105,8 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { $name = $title; } $this->setup(); - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); - $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink()); + $this->P->addMode('internallink',new Internallink()); + $this->P->addMode('externallink',new Externallink()); $this->P->parse("Foo $source Bar"); $calls = array ( array('document_start',array()), @@ -117,7 +126,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testExternalLinkJavascript() { - $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink()); + $this->P->addMode('externallink',new Externallink()); $this->P->parse("Foo javascript:alert('XSS'); Bar"); $calls = array ( array('document_start',array()), @@ -130,7 +139,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testExternalWWWLink() { - $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink()); + $this->P->addMode('externallink',new Externallink()); $this->P->parse("Foo www.google.com Bar"); $calls = array ( array('document_start',array()), @@ -145,7 +154,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testExternalWWWLinkInPath() { - $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink()); + $this->P->addMode('externallink',new Externallink()); // See issue #936. Should NOT generate a link! $this->P->parse("Foo /home/subdir/www/www.something.de/somedir/ Bar"); $calls = array ( @@ -159,7 +168,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testExternalWWWLinkFollowingPath() { - $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink()); + $this->P->addMode('externallink',new Externallink()); $this->P->parse("Foo /home/subdir/www/ www.something.de/somedir/ Bar"); $calls = array ( array('document_start',array()), @@ -174,7 +183,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testExternalFTPLink() { - $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink()); + $this->P->addMode('externallink',new Externallink()); $this->P->parse("Foo ftp.sunsite.com Bar"); $calls = array ( array('document_start',array()), @@ -189,7 +198,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testExternalFTPLinkInPath() { - $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink()); + $this->P->addMode('externallink',new Externallink()); // See issue #936. Should NOT generate a link! $this->P->parse("Foo /home/subdir/www/ftp.something.de/somedir/ Bar"); $calls = array ( @@ -203,7 +212,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testExternalFTPLinkFollowingPath() { - $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink()); + $this->P->addMode('externallink',new Externallink()); $this->P->parse("Foo /home/subdir/www/ ftp.something.de/somedir/ Bar"); $calls = array ( array('document_start',array()), @@ -218,7 +227,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testEmail() { - $this->P->addMode('emaillink',new Doku_Parser_Mode_Emaillink()); + $this->P->addMode('emaillink',new Emaillink()); $this->P->parse("Foo <bugs@php.net> Bar"); $calls = array ( array('document_start',array()), @@ -233,7 +242,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testEmailRFC2822() { - $this->P->addMode('emaillink',new Doku_Parser_Mode_Emaillink()); + $this->P->addMode('emaillink',new Emaillink()); $this->P->parse("Foo <~fix+bug's.for/ev{e}r@php.net> Bar"); $calls = array ( array('document_start',array()), @@ -248,7 +257,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testEmailCase() { - $this->P->addMode('emaillink',new Doku_Parser_Mode_Emaillink()); + $this->P->addMode('emaillink',new Emaillink()); $this->P->parse("Foo <bugs@pHp.net> Bar"); $calls = array ( array('document_start',array()), @@ -264,7 +273,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { function testInternalLinkOneChar() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[l]] Bar"); $calls = array ( array('document_start',array()), @@ -279,7 +288,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testInternalLinkNoChar() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[]] Bar"); $calls = array ( array('document_start',array()), @@ -294,7 +303,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testInternalLinkNamespaceNoTitle() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[foo:bar]] Bar"); $calls = array ( array('document_start',array()), @@ -309,7 +318,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testInternalLinkNamespace() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[x:1:y:foo_bar:z|Test]] Bar"); $calls = array ( array('document_start',array()), @@ -324,7 +333,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testInternalLinkSectionRef() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[wiki:syntax#internal|Syntax]] Bar"); $calls = array ( array('document_start',array()), @@ -339,7 +348,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testInternalLinkCodeFollows() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[wiki:internal:link|Test]] Bar <code>command [arg1 [arg2 [arg3]]]</code>"); $calls = array ( array('document_start',array()), @@ -354,7 +363,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testInternalLinkCodeFollows2() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[wiki:internal:link|[Square brackets in title] Test]] Bar <code>command [arg1 [arg2 [arg3]]]</code>"); $calls = array ( array('document_start',array()), @@ -369,7 +378,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testExternalInInternalLink() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[http://www.google.com|Google]] Bar"); $calls = array ( array('document_start',array()), @@ -384,7 +393,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testExternalInInternalLink2() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[http://www.google.com?test[]=squarebracketsinurl|Google]] Bar"); $calls = array ( array('document_start',array()), @@ -399,7 +408,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testExternalInInternalLink2CodeFollows() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[http://www.google.com?test[]=squarebracketsinurl|Google]] Bar <code>command [arg1 [arg2 [arg3]]]</code>"); $calls = array ( array('document_start',array()), @@ -414,7 +423,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testTwoInternalLinks() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[foo:bar|one]] and [[bar:foo|two]] Bar"); $calls = array ( array('document_start',array()), @@ -432,7 +441,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { function testInterwikiLink() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[iw>somepage|Some Page]] Bar"); $calls = array ( array('document_start',array()), @@ -447,7 +456,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testInterwikiLinkCase() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[IW>somepage|Some Page]] Bar"); $calls = array ( array('document_start',array()), @@ -462,7 +471,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testInterwikiPedia() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[wp>Callback_(computer_science)|callbacks]] Bar"); $calls = array ( array('document_start',array()), @@ -477,7 +486,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testCamelCase() { - $this->P->addMode('camelcaselink',new Doku_Parser_Mode_CamelCaseLink()); + $this->P->addMode('camelcaselink',new Camelcaselink()); $this->P->parse("Foo FooBar Bar"); $calls = array ( array('document_start',array()), @@ -492,7 +501,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testFileLink() { - $this->P->addMode('filelink',new Doku_Parser_Mode_FileLink()); + $this->P->addMode('filelink',new FileLink()); $this->P->parse('Foo file://temp/file.txt Bar'); $calls = array ( array('document_start',array()), @@ -507,7 +516,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testFileLinkInternal() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse('Foo [[file://temp/file.txt|Some File]] Bar'); $calls = array ( array('document_start',array()), @@ -522,7 +531,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testWindowsShareLink() { - $this->P->addMode('windowssharelink',new Doku_Parser_Mode_WindowsShareLink()); + $this->P->addMode('windowssharelink',new Windowssharelink()); $this->P->parse('Foo \\\server\share Bar'); $calls = array ( array('document_start',array()), @@ -537,7 +546,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testWindowsShareLinkHyphen() { - $this->P->addMode('windowssharelink',new Doku_Parser_Mode_WindowsShareLink()); + $this->P->addMode('windowssharelink',new Windowssharelink()); $this->P->parse('Foo \\\server\share-hyphen Bar'); $calls = array ( array('document_start',array()), @@ -552,7 +561,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testWindowsShareLinkInternal() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse('Foo [[\\\server\share|My Documents]] Bar'); $calls = array ( array('document_start',array()), @@ -567,7 +576,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaInternal() { - $this->P->addMode('media',new Doku_Parser_Mode_Media()); + $this->P->addMode('media',new Media()); $this->P->parse('Foo {{img.gif}} Bar'); $calls = array ( array('document_start',array()), @@ -582,7 +591,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaInternalLinkOnly() { - $this->P->addMode('media',new Doku_Parser_Mode_Media()); + $this->P->addMode('media',new Media()); $this->P->parse('Foo {{img.gif?linkonly}} Bar'); $calls = array ( array('document_start',array()), @@ -597,7 +606,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaNotImage() { - $this->P->addMode('media',new Doku_Parser_Mode_Media()); + $this->P->addMode('media',new Media()); $this->P->parse('Foo {{foo.txt?10x10|Some File}} Bar'); $calls = array ( array('document_start',array()), @@ -612,7 +621,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaInternalLAlign() { - $this->P->addMode('media',new Doku_Parser_Mode_Media()); + $this->P->addMode('media',new Media()); $this->P->parse('Foo {{img.gif }} Bar'); $calls = array ( array('document_start',array()), @@ -627,7 +636,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaInternalRAlign() { - $this->P->addMode('media',new Doku_Parser_Mode_Media()); + $this->P->addMode('media',new Media()); $this->P->parse('Foo {{ img.gif}} Bar'); $calls = array ( array('document_start',array()), @@ -642,7 +651,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaInternalCenter() { - $this->P->addMode('media',new Doku_Parser_Mode_Media()); + $this->P->addMode('media',new Media()); $this->P->parse('Foo {{ img.gif }} Bar'); $calls = array ( array('document_start',array()), @@ -657,7 +666,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaInternalParams() { - $this->P->addMode('media',new Doku_Parser_Mode_Media()); + $this->P->addMode('media',new Media()); $this->P->parse('Foo {{img.gif?50x100nocache}} Bar'); $calls = array ( array('document_start',array()), @@ -672,7 +681,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaInternalTitle() { - $this->P->addMode('media',new Doku_Parser_Mode_Media()); + $this->P->addMode('media',new Media()); $this->P->parse('Foo {{img.gif?50x100|Some Image}} Bar'); $calls = array ( array('document_start',array()), @@ -687,7 +696,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaExternal() { - $this->P->addMode('media',new Doku_Parser_Mode_Media()); + $this->P->addMode('media',new Media()); $this->P->parse('Foo {{http://www.google.com/img.gif}} Bar'); $calls = array ( array('document_start',array()), @@ -702,7 +711,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaExternalParams() { - $this->P->addMode('media',new Doku_Parser_Mode_Media()); + $this->P->addMode('media',new Media()); $this->P->parse('Foo {{http://www.google.com/img.gif?50x100nocache}} Bar'); $calls = array ( array('document_start',array()), @@ -717,7 +726,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaExternalTitle() { - $this->P->addMode('media',new Doku_Parser_Mode_Media()); + $this->P->addMode('media',new Media()); $this->P->parse('Foo {{http://www.google.com/img.gif?50x100|Some Image}} Bar'); $calls = array ( array('document_start',array()), @@ -733,7 +742,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaInInternalLink() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[x:1:y:foo_bar:z|{{img.gif?10x20nocache|Some Image}}]] Bar"); $image = array( @@ -760,7 +769,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaNoImageInInternalLink() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[x:1:y:foo_bar:z|{{foo.txt?10x20nocache|Some Image}}]] Bar"); $image = array( @@ -787,7 +796,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testMediaInEmailLink() { - $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink()); + $this->P->addMode('internallink',new Internallink()); $this->P->parse("Foo [[foo@example.com|{{img.gif?10x20nocache|Some Image}}]] Bar"); $image = array( @@ -814,7 +823,7 @@ class TestOfDoku_Parser_Links extends TestOfDoku_Parser { } function testNestedMedia() { - $this->P->addMode('media',new Doku_Parser_Mode_Media()); + $this->P->addMode('media',new Media()); $this->P->parse('Foo {{img.gif|{{foo.gif|{{bar.gif|Bar}}}}}} Bar'); $calls = array ( array('document_start',array()), diff --git a/_test/tests/inc/parser/parser_lists.test.php b/_test/tests/inc/parser/parser_lists.test.php index 6acaff637..2176af76d 100644 --- a/_test/tests/inc/parser/parser_lists.test.php +++ b/_test/tests/inc/parser/parser_lists.test.php @@ -1,10 +1,19 @@ <?php + +use dokuwiki\Parsing\Handler\Lists; +use dokuwiki\Parsing\ParserMode\Eol; +use dokuwiki\Parsing\ParserMode\Footnote; +use dokuwiki\Parsing\ParserMode\Formatting; +use dokuwiki\Parsing\ParserMode\Linebreak; +use dokuwiki\Parsing\ParserMode\Listblock; +use dokuwiki\Parsing\ParserMode\Unformatted; + require_once 'parser.inc.php'; class TestOfDoku_Parser_Lists extends TestOfDoku_Parser { function testUnorderedList() { - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); + $this->P->addMode('listblock',new Listblock()); $this->P->parse(' *A * B @@ -13,7 +22,7 @@ class TestOfDoku_Parser_Lists extends TestOfDoku_Parser { $calls = array ( array('document_start',array()), array('listu_open',array()), - array('listitem_open',array(1,Doku_Handler_List::NODE)), + array('listitem_open',array(1,Lists::NODE)), array('listcontent_open',array()), array('cdata',array("A")), array('listcontent_close',array()), @@ -37,7 +46,7 @@ class TestOfDoku_Parser_Lists extends TestOfDoku_Parser { } function testOrderedList() { - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); + $this->P->addMode('listblock',new Listblock()); $this->P->parse(' -A - B @@ -46,7 +55,7 @@ class TestOfDoku_Parser_Lists extends TestOfDoku_Parser { $calls = array ( array('document_start',array()), array('listo_open',array()), - array('listitem_open',array(1,Doku_Handler_List::NODE)), + array('listitem_open',array(1,Lists::NODE)), array('listcontent_open',array()), array('cdata',array("A")), array('listcontent_close',array()), @@ -71,7 +80,7 @@ class TestOfDoku_Parser_Lists extends TestOfDoku_Parser { function testMixedList() { - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); + $this->P->addMode('listblock',new Listblock()); $this->P->parse(' -A * B @@ -80,7 +89,7 @@ class TestOfDoku_Parser_Lists extends TestOfDoku_Parser { $calls = array ( array('document_start',array()), array('listo_open',array()), - array('listitem_open',array(1,Doku_Handler_List::NODE)), + array('listitem_open',array(1,Lists::NODE)), array('listcontent_open',array()), array('cdata',array("A")), array('listcontent_close',array()), @@ -102,14 +111,14 @@ class TestOfDoku_Parser_Lists extends TestOfDoku_Parser { ); $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls); } - + function testUnorderedListWinEOL() { - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); + $this->P->addMode('listblock',new Listblock()); $this->P->parse("\r\n *A\r\n * B\r\n * C\r\n"); $calls = array ( array('document_start',array()), array('listu_open',array()), - array('listitem_open',array(1,Doku_Handler_List::NODE)), + array('listitem_open',array(1,Lists::NODE)), array('listcontent_open',array()), array('cdata',array("A")), array('listcontent_close',array()), @@ -131,14 +140,14 @@ class TestOfDoku_Parser_Lists extends TestOfDoku_Parser { ); $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls); } - + function testOrderedListWinEOL() { - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); + $this->P->addMode('listblock',new Listblock()); $this->P->parse("\r\n -A\r\n - B\r\n - C\r\n"); $calls = array ( array('document_start',array()), array('listo_open',array()), - array('listitem_open',array(1,Doku_Handler_List::NODE)), + array('listitem_open',array(1,Lists::NODE)), array('listcontent_open',array()), array('cdata',array("A")), array('listcontent_close',array()), @@ -160,9 +169,9 @@ class TestOfDoku_Parser_Lists extends TestOfDoku_Parser { ); $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls); } - + function testNotAList() { - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); + $this->P->addMode('listblock',new Listblock()); $this->P->parse("Foo -bar *foo Bar"); $calls = array ( array('document_start',array()), @@ -173,10 +182,10 @@ class TestOfDoku_Parser_Lists extends TestOfDoku_Parser { ); $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls); } - + function testUnorderedListParagraph() { - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); - $this->P->addMode('eol',new Doku_Parser_Mode_Eol()); + $this->P->addMode('listblock',new Listblock()); + $this->P->addMode('eol',new Eol()); $this->P->parse('Foo *A * B @@ -188,7 +197,7 @@ Bar'); array('cdata',array("Foo")), array('p_close',array()), array('listu_open',array()), - array('listitem_open',array(1,Doku_Handler_List::NODE)), + array('listitem_open',array(1,Lists::NODE)), array('listcontent_open',array()), array('cdata',array("A")), array('listcontent_close',array()), @@ -213,12 +222,12 @@ Bar'); ); $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls); } - + // This is really a failing test - formatting able to spread across list items // Problem is fixing it would mean a major rewrite of lists function testUnorderedListStrong() { - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); - $this->P->addMode('strong',new Doku_Parser_Mode_Formatting('strong')); + $this->P->addMode('listblock',new Listblock()); + $this->P->addMode('strong',new Formatting('strong')); $this->P->parse(' ***A** *** B @@ -227,7 +236,7 @@ Bar'); $calls = array ( array('document_start',array()), array('listu_open',array()), - array('listitem_open',array(1,Doku_Handler_List::NODE)), + array('listitem_open',array(1,Lists::NODE)), array('listcontent_open',array()), array('strong_open',array()), array('cdata',array("A")), @@ -248,12 +257,12 @@ Bar'); ); $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls); } - + // This is really a failing test - unformatted able to spread across list items // Problem is fixing it would mean a major rewrite of lists function testUnorderedListUnformatted() { - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); - $this->P->addMode('unformatted',new Doku_Parser_Mode_Unformatted()); + $this->P->addMode('listblock',new Listblock()); + $this->P->addMode('unformatted',new Unformatted()); $this->P->parse(' *%%A%% *%% B @@ -262,7 +271,7 @@ Bar'); $calls = array ( array('document_start',array()), array('listu_open',array()), - array('listitem_open',array(1,Doku_Handler_List::NODE)), + array('listitem_open',array(1,Lists::NODE)), array('listcontent_open',array()), array('unformatted',array("A")), array('listcontent_close',array()), @@ -279,10 +288,10 @@ Bar'); ); $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls); } - + function testUnorderedListLinebreak() { - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); - $this->P->addMode('linebreak',new Doku_Parser_Mode_Linebreak()); + $this->P->addMode('listblock',new Listblock()); + $this->P->addMode('linebreak',new Linebreak()); $this->P->parse(' *A\\\\ D * B @@ -291,7 +300,7 @@ Bar'); $calls = array ( array('document_start',array()), array('listu_open',array()), - array('listitem_open',array(1,Doku_Handler_List::NODE)), + array('listitem_open',array(1,Lists::NODE)), array('listcontent_open',array()), array('cdata',array("A")), array('linebreak',array()), @@ -315,10 +324,10 @@ Bar'); ); $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls); } - + function testUnorderedListLinebreak2() { - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); - $this->P->addMode('linebreak',new Doku_Parser_Mode_Linebreak()); + $this->P->addMode('listblock',new Listblock()); + $this->P->addMode('linebreak',new Linebreak()); $this->P->parse(' *A\\\\ * B @@ -342,10 +351,10 @@ Bar'); ); $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls); } - + function testUnorderedListFootnote() { - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); - $this->P->addMode('footnote',new Doku_Parser_Mode_Footnote()); + $this->P->addMode('listblock',new Listblock()); + $this->P->addMode('footnote',new Footnote()); $this->P->parse(' *((A)) *(( B @@ -355,7 +364,7 @@ Bar'); $calls = array ( array('document_start',array()), array('listu_open',array()), - array('listitem_open',array(1,Doku_Handler_List::NODE)), + array('listitem_open',array(1,Lists::NODE)), array('listcontent_open',array()), array('nest', array( array( array('footnote_open',array()), diff --git a/_test/tests/inc/parser/parser_preformatted.test.php b/_test/tests/inc/parser/parser_preformatted.test.php index f7a01a7e5..ad99f2916 100644 --- a/_test/tests/inc/parser/parser_preformatted.test.php +++ b/_test/tests/inc/parser/parser_preformatted.test.php @@ -1,10 +1,20 @@ <?php + +use dokuwiki\Parsing\ParserMode\Code; +use dokuwiki\Parsing\ParserMode\Eol; +use dokuwiki\Parsing\ParserMode\File; +use dokuwiki\Parsing\ParserMode\Header; +use dokuwiki\Parsing\ParserMode\Html; +use dokuwiki\Parsing\ParserMode\Listblock; +use dokuwiki\Parsing\ParserMode\Php; +use dokuwiki\Parsing\ParserMode\Preformatted; + require_once 'parser.inc.php'; class TestOfDoku_Parser_Preformatted extends TestOfDoku_Parser { function testFile() { - $this->P->addMode('file',new Doku_Parser_Mode_File()); + $this->P->addMode('file',new File()); $this->P->parse('Foo <file>testing</file> Bar'); $calls = array ( array('document_start',array()), @@ -22,7 +32,7 @@ class TestOfDoku_Parser_Preformatted extends TestOfDoku_Parser { } function testCode() { - $this->P->addMode('code',new Doku_Parser_Mode_Code()); + $this->P->addMode('code',new Code()); $this->P->parse('Foo <code>testing</code> Bar'); $calls = array ( array('document_start',array()), @@ -39,7 +49,7 @@ class TestOfDoku_Parser_Preformatted extends TestOfDoku_Parser { } function testCodeWhitespace() { - $this->P->addMode('code',new Doku_Parser_Mode_Code()); + $this->P->addMode('code',new Code()); $this->P->parse("Foo <code \n>testing</code> Bar"); $calls = array ( array('document_start',array()), @@ -56,7 +66,7 @@ class TestOfDoku_Parser_Preformatted extends TestOfDoku_Parser { } function testCodeLang() { - $this->P->addMode('code',new Doku_Parser_Mode_Code()); + $this->P->addMode('code',new Code()); $this->P->parse("Foo <code php>testing</code> Bar"); $calls = array ( array('document_start',array()), @@ -73,7 +83,7 @@ class TestOfDoku_Parser_Preformatted extends TestOfDoku_Parser { } function testPreformatted() { - $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted()); + $this->P->addMode('preformatted',new Preformatted()); $this->P->parse("F oo\n x \n y \nBar\n"); $calls = array ( array('document_start',array()), @@ -90,7 +100,7 @@ class TestOfDoku_Parser_Preformatted extends TestOfDoku_Parser { } function testPreformattedWinEOL() { - $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted()); + $this->P->addMode('preformatted',new Preformatted()); $this->P->parse("F oo\r\n x \r\n y \r\nBar\r\n"); $calls = array ( array('document_start',array()), @@ -107,7 +117,7 @@ class TestOfDoku_Parser_Preformatted extends TestOfDoku_Parser { } function testPreformattedTab() { - $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted()); + $this->P->addMode('preformatted',new Preformatted()); $this->P->parse("F oo\n\tx\t\n\t\ty\t\nBar\n"); $calls = array ( array('document_start',array()), @@ -124,7 +134,7 @@ class TestOfDoku_Parser_Preformatted extends TestOfDoku_Parser { } function testPreformattedTabWinEOL() { - $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted()); + $this->P->addMode('preformatted',new Preformatted()); $this->P->parse("F oo\r\n\tx\t\r\n\t\ty\t\r\nBar\r\n"); $calls = array ( array('document_start',array()), @@ -141,8 +151,8 @@ class TestOfDoku_Parser_Preformatted extends TestOfDoku_Parser { } function testPreformattedList() { - $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted()); - $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock()); + $this->P->addMode('preformatted',new Preformatted()); + $this->P->addMode('listblock',new Listblock()); $this->P->parse(" - x \n * y \nF oo\n x \n y \n -X\n *Y\nBar\n"); $calls = array ( array('document_start',array()), @@ -175,7 +185,7 @@ class TestOfDoku_Parser_Preformatted extends TestOfDoku_Parser { // test for php function testPHP() { - $this->P->addMode('php',new Doku_Parser_Mode_PHP()); + $this->P->addMode('php',new Php()); $this->P->parse('Foo <php>testing</php> Bar'); $calls = array ( array('document_start',array()), @@ -192,7 +202,7 @@ class TestOfDoku_Parser_Preformatted extends TestOfDoku_Parser { // test with for HTML function testHTML() { - $this->P->addMode('html',new Doku_Parser_Mode_HTML()); + $this->P->addMode('html',new Html()); $this->P->parse('Foo <html>testing</html> Bar'); $calls = array ( array('document_start',array()), @@ -210,9 +220,9 @@ class TestOfDoku_Parser_Preformatted extends TestOfDoku_Parser { function testPreformattedPlusHeaderAndEol() { // Note that EOL must come after preformatted! - $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted()); - $this->P->addMode('header',new Doku_Parser_Mode_Header()); - $this->P->addMode('eol',new Doku_Parser_Mode_Eol()); + $this->P->addMode('preformatted',new Preformatted()); + $this->P->addMode('header',new Header()); + $this->P->addMode('eol',new Eol()); $this->P->parse("F oo\n ==Test==\n y \nBar\n"); $calls = array ( array('document_start',array()), diff --git a/_test/tests/inc/parser/parser_quote.test.php b/_test/tests/inc/parser/parser_quote.test.php index ae14671c1..190f18cc1 100644 --- a/_test/tests/inc/parser/parser_quote.test.php +++ b/_test/tests/inc/parser/parser_quote.test.php @@ -1,10 +1,14 @@ <?php + +use dokuwiki\Parsing\ParserMode\Eol; +use dokuwiki\Parsing\ParserMode\Quote; + require_once 'parser.inc.php'; class TestOfDoku_Parser_Quote extends TestOfDoku_Parser { function testQuote() { - $this->P->addMode('quote',new Doku_Parser_Mode_Quote()); + $this->P->addMode('quote',new Quote()); $this->P->parse("abc\n> def\n>>ghi\nklm"); $calls = array ( array('document_start',array()), @@ -27,7 +31,7 @@ class TestOfDoku_Parser_Quote extends TestOfDoku_Parser { } function testQuoteWinCr() { - $this->P->addMode('quote',new Doku_Parser_Mode_Quote()); + $this->P->addMode('quote',new Quote()); $this->P->parse("abc\r\n> def\r\n>>ghi\r\nklm"); $calls = array ( array('document_start',array()), @@ -50,7 +54,7 @@ class TestOfDoku_Parser_Quote extends TestOfDoku_Parser { } function testQuoteMinumumContext() { - $this->P->addMode('quote',new Doku_Parser_Mode_Quote()); + $this->P->addMode('quote',new Quote()); $this->P->parse("\n> def\n>>ghi\n "); $calls = array ( array('document_start',array()), @@ -67,8 +71,8 @@ class TestOfDoku_Parser_Quote extends TestOfDoku_Parser { } function testQuoteEol() { - $this->P->addMode('quote',new Doku_Parser_Mode_Quote()); - $this->P->addMode('eol',new Doku_Parser_Mode_Eol()); + $this->P->addMode('quote',new Quote()); + $this->P->addMode('eol',new Eol()); $this->P->parse("abc\n> def\n>>ghi\nklm"); $calls = array ( array('document_start',array()), diff --git a/_test/tests/inc/parser/parser_quotes.test.php b/_test/tests/inc/parser/parser_quotes.test.php index 6f174ddae..fb192d21f 100644 --- a/_test/tests/inc/parser/parser_quotes.test.php +++ b/_test/tests/inc/parser/parser_quotes.test.php @@ -1,4 +1,7 @@ <?php + +use dokuwiki\Parsing\ParserMode\Quotes; + require_once 'parser.inc.php'; class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { @@ -11,7 +14,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testSingleQuoteOpening() { $raw = "Foo 'hello Bar"; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( @@ -29,7 +32,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testSingleQuoteOpeningSpecial() { $raw = "Foo said:'hello Bar"; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( @@ -47,7 +50,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testSingleQuoteClosing() { $raw = "Foo hello' Bar"; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( @@ -65,7 +68,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testSingleQuoteClosingSpecial() { $raw = "Foo hello') Bar"; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( @@ -83,7 +86,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testSingleQuotes() { $raw = "Foo 'hello' Bar"; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( @@ -103,7 +106,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testApostrophe() { $raw = "hey it's fine weather today"; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( @@ -122,7 +125,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testSingleQuotesSpecial() { $raw = "Foo ('hello') Bar"; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( @@ -142,7 +145,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testDoubleQuoteOpening() { $raw = 'Foo "hello Bar'; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( @@ -160,7 +163,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testDoubleQuoteOpeningSpecial() { $raw = 'Foo said:"hello Bar'; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( @@ -178,8 +181,13 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testDoubleQuoteClosing() { $raw = 'Foo hello" Bar'; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); - $this->H->status['doublequote'] = 1; + $this->P->addMode('quotes',new Quotes()); + + /** @noinspection PhpUnhandledExceptionInspection */ + $status = $this->getInaccessibleProperty($this->H, 'status'); + $status['doublequote'] = 1; + /** @noinspection PhpUnhandledExceptionInspection */ + $this->setInaccessibleProperty($this->H, 'status', $status); $this->P->parse($raw); $calls = array ( @@ -197,8 +205,13 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testDoubleQuoteClosingSpecial() { $raw = 'Foo hello") Bar'; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); - $this->H->status['doublequote'] = 1; + $this->P->addMode('quotes',new Quotes()); + /** @noinspection PhpUnhandledExceptionInspection */ + $status = $this->getInaccessibleProperty($this->H, 'status'); + $status['doublequote'] = 1; + /** @noinspection PhpUnhandledExceptionInspection */ + $this->setInaccessibleProperty($this->H, 'status', $status); + $this->P->parse($raw); $calls = array ( @@ -215,8 +228,13 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { } function testDoubleQuoteClosingSpecial2() { $raw = 'Foo hello") Bar'; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); - $this->H->status['doublequote'] = 0; + $this->P->addMode('quotes',new Quotes()); + /** @noinspection PhpUnhandledExceptionInspection */ + $status = $this->getInaccessibleProperty($this->H, 'status'); + $status['doublequote'] = 0; + /** @noinspection PhpUnhandledExceptionInspection */ + $this->setInaccessibleProperty($this->H, 'status', $status); + $this->P->parse($raw); $calls = array ( @@ -234,7 +252,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testDoubleQuotes() { $raw = 'Foo "hello" Bar'; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( @@ -254,7 +272,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testDoubleQuotesSpecial() { $raw = 'Foo ("hello") Bar'; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( @@ -274,7 +292,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testDoubleQuotesEnclosingBrackets() { $raw = 'Foo "{hello}" Bar'; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( @@ -294,7 +312,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testDoubleQuotesEnclosingLink() { $raw = 'Foo "[[www.domain.com]]" Bar'; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( @@ -315,7 +333,7 @@ class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser { function testAllQuotes() { $raw = 'There was written "He thought \'It\'s a man\'s world\'".'; - $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes()); + $this->P->addMode('quotes',new Quotes()); $this->P->parse($raw); $calls = array ( diff --git a/_test/tests/inc/parser/parser_replacements.test.php b/_test/tests/inc/parser/parser_replacements.test.php index f0367dac0..d910dba9e 100644 --- a/_test/tests/inc/parser/parser_replacements.test.php +++ b/_test/tests/inc/parser/parser_replacements.test.php @@ -1,10 +1,18 @@ <?php + +use dokuwiki\Parsing\ParserMode\Acronym; +use dokuwiki\Parsing\ParserMode\Entity; +use dokuwiki\Parsing\ParserMode\Hr; +use dokuwiki\Parsing\ParserMode\Multiplyentity; +use dokuwiki\Parsing\ParserMode\Smiley; +use dokuwiki\Parsing\ParserMode\Wordblock; + require_once 'parser.inc.php'; class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { function testSingleAcronym() { - $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym(array('FOOBAR'))); + $this->P->addMode('acronym',new Acronym(array('FOOBAR'))); $this->P->parse('abc FOOBAR xyz'); $calls = array ( @@ -21,7 +29,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testAlmostAnAcronym() { - $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym(array('FOOBAR'))); + $this->P->addMode('acronym',new Acronym(array('FOOBAR'))); $this->P->parse('abcFOOBARxyz'); $calls = array ( @@ -36,7 +44,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testPickAcronymCorrectly() { - $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym(array('FOO'))); + $this->P->addMode('acronym',new Acronym(array('FOO'))); $this->P->parse('FOOBAR FOO'); $calls = array ( @@ -53,7 +61,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testMultipleAcronyms() { - $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym(array('FOO','BAR'))); + $this->P->addMode('acronym',new Acronym(array('FOO','BAR'))); $this->P->parse('abc FOO def BAR xyz'); $calls = array ( @@ -73,7 +81,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testMultipleAcronymsWithSubset1() { - $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym(array('FOO','A.FOO','FOO.1','A.FOO.1'))); + $this->P->addMode('acronym',new Acronym(array('FOO','A.FOO','FOO.1','A.FOO.1'))); $this->P->parse('FOO A.FOO FOO.1 A.FOO.1'); $calls = array ( @@ -96,7 +104,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testMultipleAcronymsWithSubset2() { - $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym(array('A.FOO.1','FOO.1','A.FOO','FOO'))); + $this->P->addMode('acronym',new Acronym(array('A.FOO.1','FOO.1','A.FOO','FOO'))); $this->P->parse('FOO A.FOO FOO.1 A.FOO.1'); $calls = array ( @@ -119,7 +127,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testSingleSmileyFail() { - $this->P->addMode('smiley',new Doku_Parser_Mode_Smiley(array(':-)'))); + $this->P->addMode('smiley',new Smiley(array(':-)'))); $this->P->parse('abc:-)xyz'); $calls = array ( @@ -134,7 +142,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testSingleSmiley() { - $this->P->addMode('smiley',new Doku_Parser_Mode_Smiley(array(':-)'))); + $this->P->addMode('smiley',new Smiley(array(':-)'))); $this->P->parse('abc :-) xyz'); $calls = array ( @@ -151,7 +159,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testMultipleSmileysFail() { - $this->P->addMode('smiley',new Doku_Parser_Mode_Smiley(array(':-)','^_^'))); + $this->P->addMode('smiley',new Smiley(array(':-)','^_^'))); $this->P->parse('abc:-)x^_^yz'); $calls = array ( @@ -166,7 +174,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testMultipleSmileys() { - $this->P->addMode('smiley',new Doku_Parser_Mode_Smiley(array(':-)','^_^'))); + $this->P->addMode('smiley',new Smiley(array(':-)','^_^'))); $this->P->parse('abc :-) x ^_^ yz'); $calls = array ( @@ -186,7 +194,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { function testBackslashSmileyFail() { // This smiley is really :-\\ but escaping makes like interesting - $this->P->addMode('smiley',new Doku_Parser_Mode_Smiley(array(':-\\\\'))); + $this->P->addMode('smiley',new Smiley(array(':-\\\\'))); $this->P->parse('abc:-\\\xyz'); $calls = array ( @@ -202,7 +210,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { function testBackslashSmiley() { // This smiley is really :-\\ but escaping makes like interesting - $this->P->addMode('smiley',new Doku_Parser_Mode_Smiley(array(':-\\\\'))); + $this->P->addMode('smiley',new Smiley(array(':-\\\\'))); $this->P->parse('abc :-\\\ xyz'); $calls = array ( @@ -219,7 +227,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testSingleWordblock() { - $this->P->addMode('wordblock',new Doku_Parser_Mode_Wordblock(array('CAT'))); + $this->P->addMode('wordblock',new Wordblock(array('CAT'))); $this->P->parse('abc CAT xyz'); $calls = array ( @@ -236,7 +244,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testWordblockCase() { - $this->P->addMode('wordblock',new Doku_Parser_Mode_Wordblock(array('CAT'))); + $this->P->addMode('wordblock',new Wordblock(array('CAT'))); $this->P->parse('abc cat xyz'); $calls = array ( @@ -253,7 +261,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testMultipleWordblock() { - $this->P->addMode('wordblock',new Doku_Parser_Mode_Wordblock(array('CAT','dog'))); + $this->P->addMode('wordblock',new Wordblock(array('CAT','dog'))); $this->P->parse('abc cat x DOG yz'); $calls = array ( @@ -272,7 +280,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testSingleEntity() { - $this->P->addMode('entity',new Doku_Parser_Mode_Entity(array('->'))); + $this->P->addMode('entity',new Entity(array('->'))); $this->P->parse('x -> y'); $calls = array ( @@ -289,7 +297,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testMultipleEntities() { - $this->P->addMode('entity',new Doku_Parser_Mode_Entity(array('->','<-'))); + $this->P->addMode('entity',new Entity(array('->','<-'))); $this->P->parse('x -> y <- z'); $calls = array ( @@ -308,7 +316,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testMultiplyEntity() { - $this->P->addMode('multiplyentity',new Doku_Parser_Mode_MultiplyEntity()); + $this->P->addMode('multiplyentity',new Multiplyentity()); $this->P->parse('Foo 10x20 Bar'); $calls = array ( @@ -326,7 +334,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { function testMultiplyEntityHex() { // the multiply entity pattern should not match hex numbers, eg. 0x123 - $this->P->addMode('multiplyentity',new Doku_Parser_Mode_MultiplyEntity()); + $this->P->addMode('multiplyentity',new Multiplyentity()); $this->P->parse('Foo 0x123 Bar'); $calls = array ( @@ -341,7 +349,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testHR() { - $this->P->addMode('hr',new Doku_Parser_Mode_HR()); + $this->P->addMode('hr',new Hr()); $this->P->parse("Foo \n ---- \n Bar"); $calls = array ( @@ -359,7 +367,7 @@ class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser { } function testHREol() { - $this->P->addMode('hr',new Doku_Parser_Mode_HR()); + $this->P->addMode('hr',new Hr()); $this->P->parse("Foo \n----\n Bar"); $calls = array ( diff --git a/_test/tests/inc/parser/parser_table.test.php b/_test/tests/inc/parser/parser_table.test.php index f05dd29aa..c233a4072 100644 --- a/_test/tests/inc/parser/parser_table.test.php +++ b/_test/tests/inc/parser/parser_table.test.php @@ -1,10 +1,18 @@ <?php + +use dokuwiki\Parsing\ParserMode\Eol; +use dokuwiki\Parsing\ParserMode\Footnote; +use dokuwiki\Parsing\ParserMode\Formatting; +use dokuwiki\Parsing\ParserMode\Linebreak; +use dokuwiki\Parsing\ParserMode\Table; +use dokuwiki\Parsing\ParserMode\Unformatted; + require_once 'parser.inc.php'; class TestOfDoku_Parser_Table extends TestOfDoku_Parser { function testTable() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse(' abc | Row 0 Col 1 | Row 0 Col 2 | Row 0 Col 3 | @@ -48,7 +56,7 @@ def'); } function testTableWinEOL() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse("\r\nabc\r\n| Row 0 Col 1 | Row 0 Col 2 | Row 0 Col 3 |\r\n| Row 1 Col 1 | Row 1 Col 2 | Row 1 Col 3 |\r\ndef"); $calls = array ( array('document_start',array()), @@ -88,7 +96,7 @@ def'); } function testEmptyTable() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse(' abc | @@ -113,7 +121,7 @@ def'); } function testTableHeaders() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse(' abc ^ X | Y ^ Z | @@ -148,7 +156,7 @@ def'); } function testTableHead() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse(' abc ^ X ^ Y ^ Z ^ @@ -197,7 +205,7 @@ def'); } function testTableHeadOneRowTable() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse(' abc ^ X ^ Y ^ Z ^ @@ -232,7 +240,7 @@ def'); } function testTableHeadMultiline() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse(' abc ^ X1 ^ Y1 ^ Z1 ^ @@ -293,7 +301,7 @@ def'); } function testCellAlignment() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse(' abc | X | Y ^ Z | @@ -327,7 +335,7 @@ def'); } function testCellSpan() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse(' abc | d || e | @@ -369,7 +377,7 @@ def'); } function testCellRowSpan() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse(' abc | a | c:::|| @@ -417,7 +425,7 @@ def'); } function testCellRowSpanFirstRow() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse(' abc |::: ^ d:::^:::| ::: | @@ -475,7 +483,7 @@ def'); } function testRowSpanTableHead() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse(' abc ^ X1 ^ Y1 ^ Z1 ^ @@ -533,7 +541,7 @@ def'); } function testRowSpanAcrossTableHeadBoundary() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse(' abc ^ X1 ^ Y1 ^ Z1 ^ @@ -600,8 +608,8 @@ def'); } function testCellAlignmentFormatting() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); - $this->P->addMode('strong',new Doku_Parser_Mode_Formatting('strong')); + $this->P->addMode('table',new Table()); + $this->P->addMode('strong',new Formatting('strong')); $this->P->parse(' abc | **X** | Y ^ Z | @@ -640,8 +648,8 @@ def'); } function testTableEol() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); - $this->P->addMode('eol',new Doku_Parser_Mode_Eol()); + $this->P->addMode('table',new Table()); + $this->P->addMode('eol',new Eol()); $this->P->parse(' abc | Row 0 Col 1 | Row 0 Col 2 | Row 0 Col 3 | @@ -687,8 +695,8 @@ def'); // This is really a failing test - formatting able to spread across cols // Problem is fixing it would mean a major rewrite of table handling function testTableStrong() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); - $this->P->addMode('strong',new Doku_Parser_Mode_Formatting('strong')); + $this->P->addMode('table',new Table()); + $this->P->addMode('strong',new Formatting('strong')); $this->P->parse(' abc | **Row 0 Col 1** | **Row 0 Col 2 | Row 0 Col 3** | @@ -742,8 +750,8 @@ def'); // This is really a failing test - unformatted able to spread across cols // Problem is fixing it would mean a major rewrite of table handling function testTableUnformatted() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); - $this->P->addMode('unformatted',new Doku_Parser_Mode_Unformatted()); + $this->P->addMode('table',new Table()); + $this->P->addMode('unformatted',new Unformatted()); $this->P->parse(' abc | <nowiki>Row 0 Col 1</nowiki> | <nowiki>Row 0 Col 2 | Row 0 Col 3</nowiki> | @@ -791,8 +799,8 @@ def'); } function testTableLinebreak() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); - $this->P->addMode('linebreak',new Doku_Parser_Mode_Linebreak()); + $this->P->addMode('table',new Table()); + $this->P->addMode('linebreak',new Linebreak()); $this->P->parse(' abc | Row 0\\\\ Col 1 | Row 0 Col 2 | Row 0 Col 3 | @@ -841,8 +849,8 @@ def'); // This is really a failing test - footnote able to spread across cols // Problem is fixing it would mean a major rewrite of table handling function testTableFootnote() { - $this->P->addMode('table',new Doku_Parser_Mode_Table()); - $this->P->addMode('footnote',new Doku_Parser_Mode_Footnote()); + $this->P->addMode('table',new Table()); + $this->P->addMode('footnote',new Footnote()); $this->P->parse(' abc | ((Row 0 Col 1)) | ((Row 0 Col 2 | Row 0 Col 3)) | @@ -899,7 +907,7 @@ def'); function testTable_FS1833() { $syntax = " \n| Row 0 Col 1 |\n"; - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse($syntax); $calls = array ( array('document_start',array()), @@ -920,7 +928,7 @@ def'); */ function testTable_CellFix() { $syntax = "\n| r1c1 | r1c2 | r1c3 |\n| r2c1 |\n"; - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse($syntax); $calls = array ( array('document_start',array()), @@ -961,7 +969,7 @@ def'); */ function testTable_CellFix2() { $syntax = "\n| r1c1 |\n| r2c1 | r2c2 | r2c3 |\n"; - $this->P->addMode('table',new Doku_Parser_Mode_Table()); + $this->P->addMode('table',new Table()); $this->P->parse($syntax); $calls = array ( array('document_start',array()), diff --git a/_test/tests/inc/parser/parser_unformatted.test.php b/_test/tests/inc/parser/parser_unformatted.test.php index f20ba5e8b..32c83fcce 100644 --- a/_test/tests/inc/parser/parser_unformatted.test.php +++ b/_test/tests/inc/parser/parser_unformatted.test.php @@ -1,10 +1,13 @@ <?php + +use dokuwiki\Parsing\ParserMode\Unformatted; + require_once 'parser.inc.php'; class TestOfDoku_Parser_Unformatted extends TestOfDoku_Parser { function testNowiki() { - $this->P->addMode('unformatted',new Doku_Parser_Mode_Unformatted()); + $this->P->addMode('unformatted',new Unformatted()); $this->P->parse("Foo <nowiki>testing</nowiki> Bar"); $calls = array ( array('document_start',array()), @@ -21,7 +24,7 @@ class TestOfDoku_Parser_Unformatted extends TestOfDoku_Parser { } function testDoublePercent() { - $this->P->addMode('unformatted',new Doku_Parser_Mode_Unformatted()); + $this->P->addMode('unformatted',new Unformatted()); $this->P->parse("Foo %%testing%% Bar"); $calls = array ( array('document_start',array()), diff --git a/_test/tests/inc/parser/renderer_resolveinterwiki.test.php b/_test/tests/inc/parser/renderer_resolveinterwiki.test.php index 822c41af8..2cd23dfaa 100644 --- a/_test/tests/inc/parser/renderer_resolveinterwiki.test.php +++ b/_test/tests/inc/parser/renderer_resolveinterwiki.test.php @@ -1,6 +1,6 @@ <?php -require_once DOKU_INC . 'inc/parser/renderer.php'; +use dokuwiki\test\mock\Doku_Renderer; /** * Tests for Doku_Renderer::_resolveInterWiki() diff --git a/_test/tests/inc/parser/renderer_xhtml.test.php b/_test/tests/inc/parser/renderer_xhtml.test.php index f7be39a1b..828c6dff6 100644 --- a/_test/tests/inc/parser/renderer_xhtml.test.php +++ b/_test/tests/inc/parser/renderer_xhtml.test.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\Parsing\Handler\Lists; + /** * Class renderer_xhtml_test */ @@ -123,7 +125,7 @@ class renderer_xhtml_test extends DokuWikiTest { $this->R->document_start(); $this->R->listo_open(); - $this->R->listitem_open(1, Doku_Handler_List::NODE); + $this->R->listitem_open(1, Lists::NODE); $this->R->listcontent_open(); $this->R->cdata('item1'); $this->R->listcontent_close(); @@ -145,7 +147,7 @@ class renderer_xhtml_test extends DokuWikiTest { $this->R->listcontent_close(); $this->R->listitem_close(); - $this->R->listitem_open(1, Doku_Handler_List::NODE); + $this->R->listitem_open(1, Lists::NODE); $this->R->listcontent_open(); $this->R->cdata('item3'); $this->R->listcontent_close(); @@ -188,7 +190,7 @@ class renderer_xhtml_test extends DokuWikiTest { $this->R->document_start(); $this->R->listu_open(); - $this->R->listitem_open(1, Doku_Handler_List::NODE); + $this->R->listitem_open(1, Lists::NODE); $this->R->listcontent_open(); $this->R->cdata('item1'); $this->R->listcontent_close(); @@ -210,7 +212,7 @@ class renderer_xhtml_test extends DokuWikiTest { $this->R->listcontent_close(); $this->R->listitem_close(); - $this->R->listitem_open(1, Doku_Handler_List::NODE); + $this->R->listitem_open(1, Lists::NODE); $this->R->listcontent_open(); $this->R->cdata('item3'); $this->R->listcontent_close(); diff --git a/_test/tests/inc/remote.test.php b/_test/tests/inc/remote.test.php index ee040f09a..6a9686b07 100644 --- a/_test/tests/inc/remote.test.php +++ b/_test/tests/inc/remote.test.php @@ -1,6 +1,10 @@ <?php -class MockAuth extends DokuWiki_Auth_Plugin { +use dokuwiki\Extension\AuthPlugin; +use dokuwiki\Extension\RemotePlugin; +use dokuwiki\Remote\Api; + +class MockAuth extends AuthPlugin { function isCaseSensitive() { return true; } } @@ -75,7 +79,7 @@ class RemoteAPICoreTest { } -class remote_plugin_testplugin extends DokuWiki_Remote_Plugin { +class remote_plugin_testplugin extends RemotePlugin { function _getMethods() { return array( 'method1' => array( @@ -108,7 +112,7 @@ class remote_plugin_testplugin extends DokuWiki_Remote_Plugin { function publicCall() {return true;} } -class remote_plugin_testplugin2 extends DokuWiki_Remote_Plugin { +class remote_plugin_testplugin2 extends RemotePlugin { /** * This is a dummy method * @@ -131,7 +135,7 @@ class remote_test extends DokuWikiTest { protected $userinfo; - /** @var RemoteAPI */ + /** @var Api */ protected $remote; function setUp() { @@ -144,7 +148,7 @@ class remote_test extends DokuWikiTest { parent::setUp(); // mock plugin controller to return our test plugins - $pluginManager = $this->createMock('Doku_Plugin_Controller'); + $pluginManager = $this->createMock('dokuwiki\Extension\PluginController'); $pluginManager->method('getList')->willReturn(array('testplugin', 'testplugin2')); $pluginManager->method('load')->willReturnCallback( function($type, $plugin) { @@ -162,7 +166,7 @@ class remote_test extends DokuWikiTest { $conf['useacl'] = 0; $this->userinfo = $USERINFO; - $this->remote = new RemoteAPI(); + $this->remote = new Api(); $auth = new MockAuth(); } @@ -206,7 +210,7 @@ class remote_test extends DokuWikiTest { } /** - * @expectedException RemoteAccessDeniedException + * @expectedException dokuwiki\Remote\AccessDeniedException */ function test_hasAccessFail() { global $conf; @@ -260,7 +264,7 @@ class remote_test extends DokuWikiTest { } /** - * @expectedException RemoteException + * @expectedException dokuwiki\Remote\RemoteException */ function test_forceAccessFail() { global $conf; @@ -275,7 +279,7 @@ class remote_test extends DokuWikiTest { $conf['remoteuser'] = ''; $conf['useacl'] = 1; $USERINFO['grps'] = array('grp'); - $remoteApi = new RemoteApi(); + $remoteApi = new Api(); $remoteApi->getCoreMethods(new RemoteAPICoreTest()); $this->assertEquals($remoteApi->call('wiki.stringTestMethod'), 'success'); @@ -287,12 +291,12 @@ class remote_test extends DokuWikiTest { } /** - * @expectedException RemoteException + * @expectedException dokuwiki\Remote\RemoteException */ function test_generalCoreFunctionOnArgumentMismatch() { global $conf; $conf['remote'] = 1; - $remoteApi = new RemoteApi(); + $remoteApi = new Api(); $remoteApi->getCoreMethods(new RemoteAPICoreTest()); $remoteApi->call('wiki.voidTestMethod', array('something')); @@ -305,7 +309,7 @@ class remote_test extends DokuWikiTest { $conf['remoteuser'] = ''; $conf['useacl'] = 1; - $remoteApi = new RemoteApi(); + $remoteApi = new Api(); $remoteApi->getCoreMethods(new RemoteAPICoreTest()); $this->assertEquals($remoteApi->call('wiki.oneStringArgMethod', array('string')), 'string'); @@ -321,7 +325,7 @@ class remote_test extends DokuWikiTest { $conf['remoteuser'] = ''; $conf['useacl'] = 1; - $remoteApi = new RemoteApi(); + $remoteApi = new Api(); $this->assertEquals($remoteApi->call('plugin.testplugin.method1'), null); $this->assertEquals($remoteApi->call('plugin.testplugin.method2', array('string', 7)), array('string', 7, false)); $this->assertEquals($remoteApi->call('plugin.testplugin.method2ext', array('string', 7, true)), array('string', 7, true)); @@ -329,20 +333,20 @@ class remote_test extends DokuWikiTest { } /** - * @expectedException RemoteException + * @expectedException dokuwiki\Remote\RemoteException */ function test_notExistingCall() { global $conf; $conf['remote'] = 1; - $remoteApi = new RemoteApi(); + $remoteApi = new Api(); $remoteApi->call('dose not exist'); } function test_publicCallCore() { global $conf; $conf['useacl'] = 1; - $remoteApi = new RemoteApi(); + $remoteApi = new Api(); $remoteApi->getCoreMethods(new RemoteAPICoreTest()); $this->assertTrue($remoteApi->call('wiki.publicCall')); } @@ -350,28 +354,28 @@ class remote_test extends DokuWikiTest { function test_publicCallPlugin() { global $conf; $conf['useacl'] = 1; - $remoteApi = new RemoteApi(); + $remoteApi = new Api(); $this->assertTrue($remoteApi->call('plugin.testplugin.publicCall')); } /** - * @expectedException RemoteAccessDeniedException + * @expectedException dokuwiki\Remote\AccessDeniedException */ function test_publicCallCoreDeny() { global $conf; $conf['useacl'] = 1; - $remoteApi = new RemoteApi(); + $remoteApi = new Api(); $remoteApi->getCoreMethods(new RemoteAPICoreTest()); $remoteApi->call('wiki.stringTestMethod'); } /** - * @expectedException RemoteAccessDeniedException + * @expectedException dokuwiki\Remote\AccessDeniedException */ function test_publicCallPluginDeny() { global $conf; $conf['useacl'] = 1; - $remoteApi = new RemoteApi(); + $remoteApi = new Api(); $remoteApi->call('plugin.testplugin.methodString'); } @@ -384,7 +388,7 @@ class remote_test extends DokuWikiTest { global $EVENT_HANDLER; $EVENT_HANDLER->register_hook('RPC_CALL_ADD', 'BEFORE', $this, 'pluginCallCustomPathRegister'); - $remoteApi = new RemoteAPI(); + $remoteApi = new Api(); $result = $remoteApi->call('custom.path'); $this->assertEquals($result, 'success'); } diff --git a/_test/tests/inc/remoteapicore.test.php b/_test/tests/inc/remoteapicore.test.php index 66d0e9669..70b7710e8 100644 --- a/_test/tests/inc/remoteapicore.test.php +++ b/_test/tests/inc/remoteapicore.test.php @@ -1,5 +1,9 @@ <?php +use dokuwiki\Remote\Api; +use dokuwiki\Remote\ApiCore; +use dokuwiki\test\mock\AuthPlugin; + /** * Class remoteapicore_test */ @@ -7,7 +11,7 @@ class remoteapicore_test extends DokuWikiTest { protected $userinfo; protected $oldAuthAcl; - /** @var RemoteAPI */ + /** @var Api */ protected $remote; public function setUp() { @@ -21,13 +25,13 @@ class remoteapicore_test extends DokuWikiTest { global $auth; $this->oldAuthAcl = $AUTH_ACL; $this->userinfo = $USERINFO; - $auth = new DokuWiki_Auth_Plugin(); + $auth = new AuthPlugin(); $conf['remote'] = 1; $conf['remoteuser'] = '@user'; $conf['useacl'] = 0; - $this->remote = new RemoteAPI(); + $this->remote = new Api(); } public function tearDown() { @@ -389,7 +393,7 @@ You can use up to five different levels of', } public function test_getPageVersions() { - /** @var $EVENT_HANDLER Doku_Event_Handler */ + /** @var $EVENT_HANDLER \dokuwiki\Extension\EventHandler */ global $EVENT_HANDLER; $EVENT_HANDLER->register_hook('IO_WIKIPAGE_WRITE', 'BEFORE', $this, 'handle_write'); global $conf; @@ -480,7 +484,7 @@ You can use up to five different levels of', } public function test_getXMLRPCAPIVersion() { - $this->assertEquals(DOKU_API_VERSION, $this->remote->call('dokuwiki.getXMLRPCAPIVersion')); + $this->assertEquals(ApiCore::API_VERSION, $this->remote->call('dokuwiki.getXMLRPCAPIVersion')); } public function test_getRPCVersionSupported() { diff --git a/_test/tests/inc/remoteapicore_aclcheck.test.php b/_test/tests/inc/remoteapicore_aclcheck.test.php index 25aff331f..6ba7f1dcf 100644 --- a/_test/tests/inc/remoteapicore_aclcheck.test.php +++ b/_test/tests/inc/remoteapicore_aclcheck.test.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\Remote\Api; + /** * Class remoteapicore_test */ @@ -7,7 +9,7 @@ class remoteapicore_aclcheck_test extends DokuWikiTest { protected $userinfo; protected $oldAuthAcl; - /** @var RemoteAPI */ + /** @var Api */ protected $remote; protected $pluginsEnabled = array('auth_plugin_authplain'); @@ -38,7 +40,7 @@ class remoteapicore_aclcheck_test extends DokuWikiTest { $conf['remoteuser'] = '@user'; $conf['useacl'] = 0; - $this->remote = new RemoteAPI(); + $this->remote = new Api(); } diff --git a/_test/tests/inc/subscription.test.php b/_test/tests/inc/subscription.test.php deleted file mode 100644 index 34a7b9e4b..000000000 --- a/_test/tests/inc/subscription.test.php +++ /dev/null @@ -1,246 +0,0 @@ -<?php - -class subscription_test extends DokuWikiTest { - - function test_regexp() { - // data to test against - $data = array( - "casper every\n", - "Andreas digest 1344689733", - "Cold%20Fusion every", - "zioth list 1344691369\n", - "nlights digest", - "rikblok\tdigest \t 1344716803", - ); - - // user, style, data, expected number of results - $tests = array( - array('Cold Fusion', null, null, 1), - array('casper', null, null, 1), - array('nope', null, null, 0), - array('lights', null, null, 0), - array(array('Cold Fusion', 'casper', 'nope'), null, null, 2), - array(null, 'list', null, 1), - array(null, 'every', null, 2), - array(null, 'digest', null, 3), - array(null, array('list', 'every'), null, 3), - array('casper', 'digest', null, 0), - array('casper', array('digest', 'every'), null, 1), - array('zioth', 'list', '1344691369', 1), - array('zioth', null, '1344691369', 1), - array('zioth', 'digest', '1344691369', 0), - ); - - $sub = new MockupSubscription(); - - $row = 0; - foreach($tests as $test) { - $re = $sub->buildregex($test[0], $test[1], $test[2]); - $this->assertFalse(empty($re), "test line $row"); - $result = preg_grep($re, $data); - $this->assertEquals($test[3], count($result), "test line $row. $re got\n".print_r($result, true)); - - $row++; - } - } - - function test_addremove() { - $sub = new MockupSubscription(); - - // no subscriptions - $this->assertArrayNotHasKey( - 'wiki:dokuwiki', - $sub->subscribers('wiki:dokuwiki', null, array('every', 'list', 'digest')) - ); - - // add page subscription - $sub->add('wiki:dokuwiki', 'testuser', 'every'); - - // one subscription - $this->assertArrayHasKey( - 'wiki:dokuwiki', - $sub->subscribers('wiki:dokuwiki', null, array('every', 'list', 'digest')) - ); - - // remove page subscription - $sub->remove('wiki:dokuwiki', 'testuser'); - - // no subscription - $this->assertArrayNotHasKey( - 'wiki:dokuwiki', - $sub->subscribers('wiki:dokuwiki', null, array('every', 'list', 'digest')) - ); - - // add namespace subscription - $sub->add('wiki:', 'testuser', 'every'); - - // one subscription - $this->assertArrayHasKey( - 'wiki:', - $sub->subscribers('wiki:dokuwiki', null, array('every', 'list', 'digest')) - ); - - // remove (non existing) page subscription - $sub->remove('wiki:dokuwiki', 'testuser'); - - // still one subscription - $this->assertArrayHasKey( - 'wiki:', - $sub->subscribers('wiki:dokuwiki', null, array('every', 'list', 'digest')) - ); - - // change namespace subscription - $sub->add('wiki:', 'testuser', 'digest', '1234567'); - - // still one subscription - $this->assertArrayHasKey( - 'wiki:', - $sub->subscribers('wiki:dokuwiki', null, array('every', 'list', 'digest')) - ); - - // check contents - $this->assertEquals( - array('wiki:' => array('testuser' => array('digest', '1234567'))), - $sub->subscribers('wiki:dokuwiki', null, array('every', 'list', 'digest')) - ); - - // change subscription data - $sub->add('wiki:', 'testuser', 'digest', '7654321'); - - // still one subscription - $this->assertArrayHasKey( - 'wiki:', - $sub->subscribers('wiki:dokuwiki', null, array('every', 'list', 'digest')) - ); - - // check contents - $this->assertEquals( - array('wiki:' => array('testuser' => array('digest', '7654321'))), - $sub->subscribers('wiki:dokuwiki', null, array('every', 'list', 'digest')) - ); - } - - function test_bulkdigest() { - $sub = new MockupSubscription(); - - // let's start with nothing - $this->assertEquals(0, $sub->send_bulk('sub1:test')); - - // create a subscription - $sub->add('sub1:', 'testuser', 'digest', '978328800'); // last mod 2001-01-01 - - // now create change - $_SERVER['REMOTE_USER'] = 'someguy'; - saveWikiText('sub1:test', 'foo bar', 'a subscription change', false); - - // should trigger a mail - $this->assertEquals(1, $sub->send_bulk('sub1:test')); - $this->assertEquals(array('arthur@example.com'), $sub->mails); - - $sub->reset(); - - // now create more changes - $_SERVER['REMOTE_USER'] = 'someguy'; - saveWikiText('sub1:sub2:test', 'foo bar', 'a subscription change', false); - saveWikiText('sub1:another_test', 'foo bar', 'a subscription change', false); - - // should not trigger a mail, because the subscription time has not been reached, yet - $this->assertEquals(0, $sub->send_bulk('sub1:test')); - $this->assertEquals(array(), $sub->mails); - - // reset the subscription time - $sub->add('sub1:', 'testuser', 'digest', '978328800'); // last mod 2001-01-01 - - // we now should get mails for three changes - $this->assertEquals(3, $sub->send_bulk('sub1:test')); - $this->assertEquals(array('arthur@example.com', 'arthur@example.com', 'arthur@example.com'), $sub->mails); - } - - function test_bulklist() { - $sub = new MockupSubscription(); - - // let's start with nothing - $this->assertEquals(0, $sub->send_bulk('sub1:test')); - - // create a subscription - $sub->add('sub1:', 'testuser', 'list', '978328800'); // last mod 2001-01-01 - - // now create change - $_SERVER['REMOTE_USER'] = 'someguy'; - saveWikiText('sub1:test', 'foo bar', 'a subscription change', false); - - // should trigger a mail - $this->assertEquals(1, $sub->send_bulk('sub1:test')); - $this->assertEquals(array('arthur@example.com'), $sub->mails); - - $sub->reset(); - - // now create more changes - $_SERVER['REMOTE_USER'] = 'someguy'; - saveWikiText('sub1:sub2:test', 'foo bar', 'a subscription change', false); - saveWikiText('sub1:another_test', 'foo bar', 'a subscription change', false); - - // should not trigger a mail, because the subscription time has not been reached, yet - $this->assertEquals(0, $sub->send_bulk('sub1:test')); - $this->assertEquals(array(), $sub->mails); - - // reset the subscription time - $sub->add('sub1:', 'testuser', 'list', '978328800'); // last mod 2001-01-01 - - // we now should get a single mail for all three changes - $this->assertEquals(1, $sub->send_bulk('sub1:test')); - $this->assertEquals(array('arthur@example.com'), $sub->mails); - } - - /** - * Tests, if overwriting subscriptions works even when subscriptions for the same - * user exist for two nested namespaces, this is a test for the bug described in FS#2580 - */ - function test_overwrite() { - $sub = new MockupSubscription(); - - $sub->add(':', 'admin', 'digest', '123456789'); - $sub->add(':wiki:', 'admin', 'digest', '123456789'); - $sub->add(':', 'admin', 'digest', '1234'); - $sub->add(':wiki:', 'admin', 'digest', '1234'); - - $subscriptions = $sub->subscribers(':wiki:', 'admin'); - - $this->assertCount(1, $subscriptions[':'], 'More than one subscription saved for the root namespace even though the old one should have been overwritten.'); - $this->assertCount(1, $subscriptions[':wiki:'], 'More than one subscription saved for the wiki namespace even though the old one should have been overwritten.'); - $this->assertCount(2, $subscriptions, 'Didn\'t find the expected two subscriptions'); - } -} - -/** - * makes protected methods visible for testing - */ -class MockupSubscription extends Subscription { - public $mails; // we keep sent mails here - - public function __construct() { - $this->reset(); - } - - /** - * resets the mail array - */ - public function reset() { - $this->mails = array(); - } - - public function isenabled() { - return true; - } - - public function buildregex($user = null, $style = null, $data = null) { - return parent::buildregex($user, $style, $data); - } - - protected function send($subscriber_mail, $subject, $id, $template, $trep, $hrep = null, $headers = array()) { - $this->mails[] = $subscriber_mail; - return true; - } -} - -//Setup VIM: ex: et ts=4 : diff --git a/_test/tests/inc/utf8_basename.test.php b/_test/tests/inc/utf8_basename.test.php index 1544e9915..c946e39f3 100644 --- a/_test/tests/inc/utf8_basename.test.php +++ b/_test/tests/inc/utf8_basename.test.php @@ -84,8 +84,8 @@ class utf8_basename_test extends DokuWikiTest { ); foreach($data as $test){ - $this->assertEquals($test[2], utf8_basename($test[0], $test[1]), "input: ('".$test[0]."', '".$test[1]."')"); + $this->assertEquals($test[2], \dokuwiki\Utf8\PhpString::basename($test[0], $test[1]), "input: ('".$test[0]."', '".$test[1]."')"); } } -}
\ No newline at end of file +} diff --git a/_test/tests/inc/utf8_correctidx.test.php b/_test/tests/inc/utf8_correctidx.test.php index a704fd0e8..5a24af737 100644 --- a/_test/tests/inc/utf8_correctidx.test.php +++ b/_test/tests/inc/utf8_correctidx.test.php @@ -15,7 +15,7 @@ class utf8_correctidx_test extends DokuWikiTest { $tests[] = array('aaживπά우리をあöä',1,true,1); foreach($tests as $test){ - $this->assertEquals(utf8_correctIdx($test[0],$test[1],$test[2]),$test[3]); + $this->assertEquals(\dokuwiki\Utf8\Clean::correctIdx($test[0],$test[1],$test[2]),$test[3]); } } @@ -33,7 +33,7 @@ class utf8_correctidx_test extends DokuWikiTest { $tests[] = array('aaживπά우리をあöä',4,true,4); foreach($tests as $test){ - $this->assertEquals(utf8_correctIdx($test[0],$test[1],$test[2]),$test[3]); + $this->assertEquals(\dokuwiki\Utf8\Clean::correctIdx($test[0],$test[1],$test[2]),$test[3]); } } @@ -53,7 +53,7 @@ class utf8_correctidx_test extends DokuWikiTest { $tests[] = array('aaживπά우리をあöä',13,true,13); foreach($tests as $test){ - $this->assertEquals(utf8_correctIdx($test[0],$test[1],$test[2]),$test[3]); + $this->assertEquals(\dokuwiki\Utf8\Clean::correctIdx($test[0],$test[1],$test[2]),$test[3]); } } @@ -69,7 +69,7 @@ class utf8_correctidx_test extends DokuWikiTest { $tests[] = array('aaживπά우리をあöä',128,true,29); foreach($tests as $test){ - $this->assertEquals(utf8_correctIdx($test[0],$test[1],$test[2]),$test[3]); + $this->assertEquals(\dokuwiki\Utf8\Clean::correctIdx($test[0],$test[1],$test[2]),$test[3]); } } diff --git a/_test/tests/inc/utf8_html.test.php b/_test/tests/inc/utf8_html.test.php index 4a70c3c4c..d65004fc2 100644 --- a/_test/tests/inc/utf8_html.test.php +++ b/_test/tests/inc/utf8_html.test.php @@ -8,7 +8,7 @@ class utf8_html_test extends DokuWikiTest { function test_from_1byte(){ $in = 'a'; $out = 'a'; - $this->assertEquals(utf8_tohtml($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Conversion::toHtml($in),$out); } function test_from_1byte_all(){ @@ -20,55 +20,55 @@ class utf8_html_test extends DokuWikiTest { function test_from_2byte(){ $in = "\xc3\xbc"; $out = 'ü'; - $this->assertEquals(utf8_tohtml($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Conversion::toHtml($in),$out); } function test_from_3byte(){ $in = "\xe2\x99\x8a"; $out = '♊'; - $this->assertEquals(utf8_tohtml($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Conversion::toHtml($in),$out); } function test_from_4byte(){ $in = "\xf4\x80\x80\x81"; $out = '􀀁'; - $this->assertEquals(utf8_tohtml($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Conversion::toHtml($in),$out); } function test_to_1byte(){ $out = 'a'; $in = 'a'; - $this->assertEquals(utf8_unhtml($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Conversion::fromHtml($in),$out); } function test_to_2byte(){ $out = "\xc3\xbc"; $in = 'ü'; - $this->assertEquals(utf8_unhtml($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Conversion::fromHtml($in),$out); } function test_to_3byte(){ $out = "\xe2\x99\x8a"; $in = '♊'; - $this->assertEquals(utf8_unhtml($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Conversion::fromHtml($in),$out); } function test_to_4byte(){ $out = "\xf4\x80\x80\x81"; $in = '􀀁'; - $this->assertEquals(utf8_unhtml($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Conversion::fromHtml($in),$out); } function test_without_entities(){ $out = '&#38;&#38;'; $in = '&#38;&amp;#38;'; - $this->assertEquals(utf8_unhtml($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Conversion::fromHtml($in),$out); } function test_with_entities(){ $out = '&&#38;'; $in = '&#38;&amp;#38;'; - $this->assertEquals(utf8_unhtml($in,HTML_ENTITIES),$out); + $this->assertEquals(\dokuwiki\Utf8\Conversion::fromHtml($in,HTML_ENTITIES),$out); } } diff --git a/_test/tests/inc/utf8_romanize.test.php b/_test/tests/inc/utf8_romanize.test.php index 353d48c00..5ffa38872 100644 --- a/_test/tests/inc/utf8_romanize.test.php +++ b/_test/tests/inc/utf8_romanize.test.php @@ -18,7 +18,7 @@ class utf8_romanize_test extends DokuWikiTest { foreach($tests as $test){ list($jap,$rom) = explode(';',trim($test)); - $chk = utf8_romanize($jap); + $chk = \dokuwiki\Utf8\Clean::romanize($jap); $this->assertEquals($rom,$chk,"$jap\t->\t$chk\t!=\t$rom\t($line)"); $line++; } @@ -31,7 +31,7 @@ class utf8_romanize_test extends DokuWikiTest { * @author Andreas Gohr <andi@splitbrain.org> */ function test_deaccented(){ - $this->assertEquals("a A a A a o O",utf8_romanize("å Å ä Ä ä ö Ö")); + $this->assertEquals("a A a A a o O",\dokuwiki\Utf8\Clean::romanize("å Å ä Ä ä ö Ö")); } } //Setup VIM: ex: et ts=4 : diff --git a/_test/tests/inc/utf8_stripspecials.test.php b/_test/tests/inc/utf8_stripspecials.test.php index c9dc3205f..723b36281 100644 --- a/_test/tests/inc/utf8_stripspecials.test.php +++ b/_test/tests/inc/utf8_stripspecials.test.php @@ -19,7 +19,7 @@ class utf8_stripspecials extends DokuWikiTest { $tests[] = array('string with nbsps','_','\*','string_with_nbsps'); foreach($tests as $test){ - $this->assertEquals(utf8_stripspecials($test[0],$test[1],$test[2]),$test[3]); + $this->assertEquals(\dokuwiki\Utf8\Clean::stripspecials($test[0],$test[1],$test[2]),$test[3]); } } diff --git a/_test/tests/inc/utf8_strtolower.test.php b/_test/tests/inc/utf8_strtolower.test.php index 85f5b270b..2654c72cc 100644 --- a/_test/tests/inc/utf8_strtolower.test.php +++ b/_test/tests/inc/utf8_strtolower.test.php @@ -10,7 +10,7 @@ class utf8_strtolower_test extends DokuWikiTest { ); foreach($data as $input => $expected) { - $this->assertEquals($expected, utf8_strtolower($input)); + $this->assertEquals($expected, \dokuwiki\Utf8\PhpString::strtolower($input)); } // just make sure our data was correct @@ -20,4 +20,4 @@ class utf8_strtolower_test extends DokuWikiTest { } } } -}
\ No newline at end of file +} diff --git a/_test/tests/inc/utf8_substr.test.php b/_test/tests/inc/utf8_substr.test.php index 758e8c9d4..e7d3fb555 100644 --- a/_test/tests/inc/utf8_substr.test.php +++ b/_test/tests/inc/utf8_substr.test.php @@ -21,7 +21,7 @@ class utf8_substr_test extends DokuWikiTest { $tests[] = array('живπά우리をあöä',-6,-2,'우리をあ'); foreach($tests as $test){ - $this->assertEquals(utf8_substr($test[0],$test[1],$test[2]),$test[3]); + $this->assertEquals(\dokuwiki\Utf8\PhpString::substr($test[0],$test[1],$test[2]),$test[3]); } } @@ -34,7 +34,7 @@ class utf8_substr_test extends DokuWikiTest { $tests[] = array($str,0,66002,$str); foreach($tests as $test){ - $this->assertEquals(utf8_substr($test[0],$test[1],$test[2]),$test[3]); + $this->assertEquals(\dokuwiki\Utf8\PhpString::substr($test[0],$test[1],$test[2]),$test[3]); } } diff --git a/_test/tests/inc/utf8_unicode.test.php b/_test/tests/inc/utf8_unicode.test.php index fde8c5d02..94ef9f23e 100644 --- a/_test/tests/inc/utf8_unicode.test.php +++ b/_test/tests/inc/utf8_unicode.test.php @@ -8,49 +8,49 @@ class utf8_unicode_test extends DokuWikiTest { function test_from_1byte(){ $in = 'a'; $out = array(97); - $this->assertEquals(utf8_to_unicode($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Unicode::fromUtf8($in),$out); } function test_from_2byte(){ $in = "\xc3\xbc"; $out = array(252); - $this->assertEquals(utf8_to_unicode($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Unicode::fromUtf8($in),$out); } function test_from_3byte(){ $in = "\xe2\x99\x8a"; $out = array(9802); - $this->assertEquals(utf8_to_unicode($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Unicode::fromUtf8($in),$out); } function test_from_4byte(){ $in = "\xf4\x80\x80\x81"; $out = array(1048577); - $this->assertEquals(utf8_to_unicode($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Unicode::fromUtf8($in),$out); } function test_to_1byte(){ $out = 'a'; $in = array(97); - $this->assertEquals(unicode_to_utf8($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Unicode::toUtf8($in),$out); } function test_to_2byte(){ $out = "\xc3\xbc"; $in = array(252); - $this->assertEquals(unicode_to_utf8($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Unicode::toUtf8($in),$out); } function test_to_3byte(){ $out = "\xe2\x99\x8a"; $in = array(9802); - $this->assertEquals(unicode_to_utf8($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Unicode::toUtf8($in),$out); } function test_to_4byte(){ $out = "\xf4\x80\x80\x81"; $in = array(1048577); - $this->assertEquals(unicode_to_utf8($in),$out); + $this->assertEquals(\dokuwiki\Utf8\Unicode::toUtf8($in),$out); } } diff --git a/_test/tests/inc/utf8_utf16be.test.php b/_test/tests/inc/utf8_utf16be.test.php index 943ebeffc..6ac879f68 100644 --- a/_test/tests/inc/utf8_utf16be.test.php +++ b/_test/tests/inc/utf8_utf16be.test.php @@ -12,14 +12,14 @@ class utf8_utf16be_test extends DokuWikiTest { * Convert from UTF-8 to UTF-16BE */ function test_to16be(){ - $this->assertEquals(utf8_to_utf16be($this->utf8), $this->utf16); + $this->assertEquals(\dokuwiki\Utf8\Conversion::toUtf16Be($this->utf8), $this->utf16); } /** * Convert from UTF-16BE to UTF-8 */ function test_from16be(){ - $this->assertEquals(utf16be_to_utf8($this->utf16),$this->utf8); + $this->assertEquals(\dokuwiki\Utf8\Conversion::fromUtf16Be($this->utf16),$this->utf8); } } diff --git a/bin/dwpage.php b/bin/dwpage.php index 3124563fc..967183434 100755 --- a/bin/dwpage.php +++ b/bin/dwpage.php @@ -182,7 +182,7 @@ class PageCLI extends CLI { } if(empty($localfile)) { - $localfile = getcwd() . '/' . utf8_basename($wiki_fn); + $localfile = getcwd() . '/' . \dokuwiki\Utf8\PhpString::basename($wiki_fn); } if(!file_exists(dirname($localfile))) { diff --git a/bin/gittool.php b/bin/gittool.php index 63d5b4426..990281bf6 100755 --- a/bin/gittool.php +++ b/bin/gittool.php @@ -89,17 +89,17 @@ class GitToolCLI extends CLI { echo $options->help(); break; case 'clone': - $this->cmd_clone($args); + $this->cmdClone($args); break; case 'install': - $this->cmd_install($args); + $this->cmdInstall($args); break; case 'repo': case 'repos': - $this->cmd_repos(); + $this->cmdRepos(); break; default: - $this->cmd_git($command, $args); + $this->cmdGit($command, $args); } } @@ -108,7 +108,7 @@ class GitToolCLI extends CLI { * * @param array $extensions */ - public function cmd_clone($extensions) { + public function cmdClone($extensions) { $errors = array(); $succeeded = array(); @@ -137,7 +137,7 @@ class GitToolCLI extends CLI { * * @param array $extensions */ - public function cmd_install($extensions) { + public function cmdInstall($extensions) { $errors = array(); $succeeded = array(); @@ -171,7 +171,7 @@ class GitToolCLI extends CLI { * @param $cmd * @param $arg */ - public function cmd_git($cmd, $arg) { + public function cmdGit($cmd, $arg) { $repos = $this->findRepos(); $shell = array_merge(array('git', $cmd), $arg); @@ -199,7 +199,7 @@ class GitToolCLI extends CLI { /** * Simply lists the repositories */ - public function cmd_repos() { + public function cmdRepos() { $repos = $this->findRepos(); foreach($repos as $repo) { echo "$repo\n"; diff --git a/bin/indexer.php b/bin/indexer.php index 4d19a9515..9ccdaa448 100755 --- a/bin/indexer.php +++ b/bin/indexer.php @@ -60,7 +60,7 @@ class IndexerCLI extends CLI { /** * Update the index */ - function update() { + protected function update() { global $conf; $data = array(); $this->quietecho("Searching pages... "); @@ -77,7 +77,7 @@ class IndexerCLI extends CLI { * * @param string $id */ - function index($id) { + protected function index($id) { $this->quietecho("$id... "); idx_addPage($id, !$this->quiet, $this->clear); $this->quietecho("done.\n"); @@ -86,7 +86,7 @@ class IndexerCLI extends CLI { /** * Clear all index files */ - function clearindex() { + protected function clearindex() { $this->quietecho("Clearing index... "); idx_get_indexer()->clear(); $this->quietecho("done.\n"); @@ -97,7 +97,7 @@ class IndexerCLI extends CLI { * * @param string $msg */ - function quietecho($msg) { + protected function quietecho($msg) { if(!$this->quiet) echo $msg; } } diff --git a/bin/plugin.php b/bin/plugin.php index cbe0b1fe7..a3ee78dd0 100755 --- a/bin/plugin.php +++ b/bin/plugin.php @@ -1,6 +1,7 @@ #!/usr/bin/php <?php +use dokuwiki\Extension\PluginController; use splitbrain\phpcli\CLI; use splitbrain\phpcli\Colors; use splitbrain\phpcli\Options; @@ -51,7 +52,7 @@ class PluginCLI extends CLI { * List available plugins */ protected function listPlugins() { - /** @var Doku_Plugin_Controller $plugin_controller */ + /** @var PluginController $plugin_controller */ global $plugin_controller; echo "\n"; @@ -85,10 +86,9 @@ class PluginCLI extends CLI { * Instantiate a CLI plugin * * @param string $name - * @return DokuWiki_CLI_Plugin|null + * @return \dokuwiki\Extension\CLIPlugin|null */ - protected - function loadPlugin($name) { + protected function loadPlugin($name) { // execute the plugin CLI $class = "cli_plugin_$name"; if(class_exists($class)) { diff --git a/bin/wantedpages.php b/bin/wantedpages.php index 0240eb958..f2efa0950 100755 --- a/bin/wantedpages.php +++ b/bin/wantedpages.php @@ -74,8 +74,8 @@ class WantedPagesCLI extends CLI { $this->info("searching $startdir"); - foreach($this->get_pages($startdir) as $page) { - $this->internal_links($page); + foreach($this->getPages($startdir) as $page) { + $this->internalLinks($page); } ksort($this->result); foreach($this->result as $main => $subs) { @@ -98,7 +98,7 @@ class WantedPagesCLI extends CLI { * @param string $basepath * @return int */ - protected function dir_filter($entry, $basepath) { + protected function dirFilter($entry, $basepath) { if($entry == '.' || $entry == '..') { return WantedPagesCLI::DIR_CONTINUE; } @@ -121,7 +121,7 @@ class WantedPagesCLI extends CLI { * @return array * @throws DokuCLI_Exception */ - protected function get_pages($dir) { + protected function getPages($dir) { static $trunclen = null; if(!$trunclen) { global $conf; @@ -135,11 +135,11 @@ class WantedPagesCLI extends CLI { $pages = array(); $dh = opendir($dir); while(false !== ($entry = readdir($dh))) { - $status = $this->dir_filter($entry, $dir); + $status = $this->dirFilter($entry, $dir); if($status == WantedPagesCLI::DIR_CONTINUE) { continue; } else if($status == WantedPagesCLI::DIR_NS) { - $pages = array_merge($pages, $this->get_pages($dir . '/' . $entry)); + $pages = array_merge($pages, $this->getPages($dir . '/' . $entry)); } else { $page = array( 'id' => pathID(substr($dir . '/' . $entry, $trunclen)), @@ -157,7 +157,7 @@ class WantedPagesCLI extends CLI { * * @param array $page array with page id and file path */ - function internal_links($page) { + protected function internalLinks($page) { global $conf; $instructions = p_get_instructions(file_get_contents($page['file'])); $cns = getNS($page['id']); @@ -9,6 +9,8 @@ */ // update message version - always use a string to avoid localized floats! +use dokuwiki\Extension\Event; + $updateVersion = "51"; // xdebug_start_profiling(); @@ -111,7 +113,7 @@ if($conf['breadcrumbs']) breadcrumbs(); checkUpdateMessages(); $tmp = array(); // No event data -trigger_event('DOKUWIKI_STARTED', $tmp); +Event::createAndTrigger('DOKUWIKI_STARTED', $tmp); //close session session_write_close(); @@ -120,6 +122,6 @@ session_write_close(); act_dispatch(); $tmp = array(); // No event data -trigger_event('DOKUWIKI_DONE', $tmp); +Event::createAndTrigger('DOKUWIKI_DONE', $tmp); // xdebug_dump_function_profile(1); @@ -9,6 +9,12 @@ * @global Input $INPUT */ +use dokuwiki\Cache\Cache; +use dokuwiki\ChangeLog\MediaChangeLog; +use dokuwiki\ChangeLog\PageChangeLog; +use dokuwiki\Extension\AuthPlugin; +use dokuwiki\Extension\Event; + if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__).'/'); require_once(DOKU_INC.'inc/init.php'); @@ -28,7 +34,7 @@ $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']; -$cache = new cache($key, '.feed'); +$cache = new Cache($key, '.feed'); // prepare cache depends $depends['files'] = getConfigFiles('main'); @@ -42,7 +48,7 @@ header('Pragma: public'); header('Content-Type: application/xml; charset=utf-8'); header('X-Robots-Tag: noindex'); if($cache->useCache($depends)) { - http_conditionalRequest($cache->_time); + http_conditionalRequest($cache->getTime()); if($conf['allowdebug']) header("X-CacheUsed: $cache->cache"); print $cache->retrieveCache(); exit; @@ -76,7 +82,7 @@ if(isset($modes[$opt['feed_mode']])) { 'opt' => &$opt, 'data' => &$data, ); - $event = new Doku_Event('FEED_MODE_UNKNOWN', $eventData); + $event = new Event('FEED_MODE_UNKNOWN', $eventData); if($event->advise_before(true)) { echo sprintf('<error>Unknown feed mode %s</error>', hsc($opt['feed_mode'])); exit; @@ -174,7 +180,7 @@ function rss_parseOptions() { $eventData = array( 'opt' => &$opt, ); - trigger_event('FEED_OPTS_POSTPROCESS', $eventData); + Event::createAndTrigger('FEED_OPTS_POSTPROCESS', $eventData); return $opt; } @@ -189,7 +195,7 @@ function rss_parseOptions() { function rss_buildItems(&$rss, &$data, $opt) { global $conf; global $lang; - /* @var DokuWiki_Auth_Plugin $auth */ + /* @var AuthPlugin $auth */ global $auth; $eventData = array( @@ -197,7 +203,7 @@ function rss_buildItems(&$rss, &$data, $opt) { 'data' => &$data, 'opt' => &$opt, ); - $event = new Doku_Event('FEED_DATA_PROCESS', $eventData); + $event = new Event('FEED_DATA_PROCESS', $eventData); if($event->advise_before(false)) { foreach($data as $ditem) { if(!is_array($ditem)) { @@ -443,7 +449,7 @@ function rss_buildItems(&$rss, &$data, $opt) { 'ditem' => &$ditem, 'rss' => &$rss ); - $evt = new Doku_Event('FEED_ITEM_ADD', $evdata); + $evt = new Event('FEED_ITEM_ADD', $evdata); if($evt->advise_before()) { $rss->addItem($item); } diff --git a/inc/Action/Admin.php b/inc/Action/Admin.php index cc6dfd74f..1c9afd6b9 100644 --- a/inc/Action/Admin.php +++ b/inc/Action/Admin.php @@ -28,7 +28,7 @@ class Admin extends AbstractUserAction { // retrieve admin plugin name from $_REQUEST['page'] if(($page = $INPUT->str('page', '', true)) != '') { - /** @var $plugin \DokuWiki_Admin_Plugin */ + /** @var $plugin \dokuwiki\Extension\AdminPlugin */ if($plugin = plugin_getRequestAdminPlugin()) { // FIXME this method does also permission checking if(!$plugin->isAccessibleByCurrentUser()) { throw new ActionException('denied'); diff --git a/inc/Action/Export.php b/inc/Action/Export.php index 1eec27ec3..6b46b276e 100644 --- a/inc/Action/Export.php +++ b/inc/Action/Export.php @@ -3,6 +3,7 @@ namespace dokuwiki\Action; use dokuwiki\Action\Exception\ActionAbort; +use dokuwiki\Extension\Event; /** * Class Export @@ -96,7 +97,7 @@ class Export extends AbstractAction { $data['headers'] = $headers; $data['output'] =& $output; - trigger_event('ACTION_EXPORT_POSTPROCESS', $data); + Event::createAndTrigger('ACTION_EXPORT_POSTPROCESS', $data); if(!empty($data['output'])) { if(is_array($data['headers'])) foreach($data['headers'] as $key => $val) { diff --git a/inc/Action/Logout.php b/inc/Action/Logout.php index 8b464e85d..28e8fee58 100644 --- a/inc/Action/Logout.php +++ b/inc/Action/Logout.php @@ -23,7 +23,7 @@ class Logout extends AbstractUserAction { public function checkPreconditions() { parent::checkPreconditions(); - /** @var \DokuWiki_Auth_Plugin $auth */ + /** @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; if(!$auth->canDo('logout')) throw new ActionDisabledException(); } diff --git a/inc/Action/Plugin.php b/inc/Action/Plugin.php index c3e16bf87..43964cf39 100644 --- a/inc/Action/Plugin.php +++ b/inc/Action/Plugin.php @@ -23,7 +23,7 @@ class Plugin extends AbstractAction { * @triggers TPL_ACT_UNKNOWN */ public function tplContent() { - $evt = new \Doku_Event('TPL_ACT_UNKNOWN', $this->actionname); + $evt = new \dokuwiki\Extension\Event('TPL_ACT_UNKNOWN', $this->actionname); if($evt->advise_before()) { msg('Failed to handle action: ' . hsc($this->actionname), -1); } diff --git a/inc/Action/Profile.php b/inc/Action/Profile.php index 53d8d2ff3..654a23818 100644 --- a/inc/Action/Profile.php +++ b/inc/Action/Profile.php @@ -23,7 +23,7 @@ class Profile extends AbstractUserAction { public function checkPreconditions() { parent::checkPreconditions(); - /** @var \DokuWiki_Auth_Plugin $auth */ + /** @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; if(!$auth->canDo('Profile')) throw new ActionDisabledException(); } diff --git a/inc/Action/ProfileDelete.php b/inc/Action/ProfileDelete.php index 995f81394..89c58edb5 100644 --- a/inc/Action/ProfileDelete.php +++ b/inc/Action/ProfileDelete.php @@ -23,7 +23,7 @@ class ProfileDelete extends AbstractUserAction { public function checkPreconditions() { parent::checkPreconditions(); - /** @var \DokuWiki_Auth_Plugin $auth */ + /** @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; if(!$auth->canDo('delUser')) throw new ActionDisabledException(); } diff --git a/inc/Action/Redirect.php b/inc/Action/Redirect.php index ed67d66d7..dca911a22 100644 --- a/inc/Action/Redirect.php +++ b/inc/Action/Redirect.php @@ -3,6 +3,7 @@ namespace dokuwiki\Action; use dokuwiki\Action\Exception\ActionAbort; +use dokuwiki\Extension\Event; /** * Class Redirect @@ -41,7 +42,7 @@ class Redirect extends AbstractAliasAction { } // execute the redirect - trigger_event('ACTION_SHOW_REDIRECT', $opts, array($this, 'redirect')); + Event::createAndTrigger('ACTION_SHOW_REDIRECT', $opts, array($this, 'redirect')); // should never be reached throw new ActionAbort('show'); diff --git a/inc/Action/Register.php b/inc/Action/Register.php index 0d5415868..7d21bff4a 100644 --- a/inc/Action/Register.php +++ b/inc/Action/Register.php @@ -23,7 +23,7 @@ class Register extends AbstractAclAction { public function checkPreconditions() { parent::checkPreconditions(); - /** @var \DokuWiki_Auth_Plugin $auth */ + /** @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; global $conf; if(isset($conf['openregister']) && !$conf['openregister']) throw new ActionDisabledException(); diff --git a/inc/Action/Resendpwd.php b/inc/Action/Resendpwd.php index 5b5e38194..f3f8d3bad 100644 --- a/inc/Action/Resendpwd.php +++ b/inc/Action/Resendpwd.php @@ -23,7 +23,7 @@ class Resendpwd extends AbstractAclAction { public function checkPreconditions() { parent::checkPreconditions(); - /** @var \DokuWiki_Auth_Plugin $auth */ + /** @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; global $conf; if(isset($conf['resendpasswd']) && !$conf['resendpasswd']) throw new ActionDisabledException(); //legacy option @@ -59,7 +59,7 @@ class Resendpwd extends AbstractAclAction { protected function resendpwd() { global $lang; global $conf; - /* @var \DokuWiki_Auth_Plugin $auth */ + /* @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; global $INPUT; diff --git a/inc/Action/Sitemap.php b/inc/Action/Sitemap.php index 10b1377a0..f0a925cc3 100644 --- a/inc/Action/Sitemap.php +++ b/inc/Action/Sitemap.php @@ -32,8 +32,8 @@ class Sitemap extends AbstractAction { throw new FatalException('Sitemap generation is disabled', 404); } - $sitemap = \Sitemapper::getFilePath(); - if(\Sitemapper::sitemapIsCompressed()) { + $sitemap = Sitemap::getFilePath(); + if(Sitemap::sitemapIsCompressed()) { $mime = 'application/x-gzip'; } else { $mime = 'application/xml; charset=utf-8'; @@ -41,13 +41,13 @@ class Sitemap extends AbstractAction { // Check if sitemap file exists, otherwise create it if(!is_readable($sitemap)) { - \Sitemapper::generate(); + Sitemap::generate(); } if(is_readable($sitemap)) { // Send headers header('Content-Type: ' . $mime); - header('Content-Disposition: attachment; filename=' . utf8_basename($sitemap)); + header('Content-Disposition: attachment; filename=' . \dokuwiki\Utf8\PhpString::basename($sitemap)); http_conditionalRequest(filemtime($sitemap)); diff --git a/inc/Action/Subscribe.php b/inc/Action/Subscribe.php index c16571022..a129a8698 100644 --- a/inc/Action/Subscribe.php +++ b/inc/Action/Subscribe.php @@ -4,6 +4,8 @@ namespace dokuwiki\Action; use dokuwiki\Action\Exception\ActionAbort; use dokuwiki\Action\Exception\ActionDisabledException; +use dokuwiki\Subscriptions\SubscriberManager; +use dokuwiki\Extension\Event; /** * Class Subscribe @@ -67,18 +69,18 @@ class Subscribe extends AbstractUserAction { if(empty($params['action']) || !checkSecurityToken()) return; // Handle POST data, may throw exception. - trigger_event('ACTION_HANDLE_SUBSCRIBE', $params, array($this, 'handlePostData')); + Event::createAndTrigger('ACTION_HANDLE_SUBSCRIBE', $params, array($this, 'handlePostData')); $target = $params['target']; $style = $params['style']; $action = $params['action']; // Perform action. - $sub = new \Subscription(); - if($action == 'unsubscribe') { - $ok = $sub->remove($target, $INPUT->server->str('REMOTE_USER'), $style); + $subManager = new SubscriberManager(); + if($action === 'unsubscribe') { + $ok = $subManager->remove($target, $INPUT->server->str('REMOTE_USER'), $style); } else { - $ok = $sub->add($target, $INPUT->server->str('REMOTE_USER'), $style); + $ok = $subManager->add($target, $INPUT->server->str('REMOTE_USER'), $style); } if($ok) { @@ -89,15 +91,15 @@ class Subscribe extends AbstractUserAction { ), 1 ); throw new ActionAbort('redirect'); - } else { - throw new \Exception( - sprintf( - $lang["subscr_{$action}_error"], - hsc($INFO['userinfo']['name']), - prettyprint_id($target) - ) - ); } + + throw new \Exception( + sprintf( + $lang["subscr_{$action}_error"], + hsc($INFO['userinfo']['name']), + prettyprint_id($target) + ) + ); } /** diff --git a/inc/ActionRouter.php b/inc/ActionRouter.php index edc45cfc4..dfcce3aff 100644 --- a/inc/ActionRouter.php +++ b/inc/ActionRouter.php @@ -76,7 +76,7 @@ class ActionRouter { try { // give plugins an opportunity to process the actionname - $evt = new \Doku_Event('ACTION_ACT_PREPROCESS', $actionname); + $evt = new Extension\Event('ACTION_ACT_PREPROCESS', $actionname); if ($evt->advise_before()) { $this->action = $this->loadAction($actionname); $this->checkAction($this->action); diff --git a/inc/Ajax.php b/inc/Ajax.php index d6a686445..e8cbc84d5 100644 --- a/inc/Ajax.php +++ b/inc/Ajax.php @@ -16,11 +16,11 @@ class Ajax { * @param string $call name of the ajax call */ public function __construct($call) { - $callfn = 'call_' . $call; + $callfn = 'call' . ucfirst($call); if(method_exists($this, $callfn)) { $this->$callfn(); } else { - $evt = new \Doku_Event('AJAX_CALL_UNKNOWN', $call); + $evt = new Extension\Event('AJAX_CALL_UNKNOWN', $call); if($evt->advise_before()) { print "AJAX call '" . hsc($call) . "' unknown!\n"; } else { @@ -35,7 +35,7 @@ class Ajax { * * @author Andreas Gohr <andi@splitbrain.org> */ - protected function call_qsearch() { + protected function callQsearch() { global $lang; global $INPUT; @@ -82,7 +82,7 @@ class Ajax { * @link http://www.opensearch.org/Specifications/OpenSearch/Extensions/Suggestions/1.0 * @author Mike Frysinger <vapier@gentoo.org> */ - protected function call_suggestions() { + protected function callSuggestions() { global $INPUT; $query = cleanID($INPUT->post->str('q')); @@ -107,10 +107,9 @@ class Ajax { array(), // no description array() // no urls ); - $json = new \JSON(); header('Content-Type: application/x-suggestions+json'); - print $json->encode($suggestions); + print json_encode($suggestions); } /** @@ -118,7 +117,7 @@ class Ajax { * * Andreas Gohr <andi@splitbrain.org> */ - protected function call_lock() { + protected function callLock() { global $ID; global $INFO; global $INPUT; @@ -158,7 +157,7 @@ class Ajax { * * @author Andreas Gohr <andi@splitbrain.org> */ - protected function call_draftdel() { + protected function callDraftdel() { global $INPUT; $id = cleanID($INPUT->str('id')); if(empty($id)) return; @@ -175,7 +174,7 @@ class Ajax { * * @author Andreas Gohr <andi@splitbrain.org> */ - protected function call_medians() { + protected function callMedians() { global $conf; global $INPUT; @@ -198,7 +197,7 @@ class Ajax { * * @author Andreas Gohr <andi@splitbrain.org> */ - protected function call_medialist() { + protected function callMedialist() { global $NS; global $INPUT; @@ -217,7 +216,7 @@ class Ajax { * * @author Kate Arzamastseva <pshns@ukr.net> */ - protected function call_mediadetails() { + protected function callMediadetails() { global $IMG, $JUMPTO, $REV, $fullscreen, $INPUT; $fullscreen = true; require_once(DOKU_INC . 'lib/exe/mediamanager.php'); @@ -238,7 +237,7 @@ class Ajax { * * @author Kate Arzamastseva <pshns@ukr.net> */ - protected function call_mediadiff() { + protected function callMediadiff() { global $NS; global $INPUT; @@ -254,7 +253,7 @@ class Ajax { * * @author Kate Arzamastseva <pshns@ukr.net> */ - protected function call_mediaupload() { + protected function callMediaupload() { global $NS, $MSG, $INPUT; $id = ''; @@ -299,9 +298,9 @@ class Ajax { 'ns' => $NS ); } - $json = new \JSON; + header('Content-Type: application/json'); - echo $json->encode($result); + echo json_encode($result); } /** @@ -309,7 +308,7 @@ class Ajax { * * @author Andreas Gohr <andi@splitbrain.org> */ - protected function call_index() { + protected function callIndex() { global $conf; global $INPUT; @@ -332,7 +331,7 @@ class Ajax { * * @author Andreas Gohr <gohr@cosmocode.de> */ - protected function call_linkwiz() { + protected function callLinkwiz() { global $conf; global $lang; global $INPUT; diff --git a/inc/Cache/Cache.php b/inc/Cache/Cache.php new file mode 100644 index 000000000..599dc5f59 --- /dev/null +++ b/inc/Cache/Cache.php @@ -0,0 +1,237 @@ +<?php + +namespace dokuwiki\Cache; + +use \dokuwiki\Debug\PropertyDeprecationHelper; +use dokuwiki\Extension\Event; + +/** + * Generic handling of caching + */ +class Cache +{ + use PropertyDeprecationHelper; + + public $key = ''; // primary identifier for this item + public $ext = ''; // file ext for cache data, secondary identifier for this item + public $cache = ''; // cache file name + public $depends = array(); // array containing cache dependency information, + // used by makeDefaultCacheDecision to determine cache validity + + // phpcs:disable + /** + * @deprecated since 2019-02-02 use the respective getters instead! + */ + protected $_event = ''; // event to be triggered during useCache + protected $_time; + protected $_nocache = false; // if set to true, cache will not be used or stored + // phpcs:enable + + /** + * @param string $key primary identifier + * @param string $ext file extension + */ + public function __construct($key, $ext) + { + $this->key = $key; + $this->ext = $ext; + $this->cache = getCacheName($key, $ext); + + /** + * @deprecated since 2019-02-02 use the respective getters instead! + */ + $this->deprecatePublicProperty('_event'); + $this->deprecatePublicProperty('_time'); + $this->deprecatePublicProperty('_nocache'); + } + + public function getTime() + { + return $this->_time; + } + + public function getEvent() + { + return $this->_event; + } + + public function setEvent($event) + { + $this->_event = $event; + } + + /** + * public method to determine whether the cache can be used + * + * to assist in centralisation of event triggering and calculation of cache statistics, + * don't override this function override makeDefaultCacheDecision() + * + * @param array $depends array of cache dependencies, support dependecies: + * 'age' => max age of the cache in seconds + * 'files' => cache must be younger than mtime of each file + * (nb. dependency passes if file doesn't exist) + * + * @return bool true if cache can be used, false otherwise + */ + public function useCache($depends = array()) + { + $this->depends = $depends; + $this->addDependencies(); + + if ($this->_event) { + return $this->stats(Event::createAndTrigger( + $this->_event, $this, array($this, 'makeDefaultCacheDecision')) + ); + } + + return $this->stats($this->makeDefaultCacheDecision()); + } + + /** + * internal method containing cache use decision logic + * + * this function processes the following keys in the depends array + * purge - force a purge on any non empty value + * age - expire cache if older than age (seconds) + * files - expire cache if any file in this array was updated more recently than the cache + * + * Note that this function needs to be public as it is used as callback for the event handler + * + * can be overridden + * + * @internal This method may only be called by the event handler! Call \dokuwiki\Cache\Cache::useCache instead! + * + * @return bool see useCache() + */ + public function makeDefaultCacheDecision() + { + + if ($this->_nocache) { + return false; + } // caching turned off + if (!empty($this->depends['purge'])) { + return false; + } // purge requested? + if (!($this->_time = @filemtime($this->cache))) { + return false; + } // cache exists? + + // cache too old? + if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) { + return false; + } + + if (!empty($this->depends['files'])) { + foreach ($this->depends['files'] as $file) { + if ($this->_time <= @filemtime($file)) { + return false; + } // cache older than files it depends on? + } + } + + return true; + } + + /** + * add dependencies to the depends array + * + * this method should only add dependencies, + * it should not remove any existing dependencies and + * it should only overwrite a dependency when the new value is more stringent than the old + */ + protected function addDependencies() + { + global $INPUT; + if ($INPUT->has('purge')) { + $this->depends['purge'] = true; + } // purge requested + } + + /** + * retrieve the cached data + * + * @param bool $clean true to clean line endings, false to leave line endings alone + * @return string cache contents + */ + public function retrieveCache($clean = true) + { + return io_readFile($this->cache, $clean); + } + + /** + * cache $data + * + * @param string $data the data to be cached + * @return bool true on success, false otherwise + */ + public function storeCache($data) + { + if ($this->_nocache) { + return false; + } + + return io_savefile($this->cache, $data); + } + + /** + * remove any cached data associated with this cache instance + */ + public function removeCache() + { + @unlink($this->cache); + } + + /** + * Record cache hits statistics. + * (Only when debugging allowed, to reduce overhead.) + * + * @param bool $success result of this cache use attempt + * @return bool pass-thru $success value + */ + protected function stats($success) + { + global $conf; + static $stats = null; + static $file; + + if (!$conf['allowdebug']) { + return $success; + } + + if (is_null($stats)) { + $file = $conf['cachedir'] . '/cache_stats.txt'; + $lines = explode("\n", io_readFile($file)); + + foreach ($lines as $line) { + $i = strpos($line, ','); + $stats[substr($line, 0, $i)] = $line; + } + } + + if (isset($stats[$this->ext])) { + list($ext, $count, $hits) = explode(',', $stats[$this->ext]); + } else { + $ext = $this->ext; + $count = 0; + $hits = 0; + } + + $count++; + if ($success) { + $hits++; + } + $stats[$this->ext] = "$ext,$count,$hits"; + + io_saveFile($file, join("\n", $stats)); + + return $success; + } + + /** + * @return bool + */ + public function isNoCache() + { + return $this->_nocache; + } +} diff --git a/inc/Cache/CacheInstructions.php b/inc/Cache/CacheInstructions.php new file mode 100644 index 000000000..3c4786105 --- /dev/null +++ b/inc/Cache/CacheInstructions.php @@ -0,0 +1,46 @@ +<?php + +namespace dokuwiki\Cache; + +/** + * Caching of parser instructions + */ +class CacheInstructions extends \dokuwiki\Cache\CacheParser +{ + + /** + * @param string $id page id + * @param string $file source file for cache + */ + public function __construct($id, $file) + { + parent::__construct($id, $file, 'i'); + } + + /** + * retrieve the cached data + * + * @param bool $clean true to clean line endings, false to leave line endings alone + * @return array cache contents + */ + public function retrieveCache($clean = true) + { + $contents = io_readFile($this->cache, false); + return !empty($contents) ? unserialize($contents) : array(); + } + + /** + * cache $instructions + * + * @param array $instructions the instruction to be cached + * @return bool true on success, false otherwise + */ + public function storeCache($instructions) + { + if ($this->_nocache) { + return false; + } + + return io_savefile($this->cache, serialize($instructions)); + } +} diff --git a/inc/Cache/CacheParser.php b/inc/Cache/CacheParser.php new file mode 100644 index 000000000..86453bed5 --- /dev/null +++ b/inc/Cache/CacheParser.php @@ -0,0 +1,64 @@ +<?php + +namespace dokuwiki\Cache; + +/** + * Parser caching + */ +class CacheParser extends Cache +{ + + public $file = ''; // source file for cache + public $mode = ''; // input mode (represents the processing the input file will undergo) + public $page = ''; + + /** + * + * @param string $id page id + * @param string $file source file for cache + * @param string $mode input mode + */ + public function __construct($id, $file, $mode) + { + if ($id) { + $this->page = $id; + } + $this->file = $file; + $this->mode = $mode; + + $this->_event = 'PARSER_CACHE_USE'; + parent::__construct($file . $_SERVER['HTTP_HOST'] . $_SERVER['SERVER_PORT'], '.' . $mode); + } + + /** + * method contains cache use decision logic + * + * @return bool see useCache() + */ + public function makeDefaultCacheDecision() + { + + if (!file_exists($this->file)) { + return false; + } // source exists? + return parent::makeDefaultCacheDecision(); + } + + protected function addDependencies() + { + + // parser cache file dependencies ... + $files = array( + $this->file, // ... source + DOKU_INC . 'inc/parser/Parser.php', // ... parser + DOKU_INC . 'inc/parser/handler.php', // ... handler + ); + $files = array_merge($files, getConfigFiles('main')); // ... wiki settings + + $this->depends['files'] = !empty($this->depends['files']) ? + array_merge($files, $this->depends['files']) : + $files; + parent::addDependencies(); + } + +} diff --git a/inc/Cache/CacheRenderer.php b/inc/Cache/CacheRenderer.php new file mode 100644 index 000000000..e8a28c309 --- /dev/null +++ b/inc/Cache/CacheRenderer.php @@ -0,0 +1,94 @@ +<?php + +namespace dokuwiki\Cache; + +/** + * Caching of data of renderer + */ +class CacheRenderer extends CacheParser +{ + + /** + * method contains cache use decision logic + * + * @return bool see useCache() + */ + public function makeDefaultCacheDecision() + { + global $conf; + + if (!parent::makeDefaultCacheDecision()) { + return false; + } + + if (!isset($this->page)) { + return true; + } + + // meta cache older than file it depends on? + if ($this->_time < @filemtime(metaFN($this->page, '.meta'))) { + return false; + } + + // check current link existence is consistent with cache version + // first check the purgefile + // - if the cache is more recent than the purgefile we know no links can have been updated + if ($this->_time >= @filemtime($conf['cachedir'] . '/purgefile')) { + return true; + } + + // for wiki pages, check metadata dependencies + $metadata = p_get_metadata($this->page); + + if (!isset($metadata['relation']['references']) || + empty($metadata['relation']['references'])) { + return true; + } + + foreach ($metadata['relation']['references'] as $id => $exists) { + if ($exists != page_exists($id, '', false)) { + return false; + } + } + + return true; + } + + protected function addDependencies() + { + global $conf; + + // default renderer cache file 'age' is dependent on 'cachetime' setting, two special values: + // -1 : do not cache (should not be overridden) + // 0 : cache never expires (can be overridden) - no need to set depends['age'] + if ($conf['cachetime'] == -1) { + $this->_nocache = true; + return; + } elseif ($conf['cachetime'] > 0) { + $this->depends['age'] = isset($this->depends['age']) ? + min($this->depends['age'], $conf['cachetime']) : $conf['cachetime']; + } + + // renderer cache file dependencies ... + $files = array( + DOKU_INC . 'inc/parser/' . $this->mode . '.php', // ... the renderer + ); + + // page implies metadata and possibly some other dependencies + if (isset($this->page)) { + + // for xhtml this will render the metadata if needed + $valid = p_get_metadata($this->page, 'date valid'); + if (!empty($valid['age'])) { + $this->depends['age'] = isset($this->depends['age']) ? + min($this->depends['age'], $valid['age']) : $valid['age']; + } + } + + $this->depends['files'] = !empty($this->depends['files']) ? + array_merge($files, $this->depends['files']) : + $files; + + parent::addDependencies(); + } +} diff --git a/inc/ChangeLog/ChangeLog.php b/inc/ChangeLog/ChangeLog.php new file mode 100644 index 000000000..16b5cc285 --- /dev/null +++ b/inc/ChangeLog/ChangeLog.php @@ -0,0 +1,666 @@ +<?php + +namespace dokuwiki\ChangeLog; + +/** + * methods for handling of changelog of pages or media files + */ +abstract class ChangeLog +{ + + /** @var string */ + protected $id; + /** @var int */ + protected $chunk_size; + /** @var array */ + protected $cache; + + /** + * Constructor + * + * @param string $id page id + * @param int $chunk_size maximum block size read from file + */ + public function __construct($id, $chunk_size = 8192) + { + global $cache_revinfo; + + $this->cache =& $cache_revinfo; + if (!isset($this->cache[$id])) { + $this->cache[$id] = array(); + } + + $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 + * + * @return string path to file + */ + abstract protected function getFilename(); + + /** + * Get the changelog information for a specific page id and 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 + * @return bool|array false or array with entries: + * - date: unix timestamp + * - ip: IPv4 address (127.0.0.1) + * - type: log line type + * - id: page id + * - user: user name + * - sum: edit summary (or action reason) + * - extra: extra data (varies by line type) + * + * @author Ben Coburn <btcoburn@silicodon.net> + * @author Kate Arzamastseva <pshns@ukr.net> + */ + public function getRevisionInfo($rev) + { + $rev = max($rev, 0); + + // check if it's already in the memory cache + if (isset($this->cache[$this->id]) && isset($this->cache[$this->id][$rev])) { + return $this->cache[$this->id][$rev]; + } + + //read lines from changelog + list($fp, $lines) = $this->readloglines($rev); + if ($fp) { + fclose($fp); + } + if (empty($lines)) return false; + + // parse and cache changelog lines + foreach ($lines as $value) { + $tmp = parseChangelogLine($value); + if ($tmp !== false) { + $this->cache[$this->id][$tmp['date']] = $tmp; + } + } + if (!isset($this->cache[$this->id][$rev])) { + return false; + } + return $this->cache[$this->id][$rev]; + } + + /** + * Return a list of page revisions numbers + * + * Does not guarantee that the revision exists in the attic, + * only that a line with the date exists in the changelog. + * By default the current revision is skipped. + * + * The current revision is automatically skipped when the page exists. + * See $INFO['meta']['last_change'] for the current revision. + * A negative $first let read the current revision too. + * + * For efficiency, the log lines are parsed and cached for later + * calls to getRevisionInfo. Large changelog files are read + * backwards in chunks until the requested number of changelog + * lines are recieved. + * + * @param int $first skip the first n changelog lines + * @param int $num number of revisions to return + * @return array with the revision timestamps + * + * @author Ben Coburn <btcoburn@silicodon.net> + * @author Kate Arzamastseva <pshns@ukr.net> + */ + public function getRevisions($first, $num) + { + $revs = array(); + $lines = array(); + $count = 0; + + $num = max($num, 0); + if ($num == 0) { + return $revs; + } + + if ($first < 0) { + $first = 0; + } else { + if (file_exists($this->getFilename())) { + // skip current 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) { + // read whole file + $lines = file($file); + if ($lines === false) { + return $revs; + } + } else { + // read chunks backwards + $fp = fopen($file, 'rb'); // "file pointer" + if ($fp === false) { + return $revs; + } + fseek($fp, 0, SEEK_END); + $tail = ftell($fp); + + // chunk backwards + $finger = max($tail - $this->chunk_size, 0); + while ($count < $num + $first) { + $nl = $this->getNewlinepointer($fp, $finger); + + // was the chunk big enough? if not, take another bite + if ($nl > 0 && $tail <= $nl) { + $finger = max($finger - $this->chunk_size, 0); + continue; + } else { + $finger = $nl; + } + + // read chunk + $chunk = ''; + $read_size = max($tail - $finger, 0); // found chunk size + $got = 0; + while ($got < $read_size && !feof($fp)) { + $tmp = @fread($fp, max(min($this->chunk_size, $read_size - $got), 0)); + if ($tmp === false) { + break; + } //error state + $got += strlen($tmp); + $chunk .= $tmp; + } + $tmp = explode("\n", $chunk); + array_pop($tmp); // remove trailing newline + + // combine with previous chunk + $count += count($tmp); + $lines = array_merge($tmp, $lines); + + // next chunk + if ($finger == 0) { + break; + } else { // already read all the lines + $tail = $finger; + $finger = max($tail - $this->chunk_size, 0); + } + } + fclose($fp); + } + + // skip parsing extra lines + $num = max(min(count($lines) - $first, $num), 0); + if ($first > 0 && $num > 0) { + $lines = array_slice($lines, max(count($lines) - $first - $num, 0), $num); + } 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)); + } + } + + // 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']; + } + } + + return $revs; + } + + /** + * Get the nth revision left or right handside 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. + * + * 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 + * @return bool|int + * timestamp of the requested revision + * otherwise false + */ + public function getRelativeRevision($rev, $direction) + { + $rev = max($rev, 0); + $direction = (int)$direction; + + //no direction given or last rev, so no follow-up + if (!$direction || ($direction > 0 && $this->isCurrentRevision($rev))) { + return false; + } + + //get lines from changelog + 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 + // also parse and cache changelog lines for getRevisionInfo(). + $revcounter = 0; + $relativerev = false; + $checkotherchunck = true; //always runs once + while (!$relativerev && $checkotherchunck) { + $tmp = array(); + //parse in normal or reverse order + $count = count($lines); + if ($direction > 0) { + $start = 0; + $step = 1; + } else { + $start = $count - 1; + $step = -1; + } + for ($i = $start; $i >= 0 && $i < $count; $i = $i + $step) { + $tmp = parseChangelogLine($lines[$i]); + if ($tmp !== false) { + $this->cache[$this->id][$tmp['date']] = $tmp; + //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']; + } + } + } + } + + //true when $rev is found, but not the wanted follow-up. + $checkotherchunck = $fp + && ($tmp['date'] == $rev || ($revcounter > 0 && !$relativerev)) + && !(($tail == $eof && $direction > 0) || ($head == 0 && $direction < 0)); + + if ($checkotherchunck) { + list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, $direction); + + if (empty($lines)) break; + } + } + if ($fp) { + fclose($fp); + } + + return $relativerev; + } + + /** + * Returns revisions around rev1 and rev2 + * When available it returns $max entries for each revision + * + * @param int $rev1 oldest revision timestamp + * @param int $rev2 newest revision timestamp (0 looks up last revision) + * @param int $max maximum number of revisions returned + * @return array with two arrays with revisions surrounding rev1 respectively rev2 + */ + public function getRevisionsAround($rev1, $rev2, $max = 50) + { + $max = floor(abs($max) / 2) * 2 + 1; + $rev1 = max($rev1, 0); + $rev2 = max($rev2, 0); + + if ($rev2) { + if ($rev2 < $rev1) { + $rev = $rev2; + $rev2 = $rev1; + $rev1 = $rev; + } + } else { + //empty right side means a removed page. Look up last revision. + $revs = $this->getRevisions(-1, 1); + $rev2 = $revs[0]; + } + //collect revisions around rev2 + 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); + 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; + 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']; + $index++; + + if ($index > floor($max / 2)) break 2; + } + } + + list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, -1); + } + sort($revs1); + //return wanted selection + $revs1 = array_slice($revs1, max($index - floor($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 + * + * @param number $date_at timestamp + * @return string revision ('' for current) + */ + public function getLastRevisionAt($date_at) + { + //requested date_at(timestamp) younger or equal then modified_time($this->id) => load current + if (file_exists($this->getFilename()) && $date_at >= @filemtime($this->getFilename())) { + return ''; + } else { + if ($rev = $this->getRelativeRevision($date_at + 1, -1)) { //+1 to get also the requested date revision + return $rev; + } else { + return false; + } + } + } + + /** + * 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 + * + * @param int $rev revision timestamp + * @param int $max maximum number of revisions to be returned + * @return bool|array + * return array with entries: + * - $requestedrevs: array of with $max revision timestamps + * - $revs: all parsed revision timestamps + * - $fp: filepointer 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 + * otherwise false + */ + protected function retrieveRevisionsAround($rev, $max) + { + //get lines from changelog + 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; + 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) { + //count revs after reference $rev + $aftercount++; + if ($aftercount == 1) $beforecount = count($revs); + } + //enough revs after reference $rev? + if ($aftercount > floor($max / 2)) break 2; + } + } + //retrieve next chunk + list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, 1); + } + if ($aftercount == 0) return false; + + $lasttail = $tail; + + //read additional chuncks 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; + 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++; + //enough revs before reference $rev? + if ($beforecount > max(floor($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); + } +} diff --git a/inc/ChangeLog/MediaChangeLog.php b/inc/ChangeLog/MediaChangeLog.php new file mode 100644 index 000000000..0d7d8d390 --- /dev/null +++ b/inc/ChangeLog/MediaChangeLog.php @@ -0,0 +1,30 @@ +<?php + +namespace dokuwiki\ChangeLog; + +/** + * handles changelog of a media file + */ +class MediaChangeLog extends ChangeLog +{ + + /** + * Returns path to changelog + * + * @return string path to file + */ + protected function getChangelogFilename() + { + return mediaMetaFN($this->id, '.changes'); + } + + /** + * Returns path to current page/media + * + * @return string path to file + */ + protected function getFilename() + { + return mediaFN($this->id); + } +} diff --git a/inc/ChangeLog/PageChangeLog.php b/inc/ChangeLog/PageChangeLog.php new file mode 100644 index 000000000..f1b91dee6 --- /dev/null +++ b/inc/ChangeLog/PageChangeLog.php @@ -0,0 +1,30 @@ +<?php + +namespace dokuwiki\ChangeLog; + +/** + * handles changelog of a wiki page + */ +class PageChangeLog extends ChangeLog +{ + + /** + * Returns path to changelog + * + * @return string path to file + */ + protected function getChangelogFilename() + { + return metaFN($this->id, '.changes'); + } + + /** + * Returns path to current page/media + * + * @return string path to file + */ + protected function getFilename() + { + return wikiFN($this->id); + } +} diff --git a/inc/Debug/DebugHelper.php b/inc/Debug/DebugHelper.php new file mode 100644 index 000000000..3d4b20d62 --- /dev/null +++ b/inc/Debug/DebugHelper.php @@ -0,0 +1,121 @@ +<?php + + +namespace dokuwiki\Debug; + +use Doku_Event; +use dokuwiki\Extension\EventHandler; + +class DebugHelper +{ + const INFO_DEPRECATION_LOG_EVENT = 'INFO_DEPRECATION_LOG'; + + /** + * 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 + * + * @triggers \dokuwiki\Debug::INFO_DEPRECATION_LOG_EVENT + */ + public static function dbgDeprecatedFunction($alternative = '', $callerOffset = 1) + { + global $conf; + /** @var EventHandler $EVENT_HANDLER */ + global $EVENT_HANDLER; + if ( + !$conf['allowdebug'] && + ($EVENT_HANDLER === null || !$EVENT_HANDLER->hasHandlerForEvent('INFO_DEPRECATION_LOG')) + ){ + // avoid any work if no one cares + return; + } + + $backtrace = debug_backtrace(); + for ($i = 0; $i < $callerOffset; $i += 1) { + array_shift($backtrace); + } + + list($self, $call) = $backtrace; + + self::triggerDeprecationEvent( + $backtrace, + $alternative, + trim($self['class'] . '::' . $self['function'] . '()', ':'), + trim($call['class'] . '::' . $call['function'] . '()', ':'), + $call['file'], + $call['line'] + ); + } + + /** + * This marks logs a deprecation warning for a property that should no longer be used + * + * This is usually called withing a magic getter or setter. + * For logging deprecated functions or methods see dbgDeprecatedFunction() + * + * @param string $class The class with the deprecated property + * @param string $propertyName The name of the deprecated property + * + * @triggers \dokuwiki\Debug::INFO_DEPRECATION_LOG_EVENT + */ + public static function dbgDeprecatedProperty($class, $propertyName) + { + global $conf; + global $EVENT_HANDLER; + if (!$conf['allowdebug'] && !$EVENT_HANDLER->hasHandlerForEvent(self::INFO_DEPRECATION_LOG_EVENT)) { + // avoid any work if no one cares + return; + } + + $backtrace = debug_backtrace(); + array_shift($backtrace); + $call = $backtrace[1]; + $caller = trim($call['class'] . '::' . $call['function'] . '()', ':'); + $qualifiedName = $class . '::$' . $propertyName; + self::triggerDeprecationEvent( + $backtrace, + '', + $qualifiedName, + $caller, + $backtrace[0]['file'], + $backtrace[0]['line'] + ); + } + + /** + * @param array $backtrace + * @param string $alternative + * @param string $deprecatedThing + * @param string $caller + * @param string $file + * @param int $line + */ + private static function triggerDeprecationEvent( + array $backtrace, + $alternative, + $deprecatedThing, + $caller, + $file, + $line + ) { + $data = [ + 'trace' => $backtrace, + 'alternative' => $alternative, + 'called' => $deprecatedThing, + 'caller' => $caller, + 'file' => $file, + 'line' => $line, + ]; + $event = new Doku_Event(self::INFO_DEPRECATION_LOG_EVENT, $data); + if ($event->advise_before()) { + $msg = $event->data['called'] . ' is deprecated. It was called from '; + $msg .= $event->data['caller'] . ' in ' . $event->data['file'] . ':' . $event->data['line']; + if ($event->data['alternative']) { + $msg .= ' ' . $event->data['alternative'] . ' should be used instead!'; + } + dbglog($msg); + } + $event->advise_after(); + } +} diff --git a/inc/Debug/PropertyDeprecationHelper.php b/inc/Debug/PropertyDeprecationHelper.php new file mode 100644 index 000000000..6289d5ba8 --- /dev/null +++ b/inc/Debug/PropertyDeprecationHelper.php @@ -0,0 +1,134 @@ +<?php +/** + * Trait for issuing warnings on deprecated access. + * + * Adapted from https://github.com/wikimedia/mediawiki/blob/4aedefdbfd193f323097354bf581de1c93f02715/includes/debug/DeprecationHelper.php + * + */ + + +namespace dokuwiki\Debug; + +/** + * Use this trait in classes which have properties for which public access + * is deprecated. Set the list of properties in $deprecatedPublicProperties + * and make the properties non-public. The trait will preserve public access + * but issue deprecation warnings when it is needed. + * + * Example usage: + * class Foo { + * use DeprecationHelper; + * protected $bar; + * public function __construct() { + * $this->deprecatePublicProperty( 'bar', '1.21', __CLASS__ ); + * } + * } + * + * $foo = new Foo; + * $foo->bar; // works but logs a warning + * + * Cannot be used with classes that have their own __get/__set methods. + * + */ +trait PropertyDeprecationHelper +{ + + /** + * List of deprecated properties, in <property name> => <class> format + * where <class> is the the name of the class defining the property + * + * E.g. [ '_event' => '\dokuwiki\Cache\Cache' ] + * @var string[] + */ + protected $deprecatedPublicProperties = []; + + /** + * Mark a property as deprecated. Only use this for properties that used to be public and only + * call it in the constructor. + * + * @param string $property The name of the property. + * @param null $class name of the class defining the property + * @see DebugHelper::dbgDeprecatedProperty + */ + protected function deprecatePublicProperty( + $property, + $class = null + ) { + $this->deprecatedPublicProperties[$property] = $class ?: get_class(); + } + + public function __get($name) + { + if (isset($this->deprecatedPublicProperties[$name])) { + $class = $this->deprecatedPublicProperties[$name]; + DebugHelper::dbgDeprecatedProperty($class, $name); + return $this->$name; + } + + $qualifiedName = get_class() . '::$' . $name; + if ($this->deprecationHelperGetPropertyOwner($name)) { + // Someone tried to access a normal non-public property. Try to behave like PHP would. + trigger_error("Cannot access non-public property $qualifiedName", E_USER_ERROR); + } else { + // Non-existing property. Try to behave like PHP would. + trigger_error("Undefined property: $qualifiedName", E_USER_NOTICE); + } + return null; + } + + public function __set($name, $value) + { + if (isset($this->deprecatedPublicProperties[$name])) { + $class = $this->deprecatedPublicProperties[$name]; + DebugHelper::dbgDeprecatedProperty($class, $name); + $this->$name = $value; + return; + } + + $qualifiedName = get_class() . '::$' . $name; + if ($this->deprecationHelperGetPropertyOwner($name)) { + // Someone tried to access a normal non-public property. Try to behave like PHP would. + trigger_error("Cannot access non-public property $qualifiedName", E_USER_ERROR); + } else { + // Non-existing property. Try to behave like PHP would. + $this->$name = $value; + } + } + + /** + * Like property_exists but also check for non-visible private properties and returns which + * class in the inheritance chain declared the property. + * @param string $property + * @return string|bool Best guess for the class in which the property is defined. + */ + private function deprecationHelperGetPropertyOwner($property) + { + // Easy branch: check for protected property / private property of the current class. + if (property_exists($this, $property)) { + // The class name is not necessarily correct here but getting the correct class + // name would be expensive, this will work most of the time and getting it + // wrong is not a big deal. + return __CLASS__; + } + // property_exists() returns false when the property does exist but is private (and not + // defined by the current class, for some value of "current" that differs slightly + // between engines). + // Since PHP triggers an error on public access of non-public properties but happily + // allows public access to undefined properties, we need to detect this case as well. + // Reflection is slow so use array cast hack to check for that: + $obfuscatedProps = array_keys((array)$this); + $obfuscatedPropTail = "\0$property"; + foreach ($obfuscatedProps as $obfuscatedProp) { + // private props are in the form \0<classname>\0<propname> + if (strpos($obfuscatedProp, $obfuscatedPropTail, 1) !== false) { + $classname = substr($obfuscatedProp, 1, -strlen($obfuscatedPropTail)); + if ($classname === '*') { + // sanity; this shouldn't be possible as protected properties were handled earlier + $classname = __CLASS__; + } + return $classname; + } + } + return false; + } +} diff --git a/inc/Draft.php b/inc/Draft.php index 7c3109bab..f80016c8d 100644 --- a/inc/Draft.php +++ b/inc/Draft.php @@ -86,7 +86,7 @@ class Draft 'cname' => $this->cname, 'errors' => [], ]; - $event = new \Doku_Event('DRAFT_SAVE', $draft); + $event = new Extension\Event('DRAFT_SAVE', $draft); if ($event->advise_before()) { $draft['hasBeenSaved'] = io_saveFile($draft['cname'], serialize($draft)); if ($draft['hasBeenSaved']) { diff --git a/inc/Extension/ActionPlugin.php b/inc/Extension/ActionPlugin.php new file mode 100644 index 000000000..ed6d82038 --- /dev/null +++ b/inc/Extension/ActionPlugin.php @@ -0,0 +1,22 @@ +<?php + +namespace dokuwiki\Extension; + +/** + * Action Plugin Prototype + * + * Handles action hooks within a plugin + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author Christopher Smith <chris@jalakai.co.uk> + */ +abstract class ActionPlugin extends Plugin +{ + + /** + * Registers a callback function for a given event + * + * @param \Doku_Event_Handler $controller + */ + abstract public function register(\Doku_Event_Handler $controller); +} diff --git a/inc/Extension/AdminPlugin.php b/inc/Extension/AdminPlugin.php new file mode 100644 index 000000000..7900a1ec4 --- /dev/null +++ b/inc/Extension/AdminPlugin.php @@ -0,0 +1,123 @@ +<?php + +namespace dokuwiki\Extension; + +/** + * Admin Plugin Prototype + * + * Implements an admin interface in a plugin + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author Christopher Smith <chris@jalakai.co.uk> + */ +abstract class AdminPlugin extends Plugin +{ + + /** + * Return the text that is displayed at the main admin menu + * (Default localized language string 'menu' is returned, override this function for setting another name) + * + * @param string $language language code + * @return string menu string + */ + public function getMenuText($language) + { + $menutext = $this->getLang('menu'); + if (!$menutext) { + $info = $this->getInfo(); + $menutext = $info['name'] . ' ...'; + } + return $menutext; + } + + /** + * Return the path to the icon being displayed in the main admin menu. + * By default it tries to find an 'admin.svg' file in the plugin directory. + * (Override this function for setting another image) + * + * Important: you have to return a single path, monochrome SVG icon! It has to be + * under 2 Kilobytes! + * + * We recommend icons from https://materialdesignicons.com/ or to use a matching + * style. + * + * @return string full path to the icon file + */ + public function getMenuIcon() + { + $plugin = $this->getPluginName(); + return DOKU_PLUGIN . $plugin . '/admin.svg'; + } + + /** + * Determine position in list in admin window + * Lower values are sorted up + * + * @return int + */ + public function getMenuSort() + { + return 1000; + } + + /** + * Carry out required processing + */ + public function handle() + { + // some plugins might not need this + } + + /** + * Output html of the admin page + */ + abstract public function html(); + + /** + * Checks if access should be granted to this admin plugin + * + * @return bool true if the current user may access this admin plugin + */ + public function isAccessibleByCurrentUser() { + $data = []; + $data['instance'] = $this; + $data['hasAccess'] = false; + + $event = new Event('ADMINPLUGIN_ACCESS_CHECK', $data); + if($event->advise_before()) { + if ($this->forAdminOnly()) { + $data['hasAccess'] = auth_isadmin(); + } else { + $data['hasAccess'] = auth_ismanager(); + } + } + $event->advise_after(); + + return $data['hasAccess']; + } + + /** + * Return true for access only by admins (config:superuser) or false if managers are allowed as well + * + * @return bool + */ + public function forAdminOnly() + { + return true; + } + + /** + * Return array with ToC items. Items can be created with the html_mktocitem() + * + * @see html_mktocitem() + * @see tpl_toc() + * + * @return array + */ + public function getTOC() + { + return array(); + } + +} + diff --git a/inc/Extension/AuthPlugin.php b/inc/Extension/AuthPlugin.php new file mode 100644 index 000000000..2123e1320 --- /dev/null +++ b/inc/Extension/AuthPlugin.php @@ -0,0 +1,458 @@ +<?php + +namespace dokuwiki\Extension; + +/** + * Auth Plugin Prototype + * + * allows to authenticate users in a plugin + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author Chris Smith <chris@jalakai.co.uk> + * @author Jan Schumann <js@jschumann-it.com> + */ +abstract class AuthPlugin extends Plugin +{ + public $success = true; + + /** + * Possible things an auth backend module may be able to + * do. The things a backend can do need to be set to true + * in the constructor. + */ + protected $cando = array( + 'addUser' => false, // can Users be created? + 'delUser' => false, // can Users be deleted? + 'modLogin' => false, // can login names be changed? + 'modPass' => false, // can passwords be changed? + 'modName' => false, // can real names be changed? + 'modMail' => false, // can emails be changed? + 'modGroups' => false, // can groups be changed? + 'getUsers' => false, // can a (filtered) list of users be retrieved? + 'getUserCount' => false, // can the number of users be retrieved? + 'getGroups' => false, // can a list of available groups be retrieved? + 'external' => false, // does the module do external auth checking? + 'logout' => true, // can the user logout again? (eg. not possible with HTTP auth) + ); + + /** + * Constructor. + * + * Carry out sanity checks to ensure the object is + * able to operate. Set capabilities in $this->cando + * array here + * + * For future compatibility, sub classes should always include a call + * to parent::__constructor() in their constructors! + * + * Set $this->success to false if checks fail + * + * @author Christopher Smith <chris@jalakai.co.uk> + */ + public function __construct() + { + // the base class constructor does nothing, derived class + // constructors do the real work + } + + /** + * Available Capabilities. [ DO NOT OVERRIDE ] + * + * For introspection/debugging + * + * @author Christopher Smith <chris@jalakai.co.uk> + * @return array + */ + public function getCapabilities() + { + return array_keys($this->cando); + } + + /** + * Capability check. [ DO NOT OVERRIDE ] + * + * Checks the capabilities set in the $this->cando array and + * some pseudo capabilities (shortcutting access to multiple + * ones) + * + * ususal capabilities start with lowercase letter + * shortcut capabilities start with uppercase letter + * + * @author Andreas Gohr <andi@splitbrain.org> + * @param string $cap the capability to check + * @return bool + */ + public function canDo($cap) + { + switch ($cap) { + case 'Profile': + // can at least one of the user's properties be changed? + return ($this->cando['modPass'] || + $this->cando['modName'] || + $this->cando['modMail']); + break; + case 'UserMod': + // can at least anything be changed? + return ($this->cando['modPass'] || + $this->cando['modName'] || + $this->cando['modMail'] || + $this->cando['modLogin'] || + $this->cando['modGroups'] || + $this->cando['modMail']); + break; + default: + // print a helping message for developers + if (!isset($this->cando[$cap])) { + msg("Check for unknown capability '$cap' - Do you use an outdated Plugin?", -1); + } + return $this->cando[$cap]; + } + } + + /** + * Trigger the AUTH_USERDATA_CHANGE event and call the modification function. [ DO NOT OVERRIDE ] + * + * You should use this function instead of calling createUser, modifyUser or + * deleteUsers directly. The event handlers can prevent the modification, for + * example for enforcing a user name schema. + * + * @author Gabriel Birke <birke@d-scribe.de> + * @param string $type Modification type ('create', 'modify', 'delete') + * @param array $params Parameters for the createUser, modifyUser or deleteUsers method. + * The content of this array depends on the modification type + * @return bool|null|int Result from the modification function or false if an event handler has canceled the action + */ + public function triggerUserMod($type, $params) + { + $validTypes = array( + 'create' => 'createUser', + 'modify' => 'modifyUser', + 'delete' => 'deleteUsers', + ); + if (empty($validTypes[$type])) { + return false; + } + + $result = false; + $eventdata = array('type' => $type, 'params' => $params, 'modification_result' => null); + $evt = new Event('AUTH_USER_CHANGE', $eventdata); + if ($evt->advise_before(true)) { + $result = call_user_func_array(array($this, $validTypes[$type]), $evt->data['params']); + $evt->data['modification_result'] = $result; + } + $evt->advise_after(); + unset($evt); + return $result; + } + + /** + * Log off the current user [ OPTIONAL ] + * + * Is run in addition to the ususal logoff method. Should + * only be needed when trustExternal is implemented. + * + * @see auth_logoff() + * @author Andreas Gohr <andi@splitbrain.org> + */ + public function logOff() + { + } + + /** + * Do all authentication [ OPTIONAL ] + * + * Set $this->cando['external'] = true when implemented + * + * If this function is implemented it will be used to + * authenticate a user - all other DokuWiki internals + * will not be used for authenticating, thus + * implementing the checkPass() function is not needed + * anymore. + * + * The function can be used to authenticate against third + * party cookies or Apache auth mechanisms and replaces + * the auth_login() function + * + * The function will be called with or without a set + * username. If the Username is given it was called + * from the login form and the given credentials might + * need to be checked. If no username was given it + * the function needs to check if the user is logged in + * by other means (cookie, environment). + * + * The function needs to set some globals needed by + * DokuWiki like auth_login() does. + * + * @see auth_login() + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param string $user Username + * @param string $pass Cleartext Password + * @param bool $sticky Cookie should not expire + * @return bool true on successful auth + */ + public function trustExternal($user, $pass, $sticky = false) + { + /* some example: + + global $USERINFO; + global $conf; + $sticky ? $sticky = true : $sticky = false; //sanity check + + // do the checking here + + // set the globals if authed + $USERINFO['name'] = 'FIXME'; + $USERINFO['mail'] = 'FIXME'; + $USERINFO['grps'] = array('FIXME'); + $_SERVER['REMOTE_USER'] = $user; + $_SESSION[DOKU_COOKIE]['auth']['user'] = $user; + $_SESSION[DOKU_COOKIE]['auth']['pass'] = $pass; + $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO; + return true; + + */ + } + + /** + * Check user+password [ MUST BE OVERRIDDEN ] + * + * Checks if the given user exists and the given + * plaintext password is correct + * + * May be ommited if trustExternal is used. + * + * @author Andreas Gohr <andi@splitbrain.org> + * @param string $user the user name + * @param string $pass the clear text password + * @return bool + */ + public function checkPass($user, $pass) + { + msg("no valid authorisation system in use", -1); + return false; + } + + /** + * Return user info [ MUST BE OVERRIDDEN ] + * + * Returns info about the given user needs to contain + * at least these fields: + * + * name string full name of the user + * mail string email address of the user + * grps array list of groups the user is in + * + * @author Andreas Gohr <andi@splitbrain.org> + * @param string $user the user name + * @param bool $requireGroups whether or not the returned data must include groups + * @return false|array containing user data or false + */ + public function getUserData($user, $requireGroups = true) + { + if (!$this->cando['external']) msg("no valid authorisation system in use", -1); + return false; + } + + /** + * Create a new User [implement only where required/possible] + * + * Returns false if the user already exists, null when an error + * occurred and true if everything went well. + * + * The new user HAS TO be added to the default group by this + * function! + * + * Set addUser capability when implemented + * + * @author Andreas Gohr <andi@splitbrain.org> + * @param string $user + * @param string $pass + * @param string $name + * @param string $mail + * @param null|array $grps + * @return bool|null + */ + public function createUser($user, $pass, $name, $mail, $grps = null) + { + msg("authorisation method does not allow creation of new users", -1); + return null; + } + + /** + * Modify user data [implement only where required/possible] + * + * Set the mod* capabilities according to the implemented features + * + * @author Chris Smith <chris@jalakai.co.uk> + * @param string $user nick of the user to be changed + * @param array $changes array of field/value pairs to be changed (password will be clear text) + * @return bool + */ + public function modifyUser($user, $changes) + { + msg("authorisation method does not allow modifying of user data", -1); + return false; + } + + /** + * Delete one or more users [implement only where required/possible] + * + * Set delUser capability when implemented + * + * @author Chris Smith <chris@jalakai.co.uk> + * @param array $users + * @return int number of users deleted + */ + public function deleteUsers($users) + { + msg("authorisation method does not allow deleting of users", -1); + return 0; + } + + /** + * Return a count of the number of user which meet $filter criteria + * [should be implemented whenever retrieveUsers is implemented] + * + * Set getUserCount capability when implemented + * + * @author Chris Smith <chris@jalakai.co.uk> + * @param array $filter array of field/pattern pairs, empty array for no filter + * @return int + */ + public function getUserCount($filter = array()) + { + msg("authorisation method does not provide user counts", -1); + return 0; + } + + /** + * Bulk retrieval of user data [implement only where required/possible] + * + * Set getUsers capability when implemented + * + * @author Chris Smith <chris@jalakai.co.uk> + * @param int $start index of first user to be returned + * @param int $limit max number of users to be returned, 0 for unlimited + * @param array $filter array of field/pattern pairs, null for no filter + * @return array list of userinfo (refer getUserData for internal userinfo details) + */ + public function retrieveUsers($start = 0, $limit = 0, $filter = null) + { + msg("authorisation method does not support mass retrieval of user data", -1); + return array(); + } + + /** + * Define a group [implement only where required/possible] + * + * Set addGroup capability when implemented + * + * @author Chris Smith <chris@jalakai.co.uk> + * @param string $group + * @return bool + */ + public function addGroup($group) + { + msg("authorisation method does not support independent group creation", -1); + return false; + } + + /** + * Retrieve groups [implement only where required/possible] + * + * Set getGroups capability when implemented + * + * @author Chris Smith <chris@jalakai.co.uk> + * @param int $start + * @param int $limit + * @return array + */ + public function retrieveGroups($start = 0, $limit = 0) + { + msg("authorisation method does not support group list retrieval", -1); + return array(); + } + + /** + * Return case sensitivity of the backend [OPTIONAL] + * + * When your backend is caseinsensitive (eg. you can login with USER and + * user) then you need to overwrite this method and return false + * + * @return bool + */ + public function isCaseSensitive() + { + return true; + } + + /** + * Sanitize a given username [OPTIONAL] + * + * This function is applied to any user name that is given to + * the backend and should also be applied to any user name within + * the backend before returning it somewhere. + * + * This should be used to enforce username restrictions. + * + * @author Andreas Gohr <andi@splitbrain.org> + * @param string $user username + * @return string the cleaned username + */ + public function cleanUser($user) + { + return $user; + } + + /** + * Sanitize a given groupname [OPTIONAL] + * + * This function is applied to any groupname that is given to + * the backend and should also be applied to any groupname within + * the backend before returning it somewhere. + * + * This should be used to enforce groupname restrictions. + * + * Groupnames are to be passed without a leading '@' here. + * + * @author Andreas Gohr <andi@splitbrain.org> + * @param string $group groupname + * @return string the cleaned groupname + */ + public function cleanGroup($group) + { + return $group; + } + + /** + * Check Session Cache validity [implement only where required/possible] + * + * DokuWiki caches user info in the user's session for the timespan defined + * in $conf['auth_security_timeout']. + * + * This makes sure slow authentication backends do not slow down DokuWiki. + * This also means that changes to the user database will not be reflected + * on currently logged in users. + * + * To accommodate for this, the user manager plugin will touch a reference + * file whenever a change is submitted. This function compares the filetime + * of this reference file with the time stored in the session. + * + * This reference file mechanism does not reflect changes done directly in + * the backend's database through other means than the user manager plugin. + * + * Fast backends might want to return always false, to force rechecks on + * each page load. Others might want to use their own checking here. If + * unsure, do not override. + * + * @param string $user - The username + * @author Andreas Gohr <andi@splitbrain.org> + * @return bool + */ + public function useSessionCache($user) + { + global $conf; + return ($_SESSION[DOKU_COOKIE]['auth']['time'] >= @filemtime($conf['cachedir'] . '/sessionpurge')); + } +} diff --git a/inc/Extension/CLIPlugin.php b/inc/Extension/CLIPlugin.php new file mode 100644 index 000000000..8637ccf8c --- /dev/null +++ b/inc/Extension/CLIPlugin.php @@ -0,0 +1,13 @@ +<?php + +namespace dokuwiki\Extension; + +/** + * CLI plugin prototype + * + * Provides DokuWiki plugin functionality on top of php-cli + */ +abstract class CLIPlugin extends \splitbrain\phpcli\CLI implements PluginInterface +{ + use PluginTrait; +} diff --git a/inc/Extension/Event.php b/inc/Extension/Event.php new file mode 100644 index 000000000..bbaa52e55 --- /dev/null +++ b/inc/Extension/Event.php @@ -0,0 +1,202 @@ +<?php +// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + +namespace dokuwiki\Extension; + +/** + * The Action plugin event + */ +class Event +{ + /** @var string READONLY event name, objects must register against this name to see the event */ + public $name = ''; + /** @var mixed|null READWRITE data relevant to the event, no standardised format, refer to event docs */ + public $data = null; + /** + * @var mixed|null READWRITE the results of the event action, only relevant in "_AFTER" advise + * event handlers may modify this if they are preventing the default action + * to provide the after event handlers with event results + */ + public $result = null; + /** @var bool READONLY if true, event handlers can prevent the events default action */ + public $canPreventDefault = true; + + /** @var bool whether or not to carry out the default action associated with the event */ + protected $runDefault = true; + /** @var bool whether or not to continue propagating the event to other handlers */ + protected $mayContinue = true; + + /** + * event constructor + * + * @param string $name + * @param mixed $data + */ + public function __construct($name, &$data) + { + + $this->name = $name; + $this->data =& $data; + } + + /** + * @return string + */ + public function __toString() + { + return $this->name; + } + + /** + * advise all registered BEFORE handlers of this event + * + * if these methods are used by functions outside of this object, they must + * properly handle correct processing of any default action and issue an + * advise_after() signal. e.g. + * $evt = new dokuwiki\Plugin\Doku_Event(name, data); + * if ($evt->advise_before(canPreventDefault) { + * // default action code block + * } + * $evt->advise_after(); + * unset($evt); + * + * @param bool $enablePreventDefault + * @return bool results of processing the event, usually $this->runDefault + */ + public function advise_before($enablePreventDefault = true) + { + global $EVENT_HANDLER; + + $this->canPreventDefault = $enablePreventDefault; + if ($EVENT_HANDLER !== null) { + $EVENT_HANDLER->process_event($this, 'BEFORE'); + } else { + dbglog($this->name . ':BEFORE event triggered before event system was initialized'); + } + + return (!$enablePreventDefault || $this->runDefault); + } + + /** + * advise all registered AFTER handlers of this event + * + * @param bool $enablePreventDefault + * @see advise_before() for details + */ + public function advise_after() + { + global $EVENT_HANDLER; + + $this->mayContinue = true; + + if ($EVENT_HANDLER !== null) { + $EVENT_HANDLER->process_event($this, 'AFTER'); + } else { + dbglog($this->name . ':AFTER event triggered before event system was initialized'); + } + } + + /** + * trigger + * + * - advise all registered (<event>_BEFORE) handlers that this event is about to take place + * - carry out the default action using $this->data based on $enablePrevent and + * $this->_default, all of which may have been modified by the event handlers. + * - advise all registered (<event>_AFTER) handlers that the event has taken place + * + * @param null|callable $action + * @param bool $enablePrevent + * @return mixed $event->results + * the value set by any <event>_before or <event> handlers if the default action is prevented + * or the results of the default action (as modified by <event>_after handlers) + * or NULL no action took place and no handler modified the value + */ + public function trigger($action = null, $enablePrevent = true) + { + + if (!is_callable($action)) { + $enablePrevent = false; + if ($action !== null) { + trigger_error( + 'The default action of ' . $this . + ' is not null but also not callable. Maybe the method is not public?', + E_USER_WARNING + ); + } + } + + if ($this->advise_before($enablePrevent) && is_callable($action)) { + if (is_array($action)) { + list($obj, $method) = $action; + $this->result = $obj->$method($this->data); + } else { + $this->result = $action($this->data); + } + } + + $this->advise_after(); + + return $this->result; + } + + /** + * stopPropagation + * + * stop any further processing of the event by event handlers + * this function does not prevent the default action taking place + */ + public function stopPropagation() + { + $this->mayContinue = false; + } + + /** + * may the event propagate to the next handler? + * + * @return bool + */ + public function mayPropagate() + { + return $this->mayContinue; + } + + /** + * preventDefault + * + * prevent the default action taking place + */ + public function preventDefault() + { + $this->runDefault = false; + } + + /** + * should the default action be executed? + * + * @return bool + */ + public function mayRunDefault() + { + return $this->runDefault; + } + + /** + * Convenience method to trigger an event + * + * Creates, triggers and destroys an event in one go + * + * @param string $name name for the event + * @param mixed $data event data + * @param callable $action (optional, default=NULL) default action, a php callback function + * @param bool $canPreventDefault (optional, default=true) can hooks prevent the default action + * + * @return mixed the event results value after all event processing is complete + * by default this is the return value of the default action however + * it can be set or modified by event handler hooks + */ + static public function createAndTrigger($name, &$data, $action = null, $canPreventDefault = true) + { + $evt = new Event($name, $data); + return $evt->trigger($action, $canPreventDefault); + } +} diff --git a/inc/Extension/EventHandler.php b/inc/Extension/EventHandler.php new file mode 100644 index 000000000..7bed0fe6f --- /dev/null +++ b/inc/Extension/EventHandler.php @@ -0,0 +1,108 @@ +<?php +// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + +namespace dokuwiki\Extension; + +/** + * Controls the registration and execution of all events, + */ +class EventHandler +{ + + // public properties: none + + // private properties + protected $hooks = array(); // array of events and their registered handlers + + /** + * event_handler + * + * constructor, loads all action plugins and calls their register() method giving them + * an opportunity to register any hooks they require + */ + public function __construct() + { + + // load action plugins + /** @var ActionPlugin $plugin */ + $plugin = null; + $pluginlist = plugin_list('action'); + + foreach ($pluginlist as $plugin_name) { + $plugin = plugin_load('action', $plugin_name); + + if ($plugin !== null) $plugin->register($this); + } + } + + /** + * register_hook + * + * register a hook for an event + * + * @param string $event string name used by the event, (incl '_before' or '_after' for triggers) + * @param string $advise + * @param object $obj object in whose scope method is to be executed, + * if NULL, method is assumed to be a globally available function + * @param string $method event handler function + * @param mixed $param data passed to the event handler + * @param int $seq sequence number for ordering hook execution (ascending) + */ + public function register_hook($event, $advise, $obj, $method, $param = null, $seq = 0) + { + $seq = (int)$seq; + $doSort = !isset($this->hooks[$event . '_' . $advise][$seq]); + $this->hooks[$event . '_' . $advise][$seq][] = array($obj, $method, $param); + + if ($doSort) { + ksort($this->hooks[$event . '_' . $advise]); + } + } + + /** + * process the before/after event + * + * @param Event $event + * @param string $advise BEFORE or AFTER + */ + public function process_event($event, $advise = '') + { + + $evt_name = $event->name . ($advise ? '_' . $advise : '_BEFORE'); + + if (!empty($this->hooks[$evt_name])) { + foreach ($this->hooks[$evt_name] as $sequenced_hooks) { + foreach ($sequenced_hooks as $hook) { + list($obj, $method, $param) = $hook; + + if ($obj === null) { + $method($event, $param); + } else { + $obj->$method($event, $param); + } + + if (!$event->mayPropagate()) return; + } + } + } + } + + /** + * Check if an event has any registered handlers + * + * When $advise is empty, both BEFORE and AFTER events will be considered, + * otherwise only the given advisory is checked + * + * @param string $name Name of the event + * @param string $advise BEFORE, AFTER or empty + * @return bool + */ + public function hasHandlerForEvent($name, $advise = '') + { + if ($advise) { + return isset($this->hooks[$name . '_' . $advise]); + } + + return isset($this->hooks[$name . '_BEFORE']) || isset($this->hooks[$name . '_AFTER']); + } +} diff --git a/inc/Extension/Plugin.php b/inc/Extension/Plugin.php new file mode 100644 index 000000000..03637fe4d --- /dev/null +++ b/inc/Extension/Plugin.php @@ -0,0 +1,13 @@ +<?php + +namespace dokuwiki\Extension; + +/** + * DokuWiki Base Plugin + * + * Most plugin types inherit from this class + */ +abstract class Plugin implements PluginInterface +{ + use PluginTrait; +} diff --git a/inc/Extension/PluginController.php b/inc/Extension/PluginController.php new file mode 100644 index 000000000..638fd3946 --- /dev/null +++ b/inc/Extension/PluginController.php @@ -0,0 +1,393 @@ +<?php + +namespace dokuwiki\Extension; + +/** + * Class to encapsulate access to dokuwiki plugins + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author Christopher Smith <chris@jalakai.co.uk> + */ +class PluginController +{ + /** @var array the types of plugins DokuWiki supports */ + const PLUGIN_TYPES = ['auth', 'admin', 'syntax', 'action', 'renderer', 'helper', 'remote', 'cli']; + + protected $listByType = []; + /** @var array all installed plugins and their enabled state [plugin=>enabled] */ + protected $masterList = []; + protected $pluginCascade = ['default' => [], 'local' => [], 'protected' => []]; + protected $lastLocalConfigFile = ''; + + /** + * Populates the master list of plugins + */ + public function __construct() + { + $this->loadConfig(); + $this->populateMasterList(); + } + + /** + * Returns a list of available plugins of given type + * + * @param $type string, plugin_type name; + * the type of plugin to return, + * use empty string for all types + * @param $all bool; + * false to only return enabled plugins, + * true to return both enabled and disabled plugins + * + * @return array of + * - plugin names when $type = '' + * - or plugin component names when a $type is given + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + public function getList($type = '', $all = false) + { + + // request the complete list + if (!$type) { + return $all ? array_keys($this->masterList) : array_keys(array_filter($this->masterList)); + } + + if (!isset($this->listByType[$type]['enabled'])) { + $this->listByType[$type]['enabled'] = $this->getListByType($type, true); + } + if ($all && !isset($this->listByType[$type]['disabled'])) { + $this->listByType[$type]['disabled'] = $this->getListByType($type, false); + } + + return $all + ? array_merge($this->listByType[$type]['enabled'], $this->listByType[$type]['disabled']) + : $this->listByType[$type]['enabled']; + } + + /** + * Loads the given plugin and creates an object of it + * + * @param $type string type of plugin to load + * @param $name string name of the plugin to load + * @param $new bool true to return a new instance of the plugin, false to use an already loaded instance + * @param $disabled bool true to load even disabled plugins + * @return PluginInterface|null the plugin object or null on failure + * @author Andreas Gohr <andi@splitbrain.org> + * + */ + public function load($type, $name, $new = false, $disabled = false) + { + + //we keep all loaded plugins available in global scope for reuse + global $DOKU_PLUGINS; + + list($plugin, /* $component */) = $this->splitName($name); + + // check if disabled + if (!$disabled && !$this->isEnabled($plugin)) { + return null; + } + + $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; + } + + 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 + ); + } + return null; + } + + $DOKU_PLUGINS[$type][$name] = new $class; + return $DOKU_PLUGINS[$type][$name]; + } + + /** + * Whether plugin is disabled + * + * @param string $plugin name of plugin + * @return bool true disabled, false enabled + * @deprecated in favor of the more sensible isEnabled where the return value matches the enabled state + */ + public function isDisabled($plugin) + { + dbg_deprecated('isEnabled()'); + return !$this->isEnabled($plugin); + } + + /** + * Check whether plugin is disabled + * + * @param string $plugin name of plugin + * @return bool true enabled, false disabled + */ + public function isEnabled($plugin) + { + return !empty($this->masterList[$plugin]); + } + + /** + * Disable the plugin + * + * @param string $plugin name of plugin + * @return bool true saving succeed, false saving failed + */ + public function disable($plugin) + { + if (array_key_exists($plugin, $this->pluginCascade['protected'])) return false; + $this->masterList[$plugin] = 0; + return $this->saveList(); + } + + /** + * Enable the plugin + * + * @param string $plugin name of plugin + * @return bool true saving succeed, false saving failed + */ + public function enable($plugin) + { + if (array_key_exists($plugin, $this->pluginCascade['protected'])) return false; + $this->masterList[$plugin] = 1; + return $this->saveList(); + } + + /** + * Returns cascade of the config files + * + * @return array with arrays of plugin configs + */ + public function getCascade() + { + return $this->pluginCascade; + } + + /** + * Read all installed plugins and their current enabled state + */ + protected function populateMasterList() + { + if ($dh = @opendir(DOKU_PLUGIN)) { + $all_plugins = array(); + while (false !== ($plugin = readdir($dh))) { + if ($plugin[0] === '.') continue; // skip hidden entries + if (is_file(DOKU_PLUGIN . $plugin)) continue; // skip files, we're only interested in directories + + if (array_key_exists($plugin, $this->masterList) && $this->masterList[$plugin] == 0) { + $all_plugins[$plugin] = 0; + + } elseif (array_key_exists($plugin, $this->masterList) && $this->masterList[$plugin] == 1) { + $all_plugins[$plugin] = 1; + } else { + $all_plugins[$plugin] = 1; + } + } + $this->masterList = $all_plugins; + if (!file_exists($this->lastLocalConfigFile)) { + $this->saveList(true); + } + } + } + + /** + * Includes the plugin config $files + * and returns the entries of the $plugins array set in these files + * + * @param array $files list of files to include, latter overrides previous + * @return array with entries of the $plugins arrays of the included files + */ + protected function checkRequire($files) + { + $plugins = array(); + foreach ($files as $file) { + if (file_exists($file)) { + include_once($file); + } + } + return $plugins; + } + + /** + * Save the current list of plugins + * + * @param bool $forceSave ; + * false to save only when config changed + * true to always save + * @return bool true saving succeed, false saving failed + */ + protected function saveList($forceSave = false) + { + global $conf; + + if (empty($this->masterList)) return false; + + // Rebuild list of local settings + $local_plugins = $this->rebuildLocal(); + if ($local_plugins != $this->pluginCascade['local'] || $forceSave) { + $file = $this->lastLocalConfigFile; + $out = "<?php\n/*\n * Local plugin enable/disable settings\n" . + " * Auto-generated through plugin/extension manager\n *\n" . + " * NOTE: Plugins will not be added to this file unless there " . + "is a need to override a default setting. Plugins are\n" . + " * enabled by default.\n */\n"; + foreach ($local_plugins as $plugin => $value) { + $out .= "\$plugins['$plugin'] = $value;\n"; + } + // backup current file (remove any existing backup) + if (file_exists($file)) { + $backup = $file . '.bak'; + if (file_exists($backup)) @unlink($backup); + if (!@copy($file, $backup)) return false; + if (!empty($conf['fperm'])) chmod($backup, $conf['fperm']); + } + //check if can open for writing, else restore + return io_saveFile($file, $out); + } + return false; + } + + /** + * Rebuild the set of local plugins + * + * @return array array of plugins to be saved in end($config_cascade['plugins']['local']) + */ + protected function rebuildLocal() + { + //assign to local variable to avoid overwriting + $backup = $this->masterList; + //Can't do anything about protected one so rule them out completely + $local_default = array_diff_key($backup, $this->pluginCascade['protected']); + //Diff between local+default and default + //gives us the ones we need to check and save + $diffed_ones = array_diff_key($local_default, $this->pluginCascade['default']); + //The ones which we are sure of (list of 0s not in default) + $sure_plugins = array_filter($diffed_ones, array($this, 'negate')); + //the ones in need of diff + $conflicts = array_diff_key($local_default, $diffed_ones); + //The final list + return array_merge($sure_plugins, array_diff_assoc($conflicts, $this->pluginCascade['default'])); + } + + /** + * Build the list of plugins and cascade + * + */ + protected function loadConfig() + { + global $config_cascade; + foreach (array('default', 'protected') as $type) { + if (array_key_exists($type, $config_cascade['plugins'])) { + $this->pluginCascade[$type] = $this->checkRequire($config_cascade['plugins'][$type]); + } + } + $local = $config_cascade['plugins']['local']; + $this->lastLocalConfigFile = array_pop($local); + $this->pluginCascade['local'] = $this->checkRequire(array($this->lastLocalConfigFile)); + if (is_array($local)) { + $this->pluginCascade['default'] = array_merge( + $this->pluginCascade['default'], + $this->checkRequire($local) + ); + } + $this->masterList = array_merge( + $this->pluginCascade['default'], + $this->pluginCascade['local'], + $this->pluginCascade['protected'] + ); + } + + /** + * Returns a list of available plugin components of given type + * + * @param string $type plugin_type name; the type of plugin to return, + * @param bool $enabled true to return enabled plugins, + * false to return disabled plugins + * @return array of plugin components of requested type + */ + protected function getListByType($type, $enabled) + { + $master_list = $enabled + ? array_keys(array_filter($this->masterList)) + : array_keys(array_filter($this->masterList, array($this, 'negate'))); + $plugins = array(); + + foreach ($master_list as $plugin) { + + if (file_exists(DOKU_PLUGIN . "$plugin/$type.php")) { + $plugins[] = $plugin; + continue; + } + + $typedir = DOKU_PLUGIN . "$plugin/$type/"; + if (is_dir($typedir)) { + if ($dp = opendir($typedir)) { + while (false !== ($component = readdir($dp))) { + if (strpos($component, '.') === 0 || strtolower(substr($component, -4)) !== '.php') continue; + if (is_file($typedir . $component)) { + $plugins[] = $plugin . '_' . substr($component, 0, -4); + } + } + closedir($dp); + } + } + + }//foreach + + return $plugins; + } + + /** + * Split name in a plugin name and a component name + * + * @param string $name + * @return array with + * - plugin name + * - and component name when available, otherwise empty string + */ + protected function splitName($name) + { + if (!isset($this->masterList[$name])) { + return explode('_', $name, 2); + } + + return array($name, ''); + } + + /** + * Returns inverse boolean value of the input + * + * @param mixed $input + * @return bool inversed boolean value of input + */ + protected function negate($input) + { + return !(bool)$input; + } +} diff --git a/inc/PluginInterface.php b/inc/Extension/PluginInterface.php index 608989096..f2dbe8626 100644 --- a/inc/PluginInterface.php +++ b/inc/Extension/PluginInterface.php @@ -1,14 +1,18 @@ <?php + +namespace dokuwiki\Extension; + /** * DokuWiki Plugin Interface * * Defines the public contract all DokuWiki plugins will adhere to. The actual code - * to do so is defined in DokuWiki_PluginTrait + * to do so is defined in dokuwiki\Extension\PluginTrait * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Christopher Smith <chris@jalakai.co.uk> */ -interface DokuWiki_PluginInterface { +interface PluginInterface +{ /** * General Info * @@ -107,7 +111,7 @@ interface DokuWiki_PluginInterface { * * @param string $name name of plugin to load * @param bool $msg if a message should be displayed in case the plugin is not available - * @return DokuWiki_PluginInterface|null helper plugin object + * @return PluginInterface|null helper plugin object */ public function loadHelper($name, $msg = true); diff --git a/inc/PluginTrait.php b/inc/Extension/PluginTrait.php index 57b735e85..f1db0f598 100644 --- a/inc/PluginTrait.php +++ b/inc/Extension/PluginTrait.php @@ -1,10 +1,12 @@ <?php +namespace dokuwiki\Extension; + /** - * Do not inherit directly from this class, instead inherit from the specialized - * ones in lib/plugin + * Provides standard DokuWiki plugin behaviour */ -trait DokuWiki_PluginTrait { +trait PluginTrait +{ protected $localised = false; // set to true by setupLocale() after loading language dependent strings protected $lang = array(); // array to hold language dependent strings, best accessed via ->getLang() @@ -12,12 +14,13 @@ trait DokuWiki_PluginTrait { protected $conf = array(); // array to hold plugin settings, best accessed via ->getConf() /** - * @see DokuWiki_PluginInterface::getInfo() + * @see PluginInterface::getInfo() */ - public function getInfo() { + public function getInfo() + { $parts = explode('_', get_class($this)); $info = DOKU_PLUGIN . '/' . $parts[2] . '/plugin.info.txt'; - if(file_exists($info)) return confToHash($info); + if (file_exists($info)) return confToHash($info); msg( 'getInfo() not implemented in ' . get_class($this) . ' and ' . $info . ' not found.<br />' . @@ -31,43 +34,48 @@ trait DokuWiki_PluginTrait { } /** - * @see DokuWiki_PluginInterface::isSingleton() + * @see PluginInterface::isSingleton() */ - public function isSingleton() { + public function isSingleton() + { return true; } /** - * @see DokuWiki_PluginInterface::loadHelper() + * @see PluginInterface::loadHelper() */ - public function loadHelper($name, $msg = true) { + public function loadHelper($name, $msg = true) + { $obj = plugin_load('helper', $name); - if(is_null($obj) && $msg) msg("Helper plugin $name is not available or invalid.", -1); + if (is_null($obj) && $msg) msg("Helper plugin $name is not available or invalid.", -1); return $obj; } // region introspection methods /** - * @see DokuWiki_PluginInterface::getPluginType() + * @see PluginInterface::getPluginType() */ - public function getPluginType() { + public function getPluginType() + { list($t) = explode('_', get_class($this), 2); return $t; } /** - * @see DokuWiki_PluginInterface::getPluginName() + * @see PluginInterface::getPluginName() */ - public function getPluginName() { + public function getPluginName() + { list(/* $t */, /* $p */, $n) = explode('_', get_class($this), 4); return $n; } /** - * @see DokuWiki_PluginInterface::getPluginComponent() + * @see PluginInterface::getPluginComponent() */ - public function getPluginComponent() { + public function getPluginComponent() + { list(/* $t */, /* $p */, /* $n */, $c) = explode('_', get_class($this), 4); return (isset($c) ? $c : ''); } @@ -76,31 +84,34 @@ trait DokuWiki_PluginTrait { // region localization methods /** - * @see DokuWiki_PluginInterface::getLang() + * @see PluginInterface::getLang() */ - public function getLang($id) { - if(!$this->localised) $this->setupLocale(); + public function getLang($id) + { + if (!$this->localised) $this->setupLocale(); return (isset($this->lang[$id]) ? $this->lang[$id] : ''); } /** - * @see DokuWiki_PluginInterface::locale_xhtml() + * @see PluginInterface::locale_xhtml() */ - public function locale_xhtml($id) { + public function locale_xhtml($id) + { return p_cached_output($this->localFN($id)); } /** - * @see DokuWiki_PluginInterface::localFN() + * @see PluginInterface::localFN() */ - public function localFN($id, $ext = 'txt') { + public function localFN($id, $ext = 'txt') + { global $conf; $plugin = $this->getPluginName(); $file = DOKU_CONF . 'plugin_lang/' . $plugin . '/' . $conf['lang'] . '/' . $id . '.' . $ext; - if(!file_exists($file)) { + if (!file_exists($file)) { $file = DOKU_PLUGIN . $plugin . '/lang/' . $conf['lang'] . '/' . $id . '.' . $ext; - if(!file_exists($file)) { + if (!file_exists($file)) { //fall back to english $file = DOKU_PLUGIN . $plugin . '/lang/en/' . $id . '.' . $ext; } @@ -109,10 +120,11 @@ trait DokuWiki_PluginTrait { } /** - * @see DokuWiki_PluginInterface::setupLocale() + * @see PluginInterface::setupLocale() */ - public function setupLocale() { - if($this->localised) return; + public function setupLocale() + { + if ($this->localised) return; global $conf, $config_cascade; // definitely don't invoke "global $lang" $path = DOKU_PLUGIN . $this->getPluginName() . '/lang/'; @@ -121,16 +133,16 @@ trait DokuWiki_PluginTrait { // don't include once, in case several plugin components require the same language file @include($path . 'en/lang.php'); - foreach($config_cascade['lang']['plugin'] as $config_file) { - if(file_exists($config_file . $this->getPluginName() . '/en/lang.php')) { + foreach ($config_cascade['lang']['plugin'] as $config_file) { + if (file_exists($config_file . $this->getPluginName() . '/en/lang.php')) { include($config_file . $this->getPluginName() . '/en/lang.php'); } } - if($conf['lang'] != 'en') { + if ($conf['lang'] != 'en') { @include($path . $conf['lang'] . '/lang.php'); - foreach($config_cascade['lang']['plugin'] as $config_file) { - if(file_exists($config_file . $this->getPluginName() . '/' . $conf['lang'] . '/lang.php')) { + foreach ($config_cascade['lang']['plugin'] as $config_file) { + if (file_exists($config_file . $this->getPluginName() . '/' . $conf['lang'] . '/lang.php')) { include($config_file . $this->getPluginName() . '/' . $conf['lang'] . '/lang.php'); } } @@ -144,15 +156,16 @@ trait DokuWiki_PluginTrait { // region configuration methods /** - * @see DokuWiki_PluginInterface::getConf() + * @see PluginInterface::getConf() */ - public function getConf($setting, $notset = false) { + public function getConf($setting, $notset = false) + { - if(!$this->configloaded) { + if (!$this->configloaded) { $this->loadConfig(); } - if(isset($this->conf[$setting])) { + if (isset($this->conf[$setting])) { return $this->conf[$setting]; } else { return $notset; @@ -160,16 +173,17 @@ trait DokuWiki_PluginTrait { } /** - * @see DokuWiki_PluginInterface::loadConfig() + * @see PluginInterface::loadConfig() */ - public function loadConfig() { + public function loadConfig() + { global $conf; $defaults = $this->readDefaultSettings(); $plugin = $this->getPluginName(); - foreach($defaults as $key => $value) { - if(isset($conf['plugin'][$plugin][$key])) continue; + foreach ($defaults as $key => $value) { + if (isset($conf['plugin'][$plugin][$key])) continue; $conf['plugin'][$plugin][$key] = $value; } @@ -183,12 +197,13 @@ trait DokuWiki_PluginTrait { * * @return array setting => value */ - protected function readDefaultSettings() { + protected function readDefaultSettings() + { $path = DOKU_PLUGIN . $this->getPluginName() . '/conf/'; $conf = array(); - if(file_exists($path . 'default.php')) { + if (file_exists($path . 'default.php')) { include($path . 'default.php'); } @@ -199,38 +214,41 @@ trait DokuWiki_PluginTrait { // region output methods /** - * @see DokuWiki_PluginInterface::email() + * @see PluginInterface::email() */ - public function email($email, $name = '', $class = '', $more = '') { - if(!$email) return $name; + public function email($email, $name = '', $class = '', $more = '') + { + if (!$email) return $name; $email = obfuscate($email); - if(!$name) $name = $email; + if (!$name) $name = $email; $class = "class='" . ($class ? $class : 'mail') . "'"; return "<a href='mailto:$email' $class title='$email' $more>$name</a>"; } /** - * @see DokuWiki_PluginInterface::external_link() + * @see PluginInterface::external_link() */ - public function external_link($link, $title = '', $class = '', $target = '', $more = '') { + public function external_link($link, $title = '', $class = '', $target = '', $more = '') + { global $conf; $link = htmlentities($link); - if(!$title) $title = $link; - if(!$target) $target = $conf['target']['extern']; - if($conf['relnofollow']) $more .= ' rel="nofollow"'; + if (!$title) $title = $link; + if (!$target) $target = $conf['target']['extern']; + if ($conf['relnofollow']) $more .= ' rel="nofollow"'; - if($class) $class = " class='$class'"; - if($target) $target = " target='$target'"; - if($more) $more = " " . trim($more); + if ($class) $class = " class='$class'"; + if ($target) $target = " target='$target'"; + if ($more) $more = " " . trim($more); return "<a href='$link'$class$target$more>$title</a>"; } /** - * @see DokuWiki_PluginInterface::render_text() + * @see PluginInterface::render_text() */ - public function render_text($text, $format = 'xhtml') { + public function render_text($text, $format = 'xhtml') + { return p_render($format, p_get_instructions($text), $info); } diff --git a/inc/Extension/RemotePlugin.php b/inc/Extension/RemotePlugin.php new file mode 100644 index 000000000..33bca980a --- /dev/null +++ b/inc/Extension/RemotePlugin.php @@ -0,0 +1,122 @@ +<?php + +namespace dokuwiki\Extension; + +use dokuwiki\Remote\Api; +use ReflectionException; +use ReflectionMethod; + +/** + * Remote Plugin prototype + * + * Add functionality to the remote API in a plugin + */ +abstract class RemotePlugin extends Plugin +{ + + private $api; + + /** + * Constructor + */ + public function __construct() + { + $this->api = new Api(); + } + + /** + * Get all available methods with remote access. + * + * By default it exports all public methods of a remote plugin. Methods beginning + * with an underscore are skipped. + * + * @return array Information about all provided methods. {@see dokuwiki\Remote\RemoteAPI}. + * @throws ReflectionException + */ + public function _getMethods() + { + $result = array(); + + $reflection = new \ReflectionClass($this); + foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + // skip parent methods, only methods further down are exported + $declaredin = $method->getDeclaringClass()->name; + if ($declaredin === 'dokuwiki\Extension\Plugin' || $declaredin === 'dokuwiki\Extension\RemotePlugin') { + continue; + } + $method_name = $method->name; + if (strpos($method_name, '_') === 0) { + continue; + } + + // strip asterisks + $doc = $method->getDocComment(); + $doc = preg_replace( + array('/^[ \t]*\/\*+[ \t]*/m', '/[ \t]*\*+[ \t]*/m', '/\*+\/\s*$/m', '/\s*\/\s*$/m'), + array('', '', '', ''), + $doc + ); + + // prepare data + $data = array(); + $data['name'] = $method_name; + $data['public'] = 0; + $data['doc'] = $doc; + $data['args'] = array(); + + // get parameter type from doc block type hint + foreach ($method->getParameters() as $parameter) { + $name = $parameter->name; + $type = 'string'; // we default to string + if (preg_match('/^@param[ \t]+([\w|\[\]]+)[ \t]\$' . $name . '/m', $doc, $m)) { + $type = $this->cleanTypeHint($m[1]); + } + $data['args'][] = $type; + } + + // get return type from doc block type hint + if (preg_match('/^@return[ \t]+([\w|\[\]]+)/m', $doc, $m)) { + $data['return'] = $this->cleanTypeHint($m[1]); + } else { + $data['return'] = 'string'; + } + + // add to result + $result[$method_name] = $data; + } + + return $result; + } + + /** + * Matches the given type hint against the valid options for the remote API + * + * @param string $hint + * @return string + */ + protected function cleanTypeHint($hint) + { + $types = explode('|', $hint); + foreach ($types as $t) { + if (substr($t, -2) === '[]') { + return 'array'; + } + if ($t === 'boolean') { + return 'bool'; + } + if (in_array($t, array('array', 'string', 'int', 'double', 'bool', 'null', 'date', 'file'))) { + return $t; + } + } + return 'string'; + } + + /** + * @return Api + */ + protected function getApi() + { + return $this->api; + } + +} diff --git a/inc/Extension/SyntaxPlugin.php b/inc/Extension/SyntaxPlugin.php new file mode 100644 index 000000000..e5dda9bdc --- /dev/null +++ b/inc/Extension/SyntaxPlugin.php @@ -0,0 +1,132 @@ +<?php + +namespace dokuwiki\Extension; + +use \Doku_Handler; +use \Doku_Renderer; + +/** + * Syntax Plugin Prototype + * + * All DokuWiki plugins to extend the parser/rendering mechanism + * need to inherit from this class + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author Andreas Gohr <andi@splitbrain.org> + */ +abstract class SyntaxPlugin extends \dokuwiki\Parsing\ParserMode\Plugin +{ + use PluginTrait; + + protected $allowedModesSetup = false; + + /** + * Syntax Type + * + * Needs to return one of the mode types defined in $PARSER_MODES in Parser.php + * + * @return string + */ + abstract public function getType(); + + /** + * Allowed Mode Types + * + * Defines the mode types for other dokuwiki markup that maybe nested within the + * plugin's own markup. Needs to return an array of one or more of the mode types + * defined in $PARSER_MODES in Parser.php + * + * @return array + */ + public function getAllowedTypes() + { + return array(); + } + + /** + * Paragraph Type + * + * Defines how this syntax is handled regarding paragraphs. This is important + * for correct XHTML nesting. Should return one of the following: + * + * 'normal' - The plugin can be used inside paragraphs + * 'block' - Open paragraphs need to be closed before plugin output + * 'stack' - Special case. Plugin wraps other paragraphs. + * + * @see Doku_Handler_Block + * + * @return string + */ + public function getPType() + { + return 'normal'; + } + + /** + * Handler to prepare matched data for the rendering process + * + * This function can only pass data to render() via its return value - render() + * may be not be run during the object's current life. + * + * Usually you should only need the $match param. + * + * @param string $match The text matched by the patterns + * @param int $state The lexer state for the match + * @param int $pos The character position of the matched text + * @param Doku_Handler $handler The Doku_Handler object + * @return bool|array Return an array with all data you want to use in render, false don't add an instruction + */ + abstract public function handle($match, $state, $pos, Doku_Handler $handler); + + /** + * Handles the actual output creation. + * + * The function must not assume any other of the classes methods have been run + * during the object's current life. The only reliable data it receives are its + * parameters. + * + * The function should always check for the given output format and return false + * when a format isn't supported. + * + * $renderer contains a reference to the renderer object which is + * currently handling the rendering. You need to use it for writing + * the output. How this is done depends on the renderer used (specified + * by $format + * + * The contents of the $data array depends on what the handler() function above + * created + * + * @param string $format output format being rendered + * @param Doku_Renderer $renderer the current renderer object + * @param array $data data created by handler() + * @return boolean rendered correctly? (however, returned value is not used at the moment) + */ + abstract public function render($format, Doku_Renderer $renderer, $data); + + /** + * There should be no need to override this function + * + * @param string $mode + * @return bool + */ + public function accepts($mode) + { + + if (!$this->allowedModesSetup) { + global $PARSER_MODES; + + $allowedModeTypes = $this->getAllowedTypes(); + foreach ($allowedModeTypes as $mt) { + $this->allowedModes = array_merge($this->allowedModes, $PARSER_MODES[$mt]); + } + + $idx = array_search(substr(get_class($this), 7), (array)$this->allowedModes); + if ($idx !== false) { + unset($this->allowedModes[$idx]); + } + $this->allowedModesSetup = true; + } + + return parent::accepts($mode); + } +} diff --git a/inc/FeedParser.php b/inc/FeedParser.php index 39434dcaf..8f71b96bc 100644 --- a/inc/FeedParser.php +++ b/inc/FeedParser.php @@ -1,13 +1,5 @@ <?php /** - * Class used to parse RSS and ATOM feeds - * - * @author Andreas Gohr <andi@splitbrain.org> - */ - -if(!defined('DOKU_INC')) die('meh.'); - -/** * We override some methods of the original SimplePie class here */ class FeedParser extends SimplePie { @@ -15,70 +7,21 @@ class FeedParser extends SimplePie { /** * Constructor. Set some defaults */ - function __construct(){ + public function __construct(){ parent::__construct(); $this->enable_cache(false); - $this->set_file_class('FeedParser_File'); + $this->set_file_class(\dokuwiki\FeedParserFile::class); } /** * Backward compatibility for older plugins * + * phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps * @param string $url */ - function feed_url($url){ + public function feed_url($url){ $this->set_feed_url($url); } } -/** - * Fetch an URL using our own HTTPClient - * - * Replaces SimplePie's own class - */ -class FeedParser_File extends SimplePie_File { - var $http; - var $useragent; - var $success = true; - var $headers = array(); - var $body; - var $error; - /** @noinspection PhpMissingParentConstructorInspection */ - - /** - * Inititializes the HTTPClient - * - * We ignore all given parameters - they are set in DokuHTTPClient - * - * @inheritdoc - */ - function __construct($url, $timeout=10, $redirects=5, - $headers=null, $useragent=null, $force_fsockopen=false, $curl_options = array()) { - $this->http = new DokuHTTPClient(); - $this->success = $this->http->sendRequest($url); - - $this->headers = $this->http->resp_headers; - $this->body = $this->http->resp_body; - $this->error = $this->http->error; - - $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_FSOCKOPEN; - - return $this->success; - } - - /** @inheritdoc */ - function headers(){ - return $this->headers; - } - /** @inheritdoc */ - function body(){ - return $this->body; - } - - /** @inheritdoc */ - function close(){ - return true; - } - -} diff --git a/inc/FeedParserFile.php b/inc/FeedParserFile.php new file mode 100644 index 000000000..be3417e9b --- /dev/null +++ b/inc/FeedParserFile.php @@ -0,0 +1,62 @@ +<?php + +namespace dokuwiki; + +use dokuwiki\HTTP\DokuHTTPClient; + +/** + * Fetch an URL using our own HTTPClient + * + * Replaces SimplePie's own class + */ +class FeedParserFile extends \SimplePie_File +{ + protected $http; + /** @noinspection PhpMissingParentConstructorInspection */ + + /** + * Inititializes the HTTPClient + * + * We ignore all given parameters - they are set in DokuHTTPClient + * + * @inheritdoc + */ + public function __construct( + $url, + $timeout = 10, + $redirects = 5, + $headers = null, + $useragent = null, + $force_fsockopen = false, + $curl_options = array() + ) { + $this->http = new DokuHTTPClient(); + $this->success = $this->http->sendRequest($url); + + $this->headers = $this->http->resp_headers; + $this->body = $this->http->resp_body; + $this->error = $this->http->error; + + $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_FSOCKOPEN; + + return $this->success; + } + + /** @inheritdoc */ + public function headers() + { + return $this->headers; + } + + /** @inheritdoc */ + public function body() + { + return $this->body; + } + + /** @inheritdoc */ + public function close() + { + return true; + } +} diff --git a/inc/Form/ButtonElement.php b/inc/Form/ButtonElement.php index e2afe9c97..4f585f0c1 100644 --- a/inc/Form/ButtonElement.php +++ b/inc/Form/ButtonElement.php @@ -17,7 +17,7 @@ class ButtonElement extends Element { * @param string $name * @param string $content HTML content of the button. You have to escape it yourself. */ - function __construct($name, $content = '') { + public function __construct($name, $content = '') { parent::__construct('button', array('name' => $name, 'value' => 1)); $this->content = $content; } diff --git a/inc/Form/DropdownElement.php b/inc/Form/DropdownElement.php index 023b67dd3..51f475196 100644 --- a/inc/Form/DropdownElement.php +++ b/inc/Form/DropdownElement.php @@ -104,7 +104,9 @@ class DropdownElement extends InputElement { */ public function attr($name, $value = null) { if(strtolower($name) == 'multiple') { - throw new \InvalidArgumentException('Sorry, the dropdown element does not support the "multiple" attribute'); + throw new \InvalidArgumentException( + 'Sorry, the dropdown element does not support the "multiple" attribute' + ); } return parent::attr($name, $value); } @@ -181,7 +183,13 @@ class DropdownElement extends InputElement { if($this->useInput) $this->prefillInput(); $html = '<select ' . buildAttributes($this->attrs()) . '>'; - $html = array_reduce($this->optGroups, function($html, OptGroup $optGroup) {return $html . $optGroup->toHTML();}, $html); + $html = array_reduce( + $this->optGroups, + function ($html, OptGroup $optGroup) { + return $html . $optGroup->toHTML(); + }, + $html + ); $html .= '</select>'; return $html; diff --git a/inc/Form/Form.php b/inc/Form/Form.php index 92bbd30f4..c741a698f 100644 --- a/inc/Form/Form.php +++ b/inc/Form/Form.php @@ -84,7 +84,9 @@ class Form extends Element { /** * Get the position of the element in the form or false if it is not in the form * - * Warning: This function may return Boolean FALSE, but may also return a non-Boolean value which evaluates to FALSE. Please read the section on Booleans for more information. Use the === operator for testing the return value of this function. + * Warning: This function may return Boolean FALSE, but may also return a non-Boolean value which evaluates + * to FALSE. Please read the section on Booleans for more information. Use the === operator for testing the + * return value of this function. * * @param Element $element * @@ -157,7 +159,9 @@ class Form extends Element { * @return Element */ 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(is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException( + 'You can\'t add a form to a form' + ); if($pos < 0) { $this->elements[] = $element; } else { @@ -173,7 +177,9 @@ class Form extends 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('You can\'t add a form to a form'); + 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)); } diff --git a/inc/Form/OptGroup.php b/inc/Form/OptGroup.php index 791f0b3f6..40149b171 100644 --- a/inc/Form/OptGroup.php +++ b/inc/Form/OptGroup.php @@ -51,9 +51,13 @@ class OptGroup extends Element { $this->options = array(); 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!'); + if (!key_exists('label', $val)) throw new \InvalidArgumentException( + 'If option is given as array, it has to have a "label"-key!' + ); if (key_exists('attrs', $val) && is_array($val['attrs']) && key_exists('selected', $val['attrs'])) { - throw new \InvalidArgumentException('Please use function "DropdownElement::val()" to set the selected option'); + throw new \InvalidArgumentException( + 'Please use function "DropdownElement::val()" to set the selected option' + ); } $this->options[$key] = $val; } elseif(is_int($key)) { @@ -93,7 +97,9 @@ class OptGroup extends Element { if (!empty($val['attrs']) && is_array($val['attrs'])) { $attrs = buildAttributes($val['attrs']); } - $html .= '<option' . $selected . ' value="' . hsc($key) . '" '.$attrs.'>' . hsc($val['label']) . '</option>'; + $html .= '<option' . $selected . ' value="' . hsc($key) . '" '.$attrs.'>'; + $html .= hsc($val['label']); + $html .= '</option>'; } return $html; } diff --git a/inc/HTTP/DokuHTTPClient.php b/inc/HTTP/DokuHTTPClient.php new file mode 100644 index 000000000..b1db7e3c5 --- /dev/null +++ b/inc/HTTP/DokuHTTPClient.php @@ -0,0 +1,77 @@ +<?php + + +namespace dokuwiki\HTTP; + + + +/** + * Adds DokuWiki specific configs to the HTTP client + * + * @author Andreas Goetz <cpuidle@gmx.de> + */ +class DokuHTTPClient extends HTTPClient { + + /** + * Constructor. + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + public function __construct(){ + global $conf; + + // call parent constructor + parent::__construct(); + + // set some values from the config + $this->proxy_host = $conf['proxy']['host']; + $this->proxy_port = $conf['proxy']['port']; + $this->proxy_user = $conf['proxy']['user']; + $this->proxy_pass = conf_decodeString($conf['proxy']['pass']); + $this->proxy_ssl = $conf['proxy']['ssl']; + $this->proxy_except = $conf['proxy']['except']; + + // allow enabling debugging via URL parameter (if debugging allowed) + if($conf['allowdebug']) { + if( + isset($_REQUEST['httpdebug']) || + ( + isset($_SERVER['HTTP_REFERER']) && + strpos($_SERVER['HTTP_REFERER'], 'httpdebug') !== false + ) + ) { + $this->debug = true; + } + } + } + + + /** + * Wraps an event around the parent function + * + * @triggers HTTPCLIENT_REQUEST_SEND + * @author Andreas Gohr <andi@splitbrain.org> + */ + /** + * @param string $url + * @param string|array $data the post data either as array or raw data + * @param string $method + * @return bool + */ + public function sendRequest($url,$data='',$method='GET'){ + $httpdata = array('url' => $url, + 'data' => $data, + 'method' => $method); + $evt = new \Doku_Event('HTTPCLIENT_REQUEST_SEND',$httpdata); + if($evt->advise_before()){ + $url = $httpdata['url']; + $data = $httpdata['data']; + $method = $httpdata['method']; + } + $evt->advise_after(); + unset($evt); + return parent::sendRequest($url,$data,$method); + } + +} + diff --git a/inc/HTTPClient.php b/inc/HTTP/HTTPClient.php index 9a20dc598..90c22b7f1 100644 --- a/inc/HTTPClient.php +++ b/inc/HTTP/HTTPClient.php @@ -1,18 +1,9 @@ <?php -/** - * HTTP Client - * - * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) - * @author Andreas Goetz <cpuidle@gmx.de> - */ +namespace dokuwiki\HTTP; define('HTTP_NL',"\r\n"); -/** - * Class HTTPClientException - */ -class HTTPClientException extends Exception { } /** * This class implements a basic HTTP client @@ -28,53 +19,53 @@ class HTTPClientException extends Exception { } */ class HTTPClient { //set these if you like - var $agent; // User agent - var $http; // HTTP version defaults to 1.0 - var $timeout; // read timeout (seconds) - var $cookies; - var $referer; - var $max_redirect; - var $max_bodysize; - var $max_bodysize_abort = true; // if set, abort if the response body is bigger than max_bodysize - var $header_regexp; // if set this RE must match against the headers, else abort - var $headers; - var $debug; - var $start = 0.0; // for timings - var $keep_alive = true; // keep alive rocks + public $agent; // User agent + public $http; // HTTP version defaults to 1.0 + public $timeout; // read timeout (seconds) + public $cookies; + public $referer; + public $max_redirect; + public $max_bodysize; + public $max_bodysize_abort = true; // if set, abort if the response body is bigger than max_bodysize + public $header_regexp; // if set this RE must match against the headers, else abort + public $headers; + public $debug; + public $start = 0.0; // for timings + public $keep_alive = true; // keep alive rocks // don't set these, read on error - var $error; - var $redirect_count; + public $error; + public $redirect_count; // read these after a successful request - var $status; - var $resp_body; - var $resp_headers; + public $status; + public $resp_body; + public $resp_headers; // set these to do basic authentication - var $user; - var $pass; + public $user; + public $pass; // set these if you need to use a proxy - var $proxy_host; - var $proxy_port; - var $proxy_user; - var $proxy_pass; - var $proxy_ssl; //boolean set to true if your proxy needs SSL - var $proxy_except; // regexp of URLs to exclude from proxy + public $proxy_host; + public $proxy_port; + public $proxy_user; + public $proxy_pass; + public $proxy_ssl; //boolean set to true if your proxy needs SSL + public $proxy_except; // regexp of URLs to exclude from proxy // list of kept alive connections - static $connections = array(); + protected static $connections = array(); // what we use as boundary on multipart/form-data posts - var $boundary = '---DokuWikiHTTPClient--4523452351'; + protected $boundary = '---DokuWikiHTTPClient--4523452351'; /** * Constructor. * * @author Andreas Gohr <andi@splitbrain.org> */ - function __construct(){ + public function __construct(){ $this->agent = 'Mozilla/4.0 (compatible; DokuWiki HTTP Client; '.PHP_OS.')'; $this->timeout = 15; $this->cookies = array(); @@ -89,7 +80,7 @@ class HTTPClient { $this->header_regexp= ''; if(extension_loaded('zlib')) $this->headers['Accept-encoding'] = 'gzip'; $this->headers['Accept'] = 'text/xml,application/xml,application/xhtml+xml,'. - 'text/html,text/plain,image/png,image/jpeg,image/gif,*/*'; + 'text/html,text/plain,image/png,image/jpeg,image/gif,*/*'; $this->headers['Accept-Language'] = 'en-us'; } @@ -105,7 +96,7 @@ class HTTPClient { * * @author Andreas Gohr <andi@splitbrain.org> */ - function get($url,$sloppy304=false){ + public function get($url,$sloppy304=false){ if(!$this->sendRequest($url)) return false; if($this->status == 304 && $sloppy304) return $this->resp_body; if($this->status < 200 || $this->status > 206) return false; @@ -127,13 +118,13 @@ class HTTPClient { * * @author Andreas Gohr <andi@splitbrain.org> */ - function dget($url,$data,$sloppy304=false){ + public function dget($url,$data,$sloppy304=false){ if(strpos($url,'?')){ $url .= '&'; }else{ $url .= '?'; } - $url .= $this->_postEncode($data); + $url .= $this->postEncode($data); return $this->get($url,$sloppy304); } @@ -147,7 +138,7 @@ class HTTPClient { * @return false|string response body, false on error * @author Andreas Gohr <andi@splitbrain.org> */ - function post($url,$data){ + public function post($url,$data){ if(!$this->sendRequest($url,$data,'POST')) return false; if($this->status < 200 || $this->status > 206) return false; return $this->resp_body; @@ -170,18 +161,17 @@ class HTTPClient { * @author Andreas Goetz <cpuidle@gmx.de> * @author Andreas Gohr <andi@splitbrain.org> */ - function sendRequest($url,$data='',$method='GET'){ - $this->start = $this->_time(); + public function sendRequest($url,$data='',$method='GET'){ + $this->start = $this->time(); $this->error = ''; $this->status = 0; - $this->status = 0; $this->resp_body = ''; $this->resp_headers = array(); // don't accept gzip if truncated bodies might occur if($this->max_bodysize && - !$this->max_bodysize_abort && - $this->headers['Accept-encoding'] == 'gzip'){ + !$this->max_bodysize_abort && + $this->headers['Accept-encoding'] == 'gzip'){ unset($this->headers['Accept-encoding']); } @@ -230,13 +220,13 @@ class HTTPClient { $headers['Content-Type'] = null; } switch ($headers['Content-Type']) { - case 'multipart/form-data': - $headers['Content-Type'] = 'multipart/form-data; boundary=' . $this->boundary; - $data = $this->_postMultipartEncode($data); - break; - default: - $headers['Content-Type'] = 'application/x-www-form-urlencoded'; - $data = $this->_postEncode($data); + case 'multipart/form-data': + $headers['Content-Type'] = 'multipart/form-data; boundary=' . $this->boundary; + $data = $this->postMultipartEncode($data); + break; + default: + $headers['Content-Type'] = 'application/x-www-form-urlencoded'; + $data = $this->postEncode($data); } } }elseif($method == 'GET'){ @@ -256,15 +246,15 @@ class HTTPClient { } // already connected? - $connectionId = $this->_uniqueConnectionId($server,$port); - $this->_debug('connection pool', self::$connections); + $connectionId = $this->uniqueConnectionId($server,$port); + $this->debug('connection pool', self::$connections); $socket = null; if (isset(self::$connections[$connectionId])) { - $this->_debug('reusing connection', $connectionId); + $this->debug('reusing connection', $connectionId); $socket = self::$connections[$connectionId]; } if (is_null($socket) || feof($socket)) { - $this->_debug('opening connection', $connectionId); + $this->debug('opening connection', $connectionId); // open socket $socket = @fsockopen($server,$port,$errno, $errstr, $this->timeout); if (!$socket){ @@ -275,7 +265,7 @@ class HTTPClient { // try establish a CONNECT tunnel for SSL try { - if($this->_ssltunnel($socket, $request_url)){ + if($this->ssltunnel($socket, $request_url)){ // no keep alive for tunnels $this->keep_alive = false; // tunnel is authed already @@ -311,22 +301,22 @@ class HTTPClient { // build request $request = "$method $request_url HTTP/".$this->http.HTTP_NL; - $request .= $this->_buildHeaders($headers); - $request .= $this->_getCookies(); + $request .= $this->buildHeaders($headers); + $request .= $this->getCookies(); $request .= HTTP_NL; $request .= $data; - $this->_debug('request',$request); - $this->_sendData($socket, $request, 'request'); + $this->debug('request',$request); + $this->sendData($socket, $request, 'request'); // read headers from socket $r_headers = ''; do{ - $r_line = $this->_readLine($socket, 'headers'); + $r_line = $this->readLine($socket, 'headers'); $r_headers .= $r_line; }while($r_line != "\r\n" && $r_line != "\n"); - $this->_debug('response headers',$r_headers); + $this->debug('response headers',$r_headers); // check if expected body size exceeds allowance if($this->max_bodysize && preg_match('/\r?\nContent-Length:\s*(\d+)\r?\n/i',$r_headers,$match)){ @@ -345,7 +335,7 @@ class HTTPClient { $this->status = $m[2]; // handle headers and cookies - $this->resp_headers = $this->_parseHeaders($r_headers); + $this->resp_headers = $this->parseHeaders($r_headers); if(isset($this->resp_headers['set-cookie'])){ foreach ((array) $this->resp_headers['set-cookie'] as $cookie){ list($cookie) = explode(';',$cookie,2); @@ -361,7 +351,7 @@ class HTTPClient { } } - $this->_debug('Object headers',$this->resp_headers); + $this->debug('Object headers',$this->resp_headers); // check server status code to follow redirect if($this->status == 301 || $this->status == 302 ){ @@ -381,10 +371,10 @@ class HTTPClient { 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']; + dirname($uri['path']).'/'.$this->resp_headers['location']; }else{ $this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].':'.$uri['port']. - $this->resp_headers['location']; + $this->resp_headers['location']; } } // perform redirected request, always via GET (required by RFC) @@ -398,18 +388,25 @@ class HTTPClient { //read body (with chunked encoding if needed) $r_body = ''; - if((isset($this->resp_headers['transfer-encoding']) && $this->resp_headers['transfer-encoding'] == 'chunked') - || (isset($this->resp_headers['transfer-coding']) && $this->resp_headers['transfer-coding'] == 'chunked')){ + if( + ( + isset($this->resp_headers['transfer-encoding']) && + $this->resp_headers['transfer-encoding'] == 'chunked' + ) || ( + isset($this->resp_headers['transfer-coding']) && + $this->resp_headers['transfer-coding'] == 'chunked' + ) + ) { $abort = false; do { $chunk_size = ''; - while (preg_match('/^[a-zA-Z0-9]?$/',$byte=$this->_readData($socket,1,'chunk'))){ + while (preg_match('/^[a-zA-Z0-9]?$/',$byte=$this->readData($socket,1,'chunk'))){ // read chunksize until \r $chunk_size .= $byte; if (strlen($chunk_size) > 128) // set an abritrary limit on the size of chunks throw new HTTPClientException('Allowed response size exceeded'); } - $this->_readLine($socket, 'chunk'); // readtrailing \n + $this->readLine($socket, 'chunk'); // readtrailing \n $chunk_size = hexdec($chunk_size); if($this->max_bodysize && $chunk_size+strlen($r_body) > $this->max_bodysize){ @@ -421,8 +418,8 @@ class HTTPClient { } if ($chunk_size > 0) { - $r_body .= $this->_readData($socket, $chunk_size, 'chunk'); - $this->_readData($socket, 2, 'chunk'); // read trailing \r\n + $r_body .= $this->readData($socket, $chunk_size, 'chunk'); + $this->readData($socket, 2, 'chunk'); // read trailing \r\n } } while ($chunk_size && !$abort); }elseif(isset($this->resp_headers['content-length']) && !isset($this->resp_headers['transfer-encoding'])){ @@ -433,21 +430,25 @@ class HTTPClient { // read up to the content-length or max_bodysize // for keep alive we need to read the whole message to clean up the socket for the next read - if(!$this->keep_alive && $this->max_bodysize && $this->max_bodysize < $this->resp_headers['content-length']){ - $length = $this->max_bodysize+1; + if( + !$this->keep_alive && + $this->max_bodysize && + $this->max_bodysize < $this->resp_headers['content-length'] + ) { + $length = $this->max_bodysize + 1; }else{ $length = $this->resp_headers['content-length']; } - $r_body = $this->_readData($socket, $length, 'response (content-length limited)', true); + $r_body = $this->readData($socket, $length, 'response (content-length limited)', true); }elseif( !isset($this->resp_headers['transfer-encoding']) && $this->max_bodysize && !$this->keep_alive){ - $r_body = $this->_readData($socket, $this->max_bodysize+1, 'response (content-length limited)', true); + $r_body = $this->readData($socket, $this->max_bodysize+1, 'response (content-length limited)', true); } elseif ((int)$this->status === 204) { // request has no content } else{ // read entire socket while (!feof($socket)) { - $r_body .= $this->_readData($socket, 4096, 'response (unlimited)', true); + $r_body .= $this->readData($socket, 4096, 'response (unlimited)', true); } } @@ -472,7 +473,7 @@ class HTTPClient { } if (!$this->keep_alive || - (isset($this->resp_headers['connection']) && $this->resp_headers['connection'] == 'Close')) { + (isset($this->resp_headers['connection']) && $this->resp_headers['connection'] == 'Close')) { // close socket fclose($socket); unset(self::$connections[$connectionId]); @@ -480,8 +481,8 @@ class HTTPClient { // decode gzip if needed if(isset($this->resp_headers['content-encoding']) && - $this->resp_headers['content-encoding'] == 'gzip' && - strlen($r_body) > 10 && substr($r_body,0,3)=="\x1f\x8b\x08"){ + $this->resp_headers['content-encoding'] == 'gzip' && + strlen($r_body) > 10 && substr($r_body,0,3)=="\x1f\x8b\x08"){ $this->resp_body = @gzinflate(substr($r_body, 10)); if($this->resp_body === false){ $this->error = 'Failed to decompress gzip encoded content'; @@ -491,7 +492,7 @@ class HTTPClient { $this->resp_body = $r_body; } - $this->_debug('response body',$this->resp_body); + $this->debug('response body',$this->resp_body); $this->redirect_count = 0; return true; } @@ -506,7 +507,7 @@ class HTTPClient { * @throws HTTPClientException when a tunnel is needed but could not be established * @return bool true if a tunnel was established */ - function _ssltunnel(&$socket, &$requesturl){ + protected function ssltunnel(&$socket, &$requesturl){ if(!$this->proxy_host) return false; $requestinfo = parse_url($requesturl); if($requestinfo['scheme'] != 'https') return false; @@ -520,17 +521,17 @@ class HTTPClient { } $request .= HTTP_NL; - $this->_debug('SSL Tunnel CONNECT',$request); - $this->_sendData($socket, $request, 'SSL Tunnel CONNECT'); + $this->debug('SSL Tunnel CONNECT',$request); + $this->sendData($socket, $request, 'SSL Tunnel CONNECT'); // read headers from socket $r_headers = ''; do{ - $r_line = $this->_readLine($socket, 'headers'); + $r_line = $this->readLine($socket, 'headers'); $r_headers .= $r_line; }while($r_line != "\r\n" && $r_line != "\n"); - $this->_debug('SSL Tunnel Response',$r_headers); + $this->debug('SSL Tunnel Response',$r_headers); if(preg_match('/^HTTP\/1\.[01] 200/i',$r_headers)){ // set correct peer name for verification (enabled since PHP 5.6) stream_context_set_option($socket, 'ssl', 'peer_name', $requestinfo['host']); @@ -546,11 +547,13 @@ class HTTPClient { if (@stream_socket_enable_crypto($socket, true, $cryptoMethod)) { $requesturl = $requestinfo['path']. - (!empty($requestinfo['query'])?'?'.$requestinfo['query']:''); + (!empty($requestinfo['query'])?'?'.$requestinfo['query']:''); return true; } - throw new HTTPClientException('Failed to set up crypto for secure connection to '.$requestinfo['host'], -151); + throw new HTTPClientException( + 'Failed to set up crypto for secure connection to '.$requestinfo['host'], -151 + ); } throw new HTTPClientException('Failed to establish secure proxy connection', -150); @@ -566,13 +569,13 @@ class HTTPClient { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function _sendData($socket, $data, $message) { + protected function sendData($socket, $data, $message) { // send request $towrite = strlen($data); $written = 0; while($written < $towrite){ // check timeout - $time_used = $this->_time() - $this->start; + $time_used = $this->time() - $this->start; if($time_used > $this->timeout) throw new HTTPClientException(sprintf('Timeout while sending %s (%.3fs)',$message, $time_used), -100); if(feof($socket)) @@ -584,8 +587,8 @@ class HTTPClient { $sel_e = null; // wait for stream ready or timeout (1sec) if(@stream_select($sel_r,$sel_w,$sel_e,1) === false){ - usleep(1000); - continue; + usleep(1000); + continue; } // write to stream @@ -611,17 +614,17 @@ class HTTPClient { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function _readData($socket, $nbytes, $message, $ignore_eof = false) { + protected function readData($socket, $nbytes, $message, $ignore_eof = false) { $r_data = ''; // Does not return immediately so timeout and eof can be checked if ($nbytes < 0) $nbytes = 0; $to_read = $nbytes; do { - $time_used = $this->_time() - $this->start; + $time_used = $this->time() - $this->start; if ($time_used > $this->timeout) throw new HTTPClientException( - sprintf('Timeout while reading %s after %d bytes (%.3fs)', $message, - strlen($r_data), $time_used), -100); + sprintf('Timeout while reading %s after %d bytes (%.3fs)', $message, + strlen($r_data), $time_used), -100); if(feof($socket)) { if(!$ignore_eof) throw new HTTPClientException("Premature End of File (socket) while reading $message"); @@ -635,8 +638,8 @@ class HTTPClient { $sel_e = null; // wait for stream ready or timeout (1sec) if(@stream_select($sel_r,$sel_w,$sel_e,1) === false){ - usleep(1000); - continue; + usleep(1000); + continue; } $bytes = fread($socket, $to_read); @@ -661,14 +664,14 @@ class HTTPClient { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function _readLine($socket, $message) { + protected function readLine($socket, $message) { $r_data = ''; do { - $time_used = $this->_time() - $this->start; + $time_used = $this->time() - $this->start; if ($time_used > $this->timeout) throw new HTTPClientException( - sprintf('Timeout while reading %s (%.3fs) >%s<', $message, $time_used, $r_data), - -100); + sprintf('Timeout while reading %s (%.3fs) >%s<', $message, $time_used, $r_data), + -100); if(feof($socket)) throw new HTTPClientException("Premature End of File (socket) while reading $message"); @@ -678,8 +681,8 @@ class HTTPClient { $sel_e = null; // wait for stream ready or timeout (1sec) if(@stream_select($sel_r,$sel_w,$sel_e,1) === false){ - usleep(1000); - continue; + usleep(1000); + continue; } $r_data = fgets($socket, 1024); @@ -697,12 +700,12 @@ class HTTPClient { * @param string $info * @param mixed $var */ - function _debug($info,$var=null){ + protected function debug($info,$var=null){ if(!$this->debug) return; if(php_sapi_name() == 'cli'){ - $this->_debug_text($info, $var); + $this->debugText($info, $var); }else{ - $this->_debug_html($info, $var); + $this->debugHtml($info, $var); } } @@ -712,8 +715,8 @@ class HTTPClient { * @param string $info * @param mixed $var */ - function _debug_html($info, $var=null){ - print '<b>'.$info.'</b> '.($this->_time() - $this->start).'s<br />'; + protected function debugHtml($info, $var=null){ + print '<b>'.$info.'</b> '.($this->time() - $this->start).'s<br />'; if(!is_null($var)){ ob_start(); print_r($var); @@ -729,8 +732,8 @@ class HTTPClient { * @param string $info * @param mixed $var */ - function _debug_text($info, $var=null){ - print '*'.$info.'* '.($this->_time() - $this->start)."s\n"; + protected function debugText($info, $var=null){ + print '*'.$info.'* '.($this->time() - $this->start)."s\n"; if(!is_null($var)) print_r($var); print "\n-----------------------------------------------\n"; } @@ -740,7 +743,7 @@ class HTTPClient { * * @return float */ - static function _time(){ + protected static function time(){ list($usec, $sec) = explode(" ", microtime()); return ((float)$usec + (float)$sec); } @@ -755,7 +758,7 @@ class HTTPClient { * @param string $string * @return array */ - function _parseHeaders($string){ + protected function parseHeaders($string){ $headers = array(); $lines = explode("\n",$string); array_shift($lines); //skip first line (status) @@ -786,7 +789,7 @@ class HTTPClient { * @param array $headers * @return string */ - function _buildHeaders($headers){ + protected function buildHeaders($headers){ $string = ''; foreach($headers as $key => $value){ if($value === '') continue; @@ -802,7 +805,7 @@ class HTTPClient { * * @return string */ - function _getCookies(){ + protected function getCookies(){ $headers = ''; foreach ($this->cookies as $key => $val){ $headers .= "$key=$val; "; @@ -820,7 +823,7 @@ class HTTPClient { * @param array $data * @return string */ - function _postEncode($data){ + protected function postEncode($data){ return http_build_query($data,'','&'); } @@ -833,7 +836,7 @@ class HTTPClient { * @param array $data * @return string */ - function _postMultipartEncode($data){ + protected function postMultipartEncode($data){ $boundary = '--'.$this->boundary; $out = ''; foreach($data as $key => $val){ @@ -864,80 +867,7 @@ class HTTPClient { * @param string $port * @return string unique identifier */ - function _uniqueConnectionId($server, $port) { + protected function uniqueConnectionId($server, $port) { return "$server:$port"; } } - - -/** - * Adds DokuWiki specific configs to the HTTP client - * - * @author Andreas Goetz <cpuidle@gmx.de> - */ -class DokuHTTPClient extends HTTPClient { - - /** - * Constructor. - * - * @author Andreas Gohr <andi@splitbrain.org> - */ - function __construct(){ - global $conf; - - // call parent constructor - parent::__construct(); - - // set some values from the config - $this->proxy_host = $conf['proxy']['host']; - $this->proxy_port = $conf['proxy']['port']; - $this->proxy_user = $conf['proxy']['user']; - $this->proxy_pass = conf_decodeString($conf['proxy']['pass']); - $this->proxy_ssl = $conf['proxy']['ssl']; - $this->proxy_except = $conf['proxy']['except']; - - // allow enabling debugging via URL parameter (if debugging allowed) - if($conf['allowdebug']) { - if( - isset($_REQUEST['httpdebug']) || - ( - isset($_SERVER['HTTP_REFERER']) && - strpos($_SERVER['HTTP_REFERER'], 'httpdebug') !== false - ) - ) { - $this->debug = true; - } - } - } - - - /** - * Wraps an event around the parent function - * - * @triggers HTTPCLIENT_REQUEST_SEND - * @author Andreas Gohr <andi@splitbrain.org> - */ - /** - * @param string $url - * @param string|array $data the post data either as array or raw data - * @param string $method - * @return bool - */ - function sendRequest($url,$data='',$method='GET'){ - $httpdata = array('url' => $url, - 'data' => $data, - 'method' => $method); - $evt = new Doku_Event('HTTPCLIENT_REQUEST_SEND',$httpdata); - if($evt->advise_before()){ - $url = $httpdata['url']; - $data = $httpdata['data']; - $method = $httpdata['method']; - } - $evt->advise_after(); - unset($evt); - return parent::sendRequest($url,$data,$method); - } - -} - -//Setup VIM: ex: et ts=4 : diff --git a/inc/HTTP/HTTPClientException.php b/inc/HTTP/HTTPClientException.php new file mode 100644 index 000000000..5b8f4eeca --- /dev/null +++ b/inc/HTTP/HTTPClientException.php @@ -0,0 +1,10 @@ +<?php + +namespace dokuwiki\HTTP; + +use Exception; + +class HTTPClientException extends Exception +{ + +} diff --git a/inc/IXR_Library.php b/inc/IXR_Library.php index 5ae1402b9..e671b6880 100644 --- a/inc/IXR_Library.php +++ b/inc/IXR_Library.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\HTTP\DokuHTTPClient; + /** * IXR - The Incutio XML-RPC Library * diff --git a/inc/Input.class.php b/inc/Input.class.php deleted file mode 100644 index 199994d8d..000000000 --- a/inc/Input.class.php +++ /dev/null @@ -1,335 +0,0 @@ -<?php - -/** - * Encapsulates access to the $_REQUEST array, making sure used parameters are initialized and - * have the correct type. - * - * All function access the $_REQUEST array by default, if you want to access $_POST or $_GET - * explicitly use the $post and $get members. - * - * @author Andreas Gohr <andi@splitbrain.org> - */ -class Input { - - /** @var PostInput Access $_POST parameters */ - public $post; - /** @var GetInput Access $_GET parameters */ - public $get; - /** @var ServerInput Access $_SERVER parameters */ - public $server; - - protected $access; - - /** - * @var Callable - */ - protected $filter; - - /** - * Intilizes the Input class and it subcomponents - */ - function __construct() { - $this->access = &$_REQUEST; - $this->post = new PostInput(); - $this->get = new GetInput(); - $this->server = new ServerInput(); - } - - /** - * Apply the set filter to the given value - * - * @param string $data - * @return string - */ - protected function applyfilter($data){ - if(!$this->filter) return $data; - return call_user_func($this->filter, $data); - } - - /** - * Return a filtered copy of the input object - * - * Expects a callable that accepts one string parameter and returns a filtered string - * - * @param Callable|string $filter - * @return Input - */ - public function filter($filter='stripctl'){ - $this->filter = $filter; - $clone = clone $this; - $this->filter = ''; - return $clone; - } - - /** - * Check if a parameter was set - * - * Basically a wrapper around isset. When called on the $post and $get subclasses, - * the parameter is set to $_POST or $_GET and to $_REQUEST - * - * @see isset - * @param string $name Parameter name - * @return bool - */ - public function has($name) { - return isset($this->access[$name]); - } - - /** - * Remove a parameter from the superglobals - * - * Basically a wrapper around unset. When NOT called on the $post and $get subclasses, - * the parameter will also be removed from $_POST or $_GET - * - * @see isset - * @param string $name Parameter name - */ - public function remove($name) { - if(isset($this->access[$name])) { - unset($this->access[$name]); - } - // also remove from sub classes - if(isset($this->post) && isset($_POST[$name])) { - unset($_POST[$name]); - } - if(isset($this->get) && isset($_GET[$name])) { - unset($_GET[$name]); - } - } - - /** - * Access a request parameter without any type conversion - * - * @param string $name Parameter name - * @param mixed $default Default to return if parameter isn't set - * @param bool $nonempty Return $default if parameter is set but empty() - * @return mixed - */ - public function param($name, $default = null, $nonempty = false) { - if(!isset($this->access[$name])) return $default; - $value = $this->applyfilter($this->access[$name]); - if($nonempty && empty($value)) return $default; - return $value; - } - - /** - * Sets a parameter - * - * @param string $name Parameter name - * @param mixed $value Value to set - */ - public function set($name, $value) { - $this->access[$name] = $value; - } - - /** - * Get a reference to a request parameter - * - * This avoids copying data in memory, when the parameter is not set it will be created - * and intialized with the given $default value before a reference is returned - * - * @param string $name Parameter name - * @param mixed $default If parameter is not set, initialize with this value - * @param bool $nonempty Init with $default if parameter is set but empty() - * @return mixed (reference) - */ - public function &ref($name, $default = '', $nonempty = false) { - if(!isset($this->access[$name]) || ($nonempty && empty($this->access[$name]))) { - $this->set($name, $default); - } - - return $this->access[$name]; - } - - /** - * Access a request parameter as int - * - * @param string $name Parameter name - * @param int $default Default to return if parameter isn't set or is an array - * @param bool $nonempty Return $default if parameter is set but empty() - * @return int - */ - public function int($name, $default = 0, $nonempty = false) { - if(!isset($this->access[$name])) return $default; - if(is_array($this->access[$name])) return $default; - $value = $this->applyfilter($this->access[$name]); - if($value === '') return $default; - if($nonempty && empty($value)) return $default; - - return (int) $value; - } - - /** - * Access a request parameter as string - * - * @param string $name Parameter name - * @param string $default Default to return if parameter isn't set or is an array - * @param bool $nonempty Return $default if parameter is set but empty() - * @return string - */ - public function str($name, $default = '', $nonempty = false) { - if(!isset($this->access[$name])) return $default; - if(is_array($this->access[$name])) return $default; - $value = $this->applyfilter($this->access[$name]); - if($nonempty && empty($value)) return $default; - - return (string) $value; - } - - /** - * Access a request parameter and make sure it is has a valid value - * - * Please note that comparisons to the valid values are not done typesafe (request vars - * are always strings) however the function will return the correct type from the $valids - * array when an match was found. - * - * @param string $name Parameter name - * @param array $valids Array of valid values - * @param mixed $default Default to return if parameter isn't set or not valid - * @return null|mixed - */ - public function valid($name, $valids, $default = null) { - if(!isset($this->access[$name])) return $default; - if(is_array($this->access[$name])) return $default; // we don't allow arrays - $value = $this->applyfilter($this->access[$name]); - $found = array_search($value, $valids); - if($found !== false) return $valids[$found]; // return the valid value for type safety - return $default; - } - - /** - * Access a request parameter as bool - * - * Note: $nonempty is here for interface consistency and makes not much sense for booleans - * - * @param string $name Parameter name - * @param mixed $default Default to return if parameter isn't set - * @param bool $nonempty Return $default if parameter is set but empty() - * @return bool - */ - public function bool($name, $default = false, $nonempty = false) { - if(!isset($this->access[$name])) return $default; - if(is_array($this->access[$name])) return $default; - $value = $this->applyfilter($this->access[$name]); - if($value === '') return $default; - if($nonempty && empty($value)) return $default; - - return (bool) $value; - } - - /** - * Access a request parameter as array - * - * @param string $name Parameter name - * @param mixed $default Default to return if parameter isn't set - * @param bool $nonempty Return $default if parameter is set but empty() - * @return array - */ - public function arr($name, $default = array(), $nonempty = false) { - if(!isset($this->access[$name])) return $default; - if(!is_array($this->access[$name])) return $default; - if($nonempty && empty($this->access[$name])) return $default; - - return (array) $this->access[$name]; - } - - /** - * Create a simple key from an array key - * - * This is useful to access keys where the information is given as an array key or as a single array value. - * For example when the information was submitted as the name of a submit button. - * - * This function directly changes the access array. - * - * Eg. $_REQUEST['do']['save']='Speichern' becomes $_REQUEST['do'] = 'save' - * - * This function returns the $INPUT object itself for easy chaining - * - * @param string $name - * @return Input - */ - public function extract($name){ - if(!isset($this->access[$name])) return $this; - if(!is_array($this->access[$name])) return $this; - $keys = array_keys($this->access[$name]); - if(!$keys){ - // this was an empty array - $this->remove($name); - return $this; - } - // get the first key - $value = array_shift($keys); - if($value === 0){ - // we had a numeric array, assume the value is not in the key - $value = array_shift($this->access[$name]); - } - - $this->set($name, $value); - return $this; - } -} - -/** - * Internal class used for $_POST access in Input class - */ -class PostInput extends Input { - protected $access; - - /** - * Initialize the $access array, remove subclass members - */ - function __construct() { - $this->access = &$_POST; - } - - /** - * Sets a parameter in $_POST and $_REQUEST - * - * @param string $name Parameter name - * @param mixed $value Value to set - */ - public function set($name, $value) { - parent::set($name, $value); - $_REQUEST[$name] = $value; - } -} - -/** - * Internal class used for $_GET access in Input class - */ -class GetInput extends Input { - protected $access; - - /** - * Initialize the $access array, remove subclass members - */ - function __construct() { - $this->access = &$_GET; - } - - /** - * Sets a parameter in $_GET and $_REQUEST - * - * @param string $name Parameter name - * @param mixed $value Value to set - */ - public function set($name, $value) { - parent::set($name, $value); - $_REQUEST[$name] = $value; - } -} - -/** - * Internal class used for $_SERVER access in Input class - */ -class ServerInput extends Input { - protected $access; - - /** - * Initialize the $access array, remove subclass members - */ - function __construct() { - $this->access = &$_SERVER; - } - -} diff --git a/inc/Input/Get.php b/inc/Input/Get.php new file mode 100644 index 000000000..99ab2655d --- /dev/null +++ b/inc/Input/Get.php @@ -0,0 +1,29 @@ +<?php + +namespace dokuwiki\Input; + +/** + * Internal class used for $_GET access in dokuwiki\Input\Input class + */ +class Get extends Input +{ + /** @noinspection PhpMissingParentConstructorInspection + * Initialize the $access array, remove subclass members + */ + public function __construct() + { + $this->access = &$_GET; + } + + /** + * Sets a parameter in $_GET and $_REQUEST + * + * @param string $name Parameter name + * @param mixed $value Value to set + */ + public function set($name, $value) + { + parent::set($name, $value); + $_REQUEST[$name] = $value; + } +} diff --git a/inc/Input/Input.php b/inc/Input/Input.php new file mode 100644 index 000000000..3d2426bcc --- /dev/null +++ b/inc/Input/Input.php @@ -0,0 +1,287 @@ +<?php + +namespace dokuwiki\Input; + +/** + * Encapsulates access to the $_REQUEST array, making sure used parameters are initialized and + * have the correct type. + * + * All function access the $_REQUEST array by default, if you want to access $_POST or $_GET + * explicitly use the $post and $get members. + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +class Input +{ + + /** @var Post Access $_POST parameters */ + public $post; + /** @var Get Access $_GET parameters */ + public $get; + /** @var Server Access $_SERVER parameters */ + public $server; + + protected $access; + + /** + * @var Callable + */ + protected $filter; + + /** + * Intilizes the dokuwiki\Input\Input class and it subcomponents + */ + public function __construct() + { + $this->access = &$_REQUEST; + $this->post = new Post(); + $this->get = new Get(); + $this->server = new Server(); + } + + /** + * Apply the set filter to the given value + * + * @param string $data + * @return string + */ + protected function applyfilter($data) + { + if (!$this->filter) return $data; + return call_user_func($this->filter, $data); + } + + /** + * Return a filtered copy of the input object + * + * Expects a callable that accepts one string parameter and returns a filtered string + * + * @param Callable|string $filter + * @return Input + */ + public function filter($filter = 'stripctl') + { + $this->filter = $filter; + $clone = clone $this; + $this->filter = ''; + return $clone; + } + + /** + * Check if a parameter was set + * + * Basically a wrapper around isset. When called on the $post and $get subclasses, + * the parameter is set to $_POST or $_GET and to $_REQUEST + * + * @see isset + * @param string $name Parameter name + * @return bool + */ + public function has($name) + { + return isset($this->access[$name]); + } + + /** + * Remove a parameter from the superglobals + * + * Basically a wrapper around unset. When NOT called on the $post and $get subclasses, + * the parameter will also be removed from $_POST or $_GET + * + * @see isset + * @param string $name Parameter name + */ + public function remove($name) + { + if (isset($this->access[$name])) { + unset($this->access[$name]); + } + // also remove from sub classes + if (isset($this->post) && isset($_POST[$name])) { + unset($_POST[$name]); + } + if (isset($this->get) && isset($_GET[$name])) { + unset($_GET[$name]); + } + } + + /** + * Access a request parameter without any type conversion + * + * @param string $name Parameter name + * @param mixed $default Default to return if parameter isn't set + * @param bool $nonempty Return $default if parameter is set but empty() + * @return mixed + */ + public function param($name, $default = null, $nonempty = false) + { + if (!isset($this->access[$name])) return $default; + $value = $this->applyfilter($this->access[$name]); + if ($nonempty && empty($value)) return $default; + return $value; + } + + /** + * Sets a parameter + * + * @param string $name Parameter name + * @param mixed $value Value to set + */ + public function set($name, $value) + { + $this->access[$name] = $value; + } + + /** + * Get a reference to a request parameter + * + * This avoids copying data in memory, when the parameter is not set it will be created + * and intialized with the given $default value before a reference is returned + * + * @param string $name Parameter name + * @param mixed $default If parameter is not set, initialize with this value + * @param bool $nonempty Init with $default if parameter is set but empty() + * @return mixed (reference) + */ + public function &ref($name, $default = '', $nonempty = false) + { + if (!isset($this->access[$name]) || ($nonempty && empty($this->access[$name]))) { + $this->set($name, $default); + } + + return $this->access[$name]; + } + + /** + * Access a request parameter as int + * + * @param string $name Parameter name + * @param int $default Default to return if parameter isn't set or is an array + * @param bool $nonempty Return $default if parameter is set but empty() + * @return int + */ + public function int($name, $default = 0, $nonempty = false) + { + if (!isset($this->access[$name])) return $default; + if (is_array($this->access[$name])) return $default; + $value = $this->applyfilter($this->access[$name]); + if ($value === '') return $default; + if ($nonempty && empty($value)) return $default; + + return (int)$value; + } + + /** + * Access a request parameter as string + * + * @param string $name Parameter name + * @param string $default Default to return if parameter isn't set or is an array + * @param bool $nonempty Return $default if parameter is set but empty() + * @return string + */ + public function str($name, $default = '', $nonempty = false) + { + if (!isset($this->access[$name])) return $default; + if (is_array($this->access[$name])) return $default; + $value = $this->applyfilter($this->access[$name]); + if ($nonempty && empty($value)) return $default; + + return (string)$value; + } + + /** + * Access a request parameter and make sure it is has a valid value + * + * Please note that comparisons to the valid values are not done typesafe (request vars + * are always strings) however the function will return the correct type from the $valids + * array when an match was found. + * + * @param string $name Parameter name + * @param array $valids Array of valid values + * @param mixed $default Default to return if parameter isn't set or not valid + * @return null|mixed + */ + public function valid($name, $valids, $default = null) + { + if (!isset($this->access[$name])) return $default; + if (is_array($this->access[$name])) return $default; // we don't allow arrays + $value = $this->applyfilter($this->access[$name]); + $found = array_search($value, $valids); + if ($found !== false) return $valids[$found]; // return the valid value for type safety + return $default; + } + + /** + * Access a request parameter as bool + * + * Note: $nonempty is here for interface consistency and makes not much sense for booleans + * + * @param string $name Parameter name + * @param mixed $default Default to return if parameter isn't set + * @param bool $nonempty Return $default if parameter is set but empty() + * @return bool + */ + public function bool($name, $default = false, $nonempty = false) + { + if (!isset($this->access[$name])) return $default; + if (is_array($this->access[$name])) return $default; + $value = $this->applyfilter($this->access[$name]); + if ($value === '') return $default; + if ($nonempty && empty($value)) return $default; + + return (bool)$value; + } + + /** + * Access a request parameter as array + * + * @param string $name Parameter name + * @param mixed $default Default to return if parameter isn't set + * @param bool $nonempty Return $default if parameter is set but empty() + * @return array + */ + public function arr($name, $default = array(), $nonempty = false) + { + if (!isset($this->access[$name])) return $default; + if (!is_array($this->access[$name])) return $default; + if ($nonempty && empty($this->access[$name])) return $default; + + return (array)$this->access[$name]; + } + + /** + * Create a simple key from an array key + * + * This is useful to access keys where the information is given as an array key or as a single array value. + * For example when the information was submitted as the name of a submit button. + * + * This function directly changes the access array. + * + * Eg. $_REQUEST['do']['save']='Speichern' becomes $_REQUEST['do'] = 'save' + * + * This function returns the $INPUT object itself for easy chaining + * + * @param string $name + * @return Input + */ + public function extract($name) + { + if (!isset($this->access[$name])) return $this; + if (!is_array($this->access[$name])) return $this; + $keys = array_keys($this->access[$name]); + if (!$keys) { + // this was an empty array + $this->remove($name); + return $this; + } + // get the first key + $value = array_shift($keys); + if ($value === 0) { + // we had a numeric array, assume the value is not in the key + $value = array_shift($this->access[$name]); + } + + $this->set($name, $value); + return $this; + } +} diff --git a/inc/Input/Post.php b/inc/Input/Post.php new file mode 100644 index 000000000..137cd72f4 --- /dev/null +++ b/inc/Input/Post.php @@ -0,0 +1,30 @@ +<?php + +namespace dokuwiki\Input; + +/** + * Internal class used for $_POST access in dokuwiki\Input\Input class + */ +class Post extends Input +{ + + /** @noinspection PhpMissingParentConstructorInspection + * Initialize the $access array, remove subclass members + */ + public function __construct() + { + $this->access = &$_POST; + } + + /** + * Sets a parameter in $_POST and $_REQUEST + * + * @param string $name Parameter name + * @param mixed $value Value to set + */ + public function set($name, $value) + { + parent::set($name, $value); + $_REQUEST[$name] = $value; + } +} diff --git a/inc/Input/Server.php b/inc/Input/Server.php new file mode 100644 index 000000000..60964fd8f --- /dev/null +++ b/inc/Input/Server.php @@ -0,0 +1,19 @@ +<?php + +namespace dokuwiki\Input; + +/** + * Internal class used for $_SERVER access in dokuwiki\Input\Input class + */ +class Server extends Input +{ + + /** @noinspection PhpMissingParentConstructorInspection + * Initialize the $access array, remove subclass members + */ + public function __construct() + { + $this->access = &$_SERVER; + } + +} diff --git a/inc/JSON.php b/inc/JSON.php deleted file mode 100644 index ad2272d09..000000000 --- a/inc/JSON.php +++ /dev/null @@ -1,827 +0,0 @@ -<?php -/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ - -/** - * Converts to and from JSON format. - * - * JSON (JavaScript Object Notation) is a lightweight data-interchange - * format. It is easy for humans to read and write. It is easy for machines - * to parse and generate. It is based on a subset of the JavaScript - * Programming Language, Standard ECMA-262 3rd Edition - December 1999. - * This feature can also be found in Python. JSON is a text format that is - * completely language independent but uses conventions that are familiar - * to programmers of the C-family of languages, including C, C++, C#, Java, - * JavaScript, Perl, TCL, and many others. These properties make JSON an - * ideal data-interchange language. - * - * This package provides a simple encoder and decoder for JSON notation. It - * is intended for use with client-side Javascript applications that make - * use of HTTPRequest to perform server communication functions - data can - * be encoded into JSON notation for use in a client-side javascript, or - * decoded from incoming Javascript requests. JSON format is native to - * Javascript, and can be directly eval()'ed with no further parsing - * overhead - * - * All strings should be in ASCII or UTF-8 format! - * - * LICENSE: Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: Redistributions of source code must retain the - * above copyright notice, this list of conditions and the following - * disclaimer. Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH - * DAMAGE. - * - * @author Michal Migurski <mike-json@teczno.com> - * @author Matt Knapp <mdknapp[at]gmail[dot]com> - * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> - * @copyright 2005 Michal Migurski - * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ - * @license http://www.opensource.org/licenses/bsd-license.php - * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 - */ - -// for DokuWiki -if(!defined('DOKU_INC')) die('meh.'); - -/** - * Default decoding - */ -define('JSON_STRICT_TYPE', 0); - -/** - * Marker constant for JSON::decode(), used to flag stack state - */ -define('JSON_SLICE', 1); - -/** - * Marker constant for JSON::decode(), used to flag stack state - */ -define('JSON_IN_STR', 2); - -/** - * Marker constant for JSON::decode(), used to flag stack state - */ -define('JSON_IN_ARR', 3); - -/** - * Marker constant for JSON::decode(), used to flag stack state - */ -define('JSON_IN_OBJ', 4); - -/** - * Marker constant for JSON::decode(), used to flag stack state - */ -define('JSON_IN_CMT', 5); - -/** - * Behavior switch for JSON::decode() - */ -define('JSON_LOOSE_TYPE', 16); - -/** - * Behavior switch for JSON::decode() - */ -define('JSON_SUPPRESS_ERRORS', 32); - -/** - * Converts to and from JSON format. - * - * Brief example of use: - * - * <code> - * // create a new instance of JSON - * $json = new JSON(); - * - * // convert a complexe value to JSON notation, and send it to the browser - * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); - * $output = $json->encode($value); - * - * print($output); - * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] - * - * // accept incoming POST data, assumed to be in JSON notation - * $input = file_get_contents('php://input', 1000000); - * $value = $json->decode($input); - * </code> - */ -class JSON -{ - /** - * Disables the use of PHP5's native json_decode() - * - * You shouldn't change this usually because the native function is much - * faster. However, this non-native will also parse slightly broken JSON - * which might be handy when talking to a non-conform endpoint - */ - public $skipnative = false; - - /** - * constructs a new JSON instance - * - * @param int $use object behavior flags; combine with boolean-OR - * - * possible values: - * - JSON_LOOSE_TYPE: loose typing. - * "{...}" syntax creates associative arrays - * instead of objects in decode(). - * - JSON_SUPPRESS_ERRORS: error suppression. - * Values which can't be encoded (e.g. resources) - * appear as NULL instead of throwing errors. - * By default, a deeply-nested resource will - * bubble up with an error, so all return values - * from encode() should be checked with isError() - */ - function __construct($use = 0) - { - $this->use = $use; - } - - /** - * convert a string from one UTF-16 char to one UTF-8 char - * - * Normally should be handled by mb_convert_encoding, but - * provides a slower PHP-only method for installations - * that lack the multibye string extension. - * - * @param string $utf16 UTF-16 character - * @return string UTF-8 character - * @access private - */ - function utf162utf8($utf16) - { - // oh please oh please oh please oh please oh please - if(function_exists('mb_convert_encoding')) { - return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); - } - - $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); - - switch(true) { - case ((0x7F & $bytes) == $bytes): - // this case should never be reached, because we are in ASCII range - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0x7F & $bytes); - - case (0x07FF & $bytes) == $bytes: - // return a 2-byte UTF-8 character - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0xC0 | (($bytes >> 6) & 0x1F)) - . chr(0x80 | ($bytes & 0x3F)); - - case (0xFFFF & $bytes) == $bytes: - // return a 3-byte UTF-8 character - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0xE0 | (($bytes >> 12) & 0x0F)) - . chr(0x80 | (($bytes >> 6) & 0x3F)) - . chr(0x80 | ($bytes & 0x3F)); - } - - // ignoring UTF-32 for now, sorry - return ''; - } - - /** - * convert a string from one UTF-8 char to one UTF-16 char - * - * Normally should be handled by mb_convert_encoding, but - * provides a slower PHP-only method for installations - * that lack the multibye string extension. - * - * @param string $utf8 UTF-8 character - * @return string UTF-16 character - * @access private - */ - function utf82utf16($utf8) - { - // oh please oh please oh please oh please oh please - if(function_exists('mb_convert_encoding')) { - return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); - } - - switch(strlen($utf8)) { - case 1: - // this case should never be reached, because we are in ASCII range - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return $utf8; - - case 2: - // return a UTF-16 character from a 2-byte UTF-8 char - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0x07 & (ord($utf8{0}) >> 2)) - . chr((0xC0 & (ord($utf8{0}) << 6)) - | (0x3F & ord($utf8{1}))); - - case 3: - // return a UTF-16 character from a 3-byte UTF-8 char - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr((0xF0 & (ord($utf8{0}) << 4)) - | (0x0F & (ord($utf8{1}) >> 2))) - . chr((0xC0 & (ord($utf8{1}) << 6)) - | (0x7F & ord($utf8{2}))); - } - - // ignoring UTF-32 for now, sorry - return ''; - } - - /** - * encodes an arbitrary variable into JSON format - * - * @param mixed $var any number, boolean, string, array, or object to be encoded. - * see argument 1 to JSON() above for array-parsing behavior. - * if var is a strng, note that encode() always expects it - * to be in ASCII or UTF-8 format! - * - * @return mixed JSON string representation of input var or an error if a problem occurs - * @access public - */ - function encode($var) - { - if (!$this->skipnative && function_exists('json_encode')){ - return json_encode($var); - } - - switch (gettype($var)) { - case 'boolean': - return $var ? 'true' : 'false'; - - case 'NULL': - return 'null'; - - case 'integer': - return (int) $var; - - case 'double': - case 'float': - return (float) $var; - - case 'string': - // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT - $ascii = ''; - $strlen_var = strlen($var); - - /* - * Iterate over every character in the string, - * escaping with a slash or encoding to UTF-8 where necessary - */ - for ($c = 0; $c < $strlen_var; ++$c) { - - $ord_var_c = ord($var{$c}); - - switch (true) { - case $ord_var_c == 0x08: - $ascii .= '\b'; - break; - case $ord_var_c == 0x09: - $ascii .= '\t'; - break; - case $ord_var_c == 0x0A: - $ascii .= '\n'; - break; - case $ord_var_c == 0x0C: - $ascii .= '\f'; - break; - case $ord_var_c == 0x0D: - $ascii .= '\r'; - break; - - case $ord_var_c == 0x22: - case $ord_var_c == 0x2F: - case $ord_var_c == 0x5C: - // double quote, slash, slosh - $ascii .= '\\'.$var{$c}; - break; - - case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): - // characters U-00000000 - U-0000007F (same as ASCII) - $ascii .= $var{$c}; - break; - - case (($ord_var_c & 0xE0) == 0xC0): - // characters U-00000080 - U-000007FF, mask 110XXXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c + 1})); - $c += 1; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xF0) == 0xE0): - // characters U-00000800 - U-0000FFFF, mask 1110XXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2})); - $c += 2; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xF8) == 0xF0): - // characters U-00010000 - U-001FFFFF, mask 11110XXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3})); - $c += 3; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xFC) == 0xF8): - // characters U-00200000 - U-03FFFFFF, mask 111110XX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3}), - ord($var{$c + 4})); - $c += 4; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xFE) == 0xFC): - // characters U-04000000 - U-7FFFFFFF, mask 1111110X - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3}), - ord($var{$c + 4}), - ord($var{$c + 5})); - $c += 5; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - } - } - - return '"'.$ascii.'"'; - - case 'array': - /* - * As per JSON spec if any array key is not an integer - * we must treat the the whole array as an object. We - * also try to catch a sparsely populated associative - * array with numeric keys here because some JS engines - * will create an array with empty indexes up to - * max_index which can cause memory issues and because - * the keys, which may be relevant, will be remapped - * otherwise. - * - * As per the ECMA and JSON specification an object may - * have any string as a property. Unfortunately due to - * a hole in the ECMA specification if the key is a - * ECMA reserved word or starts with a digit the - * parameter is only accessible using ECMAScript's - * bracket notation. - */ - - // treat as a JSON object - if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { - $properties = array_map(array($this, 'name_value'), - array_keys($var), - array_values($var)); - - foreach($properties as $property) { - if(JSON::isError($property)) { - return $property; - } - } - - return '{' . join(',', $properties) . '}'; - } - - // treat it like a regular array - $elements = array_map(array($this, 'encode'), $var); - - foreach($elements as $element) { - if(JSON::isError($element)) { - return $element; - } - } - - return '[' . join(',', $elements) . ']'; - - case 'object': - $vars = get_object_vars($var); - - $properties = array_map(array($this, 'name_value'), - array_keys($vars), - array_values($vars)); - - foreach($properties as $property) { - if(JSON::isError($property)) { - return $property; - } - } - - return '{' . join(',', $properties) . '}'; - - default: - return ($this->use & JSON_SUPPRESS_ERRORS) - ? 'null' - : new JSON_Error(gettype($var)." can not be encoded as JSON string"); - } - } - - /** - * array-walking function for use in generating JSON-formatted name-value pairs - * - * @param string $name name of key to use - * @param mixed $value reference to an array element to be encoded - * - * @return string JSON-formatted name-value pair, like '"name":value' - * @access private - */ - function name_value($name, $value) - { - $encoded_value = $this->encode($value); - - if(JSON::isError($encoded_value)) { - return $encoded_value; - } - - return $this->encode(strval($name)) . ':' . $encoded_value; - } - - /** - * reduce a string by removing leading and trailing comments and whitespace - * - * @param $str string string value to strip of comments and whitespace - * - * @return string string value stripped of comments and whitespace - * @access private - */ - function reduce_string($str) - { - $str = preg_replace(array( - - // eliminate single line comments in '// ...' form - '#^\s*//(.+)$#m', - - // eliminate multi-line comments in '/* ... */' form, at start of string - '#^\s*/\*(.+)\*/#Us', - - // eliminate multi-line comments in '/* ... */' form, at end of string - '#/\*(.+)\*/\s*$#Us' - - ), '', $str); - - // eliminate extraneous space - return trim($str); - } - - /** - * decodes a JSON string into appropriate variable - * - * @param string $str JSON-formatted string - * - * @return mixed number, boolean, string, array, or object - * corresponding to given JSON input string. - * See argument 1 to JSON() above for object-output behavior. - * Note that decode() always returns strings - * in ASCII or UTF-8 format! - * @access public - */ - function decode($str) - { - if (!$this->skipnative && function_exists('json_decode')){ - return json_decode($str,($this->use == JSON_LOOSE_TYPE)); - } - - $str = $this->reduce_string($str); - - switch (strtolower($str)) { - case 'true': - return true; - - case 'false': - return false; - - case 'null': - return null; - - default: - $m = array(); - - if (is_numeric($str)) { - // Lookie-loo, it's a number - - // This would work on its own, but I'm trying to be - // good about returning integers where appropriate: - // return (float)$str; - - // Return float or int, as appropriate - return ((float)$str == (integer)$str) - ? (integer)$str - : (float)$str; - - } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { - // STRINGS RETURNED IN UTF-8 FORMAT - $delim = substr($str, 0, 1); - $chrs = substr($str, 1, -1); - $utf8 = ''; - $strlen_chrs = strlen($chrs); - - for ($c = 0; $c < $strlen_chrs; ++$c) { - - $substr_chrs_c_2 = substr($chrs, $c, 2); - $ord_chrs_c = ord($chrs{$c}); - - switch (true) { - case $substr_chrs_c_2 == '\b': - $utf8 .= chr(0x08); - ++$c; - break; - case $substr_chrs_c_2 == '\t': - $utf8 .= chr(0x09); - ++$c; - break; - case $substr_chrs_c_2 == '\n': - $utf8 .= chr(0x0A); - ++$c; - break; - case $substr_chrs_c_2 == '\f': - $utf8 .= chr(0x0C); - ++$c; - break; - case $substr_chrs_c_2 == '\r': - $utf8 .= chr(0x0D); - ++$c; - break; - - case $substr_chrs_c_2 == '\\"': - case $substr_chrs_c_2 == '\\\'': - case $substr_chrs_c_2 == '\\\\': - case $substr_chrs_c_2 == '\\/': - if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || - ($delim == "'" && $substr_chrs_c_2 != '\\"')) { - $utf8 .= $chrs{++$c}; - } - break; - - case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): - // single, escaped unicode character - $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) - . chr(hexdec(substr($chrs, ($c + 4), 2))); - $utf8 .= $this->utf162utf8($utf16); - $c += 5; - break; - - case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): - $utf8 .= $chrs{$c}; - break; - - case ($ord_chrs_c & 0xE0) == 0xC0: - // characters U-00000080 - U-000007FF, mask 110XXXXX - //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 2); - ++$c; - break; - - case ($ord_chrs_c & 0xF0) == 0xE0: - // characters U-00000800 - U-0000FFFF, mask 1110XXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 3); - $c += 2; - break; - - case ($ord_chrs_c & 0xF8) == 0xF0: - // characters U-00010000 - U-001FFFFF, mask 11110XXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 4); - $c += 3; - break; - - case ($ord_chrs_c & 0xFC) == 0xF8: - // characters U-00200000 - U-03FFFFFF, mask 111110XX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 5); - $c += 4; - break; - - case ($ord_chrs_c & 0xFE) == 0xFC: - // characters U-04000000 - U-7FFFFFFF, mask 1111110X - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 6); - $c += 5; - break; - - } - - } - - return $utf8; - - } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { - // array, or object notation - - if ($str{0} == '[') { - $stk = array(JSON_IN_ARR); - $arr = array(); - } else { - if ($this->use & JSON_LOOSE_TYPE) { - $stk = array(JSON_IN_OBJ); - $obj = array(); - } else { - $stk = array(JSON_IN_OBJ); - $obj = new stdClass(); - } - } - - array_push($stk, array('what' => JSON_SLICE, - 'where' => 0, - 'delim' => false)); - - $chrs = substr($str, 1, -1); - $chrs = $this->reduce_string($chrs); - - if ($chrs == '') { - if (reset($stk) == JSON_IN_ARR) { - return $arr; - - } else { - return $obj; - - } - } - - //print("\nparsing {$chrs}\n"); - - $strlen_chrs = strlen($chrs); - - for ($c = 0; $c <= $strlen_chrs; ++$c) { - - $top = end($stk); - $substr_chrs_c_2 = substr($chrs, $c, 2); - - if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == JSON_SLICE))) { - // found a comma that is not inside a string, array, etc., - // OR we've reached the end of the character list - $slice = substr($chrs, $top['where'], ($c - $top['where'])); - array_push($stk, array('what' => JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); - //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - if (reset($stk) == JSON_IN_ARR) { - // we are in an array, so just push an element onto the stack - array_push($arr, $this->decode($slice)); - - } elseif (reset($stk) == JSON_IN_OBJ) { - // we are in an object, so figure - // out the property name and set an - // element in an associative array, - // for now - $parts = array(); - - if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { - // "name":value pair - $key = $this->decode($parts[1]); - $val = $this->decode($parts[2]); - - if ($this->use & JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } - } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { - // name:value pair, where name is unquoted - $key = $parts[1]; - $val = $this->decode($parts[2]); - - if ($this->use & JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } - } - - } - - } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != JSON_IN_STR)) { - // found a quote, and we are not inside a string - array_push($stk, array('what' => JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); - //print("Found start of string at {$c}\n"); - - } elseif (($chrs{$c} == $top['delim']) && - ($top['what'] == JSON_IN_STR) && - ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { - // found a quote, we're in a string, and it's not escaped - // we know that it's not escaped becase there is _not_ an - // odd number of backslashes at the end of the string so far - array_pop($stk); - //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); - - } elseif (($chrs{$c} == '[') && - in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) { - // found a left-bracket, and we are in an array, object, or slice - array_push($stk, array('what' => JSON_IN_ARR, 'where' => $c, 'delim' => false)); - //print("Found start of array at {$c}\n"); - - } elseif (($chrs{$c} == ']') && ($top['what'] == JSON_IN_ARR)) { - // found a right-bracket, and we're in an array - array_pop($stk); - //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif (($chrs{$c} == '{') && - in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) { - // found a left-brace, and we are in an array, object, or slice - array_push($stk, array('what' => JSON_IN_OBJ, 'where' => $c, 'delim' => false)); - //print("Found start of object at {$c}\n"); - - } elseif (($chrs{$c} == '}') && ($top['what'] == JSON_IN_OBJ)) { - // found a right-brace, and we're in an object - array_pop($stk); - //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif (($substr_chrs_c_2 == '/*') && - in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) { - // found a comment start, and we are in an array, object, or slice - array_push($stk, array('what' => JSON_IN_CMT, 'where' => $c, 'delim' => false)); - $c++; - //print("Found start of comment at {$c}\n"); - - } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == JSON_IN_CMT)) { - // found a comment end, and we're in one now - array_pop($stk); - $c++; - - for ($i = $top['where']; $i <= $c; ++$i) - $chrs = substr_replace($chrs, ' ', $i, 1); - - //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } - - } - - if (reset($stk) == JSON_IN_ARR) { - return $arr; - - } elseif (reset($stk) == JSON_IN_OBJ) { - return $obj; - - } - - } - } - } - - /** - * @todo Ultimately, this should just call PEAR::isError() - */ - function isError($data, $code = null) - { - if (class_exists('pear')) { - return PEAR::isError($data, $code); - } elseif (is_object($data) && (get_class($data) == 'JSON_error' || - is_subclass_of($data, 'JSON_error'))) { - return true; - } - - return false; - } -} - -if (class_exists('PEAR_Error')) { - - class JSON_Error extends PEAR_Error - { - function __construct($message = 'unknown error', $code = null, - $mode = null, $options = null, $userinfo = null) - { - parent::__construct($message, $code, $mode, $options, $userinfo); - } - } - -} else { - - /** - * @todo Ultimately, this class shall be descended from PEAR_Error - */ - class JSON_Error - { - function __construct($message = 'unknown error', $code = null, - $mode = null, $options = null, $userinfo = null) - { - - } - } - -} diff --git a/inc/JpegMeta.php b/inc/JpegMeta.php index afc89e49e..b11f07145 100644 --- a/inc/JpegMeta.php +++ b/inc/JpegMeta.php @@ -1302,7 +1302,7 @@ class JpegMeta { function _parseFileInfo() { if (file_exists($this->_fileName) && is_file($this->_fileName)) { $this->_info['file'] = array(); - $this->_info['file']['Name'] = utf8_decodeFN(utf8_basename($this->_fileName)); + $this->_info['file']['Name'] = utf8_decodeFN(\dokuwiki\Utf8\PhpString::basename($this->_fileName)); $this->_info['file']['Path'] = fullpath($this->_fileName); $this->_info['file']['Size'] = filesize($this->_fileName); if ($this->_info['file']['Size'] < 1024) { @@ -1393,7 +1393,7 @@ class JpegMeta { } } else { $this->_info['file'] = array(); - $this->_info['file']['Name'] = utf8_basename($this->_fileName); + $this->_info['file']['Name'] = \dokuwiki\Utf8\PhpString::basename($this->_fileName); $this->_info['file']['Url'] = $this->_fileName; } diff --git a/inc/Mailer.class.php b/inc/Mailer.class.php index 0fdb45a0a..328073bc1 100644 --- a/inc/Mailer.class.php +++ b/inc/Mailer.class.php @@ -9,6 +9,8 @@ * @author Andreas Gohr <andi@splitbrain.org> */ +use dokuwiki\Extension\Event; + // end of line for mail lines - RFC822 says CRLF but postfix (and other MTAs?) // think different if(!defined('MAILHEADER_EOL')) define('MAILHEADER_EOL', "\n"); @@ -43,12 +45,12 @@ class Mailer { global $INPUT; $server = parse_url(DOKU_URL, PHP_URL_HOST); - if(strpos($server,'.') === false) $server = $server.'.localhost'; + if(strpos($server,'.') === false) $server .= '.localhost'; - $this->partid = substr(md5(uniqid(rand(), true)),0, 8).'@'.$server; - $this->boundary = '__________'.md5(uniqid(rand(), true)); + $this->partid = substr(md5(uniqid(mt_rand(), true)),0, 8).'@'.$server; + $this->boundary = '__________'.md5(uniqid(mt_rand(), true)); - $listid = join('.', array_reverse(explode('/', DOKU_BASE))).$server; + $listid = implode('.', array_reverse(explode('/', DOKU_BASE))).$server; $listid = strtolower(trim($listid, '.')); $this->allowhtml = (bool)$conf['htmlmail']; @@ -78,7 +80,7 @@ class Mailer { */ public function attachFile($path, $mime, $name = '', $embed = '') { if(!$name) { - $name = utf8_basename($path); + $name = \dokuwiki\Utf8\PhpString::basename($path); } $this->attach[] = array( @@ -117,7 +119,7 @@ class Mailer { * @param array $matches * @return string placeholder */ - protected function autoembed_cb($matches) { + protected function autoEmbedCallBack($matches) { static $embeds = 0; $embeds++; @@ -186,7 +188,7 @@ class Mailer { * * @param string $text plain text body * @param array $textrep replacements to apply on the text part - * @param array $htmlrep replacements to apply on the HTML part, null to use $textrep (with urls wrapped in <a> tags) + * @param array $htmlrep replacements to apply on the HTML part, null to use $textrep (urls wrapped in <a> tags) * @param string $html the HTML body, leave null to create it from $text * @param bool $wrap wrap the HTML in the default header/Footer */ @@ -196,17 +198,17 @@ class Mailer { $textrep = (array)$textrep; // create HTML from text if not given - if(is_null($html)) { + if($html === null) { $html = $text; $html = hsc($html); $html = preg_replace('/^----+$/m', '<hr >', $html); $html = nl2br($html); } if($wrap) { - $wrap = rawLocale('mailwrap', 'html'); + $wrapper = rawLocale('mailwrap', 'html'); $html = preg_replace('/\n-- <br \/>.*$/s', '', $html); //strip signature $html = str_replace('@EMAILSIGNATURE@', '', $html); //strip @EMAILSIGNATURE@ - $html = str_replace('@HTMLBODY@', $html, $wrap); + $html = str_replace('@HTMLBODY@', $html, $wrapper); } if(strpos($text, '@EMAILSIGNATURE@') === false) { @@ -226,7 +228,7 @@ class Mailer { // embed media from templates $html = preg_replace_callback( '/@MEDIA\(([^\)]+)\)@/', - array($this, 'autoembed_cb'), $html + array($this, 'autoEmbedCallBack'), $html ); // add default token replacements @@ -385,7 +387,7 @@ class Mailer { } // FIXME: is there a way to encode the localpart of a emailaddress? - if(!utf8_isASCII($addr)) { + if(!\dokuwiki\Utf8\Clean::isASCII($addr)) { msg(hsc("E-Mail address <$addr> is not ASCII"), -1); continue; } @@ -401,11 +403,11 @@ class Mailer { $addr = "<$addr>"; if(defined('MAILHEADER_ASCIIONLY')) { - $text = utf8_deaccent($text); - $text = utf8_strip($text); + $text = \dokuwiki\Utf8\Clean::deaccent($text); + $text = \dokuwiki\Utf8\Clean::strip($text); } - if(strpos($text, ',') !== false || !utf8_isASCII($text)) { + if(strpos($text, ',') !== false || !\dokuwiki\Utf8\Clean::isASCII($text)) { $text = '=?UTF-8?B?'.base64_encode($text).'?='; } } else { @@ -551,10 +553,10 @@ class Mailer { if(isset($this->headers['Subject'])) { // add prefix to subject if(empty($conf['mailprefix'])) { - if(utf8_strlen($conf['title']) < 20) { + if(\dokuwiki\Utf8\PhpString::strlen($conf['title']) < 20) { $prefix = '['.$conf['title'].']'; } else { - $prefix = '['.utf8_substr($conf['title'], 0, 20).'...]'; + $prefix = '['.\dokuwiki\Utf8\PhpString::substr($conf['title'], 0, 20).'...]'; } } else { $prefix = '['.$conf['mailprefix'].']'; @@ -566,10 +568,10 @@ class Mailer { // encode subject if(defined('MAILHEADER_ASCIIONLY')) { - $this->headers['Subject'] = utf8_deaccent($this->headers['Subject']); - $this->headers['Subject'] = utf8_strip($this->headers['Subject']); + $this->headers['Subject'] = \dokuwiki\Utf8\Clean::deaccent($this->headers['Subject']); + $this->headers['Subject'] = \dokuwiki\Utf8\Clean::strip($this->headers['Subject']); } - if(!utf8_isASCII($this->headers['Subject'])) { + if(!\dokuwiki\Utf8\Clean::isASCII($this->headers['Subject'])) { $this->headers['Subject'] = '=?UTF-8?B?'.base64_encode($this->headers['Subject']).'?='; } } @@ -595,7 +597,7 @@ class Mailer { protected function prepareHeaders() { $headers = ''; foreach($this->headers as $key => $val) { - if ($val === '' || is_null($val)) continue; + if ($val === '' || $val === null) continue; $headers .= $this->wrappedHeaderLine($key, $val); } return $headers; @@ -645,7 +647,11 @@ class Mailer { 'NAME' => $INFO['userinfo']['name'], 'MAIL' => $INFO['userinfo']['mail'] ); - $signature = str_replace('@DOKUWIKIURL@', $this->replacements['text']['DOKUWIKIURL'], $lang['email_signature_text']); + $signature = str_replace( + '@DOKUWIKIURL@', + $this->replacements['text']['DOKUWIKIURL'], + $lang['email_signature_text'] + ); $this->replacements['text']['EMAILSIGNATURE'] = "\n-- \n" . $signature . "\n"; $this->replacements['html'] = array( @@ -707,7 +713,7 @@ class Mailer { ); // do our thing if BEFORE hook approves - $evt = new Doku_Event('MAIL_MESSAGE_SEND', $data); + $evt = new Event('MAIL_MESSAGE_SEND', $data); if($evt->advise_before(true)) { // clean up before using the headers $this->cleanHeaders(); @@ -746,7 +752,7 @@ class Mailer { } // send the thing - if(is_null($this->sendparam)) { + if($this->sendparam === null) { $success = @mail($to, $subject, $body, $headers); } else { $success = @mail($to, $subject, $body, $headers, $this->sendparam); diff --git a/inc/Manifest.php b/inc/Manifest.php index 843c67265..29e7f263f 100644 --- a/inc/Manifest.php +++ b/inc/Manifest.php @@ -2,6 +2,8 @@ namespace dokuwiki; +use dokuwiki\Extension\Event; + class Manifest { public function sendManifest() @@ -37,7 +39,9 @@ class Manifest } if (empty($manifest['theme_color'])) { - $manifest['theme_color'] = !empty($replacements['__theme_color__']) ? $replacements['__theme_color__'] : $replacements['__background_alt__']; + $manifest['theme_color'] = !empty($replacements['__theme_color__']) + ? $replacements['__theme_color__'] + : $replacements['__background_alt__']; } if (empty($manifest['icons'])) { @@ -72,7 +76,7 @@ class Manifest } } - trigger_event('MANIFEST_SEND', $manifest); + Event::createAndTrigger('MANIFEST_SEND', $manifest); header('Content-Type: application/manifest+json'); echo json_encode($manifest); diff --git a/inc/Menu/AbstractMenu.php b/inc/Menu/AbstractMenu.php index ce021ab64..37e5d2cc3 100644 --- a/inc/Menu/AbstractMenu.php +++ b/inc/Menu/AbstractMenu.php @@ -2,6 +2,7 @@ namespace dokuwiki\Menu; +use dokuwiki\Extension\Event; use dokuwiki\Menu\Item\AbstractItem; /** @@ -42,7 +43,7 @@ abstract class AbstractMenu implements MenuInterface { 'view' => $this->view, 'items' => array(), ); - trigger_event('MENU_ITEMS_ASSEMBLY', $data, array($this, 'loadItems')); + Event::createAndTrigger('MENU_ITEMS_ASSEMBLY', $data, array($this, 'loadItems')); return $data['items']; } diff --git a/inc/Parsing/Handler/Block.php b/inc/Parsing/Handler/Block.php new file mode 100644 index 000000000..4cfa686d4 --- /dev/null +++ b/inc/Parsing/Handler/Block.php @@ -0,0 +1,211 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +/** + * Handler for paragraphs + * + * @author Harry Fuecks <hfuecks@gmail.com> + */ +class Block +{ + protected $calls = array(); + protected $skipEol = false; + protected $inParagraph = false; + + // Blocks these should not be inside paragraphs + protected $blockOpen = array( + 'header', + 'listu_open','listo_open','listitem_open','listcontent_open', + 'table_open','tablerow_open','tablecell_open','tableheader_open','tablethead_open', + 'quote_open', + 'code','file','hr','preformatted','rss', + 'htmlblock','phpblock', + 'footnote_open', + ); + + protected $blockClose = array( + 'header', + 'listu_close','listo_close','listitem_close','listcontent_close', + 'table_close','tablerow_close','tablecell_close','tableheader_close','tablethead_close', + 'quote_close', + 'code','file','hr','preformatted','rss', + 'htmlblock','phpblock', + 'footnote_close', + ); + + // Stacks can contain paragraphs + protected $stackOpen = array( + 'section_open', + ); + + protected $stackClose = array( + 'section_close', + ); + + + /** + * Constructor. Adds loaded syntax plugins to the block and stack + * arrays + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + public function __construct() + { + global $DOKU_PLUGINS; + //check if syntax plugins were loaded + if (empty($DOKU_PLUGINS['syntax'])) return; + foreach ($DOKU_PLUGINS['syntax'] as $n => $p) { + $ptype = $p->getPType(); + if ($ptype == 'block') { + $this->blockOpen[] = 'plugin_'.$n; + $this->blockClose[] = 'plugin_'.$n; + } elseif ($ptype == 'stack') { + $this->stackOpen[] = 'plugin_'.$n; + $this->stackClose[] = 'plugin_'.$n; + } + } + } + + protected function openParagraph($pos) + { + if ($this->inParagraph) return; + $this->calls[] = array('p_open',array(), $pos); + $this->inParagraph = true; + $this->skipEol = true; + } + + /** + * Close a paragraph if needed + * + * This function makes sure there are no empty paragraphs on the stack + * + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param string|integer $pos + */ + protected function closeParagraph($pos) + { + if (!$this->inParagraph) return; + // look back if there was any content - we don't want empty paragraphs + $content = ''; + $ccount = count($this->calls); + for ($i=$ccount-1; $i>=0; $i--) { + if ($this->calls[$i][0] == 'p_open') { + break; + } elseif ($this->calls[$i][0] == 'cdata') { + $content .= $this->calls[$i][1][0]; + } else { + $content = 'found markup'; + break; + } + } + + if (trim($content)=='') { + //remove the whole paragraph + //array_splice($this->calls,$i); // <- this is much slower than the loop below + for ($x=$ccount; $x>$i; + $x--) array_pop($this->calls); + } else { + // remove ending linebreaks in the paragraph + $i=count($this->calls)-1; + if ($this->calls[$i][0] == 'cdata') $this->calls[$i][1][0] = rtrim($this->calls[$i][1][0], "\n"); + $this->calls[] = array('p_close',array(), $pos); + } + + $this->inParagraph = false; + $this->skipEol = true; + } + + protected function addCall($call) + { + $key = count($this->calls); + if ($key and ($call[0] == 'cdata') and ($this->calls[$key-1][0] == 'cdata')) { + $this->calls[$key-1][1][0] .= $call[1][0]; + } else { + $this->calls[] = $call; + } + } + + // simple version of addCall, without checking cdata + protected function storeCall($call) + { + $this->calls[] = $call; + } + + /** + * Processes the whole instruction stack to open and close paragraphs + * + * @author Harry Fuecks <hfuecks@gmail.com> + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param array $calls + * + * @return array + */ + public function process($calls) + { + // open first paragraph + $this->openParagraph(0); + foreach ($calls as $key => $call) { + $cname = $call[0]; + if ($cname == 'plugin') { + $cname='plugin_'.$call[1][0]; + $plugin = true; + $plugin_open = (($call[1][2] == DOKU_LEXER_ENTER) || ($call[1][2] == DOKU_LEXER_SPECIAL)); + $plugin_close = (($call[1][2] == DOKU_LEXER_EXIT) || ($call[1][2] == DOKU_LEXER_SPECIAL)); + } else { + $plugin = false; + } + /* stack */ + if (in_array($cname, $this->stackClose) && (!$plugin || $plugin_close)) { + $this->closeParagraph($call[2]); + $this->storeCall($call); + $this->openParagraph($call[2]); + continue; + } + if (in_array($cname, $this->stackOpen) && (!$plugin || $plugin_open)) { + $this->closeParagraph($call[2]); + $this->storeCall($call); + $this->openParagraph($call[2]); + continue; + } + /* block */ + // If it's a substition it opens and closes at the same call. + // To make sure next paragraph is correctly started, let close go first. + if (in_array($cname, $this->blockClose) && (!$plugin || $plugin_close)) { + $this->closeParagraph($call[2]); + $this->storeCall($call); + $this->openParagraph($call[2]); + continue; + } + if (in_array($cname, $this->blockOpen) && (!$plugin || $plugin_open)) { + $this->closeParagraph($call[2]); + $this->storeCall($call); + continue; + } + /* eol */ + if ($cname == 'eol') { + // Check this isn't an eol instruction to skip... + if (!$this->skipEol) { + // Next is EOL => double eol => mark as paragraph + if (isset($calls[$key+1]) && $calls[$key+1][0] == 'eol') { + $this->closeParagraph($call[2]); + $this->openParagraph($call[2]); + } else { + //if this is just a single eol make a space from it + $this->addCall(array('cdata',array("\n"), $call[2])); + } + } + continue; + } + /* normal */ + $this->addCall($call); + $this->skipEol = false; + } + // close last paragraph + $call = end($this->calls); + $this->closeParagraph($call[2]); + return $this->calls; + } +} diff --git a/inc/Parsing/Handler/CallWriter.php b/inc/Parsing/Handler/CallWriter.php new file mode 100644 index 000000000..2457143ed --- /dev/null +++ b/inc/Parsing/Handler/CallWriter.php @@ -0,0 +1,40 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +class CallWriter implements CallWriterInterface +{ + + /** @var \Doku_Handler $Handler */ + protected $Handler; + + /** + * @param \Doku_Handler $Handler + */ + public function __construct(\Doku_Handler $Handler) + { + $this->Handler = $Handler; + } + + /** @inheritdoc */ + public function writeCall($call) + { + $this->Handler->calls[] = $call; + } + + /** @inheritdoc */ + public function writeCalls($calls) + { + $this->Handler->calls = array_merge($this->Handler->calls, $calls); + } + + /** + * @inheritdoc + * function is required, but since this call writer is first/highest in + * the chain it is not required to do anything + */ + public function finalise() + { + unset($this->Handler); + } +} diff --git a/inc/Parsing/Handler/CallWriterInterface.php b/inc/Parsing/Handler/CallWriterInterface.php new file mode 100644 index 000000000..1ade7c060 --- /dev/null +++ b/inc/Parsing/Handler/CallWriterInterface.php @@ -0,0 +1,30 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +interface CallWriterInterface +{ + /** + * Add a call to our call list + * + * @param $call the call to be added + */ + public function writeCall($call); + + /** + * Append a list of calls to our call list + * + * @param $calls list of calls to be appended + */ + public function writeCalls($calls); + + /** + * Explicit request to finish up and clean up NOW! + * (probably because document end has been reached) + * + * If part of a CallWriter chain, call finalise on + * the original call writer + * + */ + public function finalise(); +} diff --git a/inc/Parsing/Handler/Lists.php b/inc/Parsing/Handler/Lists.php new file mode 100644 index 000000000..c4428fe46 --- /dev/null +++ b/inc/Parsing/Handler/Lists.php @@ -0,0 +1,213 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +class Lists implements ReWriterInterface +{ + + /** @var CallWriterInterface original call writer */ + protected $callWriter; + + protected $calls = array(); + protected $listCalls = array(); + protected $listStack = array(); + + protected $initialDepth = 0; + + const NODE = 1; + + + /** @inheritdoc */ + public function __construct(CallWriterInterface $CallWriter) + { + $this->callWriter = $CallWriter; + } + + /** @inheritdoc */ + public function writeCall($call) + { + $this->calls[] = $call; + } + + /** + * @inheritdoc + * Probably not needed but just in case... + */ + public function writeCalls($calls) + { + $this->calls = array_merge($this->calls, $calls); + } + + /** @inheritdoc */ + public function finalise() + { + $last_call = end($this->calls); + $this->writeCall(array('list_close',array(), $last_call[2])); + + $this->process(); + $this->callWriter->finalise(); + unset($this->callWriter); + } + + /** @inheritdoc */ + public function process() + { + + foreach ($this->calls as $call) { + switch ($call[0]) { + case 'list_item': + $this->listOpen($call); + break; + case 'list_open': + $this->listStart($call); + break; + case 'list_close': + $this->listEnd($call); + break; + default: + $this->listContent($call); + break; + } + } + + $this->callWriter->writeCalls($this->listCalls); + return $this->callWriter; + } + + protected function listStart($call) + { + $depth = $this->interpretSyntax($call[1][0], $listType); + + $this->initialDepth = $depth; + // array(list type, current depth, index of current listitem_open) + $this->listStack[] = array($listType, $depth, 1); + + $this->listCalls[] = array('list'.$listType.'_open',array(),$call[2]); + $this->listCalls[] = array('listitem_open',array(1),$call[2]); + $this->listCalls[] = array('listcontent_open',array(),$call[2]); + } + + + protected function listEnd($call) + { + $closeContent = true; + + while ($list = array_pop($this->listStack)) { + if ($closeContent) { + $this->listCalls[] = array('listcontent_close',array(),$call[2]); + $closeContent = false; + } + $this->listCalls[] = array('listitem_close',array(),$call[2]); + $this->listCalls[] = array('list'.$list[0].'_close', array(), $call[2]); + } + } + + protected function listOpen($call) + { + $depth = $this->interpretSyntax($call[1][0], $listType); + $end = end($this->listStack); + $key = key($this->listStack); + + // Not allowed to be shallower than initialDepth + if ($depth < $this->initialDepth) { + $depth = $this->initialDepth; + } + + if ($depth == $end[1]) { + // Just another item in the list... + if ($listType == $end[0]) { + $this->listCalls[] = array('listcontent_close',array(),$call[2]); + $this->listCalls[] = array('listitem_close',array(),$call[2]); + $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]); + $this->listCalls[] = array('listcontent_open',array(),$call[2]); + + // new list item, update list stack's index into current listitem_open + $this->listStack[$key][2] = count($this->listCalls) - 2; + + // Switched list type... + } else { + $this->listCalls[] = array('listcontent_close',array(),$call[2]); + $this->listCalls[] = array('listitem_close',array(),$call[2]); + $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]); + $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); + $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); + $this->listCalls[] = array('listcontent_open',array(),$call[2]); + + array_pop($this->listStack); + $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2); + } + } elseif ($depth > $end[1]) { // Getting deeper... + $this->listCalls[] = array('listcontent_close',array(),$call[2]); + $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); + $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); + $this->listCalls[] = array('listcontent_open',array(),$call[2]); + + // set the node/leaf state of this item's parent listitem_open to NODE + $this->listCalls[$this->listStack[$key][2]][1][1] = self::NODE; + + $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2); + } else { // Getting shallower ( $depth < $end[1] ) + $this->listCalls[] = array('listcontent_close',array(),$call[2]); + $this->listCalls[] = array('listitem_close',array(),$call[2]); + $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]); + + // Throw away the end - done + array_pop($this->listStack); + + while (1) { + $end = end($this->listStack); + $key = key($this->listStack); + + if ($end[1] <= $depth) { + // Normalize depths + $depth = $end[1]; + + $this->listCalls[] = array('listitem_close',array(),$call[2]); + + if ($end[0] == $listType) { + $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]); + $this->listCalls[] = array('listcontent_open',array(),$call[2]); + + // new list item, update list stack's index into current listitem_open + $this->listStack[$key][2] = count($this->listCalls) - 2; + } else { + // Switching list type... + $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]); + $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); + $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); + $this->listCalls[] = array('listcontent_open',array(),$call[2]); + + array_pop($this->listStack); + $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2); + } + + break; + + // Haven't dropped down far enough yet.... ( $end[1] > $depth ) + } else { + $this->listCalls[] = array('listitem_close',array(),$call[2]); + $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]); + + array_pop($this->listStack); + } + } + } + } + + protected function listContent($call) + { + $this->listCalls[] = $call; + } + + protected function interpretSyntax($match, & $type) + { + if (substr($match, -1) == '*') { + $type = 'u'; + } else { + $type = 'o'; + } + // Is the +1 needed? It used to be count(explode(...)) + // but I don't think the number is seen outside this handler + return substr_count(str_replace("\t", ' ', $match), ' ') + 1; + } +} diff --git a/inc/Parsing/Handler/Nest.php b/inc/Parsing/Handler/Nest.php new file mode 100644 index 000000000..b0044a3cb --- /dev/null +++ b/inc/Parsing/Handler/Nest.php @@ -0,0 +1,83 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +/** + * Generic call writer class to handle nesting of rendering instructions + * within a render instruction. Also see nest() method of renderer base class + * + * @author Chris Smith <chris@jalakai.co.uk> + */ +class Nest implements ReWriterInterface +{ + + /** @var CallWriterInterface original CallWriter */ + protected $callWriter; + + protected $calls = array(); + protected $closingInstruction; + + /** + * @inheritdoc + * + * @param CallWriterInterface $CallWriter the parser's current call writer, i.e. the one above us in the chain + * @param string $close closing instruction name, this is required to properly terminate the + * syntax mode if the document ends without a closing pattern + */ + public function __construct(CallWriterInterface $CallWriter, $close = "nest_close") + { + $this->callWriter = $CallWriter; + + $this->closingInstruction = $close; + } + + /** @inheritdoc */ + public function writeCall($call) + { + $this->calls[] = $call; + } + + /** @inheritdoc */ + public function writeCalls($calls) + { + $this->calls = array_merge($this->calls, $calls); + } + + /** @inheritdoc */ + public function finalise() + { + $last_call = end($this->calls); + $this->writeCall(array($this->closingInstruction,array(), $last_call[2])); + + $this->process(); + $this->callWriter->finalise(); + unset($this->callWriter); + } + + /** @inheritdoc */ + public function process() + { + // merge consecutive cdata + $unmerged_calls = $this->calls; + $this->calls = array(); + + foreach ($unmerged_calls as $call) $this->addCall($call); + + $first_call = reset($this->calls); + $this->callWriter->writeCall(array("nest", array($this->calls), $first_call[2])); + + return $this->callWriter; + } + + protected function addCall($call) + { + $key = count($this->calls); + if ($key and ($call[0] == 'cdata') and ($this->calls[$key-1][0] == 'cdata')) { + $this->calls[$key-1][1][0] .= $call[1][0]; + } elseif ($call[0] == 'eol') { + // do nothing (eol shouldn't be allowed, to counter preformatted fix in #1652 & #1699) + } else { + $this->calls[] = $call; + } + } +} diff --git a/inc/Parsing/Handler/Preformatted.php b/inc/Parsing/Handler/Preformatted.php new file mode 100644 index 000000000..a668771a7 --- /dev/null +++ b/inc/Parsing/Handler/Preformatted.php @@ -0,0 +1,76 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +class Preformatted implements ReWriterInterface +{ + + /** @var CallWriterInterface original call writer */ + protected $callWriter; + + protected $calls = array(); + protected $pos; + protected $text =''; + + /** + * @inheritdoc + */ + public function __construct(CallWriterInterface $CallWriter) + { + $this->callWriter = $CallWriter; + } + + /** @inheritdoc */ + public function writeCall($call) + { + $this->calls[] = $call; + } + + /** + * @inheritdoc + * Probably not needed but just in case... + */ + public function writeCalls($calls) + { + $this->calls = array_merge($this->calls, $calls); + } + + /** @inheritdoc */ + public function finalise() + { + $last_call = end($this->calls); + $this->writeCall(array('preformatted_end',array(), $last_call[2])); + + $this->process(); + $this->callWriter->finalise(); + unset($this->callWriter); + } + + /** @inheritdoc */ + public function process() + { + foreach ($this->calls as $call) { + switch ($call[0]) { + case 'preformatted_start': + $this->pos = $call[2]; + break; + case 'preformatted_newline': + $this->text .= "\n"; + break; + case 'preformatted_content': + $this->text .= $call[1][0]; + break; + case 'preformatted_end': + if (trim($this->text)) { + $this->callWriter->writeCall(array('preformatted', array($this->text), $this->pos)); + } + // see FS#1699 & FS#1652, add 'eol' instructions to ensure proper triggering of following p_open + $this->callWriter->writeCall(array('eol', array(), $this->pos)); + $this->callWriter->writeCall(array('eol', array(), $this->pos)); + break; + } + } + + return $this->callWriter; + } +} diff --git a/inc/Parsing/Handler/Quote.php b/inc/Parsing/Handler/Quote.php new file mode 100644 index 000000000..a786d10c0 --- /dev/null +++ b/inc/Parsing/Handler/Quote.php @@ -0,0 +1,110 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +class Quote implements ReWriterInterface +{ + + /** @var CallWriterInterface original CallWriter */ + protected $callWriter; + + protected $calls = array(); + + protected $quoteCalls = array(); + + /** @inheritdoc */ + public function __construct(CallWriterInterface $CallWriter) + { + $this->callWriter = $CallWriter; + } + + /** @inheritdoc */ + public function writeCall($call) + { + $this->calls[] = $call; + } + + /** + * @inheritdoc + * + * Probably not needed but just in case... + */ + public function writeCalls($calls) + { + $this->calls = array_merge($this->calls, $calls); + } + + /** @inheritdoc */ + public function finalise() + { + $last_call = end($this->calls); + $this->writeCall(array('quote_end',array(), $last_call[2])); + + $this->process(); + $this->callWriter->finalise(); + unset($this->callWriter); + } + + /** @inheritdoc */ + public function process() + { + + $quoteDepth = 1; + + foreach ($this->calls as $call) { + switch ($call[0]) { + + /** @noinspection PhpMissingBreakStatementInspection */ + case 'quote_start': + $this->quoteCalls[] = array('quote_open',array(),$call[2]); + // fallthrough + case 'quote_newline': + $quoteLength = $this->getDepth($call[1][0]); + + if ($quoteLength > $quoteDepth) { + $quoteDiff = $quoteLength - $quoteDepth; + for ($i = 1; $i <= $quoteDiff; $i++) { + $this->quoteCalls[] = array('quote_open',array(),$call[2]); + } + } elseif ($quoteLength < $quoteDepth) { + $quoteDiff = $quoteDepth - $quoteLength; + for ($i = 1; $i <= $quoteDiff; $i++) { + $this->quoteCalls[] = array('quote_close',array(),$call[2]); + } + } else { + if ($call[0] != 'quote_start') $this->quoteCalls[] = array('linebreak',array(),$call[2]); + } + + $quoteDepth = $quoteLength; + + break; + + case 'quote_end': + if ($quoteDepth > 1) { + $quoteDiff = $quoteDepth - 1; + for ($i = 1; $i <= $quoteDiff; $i++) { + $this->quoteCalls[] = array('quote_close',array(),$call[2]); + } + } + + $this->quoteCalls[] = array('quote_close',array(),$call[2]); + + $this->callWriter->writeCalls($this->quoteCalls); + break; + + default: + $this->quoteCalls[] = $call; + break; + } + } + + return $this->callWriter; + } + + protected function getDepth($marker) + { + preg_match('/>{1,}/', $marker, $matches); + $quoteLength = strlen($matches[0]); + return $quoteLength; + } +} diff --git a/inc/Parsing/Handler/ReWriterInterface.php b/inc/Parsing/Handler/ReWriterInterface.php new file mode 100644 index 000000000..13f7b48e3 --- /dev/null +++ b/inc/Parsing/Handler/ReWriterInterface.php @@ -0,0 +1,29 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +/** + * A ReWriter takes over from the orignal call writer and handles all new calls itself until + * the process method is called and control is given back to the original writer. + */ +interface ReWriterInterface extends CallWriterInterface +{ + + /** + * ReWriterInterface constructor. + * + * This rewriter will be registered as the new call writer in the Handler. + * The original is passed as parameter + * + * @param CallWriterInterface $callWriter the original callwriter + */ + public function __construct(CallWriterInterface $callWriter); + + /** + * Process any calls that have been added and add them to the + * original call writer + * + * @return CallWriterInterface the orignal call writer + */ + public function process(); +} diff --git a/inc/Parsing/Handler/Table.php b/inc/Parsing/Handler/Table.php new file mode 100644 index 000000000..6759ea798 --- /dev/null +++ b/inc/Parsing/Handler/Table.php @@ -0,0 +1,345 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +class Table implements ReWriterInterface +{ + + /** @var CallWriterInterface original CallWriter */ + protected $callWriter; + + protected $calls = array(); + protected $tableCalls = array(); + protected $maxCols = 0; + protected $maxRows = 1; + protected $currentCols = 0; + protected $firstCell = false; + protected $lastCellType = 'tablecell'; + protected $inTableHead = true; + protected $currentRow = array('tableheader' => 0, 'tablecell' => 0); + protected $countTableHeadRows = 0; + + /** @inheritdoc */ + public function __construct(CallWriterInterface $CallWriter) + { + $this->callWriter = $CallWriter; + } + + /** @inheritdoc */ + public function writeCall($call) + { + $this->calls[] = $call; + } + + /** + * @inheritdoc + * Probably not needed but just in case... + */ + public function writeCalls($calls) + { + $this->calls = array_merge($this->calls, $calls); + } + + /** @inheritdoc */ + public function finalise() + { + $last_call = end($this->calls); + $this->writeCall(array('table_end',array(), $last_call[2])); + + $this->process(); + $this->callWriter->finalise(); + unset($this->callWriter); + } + + /** @inheritdoc */ + public function process() + { + foreach ($this->calls as $call) { + switch ($call[0]) { + case 'table_start': + $this->tableStart($call); + break; + case 'table_row': + $this->tableRowClose($call); + $this->tableRowOpen(array('tablerow_open',$call[1],$call[2])); + break; + case 'tableheader': + case 'tablecell': + $this->tableCell($call); + break; + case 'table_end': + $this->tableRowClose($call); + $this->tableEnd($call); + break; + default: + $this->tableDefault($call); + break; + } + } + $this->callWriter->writeCalls($this->tableCalls); + + return $this->callWriter; + } + + protected function tableStart($call) + { + $this->tableCalls[] = array('table_open',$call[1],$call[2]); + $this->tableCalls[] = array('tablerow_open',array(),$call[2]); + $this->firstCell = true; + } + + protected function tableEnd($call) + { + $this->tableCalls[] = array('table_close',$call[1],$call[2]); + $this->finalizeTable(); + } + + protected function tableRowOpen($call) + { + $this->tableCalls[] = $call; + $this->currentCols = 0; + $this->firstCell = true; + $this->lastCellType = 'tablecell'; + $this->maxRows++; + if ($this->inTableHead) { + $this->currentRow = array('tablecell' => 0, 'tableheader' => 0); + } + } + + protected function tableRowClose($call) + { + if ($this->inTableHead && ($this->inTableHead = $this->isTableHeadRow())) { + $this->countTableHeadRows++; + } + // Strip off final cell opening and anything after it + while ($discard = array_pop($this->tableCalls)) { + if ($discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') { + break; + } + if (!empty($this->currentRow[$discard[0]])) { + $this->currentRow[$discard[0]]--; + } + } + $this->tableCalls[] = array('tablerow_close', array(), $call[2]); + + if ($this->currentCols > $this->maxCols) { + $this->maxCols = $this->currentCols; + } + } + + protected function isTableHeadRow() + { + $td = $this->currentRow['tablecell']; + $th = $this->currentRow['tableheader']; + + if (!$th || $td > 2) return false; + if (2*$td > $th) return false; + + return true; + } + + protected function tableCell($call) + { + if ($this->inTableHead) { + $this->currentRow[$call[0]]++; + } + if (!$this->firstCell) { + // Increase the span + $lastCall = end($this->tableCalls); + + // A cell call which follows an open cell means an empty cell so span + if ($lastCall[0] == 'tablecell_open' || $lastCall[0] == 'tableheader_open') { + $this->tableCalls[] = array('colspan',array(),$call[2]); + } + + $this->tableCalls[] = array($this->lastCellType.'_close',array(),$call[2]); + $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]); + $this->lastCellType = $call[0]; + } else { + $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]); + $this->lastCellType = $call[0]; + $this->firstCell = false; + } + + $this->currentCols++; + } + + protected function tableDefault($call) + { + $this->tableCalls[] = $call; + } + + protected function finalizeTable() + { + + // Add the max cols and rows to the table opening + if ($this->tableCalls[0][0] == 'table_open') { + // Adjust to num cols not num col delimeters + $this->tableCalls[0][1][] = $this->maxCols - 1; + $this->tableCalls[0][1][] = $this->maxRows; + $this->tableCalls[0][1][] = array_shift($this->tableCalls[0][1]); + } else { + trigger_error('First element in table call list is not table_open'); + } + + $lastRow = 0; + $lastCell = 0; + $cellKey = array(); + $toDelete = array(); + + // if still in tableheader, then there can be no table header + // as all rows can't be within <THEAD> + if ($this->inTableHead) { + $this->inTableHead = false; + $this->countTableHeadRows = 0; + } + + // Look for the colspan elements and increment the colspan on the + // previous non-empty opening cell. Once done, delete all the cells + // that contain colspans + for ($key = 0; $key < count($this->tableCalls); ++$key) { + $call = $this->tableCalls[$key]; + + switch ($call[0]) { + case 'table_open': + if ($this->countTableHeadRows) { + array_splice($this->tableCalls, $key+1, 0, array( + array('tablethead_open', array(), $call[2]))); + } + break; + + case 'tablerow_open': + $lastRow++; + $lastCell = 0; + break; + + case 'tablecell_open': + case 'tableheader_open': + $lastCell++; + $cellKey[$lastRow][$lastCell] = $key; + break; + + case 'table_align': + $prev = in_array($this->tableCalls[$key-1][0], array('tablecell_open', 'tableheader_open')); + $next = in_array($this->tableCalls[$key+1][0], array('tablecell_close', 'tableheader_close')); + // If the cell is empty, align left + if ($prev && $next) { + $this->tableCalls[$key-1][1][1] = 'left'; + + // If the previous element was a cell open, align right + } elseif ($prev) { + $this->tableCalls[$key-1][1][1] = 'right'; + + // If the next element is the close of an element, align either center or left + } elseif ($next) { + if ($this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] == 'right') { + $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'center'; + } else { + $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'left'; + } + } + + // Now convert the whitespace back to cdata + $this->tableCalls[$key][0] = 'cdata'; + break; + + case 'colspan': + $this->tableCalls[$key-1][1][0] = false; + + for ($i = $key-2; $i >= $cellKey[$lastRow][1]; $i--) { + if ($this->tableCalls[$i][0] == 'tablecell_open' || + $this->tableCalls[$i][0] == 'tableheader_open' + ) { + if (false !== $this->tableCalls[$i][1][0]) { + $this->tableCalls[$i][1][0]++; + break; + } + } + } + + $toDelete[] = $key-1; + $toDelete[] = $key; + $toDelete[] = $key+1; + break; + + case 'rowspan': + if ($this->tableCalls[$key-1][0] == 'cdata') { + // ignore rowspan if previous call was cdata (text mixed with :::) + // we don't have to check next call as that wont match regex + $this->tableCalls[$key][0] = 'cdata'; + } else { + $spanning_cell = null; + + // can't cross thead/tbody boundary + if (!$this->countTableHeadRows || ($lastRow-1 != $this->countTableHeadRows)) { + for ($i = $lastRow-1; $i > 0; $i--) { + if ($this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tablecell_open' || + $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tableheader_open' + ) { + if ($this->tableCalls[$cellKey[$i][$lastCell]][1][2] >= $lastRow - $i) { + $spanning_cell = $i; + break; + } + } + } + } + if (is_null($spanning_cell)) { + // No spanning cell found, so convert this cell to + // an empty one to avoid broken tables + $this->tableCalls[$key][0] = 'cdata'; + $this->tableCalls[$key][1][0] = ''; + break; + } + $this->tableCalls[$cellKey[$spanning_cell][$lastCell]][1][2]++; + + $this->tableCalls[$key-1][1][2] = false; + + $toDelete[] = $key-1; + $toDelete[] = $key; + $toDelete[] = $key+1; + } + break; + + case 'tablerow_close': + // Fix broken tables by adding missing cells + $moreCalls = array(); + while (++$lastCell < $this->maxCols) { + $moreCalls[] = array('tablecell_open', array(1, null, 1), $call[2]); + $moreCalls[] = array('cdata', array(''), $call[2]); + $moreCalls[] = array('tablecell_close', array(), $call[2]); + } + $moreCallsLength = count($moreCalls); + if ($moreCallsLength) { + array_splice($this->tableCalls, $key, 0, $moreCalls); + $key += $moreCallsLength; + } + + if ($this->countTableHeadRows == $lastRow) { + array_splice($this->tableCalls, $key+1, 0, array( + array('tablethead_close', array(), $call[2]))); + } + break; + } + } + + // condense cdata + $cnt = count($this->tableCalls); + for ($key = 0; $key < $cnt; $key++) { + if ($this->tableCalls[$key][0] == 'cdata') { + $ckey = $key; + $key++; + while ($this->tableCalls[$key][0] == 'cdata') { + $this->tableCalls[$ckey][1][0] .= $this->tableCalls[$key][1][0]; + $toDelete[] = $key; + $key++; + } + continue; + } + } + + foreach ($toDelete as $delete) { + unset($this->tableCalls[$delete]); + } + $this->tableCalls = array_values($this->tableCalls); + } +} diff --git a/inc/Parsing/Lexer/Lexer.php b/inc/Parsing/Lexer/Lexer.php new file mode 100644 index 000000000..c164f4ffe --- /dev/null +++ b/inc/Parsing/Lexer/Lexer.php @@ -0,0 +1,347 @@ +<?php +/** + * Lexer adapted from Simple Test: http://sourceforge.net/projects/simpletest/ + * For an intro to the Lexer see: + * https://web.archive.org/web/20120125041816/http://www.phppatterns.com/docs/develop/simple_test_lexer_notes + * + * @author Marcus Baker http://www.lastcraft.com + */ + +namespace dokuwiki\Parsing\Lexer; + +// FIXME move elsewhere + +define("DOKU_LEXER_ENTER", 1); +define("DOKU_LEXER_MATCHED", 2); +define("DOKU_LEXER_UNMATCHED", 3); +define("DOKU_LEXER_EXIT", 4); +define("DOKU_LEXER_SPECIAL", 5); + +/** + * Accepts text and breaks it into tokens. + * + * Some optimisation to make the sure the content is only scanned by the PHP regex + * parser once. Lexer modes must not start with leading underscores. + */ +class Lexer +{ + /** @var ParallelRegex[] */ + protected $regexes; + /** @var \Doku_Handler */ + protected $handler; + /** @var StateStack */ + protected $modeStack; + /** @var array mode "rewrites" */ + protected $mode_handlers; + /** @var bool case sensitive? */ + protected $case; + + /** + * Sets up the lexer in case insensitive matching by default. + * + * @param \Doku_Handler $handler Handling strategy by reference. + * @param string $start Starting handler. + * @param boolean $case True for case sensitive. + */ + public function __construct($handler, $start = "accept", $case = false) + { + $this->case = $case; + $this->regexes = array(); + $this->handler = $handler; + $this->modeStack = new StateStack($start); + $this->mode_handlers = array(); + } + + /** + * Adds a token search pattern for a particular parsing mode. + * + * The pattern does not change the current mode. + * + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + */ + public function addPattern($pattern, $mode = "accept") + { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern); + } + + /** + * Adds a pattern that will enter a new parsing mode. + * + * Useful for entering parenthesis, strings, tags, etc. + * + * @param string $pattern Perl style regex, but ( and ) lose the usual meaning. + * @param string $mode Should only apply this pattern when dealing with this type of input. + * @param string $new_mode Change parsing to this new nested mode. + */ + public function addEntryPattern($pattern, $mode, $new_mode) + { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern, $new_mode); + } + + /** + * Adds a pattern that will exit the current mode and re-enter the previous one. + * + * @param string $pattern Perl style regex, but ( and ) lose the usual meaning. + * @param string $mode Mode to leave. + */ + public function addExitPattern($pattern, $mode) + { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern, "__exit"); + } + + /** + * Adds a pattern that has a special mode. + * + * Acts as an entry and exit pattern in one go, effectively calling a special + * parser handler for this token only. + * + * @param string $pattern Perl style regex, but ( and ) lose the usual meaning. + * @param string $mode Should only apply this pattern when dealing with this type of input. + * @param string $special Use this mode for this one token. + */ + public function addSpecialPattern($pattern, $mode, $special) + { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern, "_$special"); + } + + /** + * Adds a mapping from a mode to another handler. + * + * @param string $mode Mode to be remapped. + * @param string $handler New target handler. + */ + public function mapHandler($mode, $handler) + { + $this->mode_handlers[$mode] = $handler; + } + + /** + * Splits the page text into tokens. + * + * Will fail if the handlers report an error or if no content is consumed. If successful then each + * unparsed and parsed token invokes a call to the held listener. + * + * @param string $raw Raw HTML text. + * @return boolean True on success, else false. + */ + public function parse($raw) + { + if (! isset($this->handler)) { + return false; + } + $initialLength = strlen($raw); + $length = $initialLength; + $pos = 0; + while (is_array($parsed = $this->reduce($raw))) { + list($unmatched, $matched, $mode) = $parsed; + $currentLength = strlen($raw); + $matchPos = $initialLength - $currentLength - strlen($matched); + if (! $this->dispatchTokens($unmatched, $matched, $mode, $pos, $matchPos)) { + return false; + } + if ($currentLength == $length) { + return false; + } + $length = $currentLength; + $pos = $initialLength - $currentLength; + } + if (!$parsed) { + return false; + } + return $this->invokeHandler($raw, DOKU_LEXER_UNMATCHED, $pos); + } + + /** + * Sends the matched token and any leading unmatched + * text to the parser changing the lexer to a new + * mode if one is listed. + * + * @param string $unmatched Unmatched leading portion. + * @param string $matched Actual token match. + * @param bool|string $mode Mode after match. A boolean false mode causes no change. + * @param int $initialPos + * @param int $matchPos Current byte index location in raw doc thats being parsed + * @return boolean False if there was any error from the parser. + */ + protected function dispatchTokens($unmatched, $matched, $mode, $initialPos, $matchPos) + { + if (! $this->invokeHandler($unmatched, DOKU_LEXER_UNMATCHED, $initialPos)) { + return false; + } + if ($this->isModeEnd($mode)) { + if (! $this->invokeHandler($matched, DOKU_LEXER_EXIT, $matchPos)) { + return false; + } + return $this->modeStack->leave(); + } + if ($this->isSpecialMode($mode)) { + $this->modeStack->enter($this->decodeSpecial($mode)); + if (! $this->invokeHandler($matched, DOKU_LEXER_SPECIAL, $matchPos)) { + return false; + } + return $this->modeStack->leave(); + } + if (is_string($mode)) { + $this->modeStack->enter($mode); + return $this->invokeHandler($matched, DOKU_LEXER_ENTER, $matchPos); + } + return $this->invokeHandler($matched, DOKU_LEXER_MATCHED, $matchPos); + } + + /** + * Tests to see if the new mode is actually to leave the current mode and pop an item from the matching + * mode stack. + * + * @param string $mode Mode to test. + * @return boolean True if this is the exit mode. + */ + protected function isModeEnd($mode) + { + return ($mode === "__exit"); + } + + /** + * Test to see if the mode is one where this mode is entered for this token only and automatically + * leaves immediately afterwoods. + * + * @param string $mode Mode to test. + * @return boolean True if this is the exit mode. + */ + protected function isSpecialMode($mode) + { + return (strncmp($mode, "_", 1) == 0); + } + + /** + * Strips the magic underscore marking single token modes. + * + * @param string $mode Mode to decode. + * @return string Underlying mode name. + */ + protected function decodeSpecial($mode) + { + return substr($mode, 1); + } + + /** + * Calls the parser method named after the current mode. + * + * Empty content will be ignored. The lexer has a parser handler for each mode in the lexer. + * + * @param string $content Text parsed. + * @param boolean $is_match Token is recognised rather + * than unparsed data. + * @param int $pos Current byte index location in raw doc + * thats being parsed + * @return bool + */ + protected function invokeHandler($content, $is_match, $pos) + { + if (($content === "") || ($content === false)) { + return true; + } + $handler = $this->modeStack->getCurrent(); + if (isset($this->mode_handlers[$handler])) { + $handler = $this->mode_handlers[$handler]; + } + + // modes starting with plugin_ are all handled by the same + // handler but with an additional parameter + if (substr($handler, 0, 7)=='plugin_') { + list($handler,$plugin) = explode('_', $handler, 2); + return $this->handler->$handler($content, $is_match, $pos, $plugin); + } + + return $this->handler->$handler($content, $is_match, $pos); + } + + /** + * Tries to match a chunk of text and if successful removes the recognised chunk and any leading + * unparsed data. Empty strings will not be matched. + * + * @param string $raw The subject to parse. This is the content that will be eaten. + * @return array|bool Three item list of unparsed content followed by the + * recognised token and finally the action the parser is to take. + * True if no match, false if there is a parsing error. + */ + protected function reduce(&$raw) + { + if (! isset($this->regexes[$this->modeStack->getCurrent()])) { + return false; + } + if ($raw === "") { + return true; + } + if ($action = $this->regexes[$this->modeStack->getCurrent()]->split($raw, $split)) { + list($unparsed, $match, $raw) = $split; + return array($unparsed, $match, $action); + } + return true; + } + + /** + * Escapes regex characters other than (, ) and / + * + * @param string $str + * @return string + */ + public static function escape($str) + { + $chars = array( + '/\\\\/', + '/\./', + '/\+/', + '/\*/', + '/\?/', + '/\[/', + '/\^/', + '/\]/', + '/\$/', + '/\{/', + '/\}/', + '/\=/', + '/\!/', + '/\</', + '/\>/', + '/\|/', + '/\:/' + ); + + $escaped = array( + '\\\\\\\\', + '\.', + '\+', + '\*', + '\?', + '\[', + '\^', + '\]', + '\$', + '\{', + '\}', + '\=', + '\!', + '\<', + '\>', + '\|', + '\:' + ); + return preg_replace($chars, $escaped, $str); + } +} diff --git a/inc/Parsing/Lexer/ParallelRegex.php b/inc/Parsing/Lexer/ParallelRegex.php new file mode 100644 index 000000000..96f61a10f --- /dev/null +++ b/inc/Parsing/Lexer/ParallelRegex.php @@ -0,0 +1,203 @@ +<?php +/** + * Lexer adapted from Simple Test: http://sourceforge.net/projects/simpletest/ + * For an intro to the Lexer see: + * https://web.archive.org/web/20120125041816/http://www.phppatterns.com/docs/develop/simple_test_lexer_notes + * + * @author Marcus Baker http://www.lastcraft.com + */ + +namespace dokuwiki\Parsing\Lexer; + +/** + * Compounded regular expression. + * + * Any of the contained patterns could match and when one does it's label is returned. + */ +class ParallelRegex +{ + /** @var string[] patterns to match */ + protected $patterns; + /** @var string[] labels for above patterns */ + protected $labels; + /** @var string the compound regex matching all patterns */ + protected $regex; + /** @var bool case sensitive matching? */ + protected $case; + + /** + * Constructor. Starts with no patterns. + * + * @param boolean $case True for case sensitive, false + * for insensitive. + */ + public function __construct($case) + { + $this->case = $case; + $this->patterns = array(); + $this->labels = array(); + $this->regex = null; + } + + /** + * Adds a pattern with an optional label. + * + * @param mixed $pattern Perl style regex. Must be UTF-8 + * encoded. If its a string, the (, ) + * lose their meaning unless they + * form part of a lookahead or + * lookbehind assertation. + * @param bool|string $label Label of regex to be returned + * on a match. Label must be ASCII + */ + public function addPattern($pattern, $label = true) + { + $count = count($this->patterns); + $this->patterns[$count] = $pattern; + $this->labels[$count] = $label; + $this->regex = null; + } + + /** + * Attempts to match all patterns at once against a string. + * + * @param string $subject String to match against. + * @param string $match First matched portion of + * subject. + * @return bool|string False if no match found, label if label exists, true if not + */ + public function match($subject, &$match) + { + if (count($this->patterns) == 0) { + return false; + } + if (! preg_match($this->getCompoundedRegex(), $subject, $matches)) { + $match = ""; + return false; + } + + $match = $matches[0]; + $size = count($matches); + // FIXME this could be made faster by storing the labels as keys in a hashmap + for ($i = 1; $i < $size; $i++) { + if ($matches[$i] && isset($this->labels[$i - 1])) { + return $this->labels[$i - 1]; + } + } + return true; + } + + /** + * Attempts to split the string against all patterns at once + * + * @param string $subject String to match against. + * @param array $split The split result: array containing, pre-match, match & post-match strings + * @return boolean True on success. + * + * @author Christopher Smith <chris@jalakai.co.uk> + */ + public function split($subject, &$split) + { + if (count($this->patterns) == 0) { + return false; + } + + if (! preg_match($this->getCompoundedRegex(), $subject, $matches)) { + if (function_exists('preg_last_error')) { + $err = preg_last_error(); + switch ($err) { + case PREG_BACKTRACK_LIMIT_ERROR: + msg('A PCRE backtrack error occured. Try to increase the pcre.backtrack_limit in php.ini', -1); + break; + case PREG_RECURSION_LIMIT_ERROR: + msg('A PCRE recursion error occured. Try to increase the pcre.recursion_limit in php.ini', -1); + break; + case PREG_BAD_UTF8_ERROR: + msg('A PCRE UTF-8 error occured. This might be caused by a faulty plugin', -1); + break; + case PREG_INTERNAL_ERROR: + msg('A PCRE internal error occured. This might be caused by a faulty plugin', -1); + break; + } + } + + $split = array($subject, "", ""); + return false; + } + + $idx = count($matches)-2; + list($pre, $post) = preg_split($this->patterns[$idx].$this->getPerlMatchingFlags(), $subject, 2); + $split = array($pre, $matches[0], $post); + + return isset($this->labels[$idx]) ? $this->labels[$idx] : true; + } + + /** + * Compounds the patterns into a single + * regular expression separated with the + * "or" operator. Caches the regex. + * Will automatically escape (, ) and / tokens. + * + * @return null|string + */ + protected function getCompoundedRegex() + { + if ($this->regex == null) { + $cnt = count($this->patterns); + for ($i = 0; $i < $cnt; $i++) { + /* + * decompose the input pattern into "(", "(?", ")", + * "[...]", "[]..]", "[^]..]", "[...[:...:]..]", "\x"... + * elements. + */ + preg_match_all('/\\\\.|' . + '\(\?|' . + '[()]|' . + '\[\^?\]?(?:\\\\.|\[:[^]]*:\]|[^]\\\\])*\]|' . + '[^[()\\\\]+/', $this->patterns[$i], $elts); + + $pattern = ""; + $level = 0; + + foreach ($elts[0] as $elt) { + /* + * for "(", ")" remember the nesting level, add "\" + * only to the non-"(?" ones. + */ + + switch ($elt) { + case '(': + $pattern .= '\('; + break; + case ')': + if ($level > 0) + $level--; /* closing (? */ + else $pattern .= '\\'; + $pattern .= ')'; + break; + case '(?': + $level++; + $pattern .= '(?'; + break; + default: + if (substr($elt, 0, 1) == '\\') + $pattern .= $elt; + else $pattern .= str_replace('/', '\/', $elt); + } + } + $this->patterns[$i] = "($pattern)"; + } + $this->regex = "/" . implode("|", $this->patterns) . "/" . $this->getPerlMatchingFlags(); + } + return $this->regex; + } + + /** + * Accessor for perl regex mode flags to use. + * @return string Perl regex flags. + */ + protected function getPerlMatchingFlags() + { + return ($this->case ? "msS" : "msSi"); + } +} diff --git a/inc/Parsing/Lexer/StateStack.php b/inc/Parsing/Lexer/StateStack.php new file mode 100644 index 000000000..325412bb4 --- /dev/null +++ b/inc/Parsing/Lexer/StateStack.php @@ -0,0 +1,60 @@ +<?php +/** + * Lexer adapted from Simple Test: http://sourceforge.net/projects/simpletest/ + * For an intro to the Lexer see: + * https://web.archive.org/web/20120125041816/http://www.phppatterns.com/docs/develop/simple_test_lexer_notes + * + * @author Marcus Baker http://www.lastcraft.com + */ + +namespace dokuwiki\Parsing\Lexer; + +/** + * States for a stack machine. + */ +class StateStack +{ + protected $stack; + + /** + * Constructor. Starts in named state. + * @param string $start Starting state name. + */ + public function __construct($start) + { + $this->stack = array($start); + } + + /** + * Accessor for current state. + * @return string State. + */ + public function getCurrent() + { + return $this->stack[count($this->stack) - 1]; + } + + /** + * Adds a state to the stack and sets it to be the current state. + * + * @param string $state New state. + */ + public function enter($state) + { + array_push($this->stack, $state); + } + + /** + * Leaves the current state and reverts + * to the previous one. + * @return boolean false if we attempt to drop off the bottom of the list. + */ + public function leave() + { + if (count($this->stack) == 1) { + return false; + } + array_pop($this->stack); + return true; + } +} diff --git a/inc/Parsing/Parser.php b/inc/Parsing/Parser.php new file mode 100644 index 000000000..b2070569f --- /dev/null +++ b/inc/Parsing/Parser.php @@ -0,0 +1,114 @@ +<?php + +namespace dokuwiki\Parsing; + +use Doku_Handler; +use dokuwiki\Parsing\Lexer\Lexer; +use dokuwiki\Parsing\ParserMode\Base; +use dokuwiki\Parsing\ParserMode\ModeInterface; + +/** + * Sets up the Lexer with modes and points it to the Handler + * For an intro to the Lexer see: wiki:parser + */ +class Parser { + + /** @var Doku_Handler */ + protected $handler; + + /** @var Lexer $lexer */ + protected $lexer; + + /** @var ModeInterface[] $modes */ + protected $modes = array(); + + /** @var bool mode connections may only be set up once */ + protected $connected = false; + + /** + * dokuwiki\Parsing\Doku_Parser constructor. + * + * @param Doku_Handler $handler + */ + public function __construct(Doku_Handler $handler) { + $this->handler = $handler; + } + + /** + * Adds the base mode and initialized the lexer + * + * @param Base $BaseMode + */ + protected function addBaseMode($BaseMode) { + $this->modes['base'] = $BaseMode; + if(!$this->lexer) { + $this->lexer = new Lexer($this->handler, 'base', true); + } + $this->modes['base']->Lexer = $this->lexer; + } + + /** + * Add a new syntax element (mode) to the parser + * + * PHP preserves order of associative elements + * Mode sequence is important + * + * @param string $name + * @param ModeInterface $Mode + */ + public function addMode($name, ModeInterface $Mode) { + if(!isset($this->modes['base'])) { + $this->addBaseMode(new Base()); + } + $Mode->Lexer = $this->lexer; // FIXME should be done by setter + $this->modes[$name] = $Mode; + } + + /** + * Connect all modes with each other + * + * This is the last step before actually parsing. + */ + protected function connectModes() { + + if($this->connected) { + return; + } + + foreach(array_keys($this->modes) as $mode) { + // Base isn't connected to anything + if($mode == 'base') { + continue; + } + $this->modes[$mode]->preConnect(); + + foreach(array_keys($this->modes) as $cm) { + + if($this->modes[$cm]->accepts($mode)) { + $this->modes[$mode]->connectTo($cm); + } + + } + + $this->modes[$mode]->postConnect(); + } + + $this->connected = true; + } + + /** + * Parses wiki syntax to instructions + * + * @param string $doc the wiki syntax text + * @return array instructions + */ + public function parse($doc) { + $this->connectModes(); + // Normalize CRs and pad doc + $doc = "\n" . str_replace("\r\n", "\n", $doc) . "\n"; + $this->lexer->parse($doc); + $this->handler->finalize(); + return $this->handler->calls; + } + +} diff --git a/inc/Parsing/ParserMode/AbstractMode.php b/inc/Parsing/ParserMode/AbstractMode.php new file mode 100644 index 000000000..15fc9fe04 --- /dev/null +++ b/inc/Parsing/ParserMode/AbstractMode.php @@ -0,0 +1,40 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +/** + * This class and all the subclasses below are used to reduce the effort required to register + * modes with the Lexer. + * + * @author Harry Fuecks <hfuecks@gmail.com> + */ +abstract class AbstractMode implements ModeInterface +{ + /** @var \dokuwiki\Parsing\Lexer\Lexer $Lexer will be injected on loading FIXME this should be done by setter */ + public $Lexer; + protected $allowedModes = array(); + + /** @inheritdoc */ + abstract public function getSort(); + + /** @inheritdoc */ + public function preConnect() + { + } + + /** @inheritdoc */ + public function connectTo($mode) + { + } + + /** @inheritdoc */ + public function postConnect() + { + } + + /** @inheritdoc */ + public function accepts($mode) + { + return in_array($mode, (array) $this->allowedModes); + } +} diff --git a/inc/Parsing/ParserMode/Acronym.php b/inc/Parsing/ParserMode/Acronym.php new file mode 100644 index 000000000..b42a7b573 --- /dev/null +++ b/inc/Parsing/ParserMode/Acronym.php @@ -0,0 +1,68 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Acronym extends AbstractMode +{ + // A list + protected $acronyms = array(); + protected $pattern = ''; + + /** + * Acronym constructor. + * + * @param string[] $acronyms + */ + public function __construct($acronyms) + { + usort($acronyms, array($this,'compare')); + $this->acronyms = $acronyms; + } + + /** @inheritdoc */ + public function preConnect() + { + if (!count($this->acronyms)) return; + + $bound = '[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]'; + $acronyms = array_map(['\\dokuwiki\\Parsing\\Lexer\\Lexer', 'escape'], $this->acronyms); + $this->pattern = '(?<=^|'.$bound.')(?:'.join('|', $acronyms).')(?='.$bound.')'; + } + + /** @inheritdoc */ + public function connectTo($mode) + { + if (!count($this->acronyms)) return; + + if (strlen($this->pattern) > 0) { + $this->Lexer->addSpecialPattern($this->pattern, $mode, 'acronym'); + } + } + + /** @inheritdoc */ + public function getSort() + { + return 240; + } + + /** + * sort callback to order by string length descending + * + * @param string $a + * @param string $b + * + * @return int + */ + protected function compare($a, $b) + { + $a_len = strlen($a); + $b_len = strlen($b); + if ($a_len > $b_len) { + return -1; + } elseif ($a_len < $b_len) { + return 1; + } + + return 0; + } +} diff --git a/inc/Parsing/ParserMode/Base.php b/inc/Parsing/ParserMode/Base.php new file mode 100644 index 000000000..562275600 --- /dev/null +++ b/inc/Parsing/ParserMode/Base.php @@ -0,0 +1,31 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Base extends AbstractMode +{ + + /** + * Base constructor. + */ + public function __construct() + { + global $PARSER_MODES; + + $this->allowedModes = array_merge( + $PARSER_MODES['container'], + $PARSER_MODES['baseonly'], + $PARSER_MODES['paragraphs'], + $PARSER_MODES['formatting'], + $PARSER_MODES['substition'], + $PARSER_MODES['protected'], + $PARSER_MODES['disabled'] + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 0; + } +} diff --git a/inc/Parsing/ParserMode/Camelcaselink.php b/inc/Parsing/ParserMode/Camelcaselink.php new file mode 100644 index 000000000..ef0b32531 --- /dev/null +++ b/inc/Parsing/ParserMode/Camelcaselink.php @@ -0,0 +1,23 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Camelcaselink extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern( + '\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b', + $mode, + 'camelcaselink' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 290; + } +} diff --git a/inc/Parsing/ParserMode/Code.php b/inc/Parsing/ParserMode/Code.php new file mode 100644 index 000000000..aa494377d --- /dev/null +++ b/inc/Parsing/ParserMode/Code.php @@ -0,0 +1,25 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Code extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('<code\b(?=.*</code>)', $mode, 'code'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('</code>', 'code'); + } + + /** @inheritdoc */ + public function getSort() + { + return 200; + } +} diff --git a/inc/Parsing/ParserMode/Emaillink.php b/inc/Parsing/ParserMode/Emaillink.php new file mode 100644 index 000000000..f9af28c66 --- /dev/null +++ b/inc/Parsing/ParserMode/Emaillink.php @@ -0,0 +1,20 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Emaillink extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + // pattern below is defined in inc/mail.php + $this->Lexer->addSpecialPattern('<'.PREG_PATTERN_VALID_EMAIL.'>', $mode, 'emaillink'); + } + + /** @inheritdoc */ + public function getSort() + { + return 340; + } +} diff --git a/inc/Parsing/ParserMode/Entity.php b/inc/Parsing/ParserMode/Entity.php new file mode 100644 index 000000000..b670124b2 --- /dev/null +++ b/inc/Parsing/ParserMode/Entity.php @@ -0,0 +1,50 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +use dokuwiki\Parsing\Lexer\Lexer; + +class Entity extends AbstractMode +{ + + protected $entities = array(); + protected $pattern = ''; + + /** + * Entity constructor. + * @param string[] $entities + */ + public function __construct($entities) + { + $this->entities = $entities; + } + + + /** @inheritdoc */ + public function preConnect() + { + if (!count($this->entities) || $this->pattern != '') return; + + $sep = ''; + foreach ($this->entities as $entity) { + $this->pattern .= $sep. Lexer::escape($entity); + $sep = '|'; + } + } + + /** @inheritdoc */ + public function connectTo($mode) + { + if (!count($this->entities)) return; + + if (strlen($this->pattern) > 0) { + $this->Lexer->addSpecialPattern($this->pattern, $mode, 'entity'); + } + } + + /** @inheritdoc */ + public function getSort() + { + return 260; + } +} diff --git a/inc/Parsing/ParserMode/Eol.php b/inc/Parsing/ParserMode/Eol.php new file mode 100644 index 000000000..a5886b51f --- /dev/null +++ b/inc/Parsing/ParserMode/Eol.php @@ -0,0 +1,25 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Eol extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $badModes = array('listblock','table'); + if (in_array($mode, $badModes)) { + return; + } + // see FS#1652, pattern extended to swallow preceding whitespace to avoid + // issues with lines that only contain whitespace + $this->Lexer->addSpecialPattern('(?:^[ \t]*)?\n', $mode, 'eol'); + } + + /** @inheritdoc */ + public function getSort() + { + return 370; + } +} diff --git a/inc/Parsing/ParserMode/Externallink.php b/inc/Parsing/ParserMode/Externallink.php new file mode 100644 index 000000000..747574595 --- /dev/null +++ b/inc/Parsing/ParserMode/Externallink.php @@ -0,0 +1,44 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Externallink extends AbstractMode +{ + protected $schemes = array(); + protected $patterns = array(); + + /** @inheritdoc */ + public function preConnect() + { + if (count($this->patterns)) return; + + $ltrs = '\w'; + $gunk = '/\#~:.?+=&%@!\-\[\]'; + $punc = '.:?\-;,'; + $host = $ltrs.$punc; + $any = $ltrs.$gunk.$punc; + + $this->schemes = getSchemes(); + foreach ($this->schemes as $scheme) { + $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.'])'; + } + + /** @inheritdoc */ + public function connectTo($mode) + { + + foreach ($this->patterns as $pattern) { + $this->Lexer->addSpecialPattern($pattern, $mode, 'externallink'); + } + } + + /** @inheritdoc */ + public function getSort() + { + return 330; + } +} diff --git a/inc/Parsing/ParserMode/File.php b/inc/Parsing/ParserMode/File.php new file mode 100644 index 000000000..149134135 --- /dev/null +++ b/inc/Parsing/ParserMode/File.php @@ -0,0 +1,25 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class File extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('<file\b(?=.*</file>)', $mode, 'file'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('</file>', 'file'); + } + + /** @inheritdoc */ + public function getSort() + { + return 210; + } +} diff --git a/inc/Parsing/ParserMode/Filelink.php b/inc/Parsing/ParserMode/Filelink.php new file mode 100644 index 000000000..3cd86cb8b --- /dev/null +++ b/inc/Parsing/ParserMode/Filelink.php @@ -0,0 +1,39 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Filelink extends AbstractMode +{ + + protected $pattern; + + /** @inheritdoc */ + public function preConnect() + { + + $ltrs = '\w'; + $gunk = '/\#~:.?+=&%@!\-'; + $punc = '.:?\-;,'; + $host = $ltrs.$punc; + $any = $ltrs.$gunk.$punc; + + $this->pattern = '\b(?i)file(?-i)://['.$any.']+?['. + $punc.']*[^'.$any.']'; + } + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern( + $this->pattern, + $mode, + 'filelink' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 360; + } +} diff --git a/inc/Parsing/ParserMode/Footnote.php b/inc/Parsing/ParserMode/Footnote.php new file mode 100644 index 000000000..c399f9849 --- /dev/null +++ b/inc/Parsing/ParserMode/Footnote.php @@ -0,0 +1,50 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Footnote extends AbstractMode +{ + + /** + * Footnote constructor. + */ + public function __construct() + { + global $PARSER_MODES; + + $this->allowedModes = array_merge( + $PARSER_MODES['container'], + $PARSER_MODES['formatting'], + $PARSER_MODES['substition'], + $PARSER_MODES['protected'], + $PARSER_MODES['disabled'] + ); + + unset($this->allowedModes[array_search('footnote', $this->allowedModes)]); + } + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern( + '\x28\x28(?=.*\x29\x29)', + $mode, + 'footnote' + ); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern( + '\x29\x29', + 'footnote' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 150; + } +} diff --git a/inc/Parsing/ParserMode/Formatting.php b/inc/Parsing/ParserMode/Formatting.php new file mode 100644 index 000000000..a3c465cc0 --- /dev/null +++ b/inc/Parsing/ParserMode/Formatting.php @@ -0,0 +1,115 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +/** + * This class sets the markup for bold (=strong), + * italic (=emphasis), underline etc. + */ +class Formatting extends AbstractMode +{ + protected $type; + + protected $formatting = array( + 'strong' => array( + 'entry' => '\*\*(?=.*\*\*)', + 'exit' => '\*\*', + 'sort' => 70 + ), + + 'emphasis' => array( + 'entry' => '//(?=[^\x00]*[^:])', //hack for bugs #384 #763 #1468 + 'exit' => '//', + 'sort' => 80 + ), + + 'underline' => array( + 'entry' => '__(?=.*__)', + 'exit' => '__', + 'sort' => 90 + ), + + 'monospace' => array( + 'entry' => '\x27\x27(?=.*\x27\x27)', + 'exit' => '\x27\x27', + 'sort' => 100 + ), + + 'subscript' => array( + 'entry' => '<sub>(?=.*</sub>)', + 'exit' => '</sub>', + 'sort' => 110 + ), + + 'superscript' => array( + 'entry' => '<sup>(?=.*</sup>)', + 'exit' => '</sup>', + 'sort' => 120 + ), + + 'deleted' => array( + 'entry' => '<del>(?=.*</del>)', + 'exit' => '</del>', + 'sort' => 130 + ), + ); + + /** + * @param string $type + */ + public function __construct($type) + { + global $PARSER_MODES; + + if (!array_key_exists($type, $this->formatting)) { + trigger_error('Invalid formatting type ' . $type, E_USER_WARNING); + } + + $this->type = $type; + + // formatting may contain other formatting but not it self + $modes = $PARSER_MODES['formatting']; + $key = array_search($type, $modes); + if (is_int($key)) { + unset($modes[$key]); + } + + $this->allowedModes = array_merge( + $modes, + $PARSER_MODES['substition'], + $PARSER_MODES['disabled'] + ); + } + + /** @inheritdoc */ + public function connectTo($mode) + { + + // Can't nest formatting in itself + if ($mode == $this->type) { + return; + } + + $this->Lexer->addEntryPattern( + $this->formatting[$this->type]['entry'], + $mode, + $this->type + ); + } + + /** @inheritdoc */ + public function postConnect() + { + + $this->Lexer->addExitPattern( + $this->formatting[$this->type]['exit'], + $this->type + ); + } + + /** @inheritdoc */ + public function getSort() + { + return $this->formatting[$this->type]['sort']; + } +} diff --git a/inc/Parsing/ParserMode/Header.php b/inc/Parsing/ParserMode/Header.php new file mode 100644 index 000000000..854b3178c --- /dev/null +++ b/inc/Parsing/ParserMode/Header.php @@ -0,0 +1,24 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Header extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + //we're not picky about the closing ones, two are enough + $this->Lexer->addSpecialPattern( + '[ \t]*={2,}[^\n]+={2,}[ \t]*(?=\n)', + $mode, + 'header' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 50; + } +} diff --git a/inc/Parsing/ParserMode/Hr.php b/inc/Parsing/ParserMode/Hr.php new file mode 100644 index 000000000..e4f0b444b --- /dev/null +++ b/inc/Parsing/ParserMode/Hr.php @@ -0,0 +1,19 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Hr extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern('\n[ \t]*-{4,}[ \t]*(?=\n)', $mode, 'hr'); + } + + /** @inheritdoc */ + public function getSort() + { + return 160; + } +} diff --git a/inc/Parsing/ParserMode/Html.php b/inc/Parsing/ParserMode/Html.php new file mode 100644 index 000000000..f5b63ef09 --- /dev/null +++ b/inc/Parsing/ParserMode/Html.php @@ -0,0 +1,27 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Html extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('<html>(?=.*</html>)', $mode, 'html'); + $this->Lexer->addEntryPattern('<HTML>(?=.*</HTML>)', $mode, 'htmlblock'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('</html>', 'html'); + $this->Lexer->addExitPattern('</HTML>', 'htmlblock'); + } + + /** @inheritdoc */ + public function getSort() + { + return 190; + } +} diff --git a/inc/Parsing/ParserMode/Internallink.php b/inc/Parsing/ParserMode/Internallink.php new file mode 100644 index 000000000..6def0d9a3 --- /dev/null +++ b/inc/Parsing/ParserMode/Internallink.php @@ -0,0 +1,20 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Internallink extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + // Word boundaries? + $this->Lexer->addSpecialPattern("\[\[.*?\]\](?!\])", $mode, 'internallink'); + } + + /** @inheritdoc */ + public function getSort() + { + return 300; + } +} diff --git a/inc/Parsing/ParserMode/Linebreak.php b/inc/Parsing/ParserMode/Linebreak.php new file mode 100644 index 000000000..dd95cc383 --- /dev/null +++ b/inc/Parsing/ParserMode/Linebreak.php @@ -0,0 +1,19 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Linebreak extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern('\x5C{2}(?:[ \t]|(?=\n))', $mode, 'linebreak'); + } + + /** @inheritdoc */ + public function getSort() + { + return 140; + } +} diff --git a/inc/Parsing/ParserMode/Listblock.php b/inc/Parsing/ParserMode/Listblock.php new file mode 100644 index 000000000..eef762777 --- /dev/null +++ b/inc/Parsing/ParserMode/Listblock.php @@ -0,0 +1,44 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Listblock extends AbstractMode +{ + + /** + * Listblock constructor. + */ + public function __construct() + { + global $PARSER_MODES; + + $this->allowedModes = array_merge( + $PARSER_MODES['formatting'], + $PARSER_MODES['substition'], + $PARSER_MODES['disabled'], + $PARSER_MODES['protected'] + ); + } + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('[ \t]*\n {2,}[\-\*]', $mode, 'listblock'); + $this->Lexer->addEntryPattern('[ \t]*\n\t{1,}[\-\*]', $mode, 'listblock'); + + $this->Lexer->addPattern('\n {2,}[\-\*]', 'listblock'); + $this->Lexer->addPattern('\n\t{1,}[\-\*]', 'listblock'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('\n', 'listblock'); + } + + /** @inheritdoc */ + public function getSort() + { + return 10; + } +} diff --git a/inc/Parsing/ParserMode/Media.php b/inc/Parsing/ParserMode/Media.php new file mode 100644 index 000000000..f93f94773 --- /dev/null +++ b/inc/Parsing/ParserMode/Media.php @@ -0,0 +1,20 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Media extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + // Word boundaries? + $this->Lexer->addSpecialPattern("\{\{(?:[^\}]|(?:\}[^\}]))+\}\}", $mode, 'media'); + } + + /** @inheritdoc */ + public function getSort() + { + return 320; + } +} diff --git a/inc/Parsing/ParserMode/ModeInterface.php b/inc/Parsing/ParserMode/ModeInterface.php new file mode 100644 index 000000000..7cca0385f --- /dev/null +++ b/inc/Parsing/ParserMode/ModeInterface.php @@ -0,0 +1,46 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +/** + * Defines a mode (syntax component) in the Parser + */ +interface ModeInterface +{ + /** + * returns a number used to determine in which order modes are added + * + * @return int; + */ + public function getSort(); + + /** + * Called before any calls to connectTo + * + * @return void + */ + public function preConnect(); + + /** + * Connects the mode + * + * @param string $mode + * @return void + */ + public function connectTo($mode); + + /** + * Called after all calls to connectTo + * + * @return void + */ + public function postConnect(); + + /** + * Check if given mode is accepted inside this mode + * + * @param string $mode + * @return bool + */ + public function accepts($mode); +} diff --git a/inc/Parsing/ParserMode/Multiplyentity.php b/inc/Parsing/ParserMode/Multiplyentity.php new file mode 100644 index 000000000..89df136df --- /dev/null +++ b/inc/Parsing/ParserMode/Multiplyentity.php @@ -0,0 +1,27 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +/** + * Implements the 640x480 replacement + */ +class Multiplyentity extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + + $this->Lexer->addSpecialPattern( + '(?<=\b)(?:[1-9]|\d{2,})[xX]\d+(?=\b)', + $mode, + 'multiplyentity' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 270; + } +} diff --git a/inc/Parsing/ParserMode/Nocache.php b/inc/Parsing/ParserMode/Nocache.php new file mode 100644 index 000000000..fa6db8305 --- /dev/null +++ b/inc/Parsing/ParserMode/Nocache.php @@ -0,0 +1,19 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Nocache extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern('~~NOCACHE~~', $mode, 'nocache'); + } + + /** @inheritdoc */ + public function getSort() + { + return 40; + } +} diff --git a/inc/Parsing/ParserMode/Notoc.php b/inc/Parsing/ParserMode/Notoc.php new file mode 100644 index 000000000..5956207c1 --- /dev/null +++ b/inc/Parsing/ParserMode/Notoc.php @@ -0,0 +1,19 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Notoc extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern('~~NOTOC~~', $mode, 'notoc'); + } + + /** @inheritdoc */ + public function getSort() + { + return 30; + } +} diff --git a/inc/Parsing/ParserMode/Php.php b/inc/Parsing/ParserMode/Php.php new file mode 100644 index 000000000..914648b51 --- /dev/null +++ b/inc/Parsing/ParserMode/Php.php @@ -0,0 +1,27 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Php extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('<php>(?=.*</php>)', $mode, 'php'); + $this->Lexer->addEntryPattern('<PHP>(?=.*</PHP>)', $mode, 'phpblock'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('</php>', 'php'); + $this->Lexer->addExitPattern('</PHP>', 'phpblock'); + } + + /** @inheritdoc */ + public function getSort() + { + return 180; + } +} diff --git a/inc/Parsing/ParserMode/Plugin.php b/inc/Parsing/ParserMode/Plugin.php new file mode 100644 index 000000000..c885c6037 --- /dev/null +++ b/inc/Parsing/ParserMode/Plugin.php @@ -0,0 +1,8 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +/** + * @fixme do we need this anymore or could the syntax plugin inherit directly from abstract mode? + */ +abstract class Plugin extends AbstractMode {} diff --git a/inc/Parsing/ParserMode/Preformatted.php b/inc/Parsing/ParserMode/Preformatted.php new file mode 100644 index 000000000..7dfc47489 --- /dev/null +++ b/inc/Parsing/ParserMode/Preformatted.php @@ -0,0 +1,31 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Preformatted extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + // Has hard coded awareness of lists... + $this->Lexer->addEntryPattern('\n (?![\*\-])', $mode, 'preformatted'); + $this->Lexer->addEntryPattern('\n\t(?![\*\-])', $mode, 'preformatted'); + + // How to effect a sub pattern with the Lexer! + $this->Lexer->addPattern('\n ', 'preformatted'); + $this->Lexer->addPattern('\n\t', 'preformatted'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('\n', 'preformatted'); + } + + /** @inheritdoc */ + public function getSort() + { + return 20; + } +} diff --git a/inc/Parsing/ParserMode/Quote.php b/inc/Parsing/ParserMode/Quote.php new file mode 100644 index 000000000..65525b241 --- /dev/null +++ b/inc/Parsing/ParserMode/Quote.php @@ -0,0 +1,41 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Quote extends AbstractMode +{ + + /** + * Quote constructor. + */ + public function __construct() + { + global $PARSER_MODES; + + $this->allowedModes = array_merge( + $PARSER_MODES['formatting'], + $PARSER_MODES['substition'], + $PARSER_MODES['disabled'], + $PARSER_MODES['protected'] + ); + } + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('\n>{1,}', $mode, 'quote'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addPattern('\n>{1,}', 'quote'); + $this->Lexer->addExitPattern('\n', 'quote'); + } + + /** @inheritdoc */ + public function getSort() + { + return 220; + } +} diff --git a/inc/Parsing/ParserMode/Quotes.php b/inc/Parsing/ParserMode/Quotes.php new file mode 100644 index 000000000..13db2e679 --- /dev/null +++ b/inc/Parsing/ParserMode/Quotes.php @@ -0,0 +1,51 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Quotes extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + global $conf; + + $ws = '\s/\#~:+=&%@\-\x28\x29\]\[{}><"\''; // whitespace + $punc = ';,\.?!'; + + if ($conf['typography'] == 2) { + $this->Lexer->addSpecialPattern( + "(?<=^|[$ws])'(?=[^$ws$punc])", + $mode, + 'singlequoteopening' + ); + $this->Lexer->addSpecialPattern( + "(?<=^|[^$ws]|[$punc])'(?=$|[$ws$punc])", + $mode, + 'singlequoteclosing' + ); + $this->Lexer->addSpecialPattern( + "(?<=^|[^$ws$punc])'(?=$|[^$ws$punc])", + $mode, + 'apostrophe' + ); + } + + $this->Lexer->addSpecialPattern( + "(?<=^|[$ws])\"(?=[^$ws$punc])", + $mode, + 'doublequoteopening' + ); + $this->Lexer->addSpecialPattern( + "\"", + $mode, + 'doublequoteclosing' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 280; + } +} diff --git a/inc/Parsing/ParserMode/Rss.php b/inc/Parsing/ParserMode/Rss.php new file mode 100644 index 000000000..a62d9b807 --- /dev/null +++ b/inc/Parsing/ParserMode/Rss.php @@ -0,0 +1,19 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Rss extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern("\{\{rss>[^\}]+\}\}", $mode, 'rss'); + } + + /** @inheritdoc */ + public function getSort() + { + return 310; + } +} diff --git a/inc/Parsing/ParserMode/Smiley.php b/inc/Parsing/ParserMode/Smiley.php new file mode 100644 index 000000000..084ccc9ed --- /dev/null +++ b/inc/Parsing/ParserMode/Smiley.php @@ -0,0 +1,48 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +use dokuwiki\Parsing\Lexer\Lexer; + +class Smiley extends AbstractMode +{ + protected $smileys = array(); + protected $pattern = ''; + + /** + * Smiley constructor. + * @param string[] $smileys + */ + public function __construct($smileys) + { + $this->smileys = $smileys; + } + + /** @inheritdoc */ + public function preConnect() + { + if (!count($this->smileys) || $this->pattern != '') return; + + $sep = ''; + foreach ($this->smileys as $smiley) { + $this->pattern .= $sep.'(?<=\W|^)'. Lexer::escape($smiley).'(?=\W|$)'; + $sep = '|'; + } + } + + /** @inheritdoc */ + public function connectTo($mode) + { + if (!count($this->smileys)) return; + + if (strlen($this->pattern) > 0) { + $this->Lexer->addSpecialPattern($this->pattern, $mode, 'smiley'); + } + } + + /** @inheritdoc */ + public function getSort() + { + return 230; + } +} diff --git a/inc/Parsing/ParserMode/Table.php b/inc/Parsing/ParserMode/Table.php new file mode 100644 index 000000000..b4b512380 --- /dev/null +++ b/inc/Parsing/ParserMode/Table.php @@ -0,0 +1,47 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Table extends AbstractMode +{ + + /** + * Table constructor. + */ + public function __construct() + { + global $PARSER_MODES; + + $this->allowedModes = array_merge( + $PARSER_MODES['formatting'], + $PARSER_MODES['substition'], + $PARSER_MODES['disabled'], + $PARSER_MODES['protected'] + ); + } + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('[\t ]*\n\^', $mode, 'table'); + $this->Lexer->addEntryPattern('[\t ]*\n\|', $mode, 'table'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addPattern('\n\^', 'table'); + $this->Lexer->addPattern('\n\|', 'table'); + $this->Lexer->addPattern('[\t ]*:::[\t ]*(?=[\|\^])', 'table'); + $this->Lexer->addPattern('[\t ]+', 'table'); + $this->Lexer->addPattern('\^', 'table'); + $this->Lexer->addPattern('\|', 'table'); + $this->Lexer->addExitPattern('\n', 'table'); + } + + /** @inheritdoc */ + public function getSort() + { + return 60; + } +} diff --git a/inc/Parsing/ParserMode/Unformatted.php b/inc/Parsing/ParserMode/Unformatted.php new file mode 100644 index 000000000..1bc2826e6 --- /dev/null +++ b/inc/Parsing/ParserMode/Unformatted.php @@ -0,0 +1,28 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Unformatted extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('<nowiki>(?=.*</nowiki>)', $mode, 'unformatted'); + $this->Lexer->addEntryPattern('%%(?=.*%%)', $mode, 'unformattedalt'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('</nowiki>', 'unformatted'); + $this->Lexer->addExitPattern('%%', 'unformattedalt'); + $this->Lexer->mapHandler('unformattedalt', 'unformatted'); + } + + /** @inheritdoc */ + public function getSort() + { + return 170; + } +} diff --git a/inc/Parsing/ParserMode/Windowssharelink.php b/inc/Parsing/ParserMode/Windowssharelink.php new file mode 100644 index 000000000..747d4d8a9 --- /dev/null +++ b/inc/Parsing/ParserMode/Windowssharelink.php @@ -0,0 +1,31 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Windowssharelink extends AbstractMode +{ + + protected $pattern; + + /** @inheritdoc */ + public function preConnect() + { + $this->pattern = "\\\\\\\\\w+?(?:\\\\[\w\-$]+)+"; + } + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern( + $this->pattern, + $mode, + 'windowssharelink' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 350; + } +} diff --git a/inc/Parsing/ParserMode/Wordblock.php b/inc/Parsing/ParserMode/Wordblock.php new file mode 100644 index 000000000..50b24b2db --- /dev/null +++ b/inc/Parsing/ParserMode/Wordblock.php @@ -0,0 +1,52 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +use dokuwiki\Parsing\Lexer\Lexer; + +/** + * @fixme is this actually used? + */ +class Wordblock extends AbstractMode +{ + protected $badwords = array(); + protected $pattern = ''; + + /** + * Wordblock constructor. + * @param $badwords + */ + public function __construct($badwords) + { + $this->badwords = $badwords; + } + + /** @inheritdoc */ + public function preConnect() + { + + if (count($this->badwords) == 0 || $this->pattern != '') { + return; + } + + $sep = ''; + foreach ($this->badwords as $badword) { + $this->pattern .= $sep.'(?<=\b)(?i)'. Lexer::escape($badword).'(?-i)(?=\b)'; + $sep = '|'; + } + } + + /** @inheritdoc */ + public function connectTo($mode) + { + if (strlen($this->pattern) > 0) { + $this->Lexer->addSpecialPattern($this->pattern, $mode, 'wordblock'); + } + } + + /** @inheritdoc */ + public function getSort() + { + return 250; + } +} diff --git a/inc/PassHash.class.php b/inc/PassHash.php index 56c7cfd79..8ac623cd1 100644 --- a/inc/PassHash.class.php +++ b/inc/PassHash.php @@ -1,4 +1,8 @@ <?php +// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + +namespace dokuwiki; + /** * Password Hashing Class * @@ -21,7 +25,7 @@ class PassHash { * @param string $hash Hash to compare against * @return bool */ - function verify_hash($clear, $hash) { + public function verify_hash($clear, $hash) { $method = ''; $salt = ''; $magic = ''; @@ -72,7 +76,7 @@ class PassHash { } elseif(preg_match('/^\$6\$(rounds=\d+)?\$?(.+?)\$/', $hash, $m)) { $method = 'sha512'; $salt = $m[2]; - $magic = $m[1]; + $magic = $m[1]; } elseif($len == 32) { $method = 'md5'; } elseif($len == 40) { @@ -354,7 +358,7 @@ class PassHash { * @param string $salt The salt to use, null for random * @param string $magic The hash identifier (P or H) * @param int $compute The iteration count for new passwords - * @throws Exception + * @throws \Exception * @return string Hashed password */ public function hash_pmd5($clear, $salt = null, $magic = 'P', $compute = 8) { @@ -367,7 +371,7 @@ class PassHash { $iter = strpos($itoa64, $iterc); if($iter > 30) { - throw new Exception("Too high iteration count ($iter) in ". + throw new \Exception("Too high iteration count ($iter) in ". __CLASS__.'::'.__FUNCTION__); } @@ -412,6 +416,7 @@ class PassHash { * @param int $compute * * @return string + * @throws \Exception */ public function hash_hmd5($clear, $salt = null, $magic = 'H', $compute = 8) { return $this->hash_pmd5($clear, $salt, $magic, $compute); @@ -461,7 +466,7 @@ class PassHash { * @param string $salt The salt to use, null for random * @param array $opts ('algo' => hash algorithm, 'iter' => iterations) * @return string Hashed password - * @throws Exception when PHP is missing support for the method/algo + * @throws \Exception when PHP is missing support for the method/algo */ public function hash_djangopbkdf2($clear, $salt=null, $opts=array()) { $this->init_salt($salt, 12); @@ -476,10 +481,10 @@ class PassHash { $iter = (int) $opts['iter']; } if(!function_exists('hash_pbkdf2')) { - throw new Exception('This PHP installation has no PBKDF2 support'); + throw new \Exception('This PHP installation has no PBKDF2 support'); } if(!in_array($algo, hash_algos())) { - throw new Exception("This PHP installation has no $algo support"); + throw new \Exception("This PHP installation has no $algo support"); } $hash = base64_encode(hash_pbkdf2($algo, $clear, $salt, $iter, 0, true)); @@ -493,7 +498,7 @@ class PassHash { * @param string $salt The salt to use, null for random * @param array $opts ('iter' => iterations) * @return string Hashed password - * @throws Exception when PHP is missing support for the method/algo + * @throws \Exception when PHP is missing support for the method/algo */ public function hash_djangopbkdf2_sha256($clear, $salt=null, $opts=array()) { $opts['algo'] = 'sha256'; @@ -507,7 +512,7 @@ class PassHash { * @param string $salt The salt to use, null for random * @param array $opts ('iter' => iterations) * @return string Hashed password - * @throws Exception when PHP is missing support for the method/algo + * @throws \Exception when PHP is missing support for the method/algo */ public function hash_djangopbkdf2_sha1($clear, $salt=null, $opts=array()) { $opts['algo'] = 'sha1'; @@ -528,12 +533,12 @@ class PassHash { * @param string $clear The clear text to hash * @param string $salt The salt to use, null for random * @param int $compute The iteration count (between 4 and 31) - * @throws Exception + * @throws \Exception * @return string Hashed password */ public function hash_bcrypt($clear, $salt = null, $compute = 10) { if(!defined('CRYPT_BLOWFISH') || CRYPT_BLOWFISH != 1) { - throw new Exception('This PHP installation has no bcrypt support'); + throw new \Exception('This PHP installation has no bcrypt support'); } if(is_null($salt)) { @@ -553,20 +558,20 @@ class PassHash { * * @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 string $magic The rounds for sha512 (for example "rounds=3000"), null for default value * @return string Hashed password - * @throws Exception + * @throws \Exception */ public function hash_sha512($clear, $salt = null, $magic = null) { if(!defined('CRYPT_SHA512') || CRYPT_SHA512 != 1) { - throw new Exception('This PHP installation has no SHA512 support'); + throw new \Exception('This PHP installation has no SHA512 support'); } $this->init_salt($salt, 8, false); - if(empty($magic)) { - return crypt($clear, '$6$'.$salt.'$'); - }else{ - return crypt($clear, '$6$'.$magic.'$'.$salt.'$'); - } + if(empty($magic)) { + return crypt($clear, '$6$'.$salt.'$'); + }else{ + return crypt($clear, '$6$'.$magic.'$'.$salt.'$'); + } } /** @@ -633,13 +638,19 @@ class PassHash { } /** - * Use DokuWiki's secure random generator if available + * Use a secure random generator * * @param int $min * @param int $max * @return int */ protected function random($min, $max){ - return random_int($min, $max); + try { + return random_int($min, $max); + } catch (\Exception $e) { + // availability of random source is checked elsewhere in DokuWiki + // we demote this to an unchecked runtime exception here + throw new \RuntimeException($e->getMessage(), $e->getCode(), $e); + } } } diff --git a/inc/Plugin.php b/inc/Plugin.php deleted file mode 100644 index 9cd0ae805..000000000 --- a/inc/Plugin.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -/** - * DokuWiki Plugin - * - * Most of DokuWiki's plugin types simply inherit from this. All it does is - * add the DokuWiki_PluginTrait to the class. - */ -class DokuWiki_Plugin implements DokuWiki_PluginInterface { - use DokuWiki_PluginTrait; -} diff --git a/inc/Remote/AccessDeniedException.php b/inc/Remote/AccessDeniedException.php new file mode 100644 index 000000000..65f668930 --- /dev/null +++ b/inc/Remote/AccessDeniedException.php @@ -0,0 +1,10 @@ +<?php + +namespace dokuwiki\Remote; + +/** + * Class AccessDeniedException + */ +class AccessDeniedException extends RemoteException +{ +} diff --git a/inc/remote.php b/inc/Remote/Api.php index 2d2e327c8..5d9def59a 100644 --- a/inc/remote.php +++ b/inc/Remote/Api.php @@ -1,9 +1,9 @@ <?php -if (!defined('DOKU_INC')) die(); +namespace dokuwiki\Remote; -class RemoteException extends Exception {} -class RemoteAccessDeniedException extends RemoteException {} +use dokuwiki\Extension\Event; +use dokuwiki\Extension\RemotePlugin; /** * This class provides information about remote access to the wiki. @@ -33,19 +33,18 @@ class RemoteAccessDeniedException extends RemoteException {} * * plugin methods are formed like 'plugin.<plugin name>.<method name>'. * i.e.: plugin.clock.getTime or plugin.clock_gmt.getTime - * - * @throws RemoteException */ -class RemoteAPI { +class Api +{ /** - * @var RemoteAPICore + * @var ApiCore */ private $coreMethods = null; /** * @var array remote methods provided by dokuwiki plugins - will be filled lazy via - * {@see RemoteAPI#getPluginMethods} + * {@see dokuwiki\Remote\RemoteAPI#getPluginMethods} */ private $pluginMethods = null; @@ -63,7 +62,8 @@ class RemoteAPI { /** * constructor */ - public function __construct() { + public function __construct() + { $this->dateTransformation = array($this, 'dummyTransformation'); $this->fileTransformation = array($this, 'dummyTransformation'); } @@ -72,8 +72,10 @@ class RemoteAPI { * Get all available methods with remote access. * * @return array with information to all available methods + * @throws RemoteException */ - public function getMethods() { + public function getMethods() + { return array_merge($this->getCoreMethods(), $this->getPluginMethods()); } @@ -83,8 +85,10 @@ class RemoteAPI { * @param string $method name of the method to call. * @param array $args arguments to pass to the given method * @return mixed result of method call, must be a primitive type. + * @throws RemoteException */ - public function call($method, $args = array()) { + public function call($method, $args = array()) + { if ($args === null) { $args = array(); } @@ -104,7 +108,8 @@ class RemoteAPI { * @param string $name name of the method * @return bool if method exists */ - private function coreMethodExist($name) { + private function coreMethodExist($name) + { $coreMethods = $this->getCoreMethods(); return array_key_exists($name, $coreMethods); } @@ -113,11 +118,12 @@ class RemoteAPI { * Try to call custom methods provided by plugins * * @param string $method name of method - * @param array $args + * @param array $args * @return mixed * @throws RemoteException if method not exists */ - private function callCustomCallPlugin($method, $args) { + private function callCustomCallPlugin($method, $args) + { $customCalls = $this->getCustomCallPlugins(); if (!array_key_exists($method, $customCalls)) { throw new RemoteException('Method does not exist', -32603); @@ -132,10 +138,11 @@ class RemoteAPI { * @return array with pairs of custom plugin calls * @triggers RPC_CALL_ADD */ - private function getCustomCallPlugins() { + private function getCustomCallPlugins() + { if ($this->pluginCustomCalls === null) { $data = array(); - trigger_event('RPC_CALL_ADD', $data); + Event::createAndTrigger('RPC_CALL_ADD', $data); $this->pluginCustomCalls = $data; } return $this->pluginCustomCalls; @@ -146,11 +153,12 @@ class RemoteAPI { * * @param string $pluginName * @param string $method method name - * @param array $args + * @param array $args * @return mixed return of custom method * @throws RemoteException */ - private function callPlugin($pluginName, $method, $args) { + private function callPlugin($pluginName, $method, $args) + { $plugin = plugin_load('remote', $pluginName); $methods = $this->getPluginMethods(); if (!$plugin) { @@ -165,11 +173,12 @@ class RemoteAPI { * Call a core method * * @param string $method name of method - * @param array $args + * @param array $args * @return mixed * @throws RemoteException if method not exist */ - private function callCoreMethod($method, $args) { + private function callCoreMethod($method, $args) + { $coreMethods = $this->getCoreMethods(); $this->checkAccess($coreMethods[$method]); if (!isset($coreMethods[$method])) { @@ -183,11 +192,13 @@ class RemoteAPI { * Check if access should be checked * * @param array $methodMeta data about the method + * @throws AccessDeniedException */ - private function checkAccess($methodMeta) { + private function checkAccess($methodMeta) + { if (!isset($methodMeta['public'])) { $this->forceAccess(); - } else{ + } else { if ($methodMeta['public'] == '0') { $this->forceAccess(); } @@ -201,7 +212,8 @@ class RemoteAPI { * @param array $args * @throws RemoteException if wrong parameter count */ - private function checkArgumentLength($methodMeta, $args) { + private function checkArgumentLength($methodMeta, $args) + { if (count($methodMeta['args']) < count($args)) { throw new RemoteException('Method does not exist - wrong parameter count.', -32603); } @@ -214,36 +226,38 @@ class RemoteAPI { * @param string $method name of method * @return string */ - private function getMethodName($methodMeta, $method) { + private function getMethodName($methodMeta, $method) + { if (isset($methodMeta[$method]['name'])) { return $methodMeta[$method]['name']; } $method = explode('.', $method); - return $method[count($method)-1]; + return $method[count($method) - 1]; } /** * Perform access check for current user * * @return bool true if the current user has access to remote api. - * @throws RemoteAccessDeniedException If remote access disabled + * @throws AccessDeniedException If remote access disabled */ - public function hasAccess() { + public function hasAccess() + { global $conf; global $USERINFO; - /** @var Input $INPUT */ + /** @var \dokuwiki\Input\Input $INPUT */ global $INPUT; if (!$conf['remote']) { - throw new RemoteAccessDeniedException('server error. RPC server not enabled.',-32604); //should not be here,just throw + throw new AccessDeniedException('server error. RPC server not enabled.', -32604); } - if(trim($conf['remoteuser']) == '!!not set!!') { + if (trim($conf['remoteuser']) == '!!not set!!') { return false; } - if(!$conf['useacl']) { + if (!$conf['useacl']) { return true; } - if(trim($conf['remoteuser']) == '') { + if (trim($conf['remoteuser']) == '') { return true; } @@ -254,11 +268,12 @@ class RemoteAPI { * Requests access * * @return void - * @throws RemoteException On denied access. + * @throws AccessDeniedException On denied access. */ - public function forceAccess() { + public function forceAccess() + { if (!$this->hasAccess()) { - throw new RemoteAccessDeniedException('server error. not authorized to call method', -32604); + throw new AccessDeniedException('server error. not authorized to call method', -32604); } } @@ -268,19 +283,27 @@ class RemoteAPI { * @return array all plugin methods. * @throws RemoteException if not implemented */ - public function getPluginMethods() { + public function getPluginMethods() + { if ($this->pluginMethods === null) { $this->pluginMethods = array(); $plugins = plugin_list('remote'); foreach ($plugins as $pluginName) { - /** @var DokuWiki_Remote_Plugin $plugin */ + /** @var RemotePlugin $plugin */ $plugin = plugin_load('remote', $pluginName); - if (!is_subclass_of($plugin, 'DokuWiki_Remote_Plugin')) { - throw new RemoteException("Plugin $pluginName does not implement DokuWiki_Remote_Plugin"); + if (!is_subclass_of($plugin, 'dokuwiki\Extension\RemotePlugin')) { + throw new RemoteException( + "Plugin $pluginName does not implement dokuwiki\Plugin\DokuWiki_Remote_Plugin" + ); + } + + try { + $methods = $plugin->_getMethods(); + } catch (\ReflectionException $e) { + throw new RemoteException('Automatic aggregation of available remote methods failed', 0, $e); } - $methods = $plugin->_getMethods(); foreach ($methods as $method => $meta) { $this->pluginMethods["plugin.$pluginName.$method"] = $meta; } @@ -292,14 +315,15 @@ class RemoteAPI { /** * Collects all the core methods * - * @param RemoteAPICore $apiCore this parameter is used for testing. Here you can pass a non-default RemoteAPICore - * instance. (for mocking) + * @param ApiCore $apiCore this parameter is used for testing. Here you can pass a non-default RemoteAPICore + * instance. (for mocking) * @return array all core methods. */ - public function getCoreMethods($apiCore = null) { + public function getCoreMethods($apiCore = null) + { if ($this->coreMethods === null) { if ($apiCore === null) { - $this->coreMethods = new RemoteAPICore($this); + $this->coreMethods = new ApiCore($this); } else { $this->coreMethods = $apiCore; } @@ -313,7 +337,8 @@ class RemoteAPI { * @param mixed $data * @return mixed */ - public function toFile($data) { + public function toFile($data) + { return call_user_func($this->fileTransformation, $data); } @@ -323,7 +348,8 @@ class RemoteAPI { * @param mixed $data * @return mixed */ - public function toDate($data) { + public function toDate($data) + { return call_user_func($this->dateTransformation, $data); } @@ -333,7 +359,8 @@ class RemoteAPI { * @param mixed $data * @return mixed */ - public function dummyTransformation($data) { + public function dummyTransformation($data) + { return $data; } @@ -342,7 +369,8 @@ class RemoteAPI { * * @param callback $dateTransformation */ - public function setDateTransformation($dateTransformation) { + public function setDateTransformation($dateTransformation) + { $this->dateTransformation = $dateTransformation; } @@ -351,7 +379,8 @@ class RemoteAPI { * * @param callback $fileTransformation */ - public function setFileTransformation($fileTransformation) { + public function setFileTransformation($fileTransformation) + { $this->fileTransformation = $fileTransformation; } } diff --git a/inc/RemoteAPICore.php b/inc/Remote/ApiCore.php index c56d20cb0..9a041452d 100644 --- a/inc/RemoteAPICore.php +++ b/inc/Remote/ApiCore.php @@ -1,22 +1,32 @@ <?php -/** - * Increased whenever the API is changed - */ +namespace dokuwiki\Remote; + +use Doku_Renderer_xhtml; +use dokuwiki\ChangeLog\MediaChangeLog; +use dokuwiki\ChangeLog\PageChangeLog; +use dokuwiki\Extension\Event; + define('DOKU_API_VERSION', 10); /** * Provides the core methods for the remote API. * The methods are ordered in 'wiki.<method>' and 'dokuwiki.<method>' namespaces */ -class RemoteAPICore { +class ApiCore +{ + /** @var int Increased whenever the API is changed */ + const API_VERSION = 10; + + /** @var Api */ private $api; /** - * @param RemoteAPI $api + * @param Api $api */ - public function __construct(RemoteAPI $api) { + public function __construct(Api $api) + { $this->api = $api; } @@ -25,7 +35,8 @@ class RemoteAPICore { * * @return array */ - public function __getRemoteInfo() { + public function __getRemoteInfo() + { return array( 'dokuwiki.getVersion' => array( 'args' => array(), @@ -52,7 +63,7 @@ class RemoteAPICore { ), 'dokuwiki.getTime' => array( 'args' => array(), 'return' => 'int', - 'doc' => 'Returns the current time at the remote wiki server as Unix timestamp.', + 'doc' => 'Returns the current time at the remote wiki server as Unix timestamp.', ), 'dokuwiki.setLocks' => array( 'args' => array('array'), 'return' => 'array', @@ -165,7 +176,7 @@ class RemoteAPICore { 'public' => '1', ), 'wiki.getRPCVersionSupported' => array( 'args' => array(), - 'name' => 'wiki_RPCVersion', + 'name' => 'wikiRpcVersion', 'return' => 'int', 'doc' => 'Returns 2 with the supported RPC API version.', 'public' => '1' @@ -177,14 +188,16 @@ class RemoteAPICore { /** * @return string */ - public function getVersion() { + public function getVersion() + { return getVersion(); } /** * @return int unix timestamp */ - public function getTime() { + public function getTime() + { return time(); } @@ -194,15 +207,16 @@ class RemoteAPICore { * @param string $id wiki page id * @param int|string $rev revision timestamp of the page or empty string * @return string page text. - * @throws RemoteAccessDeniedException if no permission for page + * @throws AccessDeniedException if no permission for page */ - public function rawPage($id,$rev=''){ + public function rawPage($id, $rev = '') + { $id = $this->resolvePageId($id); - if(auth_quickaclcheck($id) < AUTH_READ){ - throw new RemoteAccessDeniedException('You are not allowed to read this file', 111); + if (auth_quickaclcheck($id) < AUTH_READ) { + throw new AccessDeniedException('You are not allowed to read this file', 111); } - $text = rawWiki($id,$rev); - if(!$text) { + $text = rawWiki($id, $rev); + if (!$text) { return pageTemplate($id); } else { return $text; @@ -216,13 +230,14 @@ class RemoteAPICore { * * @param string $id file id * @return mixed media file - * @throws RemoteAccessDeniedException no permission for media + * @throws AccessDeniedException no permission for media * @throws RemoteException not exist */ - public function getAttachment($id){ + public function getAttachment($id) + { $id = cleanID($id); - if (auth_quickaclcheck(getNS($id).':*') < AUTH_READ) { - throw new RemoteAccessDeniedException('You are not allowed to read this file', 211); + if (auth_quickaclcheck(getNS($id) . ':*') < AUTH_READ) { + throw new AccessDeniedException('You are not allowed to read this file', 211); } $file = mediaFN($id); @@ -242,7 +257,8 @@ class RemoteAPICore { * @param string $id page id * @return array */ - public function getAttachmentInfo($id){ + public function getAttachmentInfo($id) + { $id = cleanID($id); $info = array( 'lastModified' => $this->api->toDate(0), @@ -250,15 +266,15 @@ class RemoteAPICore { ); $file = mediaFN($id); - if(auth_quickaclcheck(getNS($id) . ':*') >= AUTH_READ) { - if(file_exists($file)) { + if (auth_quickaclcheck(getNS($id) . ':*') >= AUTH_READ) { + if (file_exists($file)) { $info['lastModified'] = $this->api->toDate(filemtime($file)); $info['size'] = filesize($file); } else { //Is it deleted media with changelog? $medialog = new MediaChangeLog($id); $revisions = $medialog->getRevisions(0, 1); - if(!empty($revisions)) { + if (!empty($revisions)) { $info['lastModified'] = $this->api->toDate($revisions[0]); } } @@ -270,17 +286,18 @@ class RemoteAPICore { /** * Return a wiki page rendered to html * - * @param string $id page id + * @param string $id page id * @param string|int $rev revision timestamp or empty string * @return null|string html - * @throws RemoteAccessDeniedException no access to page + * @throws AccessDeniedException no access to page */ - public function htmlPage($id,$rev=''){ + public function htmlPage($id, $rev = '') + { $id = $this->resolvePageId($id); - if(auth_quickaclcheck($id) < AUTH_READ){ - throw new RemoteAccessDeniedException('You are not allowed to read this page', 111); + if (auth_quickaclcheck($id) < AUTH_READ) { + throw new AccessDeniedException('You are not allowed to read this page', 111); } - return p_wiki_xhtml($id,$rev,false); + return p_wiki_xhtml($id, $rev, false); } /** @@ -288,14 +305,15 @@ class RemoteAPICore { * * @return array */ - public function listPages(){ - $list = array(); + public function listPages() + { + $list = array(); $pages = idx_get_indexer()->getPages(); - $pages = array_filter(array_filter($pages,'isVisiblePage'),'page_exists'); + $pages = array_filter(array_filter($pages, 'isVisiblePage'), 'page_exists'); - foreach(array_keys($pages) as $idx) { + foreach (array_keys($pages) as $idx) { $perm = auth_quickaclcheck($pages[$idx]); - if($perm < AUTH_READ) { + if ($perm < AUTH_READ) { continue; } $page = array(); @@ -313,15 +331,16 @@ class RemoteAPICore { * List all pages in the given namespace (and below) * * @param string $ns - * @param array $opts + * @param array $opts * $opts['depth'] recursion level, 0 for all * $opts['hash'] do md5 sum of content? * @return array */ - public function readNamespace($ns,$opts){ + public function readNamespace($ns, $opts) + { global $conf; - if(!is_array($opts)) $opts=array(); + if (!is_array($opts)) $opts = array(); $ns = cleanID($ns); $dir = utf8_encodeFN(str_replace(':', '/', $ns)); @@ -337,29 +356,30 @@ class RemoteAPICore { * @param string $query * @return array */ - public function search($query){ + public function search($query) + { $regex = array(); - $data = ft_pageSearch($query,$regex); + $data = ft_pageSearch($query, $regex); $pages = array(); // prepare additional data $idx = 0; - foreach($data as $id => $score){ + foreach ($data as $id => $score) { $file = wikiFN($id); - if($idx < FT_SNIPPET_NUMBER){ - $snippet = ft_snippet($id,$regex); + if ($idx < FT_SNIPPET_NUMBER) { + $snippet = ft_snippet($id, $regex); $idx++; - }else{ + } else { $snippet = ''; } $pages[] = array( - 'id' => $id, - 'score' => intval($score), - 'rev' => filemtime($file), - 'mtime' => filemtime($file), - 'size' => filesize($file), + 'id' => $id, + 'score' => intval($score), + 'rev' => filemtime($file), + 'mtime' => filemtime($file), + 'size' => filesize($file), 'snippet' => $snippet, 'title' => useHeading('navigation') ? p_get_first_heading($id) : $id ); @@ -372,7 +392,8 @@ class RemoteAPICore { * * @return string */ - public function getTitle(){ + public function getTitle() + { global $conf; return $conf['title']; } @@ -387,15 +408,16 @@ class RemoteAPICore { * @author Gina Haeussge <osd@foosel.net> * * @param string $ns - * @param array $options + * @param array $options * $options['depth'] recursion level, 0 for all * $options['showmsg'] shows message if invalid media id is used * $options['pattern'] check given pattern * $options['hash'] add hashes to result list * @return array - * @throws RemoteAccessDeniedException no access to the media files + * @throws AccessDeniedException no access to the media files */ - public function listAttachments($ns, $options = array()) { + public function listAttachments($ns, $options = array()) + { global $conf; $ns = cleanID($ns); @@ -403,15 +425,15 @@ class RemoteAPICore { if (!is_array($options)) $options = array(); $options['skipacl'] = 0; // no ACL skipping for XMLRPC - if(auth_quickaclcheck($ns.':*') >= AUTH_READ) { + if (auth_quickaclcheck($ns . ':*') >= AUTH_READ) { $dir = utf8_encodeFN(str_replace(':', '/', $ns)); $data = array(); search($data, $conf['mediadir'], 'search_media', $options, $dir); $len = count($data); - if(!$len) return array(); + if (!$len) return array(); - for($i=0; $i<$len; $i++) { + for ($i = 0; $i < $len; $i++) { unset($data[$i]['meta']); $data[$i]['perms'] = $data[$i]['perm']; unset($data[$i]['perm']); @@ -419,7 +441,7 @@ class RemoteAPICore { } return $data; } else { - throw new RemoteAccessDeniedException('You are not allowed to list media files.', 215); + throw new AccessDeniedException('You are not allowed to list media files.', 215); } } @@ -429,33 +451,35 @@ class RemoteAPICore { * @param string $id page id * @return array */ - function listBackLinks($id){ + public function listBackLinks($id) + { return ft_backlinks($this->resolvePageId($id)); } /** * Return some basic data about a page * - * @param string $id page id + * @param string $id page id * @param string|int $rev revision timestamp or empty string * @return array - * @throws RemoteAccessDeniedException no access for page + * @throws AccessDeniedException no access for page * @throws RemoteException page not exist */ - public function pageInfo($id,$rev=''){ + public function pageInfo($id, $rev = '') + { $id = $this->resolvePageId($id); - if(auth_quickaclcheck($id) < AUTH_READ){ - throw new RemoteAccessDeniedException('You are not allowed to read this page', 111); + if (auth_quickaclcheck($id) < AUTH_READ) { + throw new AccessDeniedException('You are not allowed to read this page', 111); } - $file = wikiFN($id,$rev); + $file = wikiFN($id, $rev); $time = @filemtime($file); - if(!$time){ + if (!$time) { throw new RemoteException('The requested page does not exist', 121); } // set revision to current version if empty, use revision otherwise // as the timestamps of old files are not necessarily correct - if($rev === '') { + if ($rev === '') { $rev = $time; } @@ -463,10 +487,10 @@ class RemoteAPICore { $info = $pagelog->getRevisionInfo($rev); $data = array( - 'name' => $id, + 'name' => $id, 'lastModified' => $this->api->toDate($rev), - 'author' => (($info['user']) ? $info['user'] : $info['ip']), - 'version' => $rev + 'author' => (($info['user']) ? $info['user'] : $info['ip']), + 'version' => $rev ); return ($data); @@ -481,53 +505,54 @@ class RemoteAPICore { * @param string $text wiki text * @param array $params parameters: summary, minor edit * @return bool - * @throws RemoteAccessDeniedException no write access for page + * @throws AccessDeniedException no write access for page * @throws RemoteException no id, empty new page or locked */ - public function putPage($id, $text, $params) { + public function putPage($id, $text, $params) + { global $TEXT; global $lang; - $id = $this->resolvePageId($id); - $TEXT = cleanText($text); - $sum = $params['sum']; + $id = $this->resolvePageId($id); + $TEXT = cleanText($text); + $sum = $params['sum']; $minor = $params['minor']; - if(empty($id)) { + if (empty($id)) { throw new RemoteException('Empty page ID', 131); } - if(!page_exists($id) && trim($TEXT) == '' ) { + if (!page_exists($id) && trim($TEXT) == '') { throw new RemoteException('Refusing to write an empty new wiki page', 132); } - if(auth_quickaclcheck($id) < AUTH_EDIT) { - throw new RemoteAccessDeniedException('You are not allowed to edit this page', 112); + if (auth_quickaclcheck($id) < AUTH_EDIT) { + throw new AccessDeniedException('You are not allowed to edit this page', 112); } // Check, if page is locked - if(checklock($id)) { + if (checklock($id)) { throw new RemoteException('The page is currently locked', 133); } // SPAM check - if(checkwordblock()) { + if (checkwordblock()) { throw new RemoteException('Positive wordblock check', 134); } // autoset summary on new pages - if(!page_exists($id) && empty($sum)) { + if (!page_exists($id) && empty($sum)) { $sum = $lang['created']; } // autoset summary on deleted pages - if(page_exists($id) && empty($TEXT) && empty($sum)) { + if (page_exists($id) && empty($TEXT) && empty($sum)) { $sum = $lang['deleted']; } lock($id); - saveWikiText($id,$TEXT,$sum,$minor); + saveWikiText($id, $TEXT, $sum, $minor); unlock($id); @@ -544,13 +569,15 @@ class RemoteAPICore { * @param string $text wiki text * @param array $params such as summary,minor * @return bool|string + * @throws RemoteException */ - public function appendPage($id, $text, $params) { + public function appendPage($id, $text, $params) + { $currentpage = $this->rawPage($id); if (!is_string($currentpage)) { return $currentpage; } - return $this->putPage($id, $currentpage.$text, $params); + return $this->putPage($id, $currentpage . $text, $params); } /** @@ -560,14 +587,14 @@ class RemoteAPICore { * * @return bool * - * @throws RemoteAccessDeniedException + * @throws AccessDeniedException */ public function deleteUsers($usernames) { if (!auth_isadmin()) { - throw new RemoteAccessDeniedException('Only admins are allowed to delete users', 114); + throw new AccessDeniedException('Only admins are allowed to delete users', 114); } - /** @var DokuWiki_Auth_Plugin $auth */ + /** @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; return (bool)$auth->triggerUserMod('delete', array($usernames)); } @@ -583,17 +610,18 @@ class RemoteAPICore { * @return false|string * @throws RemoteException */ - public function putAttachment($id, $file, $params) { + public function putAttachment($id, $file, $params) + { $id = cleanID($id); - $auth = auth_quickaclcheck(getNS($id).':*'); + $auth = auth_quickaclcheck(getNS($id) . ':*'); - if(!isset($id)) { + if (!isset($id)) { throw new RemoteException('Filename not given.', 231); } global $conf; - $ftmp = $conf['tmpdir'] . '/' . md5($id.clientIP()); + $ftmp = $conf['tmpdir'] . '/' . md5($id . clientIP()); // save temporary file @unlink($ftmp); @@ -614,17 +642,18 @@ class RemoteAPICore { * * @param string $id page id * @return int - * @throws RemoteAccessDeniedException no permissions + * @throws AccessDeniedException no permissions * @throws RemoteException file in use or not deleted */ - public function deleteAttachment($id){ + public function deleteAttachment($id) + { $id = cleanID($id); - $auth = auth_quickaclcheck(getNS($id).':*'); + $auth = auth_quickaclcheck(getNS($id) . ':*'); $res = media_delete($id, $auth); if ($res & DOKU_MEDIA_DELETED) { return 0; } elseif ($res & DOKU_MEDIA_NOT_AUTH) { - throw new RemoteAccessDeniedException('You don\'t have permissions to delete files.', 212); + throw new AccessDeniedException('You don\'t have permissions to delete files.', 212); } elseif ($res & DOKU_MEDIA_INUSE) { throw new RemoteException('File is still referenced', 232); } else { @@ -640,17 +669,18 @@ class RemoteAPICore { * @param array|null $groups array of groups * @return int permission level */ - public function aclCheck($id, $user = null, $groups = null) { - /** @var DokuWiki_Auth_Plugin $auth */ + public function aclCheck($id, $user = null, $groups = null) + { + /** @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; $id = $this->resolvePageId($id); - if($user === null) { + if ($user === null) { return auth_quickaclcheck($id); } else { - if($groups === null) { + if ($groups === null) { $userinfo = $auth->getUserData($user); - if($userinfo === false) { + if ($userinfo === false) { $groups = array(); } else { $groups = $userinfo['grps']; @@ -667,44 +697,45 @@ class RemoteAPICore { * * @param string $id page id * @return array - * @throws RemoteAccessDeniedException no read access for page + * @throws AccessDeniedException no read access for page */ - public function listLinks($id) { + public function listLinks($id) + { $id = $this->resolvePageId($id); - if(auth_quickaclcheck($id) < AUTH_READ){ - throw new RemoteAccessDeniedException('You are not allowed to read this page', 111); + if (auth_quickaclcheck($id) < AUTH_READ) { + throw new AccessDeniedException('You are not allowed to read this page', 111); } $links = array(); // resolve page instructions - $ins = p_cached_instructions(wikiFN($id)); + $ins = p_cached_instructions(wikiFN($id)); // instantiate new Renderer - needed for interwiki links $Renderer = new Doku_Renderer_xhtml(); $Renderer->interwiki = getInterwiki(); // parse parse instructions - foreach($ins as $in) { + foreach ($ins as $in) { $link = array(); - switch($in[0]) { + switch ($in[0]) { case 'internallink': $link['type'] = 'local'; $link['page'] = $in[1][0]; $link['href'] = wl($in[1][0]); - array_push($links,$link); + array_push($links, $link); break; case 'externallink': $link['type'] = 'extern'; $link['page'] = $in[1][0]; $link['href'] = $in[1][0]; - array_push($links,$link); + array_push($links, $link); break; case 'interwikilink': - $url = $Renderer->_resolveInterWiki($in[1][2],$in[1][3]); + $url = $Renderer->_resolveInterWiki($in[1][2], $in[1][3]); $link['type'] = 'extern'; $link['page'] = $url; $link['href'] = $url; - array_push($links,$link); + array_push($links, $link); break; } } @@ -722,8 +753,9 @@ class RemoteAPICore { * @return array * @throws RemoteException no valid timestamp */ - public function getRecentChanges($timestamp) { - if(strlen($timestamp) != 10) { + public function getRecentChanges($timestamp) + { + if (strlen($timestamp) != 10) { throw new RemoteException('The provided value is not a valid timestamp', 311); } @@ -733,12 +765,12 @@ class RemoteAPICore { foreach ($recents as $recent) { $change = array(); - $change['name'] = $recent['id']; + $change['name'] = $recent['id']; $change['lastModified'] = $this->api->toDate($recent['date']); - $change['author'] = $recent['user']; - $change['version'] = $recent['date']; - $change['perms'] = $recent['perms']; - $change['size'] = @filesize(wikiFN($recent['id'])); + $change['author'] = $recent['user']; + $change['version'] = $recent['date']; + $change['perms'] = $recent['perms']; + $change['size'] = @filesize(wikiFN($recent['id'])); array_push($changes, $change); } @@ -760,8 +792,9 @@ class RemoteAPICore { * @return array * @throws RemoteException no valid timestamp */ - public function getRecentMediaChanges($timestamp) { - if(strlen($timestamp) != 10) + public function getRecentMediaChanges($timestamp) + { + if (strlen($timestamp) != 10) throw new RemoteException('The provided value is not a valid timestamp', 311); $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES); @@ -770,12 +803,12 @@ class RemoteAPICore { foreach ($recents as $recent) { $change = array(); - $change['name'] = $recent['id']; + $change['name'] = $recent['id']; $change['lastModified'] = $this->api->toDate($recent['date']); - $change['author'] = $recent['user']; - $change['version'] = $recent['date']; - $change['perms'] = $recent['perms']; - $change['size'] = @filesize(mediaFN($recent['id'])); + $change['author'] = $recent['user']; + $change['version'] = $recent['date']; + $change['perms'] = $recent['perms']; + $change['size'] = @filesize(mediaFN($recent['id'])); array_push($changes, $change); } @@ -794,22 +827,26 @@ class RemoteAPICore { * * @author Michael Klier <chi@chimeric.de> * - * @param string $id page id - * @param int $first skip the first n changelog lines (0 = from current(if exists), 1 = from 1st old rev, 2 = from 2nd old rev, etc) + * @param string $id page id + * @param int $first skip the first n changelog lines + * 0 = from current(if exists) + * 1 = from 1st old rev + * 2 = from 2nd old rev, etc * @return array - * @throws RemoteAccessDeniedException no read access for page + * @throws AccessDeniedException no read access for page * @throws RemoteException empty id */ - public function pageVersions($id, $first) { + public function pageVersions($id, $first) + { $id = $this->resolvePageId($id); - if(auth_quickaclcheck($id) < AUTH_READ) { - throw new RemoteAccessDeniedException('You are not allowed to read this page', 111); + if (auth_quickaclcheck($id) < AUTH_READ) { + throw new AccessDeniedException('You are not allowed to read this page', 111); } global $conf; $versions = array(); - if(empty($id)) { + if (empty($id)) { throw new RemoteException('Empty page ID', 131); } @@ -819,29 +856,29 @@ class RemoteAPICore { $pagelog = new PageChangeLog($id); $revisions = $pagelog->getRevisions($first_rev, $conf['recent']); - if($first == 0) { + if ($first == 0) { array_unshift($revisions, ''); // include current revision - if ( count($revisions) > $conf['recent'] ){ + if (count($revisions) > $conf['recent']) { array_pop($revisions); // remove extra log entry } } - if(!empty($revisions)) { - foreach($revisions as $rev) { - $file = wikiFN($id,$rev); + if (!empty($revisions)) { + foreach ($revisions as $rev) { + $file = wikiFN($id, $rev); $time = @filemtime($file); // we check if the page actually exists, if this is not the // case this can lead to less pages being returned than // specified via $conf['recent'] - if($time){ + if ($time) { $pagelog->setChunkSize(1024); $info = $pagelog->getRevisionInfo($rev ? $rev : $time); - if(!empty($info)) { + if (!empty($info)) { $data = array(); $data['user'] = $info['user']; - $data['ip'] = $info['ip']; + $data['ip'] = $info['ip']; $data['type'] = $info['type']; - $data['sum'] = $info['sum']; + $data['sum'] = $info['sum']; $data['modified'] = $this->api->toDate($info['date']); $data['version'] = $info['date']; array_push($versions, $data); @@ -857,11 +894,11 @@ class RemoteAPICore { /** * The version of Wiki RPC API supported */ - public function wiki_RPCVersion(){ + public function wikiRpcVersion() + { return 2; } - /** * Locks or unlocks a given batch of pages * @@ -874,35 +911,36 @@ class RemoteAPICore { * @param array[] $set list pages with array('lock' => array, 'unlock' => array) * @return array */ - public function setLocks($set){ - $locked = array(); - $lockfail = array(); - $unlocked = array(); + public function setLocks($set) + { + $locked = array(); + $lockfail = array(); + $unlocked = array(); $unlockfail = array(); - foreach((array) $set['lock'] as $id){ + foreach ((array) $set['lock'] as $id) { $id = $this->resolvePageId($id); - if(auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)){ + if (auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)) { $lockfail[] = $id; - }else{ + } else { lock($id); $locked[] = $id; } } - foreach((array) $set['unlock'] as $id){ + foreach ((array) $set['unlock'] as $id) { $id = $this->resolvePageId($id); - if(auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)){ + if (auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)) { $unlockfail[] = $id; - }else{ + } else { $unlocked[] = $id; } } return array( - 'locked' => $locked, - 'lockfail' => $lockfail, - 'unlocked' => $unlocked, + 'locked' => $locked, + 'lockfail' => $lockfail, + 'unlocked' => $unlocked, 'unlockfail' => $unlockfail, ); } @@ -912,8 +950,9 @@ class RemoteAPICore { * * @return int */ - public function getAPIVersion(){ - return DOKU_API_VERSION; + public function getAPIVersion() + { + return self::API_VERSION; } /** @@ -923,25 +962,26 @@ class RemoteAPICore { * @param string $pass * @return int */ - public function login($user,$pass){ + public function login($user, $pass) + { global $conf; - /** @var DokuWiki_Auth_Plugin $auth */ + /** @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; - if(!$conf['useacl']) return 0; - if(!$auth) return 0; + if (!$conf['useacl']) return 0; + if (!$auth) return 0; @session_start(); // reopen session for login - if($auth->canDo('external')){ - $ok = $auth->trustExternal($user,$pass,false); - }else{ + if ($auth->canDo('external')) { + $ok = $auth->trustExternal($user, $pass, false); + } else { $evdata = array( - 'user' => $user, + 'user' => $user, 'password' => $pass, - 'sticky' => false, - 'silent' => true, + 'sticky' => false, + 'silent' => true, ); - $ok = trigger_event('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper'); + $ok = Event::createAndTrigger('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper'); } session_write_close(); // we're done with the session @@ -953,11 +993,12 @@ class RemoteAPICore { * * @return int */ - public function logoff(){ + public function logoff() + { global $conf; global $auth; - if(!$conf['useacl']) return 0; - if(!$auth) return 0; + if (!$conf['useacl']) return 0; + if (!$auth) return 0; auth_logoff(); @@ -970,14 +1011,13 @@ class RemoteAPICore { * @param string $id page id * @return string */ - private function resolvePageId($id) { + private function resolvePageId($id) + { $id = cleanID($id); - if(empty($id)) { + if (empty($id)) { global $conf; $id = cleanID($conf['start']); } return $id; } - } - diff --git a/inc/Remote/RemoteException.php b/inc/Remote/RemoteException.php new file mode 100644 index 000000000..129a6c240 --- /dev/null +++ b/inc/Remote/RemoteException.php @@ -0,0 +1,10 @@ +<?php + +namespace dokuwiki\Remote; + +/** + * Class RemoteException + */ +class RemoteException extends \Exception +{ +} diff --git a/inc/Remote/XmlRpcServer.php b/inc/Remote/XmlRpcServer.php new file mode 100644 index 000000000..1b0097856 --- /dev/null +++ b/inc/Remote/XmlRpcServer.php @@ -0,0 +1,61 @@ +<?php + +namespace dokuwiki\Remote; + +/** + * Contains needed wrapper functions and registers all available XMLRPC functions. + */ +class XmlRpcServer extends \IXR_Server +{ + protected $remote; + + /** + * Constructor. Register methods and run Server + */ + public function __construct() + { + $this->remote = new Api(); + $this->remote->setDateTransformation(array($this, 'toDate')); + $this->remote->setFileTransformation(array($this, 'toFile')); + parent::__construct(); + } + + /** + * @inheritdoc + */ + public function call($methodname, $args) + { + try { + $result = $this->remote->call($methodname, $args); + return $result; + } /** @noinspection PhpRedundantCatchClauseInspection */ catch (AccessDeniedException $e) { + if (!isset($_SERVER['REMOTE_USER'])) { + http_status(401); + return new \IXR_Error(-32603, "server error. not authorized to call method $methodname"); + } else { + http_status(403); + return new \IXR_Error(-32604, "server error. forbidden to call the method $methodname"); + } + } catch (RemoteException $e) { + return new \IXR_Error($e->getCode(), $e->getMessage()); + } + } + + /** + * @param string|int $data iso date(yyyy[-]mm[-]dd[ hh:mm[:ss]]) or timestamp + * @return \IXR_Date + */ + public function toDate($data) + { + return new \IXR_Date($data); + } + + /** + * @param string $data + * @return \IXR_Base64 + */ + public function toFile($data) + { + return new \IXR_Base64($data); + } +} diff --git a/inc/SafeFN.class.php b/inc/SafeFN.class.php index b9e4a2b2a..c5489b185 100644 --- a/inc/SafeFN.class.php +++ b/inc/SafeFN.class.php @@ -45,7 +45,7 @@ class SafeFN { * @author Christopher Smith <chris@jalakai.co.uk> */ public static function encode($filename) { - return self::unicode_to_safe(utf8_to_unicode($filename)); + return self::unicodeToSafe(\dokuwiki\Utf8\Unicode::fromUtf8($filename)); } /** @@ -74,14 +74,14 @@ class SafeFN { * @author Christopher Smith <chris@jalakai.co.uk> */ public static function decode($filename) { - return unicode_to_utf8(self::safe_to_unicode(strtolower($filename))); + return \dokuwiki\Utf8\Unicode::toUtf8(self::safeToUnicode(strtolower($filename))); } - public static function validate_printable_utf8($printable_utf8) { + public static function validatePrintableUtf8($printable_utf8) { return !preg_match('#[\x01-\x1f]#',$printable_utf8); } - public static function validate_safe($safe) { + public static function validateSafe($safe) { return !preg_match('#[^'.self::$plain.self::$post_indicator.self::$pre_indicator.']#',$safe); } @@ -93,7 +93,7 @@ class SafeFN { * * @author Christopher Smith <chris@jalakai.co.uk> */ - private static function unicode_to_safe($unicode) { + private static function unicodeToSafe($unicode) { $safe = ''; $converted = false; @@ -126,7 +126,7 @@ class SafeFN { * * @author Christopher Smith <chris@jalakai.co.uk> */ - private static function safe_to_unicode($safe) { + private static function safeToUnicode($safe) { $unicode = array(); $split = preg_split('#(?=['.self::$post_indicator.self::$pre_indicator.'])#',$safe,-1,PREG_SPLIT_NO_EMPTY); diff --git a/inc/Sitemap/Item.php b/inc/Sitemap/Item.php new file mode 100644 index 000000000..d11bfc135 --- /dev/null +++ b/inc/Sitemap/Item.php @@ -0,0 +1,66 @@ +<?php + +namespace dokuwiki\Sitemap; + +/** + * An item of a sitemap. + * + * @author Michael Hamann + */ +class Item { + public $url; + public $lastmod; + public $changefreq; + public $priority; + + /** + * Create a new item. + * + * @param string $url The url of the item + * @param int $lastmod Timestamp of the last modification + * @param string $changefreq How frequently the item is likely to change. + * Valid values: always, hourly, daily, weekly, monthly, yearly, never. + * @param $priority float|string The priority of the item relative to other URLs on your site. + * Valid values range from 0.0 to 1.0. + */ + public function __construct($url, $lastmod, $changefreq = null, $priority = null) { + $this->url = $url; + $this->lastmod = $lastmod; + $this->changefreq = $changefreq; + $this->priority = $priority; + } + + /** + * Helper function for creating an item for a wikipage id. + * + * @param string $id A wikipage id. + * @param string $changefreq How frequently the item is likely to change. + * Valid values: always, hourly, daily, weekly, monthly, yearly, never. + * @param float|string $priority The priority of the item relative to other URLs on your site. + * Valid values range from 0.0 to 1.0. + * @return Item The sitemap item. + */ + public static function createFromID($id, $changefreq = null, $priority = null) { + $id = trim($id); + $date = @filemtime(wikiFN($id)); + if(!$date) return null; + return new Item(wl($id, '', true), $date, $changefreq, $priority); + } + + /** + * Get the XML representation of the sitemap item. + * + * @return string The XML representation. + */ + public function toXML() { + $result = ' <url>'.NL + .' <loc>'.hsc($this->url).'</loc>'.NL + .' <lastmod>'.date_iso8601($this->lastmod).'</lastmod>'.NL; + if ($this->changefreq !== null) + $result .= ' <changefreq>'.hsc($this->changefreq).'</changefreq>'.NL; + if ($this->priority !== null) + $result .= ' <priority>'.hsc($this->priority).'</priority>'.NL; + $result .= ' </url>'.NL; + return $result; + } +} diff --git a/inc/Sitemapper.php b/inc/Sitemap/Mapper.php index 037990e96..2f0567f05 100644 --- a/inc/Sitemapper.php +++ b/inc/Sitemap/Mapper.php @@ -6,14 +6,16 @@ * @author Michael Hamann <michael@content-space.de> */ -if(!defined('DOKU_INC')) die('meh.'); +namespace dokuwiki\Sitemap; + +use dokuwiki\HTTP\DokuHTTPClient; /** * A class for building sitemaps and pinging search engines with the sitemap URL. * * @author Michael Hamann */ -class Sitemapper { +class Mapper { /** * Builds a Google Sitemap of all public pages known to the indexer * @@ -31,7 +33,7 @@ class Sitemapper { global $conf; if($conf['sitemap'] < 1 || !is_numeric($conf['sitemap'])) return false; - $sitemap = Sitemapper::getFilePath(); + $sitemap = Mapper::getFilePath(); if(file_exists($sitemap)){ if(!is_writable($sitemap)) return false; @@ -56,16 +58,16 @@ class Sitemapper { //skip hidden, non existing and restricted files if(isHiddenPage($id)) continue; if(auth_aclcheck($id,'',array()) < AUTH_READ) continue; - $item = SitemapItem::createFromID($id); + $item = Item::createFromID($id); if ($item !== null) $items[] = $item; } $eventData = array('items' => &$items, 'sitemap' => &$sitemap); - $event = new Doku_Event('SITEMAP_GENERATE', $eventData); + $event = new \dokuwiki\Extension\Event('SITEMAP_GENERATE', $eventData); if ($event->advise_before(true)) { //save the new sitemap - $event->result = io_saveFile($sitemap, Sitemapper::getXML($items)); + $event->result = io_saveFile($sitemap, Mapper::getXML($items)); } $event->advise_after(); @@ -85,7 +87,7 @@ class Sitemapper { echo '<?xml version="1.0" encoding="UTF-8"?>'.NL; echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'.NL; foreach ($items as $item) { - /** @var SitemapItem $item */ + /** @var Item $item */ echo $item->toXML(); } echo '</urlset>'.NL; @@ -145,7 +147,7 @@ class Sitemapper { $data = array('ping_urls' => $ping_urls, 'encoded_sitemap_url' => $encoded_sitemap_url ); - $event = new Doku_Event('SITEMAP_PING', $data); + $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"); @@ -160,61 +162,3 @@ class Sitemapper { } } -/** - * An item of a sitemap. - * - * @author Michael Hamann - */ -class SitemapItem { - public $url; - public $lastmod; - public $changefreq; - public $priority; - - /** - * Create a new item. - * - * @param string $url The url of the item - * @param int $lastmod Timestamp of the last modification - * @param string $changefreq How frequently the item is likely to change. Valid values: always, hourly, daily, weekly, monthly, yearly, never. - * @param $priority float|string The priority of the item relative to other URLs on your site. Valid values range from 0.0 to 1.0. - */ - public function __construct($url, $lastmod, $changefreq = null, $priority = null) { - $this->url = $url; - $this->lastmod = $lastmod; - $this->changefreq = $changefreq; - $this->priority = $priority; - } - - /** - * Helper function for creating an item for a wikipage id. - * - * @param string $id A wikipage id. - * @param string $changefreq How frequently the item is likely to change. Valid values: always, hourly, daily, weekly, monthly, yearly, never. - * @param float|string $priority The priority of the item relative to other URLs on your site. Valid values range from 0.0 to 1.0. - * @return SitemapItem The sitemap item. - */ - public static function createFromID($id, $changefreq = null, $priority = null) { - $id = trim($id); - $date = @filemtime(wikiFN($id)); - if(!$date) return null; - return new SitemapItem(wl($id, '', true), $date, $changefreq, $priority); - } - - /** - * Get the XML representation of the sitemap item. - * - * @return string The XML representation. - */ - public function toXML() { - $result = ' <url>'.NL - .' <loc>'.hsc($this->url).'</loc>'.NL - .' <lastmod>'.date_iso8601($this->lastmod).'</lastmod>'.NL; - if ($this->changefreq !== null) - $result .= ' <changefreq>'.hsc($this->changefreq).'</changefreq>'.NL; - if ($this->priority !== null) - $result .= ' <priority>'.hsc($this->priority).'</priority>'.NL; - $result .= ' </url>'.NL; - return $result; - } -} diff --git a/inc/StyleUtils.php b/inc/StyleUtils.php index f2ac4be5e..d9f19a58b 100644 --- a/inc/StyleUtils.php +++ b/inc/StyleUtils.php @@ -2,6 +2,11 @@ namespace dokuwiki; +/** + * Class StyleUtils + * + * Reads and applies the template's style.ini settings + */ class StyleUtils { @@ -95,23 +100,27 @@ class StyleUtils $config = parse_ini_file($inifile, true); if (is_array($config['stylesheets'])) { - foreach ($config['stylesheets'] as $inifile => $mode) { // validate and include style files - $stylesheets = array_merge($stylesheets, $this->getValidatedStyles($stylesheets, $inifile, $mode, $incbase, $webbase)); + $stylesheets = array_merge( + $stylesheets, + $this->getValidatedStyles($stylesheets, $inifile, $mode, $incbase, $webbase) + ); $combined['stylesheets'] = array_merge($combined['stylesheets'], $stylesheets); } } if (is_array($config['replacements'])) { - $replacements = array_replace($replacements, $this->cssFixreplacementurls($config['replacements'], $webbase)); + $replacements = array_replace( + $replacements, + $this->cssFixreplacementurls($config['replacements'], $webbase) + ); $combined['replacements'] = array_merge($combined['replacements'], $replacements); } } } } - return $combined; } @@ -134,7 +143,8 @@ class StyleUtils if (file_exists($incbase . $basename . '.' . $newExtension)) { $stylesheets[$mode][$incbase . $basename . '.' . $newExtension] = $webbase; if ($conf['allowdebug']) { - msg("Stylesheet $file not found, using $basename.$newExtension instead. Please contact developer of \"$this->tpl\" template.", 2); + msg("Stylesheet $file not found, using $basename.$newExtension instead. " . + "Please contact developer of \"$this->tpl\" template.", 2); } } elseif ($conf['allowdebug']) { msg("Stylesheet $file not found, please contact the developer of \"$this->tpl\" template.", 2); @@ -173,7 +183,11 @@ class StyleUtils protected function cssFixreplacementurls($replacements, $location) { foreach ($replacements as $key => $value) { - $replacements[$key] = preg_replace('#(url\([ \'"]*)(?!/|data:|http://|https://| |\'|")#', '\\1' . $location, $value); + $replacements[$key] = preg_replace( + '#(url\([ \'"]*)(?!/|data:|http://|https://| |\'|")#', + '\\1' . $location, + $value + ); } return $replacements; } diff --git a/inc/Subscriptions/BulkSubscriptionSender.php b/inc/Subscriptions/BulkSubscriptionSender.php new file mode 100644 index 000000000..672ef90f6 --- /dev/null +++ b/inc/Subscriptions/BulkSubscriptionSender.php @@ -0,0 +1,261 @@ +<?php + + +namespace dokuwiki\Subscriptions; + + +use dokuwiki\ChangeLog\PageChangeLog; +use dokuwiki\Input\Input; +use DokuWiki_Auth_Plugin; + +class BulkSubscriptionSender extends SubscriptionSender +{ + + /** + * Send digest and list subscriptions + * + * This sends mails to all subscribers that have a subscription for namespaces above + * the given page if the needed $conf['subscribe_time'] has passed already. + * + * This function is called form lib/exe/indexer.php + * + * @param string $page + * + * @return int number of sent mails + */ + public function sendBulk($page) + { + $subscriberManager = new SubscriberManager(); + if (!$subscriberManager->isenabled()) { + return 0; + } + + /** @var DokuWiki_Auth_Plugin $auth */ + global $auth; + global $conf; + global $USERINFO; + /** @var Input $INPUT */ + global $INPUT; + $count = 0; + + $subscriptions = $subscriberManager->subscribers($page, null, ['digest', 'list']); + + // remember current user info + $olduinfo = $USERINFO; + $olduser = $INPUT->server->str('REMOTE_USER'); + + foreach ($subscriptions as $target => $users) { + if (!$this->lock($target)) { + continue; + } + + foreach ($users as $user => $info) { + list($style, $lastupdate) = $info; + + $lastupdate = (int)$lastupdate; + if ($lastupdate + $conf['subscribe_time'] > time()) { + // Less than the configured time period passed since last + // update. + continue; + } + + // Work as the user to make sure ACLs apply correctly + $USERINFO = $auth->getUserData($user); + $INPUT->server->set('REMOTE_USER', $user); + if ($USERINFO === false) { + continue; + } + if (!$USERINFO['mail']) { + continue; + } + + if (substr($target, -1, 1) === ':') { + // subscription target is a namespace, get all changes within + $changes = getRecentsSince($lastupdate, null, getNS($target)); + } else { + // single page subscription, check ACL ourselves + if (auth_quickaclcheck($target) < AUTH_READ) { + continue; + } + $meta = p_get_metadata($target); + $changes = [$meta['last_change']]; + } + + // Filter out pages only changed in small and own edits + $change_ids = []; + foreach ($changes as $rev) { + $n = 0; + while (!is_null($rev) && $rev['date'] >= $lastupdate && + ($INPUT->server->str('REMOTE_USER') === $rev['user'] || + $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)) { + $pagelog = new PageChangeLog($rev['id']); + $rev = $pagelog->getRevisions($n++, 1); + $rev = (count($rev) > 0) ? $rev[0] : null; + } + + if (!is_null($rev) && $rev['date'] >= $lastupdate) { + // Some change was not a minor one and not by myself + $change_ids[] = $rev['id']; + } + } + + // send it + if ($style === 'digest') { + foreach ($change_ids as $change_id) { + $this->sendDigest( + $USERINFO['mail'], + $change_id, + $lastupdate + ); + $count++; + } + } else { + if ($style === 'list') { + $this->sendList($USERINFO['mail'], $change_ids, $target); + $count++; + } + } + // TODO: Handle duplicate subscriptions. + + // Update notification time. + $subscriberManager->add($target, $user, $style, time()); + } + $this->unlock($target); + } + + // restore current user info + $USERINFO = $olduinfo; + $INPUT->server->set('REMOTE_USER', $olduser); + return $count; + } + + /** + * Lock subscription info + * + * We don't use io_lock() her because we do not wait for the lock and use a larger stale time + * + * @param string $id The target page or namespace, specified by id; Namespaces + * are identified by appending a colon. + * + * @return bool true, if you got a succesful lock + * @author Adrian Lang <lang@cosmocode.de> + */ + protected function lock($id) + { + global $conf; + + $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock'; + + if (is_dir($lock) && time() - @filemtime($lock) > 60 * 5) { + // looks like a stale lock - remove it + @rmdir($lock); + } + + // try creating the lock directory + if (!@mkdir($lock, $conf['dmode'])) { + return false; + } + + if (!empty($conf['dperm'])) { + chmod($lock, $conf['dperm']); + } + return true; + } + + /** + * Unlock subscription info + * + * @param string $id The target page or namespace, specified by id; Namespaces + * are identified by appending a colon. + * + * @return bool + * @author Adrian Lang <lang@cosmocode.de> + */ + protected function unlock($id) + { + global $conf; + $lock = $conf['lockdir'] . '/_subscr_' . md5($id) . '.lock'; + return @rmdir($lock); + } + + /** + * Send a digest mail + * + * Sends a digest mail showing a bunch of changes of a single page. Basically the same as sendPageDiff() + * but determines the last known revision first + * + * @param string $subscriber_mail The target mail address + * @param string $id The ID + * @param int $lastupdate Time of the last notification + * + * @return bool + * @author Adrian Lang <lang@cosmocode.de> + * + */ + protected function sendDigest($subscriber_mail, $id, $lastupdate) + { + $pagelog = new PageChangeLog($id); + $n = 0; + do { + $rev = $pagelog->getRevisions($n++, 1); + $rev = (count($rev) > 0) ? $rev[0] : null; + } while (!is_null($rev) && $rev > $lastupdate); + + // TODO I'm not happy with the following line and passing $this->mailer around. Not sure how to solve it better + $pageSubSender = new PageSubscriptionSender($this->mailer); + return $pageSubSender->sendPageDiff( + $subscriber_mail, + 'subscr_digest', + $id, + $rev + ); + } + + /** + * Send a list mail + * + * Sends a list mail showing a list of changed pages. + * + * @param string $subscriber_mail The target mail address + * @param array $ids Array of ids + * @param string $ns_id The id of the namespace + * + * @return bool true if a mail was sent + * @author Adrian Lang <lang@cosmocode.de> + * + */ + protected function sendList($subscriber_mail, $ids, $ns_id) + { + if (count($ids) === 0) { + return false; + } + + $tlist = ''; + $hlist = '<ul>'; + foreach ($ids as $id) { + $link = wl($id, [], true); + $tlist .= '* ' . $link . NL; + $hlist .= '<li><a href="' . $link . '">' . hsc($id) . '</a></li>' . NL; + } + $hlist .= '</ul>'; + + $id = prettyprint_id($ns_id); + $trep = [ + 'DIFF' => rtrim($tlist), + 'PAGE' => $id, + 'SUBSCRIBE' => wl($id, ['do' => 'subscribe'], true, '&'), + ]; + $hrep = [ + 'DIFF' => $hlist, + ]; + + return $this->send( + $subscriber_mail, + 'subscribe_list', + $ns_id, + 'subscr_list', + $trep, + $hrep + ); + } +} diff --git a/inc/Subscriptions/MediaSubscriptionSender.php b/inc/Subscriptions/MediaSubscriptionSender.php new file mode 100644 index 000000000..5a0f86ed6 --- /dev/null +++ b/inc/Subscriptions/MediaSubscriptionSender.php @@ -0,0 +1,46 @@ +<?php + + +namespace dokuwiki\Subscriptions; + + +class MediaSubscriptionSender extends SubscriptionSender +{ + + /** + * Send the diff for some media change + * + * @fixme this should embed thumbnails of images in HTML version + * + * @param string $subscriber_mail The target mail address + * @param string $template Mail template ('uploadmail', ...) + * @param string $id Media file for which the notification is + * @param int|bool $rev Old revision if any + */ + public function sendMediaDiff($subscriber_mail, $template, $id, $rev = false) + { + global $conf; + + $file = mediaFN($id); + list($mime, /* $ext */) = mimetype($id); + + $trep = [ + 'MIME' => $mime, + 'MEDIA' => ml($id, '', true, '&', true), + 'SIZE' => filesize_h(filesize($file)), + ]; + + if ($rev && $conf['mediarevisions']) { + $trep['OLD'] = ml($id, "rev=$rev", true, '&', true); + } else { + $trep['OLD'] = '---'; + } + + $headers = ['Message-Id' => $this->getMessageID($id, @filemtime($file))]; + if ($rev) { + $headers['In-Reply-To'] = $this->getMessageID($id, $rev); + } + + $this->send($subscriber_mail, 'upload', $id, $template, $trep, null, $headers); + } +} diff --git a/inc/Subscriptions/PageSubscriptionSender.php b/inc/Subscriptions/PageSubscriptionSender.php new file mode 100644 index 000000000..8fca582a4 --- /dev/null +++ b/inc/Subscriptions/PageSubscriptionSender.php @@ -0,0 +1,87 @@ +<?php + + +namespace dokuwiki\Subscriptions; + + +use Diff; +use InlineDiffFormatter; +use UnifiedDiffFormatter; + +class PageSubscriptionSender extends SubscriptionSender +{ + + /** + * Send the diff for some page change + * + * @param string $subscriber_mail The target mail address + * @param string $template Mail template ('subscr_digest', 'subscr_single', 'mailtext', ...) + * @param string $id Page for which the notification is + * @param int|null $rev Old revision if any + * @param string $summary Change summary if any + * + * @return bool true if successfully sent + */ + public function sendPageDiff($subscriber_mail, $template, $id, $rev = null, $summary = '') + { + global $DIFF_INLINESTYLES; + + // prepare replacements (keys not set in hrep will be taken from trep) + $trep = [ + 'PAGE' => $id, + 'NEWPAGE' => wl($id, '', true, '&'), + 'SUMMARY' => $summary, + 'SUBSCRIBE' => wl($id, ['do' => 'subscribe'], true, '&'), + ]; + $hrep = []; + + if ($rev) { + $subject = 'changed'; + $trep['OLDPAGE'] = wl($id, "rev=$rev", true, '&'); + + $old_content = rawWiki($id, $rev); + $new_content = rawWiki($id); + + $df = new Diff( + explode("\n", $old_content), + explode("\n", $new_content) + ); + $dformat = new UnifiedDiffFormatter(); + $tdiff = $dformat->format($df); + + $DIFF_INLINESTYLES = true; + $df = new Diff( + explode("\n", $old_content), + explode("\n", $new_content) + ); + $dformat = new InlineDiffFormatter(); + $hdiff = $dformat->format($df); + $hdiff = '<table>' . $hdiff . '</table>'; + $DIFF_INLINESTYLES = false; + } else { + $subject = 'newpage'; + $trep['OLDPAGE'] = '---'; + $tdiff = rawWiki($id); + $hdiff = nl2br(hsc($tdiff)); + } + + $trep['DIFF'] = $tdiff; + $hrep['DIFF'] = $hdiff; + + $headers = ['Message-Id' => $this->getMessageID($id)]; + if ($rev) { + $headers['In-Reply-To'] = $this->getMessageID($id, $rev); + } + + return $this->send( + $subscriber_mail, + $subject, + $id, + $template, + $trep, + $hrep, + $headers + ); + } + +} diff --git a/inc/Subscriptions/RegistrationSubscriptionSender.php b/inc/Subscriptions/RegistrationSubscriptionSender.php new file mode 100644 index 000000000..bd4887599 --- /dev/null +++ b/inc/Subscriptions/RegistrationSubscriptionSender.php @@ -0,0 +1,40 @@ +<?php + +namespace dokuwiki\Subscriptions; + +class RegistrationSubscriptionSender extends SubscriptionSender +{ + + /** + * Send a notify mail on new registration + * + * @param string $login login name of the new user + * @param string $fullname full name of the new user + * @param string $email email address of the new user + * + * @return bool true if a mail was sent + * @author Andreas Gohr <andi@splitbrain.org> + * + */ + public function sendRegister($login, $fullname, $email) + { + global $conf; + if (empty($conf['registernotify'])) { + return false; + } + + $trep = [ + 'NEWUSER' => $login, + 'NEWNAME' => $fullname, + 'NEWEMAIL' => $email, + ]; + + return $this->send( + $conf['registernotify'], + 'new_user', + $login, + 'registermail', + $trep + ); + } +} diff --git a/inc/Subscriptions/SubscriberManager.php b/inc/Subscriptions/SubscriberManager.php new file mode 100644 index 000000000..138176fe9 --- /dev/null +++ b/inc/Subscriptions/SubscriberManager.php @@ -0,0 +1,285 @@ +<?php + +namespace dokuwiki\Subscriptions; + +use dokuwiki\Input\Input; +use DokuWiki_Auth_Plugin; +use Exception; + +class SubscriberManager +{ + + /** + * Check if subscription system is enabled + * + * @return bool + */ + public function isenabled() + { + return actionOK('subscribe'); + } + + /** + * Adds a new subscription for the given page or namespace + * + * This will automatically overwrite any existent subscription for the given user on this + * *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces. + * + * @throws Exception when user or style is empty + * + * @param string $id The target page or namespace, specified by id; Namespaces + * are identified by appending a colon. + * @param string $user + * @param string $style + * @param string $data + * + * @return bool + */ + public function add($id, $user, $style, $data = '') + { + if (!$this->isenabled()) { + return false; + } + + // delete any existing subscription + $this->remove($id, $user); + + $user = auth_nameencode(trim($user)); + $style = trim($style); + $data = trim($data); + + if (!$user) { + throw new Exception('no subscription user given'); + } + if (!$style) { + throw new Exception('no subscription style given'); + } + if (!$data) { + $data = time(); + } //always add current time for new subscriptions + + $line = "$user $style $data\n"; + $file = $this->file($id); + return io_saveFile($file, $line, true); + } + + + /** + * Removes a subscription for the given page or namespace + * + * This removes all subscriptions matching the given criteria on the given page or + * namespace. It will *not* modify any subscriptions that may exist in higher + * namespaces. + * + * @param string $id The target object’s (namespace or page) id + * @param string|array $user + * @param string|array $style + * @param string|array $data + * + * @return bool + */ + public function remove($id, $user = null, $style = null, $data = null) + { + if (!$this->isenabled()) { + return false; + } + + $file = $this->file($id); + if (!file_exists($file)) { + return true; + } + + $regexBuilder = new SubscriberRegexBuilder(); + $re = $regexBuilder->buildRegex($user, $style, $data); + return io_deleteFromFile($file, $re, true); + } + + /** + * Get data for $INFO['subscribed'] + * + * $INFO['subscribed'] is either false if no subscription for the current page + * and user is in effect. Else it contains an array of arrays with the fields + * “target”, “style”, and optionally “data”. + * + * @author Adrian Lang <lang@cosmocode.de> + * + * @param string $id Page ID, defaults to global $ID + * @param string $user User, defaults to $_SERVER['REMOTE_USER'] + * + * @return array|false + */ + public function userSubscription($id = '', $user = '') + { + if (!$this->isenabled()) { + return false; + } + + global $ID; + /** @var Input $INPUT */ + global $INPUT; + if (!$id) { + $id = $ID; + } + if (!$user) { + $user = $INPUT->server->str('REMOTE_USER'); + } + + $subs = $this->subscribers($id, $user); + if (!count($subs)) { + return false; + } + + $result = []; + foreach ($subs as $target => $info) { + $result[] = [ + 'target' => $target, + 'style' => $info[$user][0], + 'data' => $info[$user][1], + ]; + } + + return $result; + } + + /** + * Recursively search for matching subscriptions + * + * This function searches all relevant subscription files for a page or + * namespace. + * + * @author Adrian Lang <lang@cosmocode.de> + * + * @param string $page The target object’s (namespace or page) id + * @param string|array $user + * @param string|array $style + * @param string|array $data + * + * @return array + */ + public function subscribers($page, $user = null, $style = null, $data = null) + { + if (!$this->isenabled()) { + return []; + } + + // Construct list of files which may contain relevant subscriptions. + $files = [':' => $this->file(':')]; + do { + $files[$page] = $this->file($page); + $page = getNS(rtrim($page, ':')) . ':'; + } while ($page !== ':'); + + $regexBuilder = new SubscriberRegexBuilder(); + $re = $regexBuilder->buildRegex($user, $style, $data); + + // Handle files. + $result = []; + foreach ($files as $target => $file) { + if (!file_exists($file)) { + continue; + } + + $lines = file($file); + foreach ($lines as $line) { + // fix old style subscription files + if (strpos($line, ' ') === false) { + $line = trim($line) . " every\n"; + } + + // check for matching entries + if (!preg_match($re, $line, $m)) { + continue; + } + + $u = rawurldecode($m[1]); // decode the user name + if (!isset($result[$target])) { + $result[$target] = []; + } + $result[$target][$u] = [$m[2], $m[3]]; // add to result + } + } + return array_reverse($result); + } + + /** + * Default callback for COMMON_NOTIFY_ADDRESSLIST + * + * Aggregates all email addresses of user who have subscribed the given page with 'every' style + * + * @author Adrian Lang <lang@cosmocode.de> + * @author Steven Danz <steven-danz@kc.rr.com> + * + * @todo move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead, + * use an array for the addresses within it + * + * @param array &$data Containing the entries: + * - $id (the page id), + * - $self (whether the author should be notified, + * - $addresslist (current email address list) + * - $replacements (array of additional string substitutions, @KEY@ to be replaced by value) + */ + public function notifyAddresses(&$data) + { + if (!$this->isenabled()) { + return; + } + + /** @var DokuWiki_Auth_Plugin $auth */ + global $auth; + global $conf; + /** @var \Input $INPUT */ + global $INPUT; + + $id = $data['id']; + $self = $data['self']; + $addresslist = $data['addresslist']; + + $subscriptions = $this->subscribers($id, null, 'every'); + + $result = []; + foreach ($subscriptions as $target => $users) { + foreach ($users as $user => $info) { + $userinfo = $auth->getUserData($user); + if ($userinfo === false) { + continue; + } + if (!$userinfo['mail']) { + continue; + } + if (!$self && $user == $INPUT->server->str('REMOTE_USER')) { + continue; + } //skip our own changes + + $level = auth_aclcheck($id, $user, $userinfo['grps']); + if ($level >= AUTH_READ) { + if (strcasecmp($userinfo['mail'], $conf['notify']) != 0) { //skip user who get notified elsewhere + $result[$user] = $userinfo['mail']; + } + } + } + } + $data['addresslist'] = trim($addresslist . ',' . implode(',', $result), ','); + } + + /** + * Return the subscription meta file for the given ID + * + * @author Adrian Lang <lang@cosmocode.de> + * + * @param string $id The target page or namespace, specified by id; Namespaces + * are identified by appending a colon. + * + * @return string + */ + protected function file($id) + { + $meta_fname = '.mlist'; + if ((substr($id, -1, 1) === ':')) { + $meta_froot = getNS($id); + $meta_fname = '/' . $meta_fname; + } else { + $meta_froot = $id; + } + return metaFN((string)$meta_froot, $meta_fname); + } +} diff --git a/inc/Subscriptions/SubscriberRegexBuilder.php b/inc/Subscriptions/SubscriberRegexBuilder.php new file mode 100644 index 000000000..959702aac --- /dev/null +++ b/inc/Subscriptions/SubscriberRegexBuilder.php @@ -0,0 +1,70 @@ +<?php + +namespace dokuwiki\Subscriptions; + +use Exception; + +class SubscriberRegexBuilder +{ + + /** + * Construct a regular expression for parsing a subscription definition line + * + * @param string|array $user + * @param string|array $style + * @param string|array $data + * + * @return string complete regexp including delimiters + * @throws Exception when no data is passed + * @author Andreas Gohr <andi@splitbrain.org> + * + */ + public function buildRegex($user = null, $style = null, $data = null) + { + // always work with arrays + $user = (array)$user; + $style = (array)$style; + $data = (array)$data; + + // clean + $user = array_filter(array_map('trim', $user)); + $style = array_filter(array_map('trim', $style)); + $data = array_filter(array_map('trim', $data)); + + // user names are encoded + $user = array_map('auth_nameencode', $user); + + // quote + $user = array_map('preg_quote_cb', $user); + $style = array_map('preg_quote_cb', $style); + $data = array_map('preg_quote_cb', $data); + + // join + $user = join('|', $user); + $style = join('|', $style); + $data = join('|', $data); + + // any data at all? + if ($user . $style . $data === '') { + throw new Exception('no data passed'); + } + + // replace empty values, set which ones are optional + $sopt = ''; + $dopt = ''; + if ($user === '') { + $user = '\S+'; + } + if ($style === '') { + $style = '\S+'; + $sopt = '?'; + } + if ($data === '') { + $data = '\S+'; + $dopt = '?'; + } + + // assemble + return "/^($user)(?:\\s+($style))$sopt(?:\\s+($data))$dopt$/"; + } +} diff --git a/inc/Subscriptions/SubscriptionSender.php b/inc/Subscriptions/SubscriptionSender.php new file mode 100644 index 000000000..afc05bfc0 --- /dev/null +++ b/inc/Subscriptions/SubscriptionSender.php @@ -0,0 +1,86 @@ +<?php + +namespace dokuwiki\Subscriptions; + +use Mailer; + +abstract class SubscriptionSender +{ + protected $mailer; + + public function __construct(Mailer $mailer = null) + { + if ($mailer === null) { + $mailer = new Mailer(); + } + $this->mailer = $mailer; + } + + /** + * Get a valid message id for a certain $id and revision (or the current revision) + * + * @param string $id The id of the page (or media file) the message id should be for + * @param string $rev The revision of the page, set to the current revision of the page $id if not set + * + * @return string + */ + protected function getMessageID($id, $rev = null) + { + static $listid = null; + if (is_null($listid)) { + $server = parse_url(DOKU_URL, PHP_URL_HOST); + $listid = join('.', array_reverse(explode('/', DOKU_BASE))) . $server; + $listid = urlencode($listid); + $listid = strtolower(trim($listid, '.')); + } + + if (is_null($rev)) { + $rev = @filemtime(wikiFN($id)); + } + + return "<$id?rev=$rev@$listid>"; + } + + /** + * Helper function for sending a mail + * + * @param string $subscriber_mail The target mail address + * @param string $subject The lang id of the mail subject (without the + * prefix “mail_”) + * @param string $context The context of this mail, eg. page or namespace id + * @param string $template The name of the mail template + * @param array $trep Predefined parameters used to parse the + * template (in text format) + * @param array $hrep Predefined parameters used to parse the + * template (in HTML format), null to default to $trep + * @param array $headers Additional mail headers in the form 'name' => 'value' + * + * @return bool + * @author Adrian Lang <lang@cosmocode.de> + * + */ + protected function send($subscriber_mail, $subject, $context, $template, $trep, $hrep = null, $headers = []) + { + global $lang; + global $conf; + + $text = rawLocale($template); + $subject = $lang['mail_' . $subject] . ' ' . $context; + $mail = $this->mailer; + $mail->bcc($subscriber_mail); + $mail->subject($subject); + $mail->setBody($text, $trep, $hrep); + if (in_array($template, ['subscr_list', 'subscr_digest'])) { + $mail->from($conf['mailfromnobody']); + } + if (isset($trep['SUBSCRIBE'])) { + $mail->setHeader('List-Unsubscribe', '<' . $trep['SUBSCRIBE'] . '>', false); + } + + foreach ($headers as $header => $value) { + $mail->setHeader($header, $value); + } + + return $mail->send(); + } +} diff --git a/inc/TaskRunner.php b/inc/TaskRunner.php index a054ddfd0..96d32c9e7 100644 --- a/inc/TaskRunner.php +++ b/inc/TaskRunner.php @@ -2,8 +2,9 @@ namespace dokuwiki; -use Doku_Event; -use Sitemapper; +use dokuwiki\Extension\Event; +use dokuwiki\Sitemap\Mapper; +use dokuwiki\Subscriptions\BulkSubscriptionSender; use Subscription; /** @@ -44,7 +45,7 @@ class TaskRunner // run one of the jobs $tmp = []; // No event data - $evt = new Doku_Event('INDEXER_TASKS_RUN', $tmp); + $evt = new Event('INDEXER_TASKS_RUN', $tmp); if ($evt->advise_before()) { $this->runIndexer() or $this->runSitemapper() or @@ -122,12 +123,15 @@ class TaskRunner for ($i = 0; $i < count($lines); $i++) { $log = parseChangelogLine($lines[$i]); if ($log === false) { - continue; - } // discard junk + continue; // discard junk + } + if ($log['date'] < $trim_time) { - $old_lines[$log['date'] . ".$i"] = $lines[$i]; // keep old lines for now (append .$i to prevent key collisions) + // keep old lines for now (append .$i to prevent key collisions) + $old_lines[$log['date'] . ".$i"] = $lines[$i]; } else { - $out_lines[$log['date'] . ".$i"] = $lines[$i]; // definitely keep these lines + // definitely keep these lines + $out_lines[$log['date'] . ".$i"] = $lines[$i]; } } @@ -153,7 +157,7 @@ class TaskRunner 'trimmedChangelogLines' => $out_lines, 'removedChangelogLines' => $extra > 0 ? array_slice($old_lines, 0, -$extra) : $old_lines, ]; - trigger_event('TASK_RECENTCHANGES_TRIM', $eventData); + Event::createAndTrigger('TASK_RECENTCHANGES_TRIM', $eventData); $out_lines = $eventData['trimmedChangelogLines']; // save trimmed changelog @@ -185,7 +189,6 @@ class TaskRunner protected function runIndexer() { global $ID; - global $conf; print 'runIndexer(): started' . NL; if ((string) $ID === '') { @@ -208,7 +211,7 @@ class TaskRunner protected function runSitemapper() { print 'runSitemapper(): started' . NL; - $result = Sitemapper::generate() && Sitemapper::pingSearchEngines(); + $result = Mapper::generate() && Mapper::pingSearchEngines(); print 'runSitemapper(): finished' . NL; return $result; } @@ -221,7 +224,6 @@ class TaskRunner */ protected function sendDigest() { - global $conf; global $ID; echo 'sendDigest(): started' . NL; @@ -229,8 +231,8 @@ class TaskRunner echo 'sendDigest(): disabled' . NL; return false; } - $sub = new Subscription(); - $sent = $sub->send_bulk($ID); + $sub = new BulkSubscriptionSender(); + $sent = $sub->sendBulk($ID); echo "sendDigest(): sent $sent mails" . NL; echo 'sendDigest(): finished' . NL; diff --git a/inc/Ui/Admin.php b/inc/Ui/Admin.php index 20416e137..fe319d414 100644 --- a/inc/Ui/Admin.php +++ b/inc/Ui/Admin.php @@ -77,11 +77,11 @@ class Admin extends Ui { protected function showSecurityCheck() { global $conf; if(substr($conf['savedir'], 0, 2) !== './') return; + $img = DOKU_URL . $conf['savedir'] . + '/dont-panic-if-you-see-this-in-your-logs-it-means-your-directory-permissions-are-correct.png'; echo '<a style="border:none; float:right;" href="http://www.dokuwiki.org/security#web_access_security"> - <img src="' . DOKU_URL . $conf['savedir'] . - '/dont-panic-if-you-see-this-in-your-logs-it-means-your-directory-permissions-are-correct.png" - alt="Your data directory seems to be protected properly." + <img src="' . $img . '" alt="Your data directory seems to be protected properly." onerror="this.parentNode.style.display=\'none\'" /></a>'; } @@ -119,8 +119,8 @@ class Admin extends Ui { $menu = ['admin' => [], 'manager' => [], 'other' => []]; foreach($pluginlist as $p) { - /** @var \DokuWiki_Admin_Plugin $obj */ - if (($obj = plugin_load('admin', $p)) === null) continue; + /** @var \dokuwiki\Extension\AdminPlugin $obj */ + if(($obj = plugin_load('admin', $p)) === null) continue; // check permissions if (!$obj->isAccessibleByCurrentUser()) continue; @@ -158,7 +158,7 @@ class Admin extends Ui { * @param array $b * @return int */ - protected function menuSort ($a, $b) { + protected function menuSort($a, $b) { $strcmp = strcasecmp($a['prompt'], $b['prompt']); if($strcmp != 0) return $strcmp; if($a['sort'] === $b['sort']) return 0; diff --git a/inc/Ui/Search.php b/inc/Ui/Search.php index 184fdb105..e4eef6706 100644 --- a/inc/Ui/Search.php +++ b/inc/Ui/Search.php @@ -2,7 +2,8 @@ namespace dokuwiki\Ui; -use \dokuwiki\Form\Form; +use dokuwiki\Extension\Event; +use dokuwiki\Form\Form; class Search extends Ui { @@ -86,7 +87,7 @@ class Search extends Ui $searchForm->addFieldsetClose(); - trigger_event('FORM_SEARCH_OUTPUT', $searchForm); + Event::createAndTrigger('FORM_SEARCH_OUTPUT', $searchForm); return $searchForm->toHTML(); } @@ -499,7 +500,7 @@ class Search extends Ui public function createPagenameFromQuery($parsedQuery) { $cleanedQuery = cleanID($parsedQuery['query']); // already strtolowered - if ($cleanedQuery === utf8_strtolower($parsedQuery['query'])) { + if ($cleanedQuery === \dokuwiki\Utf8\PhpString::strtolower($parsedQuery['query'])) { return ':' . $cleanedQuery; } $pagename = ''; @@ -538,7 +539,7 @@ class Search extends Ui 'listItemContent' => [$link], 'page' => $id, ]; - trigger_event('SEARCH_RESULT_PAGELOOKUP', $eventData); + Event::createAndTrigger('SEARCH_RESULT_PAGELOOKUP', $eventData); $html .= '<li>' . implode('', $eventData['listItemContent']) . '</li>'; } $html .= '</ul> '; @@ -587,7 +588,9 @@ class Search extends Ui $resultBody = []; $mtime = filemtime(wikiFN($id)); $lastMod = '<span class="lastmod">' . $lang['lastmod'] . '</span> '; - $lastMod .= '<time datetime="' . date_iso8601($mtime) . '" title="'.dformat($mtime).'">' . dformat($mtime, '%f') . '</time>'; + $lastMod .= '<time datetime="' . date_iso8601($mtime) . '" title="' . dformat($mtime) . '">' . + dformat($mtime, '%f') . + '</time>'; $resultBody['meta'] = $lastMod; if ($cnt !== 0) { $num++; @@ -604,7 +607,7 @@ class Search extends Ui 'page' => $id, 'position' => $position, ]; - trigger_event('SEARCH_RESULT_FULLPAGE', $eventData); + Event::createAndTrigger('SEARCH_RESULT_FULLPAGE', $eventData); $html .= '<div class="search_fullpage_result">'; $html .= '<dt>' . implode(' ', $eventData['resultHeader']) . '</dt>'; foreach ($eventData['resultBody'] as $class => $htmlContent) { diff --git a/inc/Utf8/Asian.php b/inc/Utf8/Asian.php new file mode 100644 index 000000000..c7baa3029 --- /dev/null +++ b/inc/Utf8/Asian.php @@ -0,0 +1,99 @@ +<?php + +namespace dokuwiki\Utf8; + +/** + * Methods and constants to handle Asian "words" + * + * This uses a crude regexp to determine which parts of an Asian string should be treated as words. + * This is necessary because in some Asian languages a single unicode char represents a whole idea + * without spaces separating them. + */ +class Asian +{ + + /** + * This defines a non-capturing group for the use in regular expressions to match any asian character that + * needs to be treated as a word. Uses the Unicode-Ranges for Asian characters taken from + * http://en.wikipedia.org/wiki/Unicode_block + */ + const REGEXP = + '(?:' . + + '[\x{0E00}-\x{0E7F}]' . // Thai + + '|' . + + '[' . + '\x{2E80}-\x{3040}' . // CJK -> Hangul + '\x{309D}-\x{30A0}' . + '\x{30FD}-\x{31EF}\x{3200}-\x{D7AF}' . + '\x{F900}-\x{FAFF}' . // CJK Compatibility Ideographs + '\x{FE30}-\x{FE4F}' . // CJK Compatibility Forms + "\xF0\xA0\x80\x80-\xF0\xAA\x9B\x9F" . // CJK Extension B + "\xF0\xAA\x9C\x80-\xF0\xAB\x9C\xBF" . // CJK Extension C + "\xF0\xAB\x9D\x80-\xF0\xAB\xA0\x9F" . // CJK Extension D + "\xF0\xAF\xA0\x80-\xF0\xAF\xAB\xBF" . // CJK Compatibility Supplement + ']' . + + '|' . + + '[' . // Hiragana/Katakana (can be two characters) + '\x{3042}\x{3044}\x{3046}\x{3048}' . + '\x{304A}-\x{3062}\x{3064}-\x{3082}' . + '\x{3084}\x{3086}\x{3088}-\x{308D}' . + '\x{308F}-\x{3094}' . + '\x{30A2}\x{30A4}\x{30A6}\x{30A8}' . + '\x{30AA}-\x{30C2}\x{30C4}-\x{30E2}' . + '\x{30E4}\x{30E6}\x{30E8}-\x{30ED}' . + '\x{30EF}-\x{30F4}\x{30F7}-\x{30FA}' . + '][' . + '\x{3041}\x{3043}\x{3045}\x{3047}\x{3049}' . + '\x{3063}\x{3083}\x{3085}\x{3087}\x{308E}\x{3095}-\x{309C}' . + '\x{30A1}\x{30A3}\x{30A5}\x{30A7}\x{30A9}' . + '\x{30C3}\x{30E3}\x{30E5}\x{30E7}\x{30EE}\x{30F5}\x{30F6}\x{30FB}\x{30FC}' . + '\x{31F0}-\x{31FF}' . + ']?' . + ')'; + + + /** + * Check if the given term contains Asian word characters + * + * @param string $term + * @return bool + */ + public static function isAsianWords($term) + { + return (bool)preg_match('/' . self::REGEXP . '/u', $term); + } + + /** + * Surround all Asian words in the given text with the given separator + * + * @param string $text Original text containing asian words + * @param string $sep the separator to use + * @return string Text with separated asian words + */ + public static function separateAsianWords($text, $sep = ' ') + { + // handle asian chars as single words (may fail on older PHP version) + $asia = @preg_replace('/(' . self::REGEXP . ')/u', $sep . '\1' . $sep, $text); + if (!is_null($asia)) $text = $asia; // recover from regexp falure + + return $text; + } + + /** + * Split the given text into separate parts + * + * Each part is either a non-asian string, or a single asian word + * + * @param string $term + * @return string[] + */ + public static function splitAsianWords($term) + { + return preg_split('/(' . self::REGEXP . '+)/u', $term, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + } +} diff --git a/inc/Utf8/Clean.php b/inc/Utf8/Clean.php new file mode 100644 index 000000000..79c46810c --- /dev/null +++ b/inc/Utf8/Clean.php @@ -0,0 +1,204 @@ +<?php + +namespace dokuwiki\Utf8; + +/** + * Methods to assess and clean UTF-8 strings + */ +class Clean +{ + /** + * Checks if a string contains 7bit ASCII only + * + * @author Andreas Haerter <andreas.haerter@dev.mail-node.com> + * + * @param string $str + * @return bool + */ + public static function isASCII($str) + { + return (preg_match('/(?:[^\x00-\x7F])/', $str) !== 1); + } + + /** + * Tries to detect if a string is in Unicode encoding + * + * @author <bmorel@ssi.fr> + * @link http://php.net/manual/en/function.utf8-encode.php + * + * @param string $str + * @return bool + */ + public static function isUtf8($str) + { + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + $b = ord($str[$i]); + if ($b < 0x80) continue; # 0bbbbbbb + elseif (($b & 0xE0) === 0xC0) $n = 1; # 110bbbbb + elseif (($b & 0xF0) === 0xE0) $n = 2; # 1110bbbb + elseif (($b & 0xF8) === 0xF0) $n = 3; # 11110bbb + elseif (($b & 0xFC) === 0xF8) $n = 4; # 111110bb + elseif (($b & 0xFE) === 0xFC) $n = 5; # 1111110b + else return false; # Does not match any model + + for ($j = 0; $j < $n; $j++) { # n bytes matching 10bbbbbb follow ? + if ((++$i === $len) || ((ord($str[$i]) & 0xC0) !== 0x80)) + return false; + } + } + return true; + } + + /** + * Strips all high byte chars + * + * Returns a pure ASCII7 string + * + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param string $str + * @return string + */ + public static function strip($str) + { + $ascii = ''; + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + if (ord($str{$i}) < 128) { + $ascii .= $str{$i}; + } + } + return $ascii; + } + + /** + * Removes special characters (nonalphanumeric) from a UTF-8 string + * + * This function adds the controlchars 0x00 to 0x19 to the array of + * stripped chars (they are not included in $UTF8_SPECIAL_CHARS) + * + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param string $string The UTF8 string to strip of special chars + * @param string $repl Replace special with this string + * @param string $additional Additional chars to strip (used in regexp char class) + * @return string + */ + public static function stripspecials($string, $repl = '', $additional = '') + { + static $specials = null; + if ($specials === null) { + $specials = preg_quote(Table::specialChars(), '/'); + } + + return preg_replace('/[' . $additional . '\x00-\x19' . $specials . ']/u', $repl, $string); + } + + /** + * Replace bad bytes with an alternative character + * + * ASCII character is recommended for replacement char + * + * PCRE Pattern to locate bad bytes in a UTF-8 string + * Comes from W3 FAQ: Multilingual Forms + * Note: modified to include full ASCII range including control chars + * + * @author Harry Fuecks <hfuecks@gmail.com> + * @see http://www.w3.org/International/questions/qa-forms-utf-8 + * + * @param string $str to search + * @param string $replace to replace bad bytes with (defaults to '?') - use ASCII + * @return string + */ + public static function replaceBadBytes($str, $replace = '') + { + $UTF8_BAD = + '([\x00-\x7F]' . # ASCII (including control chars) + '|[\xC2-\xDF][\x80-\xBF]' . # non-overlong 2-byte + '|\xE0[\xA0-\xBF][\x80-\xBF]' . # excluding overlongs + '|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}' . # straight 3-byte + '|\xED[\x80-\x9F][\x80-\xBF]' . # excluding surrogates + '|\xF0[\x90-\xBF][\x80-\xBF]{2}' . # planes 1-3 + '|[\xF1-\xF3][\x80-\xBF]{3}' . # planes 4-15 + '|\xF4[\x80-\x8F][\x80-\xBF]{2}' . # plane 16 + '|(.{1}))'; # invalid byte + ob_start(); + while (preg_match('/' . $UTF8_BAD . '/S', $str, $matches)) { + if (!isset($matches[2])) { + echo $matches[0]; + } else { + echo $replace; + } + $str = substr($str, strlen($matches[0])); + } + return ob_get_clean(); + } + + + /** + * Replace accented UTF-8 characters by unaccented ASCII-7 equivalents + * + * Use the optional parameter to just deaccent lower ($case = -1) or upper ($case = 1) + * letters. Default is to deaccent both cases ($case = 0) + * + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param string $string + * @param int $case + * @return string + */ + public static function deaccent($string, $case = 0) + { + if ($case <= 0) { + $string = strtr($string, Table::lowerAccents()); + } + if ($case >= 0) { + $string = strtr($string, Table::upperAccents()); + } + return $string; + } + + /** + * Romanize a non-latin string + * + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param string $string + * @return string + */ + public static function romanize($string) + { + if (self::isASCII($string)) return $string; //nothing to do + + return strtr($string, Table::romanization()); + } + + /** + * adjust a byte index into a utf8 string to a utf8 character boundary + * + * @author chris smith <chris@jalakai.co.uk> + * + * @param string $str utf8 character string + * @param int $i byte index into $str + * @param bool $next direction to search for boundary, false = up (current character) true = down (next character) + * @return int byte index into $str now pointing to a utf8 character boundary + */ + public static function correctIdx($str, $i, $next = false) + { + + if ($i <= 0) return 0; + + $limit = strlen($str); + if ($i >= $limit) return $limit; + + if ($next) { + while (($i < $limit) && ((ord($str[$i]) & 0xC0) === 0x80)) $i++; + } else { + while ($i && ((ord($str[$i]) & 0xC0) === 0x80)) $i--; + } + + return $i; + } + +} diff --git a/inc/Utf8/Conversion.php b/inc/Utf8/Conversion.php new file mode 100644 index 000000000..fad9cd0b1 --- /dev/null +++ b/inc/Utf8/Conversion.php @@ -0,0 +1,162 @@ +<?php + +namespace dokuwiki\Utf8; + +/** + * Methods to convert from and to UTF-8 strings + */ +class Conversion +{ + + /** + * Encodes UTF-8 characters to HTML entities + * + * @author Tom N Harris <tnharris@whoopdedo.org> + * @author <vpribish at shopping dot com> + * @link http://php.net/manual/en/function.utf8-decode.php + * + * @param string $str + * @param bool $all Encode non-utf8 char to HTML as well + * @return string + */ + public static function toHtml($str, $all = false) + { + $ret = ''; + foreach (Unicode::fromUtf8($str) as $cp) { + if ($cp < 0x80 && !$all) { + $ret .= chr($cp); + } elseif ($cp < 0x100) { + $ret .= "&#$cp;"; + } else { + $ret .= '&#x' . dechex($cp) . ';'; + } + } + return $ret; + } + + /** + * Decodes HTML entities to UTF-8 characters + * + * Convert any &#..; entity to a codepoint, + * The entities flag defaults to only decoding numeric entities. + * Pass HTML_ENTITIES and named entities, including & < etc. + * are handled as well. Avoids the problem that would occur if you + * had to decode "&#38;&amp;#38;" + * + * unhtmlspecialchars(\dokuwiki\Utf8\Conversion::fromHtml($s)) -> "&&" + * \dokuwiki\Utf8\Conversion::fromHtml(unhtmlspecialchars($s)) -> "&&#38;" + * what it should be -> "&&#38;" + * + * @author Tom N Harris <tnharris@whoopdedo.org> + * + * @param string $str UTF-8 encoded string + * @param boolean $entities decode name entities in addtition to numeric ones + * @return string UTF-8 encoded string with numeric (and named) entities replaced. + */ + public static function fromHtml($str, $entities = false) + { + if (!$entities) { + return preg_replace_callback( + '/(&#([Xx])?([0-9A-Za-z]+);)/m', + [__CLASS__, 'decodeNumericEntity'], + $str + ); + } + + return preg_replace_callback( + '/&(#)?([Xx])?([0-9A-Za-z]+);/m', + [__CLASS__, 'decodeAnyEntity'], + $str + ); + } + + /** + * Decodes any HTML entity to it's correct UTF-8 char equivalent + * + * @param string $ent An entity + * @return string + */ + protected static function decodeAnyEntity($ent) + { + // create the named entity lookup table + static $table = null; + if ($table === null) { + $table = get_html_translation_table(HTML_ENTITIES); + $table = array_flip($table); + $table = array_map( + static function ($c) { + return Unicode::toUtf8(array(ord($c))); + }, + $table + ); + } + + if ($ent[1] === '#') { + return self::decodeNumericEntity($ent); + } + + if (array_key_exists($ent[0], $table)) { + return $table[$ent[0]]; + } + + return $ent[0]; + } + + /** + * Decodes numeric HTML entities to their correct UTF-8 characters + * + * @param $ent string A numeric entity + * @return string|false + */ + protected static function decodeNumericEntity($ent) + { + switch ($ent[2]) { + case 'X': + case 'x': + $cp = hexdec($ent[3]); + break; + default: + $cp = intval($ent[3]); + break; + } + return Unicode::toUtf8(array($cp)); + } + + /** + * UTF-8 to UTF-16BE conversion. + * + * Maybe really UCS-2 without mb_string due to utf8_to_unicode limits + * + * @param string $str + * @param bool $bom + * @return string + */ + public static function toUtf16be($str, $bom = false) + { + $out = $bom ? "\xFE\xFF" : ''; + if (UTF8_MBSTRING) { + return $out . mb_convert_encoding($str, 'UTF-16BE', 'UTF-8'); + } + + $uni = Unicode::fromUtf8($str); + foreach ($uni as $cp) { + $out .= pack('n', $cp); + } + return $out; + } + + /** + * UTF-8 to UTF-16BE conversion. + * + * Maybe really UCS-2 without mb_string due to utf8_to_unicode limits + * + * @param string $str + * @return false|string + */ + public static function fromUtf16be($str) + { + $uni = unpack('n*', $str); + return Unicode::toUtf8($uni); + } + +} diff --git a/inc/Utf8/PhpString.php b/inc/Utf8/PhpString.php new file mode 100644 index 000000000..5bcd601a4 --- /dev/null +++ b/inc/Utf8/PhpString.php @@ -0,0 +1,383 @@ +<?php + +namespace dokuwiki\Utf8; + +/** + * UTF-8 aware equivalents to PHP's string functions + */ +class PhpString +{ + + /** + * A locale independent basename() implementation + * + * works around a bug in PHP's basename() implementation + * + * @param string $path A path + * @param string $suffix If the name component ends in suffix this will also be cut off + * @return string + * @link https://bugs.php.net/bug.php?id=37738 + * + * @see basename() + */ + public static function basename($path, $suffix = '') + { + $path = trim($path, '\\/'); + $rpos = max(strrpos($path, '/'), strrpos($path, '\\')); + if ($rpos) { + $path = substr($path, $rpos + 1); + } + + $suflen = strlen($suffix); + if ($suflen && (substr($path, -$suflen) === $suffix)) { + $path = substr($path, 0, -$suflen); + } + + return $path; + } + + /** + * Unicode aware replacement for strlen() + * + * utf8_decode() converts characters that are not in ISO-8859-1 + * to '?', which, for the purpose of counting, is alright - It's + * even faster than mb_strlen. + * + * @param string $string + * @return int + * @see utf8_decode() + * + * @author <chernyshevsky at hotmail dot com> + * @see strlen() + */ + public static function strlen($string) + { + if (function_exists('utf8_decode')) { + return strlen(utf8_decode($string)); + } + + if (UTF8_MBSTRING) { + return mb_strlen($string, 'UTF-8'); + } + + if (function_exists('iconv_strlen')) { + return iconv_strlen($string, 'UTF-8'); + } + + return strlen($string); + } + + /** + * UTF-8 aware alternative to substr + * + * Return part of a string given character offset (and optionally length) + * + * @param string $str + * @param int $offset number of UTF-8 characters offset (from left) + * @param int $length (optional) length in UTF-8 characters from offset + * @return string + * @author Harry Fuecks <hfuecks@gmail.com> + * @author Chris Smith <chris@jalakai.co.uk> + * + */ + public static function substr($str, $offset, $length = null) + { + if (UTF8_MBSTRING) { + if ($length === null) { + return mb_substr($str, $offset); + } + + return mb_substr($str, $offset, $length); + } + + /* + * Notes: + * + * no mb string support, so we'll use pcre regex's with 'u' flag + * pcre only supports repetitions of less than 65536, in order to accept up to MAXINT values for + * offset and length, we'll repeat a group of 65535 characters when needed (ok, up to MAXINT-65536) + * + * substr documentation states false can be returned in some cases (e.g. offset > string length) + * mb_substr never returns false, it will return an empty string instead. + * + * calculating the number of characters in the string is a relatively expensive operation, so + * we only carry it out when necessary. It isn't necessary for +ve offsets and no specified length + */ + + // cast parameters to appropriate types to avoid multiple notices/warnings + $str = (string)$str; // generates E_NOTICE for PHP4 objects, but not PHP5 objects + $offset = (int)$offset; + if ($length !== null) $length = (int)$length; + + // handle trivial cases + if ($length === 0) return ''; + if ($offset < 0 && $length < 0 && $length < $offset) return ''; + + $offset_pattern = ''; + $length_pattern = ''; + + // normalise -ve offsets (we could use a tail anchored pattern, but they are horribly slow!) + if ($offset < 0) { + $strlen = self::strlen($str); // see notes + $offset = $strlen + $offset; + if ($offset < 0) $offset = 0; + } + + // establish a pattern for offset, a non-captured group equal in length to offset + if ($offset > 0) { + $Ox = (int)($offset / 65535); + $Oy = $offset % 65535; + + if ($Ox) $offset_pattern = '(?:.{65535}){' . $Ox . '}'; + $offset_pattern = '^(?:' . $offset_pattern . '.{' . $Oy . '})'; + } else { + $offset_pattern = '^'; // offset == 0; just anchor the pattern + } + + // establish a pattern for length + if ($length === null) { + $length_pattern = '(.*)$'; // the rest of the string + } else { + + if (!isset($strlen)) $strlen = self::strlen($str); // see notes + if ($offset > $strlen) return ''; // another trivial case + + if ($length > 0) { + + // reduce any length that would go past the end of the string + $length = min($strlen - $offset, $length); + + $Lx = (int)($length / 65535); + $Ly = $length % 65535; + + // +ve length requires ... a captured group of length characters + if ($Lx) $length_pattern = '(?:.{65535}){' . $Lx . '}'; + $length_pattern = '(' . $length_pattern . '.{' . $Ly . '})'; + + } else if ($length < 0) { + + if ($length < ($offset - $strlen)) return ''; + + $Lx = (int)((-$length) / 65535); + $Ly = (-$length) % 65535; + + // -ve length requires ... capture everything except a group of -length characters + // anchored at the tail-end of the string + if ($Lx) $length_pattern = '(?:.{65535}){' . $Lx . '}'; + $length_pattern = '(.*)(?:' . $length_pattern . '.{' . $Ly . '})$'; + } + } + + if (!preg_match('#' . $offset_pattern . $length_pattern . '#us', $str, $match)) return ''; + return $match[1]; + } + + // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + /** + * Unicode aware replacement for substr_replace() + * + * @param string $string input string + * @param string $replacement the replacement + * @param int $start the replacing will begin at the start'th offset into string. + * @param int $length If given and is positive, it represents the length of the portion of string which is + * to be replaced. If length is zero then this function will have the effect of inserting + * replacement into string at the given start offset. + * @return string + * @see substr_replace() + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + public static function substr_replace($string, $replacement, $start, $length = 0) + { + $ret = ''; + if ($start > 0) $ret .= self::substr($string, 0, $start); + $ret .= $replacement; + $ret .= self::substr($string, $start + $length); + return $ret; + } + // phpcs:enable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + + /** + * Unicode aware replacement for ltrim() + * + * @param string $str + * @param string $charlist + * @return string + * @see ltrim() + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + public static function ltrim($str, $charlist = '') + { + if ($charlist === '') return ltrim($str); + + //quote charlist for use in a characterclass + $charlist = preg_replace('!([\\\\\\-\\]\\[/])!', '\\\${1}', $charlist); + + return preg_replace('/^[' . $charlist . ']+/u', '', $str); + } + + /** + * Unicode aware replacement for rtrim() + * + * @param string $str + * @param string $charlist + * @return string + * @see rtrim() + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + public static function rtrim($str, $charlist = '') + { + if ($charlist === '') return rtrim($str); + + //quote charlist for use in a characterclass + $charlist = preg_replace('!([\\\\\\-\\]\\[/])!', '\\\${1}', $charlist); + + return preg_replace('/[' . $charlist . ']+$/u', '', $str); + } + + /** + * Unicode aware replacement for trim() + * + * @param string $str + * @param string $charlist + * @return string + * @see trim() + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + public static function trim($str, $charlist = '') + { + if ($charlist === '') return trim($str); + + return self::ltrim(self::rtrim($str, $charlist), $charlist); + } + + /** + * This is a unicode aware replacement for strtolower() + * + * Uses mb_string extension if available + * + * @param string $string + * @return string + * @see \dokuwiki\Utf8\PhpString::strtoupper() + * + * @author Leo Feyer <leo@typolight.org> + * @see strtolower() + */ + public static function strtolower($string) + { + if (UTF8_MBSTRING) { + if (class_exists('Normalizer', $autoload = false)) { + return \Normalizer::normalize(mb_strtolower($string, 'utf-8')); + } + return (mb_strtolower($string, 'utf-8')); + } + return strtr($string, Table::upperCaseToLowerCase()); + } + + /** + * This is a unicode aware replacement for strtoupper() + * + * Uses mb_string extension if available + * + * @param string $string + * @return string + * @see \dokuwiki\Utf8\PhpString::strtoupper() + * + * @author Leo Feyer <leo@typolight.org> + * @see strtoupper() + */ + public static function strtoupper($string) + { + if (UTF8_MBSTRING) return mb_strtoupper($string, 'utf-8'); + + return strtr($string, Table::lowerCaseToUpperCase()); + } + + + /** + * UTF-8 aware alternative to ucfirst + * Make a string's first character uppercase + * + * @param string $str + * @return string with first character as upper case (if applicable) + * @author Harry Fuecks + * + */ + public static function ucfirst($str) + { + switch (self::strlen($str)) { + case 0: + return ''; + case 1: + return self::strtoupper($str); + default: + preg_match('/^(.{1})(.*)$/us', $str, $matches); + return self::strtoupper($matches[1]) . $matches[2]; + } + } + + /** + * UTF-8 aware alternative to ucwords + * Uppercase the first character of each word in a string + * + * @param string $str + * @return string with first char of each word uppercase + * @author Harry Fuecks + * @see http://php.net/ucwords + * + */ + public static function ucwords($str) + { + // Note: [\x0c\x09\x0b\x0a\x0d\x20] matches; + // form feeds, horizontal tabs, vertical tabs, linefeeds and carriage returns + // This corresponds to the definition of a "word" defined at http://php.net/ucwords + $pattern = '/(^|([\x0c\x09\x0b\x0a\x0d\x20]+))([^\x0c\x09\x0b\x0a\x0d\x20]{1})[^\x0c\x09\x0b\x0a\x0d\x20]*/u'; + + return preg_replace_callback( + $pattern, + function ($matches) { + $leadingws = $matches[2]; + $ucfirst = self::strtoupper($matches[3]); + $ucword = self::substr_replace(ltrim($matches[0]), $ucfirst, 0, 1); + return $leadingws . $ucword; + }, + $str + ); + } + + /** + * This is an Unicode aware replacement for strpos + * + * @param string $haystack + * @param string $needle + * @param integer $offset + * @return integer + * @author Leo Feyer <leo@typolight.org> + * @see strpos() + * + */ + public static function strpos($haystack, $needle, $offset = 0) + { + $comp = 0; + $length = null; + + while ($length === null || $length < $offset) { + $pos = strpos($haystack, $needle, $offset + $comp); + + if ($pos === false) + return false; + + $length = self::strlen(substr($haystack, 0, $pos)); + + if ($length < $offset) + $comp = $pos - $length; + } + + return $length; + } + + +} diff --git a/inc/Utf8/Table.php b/inc/Utf8/Table.php new file mode 100644 index 000000000..8683c9238 --- /dev/null +++ b/inc/Utf8/Table.php @@ -0,0 +1,93 @@ +<?php + +namespace dokuwiki\Utf8; + +/** + * Provides static access to the UTF-8 conversion tables + * + * Lazy-Loads tables on first access + */ +class Table +{ + + /** + * Get the upper to lower case conversion table + * + * @return array + */ + public static function upperCaseToLowerCase() + { + static $table = null; + if ($table === null) $table = include __DIR__ . '/tables/case.php'; + return $table; + } + + /** + * Get the lower to upper case conversion table + * + * @return array + */ + public static function lowerCaseToUpperCase() + { + static $table = null; + if ($table === null) { + $uclc = self::upperCaseToLowerCase(); + $table = array_flip($uclc); + } + return $table; + } + + /** + * Get the lower case accent table + * @return array + */ + public static function lowerAccents() + { + static $table = null; + if ($table === null) { + $table = include __DIR__ . '/tables/loweraccents.php'; + } + return $table; + } + + /** + * Get the lower case accent table + * @return array + */ + public static function upperAccents() + { + static $table = null; + if ($table === null) { + $table = include __DIR__ . '/tables/upperaccents.php'; + } + return $table; + } + + /** + * Get the romanization table + * @return array + */ + public static function romanization() + { + static $table = null; + if ($table === null) { + $table = include __DIR__ . '/tables/romanization.php'; + } + return $table; + } + + /** + * Get the special chars as a concatenated string + * @return string + */ + public static function specialChars() + { + static $string = null; + if ($string === null) { + $table = include __DIR__ . '/tables/specials.php'; + // FIXME should we cache this to file system? + $string = Unicode::toUtf8($table); + } + return $string; + } +} diff --git a/inc/Utf8/Unicode.php b/inc/Utf8/Unicode.php new file mode 100644 index 000000000..c706d716b --- /dev/null +++ b/inc/Utf8/Unicode.php @@ -0,0 +1,277 @@ +<?php + +namespace dokuwiki\Utf8; + +/** + * Convert between UTF-8 and a list of Unicode Code Points + */ +class Unicode +{ + + /** + * Takes an UTF-8 string and returns an array of ints representing the + * Unicode characters. Astral planes are supported ie. the ints in the + * output can be > 0xFFFF. Occurrances of the BOM are ignored. Surrogates + * are not allowed. + * + * If $strict is set to true the function returns false if the input + * string isn't a valid UTF-8 octet sequence and raises a PHP error at + * level E_USER_WARNING + * + * Note: this function has been modified slightly in this library to + * trigger errors on encountering bad bytes + * + * @author <hsivonen@iki.fi> + * @author Harry Fuecks <hfuecks@gmail.com> + * @see unicode_to_utf8 + * @link http://hsivonen.iki.fi/php-utf8/ + * @link http://sourceforge.net/projects/phputf8/ + * @todo break into less complex chunks + * @todo use exceptions instead of user errors + * + * @param string $str UTF-8 encoded string + * @param boolean $strict Check for invalid sequences? + * @return mixed array of unicode code points or false if UTF-8 invalid + */ + public static function fromUtf8($str, $strict = false) + { + $mState = 0; // cached expected number of octets after the current octet + // until the beginning of the next UTF8 character sequence + $mUcs4 = 0; // cached Unicode character + $mBytes = 1; // cached expected number of octets in the current sequence + + $out = array(); + + $len = strlen($str); + + for ($i = 0; $i < $len; $i++) { + + $in = ord($str{$i}); + + if ($mState === 0) { + + // When mState is zero we expect either a US-ASCII character or a + // multi-octet sequence. + if (0 === (0x80 & $in)) { + // US-ASCII, pass straight through. + $out[] = $in; + $mBytes = 1; + + } else if (0xC0 === (0xE0 & $in)) { + // First octet of 2 octet sequence + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 0x1F) << 6; + $mState = 1; + $mBytes = 2; + + } else if (0xE0 === (0xF0 & $in)) { + // First octet of 3 octet sequence + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 0x0F) << 12; + $mState = 2; + $mBytes = 3; + + } else if (0xF0 === (0xF8 & $in)) { + // First octet of 4 octet sequence + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 0x07) << 18; + $mState = 3; + $mBytes = 4; + + } else if (0xF8 === (0xFC & $in)) { + /* First octet of 5 octet sequence. + * + * This is illegal because the encoded codepoint must be either + * (a) not the shortest form or + * (b) outside the Unicode range of 0-0x10FFFF. + * Rather than trying to resynchronize, we will carry on until the end + * of the sequence and let the later error handling code catch it. + */ + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 0x03) << 24; + $mState = 4; + $mBytes = 5; + + } else if (0xFC === (0xFE & $in)) { + // First octet of 6 octet sequence, see comments for 5 octet sequence. + $mUcs4 = $in; + $mUcs4 = ($mUcs4 & 1) << 30; + $mState = 5; + $mBytes = 6; + + } elseif ($strict) { + /* Current octet is neither in the US-ASCII range nor a legal first + * octet of a multi-octet sequence. + */ + trigger_error( + 'utf8_to_unicode: Illegal sequence identifier ' . + 'in UTF-8 at byte ' . $i, + E_USER_WARNING + ); + return false; + + } + + } else { + + // When mState is non-zero, we expect a continuation of the multi-octet + // sequence + if (0x80 === (0xC0 & $in)) { + + // Legal continuation. + $shift = ($mState - 1) * 6; + $tmp = $in; + $tmp = ($tmp & 0x0000003F) << $shift; + $mUcs4 |= $tmp; + + /** + * End of the multi-octet sequence. mUcs4 now contains the final + * Unicode codepoint to be output + */ + if (0 === --$mState) { + + /* + * Check for illegal sequences and codepoints. + */ + // From Unicode 3.1, non-shortest form is illegal + if (((2 === $mBytes) && ($mUcs4 < 0x0080)) || + ((3 === $mBytes) && ($mUcs4 < 0x0800)) || + ((4 === $mBytes) && ($mUcs4 < 0x10000)) || + (4 < $mBytes) || + // From Unicode 3.2, surrogate characters are illegal + (($mUcs4 & 0xFFFFF800) === 0xD800) || + // Codepoints outside the Unicode range are illegal + ($mUcs4 > 0x10FFFF)) { + + if ($strict) { + trigger_error( + 'utf8_to_unicode: Illegal sequence or codepoint ' . + 'in UTF-8 at byte ' . $i, + E_USER_WARNING + ); + + return false; + } + + } + + if (0xFEFF !== $mUcs4) { + // BOM is legal but we don't want to output it + $out[] = $mUcs4; + } + + //initialize UTF8 cache + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + } + + } elseif ($strict) { + /** + *((0xC0 & (*in) != 0x80) && (mState != 0)) + * Incomplete multi-octet sequence. + */ + trigger_error( + 'utf8_to_unicode: Incomplete multi-octet ' . + ' sequence in UTF-8 at byte ' . $i, + E_USER_WARNING + ); + + return false; + } + } + } + return $out; + } + + /** + * Takes an array of ints representing the Unicode characters and returns + * a UTF-8 string. Astral planes are supported ie. the ints in the + * input can be > 0xFFFF. Occurrances of the BOM are ignored. Surrogates + * are not allowed. + * + * If $strict is set to true the function returns false if the input + * array contains ints that represent surrogates or are outside the + * Unicode range and raises a PHP error at level E_USER_WARNING + * + * Note: this function has been modified slightly in this library to use + * output buffering to concatenate the UTF-8 string (faster) as well as + * reference the array by it's keys + * + * @param array $arr of unicode code points representing a string + * @param boolean $strict Check for invalid sequences? + * @return string|false UTF-8 string or false if array contains invalid code points + * + * @author <hsivonen@iki.fi> + * @author Harry Fuecks <hfuecks@gmail.com> + * @see utf8_to_unicode + * @link http://hsivonen.iki.fi/php-utf8/ + * @link http://sourceforge.net/projects/phputf8/ + * @todo use exceptions instead of user errors + */ + public static function toUtf8($arr, $strict = false) + { + if (!is_array($arr)) return ''; + ob_start(); + + foreach (array_keys($arr) as $k) { + + if (($arr[$k] >= 0) && ($arr[$k] <= 0x007f)) { + # ASCII range (including control chars) + + echo chr($arr[$k]); + + } else if ($arr[$k] <= 0x07ff) { + # 2 byte sequence + + echo chr(0xc0 | ($arr[$k] >> 6)); + echo chr(0x80 | ($arr[$k] & 0x003f)); + + } else if ($arr[$k] == 0xFEFF) { + # Byte order mark (skip) + // nop -- zap the BOM + + } else if ($arr[$k] >= 0xD800 && $arr[$k] <= 0xDFFF) { + # Test for illegal surrogates + + // found a surrogate + if ($strict) { + trigger_error( + 'unicode_to_utf8: Illegal surrogate ' . + 'at index: ' . $k . ', value: ' . $arr[$k], + E_USER_WARNING + ); + return false; + } + + } else if ($arr[$k] <= 0xffff) { + # 3 byte sequence + + echo chr(0xe0 | ($arr[$k] >> 12)); + echo chr(0x80 | (($arr[$k] >> 6) & 0x003f)); + echo chr(0x80 | ($arr[$k] & 0x003f)); + + } else if ($arr[$k] <= 0x10ffff) { + # 4 byte sequence + + echo chr(0xf0 | ($arr[$k] >> 18)); + echo chr(0x80 | (($arr[$k] >> 12) & 0x3f)); + echo chr(0x80 | (($arr[$k] >> 6) & 0x3f)); + echo chr(0x80 | ($arr[$k] & 0x3f)); + + } elseif ($strict) { + + trigger_error( + 'unicode_to_utf8: Codepoint out of Unicode range ' . + 'at index: ' . $k . ', value: ' . $arr[$k], + E_USER_WARNING + ); + + // out of range + return false; + } + } + + return ob_get_clean(); + } +} diff --git a/inc/Utf8/tables/case.php b/inc/Utf8/tables/case.php new file mode 100644 index 000000000..ac5f5629e --- /dev/null +++ b/inc/Utf8/tables/case.php @@ -0,0 +1,567 @@ +<?php +/** + * UTF-8 Case lookup table + * + * This lookuptable defines the lower case letters to their corresponding + * upper case letter in UTF-8 + * + * @author Andreas Gohr <andi@splitbrain.org> + */ +return [ + 'A' => 'a', + 'A' => 'a', + 'Á' => 'á', + 'À' => 'à', + 'Ă' => 'ă', + 'Ắ' => 'ắ', + 'Ẵ' => 'ẵ', + 'Ẳ' => 'ẳ', + 'Â' => 'â', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẫ' => 'ẫ', + 'Ǎ' => 'ǎ', + 'Å' => 'å', + 'Ǻ' => 'ǻ', + 'Ä' => 'ä', + 'Ǟ' => 'ǟ', + 'Ã' => 'ã', + 'Ǡ' => 'ǡ', + 'Ą' => 'ą', + 'Ā' => 'ā', + 'Ả' => 'ả', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ặ' => 'ặ', + 'Ậ' => 'ậ', + 'Ḁ' => 'ḁ', + 'Æ' => 'æ', + 'Ǽ' => 'ǽ', + 'Ǣ' => 'ǣ', + 'B' => 'b', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'C' => 'c', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Č' => 'č', + 'Ċ' => 'ċ', + 'Ç' => 'ç', + 'Ƈ' => 'ƈ', + 'D' => 'd', + 'D' => 'd', + 'Ď' => 'ď', + 'Ḋ' => 'ḋ', + 'Ḑ' => 'ḑ', + 'Ḍ' => 'ḍ', + 'Ḓ' => 'ḓ', + 'Ḏ' => 'ḏ', + 'Ð' => 'ð', + 'Dz' => 'dz', //FIXME + 'Dž' => 'dž', //FIXME + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'E' => 'e', + 'E' => 'e', + 'É' => 'é', + 'È' => 'è', + 'Ê' => 'ê', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ễ' => 'ễ', + 'Ể' => 'ể', + 'Ě' => 'ě', + 'Ẽ' => 'ẽ', + 'Ė' => 'ė', + 'Ȩ' => 'ȩ', + 'Ḝ' => 'ḝ', + 'Ę' => 'ę', + 'Ē' => 'ē', + 'Ḕ' => 'ḕ', + 'Ẻ' => 'ẻ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ẹ' => 'ẹ', + 'Ệ' => 'ệ', + 'Ḛ' => 'ḛ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'F' => 'f', + 'F' => 'f', + 'Ƒ' => 'ƒ', + 'G' => 'g', + 'G' => 'g', + 'Ǵ' => 'ǵ', + 'Ğ' => 'ğ', + 'Ĝ' => 'ĝ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ḡ' => 'ḡ', + 'Ǥ' => 'ǥ', + 'Ɣ' => 'ɣ', + 'Ƣ' => 'ƣ', + 'H' => 'h', + 'Ĥ' => 'ĥ', + 'Ȟ' => 'ȟ', + 'Ḧ' => 'ḧ', + 'Ḣ' => 'ḣ', + 'Ḩ' => 'ḩ', + 'Ḥ' => 'ḥ', + 'Ḫ' => 'ḫ', + 'Ƕ' => 'ƕ', + 'I' => 'i', + 'I' => 'i', + 'Í' => 'í', + 'Ĭ' => 'ĭ', + 'Î' => 'î', + 'Ǐ' => 'ǐ', + 'Ï' => 'ï', + 'Ḯ' => 'ḯ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ỉ' => 'ỉ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ị' => 'ị', + 'Ḭ' => 'ḭ', + 'Ɨ' => 'ɨ', + 'Ɩ' => 'ɩ', + 'J' => 'j', + 'J' => 'j', + 'Ĵ' => 'ĵ', + 'K' => 'k', + 'Ḱ' => 'ḱ', + 'Ǩ' => 'ǩ', + 'Ķ' => 'ķ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ƙ' => 'ƙ', + 'L' => 'l', + 'Ĺ' => 'ĺ', + 'Ľ' => 'ľ', + 'Ļ' => 'ļ', + 'Ł' => 'ł', + 'Ḷ' => 'ḷ', + 'Ḽ' => 'ḽ', + 'Ḻ' => 'ḻ', + 'Ŀ' => 'ŀ', + 'Lj' => 'lj', // FIXME + 'M' => 'm', + 'M' => 'm', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'N' => 'n', + 'N' => 'n', + 'Ń' => 'ń', + 'Ǹ' => 'ǹ', + 'Ñ' => 'ñ', + 'Ṅ' => 'ṅ', + 'Ņ' => 'ņ', + 'Ṇ' => 'ṇ', + 'Ṋ' => 'ṋ', + 'Ṉ' => 'ṉ', + 'Ɲ' => 'ɲ', + 'Ƞ' => 'ƞ', + 'Ŋ' => 'ŋ', + 'O' => 'o', + 'O' => 'o', + 'Ó' => 'ó', + 'Ŏ' => 'ŏ', + 'Ô' => 'ô', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ỗ' => 'ỗ', + 'Ổ' => 'ổ', + 'Ö' => 'ö', + 'Ȫ' => 'ȫ', + 'Ő' => 'ő', + 'Õ' => 'õ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ø' => 'ø', + 'Ǿ' => 'ǿ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ṓ' => 'ṓ', + 'Ṑ' => 'ṑ', + 'Ỏ' => 'ỏ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ơ' => 'ơ', + 'Ờ' => 'ờ', + 'Ỡ' => 'ỡ', + 'Ở' => 'ở', + 'Ợ' => 'ợ', + 'Ọ' => 'ọ', + 'Ộ' => 'ộ', + 'Ɔ' => 'ɔ', + 'Ɵ' => 'ɵ', + 'Ȣ' => 'ȣ', + 'P' => 'p', + 'P' => 'p', + 'Ṕ' => 'ṕ', + 'Ƥ' => 'ƥ', + 'Q' => 'q', + 'Q' => 'q', + 'R' => 'r', + 'R' => 'r', + 'Ŕ' => 'ŕ', + 'Ṙ' => 'ṙ', + 'Ŗ' => 'ŗ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ʀ' => 'ʀ', + 'S' => 's', + 'S' => 's', + 'Ś' => 'ś', + 'Ṥ' => 'ṥ', + 'Ŝ' => 'ŝ', + 'Ṧ' => 'ṧ', + 'Ṡ' => 'ṡ', + 'Ş' => 'ş', + 'Ṣ' => 'ṣ', + 'Ṩ' => 'ṩ', + 'Ș' => 'ș', + 'T' => 't', + 'T' => 't', + 'Ť' => 'ť', + 'Ṫ' => 'ṫ', + 'Ţ' => 'ţ', + 'Ṭ' => 'ṭ', + 'Ṱ' => 'ṱ', + 'Ṯ' => 'ṯ', + 'Ŧ' => 'ŧ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'U' => 'u', + 'Ú' => 'ú', + 'Ù' => 'ù', + 'Ŭ' => 'ŭ', + 'Û' => 'û', + 'Ǔ' => 'ǔ', + 'Ů' => 'ů', + 'Ǘ' => 'ǘ', + 'Ǜ' => 'ǜ', + 'Ǚ' => 'ǚ', + 'Ǖ' => 'ǖ', + 'Ű' => 'ű', + 'Ũ' => 'ũ', + 'Ų' => 'ų', + 'Ū' => 'ū', + 'Ṻ' => 'ṻ', + 'Ủ' => 'ủ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ữ' => 'ữ', + 'Ử' => 'ử', + 'Ự' => 'ự', + 'Ụ' => 'ụ', + 'Ṷ' => 'ṷ', + 'Ṵ' => 'ṵ', + 'Ɯ' => 'ɯ', + 'Ʊ' => 'ʊ', + 'V' => 'v', + 'V' => 'v', + 'Ṿ' => 'ṿ', + 'Ʋ' => 'ʋ', + 'W' => 'w', + 'W' => 'w', + 'Ẃ' => 'ẃ', + 'Ẁ' => 'ẁ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'X' => 'x', + 'X' => 'x', + 'Ẍ' => 'ẍ', + 'Y' => 'y', + 'Y' => 'y', + 'Ý' => 'ý', + 'Ỳ' => 'ỳ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ẏ' => 'ẏ', + 'Ȳ' => 'ȳ', + 'Ỷ' => 'ỷ', + 'Ỵ' => 'ỵ', + 'Ƴ' => 'ƴ', + 'Ȝ' => 'ȝ', + 'Z' => 'z', + 'Ź' => 'ź', + 'Ẑ' => 'ẑ', + 'Ž' => 'ž', + 'Ż' => 'ż', + 'Ẓ' => 'ẓ', + 'Ƶ' => 'ƶ', + 'Ȥ' => 'ȥ', + 'Ʒ' => 'ʒ', + 'Ǯ' => 'ǯ', + 'Ƹ' => 'ƹ', + 'Þ' => 'þ', + 'Ƨ' => 'ƨ', + 'Ƽ' => 'ƽ', + 'Ƅ' => 'ƅ', + 'Α' => 'α', + 'Ἀ' => 'ἀ', + 'Ἄ' => 'ἄ', + 'Ἂ' => 'ἂ', + 'ᾊ' => 'ᾂ', + 'Ἆ' => 'ἆ', + 'ᾎ' => 'ᾆ', + 'ᾈ' => 'ᾀ', + 'Ἁ' => 'ἁ', + 'ᾍ' => 'ᾅ', + 'Ἃ' => 'ἃ', + 'ᾋ' => 'ᾃ', + 'Ἇ' => 'ἇ', + 'ᾏ' => 'ᾇ', + 'ᾉ' => 'ᾁ', + 'Ὰ' => 'ὰ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'ᾼ' => 'ᾳ', + 'Β' => 'β', + 'Γ' => 'γ', + 'Ε' => 'ε', + 'Ἐ' => 'ἐ', + 'Ἔ' => 'ἔ', + 'Ἒ' => 'ἒ', + 'Ἑ' => 'ἑ', + 'Ἕ' => 'ἕ', + 'Έ' => 'έ', + 'Ὲ' => 'ὲ', + 'Ϝ' => 'ϝ', + 'Ϛ' => 'ϛ', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'ᾜ' => 'ᾔ', + 'Ἢ' => 'ἢ', + 'ᾚ' => 'ᾒ', + 'Ἦ' => 'ἦ', + 'ᾞ' => 'ᾖ', + 'ᾘ' => 'ᾐ', + 'Ἥ' => 'ἥ', + 'ᾝ' => 'ᾕ', + 'Ἣ' => 'ἣ', + 'ᾛ' => 'ᾓ', + 'Ἧ' => 'ἧ', + 'ᾟ' => 'ᾗ', + 'Ή' => 'ή', + 'Ὴ' => 'ὴ', + 'ῌ' => 'ῃ', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Ἰ' => 'ἰ', + 'Ἲ' => 'ἲ', + 'Ἶ' => 'ἶ', + 'Ἱ' => 'ἱ', + 'Ἵ' => 'ἵ', + 'Ἳ' => 'ἳ', + 'Ἷ' => 'ἷ', + 'Ὶ' => 'ὶ', + 'Ῐ' => 'ῐ', + 'Ϊ' => 'ϊ', + 'Ῑ' => 'ῑ', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Ὀ' => 'ὀ', + 'Ὄ' => 'ὄ', + 'Ὂ' => 'ὂ', + 'Ὅ' => 'ὅ', + 'Ὃ' => 'ὃ', + 'Ό' => 'ό', + 'Ὸ' => 'ὸ', + 'Π' => 'π', + 'Ϟ' => 'ϟ', + 'Ρ' => 'ρ', + 'Ῥ' => 'ῥ', + 'Σ' => 'ς', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὗ' => 'ὗ', + 'Ύ' => 'ύ', + 'Ὺ' => 'ὺ', + 'Ϋ' => 'ϋ', + 'Ῡ' => 'ῡ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ὤ' => 'ὤ', + 'ᾬ' => 'ᾤ', + 'Ὢ' => 'ὢ', + 'Ὦ' => 'ὦ', + 'ᾮ' => 'ᾦ', + 'Ὡ' => 'ὡ', + 'Ὥ' => 'ὥ', + 'ᾭ' => 'ᾥ', + 'Ὣ' => 'ὣ', + 'Ὧ' => 'ὧ', + 'ᾯ' => 'ᾧ', + 'ᾩ' => 'ᾡ', + 'Ώ' => 'ώ', + 'Ὼ' => 'ὼ', + 'ῼ' => 'ῳ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'А' => 'а', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӕ' => 'ӕ', + 'В' => 'в', + 'Г' => 'г', + 'Ѓ' => 'ѓ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Ԁ' => 'ԁ', + 'Ђ' => 'ђ', + 'Ԃ' => 'ԃ', + 'Ҙ' => 'ҙ', + 'Е' => 'е', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Є' => 'є', + 'Ж' => 'ж', + 'Ӂ' => 'ӂ', + 'Ӝ' => 'ӝ', + 'Җ' => 'җ', + 'Ӟ' => 'ӟ', + 'Ԅ' => 'ԅ', + 'Ѕ' => 'ѕ', + 'Ӡ' => 'ӡ', + 'Ԇ' => 'ԇ', + 'И' => 'и', + 'Ӥ' => 'ӥ', + 'Ӣ' => 'ӣ', + 'Ҋ' => 'ҋ', + 'І' => 'і', + 'Ї' => 'ї', + 'Й' => 'й', + 'К' => 'к', + 'Ќ' => 'ќ', + 'Қ' => 'қ', + 'Ӄ' => 'ӄ', + 'Ҡ' => 'ҡ', + 'Ҟ' => 'ҟ', + 'Л' => 'л', + 'Ӆ' => 'ӆ', + 'Љ' => 'љ', + 'Ԉ' => 'ԉ', + 'М' => 'м', + 'Ӎ' => 'ӎ', + 'Ӊ' => 'ӊ', + 'Ң' => 'ң', + 'Ӈ' => 'ӈ', + 'Ҥ' => 'ҥ', + 'Њ' => 'њ', + 'Ԋ' => 'ԋ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'П' => 'п', + 'Ҧ' => 'ҧ', + 'Ҁ' => 'ҁ', + 'Ҏ' => 'ҏ', + 'С' => 'с', + 'Ԍ' => 'ԍ', + 'Ҫ' => 'ҫ', + 'Т' => 'т', + 'Ԏ' => 'ԏ', + 'Ћ' => 'ћ', + 'У' => 'у', + 'Ў' => 'ў', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӯ' => 'ӯ', + 'Ұ' => 'ұ', + 'Ѹ' => 'ѹ', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ҳ' => 'ҳ', + 'Һ' => 'һ', + 'Ѿ' => 'ѿ', + 'Ѽ' => 'ѽ', + 'Ѻ' => 'ѻ', + 'Ц' => 'ц', + 'Ҵ' => 'ҵ', + 'Ч' => 'ч', + 'Ҷ' => 'ҷ', + 'Ӌ' => 'ӌ', + 'Ҹ' => 'ҹ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Џ' => 'џ', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ӹ' => 'ӹ', + 'Ь' => 'ь', + 'Ҍ' => 'ҍ', + 'Э' => 'э', + 'Ӭ' => 'ӭ', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ҩ' => 'ҩ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', +]; diff --git a/inc/Utf8/tables/loweraccents.php b/inc/Utf8/tables/loweraccents.php new file mode 100644 index 000000000..cc3ec8eae --- /dev/null +++ b/inc/Utf8/tables/loweraccents.php @@ -0,0 +1,116 @@ +<?php +/** + * UTF-8 lookup table for lower case accented letters + * + * This lookuptable defines replacements for accented characters from the ASCII-7 + * range. This are lower case letters only. + * + * @author Andreas Gohr <andi@splitbrain.org> + * @see \dokuwiki\Utf8\Clean::deaccent() + */ +return [ + 'á' => 'a', + 'à' => 'a', + 'ă' => 'a', + 'â' => 'a', + 'å' => 'a', + 'ä' => 'ae', + 'ã' => 'a', + 'ą' => 'a', + 'ā' => 'a', + 'æ' => 'ae', + 'ḃ' => 'b', + 'ć' => 'c', + 'ĉ' => 'c', + 'č' => 'c', + 'ċ' => 'c', + 'ç' => 'c', + 'ď' => 'd', + 'ḋ' => 'd', + 'đ' => 'd', + 'ð' => 'dh', + 'é' => 'e', + 'è' => 'e', + 'ĕ' => 'e', + 'ê' => 'e', + 'ě' => 'e', + 'ë' => 'e', + 'ė' => 'e', + 'ę' => 'e', + 'ē' => 'e', + 'ḟ' => 'f', + 'ƒ' => 'f', + 'ğ' => 'g', + 'ĝ' => 'g', + 'ġ' => 'g', + 'ģ' => 'g', + 'ĥ' => 'h', + 'ħ' => 'h', + 'í' => 'i', + 'ì' => 'i', + 'î' => 'i', + 'ï' => 'i', + 'ĩ' => 'i', + 'į' => 'i', + 'ī' => 'i', + 'ĵ' => 'j', + 'ķ' => 'k', + 'ĺ' => 'l', + 'ľ' => 'l', + 'ļ' => 'l', + 'ł' => 'l', + 'ṁ' => 'm', + 'ń' => 'n', + 'ň' => 'n', + 'ñ' => 'n', + 'ņ' => 'n', + 'ó' => 'o', + 'ò' => 'o', + 'ô' => 'o', + 'ö' => 'oe', + 'ő' => 'o', + 'õ' => 'o', + 'ø' => 'o', + 'ō' => 'o', + 'ơ' => 'o', + 'ṗ' => 'p', + 'ŕ' => 'r', + 'ř' => 'r', + 'ŗ' => 'r', + 'ś' => 's', + 'ŝ' => 's', + 'š' => 's', + 'ṡ' => 's', + 'ş' => 's', + 'ș' => 's', + 'ß' => 'ss', + 'ť' => 't', + 'ṫ' => 't', + 'ţ' => 't', + 'ț' => 't', + 'ŧ' => 't', + 'ú' => 'u', + 'ù' => 'u', + 'ŭ' => 'u', + 'û' => 'u', + 'ů' => 'u', + 'ü' => 'ue', + 'ű' => 'u', + 'ũ' => 'u', + 'ų' => 'u', + 'ū' => 'u', + 'ư' => 'u', + 'ẃ' => 'w', + 'ẁ' => 'w', + 'ŵ' => 'w', + 'ẅ' => 'w', + 'ý' => 'y', + 'ỳ' => 'y', + 'ŷ' => 'y', + 'ÿ' => 'y', + 'ź' => 'z', + 'ž' => 'z', + 'ż' => 'z', + 'þ' => 'th', + 'µ' => 'u', +]; diff --git a/inc/Utf8/tables/romanization.php b/inc/Utf8/tables/romanization.php new file mode 100644 index 000000000..e757b9c4d --- /dev/null +++ b/inc/Utf8/tables/romanization.php @@ -0,0 +1,1458 @@ +<?php +/** + * Romanization lookup table + * + * This lookup tables provides a way to transform strings written in a language + * different from the ones based upon latin letters into plain ASCII. + * + * Please note: this is not a scientific transliteration table. It only works + * oneway from nonlatin to ASCII and it works by simple character replacement + * only. Specialities of each language are not supported. + * + * @todo some keys are used multiple times + * @todo remove or integrate commented pairs + * + * @author Andreas Gohr <andi@splitbrain.org> + * @author Vitaly Blokhin <vitinfo@vitn.com> + * @author Bisqwit <bisqwit@iki.fi> + * @author Arthit Suriyawongkul <arthit@gmail.com> + * @author Denis Scheither <amorphis@uni-bremen.de> + * @author Eivind Morland <eivind.morland@gmail.com> + * @link http://www.uconv.com/translit.htm + * @link http://kanjidict.stc.cx/hiragana.php?src=2 + * @link http://www.translatum.gr/converter/greek-transliteration.htm + * @link http://en.wikipedia.org/wiki/Royal_Thai_General_System_of_Transcription + * @link http://www.btranslations.com/resources/romanization/korean.asp + */ +return [ + // scandinavian - differs from what we do in deaccent + 'å' => 'a', + 'Å' => 'A', + 'ä' => 'a', + 'Ä' => 'A', + 'ö' => 'o', + 'Ö' => 'O', + + //russian cyrillic + 'а' => 'a', + 'А' => 'A', + 'б' => 'b', + 'Б' => 'B', + 'в' => 'v', + 'В' => 'V', + 'г' => 'g', + 'Г' => 'G', + 'д' => 'd', + 'Д' => 'D', + 'е' => 'e', + 'Е' => 'E', + 'ё' => 'jo', + 'Ё' => 'Jo', + 'ж' => 'zh', + 'Ж' => 'Zh', + 'з' => 'z', + 'З' => 'Z', + 'и' => 'i', + 'И' => 'I', + 'й' => 'j', + 'Й' => 'J', + 'к' => 'k', + 'К' => 'K', + 'л' => 'l', + 'Л' => 'L', + 'м' => 'm', + 'М' => 'M', + 'н' => 'n', + 'Н' => 'N', + 'о' => 'o', + 'О' => 'O', + 'п' => 'p', + 'П' => 'P', + 'р' => 'r', + 'Р' => 'R', + 'с' => 's', + 'С' => 'S', + 'т' => 't', + 'Т' => 'T', + 'у' => 'u', + 'У' => 'U', + 'ф' => 'f', + 'Ф' => 'F', + 'х' => 'x', + 'Х' => 'X', + 'ц' => 'c', + 'Ц' => 'C', + 'ч' => 'ch', + 'Ч' => 'Ch', + 'ш' => 'sh', + 'Ш' => 'Sh', + 'щ' => 'sch', + 'Щ' => 'Sch', + 'ъ' => '', + 'Ъ' => '', + 'ы' => 'y', + 'Ы' => 'Y', + 'ь' => '', + 'Ь' => '', + 'э' => 'eh', + 'Э' => 'Eh', + 'ю' => 'ju', + 'Ю' => 'Ju', + 'я' => 'ja', + 'Я' => 'Ja', + + // Ukrainian cyrillic + 'Ґ' => 'Gh', + 'ґ' => 'gh', + 'Є' => 'Je', + 'є' => 'je', + 'І' => 'I', + 'і' => 'i', + 'Ї' => 'Ji', + 'ї' => 'ji', + + // Georgian + 'ა' => 'a', + 'ბ' => 'b', + 'გ' => 'g', + 'დ' => 'd', + 'ე' => 'e', + 'ვ' => 'v', + 'ზ' => 'z', + 'თ' => 'th', + 'ი' => 'i', + 'კ' => 'p', + 'ლ' => 'l', + 'მ' => 'm', + 'ნ' => 'n', + 'ო' => 'o', + 'პ' => 'p', + 'ჟ' => 'zh', + 'რ' => 'r', + 'ს' => 's', + 'ტ' => 't', + 'უ' => 'u', + 'ფ' => 'ph', + 'ქ' => 'kh', + 'ღ' => 'gh', + 'ყ' => 'q', + 'შ' => 'sh', + 'ჩ' => 'ch', + 'ც' => 'c', + 'ძ' => 'dh', + 'წ' => 'w', + 'ჭ' => 'j', + 'ხ' => 'x', + 'ჯ' => 'jh', + 'ჰ' => 'xh', + + //Sanskrit + 'अ' => 'a', + 'आ' => 'ah', + 'इ' => 'i', + 'ई' => 'ih', + 'उ' => 'u', + 'ऊ' => 'uh', + 'ऋ' => 'ry', + 'ॠ' => 'ryh', + 'ऌ' => 'ly', + 'ॡ' => 'lyh', + 'ए' => 'e', + 'ऐ' => 'ay', + 'ओ' => 'o', + 'औ' => 'aw', + 'अं' => 'amh', + 'अः' => 'aq', + 'क' => 'k', + 'ख' => 'kh', + 'ग' => 'g', + 'घ' => 'gh', + 'ङ' => 'nh', + 'च' => 'c', + 'छ' => 'ch', + 'ज' => 'j', + 'झ' => 'jh', + 'ञ' => 'ny', + 'ट' => 'tq', + 'ठ' => 'tqh', + 'ड' => 'dq', + 'ढ' => 'dqh', + 'ण' => 'nq', + 'त' => 't', + 'थ' => 'th', + 'द' => 'd', + 'ध' => 'dh', + 'न' => 'n', + 'प' => 'p', + 'फ' => 'ph', + 'ब' => 'b', + 'भ' => 'bh', + 'म' => 'm', + 'य' => 'z', + 'र' => 'r', + 'ल' => 'l', + 'व' => 'v', + 'श' => 'sh', + 'ष' => 'sqh', + 'स' => 's', + 'ह' => 'x', + + //Sanskrit diacritics + 'Ā' => 'A', + 'Ī' => 'I', + 'Ū' => 'U', + 'Ṛ' => 'R', + 'Ṝ' => 'R', + 'Ṅ' => 'N', + 'Ñ' => 'N', + 'Ṭ' => 'T', + 'Ḍ' => 'D', + 'Ṇ' => 'N', + 'Ś' => 'S', + 'Ṣ' => 'S', + 'Ṁ' => 'M', + 'Ṃ' => 'M', + 'Ḥ' => 'H', + 'Ḷ' => 'L', + 'Ḹ' => 'L', + 'ā' => 'a', + 'ī' => 'i', + 'ū' => 'u', + 'ṛ' => 'r', + 'ṝ' => 'r', + 'ṅ' => 'n', + 'ñ' => 'n', + 'ṭ' => 't', + 'ḍ' => 'd', + 'ṇ' => 'n', + 'ś' => 's', + 'ṣ' => 's', + 'ṁ' => 'm', + 'ṃ' => 'm', + 'ḥ' => 'h', + 'ḷ' => 'l', + 'ḹ' => 'l', + + //Hebrew + 'א' => 'a', + 'ב' => 'b', + 'ג' => 'g', + 'ד' => 'd', + 'ה' => 'h', + 'ו' => 'v', + 'ז' => 'z', + 'ח' => 'kh', + 'ט' => 'th', + 'י' => 'y', + 'ך' => 'h', + 'כ' => 'k', + 'ל' => 'l', + 'ם' => 'm', + 'מ' => 'm', + 'ן' => 'n', + 'נ' => 'n', + 'ס' => 's', + 'ע' => 'ah', + 'ף' => 'f', + 'פ' => 'p', + 'ץ' => 'c', + 'צ' => 'c', + 'ק' => 'q', + 'ר' => 'r', + 'ש' => 'sh', + 'ת' => 't', + + //Arabic + 'ا' => 'a', + 'ب' => 'b', + 'ت' => 't', + 'ث' => 'th', + 'ج' => 'g', + 'ح' => 'xh', + 'خ' => 'x', + 'د' => 'd', + 'ذ' => 'dh', + 'ر' => 'r', + 'ز' => 'z', + 'س' => 's', + 'ش' => 'sh', + 'ص' => 's\'', + 'ض' => 'd\'', + 'ط' => 't\'', + 'ظ' => 'z\'', + 'ع' => 'y', + 'غ' => 'gh', + 'ف' => 'f', + 'ق' => 'q', + 'ك' => 'k', + 'ل' => 'l', + 'م' => 'm', + 'ن' => 'n', + 'ه' => 'x\'', + 'و' => 'u', + 'ي' => 'i', + + // Japanese characters (last update: 2008-05-09) + + // Japanese hiragana + + // 3 character syllables, っ doubles the consonant after + 'っちゃ' => 'ccha', + 'っちぇ' => 'cche', + 'っちょ' => 'ccho', + 'っちゅ' => 'cchu', + 'っびゃ' => 'bbya', + 'っびぇ' => 'bbye', + 'っびぃ' => 'bbyi', + 'っびょ' => 'bbyo', + 'っびゅ' => 'bbyu', + 'っぴゃ' => 'ppya', + 'っぴぇ' => 'ppye', + 'っぴぃ' => 'ppyi', + 'っぴょ' => 'ppyo', + 'っぴゅ' => 'ppyu', + 'っちゃ' => 'ccha', + 'っちぇ' => 'cche', + 'っち' => 'cchi', + 'っちょ' => 'ccho', + 'っちゅ' => 'cchu', + // 'っひゃ'=>'hya', + // 'っひぇ'=>'hye', + // 'っひぃ'=>'hyi', + // 'っひょ'=>'hyo', + // 'っひゅ'=>'hyu', + 'っきゃ' => 'kkya', + 'っきぇ' => 'kkye', + 'っきぃ' => 'kkyi', + 'っきょ' => 'kkyo', + 'っきゅ' => 'kkyu', + 'っぎゃ' => 'ggya', + 'っぎぇ' => 'ggye', + 'っぎぃ' => 'ggyi', + 'っぎょ' => 'ggyo', + 'っぎゅ' => 'ggyu', + 'っみゃ' => 'mmya', + 'っみぇ' => 'mmye', + 'っみぃ' => 'mmyi', + 'っみょ' => 'mmyo', + 'っみゅ' => 'mmyu', + 'っにゃ' => 'nnya', + 'っにぇ' => 'nnye', + 'っにぃ' => 'nnyi', + 'っにょ' => 'nnyo', + 'っにゅ' => 'nnyu', + 'っりゃ' => 'rrya', + 'っりぇ' => 'rrye', + 'っりぃ' => 'rryi', + 'っりょ' => 'rryo', + 'っりゅ' => 'rryu', + 'っしゃ' => 'ssha', + 'っしぇ' => 'sshe', + 'っし' => 'sshi', + 'っしょ' => 'ssho', + 'っしゅ' => 'sshu', + + // seperate hiragana 'n' ('n' + 'i' != 'ni', normally we would write "kon'nichi wa" but the + // apostrophe would be converted to _ anyway) + 'んあ' => 'n_a', + 'んえ' => 'n_e', + 'んい' => 'n_i', + 'んお' => 'n_o', + 'んう' => 'n_u', + 'んや' => 'n_ya', + 'んよ' => 'n_yo', + 'んゆ' => 'n_yu', + + // 2 character syllables - normal + 'ふぁ' => 'fa', + 'ふぇ' => 'fe', + 'ふぃ' => 'fi', + 'ふぉ' => 'fo', + 'ちゃ' => 'cha', + 'ちぇ' => 'che', + 'ち' => 'chi', + 'ちょ' => 'cho', + 'ちゅ' => 'chu', + 'ひゃ' => 'hya', + 'ひぇ' => 'hye', + 'ひぃ' => 'hyi', + 'ひょ' => 'hyo', + 'ひゅ' => 'hyu', + 'びゃ' => 'bya', + 'びぇ' => 'bye', + 'びぃ' => 'byi', + 'びょ' => 'byo', + 'びゅ' => 'byu', + 'ぴゃ' => 'pya', + 'ぴぇ' => 'pye', + 'ぴぃ' => 'pyi', + 'ぴょ' => 'pyo', + 'ぴゅ' => 'pyu', + 'きゃ' => 'kya', + 'きぇ' => 'kye', + 'きぃ' => 'kyi', + 'きょ' => 'kyo', + 'きゅ' => 'kyu', + 'ぎゃ' => 'gya', + 'ぎぇ' => 'gye', + 'ぎぃ' => 'gyi', + 'ぎょ' => 'gyo', + 'ぎゅ' => 'gyu', + 'みゃ' => 'mya', + 'みぇ' => 'mye', + 'みぃ' => 'myi', + 'みょ' => 'myo', + 'みゅ' => 'myu', + 'にゃ' => 'nya', + 'にぇ' => 'nye', + 'にぃ' => 'nyi', + 'にょ' => 'nyo', + 'にゅ' => 'nyu', + 'りゃ' => 'rya', + 'りぇ' => 'rye', + 'りぃ' => 'ryi', + 'りょ' => 'ryo', + 'りゅ' => 'ryu', + 'しゃ' => 'sha', + 'しぇ' => 'she', + 'し' => 'shi', + 'しょ' => 'sho', + 'しゅ' => 'shu', + 'じゃ' => 'ja', + 'じぇ' => 'je', + 'じょ' => 'jo', + 'じゅ' => 'ju', + 'うぇ' => 'we', + 'うぃ' => 'wi', + 'いぇ' => 'ye', + + // 2 character syllables, っ doubles the consonant after + 'っば' => 'bba', + 'っべ' => 'bbe', + 'っび' => 'bbi', + 'っぼ' => 'bbo', + 'っぶ' => 'bbu', + 'っぱ' => 'ppa', + 'っぺ' => 'ppe', + 'っぴ' => 'ppi', + 'っぽ' => 'ppo', + 'っぷ' => 'ppu', + 'った' => 'tta', + 'って' => 'tte', + 'っち' => 'cchi', + 'っと' => 'tto', + 'っつ' => 'ttsu', + 'っだ' => 'dda', + 'っで' => 'dde', + 'っぢ' => 'ddi', + 'っど' => 'ddo', + 'っづ' => 'ddu', + 'っが' => 'gga', + 'っげ' => 'gge', + 'っぎ' => 'ggi', + 'っご' => 'ggo', + 'っぐ' => 'ggu', + 'っか' => 'kka', + 'っけ' => 'kke', + 'っき' => 'kki', + 'っこ' => 'kko', + 'っく' => 'kku', + 'っま' => 'mma', + 'っめ' => 'mme', + 'っみ' => 'mmi', + 'っも' => 'mmo', + 'っむ' => 'mmu', + 'っな' => 'nna', + 'っね' => 'nne', + 'っに' => 'nni', + 'っの' => 'nno', + 'っぬ' => 'nnu', + 'っら' => 'rra', + 'っれ' => 'rre', + 'っり' => 'rri', + 'っろ' => 'rro', + 'っる' => 'rru', + 'っさ' => 'ssa', + 'っせ' => 'sse', + 'っし' => 'sshi', + 'っそ' => 'sso', + 'っす' => 'ssu', + 'っざ' => 'zza', + 'っぜ' => 'zze', + 'っじ' => 'jji', + 'っぞ' => 'zzo', + 'っず' => 'zzu', + + // 1 character syllabels + 'あ' => 'a', + 'え' => 'e', + 'い' => 'i', + 'お' => 'o', + 'う' => 'u', + 'ん' => 'n', + 'は' => 'ha', + 'へ' => 'he', + 'ひ' => 'hi', + 'ほ' => 'ho', + 'ふ' => 'fu', + 'ば' => 'ba', + 'べ' => 'be', + 'び' => 'bi', + 'ぼ' => 'bo', + 'ぶ' => 'bu', + 'ぱ' => 'pa', + 'ぺ' => 'pe', + 'ぴ' => 'pi', + 'ぽ' => 'po', + 'ぷ' => 'pu', + 'た' => 'ta', + 'て' => 'te', + 'ち' => 'chi', + 'と' => 'to', + 'つ' => 'tsu', + 'だ' => 'da', + 'で' => 'de', + 'ぢ' => 'di', + 'ど' => 'do', + 'づ' => 'du', + 'が' => 'ga', + 'げ' => 'ge', + 'ぎ' => 'gi', + 'ご' => 'go', + 'ぐ' => 'gu', + 'か' => 'ka', + 'け' => 'ke', + 'き' => 'ki', + 'こ' => 'ko', + 'く' => 'ku', + 'ま' => 'ma', + 'め' => 'me', + 'み' => 'mi', + 'も' => 'mo', + 'む' => 'mu', + 'な' => 'na', + 'ね' => 'ne', + 'に' => 'ni', + 'の' => 'no', + 'ぬ' => 'nu', + 'ら' => 'ra', + 'れ' => 're', + 'り' => 'ri', + 'ろ' => 'ro', + 'る' => 'ru', + 'さ' => 'sa', + 'せ' => 'se', + 'し' => 'shi', + 'そ' => 'so', + 'す' => 'su', + 'わ' => 'wa', + 'を' => 'wo', + 'ざ' => 'za', + 'ぜ' => 'ze', + 'じ' => 'ji', + 'ぞ' => 'zo', + 'ず' => 'zu', + 'や' => 'ya', + 'よ' => 'yo', + 'ゆ' => 'yu', + // old characters + 'ゑ' => 'we', + 'ゐ' => 'wi', + + // convert what's left (probably only kicks in when something's missing above) + // 'ぁ'=>'a','ぇ'=>'e','ぃ'=>'i','ぉ'=>'o','ぅ'=>'u', + // 'ゃ'=>'ya','ょ'=>'yo','ゅ'=>'yu', + + // never seen one of those (disabled for the moment) + // 'ヴぁ'=>'va','ヴぇ'=>'ve','ヴぃ'=>'vi','ヴぉ'=>'vo','ヴ'=>'vu', + // 'でゃ'=>'dha','でぇ'=>'dhe','でぃ'=>'dhi','でょ'=>'dho','でゅ'=>'dhu', + // 'どぁ'=>'dwa','どぇ'=>'dwe','どぃ'=>'dwi','どぉ'=>'dwo','どぅ'=>'dwu', + // 'ぢゃ'=>'dya','ぢぇ'=>'dye','ぢぃ'=>'dyi','ぢょ'=>'dyo','ぢゅ'=>'dyu', + // 'ふぁ'=>'fwa','ふぇ'=>'fwe','ふぃ'=>'fwi','ふぉ'=>'fwo','ふぅ'=>'fwu', + // 'ふゃ'=>'fya','ふぇ'=>'fye','ふぃ'=>'fyi','ふょ'=>'fyo','ふゅ'=>'fyu', + // 'すぁ'=>'swa','すぇ'=>'swe','すぃ'=>'swi','すぉ'=>'swo','すぅ'=>'swu', + // 'てゃ'=>'tha','てぇ'=>'the','てぃ'=>'thi','てょ'=>'tho','てゅ'=>'thu', + // 'つゃ'=>'tsa','つぇ'=>'tse','つぃ'=>'tsi','つょ'=>'tso','つ'=>'tsu', + // 'とぁ'=>'twa','とぇ'=>'twe','とぃ'=>'twi','とぉ'=>'two','とぅ'=>'twu', + // 'ヴゃ'=>'vya','ヴぇ'=>'vye','ヴぃ'=>'vyi','ヴょ'=>'vyo','ヴゅ'=>'vyu', + // 'うぁ'=>'wha','うぇ'=>'whe','うぃ'=>'whi','うぉ'=>'who','うぅ'=>'whu', + // 'じゃ'=>'zha','じぇ'=>'zhe','じぃ'=>'zhi','じょ'=>'zho','じゅ'=>'zhu', + // 'じゃ'=>'zya','じぇ'=>'zye','じぃ'=>'zyi','じょ'=>'zyo','じゅ'=>'zyu', + + // 'spare' characters from other romanization systems + // 'だ'=>'da','で'=>'de','ぢ'=>'di','ど'=>'do','づ'=>'du', + // 'ら'=>'la','れ'=>'le','り'=>'li','ろ'=>'lo','る'=>'lu', + // 'さ'=>'sa','せ'=>'se','し'=>'si','そ'=>'so','す'=>'su', + // 'ちゃ'=>'cya','ちぇ'=>'cye','ちぃ'=>'cyi','ちょ'=>'cyo','ちゅ'=>'cyu', + //'じゃ'=>'jya','じぇ'=>'jye','じぃ'=>'jyi','じょ'=>'jyo','じゅ'=>'jyu', + //'りゃ'=>'lya','りぇ'=>'lye','りぃ'=>'lyi','りょ'=>'lyo','りゅ'=>'lyu', + //'しゃ'=>'sya','しぇ'=>'sye','しぃ'=>'syi','しょ'=>'syo','しゅ'=>'syu', + //'ちゃ'=>'tya','ちぇ'=>'tye','ちぃ'=>'tyi','ちょ'=>'tyo','ちゅ'=>'tyu', + //'し'=>'ci',,い'=>'yi','ぢ'=>'dzi', + //'っじゃ'=>'jja','っじぇ'=>'jje','っじ'=>'jji','っじょ'=>'jjo','っじゅ'=>'jju', + + + // Japanese katakana + + // 4 character syllables: ッ doubles the consonant after, ー doubles the vowel before + // (usualy written with macron, but we don't want that in our URLs) + 'ッビャー' => 'bbyaa', + 'ッビェー' => 'bbyee', + 'ッビィー' => 'bbyii', + 'ッビョー' => 'bbyoo', + 'ッビュー' => 'bbyuu', + 'ッピャー' => 'ppyaa', + 'ッピェー' => 'ppyee', + 'ッピィー' => 'ppyii', + 'ッピョー' => 'ppyoo', + 'ッピュー' => 'ppyuu', + 'ッキャー' => 'kkyaa', + 'ッキェー' => 'kkyee', + 'ッキィー' => 'kkyii', + 'ッキョー' => 'kkyoo', + 'ッキュー' => 'kkyuu', + 'ッギャー' => 'ggyaa', + 'ッギェー' => 'ggyee', + 'ッギィー' => 'ggyii', + 'ッギョー' => 'ggyoo', + 'ッギュー' => 'ggyuu', + 'ッミャー' => 'mmyaa', + 'ッミェー' => 'mmyee', + 'ッミィー' => 'mmyii', + 'ッミョー' => 'mmyoo', + 'ッミュー' => 'mmyuu', + 'ッニャー' => 'nnyaa', + 'ッニェー' => 'nnyee', + 'ッニィー' => 'nnyii', + 'ッニョー' => 'nnyoo', + 'ッニュー' => 'nnyuu', + 'ッリャー' => 'rryaa', + 'ッリェー' => 'rryee', + 'ッリィー' => 'rryii', + 'ッリョー' => 'rryoo', + 'ッリュー' => 'rryuu', + 'ッシャー' => 'sshaa', + 'ッシェー' => 'sshee', + 'ッシー' => 'sshii', + 'ッショー' => 'sshoo', + 'ッシュー' => 'sshuu', + 'ッチャー' => 'cchaa', + 'ッチェー' => 'cchee', + 'ッチー' => 'cchii', + 'ッチョー' => 'cchoo', + 'ッチュー' => 'cchuu', + 'ッティー' => 'ttii', + 'ッヂィー' => 'ddii', + + // 3 character syllables - doubled vowels + 'ファー' => 'faa', + 'フェー' => 'fee', + 'フィー' => 'fii', + 'フォー' => 'foo', + 'フャー' => 'fyaa', + 'フェー' => 'fyee', + 'フィー' => 'fyii', + 'フョー' => 'fyoo', + 'フュー' => 'fyuu', + 'ヒャー' => 'hyaa', + 'ヒェー' => 'hyee', + 'ヒィー' => 'hyii', + 'ヒョー' => 'hyoo', + 'ヒュー' => 'hyuu', + 'ビャー' => 'byaa', + 'ビェー' => 'byee', + 'ビィー' => 'byii', + 'ビョー' => 'byoo', + 'ビュー' => 'byuu', + 'ピャー' => 'pyaa', + 'ピェー' => 'pyee', + 'ピィー' => 'pyii', + 'ピョー' => 'pyoo', + 'ピュー' => 'pyuu', + 'キャー' => 'kyaa', + 'キェー' => 'kyee', + 'キィー' => 'kyii', + 'キョー' => 'kyoo', + 'キュー' => 'kyuu', + 'ギャー' => 'gyaa', + 'ギェー' => 'gyee', + 'ギィー' => 'gyii', + 'ギョー' => 'gyoo', + 'ギュー' => 'gyuu', + 'ミャー' => 'myaa', + 'ミェー' => 'myee', + 'ミィー' => 'myii', + 'ミョー' => 'myoo', + 'ミュー' => 'myuu', + 'ニャー' => 'nyaa', + 'ニェー' => 'nyee', + 'ニィー' => 'nyii', + 'ニョー' => 'nyoo', + 'ニュー' => 'nyuu', + 'リャー' => 'ryaa', + 'リェー' => 'ryee', + 'リィー' => 'ryii', + 'リョー' => 'ryoo', + 'リュー' => 'ryuu', + 'シャー' => 'shaa', + 'シェー' => 'shee', + 'シー' => 'shii', + 'ショー' => 'shoo', + 'シュー' => 'shuu', + 'ジャー' => 'jaa', + 'ジェー' => 'jee', + 'ジー' => 'jii', + 'ジョー' => 'joo', + 'ジュー' => 'juu', + 'スァー' => 'swaa', + 'スェー' => 'swee', + 'スィー' => 'swii', + 'スォー' => 'swoo', + 'スゥー' => 'swuu', + 'デァー' => 'daa', + 'デェー' => 'dee', + 'ディー' => 'dii', + 'デォー' => 'doo', + 'デゥー' => 'duu', + 'チャー' => 'chaa', + 'チェー' => 'chee', + 'チー' => 'chii', + 'チョー' => 'choo', + 'チュー' => 'chuu', + 'ヂャー' => 'dyaa', + 'ヂェー' => 'dyee', + 'ヂィー' => 'dyii', + 'ヂョー' => 'dyoo', + 'ヂュー' => 'dyuu', + 'ツャー' => 'tsaa', + 'ツェー' => 'tsee', + 'ツィー' => 'tsii', + 'ツョー' => 'tsoo', + 'ツー' => 'tsuu', + 'トァー' => 'twaa', + 'トェー' => 'twee', + 'トィー' => 'twii', + 'トォー' => 'twoo', + 'トゥー' => 'twuu', + 'ドァー' => 'dwaa', + 'ドェー' => 'dwee', + 'ドィー' => 'dwii', + 'ドォー' => 'dwoo', + 'ドゥー' => 'dwuu', + 'ウァー' => 'whaa', + 'ウェー' => 'whee', + 'ウィー' => 'whii', + 'ウォー' => 'whoo', + 'ウゥー' => 'whuu', + 'ヴャー' => 'vyaa', + 'ヴェー' => 'vyee', + 'ヴィー' => 'vyii', + 'ヴョー' => 'vyoo', + 'ヴュー' => 'vyuu', + 'ヴァー' => 'vaa', + 'ヴェー' => 'vee', + 'ヴィー' => 'vii', + 'ヴォー' => 'voo', + 'ヴー' => 'vuu', + 'ウェー' => 'wee', + 'ウィー' => 'wii', + 'イェー' => 'yee', + 'ティー' => 'tii', + 'ヂィー' => 'dii', + + // 3 character syllables - doubled consonants + 'ッビャ' => 'bbya', + 'ッビェ' => 'bbye', + 'ッビィ' => 'bbyi', + 'ッビョ' => 'bbyo', + 'ッビュ' => 'bbyu', + 'ッピャ' => 'ppya', + 'ッピェ' => 'ppye', + 'ッピィ' => 'ppyi', + 'ッピョ' => 'ppyo', + 'ッピュ' => 'ppyu', + 'ッキャ' => 'kkya', + 'ッキェ' => 'kkye', + 'ッキィ' => 'kkyi', + 'ッキョ' => 'kkyo', + 'ッキュ' => 'kkyu', + 'ッギャ' => 'ggya', + 'ッギェ' => 'ggye', + 'ッギィ' => 'ggyi', + 'ッギョ' => 'ggyo', + 'ッギュ' => 'ggyu', + 'ッミャ' => 'mmya', + 'ッミェ' => 'mmye', + 'ッミィ' => 'mmyi', + 'ッミョ' => 'mmyo', + 'ッミュ' => 'mmyu', + 'ッニャ' => 'nnya', + 'ッニェ' => 'nnye', + 'ッニィ' => 'nnyi', + 'ッニョ' => 'nnyo', + 'ッニュ' => 'nnyu', + 'ッリャ' => 'rrya', + 'ッリェ' => 'rrye', + 'ッリィ' => 'rryi', + 'ッリョ' => 'rryo', + 'ッリュ' => 'rryu', + 'ッシャ' => 'ssha', + 'ッシェ' => 'sshe', + 'ッシ' => 'sshi', + 'ッショ' => 'ssho', + 'ッシュ' => 'sshu', + 'ッチャ' => 'ccha', + 'ッチェ' => 'cche', + 'ッチ' => 'cchi', + 'ッチョ' => 'ccho', + 'ッチュ' => 'cchu', + 'ッティ' => 'tti', + 'ッヂィ' => 'ddi', + + // 3 character syllables - doubled vowel and consonants + 'ッバー' => 'bbaa', + 'ッベー' => 'bbee', + 'ッビー' => 'bbii', + 'ッボー' => 'bboo', + 'ッブー' => 'bbuu', + 'ッパー' => 'ppaa', + 'ッペー' => 'ppee', + 'ッピー' => 'ppii', + 'ッポー' => 'ppoo', + 'ップー' => 'ppuu', + 'ッケー' => 'kkee', + 'ッキー' => 'kkii', + 'ッコー' => 'kkoo', + 'ックー' => 'kkuu', + 'ッカー' => 'kkaa', + 'ッガー' => 'ggaa', + 'ッゲー' => 'ggee', + 'ッギー' => 'ggii', + 'ッゴー' => 'ggoo', + 'ッグー' => 'gguu', + 'ッマー' => 'maa', + 'ッメー' => 'mee', + 'ッミー' => 'mii', + 'ッモー' => 'moo', + 'ッムー' => 'muu', + 'ッナー' => 'nnaa', + 'ッネー' => 'nnee', + 'ッニー' => 'nnii', + 'ッノー' => 'nnoo', + 'ッヌー' => 'nnuu', + 'ッラー' => 'rraa', + 'ッレー' => 'rree', + 'ッリー' => 'rrii', + 'ッロー' => 'rroo', + 'ッルー' => 'rruu', + 'ッサー' => 'ssaa', + 'ッセー' => 'ssee', + 'ッシー' => 'sshii', + 'ッソー' => 'ssoo', + 'ッスー' => 'ssuu', + 'ッザー' => 'zzaa', + 'ッゼー' => 'zzee', + 'ッジー' => 'jjii', + 'ッゾー' => 'zzoo', + 'ッズー' => 'zzuu', + 'ッター' => 'ttaa', + 'ッテー' => 'ttee', + 'ッチー' => 'chii', + 'ットー' => 'ttoo', + 'ッツー' => 'ttsuu', + 'ッダー' => 'ddaa', + 'ッデー' => 'ddee', + 'ッヂー' => 'ddii', + 'ッドー' => 'ddoo', + 'ッヅー' => 'dduu', + + // 2 character syllables - normal + 'ファ' => 'fa', + 'フェ' => 'fe', + 'フィ' => 'fi', + 'フォ' => 'fo', + 'フゥ' => 'fu', + // 'フャ'=>'fya', + // 'フェ'=>'fye', + // 'フィ'=>'fyi', + // 'フョ'=>'fyo', + // 'フュ'=>'fyu', + 'フャ' => 'fa', + 'フェ' => 'fe', + 'フィ' => 'fi', + 'フョ' => 'fo', + 'フュ' => 'fu', + 'ヒャ' => 'hya', + 'ヒェ' => 'hye', + 'ヒィ' => 'hyi', + 'ヒョ' => 'hyo', + 'ヒュ' => 'hyu', + 'ビャ' => 'bya', + 'ビェ' => 'bye', + 'ビィ' => 'byi', + 'ビョ' => 'byo', + 'ビュ' => 'byu', + 'ピャ' => 'pya', + 'ピェ' => 'pye', + 'ピィ' => 'pyi', + 'ピョ' => 'pyo', + 'ピュ' => 'pyu', + 'キャ' => 'kya', + 'キェ' => 'kye', + 'キィ' => 'kyi', + 'キョ' => 'kyo', + 'キュ' => 'kyu', + 'ギャ' => 'gya', + 'ギェ' => 'gye', + 'ギィ' => 'gyi', + 'ギョ' => 'gyo', + 'ギュ' => 'gyu', + 'ミャ' => 'mya', + 'ミェ' => 'mye', + 'ミィ' => 'myi', + 'ミョ' => 'myo', + 'ミュ' => 'myu', + 'ニャ' => 'nya', + 'ニェ' => 'nye', + 'ニィ' => 'nyi', + 'ニョ' => 'nyo', + 'ニュ' => 'nyu', + 'リャ' => 'rya', + 'リェ' => 'rye', + 'リィ' => 'ryi', + 'リョ' => 'ryo', + 'リュ' => 'ryu', + 'シャ' => 'sha', + 'シェ' => 'she', + 'ショ' => 'sho', + 'シュ' => 'shu', + 'ジャ' => 'ja', + 'ジェ' => 'je', + 'ジョ' => 'jo', + 'ジュ' => 'ju', + 'スァ' => 'swa', + 'スェ' => 'swe', + 'スィ' => 'swi', + 'スォ' => 'swo', + 'スゥ' => 'swu', + 'デァ' => 'da', + 'デェ' => 'de', + 'ディ' => 'di', + 'デォ' => 'do', + 'デゥ' => 'du', + 'チャ' => 'cha', + 'チェ' => 'che', + 'チ' => 'chi', + 'チョ' => 'cho', + 'チュ' => 'chu', + // 'ヂャ'=>'dya', + // 'ヂェ'=>'dye', + // 'ヂィ'=>'dyi', + // 'ヂョ'=>'dyo', + // 'ヂュ'=>'dyu', + 'ツャ' => 'tsa', + 'ツェ' => 'tse', + 'ツィ' => 'tsi', + 'ツョ' => 'tso', + 'ツ' => 'tsu', + 'トァ' => 'twa', + 'トェ' => 'twe', + 'トィ' => 'twi', + 'トォ' => 'two', + 'トゥ' => 'twu', + 'ドァ' => 'dwa', + 'ドェ' => 'dwe', + 'ドィ' => 'dwi', + 'ドォ' => 'dwo', + 'ドゥ' => 'dwu', + 'ウァ' => 'wha', + 'ウェ' => 'whe', + 'ウィ' => 'whi', + 'ウォ' => 'who', + 'ウゥ' => 'whu', + 'ヴャ' => 'vya', + 'ヴェ' => 'vye', + 'ヴィ' => 'vyi', + 'ヴョ' => 'vyo', + 'ヴュ' => 'vyu', + 'ヴァ' => 'va', + 'ヴェ' => 've', + 'ヴィ' => 'vi', + 'ヴォ' => 'vo', + 'ヴ' => 'vu', + 'ウェ' => 'we', + 'ウィ' => 'wi', + 'イェ' => 'ye', + 'ティ' => 'ti', + 'ヂィ' => 'di', + + // 2 character syllables - doubled vocal + 'アー' => 'aa', + 'エー' => 'ee', + 'イー' => 'ii', + 'オー' => 'oo', + 'ウー' => 'uu', + 'ダー' => 'daa', + 'デー' => 'dee', + 'ヂー' => 'dii', + 'ドー' => 'doo', + 'ヅー' => 'duu', + 'ハー' => 'haa', + 'ヘー' => 'hee', + 'ヒー' => 'hii', + 'ホー' => 'hoo', + 'フー' => 'fuu', + 'バー' => 'baa', + 'ベー' => 'bee', + 'ビー' => 'bii', + 'ボー' => 'boo', + 'ブー' => 'buu', + 'パー' => 'paa', + 'ペー' => 'pee', + 'ピー' => 'pii', + 'ポー' => 'poo', + 'プー' => 'puu', + 'ケー' => 'kee', + 'キー' => 'kii', + 'コー' => 'koo', + 'クー' => 'kuu', + 'カー' => 'kaa', + 'ガー' => 'gaa', + 'ゲー' => 'gee', + 'ギー' => 'gii', + 'ゴー' => 'goo', + 'グー' => 'guu', + 'マー' => 'maa', + 'メー' => 'mee', + 'ミー' => 'mii', + 'モー' => 'moo', + 'ムー' => 'muu', + 'ナー' => 'naa', + 'ネー' => 'nee', + 'ニー' => 'nii', + 'ノー' => 'noo', + 'ヌー' => 'nuu', + 'ラー' => 'raa', + 'レー' => 'ree', + 'リー' => 'rii', + 'ロー' => 'roo', + 'ルー' => 'ruu', + 'サー' => 'saa', + 'セー' => 'see', + 'シー' => 'shii', + 'ソー' => 'soo', + 'スー' => 'suu', + 'ザー' => 'zaa', + 'ゼー' => 'zee', + 'ジー' => 'jii', + 'ゾー' => 'zoo', + 'ズー' => 'zuu', + 'ター' => 'taa', + 'テー' => 'tee', + 'チー' => 'chii', + 'トー' => 'too', + 'ツー' => 'tsuu', + 'ワー' => 'waa', + 'ヲー' => 'woo', + 'ヤー' => 'yaa', + 'ヨー' => 'yoo', + 'ユー' => 'yuu', + 'ヵー' => 'kaa', + 'ヶー' => 'kee', + // old characters + 'ヱー' => 'wee', + 'ヰー' => 'wii', + + // seperate katakana 'n' + 'ンア' => 'n_a', + 'ンエ' => 'n_e', + 'ンイ' => 'n_i', + 'ンオ' => 'n_o', + 'ンウ' => 'n_u', + 'ンヤ' => 'n_ya', + 'ンヨ' => 'n_yo', + 'ンユ' => 'n_yu', + + // 2 character syllables - doubled consonants + 'ッバ' => 'bba', + 'ッベ' => 'bbe', + 'ッビ' => 'bbi', + 'ッボ' => 'bbo', + 'ッブ' => 'bbu', + 'ッパ' => 'ppa', + 'ッペ' => 'ppe', + 'ッピ' => 'ppi', + 'ッポ' => 'ppo', + 'ップ' => 'ppu', + 'ッケ' => 'kke', + 'ッキ' => 'kki', + 'ッコ' => 'kko', + 'ック' => 'kku', + 'ッカ' => 'kka', + 'ッガ' => 'gga', + 'ッゲ' => 'gge', + 'ッギ' => 'ggi', + 'ッゴ' => 'ggo', + 'ッグ' => 'ggu', + 'ッマ' => 'ma', + 'ッメ' => 'me', + 'ッミ' => 'mi', + 'ッモ' => 'mo', + 'ッム' => 'mu', + 'ッナ' => 'nna', + 'ッネ' => 'nne', + 'ッニ' => 'nni', + 'ッノ' => 'nno', + 'ッヌ' => 'nnu', + 'ッラ' => 'rra', + 'ッレ' => 'rre', + 'ッリ' => 'rri', + 'ッロ' => 'rro', + 'ッル' => 'rru', + 'ッサ' => 'ssa', + 'ッセ' => 'sse', + 'ッシ' => 'sshi', + 'ッソ' => 'sso', + 'ッス' => 'ssu', + 'ッザ' => 'zza', + 'ッゼ' => 'zze', + 'ッジ' => 'jji', + 'ッゾ' => 'zzo', + 'ッズ' => 'zzu', + 'ッタ' => 'tta', + 'ッテ' => 'tte', + 'ッチ' => 'cchi', + 'ット' => 'tto', + 'ッツ' => 'ttsu', + 'ッダ' => 'dda', + 'ッデ' => 'dde', + 'ッヂ' => 'ddi', + 'ッド' => 'ddo', + 'ッヅ' => 'ddu', + + // 1 character syllables + 'ア' => 'a', + 'エ' => 'e', + 'イ' => 'i', + 'オ' => 'o', + 'ウ' => 'u', + 'ン' => 'n', + 'ハ' => 'ha', + 'ヘ' => 'he', + 'ヒ' => 'hi', + 'ホ' => 'ho', + 'フ' => 'fu', + 'バ' => 'ba', + 'ベ' => 'be', + 'ビ' => 'bi', + 'ボ' => 'bo', + 'ブ' => 'bu', + 'パ' => 'pa', + 'ペ' => 'pe', + 'ピ' => 'pi', + 'ポ' => 'po', + 'プ' => 'pu', + 'ケ' => 'ke', + 'キ' => 'ki', + 'コ' => 'ko', + 'ク' => 'ku', + 'カ' => 'ka', + 'ガ' => 'ga', + 'ゲ' => 'ge', + 'ギ' => 'gi', + 'ゴ' => 'go', + 'グ' => 'gu', + 'マ' => 'ma', + 'メ' => 'me', + 'ミ' => 'mi', + 'モ' => 'mo', + 'ム' => 'mu', + 'ナ' => 'na', + 'ネ' => 'ne', + 'ニ' => 'ni', + 'ノ' => 'no', + 'ヌ' => 'nu', + 'ラ' => 'ra', + 'レ' => 're', + 'リ' => 'ri', + 'ロ' => 'ro', + 'ル' => 'ru', + 'サ' => 'sa', + 'セ' => 'se', + 'シ' => 'shi', + 'ソ' => 'so', + 'ス' => 'su', + 'ザ' => 'za', + 'ゼ' => 'ze', + 'ジ' => 'ji', + 'ゾ' => 'zo', + 'ズ' => 'zu', + 'タ' => 'ta', + 'テ' => 'te', + 'チ' => 'chi', + 'ト' => 'to', + 'ツ' => 'tsu', + 'ダ' => 'da', + 'デ' => 'de', + 'ヂ' => 'di', + 'ド' => 'do', + 'ヅ' => 'du', + 'ワ' => 'wa', + 'ヲ' => 'wo', + 'ヤ' => 'ya', + 'ヨ' => 'yo', + 'ユ' => 'yu', + 'ヵ' => 'ka', + 'ヶ' => 'ke', + // old characters + 'ヱ' => 'we', + 'ヰ' => 'wi', + + // convert what's left (probably only kicks in when something's missing above) + 'ァ' => 'a', + 'ェ' => 'e', + 'ィ' => 'i', + 'ォ' => 'o', + 'ゥ' => 'u', + 'ャ' => 'ya', + 'ョ' => 'yo', + 'ュ' => 'yu', + + // special characters + '・' => '_', + '、' => '_', + 'ー' => '_', + // when used with hiragana (seldom), this character would not be converted otherwise + + // 'ラ'=>'la', + // 'レ'=>'le', + // 'リ'=>'li', + // 'ロ'=>'lo', + // 'ル'=>'lu', + // 'チャ'=>'cya', + // 'チェ'=>'cye', + // 'チィ'=>'cyi', + // 'チョ'=>'cyo', + // 'チュ'=>'cyu', + // 'デャ'=>'dha', + // 'デェ'=>'dhe', + // 'ディ'=>'dhi', + // 'デョ'=>'dho', + // 'デュ'=>'dhu', + // 'リャ'=>'lya', + // 'リェ'=>'lye', + // 'リィ'=>'lyi', + // 'リョ'=>'lyo', + // 'リュ'=>'lyu', + // 'テャ'=>'tha', + // 'テェ'=>'the', + // 'ティ'=>'thi', + // 'テョ'=>'tho', + // 'テュ'=>'thu', + // 'ファ'=>'fwa', + // 'フェ'=>'fwe', + // 'フィ'=>'fwi', + // 'フォ'=>'fwo', + // 'フゥ'=>'fwu', + // 'チャ'=>'tya', + // 'チェ'=>'tye', + // 'チィ'=>'tyi', + // 'チョ'=>'tyo', + // 'チュ'=>'tyu', + // 'ジャ'=>'jya', + // 'ジェ'=>'jye', + // 'ジィ'=>'jyi', + // 'ジョ'=>'jyo', + // 'ジュ'=>'jyu', + // 'ジャ'=>'zha', + // 'ジェ'=>'zhe', + // 'ジィ'=>'zhi', + // 'ジョ'=>'zho', + // 'ジュ'=>'zhu', + // 'ジャ'=>'zya', + // 'ジェ'=>'zye', + // 'ジィ'=>'zyi', + // 'ジョ'=>'zyo', + // 'ジュ'=>'zyu', + // 'シャ'=>'sya', + // 'シェ'=>'sye', + // 'シィ'=>'syi', + // 'ショ'=>'syo', + // 'シュ'=>'syu', + // 'シ'=>'ci', + // 'フ'=>'hu', + // 'シ'=>'si', + // 'チ'=>'ti', + // 'ツ'=>'tu', + // 'イ'=>'yi', + // 'ヂ'=>'dzi', + + // "Greeklish" + 'Γ' => 'G', + 'Δ' => 'E', + 'Θ' => 'Th', + 'Λ' => 'L', + 'Ξ' => 'X', + 'Π' => 'P', + 'Σ' => 'S', + 'Φ' => 'F', + 'Ψ' => 'Ps', + 'γ' => 'g', + 'δ' => 'e', + 'θ' => 'th', + 'λ' => 'l', + 'ξ' => 'x', + 'π' => 'p', + 'σ' => 's', + 'φ' => 'f', + 'ψ' => 'ps', + + // Thai + 'ก' => 'k', + 'ข' => 'kh', + 'ฃ' => 'kh', + 'ค' => 'kh', + 'ฅ' => 'kh', + 'ฆ' => 'kh', + 'ง' => 'ng', + 'จ' => 'ch', + 'ฉ' => 'ch', + 'ช' => 'ch', + 'ซ' => 's', + 'ฌ' => 'ch', + 'ญ' => 'y', + 'ฎ' => 'd', + 'ฏ' => 't', + 'ฐ' => 'th', + 'ฑ' => 'd', + 'ฒ' => 'th', + 'ณ' => 'n', + 'ด' => 'd', + 'ต' => 't', + 'ถ' => 'th', + 'ท' => 'th', + 'ธ' => 'th', + 'น' => 'n', + 'บ' => 'b', + 'ป' => 'p', + 'ผ' => 'ph', + 'ฝ' => 'f', + 'พ' => 'ph', + 'ฟ' => 'f', + 'ภ' => 'ph', + 'ม' => 'm', + 'ย' => 'y', + 'ร' => 'r', + 'ฤ' => 'rue', + 'ฤๅ' => 'rue', + 'ล' => 'l', + 'ฦ' => 'lue', + 'ฦๅ' => 'lue', + 'ว' => 'w', + 'ศ' => 's', + 'ษ' => 's', + 'ส' => 's', + 'ห' => 'h', + 'ฬ' => 'l', + 'ฮ' => 'h', + 'ะ' => 'a', + 'ั' => 'a', + 'รร' => 'a', + 'า' => 'a', + 'ๅ' => 'a', + 'ำ' => 'am', + 'ํา' => 'am', + 'ิ' => 'i', + 'ี' => 'i', + 'ึ' => 'ue', + 'ี' => 'ue', + 'ุ' => 'u', + 'ู' => 'u', + 'เ' => 'e', + 'แ' => 'ae', + 'โ' => 'o', + 'อ' => 'o', + 'ียะ' => 'ia', + 'ีย' => 'ia', + 'ือะ' => 'uea', + 'ือ' => 'uea', + 'ัวะ' => 'ua', + 'ัว' => 'ua', + 'ใ' => 'ai', + 'ไ' => 'ai', + 'ัย' => 'ai', + 'าย' => 'ai', + 'าว' => 'ao', + 'ุย' => 'ui', + 'อย' => 'oi', + 'ือย' => 'ueai', + 'วย' => 'uai', + 'ิว' => 'io', + '็ว' => 'eo', + 'ียว' => 'iao', + '่' => '', + '้' => '', + '๊' => '', + '๋' => '', + '็' => '', + '์' => '', + '๎' => '', + 'ํ' => '', + 'ฺ' => '', + 'ๆ' => '2', + '๏' => 'o', + 'ฯ' => '-', + '๚' => '-', + '๛' => '-', + '๐' => '0', + '๑' => '1', + '๒' => '2', + '๓' => '3', + '๔' => '4', + '๕' => '5', + '๖' => '6', + '๗' => '7', + '๘' => '8', + '๙' => '9', + + // Korean + 'ㄱ' => 'k', 'ㅋ' => 'kh', + 'ㄲ' => 'kk', + 'ㄷ' => 't', + 'ㅌ' => 'th', + 'ㄸ' => 'tt', + 'ㅂ' => 'p', + 'ㅍ' => 'ph', + 'ㅃ' => 'pp', + 'ㅈ' => 'c', + 'ㅊ' => 'ch', + 'ㅉ' => 'cc', + 'ㅅ' => 's', + 'ㅆ' => 'ss', + 'ㅎ' => 'h', + 'ㅇ' => 'ng', + 'ㄴ' => 'n', + 'ㄹ' => 'l', + 'ㅁ' => 'm', + 'ㅏ' => 'a', + 'ㅓ' => 'e', + 'ㅗ' => 'o', + 'ㅜ' => 'wu', + 'ㅡ' => 'u', + 'ㅣ' => 'i', + 'ㅐ' => 'ay', + 'ㅔ' => 'ey', + 'ㅚ' => 'oy', + 'ㅘ' => 'wa', + 'ㅝ' => 'we', + 'ㅟ' => 'wi', + 'ㅙ' => 'way', + 'ㅞ' => 'wey', + 'ㅢ' => 'uy', + 'ㅑ' => 'ya', + 'ㅕ' => 'ye', + 'ㅛ' => 'oy', + 'ㅠ' => 'yu', + 'ㅒ' => 'yay', + 'ㅖ' => 'yey', +]; diff --git a/inc/Utf8/tables/specials.php b/inc/Utf8/tables/specials.php new file mode 100644 index 000000000..3dda94b39 --- /dev/null +++ b/inc/Utf8/tables/specials.php @@ -0,0 +1,620 @@ +<?php +/** + * UTF-8 array of common special characters + * + * This array should contain all special characters (not a letter or digit) + * defined in the various local charsets - it's not a complete list of non-alphanum + * characters in UTF-8. It's not perfect but should match most cases of special + * chars. + * + * The controlchars 0x00 to 0x19 are _not_ included in this array. The space 0x20 is! + * These chars are _not_ in the array either: _ (0x5f), : 0x3a, . 0x2e, - 0x2d, * 0x2a + * + * @author Andreas Gohr <andi@splitbrain.org> + * @see \dokuwiki\Utf8\Clean::stripspecials() + */ +return [ + 0x1a, // + 0x1b, // + 0x1c, // + 0x1d, // + 0x1e, // + 0x1f, // + 0x20, // <space> + 0x21, // ! + 0x22, // " + 0x23, // # + 0x24, // $ + 0x25, // % + 0x26, // & + 0x27, // ' + 0x28, // ( + 0x29, // ) + 0x2b, // + + 0x2c, // , + 0x2f, // / + 0x3b, // ; + 0x3c, // < + 0x3d, // = + 0x3e, // > + 0x3f, // ? + 0x40, // @ + 0x5b, // [ + 0x5c, // \ + 0x5d, // ] + 0x5e, // ^ + 0x60, // ` + 0x7b, // { + 0x7c, // | + 0x7d, // } + 0x7e, // ~ + 0x7f, // + 0x80, // + 0x81, // + 0x82, // + 0x83, // + 0x84, // + 0x85, //
+ 0x86, // + 0x87, // + 0x88, // + 0x89, // + 0x8a, // + 0x8b, // + 0x8c, // + 0x8d, // + 0x8e, // + 0x8f, // + 0x90, // + 0x91, // + 0x92, // + 0x93, // + 0x94, // + 0x95, // + 0x96, // + 0x97, // + 0x98, // + 0x99, // + 0x9a, // + 0x9b, // + 0x9c, // + 0x9d, // + 0x9e, // + 0x9f, // + 0xa0, // + 0xa1, // ¡ + 0xa2, // ¢ + 0xa3, // £ + 0xa4, // ¤ + 0xa5, // ¥ + 0xa6, // ¦ + 0xa7, // § + 0xa8, // ¨ + 0xa9, // © + 0xaa, // ª + 0xab, // « + 0xac, // ¬ + 0xad, // + 0xae, // ® + 0xaf, // ¯ + 0xb0, // ° + 0xb1, // ± + 0xb2, // ² + 0xb3, // ³ + 0xb4, // ´ + 0xb5, // µ + 0xb6, // ¶ + 0xb7, // · + 0xb8, // ¸ + 0xb9, // ¹ + 0xba, // º + 0xbb, // » + 0xbc, // ¼ + 0xbd, // ½ + 0xbe, // ¾ + 0xbf, // ¿ + 0xd7, // × + 0xf7, // ÷ + 0x2c7, // ˇ + 0x2d8, // ˘ + 0x2d9, // ˙ + 0x2da, // ˚ + 0x2db, // ˛ + 0x2dc, // ˜ + 0x2dd, // ˝ + 0x300, // ̀ + 0x301, // ́ + 0x303, // ̃ + 0x309, // ̉ + 0x323, // ̣ + 0x384, // ΄ + 0x385, // ΅ + 0x387, // · + 0x3c6, // φ + 0x3d1, // ϑ + 0x3d2, // ϒ + 0x3d5, // ϕ + 0x3d6, // ϖ + 0x5b0, // ְ + 0x5b1, // ֱ + 0x5b2, // ֲ + 0x5b3, // ֳ + 0x5b4, // ִ + 0x5b5, // ֵ + 0x5b6, // ֶ + 0x5b7, // ַ + 0x5b8, // ָ + 0x5b9, // ֹ + 0x5bb, // ֻ + 0x5bc, // ּ + 0x5bd, // ֽ + 0x5be, // ־ + 0x5bf, // ֿ + 0x5c0, // ׀ + 0x5c1, // ׁ + 0x5c2, // ׂ + 0x5c3, // ׃ + 0x5f3, // ׳ + 0x5f4, // ״ + 0x60c, // ، + 0x61b, // ؛ + 0x61f, // ؟ + 0x640, // ـ + 0x64b, // ً + 0x64c, // ٌ + 0x64d, // ٍ + 0x64e, // َ + 0x64f, // ُ + 0x650, // ِ + 0x651, // ّ + 0x652, // ْ + 0x66a, // ٪ + 0xe3f, // ฿ + 0x200c, // + 0x200d, // + 0x200e, // + 0x200f, // + 0x2013, // – + 0x2014, // — + 0x2015, // ― + 0x2017, // ‗ + 0x2018, // ‘ + 0x2019, // ’ + 0x201a, // ‚ + 0x201c, // “ + 0x201d, // ” + 0x201e, // „ + 0x2020, // † + 0x2021, // ‡ + 0x2022, // • + 0x2026, // … + 0x2030, // ‰ + 0x2032, // ′ + 0x2033, // ″ + 0x2039, // ‹ + 0x203a, // › + 0x2044, // ⁄ + 0x20a7, // ₧ + 0x20aa, // ₪ + 0x20ab, // ₫ + 0x20ac, // € + 0x2116, // № + 0x2118, // ℘ + 0x2122, // ™ + 0x2126, // Ω + 0x2135, // ℵ + 0x2190, // ← + 0x2191, // ↑ + 0x2192, // → + 0x2193, // ↓ + 0x2194, // ↔ + 0x2195, // ↕ + 0x21b5, // ↵ + 0x21d0, // ⇐ + 0x21d1, // ⇑ + 0x21d2, // ⇒ + 0x21d3, // ⇓ + 0x21d4, // ⇔ + 0x2200, // ∀ + 0x2202, // ∂ + 0x2203, // ∃ + 0x2205, // ∅ + 0x2206, // ∆ + 0x2207, // ∇ + 0x2208, // ∈ + 0x2209, // ∉ + 0x220b, // ∋ + 0x220f, // ∏ + 0x2211, // ∑ + 0x2212, // − + 0x2215, // ∕ + 0x2217, // ∗ + 0x2219, // ∙ + 0x221a, // √ + 0x221d, // ∝ + 0x221e, // ∞ + 0x2220, // ∠ + 0x2227, // ∧ + 0x2228, // ∨ + 0x2229, // ∩ + 0x222a, // ∪ + 0x222b, // ∫ + 0x2234, // ∴ + 0x223c, // ∼ + 0x2245, // ≅ + 0x2248, // ≈ + 0x2260, // ≠ + 0x2261, // ≡ + 0x2264, // ≤ + 0x2265, // ≥ + 0x2282, // ⊂ + 0x2283, // ⊃ + 0x2284, // ⊄ + 0x2286, // ⊆ + 0x2287, // ⊇ + 0x2295, // ⊕ + 0x2297, // ⊗ + 0x22a5, // ⊥ + 0x22c5, // ⋅ + 0x2310, // ⌐ + 0x2320, // ⌠ + 0x2321, // ⌡ + 0x2329, // 〈 + 0x232a, // 〉 + 0x2469, // ⑩ + 0x2500, // ─ + 0x2502, // │ + 0x250c, // ┌ + 0x2510, // ┐ + 0x2514, // └ + 0x2518, // ┘ + 0x251c, // ├ + 0x2524, // ┤ + 0x252c, // ┬ + 0x2534, // ┴ + 0x253c, // ┼ + 0x2550, // ═ + 0x2551, // ║ + 0x2552, // ╒ + 0x2553, // ╓ + 0x2554, // ╔ + 0x2555, // ╕ + 0x2556, // ╖ + 0x2557, // ╗ + 0x2558, // ╘ + 0x2559, // ╙ + 0x255a, // ╚ + 0x255b, // ╛ + 0x255c, // ╜ + 0x255d, // ╝ + 0x255e, // ╞ + 0x255f, // ╟ + 0x2560, // ╠ + 0x2561, // ╡ + 0x2562, // ╢ + 0x2563, // ╣ + 0x2564, // ╤ + 0x2565, // ╥ + 0x2566, // ╦ + 0x2567, // ╧ + 0x2568, // ╨ + 0x2569, // ╩ + 0x256a, // ╪ + 0x256b, // ╫ + 0x256c, // ╬ + 0x2580, // ▀ + 0x2584, // ▄ + 0x2588, // █ + 0x258c, // ▌ + 0x2590, // ▐ + 0x2591, // ░ + 0x2592, // ▒ + 0x2593, // ▓ + 0x25a0, // ■ + 0x25b2, // ▲ + 0x25bc, // ▼ + 0x25c6, // ◆ + 0x25ca, // ◊ + 0x25cf, // ● + 0x25d7, // ◗ + 0x2605, // ★ + 0x260e, // ☎ + 0x261b, // ☛ + 0x261e, // ☞ + 0x2660, // ♠ + 0x2663, // ♣ + 0x2665, // ♥ + 0x2666, // ♦ + 0x2701, // ✁ + 0x2702, // ✂ + 0x2703, // ✃ + 0x2704, // ✄ + 0x2706, // ✆ + 0x2707, // ✇ + 0x2708, // ✈ + 0x2709, // ✉ + 0x270c, // ✌ + 0x270d, // ✍ + 0x270e, // ✎ + 0x270f, // ✏ + 0x2710, // ✐ + 0x2711, // ✑ + 0x2712, // ✒ + 0x2713, // ✓ + 0x2714, // ✔ + 0x2715, // ✕ + 0x2716, // ✖ + 0x2717, // ✗ + 0x2718, // ✘ + 0x2719, // ✙ + 0x271a, // ✚ + 0x271b, // ✛ + 0x271c, // ✜ + 0x271d, // ✝ + 0x271e, // ✞ + 0x271f, // ✟ + 0x2720, // ✠ + 0x2721, // ✡ + 0x2722, // ✢ + 0x2723, // ✣ + 0x2724, // ✤ + 0x2725, // ✥ + 0x2726, // ✦ + 0x2727, // ✧ + 0x2729, // ✩ + 0x272a, // ✪ + 0x272b, // ✫ + 0x272c, // ✬ + 0x272d, // ✭ + 0x272e, // ✮ + 0x272f, // ✯ + 0x2730, // ✰ + 0x2731, // ✱ + 0x2732, // ✲ + 0x2733, // ✳ + 0x2734, // ✴ + 0x2735, // ✵ + 0x2736, // ✶ + 0x2737, // ✷ + 0x2738, // ✸ + 0x2739, // ✹ + 0x273a, // ✺ + 0x273b, // ✻ + 0x273c, // ✼ + 0x273d, // ✽ + 0x273e, // ✾ + 0x273f, // ✿ + 0x2740, // ❀ + 0x2741, // ❁ + 0x2742, // ❂ + 0x2743, // ❃ + 0x2744, // ❄ + 0x2745, // ❅ + 0x2746, // ❆ + 0x2747, // ❇ + 0x2748, // ❈ + 0x2749, // ❉ + 0x274a, // ❊ + 0x274b, // ❋ + 0x274d, // ❍ + 0x274f, // ❏ + 0x2750, // ❐ + 0x2751, // ❑ + 0x2752, // ❒ + 0x2756, // ❖ + 0x2758, // ❘ + 0x2759, // ❙ + 0x275a, // ❚ + 0x275b, // ❛ + 0x275c, // ❜ + 0x275d, // ❝ + 0x275e, // ❞ + 0x2761, // ❡ + 0x2762, // ❢ + 0x2763, // ❣ + 0x2764, // ❤ + 0x2765, // ❥ + 0x2766, // ❦ + 0x2767, // ❧ + 0x277f, // ❿ + 0x2789, // ➉ + 0x2793, // ➓ + 0x2794, // ➔ + 0x2798, // ➘ + 0x2799, // ➙ + 0x279a, // ➚ + 0x279b, // ➛ + 0x279c, // ➜ + 0x279d, // ➝ + 0x279e, // ➞ + 0x279f, // ➟ + 0x27a0, // ➠ + 0x27a1, // ➡ + 0x27a2, // ➢ + 0x27a3, // ➣ + 0x27a4, // ➤ + 0x27a5, // ➥ + 0x27a6, // ➦ + 0x27a7, // ➧ + 0x27a8, // ➨ + 0x27a9, // ➩ + 0x27aa, // ➪ + 0x27ab, // ➫ + 0x27ac, // ➬ + 0x27ad, // ➭ + 0x27ae, // ➮ + 0x27af, // ➯ + 0x27b1, // ➱ + 0x27b2, // ➲ + 0x27b3, // ➳ + 0x27b4, // ➴ + 0x27b5, // ➵ + 0x27b6, // ➶ + 0x27b7, // ➷ + 0x27b8, // ➸ + 0x27b9, // ➹ + 0x27ba, // ➺ + 0x27bb, // ➻ + 0x27bc, // ➼ + 0x27bd, // ➽ + 0x27be, // ➾ + 0x3000, // + 0x3001, // 、 + 0x3002, // 。 + 0x3003, // 〃 + 0x3008, // 〈 + 0x3009, // 〉 + 0x300a, // 《 + 0x300b, // 》 + 0x300c, // 「 + 0x300d, // 」 + 0x300e, // 『 + 0x300f, // 』 + 0x3010, // 【 + 0x3011, // 】 + 0x3012, // 〒 + 0x3014, // 〔 + 0x3015, // 〕 + 0x3016, // 〖 + 0x3017, // 〗 + 0x3018, // 〘 + 0x3019, // 〙 + 0x301a, // 〚 + 0x301b, // 〛 + 0x3036, // 〶 + 0xf6d9, // + 0xf6da, // + 0xf6db, // + 0xf8d7, // + 0xf8d8, // + 0xf8d9, // + 0xf8da, // + 0xf8db, // + 0xf8dc, // + 0xf8dd, // + 0xf8de, // + 0xf8df, // + 0xf8e0, // + 0xf8e1, // + 0xf8e2, // + 0xf8e3, // + 0xf8e4, // + 0xf8e5, // + 0xf8e6, // + 0xf8e7, // + 0xf8e8, // + 0xf8e9, // + 0xf8ea, // + 0xf8eb, // + 0xf8ec, // + 0xf8ed, // + 0xf8ee, // + 0xf8ef, // + 0xf8f0, // + 0xf8f1, // + 0xf8f2, // + 0xf8f3, // + 0xf8f4, // + 0xf8f5, // + 0xf8f6, // + 0xf8f7, // + 0xf8f8, // + 0xf8f9, // + 0xf8fa, // + 0xf8fb, // + 0xf8fc, // + 0xf8fd, // + 0xf8fe, // + 0xfe7c, // ﹼ + 0xfe7d, // ﹽ + 0xff01, // ! + 0xff02, // " + 0xff03, // # + 0xff04, // $ + 0xff05, // % + 0xff06, // & + 0xff07, // ' + 0xff08, // ( + 0xff09, // ) + 0xff09, // ) + 0xff0a, // * + 0xff0b, // + + 0xff0c, // , + 0xff0d, // - + 0xff0e, // . + 0xff0f, // / + 0xff1a, // : + 0xff1b, // ; + 0xff1c, // < + 0xff1d, // = + 0xff1e, // > + 0xff1f, // ? + 0xff20, // @ + 0xff3b, // [ + 0xff3c, // \ + 0xff3d, // ] + 0xff3e, // ^ + 0xff40, // ` + 0xff5b, // { + 0xff5c, // | + 0xff5d, // } + 0xff5e, // ~ + 0xff5f, // ⦅ + 0xff60, // ⦆ + 0xff61, // 。 + 0xff62, // 「 + 0xff63, // 」 + 0xff64, // 、 + 0xff65, // ・ + 0xffe0, // ¢ + 0xffe1, // £ + 0xffe2, // ¬ + 0xffe3, //  ̄ + 0xffe4, // ¦ + 0xffe5, // ¥ + 0xffe6, // ₩ + 0xffe8, // │ + 0xffe9, // ← + 0xffea, // ↑ + 0xffeb, // → + 0xffec, // ↓ + 0xffed, // ■ + 0xffee, // ○ + 0x1d6fc, // 𝛼 + 0x1d6fd, // 𝛽 + 0x1d6fe, // 𝛾 + 0x1d6ff, // 𝛿 + 0x1d700, // 𝜀 + 0x1d701, // 𝜁 + 0x1d702, // 𝜂 + 0x1d703, // 𝜃 + 0x1d704, // 𝜄 + 0x1d705, // 𝜅 + 0x1d706, // 𝜆 + 0x1d707, // 𝜇 + 0x1d708, // 𝜈 + 0x1d709, // 𝜉 + 0x1d70a, // 𝜊 + 0x1d70b, // 𝜋 + 0x1d70c, // 𝜌 + 0x1d70d, // 𝜍 + 0x1d70e, // 𝜎 + 0x1d70f, // 𝜏 + 0x1d710, // 𝜐 + 0x1d711, // 𝜑 + 0x1d712, // 𝜒 + 0x1d713, // 𝜓 + 0x1d714, // 𝜔 + 0x1d715, // 𝜕 + 0x1d716, // 𝜖 + 0x1d717, // 𝜗 + 0x1d718, // 𝜘 + 0x1d719, // 𝜙 + 0x1d71a, // 𝜚 + 0x1d71b, // 𝜛 + 0xc2a0, // 슠 + 0xe28087, // + 0xe280af, // + 0xe281a0, // + 0xefbbbf, // +]; diff --git a/inc/Utf8/tables/upperaccents.php b/inc/Utf8/tables/upperaccents.php new file mode 100644 index 000000000..e6e48de2c --- /dev/null +++ b/inc/Utf8/tables/upperaccents.php @@ -0,0 +1,114 @@ +<?php +/** + * UTF-8 lookup table for upper case accented letters + * + * This lookuptable defines replacements for accented characters from the ASCII-7 + * range. This are upper case letters only. + * + * @author Andreas Gohr <andi@splitbrain.org> + * @see \dokuwiki\Utf8\Clean::deaccent() + */ +return [ + 'Á' => 'A', + 'À' => 'A', + 'Ă' => 'A', + 'Â' => 'A', + 'Å' => 'A', + 'Ä' => 'Ae', + 'Ã' => 'A', + 'Ą' => 'A', + 'Ā' => 'A', + 'Æ' => 'Ae', + 'Ḃ' => 'B', + 'Ć' => 'C', + 'Ĉ' => 'C', + 'Č' => 'C', + 'Ċ' => 'C', + 'Ç' => 'C', + 'Ď' => 'D', + 'Ḋ' => 'D', + 'Đ' => 'D', + 'Ð' => 'Dh', + 'É' => 'E', + 'È' => 'E', + 'Ĕ' => 'E', + 'Ê' => 'E', + 'Ě' => 'E', + 'Ë' => 'E', + 'Ė' => 'E', + 'Ę' => 'E', + 'Ē' => 'E', + 'Ḟ' => 'F', + 'Ƒ' => 'F', + 'Ğ' => 'G', + 'Ĝ' => 'G', + 'Ġ' => 'G', + 'Ģ' => 'G', + 'Ĥ' => 'H', + 'Ħ' => 'H', + 'Í' => 'I', + 'Ì' => 'I', + 'Î' => 'I', + 'Ï' => 'I', + 'Ĩ' => 'I', + 'Į' => 'I', + 'Ī' => 'I', + 'Ĵ' => 'J', + 'Ķ' => 'K', + 'Ĺ' => 'L', + 'Ľ' => 'L', + 'Ļ' => 'L', + 'Ł' => 'L', + 'Ṁ' => 'M', + 'Ń' => 'N', + 'Ň' => 'N', + 'Ñ' => 'N', + 'Ņ' => 'N', + 'Ó' => 'O', + 'Ò' => 'O', + 'Ô' => 'O', + 'Ö' => 'Oe', + 'Ő' => 'O', + 'Õ' => 'O', + 'Ø' => 'O', + 'Ō' => 'O', + 'Ơ' => 'O', + 'Ṗ' => 'P', + 'Ŕ' => 'R', + 'Ř' => 'R', + 'Ŗ' => 'R', + 'Ś' => 'S', + 'Ŝ' => 'S', + 'Š' => 'S', + 'Ṡ' => 'S', + 'Ş' => 'S', + 'Ș' => 'S', + 'Ť' => 'T', + 'Ṫ' => 'T', + 'Ţ' => 'T', + 'Ț' => 'T', + 'Ŧ' => 'T', + 'Ú' => 'U', + 'Ù' => 'U', + 'Ŭ' => 'U', + 'Û' => 'U', + 'Ů' => 'U', + 'Ü' => 'Ue', + 'Ű' => 'U', + 'Ũ' => 'U', + 'Ų' => 'U', + 'Ū' => 'U', + 'Ư' => 'U', + 'Ẃ' => 'W', + 'Ẁ' => 'W', + 'Ŵ' => 'W', + 'Ẅ' => 'W', + 'Ý' => 'Y', + 'Ỳ' => 'Y', + 'Ŷ' => 'Y', + 'Ÿ' => 'Y', + 'Ź' => 'Z', + 'Ž' => 'Z', + 'Ż' => 'Z', + 'Þ' => 'Th', +]; diff --git a/inc/actions.php b/inc/actions.php index 9ba887860..4ea529dcd 100644 --- a/inc/actions.php +++ b/inc/actions.php @@ -6,7 +6,7 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); +use dokuwiki\Extension\Event; /** * All action processing starts here @@ -16,7 +16,7 @@ function act_dispatch(){ $router = \dokuwiki\ActionRouter::getInstance(true); $headers = array('Content-Type: text/html; charset=utf-8'); - trigger_event('ACTION_HEADERS_SEND',$headers,'act_sendheaders'); + Event::createAndTrigger('ACTION_HEADERS_SEND',$headers,'act_sendheaders'); // clear internal variables unset($router); diff --git a/inc/auth.php b/inc/auth.php index 8599891fe..3fa8d8672 100644 --- a/inc/auth.php +++ b/inc/auth.php @@ -9,9 +9,13 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); - // some ACL level defines +use dokuwiki\PassHash; +use dokuwiki\Subscriptions\RegistrationSubscriptionSender; +use dokuwiki\Extension\AuthPlugin; +use dokuwiki\Extension\PluginController; +use dokuwiki\Extension\Event; + define('AUTH_NONE', 0); define('AUTH_READ', 1); define('AUTH_EDIT', 2); @@ -34,13 +38,13 @@ define('AUTH_ADMIN', 255); */ function auth_setup() { global $conf; - /* @var DokuWiki_Auth_Plugin $auth */ + /* @var AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; global $AUTH_ACL; global $lang; - /* @var Doku_Plugin_Controller $plugin_controller */ + /* @var PluginController $plugin_controller */ global $plugin_controller; $AUTH_ACL = array(); @@ -106,7 +110,7 @@ function auth_setup() { 'sticky' => $INPUT->bool('r'), 'silent' => $INPUT->bool('http_credentials') ); - trigger_event('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper'); + Event::createAndTrigger('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper'); } //load ACL into a global array XXX @@ -211,7 +215,7 @@ function auth_login($user, $pass, $sticky = false, $silent = false) { global $USERINFO; global $conf; global $lang; - /* @var DokuWiki_Auth_Plugin $auth */ + /* @var AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; @@ -407,7 +411,7 @@ function auth_decrypt($ciphertext, $secret) { function auth_logoff($keepbc = false) { global $conf; global $USERINFO; - /* @var DokuWiki_Auth_Plugin $auth */ + /* @var AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; @@ -451,7 +455,7 @@ function auth_logoff($keepbc = false) { function auth_ismanager($user = null, $groups = null, $adminonly = false) { global $conf; global $USERINFO; - /* @var DokuWiki_Auth_Plugin $auth */ + /* @var AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; @@ -508,13 +512,13 @@ function auth_isadmin($user = null, $groups = null) { * @return bool true for membership acknowledged */ function auth_isMember($memberlist, $user, array $groups) { - /* @var DokuWiki_Auth_Plugin $auth */ + /* @var AuthPlugin $auth */ global $auth; if(!$auth) return false; // clean user and groups if(!$auth->isCaseSensitive()) { - $user = utf8_strtolower($user); + $user = \dokuwiki\Utf8\PhpString::strtolower($user); $groups = array_map('utf8_strtolower', $groups); } $user = $auth->cleanUser($user); @@ -529,7 +533,7 @@ function auth_isMember($memberlist, $user, array $groups) { // compare cleaned values foreach($members as $member) { if($member == '@ALL' ) return true; - if(!$auth->isCaseSensitive()) $member = utf8_strtolower($member); + if(!$auth->isCaseSensitive()) $member = \dokuwiki\Utf8\PhpString::strtolower($member); if($member[0] == '@') { $member = $auth->cleanGroup(substr($member, 1)); if(in_array($member, $groups)) return true; @@ -581,7 +585,7 @@ function auth_aclcheck($id, $user, $groups) { 'groups' => $groups ); - return trigger_event('AUTH_ACL_CHECK', $data, 'auth_aclcheck_cb'); + return Event::createAndTrigger('AUTH_ACL_CHECK', $data, 'auth_aclcheck_cb'); } /** @@ -601,7 +605,7 @@ function auth_aclcheck_cb($data) { global $conf; global $AUTH_ACL; - /* @var DokuWiki_Auth_Plugin $auth */ + /* @var AuthPlugin $auth */ global $auth; // if no ACL is used always return upload rights @@ -617,7 +621,7 @@ function auth_aclcheck_cb($data) { } if(!$auth->isCaseSensitive()) { - $user = utf8_strtolower($user); + $user = \dokuwiki\Utf8\PhpString::strtolower($user); $groups = array_map('utf8_strtolower', $groups); } $user = auth_nameencode($auth->cleanUser($user)); @@ -644,7 +648,7 @@ function auth_aclcheck_cb($data) { $match = preg_replace('/#.*$/', '', $match); //ignore comments $acl = preg_split('/[ \t]+/', $match); if(!$auth->isCaseSensitive() && $acl[1] !== '@ALL') { - $acl[1] = utf8_strtolower($acl[1]); + $acl[1] = \dokuwiki\Utf8\PhpString::strtolower($acl[1]); } if(!in_array($acl[1], $groups)) { continue; @@ -674,7 +678,7 @@ function auth_aclcheck_cb($data) { $match = preg_replace('/#.*$/', '', $match); //ignore comments $acl = preg_split('/[ \t]+/', $match); if(!$auth->isCaseSensitive() && $acl[1] !== '@ALL') { - $acl[1] = utf8_strtolower($acl[1]); + $acl[1] = \dokuwiki\Utf8\PhpString::strtolower($acl[1]); } if(!in_array($acl[1], $groups)) { continue; @@ -778,7 +782,7 @@ function auth_pwgen($foruser = '') { 'foruser' => $foruser ); - $evt = new Doku_Event('AUTH_PASSWORD_GENERATE', $data); + $evt = new Event('AUTH_PASSWORD_GENERATE', $data); if($evt->advise_before(true)) { $c = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones $v = 'aeiou'; //vowels @@ -810,7 +814,7 @@ function auth_pwgen($foruser = '') { */ function auth_sendPassword($user, $password) { global $lang; - /* @var DokuWiki_Auth_Plugin $auth */ + /* @var AuthPlugin $auth */ global $auth; if(!$auth) return false; @@ -845,7 +849,7 @@ function auth_sendPassword($user, $password) { function register() { global $lang; global $conf; - /* @var DokuWiki_Auth_Plugin $auth */ + /* @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; global $INPUT; @@ -887,8 +891,8 @@ function register() { } // send notification about the new user - $subscription = new Subscription(); - $subscription->send_register($login, $fullname, $email); + $subscription = new RegistrationSubscriptionSender(); + $subscription->sendRegister($login, $fullname, $email); // are we done? if(!$conf['autopasswd']) { @@ -914,7 +918,7 @@ function register() { function updateprofile() { global $conf; global $lang; - /* @var DokuWiki_Auth_Plugin $auth */ + /* @var AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; @@ -1003,7 +1007,7 @@ function updateprofile() { function auth_deleteprofile(){ global $conf; global $lang; - /* @var DokuWiki_Auth_Plugin $auth */ + /* @var \dokuwiki\Extension\AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; @@ -1057,7 +1061,7 @@ function auth_deleteprofile(){ function act_resendpwd() { global $lang; global $conf; - /* @var DokuWiki_Auth_Plugin $auth */ + /* @var AuthPlugin $auth */ global $auth; /* @var Input $INPUT */ global $INPUT; @@ -1225,7 +1229,7 @@ function auth_verifyPassword($clear, $crypt) { */ function auth_setCookie($user, $pass, $sticky) { global $conf; - /* @var DokuWiki_Auth_Plugin $auth */ + /* @var AuthPlugin $auth */ global $auth; global $USERINFO; diff --git a/inc/cache.php b/inc/cache.php index 8589d91ba..b5793c263 100644 --- a/inc/cache.php +++ b/inc/cache.php @@ -1,337 +1,57 @@ <?php -/** - * Generic class to handle caching - * - * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) - * @author Chris Smith <chris@jalakai.co.uk> - */ - -if(!defined('DOKU_INC')) die('meh.'); +// phpcs:ignoreFile +use dokuwiki\Cache\CacheParser; +use dokuwiki\Cache\CacheInstructions; +use dokuwiki\Cache\CacheRenderer; +use dokuwiki\Debug\DebugHelper; /** - * Generic handling of caching + * @deprecated since 2019-02-02 use \dokuwiki\Cache\Cache instead! */ -class cache { - public $key = ''; // primary identifier for this item - public $ext = ''; // file ext for cache data, secondary identifier for this item - public $cache = ''; // cache file name - public $depends = array(); // array containing cache dependency information, - // used by _useCache to determine cache validity - - public $_event = ''; // event to be triggered during useCache - public $_time; - public $_nocache = false; // if set to true, cache will not be used or stored - - /** - * @param string $key primary identifier - * @param string $ext file extension - */ - public function __construct($key,$ext) { - $this->key = $key; - $this->ext = $ext; - $this->cache = getCacheName($key,$ext); - } - - /** - * public method to determine whether the cache can be used - * - * to assist in centralisation of event triggering and calculation of cache statistics, - * don't override this function override _useCache() - * - * @param array $depends array of cache dependencies, support dependecies: - * 'age' => max age of the cache in seconds - * 'files' => cache must be younger than mtime of each file - * (nb. dependency passes if file doesn't exist) - * - * @return bool true if cache can be used, false otherwise - */ - public function useCache($depends=array()) { - $this->depends = $depends; - $this->_addDependencies(); - - if ($this->_event) { - return $this->_stats(trigger_event($this->_event, $this, array($this,'_useCache'))); - } else { - return $this->_stats($this->_useCache()); - } - } - - /** - * private method containing cache use decision logic - * - * this function processes the following keys in the depends array - * purge - force a purge on any non empty value - * age - expire cache if older than age (seconds) - * files - expire cache if any file in this array was updated more recently than the cache - * - * Note that this function needs to be public as it is used as callback for the event handler - * - * can be overridden - * - * @return bool see useCache() - */ - public function _useCache() { - - if ($this->_nocache) return false; // caching turned off - if (!empty($this->depends['purge'])) return false; // purge requested? - if (!($this->_time = @filemtime($this->cache))) return false; // cache exists? - - // cache too old? - if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) return false; - - if (!empty($this->depends['files'])) { - foreach ($this->depends['files'] as $file) { - if ($this->_time <= @filemtime($file)) return false; // cache older than files it depends on? - } - } - - return true; - } - - /** - * add dependencies to the depends array - * - * this method should only add dependencies, - * it should not remove any existing dependencies and - * it should only overwrite a dependency when the new value is more stringent than the old - */ - protected function _addDependencies() { - global $INPUT; - if ($INPUT->has('purge')) $this->depends['purge'] = true; // purge requested - } - - /** - * retrieve the cached data - * - * @param bool $clean true to clean line endings, false to leave line endings alone - * @return string cache contents - */ - public function retrieveCache($clean=true) { - return io_readFile($this->cache, $clean); - } - - /** - * cache $data - * - * @param string $data the data to be cached - * @return bool true on success, false otherwise - */ - public function storeCache($data) { - if ($this->_nocache) return false; - - return io_savefile($this->cache, $data); - } - - /** - * remove any cached data associated with this cache instance - */ - public function removeCache() { - @unlink($this->cache); - } - - /** - * Record cache hits statistics. - * (Only when debugging allowed, to reduce overhead.) - * - * @param bool $success result of this cache use attempt - * @return bool pass-thru $success value - */ - protected function _stats($success) { - global $conf; - static $stats = null; - static $file; - - if (!$conf['allowdebug']) { return $success; } - - if (is_null($stats)) { - $file = $conf['cachedir'].'/cache_stats.txt'; - $lines = explode("\n",io_readFile($file)); - - foreach ($lines as $line) { - $i = strpos($line,','); - $stats[substr($line,0,$i)] = $line; - } - } - - if (isset($stats[$this->ext])) { - list($ext,$count,$hits) = explode(',',$stats[$this->ext]); - } else { - $ext = $this->ext; - $count = 0; - $hits = 0; - } - - $count++; - if ($success) $hits++; - $stats[$this->ext] = "$ext,$count,$hits"; - - io_saveFile($file,join("\n",$stats)); - - return $success; +class cache extends \dokuwiki\Cache\Cache +{ + public function __construct($key, $ext) + { + DebugHelper::dbgDeprecatedFunction(dokuwiki\Cache\Cache::class); + parent::__construct($key, $ext); } } /** - * Parser caching + * @deprecated since 2019-02-02 use \dokuwiki\Cache\CacheParser instead! */ -class cache_parser extends cache { +class cache_parser extends \dokuwiki\Cache\CacheParser +{ - public $file = ''; // source file for cache - public $mode = ''; // input mode (represents the processing the input file will undergo) - public $page = ''; - - public $_event = 'PARSER_CACHE_USE'; - - /** - * - * @param string $id page id - * @param string $file source file for cache - * @param string $mode input mode - */ - public function __construct($id, $file, $mode) { - if ($id) $this->page = $id; - $this->file = $file; - $this->mode = $mode; - - parent::__construct($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.'.$mode); - } - - /** - * method contains cache use decision logic - * - * @return bool see useCache() - */ - public function _useCache() { - - if (!file_exists($this->file)) return false; // source exists? - return parent::_useCache(); - } - - protected function _addDependencies() { - - // parser cache file dependencies ... - $files = array($this->file, // ... source - DOKU_INC.'inc/parser/parser.php', // ... parser - DOKU_INC.'inc/parser/handler.php', // ... handler - ); - $files = array_merge($files, getConfigFiles('main')); // ... wiki settings - - $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files; - parent::_addDependencies(); + public function __construct($id, $file, $mode) + { + DebugHelper::dbgDeprecatedFunction(CacheParser::class); + parent::__construct($id, $file, $mode); } } /** - * Caching of data of renderer + * @deprecated since 2019-02-02 use \dokuwiki\Cache\CacheRenderer instead! */ -class cache_renderer extends cache_parser { - - /** - * method contains cache use decision logic - * - * @return bool see useCache() - */ - public function _useCache() { - global $conf; - - if (!parent::_useCache()) return false; - - if (!isset($this->page)) { - return true; - } - - if ($this->_time < @filemtime(metaFN($this->page,'.meta'))) return false; // meta cache older than file it depends on? - - // check current link existence is consistent with cache version - // first check the purgefile - // - if the cache is more recent than the purgefile we know no links can have been updated - if ($this->_time >= @filemtime($conf['cachedir'].'/purgefile')) { - return true; - } - - // for wiki pages, check metadata dependencies - $metadata = p_get_metadata($this->page); +class cache_renderer extends \dokuwiki\Cache\CacheRenderer +{ - if (!isset($metadata['relation']['references']) || - empty($metadata['relation']['references'])) { - return true; - } - - foreach ($metadata['relation']['references'] as $id => $exists) { - if ($exists != page_exists($id,'',false)) return false; - } - - return true; - } - - protected function _addDependencies() { - global $conf; - - // default renderer cache file 'age' is dependent on 'cachetime' setting, two special values: - // -1 : do not cache (should not be overridden) - // 0 : cache never expires (can be overridden) - no need to set depends['age'] - if ($conf['cachetime'] == -1) { - $this->_nocache = true; - return; - } elseif ($conf['cachetime'] > 0) { - $this->depends['age'] = isset($this->depends['age']) ? - min($this->depends['age'],$conf['cachetime']) : $conf['cachetime']; - } - - // renderer cache file dependencies ... - $files = array( - DOKU_INC.'inc/parser/'.$this->mode.'.php', // ... the renderer - ); - - // page implies metadata and possibly some other dependencies - if (isset($this->page)) { - - $valid = p_get_metadata($this->page, 'date valid'); // for xhtml this will render the metadata if needed - if (!empty($valid['age'])) { - $this->depends['age'] = isset($this->depends['age']) ? - min($this->depends['age'],$valid['age']) : $valid['age']; - } - } - - $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files; - parent::_addDependencies(); + public function __construct($id, $file, $mode) + { + DebugHelper::dbgDeprecatedFunction(CacheRenderer::class); + parent::__construct($id, $file, $mode); } } /** - * Caching of parser instructions + * @deprecated since 2019-02-02 use \dokuwiki\Cache\CacheInstructions instead! */ -class cache_instructions extends cache_parser { - - /** - * @param string $id page id - * @param string $file source file for cache - */ - public function __construct($id, $file) { - parent::__construct($id, $file, 'i'); - } - - /** - * retrieve the cached data - * - * @param bool $clean true to clean line endings, false to leave line endings alone - * @return array cache contents - */ - public function retrieveCache($clean=true) { - $contents = io_readFile($this->cache, false); - return !empty($contents) ? unserialize($contents) : array(); - } - - /** - * cache $instructions - * - * @param array $instructions the instruction to be cached - * @return bool true on success, false otherwise - */ - public function storeCache($instructions) { - if ($this->_nocache) return false; - - return io_savefile($this->cache,serialize($instructions)); +class cache_instructions extends \dokuwiki\Cache\CacheInstructions +{ + public function __construct($id, $file) + { + DebugHelper::dbgDeprecatedFunction(CacheInstructions::class); + parent::__construct($id, $file); } } diff --git a/inc/changelog.php b/inc/changelog.php index b70945b1c..60c49c2ee 100644 --- a/inc/changelog.php +++ b/inc/changelog.php @@ -93,7 +93,7 @@ function addLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', $extr 'type' => str_replace($strip, '', $type), 'id' => $id, 'user' => $user, - 'sum' => utf8_substr(str_replace($strip, '', $summary), 0, 255), + 'sum' => \dokuwiki\Utf8\PhpString::substr(str_replace($strip, '', $summary), 0, 255), 'extra' => str_replace($strip, '', $extra), 'sizechange' => $sizechange ); @@ -104,13 +104,15 @@ function addLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', $extr if (!$wasRemoved) { $oldmeta = p_read_metadata($id); $meta = array(); - if ($wasCreated && empty($oldmeta['persistent']['date']['created'])){ // newly created + if ($wasCreated && empty($oldmeta['persistent']['date']['created'])){ + // newly created $meta['date']['created'] = $created; if ($user){ $meta['creator'] = $INFO['userinfo']['name']; $meta['user'] = $user; } - } elseif (($wasCreated || $wasReverted) && !empty($oldmeta['persistent']['date']['created'])) { // re-created / restored + } elseif (($wasCreated || $wasReverted) && !empty($oldmeta['persistent']['date']['created'])) { + // re-created / restored $meta['date']['created'] = $oldmeta['persistent']['date']['created']; $meta['date']['modified'] = $created; // use the files ctime here $meta['creator'] = $oldmeta['persistent']['creator']; @@ -147,7 +149,15 @@ function addLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', $extr * - (none, so far) * @param null|int $sizechange Change of filesize */ -function addMediaLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', $extra='', $flags=null, $sizechange = null){ +function addMediaLogEntry( + $date, + $id, + $type=DOKU_CHANGE_TYPE_EDIT, + $summary='', + $extra='', + $flags=null, + $sizechange = null) +{ global $conf; /** @var Input $INPUT */ global $INPUT; @@ -170,7 +180,7 @@ function addMediaLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', 'type' => str_replace($strip, '', $type), 'id' => $id, 'user' => $user, - 'sum' => utf8_substr(str_replace($strip, '', $summary), 0, 255), + 'sum' => \dokuwiki\Utf8\PhpString::substr(str_replace($strip, '', $summary), 0, 255), 'extra' => str_replace($strip, '', $extra), 'sizechange' => $sizechange ); @@ -244,7 +254,12 @@ function getRecents($first,$num,$ns='',$flags=0){ } } if (($flags & RECENTS_MEDIA_PAGES_MIXED) && empty($media_rec) && $media_lines_position >= 0) { - $media_rec = _handleRecent(@$media_lines[$media_lines_position], $ns, $flags | RECENTS_MEDIA_CHANGES, $seen); + $media_rec = _handleRecent( + @$media_lines[$media_lines_position], + $ns, + $flags | RECENTS_MEDIA_CHANGES, + $seen + ); if (!$media_rec) { $media_lines_position --; continue; @@ -384,691 +399,3 @@ function _handleRecent($line,$ns,$flags,&$seen){ return $recent; } - -/** - * Class ChangeLog - * methods for handling of changelog of pages or media files - */ -abstract class ChangeLog { - - /** @var string */ - protected $id; - /** @var int */ - protected $chunk_size; - /** @var array */ - protected $cache; - - /** - * Constructor - * - * @param string $id page id - * @param int $chunk_size maximum block size read from file - */ - public function __construct($id, $chunk_size = 8192) { - global $cache_revinfo; - - $this->cache =& $cache_revinfo; - if(!isset($this->cache[$id])) { - $this->cache[$id] = array(); - } - - $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 - * - * @return string path to file - */ - abstract protected function getFilename(); - - /** - * Get the changelog information for a specific page id and 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 - * @return bool|array false or array with entries: - * - date: unix timestamp - * - ip: IPv4 address (127.0.0.1) - * - type: log line type - * - id: page id - * - user: user name - * - sum: edit summary (or action reason) - * - extra: extra data (varies by line type) - * - * @author Ben Coburn <btcoburn@silicodon.net> - * @author Kate Arzamastseva <pshns@ukr.net> - */ - public function getRevisionInfo($rev) { - $rev = max($rev, 0); - - // check if it's already in the memory cache - if(isset($this->cache[$this->id]) && isset($this->cache[$this->id][$rev])) { - return $this->cache[$this->id][$rev]; - } - - //read lines from changelog - list($fp, $lines) = $this->readloglines($rev); - if($fp) { - fclose($fp); - } - if(empty($lines)) return false; - - // parse and cache changelog lines - foreach($lines as $value) { - $tmp = parseChangelogLine($value); - if($tmp !== false) { - $this->cache[$this->id][$tmp['date']] = $tmp; - } - } - if(!isset($this->cache[$this->id][$rev])) { - return false; - } - return $this->cache[$this->id][$rev]; - } - - /** - * Return a list of page revisions numbers - * - * Does not guarantee that the revision exists in the attic, - * only that a line with the date exists in the changelog. - * By default the current revision is skipped. - * - * The current revision is automatically skipped when the page exists. - * See $INFO['meta']['last_change'] for the current revision. - * A negative $first let read the current revision too. - * - * For efficiency, the log lines are parsed and cached for later - * calls to getRevisionInfo. Large changelog files are read - * backwards in chunks until the requested number of changelog - * lines are recieved. - * - * @param int $first skip the first n changelog lines - * @param int $num number of revisions to return - * @return array with the revision timestamps - * - * @author Ben Coburn <btcoburn@silicodon.net> - * @author Kate Arzamastseva <pshns@ukr.net> - */ - public function getRevisions($first, $num) { - $revs = array(); - $lines = array(); - $count = 0; - - $num = max($num, 0); - if($num == 0) { - return $revs; - } - - if($first < 0) { - $first = 0; - } else if(file_exists($this->getFilename())) { - // skip current 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) { - // read whole file - $lines = file($file); - if($lines === false) { - return $revs; - } - } else { - // read chunks backwards - $fp = fopen($file, 'rb'); // "file pointer" - if($fp === false) { - return $revs; - } - fseek($fp, 0, SEEK_END); - $tail = ftell($fp); - - // chunk backwards - $finger = max($tail - $this->chunk_size, 0); - while($count < $num + $first) { - $nl = $this->getNewlinepointer($fp, $finger); - - // was the chunk big enough? if not, take another bite - if($nl > 0 && $tail <= $nl) { - $finger = max($finger - $this->chunk_size, 0); - continue; - } else { - $finger = $nl; - } - - // read chunk - $chunk = ''; - $read_size = max($tail - $finger, 0); // found chunk size - $got = 0; - while($got < $read_size && !feof($fp)) { - $tmp = @fread($fp, max(min($this->chunk_size, $read_size - $got), 0)); - if($tmp === false) { - break; - } //error state - $got += strlen($tmp); - $chunk .= $tmp; - } - $tmp = explode("\n", $chunk); - array_pop($tmp); // remove trailing newline - - // combine with previous chunk - $count += count($tmp); - $lines = array_merge($tmp, $lines); - - // next chunk - if($finger == 0) { - break; - } // already read all the lines - else { - $tail = $finger; - $finger = max($tail - $this->chunk_size, 0); - } - } - fclose($fp); - } - - // skip parsing extra lines - $num = max(min(count($lines) - $first, $num), 0); - if ($first > 0 && $num > 0) { $lines = array_slice($lines, max(count($lines) - $first - $num, 0), $num); } - else if($first > 0 && $num == 0) { $lines = array_slice($lines, 0, max(count($lines) - $first, 0)); } - else if($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']; - } - } - - return $revs; - } - - /** - * Get the nth revision left or right handside 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. - * - * 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 - * @return bool|int - * timestamp of the requested revision - * otherwise false - */ - public function getRelativeRevision($rev, $direction) { - $rev = max($rev, 0); - $direction = (int) $direction; - - //no direction given or last rev, so no follow-up - if(!$direction || ($direction > 0 && $this->isCurrentRevision($rev))) { - return false; - } - - //get lines from changelog - 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 - // also parse and cache changelog lines for getRevisionInfo(). - $revcounter = 0; - $relativerev = false; - $checkotherchunck = true; //always runs once - while(!$relativerev && $checkotherchunck) { - $tmp = array(); - //parse in normal or reverse order - $count = count($lines); - if($direction > 0) { - $start = 0; - $step = 1; - } else { - $start = $count - 1; - $step = -1; - } - for($i = $start; $i >= 0 && $i < $count; $i = $i + $step) { - $tmp = parseChangelogLine($lines[$i]); - if($tmp !== false) { - $this->cache[$this->id][$tmp['date']] = $tmp; - //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']; - } - } - } - } - - //true when $rev is found, but not the wanted follow-up. - $checkotherchunck = $fp - && ($tmp['date'] == $rev || ($revcounter > 0 && !$relativerev)) - && !(($tail == $eof && $direction > 0) || ($head == 0 && $direction < 0)); - - if($checkotherchunck) { - list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, $direction); - - if(empty($lines)) break; - } - } - if($fp) { - fclose($fp); - } - - return $relativerev; - } - - /** - * Returns revisions around rev1 and rev2 - * When available it returns $max entries for each revision - * - * @param int $rev1 oldest revision timestamp - * @param int $rev2 newest revision timestamp (0 looks up last revision) - * @param int $max maximum number of revisions returned - * @return array with two arrays with revisions surrounding rev1 respectively rev2 - */ - public function getRevisionsAround($rev1, $rev2, $max = 50) { - $max = floor(abs($max) / 2)*2 + 1; - $rev1 = max($rev1, 0); - $rev2 = max($rev2, 0); - - if($rev2) { - if($rev2 < $rev1) { - $rev = $rev2; - $rev2 = $rev1; - $rev1 = $rev; - } - } else { - //empty right side means a removed page. Look up last revision. - $revs = $this->getRevisions(-1, 1); - $rev2 = $revs[0]; - } - //collect revisions around rev2 - 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); - 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; - 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']; - $index++; - - if($index > floor($max / 2)) break 2; - } - } - - list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, -1); - } - sort($revs1); - //return wanted selection - $revs1 = array_slice($revs1, max($index - floor($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 - * - * @param number $date_at timestamp - * @return string revision ('' for current) - */ - function getLastRevisionAt($date_at){ - //requested date_at(timestamp) younger or equal then modified_time($this->id) => load current - if(file_exists($this->getFilename()) && $date_at >= @filemtime($this->getFilename())) { - return ''; - } else if ($rev = $this->getRelativeRevision($date_at+1, -1)) { //+1 to get also the requested date revision - return $rev; - } else { - return false; - } - } - - /** - * 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 - * - * @param int $rev revision timestamp - * @param int $max maximum number of revisions to be returned - * @return bool|array - * return array with entries: - * - $requestedrevs: array of with $max revision timestamps - * - $revs: all parsed revision timestamps - * - $fp: filepointer 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 - * otherwise false - */ - protected function retrieveRevisionsAround($rev, $max) { - //get lines from changelog - 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; - 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) { - //count revs after reference $rev - $aftercount++; - if($aftercount == 1) $beforecount = count($revs); - } - //enough revs after reference $rev? - if($aftercount > floor($max / 2)) break 2; - } - } - //retrieve next chunk - list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, 1); - } - if($aftercount == 0) return false; - - $lasttail = $tail; - - //read additional chuncks 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; - 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++; - //enough revs before reference $rev? - if($beforecount > max(floor($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); - } -} - -/** - * Class PageChangelog handles changelog of a wiki page - */ -class PageChangelog extends ChangeLog { - - /** - * Returns path to changelog - * - * @return string path to file - */ - protected function getChangelogFilename() { - return metaFN($this->id, '.changes'); - } - - /** - * Returns path to current page/media - * - * @return string path to file - */ - protected function getFilename() { - return wikiFN($this->id); - } -} - -/** - * Class MediaChangelog handles changelog of a media file - */ -class MediaChangelog extends ChangeLog { - - /** - * Returns path to changelog - * - * @return string path to file - */ - protected function getChangelogFilename() { - return mediaMetaFN($this->id, '.changes'); - } - - /** - * Returns path to current page/media - * - * @return string path to file - */ - protected function getFilename() { - return mediaFN($this->id); - } -} diff --git a/inc/cli.php b/inc/cli.php index cb4fc587d..e64798386 100644 --- a/inc/cli.php +++ b/inc/cli.php @@ -5,6 +5,7 @@ * * All DokuWiki commandline scripts should inherit from this class and implement the abstract methods. * + * @deprecated 2017-11-10 * @author Andreas Gohr <andi@splitbrain.org> */ abstract class DokuCLI { diff --git a/inc/common.php b/inc/common.php index 2d1f9703c..6822ccbd3 100644 --- a/inc/common.php +++ b/inc/common.php @@ -6,7 +6,13 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); +use dokuwiki\Cache\CacheInstructions; +use dokuwiki\Cache\CacheRenderer; +use dokuwiki\ChangeLog\PageChangeLog; +use dokuwiki\Subscriptions\PageSubscriptionSender; +use dokuwiki\Subscriptions\SubscriberManager; +use dokuwiki\Extension\AuthPlugin; +use dokuwiki\Extension\Event; /** * These constants are used with the recents function @@ -100,7 +106,7 @@ function getSecurityToken() { // CSRF checks are only for logged in users - do not generate for anonymous if(trim($user) == '' || trim($session) == '') return ''; - return PassHash::hmac('md5', $session.$user, auth_cookiesalt()); + return \dokuwiki\PassHash::hmac('md5', $session.$user, auth_cookiesalt()); } /** @@ -212,8 +218,8 @@ function pageinfo() { $info['rev'] = $REV; if($INPUT->server->has('REMOTE_USER')) { - $sub = new Subscription(); - $info['subscribed'] = $sub->user_subscription(); + $subManager = new SubscriberManager(); + $info['subscribed'] = $subManager->userSubscription(); } else { $info['subscribed'] = false; } @@ -485,7 +491,9 @@ function wl($id = '', $urlParameters = '', $absolute = false, $separator = '& global $conf; if(is_array($urlParameters)) { if(isset($urlParameters['rev']) && !$urlParameters['rev']) unset($urlParameters['rev']); - if(isset($urlParameters['at']) && $conf['date_at_format']) $urlParameters['at'] = date($conf['date_at_format'],$urlParameters['at']); + if(isset($urlParameters['at']) && $conf['date_at_format']) { + $urlParameters['at'] = date($conf['date_at_format'], $urlParameters['at']); + } $urlParameters = buildURLparams($urlParameters, $separator); } else { $urlParameters = str_replace(',', $separator, $urlParameters); @@ -709,7 +717,13 @@ function checkwordblock($text = '') { if(!$text) $text = "$PRE $TEXT $SUF $SUM"; // we prepare the text a tiny bit to prevent spammers circumventing URL checks - $text = preg_replace('!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i', '\1http://\2 \2\3', $text); + // phpcs:disable Generic.Files.LineLength.TooLong + $text = preg_replace( + '!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i', + '\1http://\2 \2\3', + $text + ); + // phpcs:enable $wordblocks = getWordblocks(); // how many lines to read at once (to work around some PCRE limits) @@ -745,7 +759,7 @@ function checkwordblock($text = '') { $callback = function () { return true; }; - return trigger_event('COMMON_WORDBLOCK_BLOCKED', $data, $callback, true); + return Event::createAndTrigger('COMMON_WORDBLOCK_BLOCKED', $data, $callback, true); } } return false; @@ -835,6 +849,7 @@ function clientIP($single = false) { * * @link http://www.brainhandles.com/2007/10/15/detecting-mobile-browsers/#code * + * @deprecated 2018-04-27 you probably want media queries instead anyway * @return bool if true, client is mobile browser; otherwise false */ function clientismobile() { @@ -847,7 +862,18 @@ function clientismobile() { if(!$INPUT->server->has('HTTP_USER_AGENT')) return false; - $uamatches = 'midp|j2me|avantg|docomo|novarra|palmos|palmsource|240x320|opwv|chtml|pda|windows ce|mmp\/|blackberry|mib\/|symbian|wireless|nokia|hand|mobi|phone|cdm|up\.b|audio|SIE\-|SEC\-|samsung|HTC|mot\-|mitsu|sagem|sony|alcatel|lg|erics|vx|NEC|philips|mmm|xx|panasonic|sharp|wap|sch|rover|pocket|benq|java|pt|pg|vox|amoi|bird|compal|kg|voda|sany|kdd|dbt|sendo|sgh|gradi|jb|\d\d\di|moto'; + $uamatches = join( + '|', + [ + 'midp', 'j2me', 'avantg', 'docomo', 'novarra', 'palmos', 'palmsource', '240x320', 'opwv', + 'chtml', 'pda', 'windows ce', 'mmp\/', 'blackberry', 'mib\/', 'symbian', 'wireless', 'nokia', + 'hand', 'mobi', 'phone', 'cdm', 'up\.b', 'audio', 'SIE\-', 'SEC\-', 'samsung', 'HTC', 'mot\-', + 'mitsu', 'sagem', 'sony', 'alcatel', 'lg', 'erics', 'vx', 'NEC', 'philips', 'mmm', 'xx', + 'panasonic', 'sharp', 'wap', 'sch', 'rover', 'pocket', 'benq', 'java', 'pt', 'pg', 'vox', + 'amoi', 'bird', 'compal', 'kg', 'voda', 'sany', 'kdd', 'dbt', 'sendo', 'sgh', 'gradi', 'jb', + '\d\d\di', 'moto' + ] + ); if(preg_match("/$uamatches/i", $INPUT->server->str('HTTP_USER_AGENT'))) return true; @@ -991,7 +1017,7 @@ function cleanText($text) { // if the text is not valid UTF-8 we simply assume latin1 // this won't break any worse than it breaks with the wrong encoding // but might actually fix the problem in many cases - if(!utf8_check($text)) $text = utf8_encode($text); + if(!\dokuwiki\Utf8\Clean::isUtf8($text)) $text = utf8_encode($text); return $text; } @@ -1060,7 +1086,7 @@ function pageTemplate($id) { 'doreplace' => true // should wildcard replacements be done on the text? ); - $evt = new Doku_Event('COMMON_PAGETPL_LOAD', $data); + $evt = new Event('COMMON_PAGETPL_LOAD', $data); if($evt->advise_before(true)) { // the before event might have loaded the content already if(empty($data['tpl'])) { @@ -1147,12 +1173,12 @@ function parsePageTemplate(&$data) { utf8_ucwords(curNS($id)), utf8_strtoupper(curNS($id)), $file, - utf8_ucfirst($file), - utf8_strtoupper($file), + \dokuwiki\Utf8\PhpString::ucfirst($file), + \dokuwiki\Utf8\PhpString::strtoupper($file), $page, - utf8_ucfirst($page), - utf8_ucwords($page), - utf8_strtoupper($page), + \dokuwiki\Utf8\PhpString::ucfirst($page), + \dokuwiki\Utf8\PhpString::ucwords($page), + \dokuwiki\Utf8\PhpString::strtoupper($page), $INPUT->server->str('REMOTE_USER'), $USERINFO['name'], $USERINFO['mail'], @@ -1269,9 +1295,17 @@ function detectExternalEdit($id) { $filesize_new = filesize($fileLastMod); $sizechange = $filesize_new - $filesize_old; - addLogEntry($lastMod, $id, DOKU_CHANGE_TYPE_EDIT, $lang['external_edit'], '', array('ExternalEdit'=> true), $sizechange); + addLogEntry( + $lastMod, + $id, + DOKU_CHANGE_TYPE_EDIT, + $lang['external_edit'], + '', + array('ExternalEdit' => true), + $sizechange + ); // remove soon to be stale instructions - $cache = new cache_instructions($id, $fileLastMod); + $cache = new CacheInstructions($id, $fileLastMod); $cache->removeCache(); } } @@ -1335,7 +1369,7 @@ function saveWikiText($id, $text, $summary, $minor = false) { $svdta['changeType'] = DOKU_CHANGE_TYPE_MINOR_EDIT; } - $event = new Doku_Event('COMMON_WIKIPAGE_SAVE', $svdta); + $event = new Event('COMMON_WIKIPAGE_SAVE', $svdta); if(!$event->advise_before()) return; // if the content has not been changed, no save happens (plugins may override this) @@ -1354,7 +1388,7 @@ function saveWikiText($id, $text, $summary, $minor = false) { if($svdta['changeType'] == DOKU_CHANGE_TYPE_DELETE) { // Send "update" event with empty data, so plugins can react to page deletion $data = array(array($svdta['file'], '', false), getNS($id), noNS($id), false); - trigger_event('IO_WIKIPAGE_WRITE', $data); + Event::createAndTrigger('IO_WIKIPAGE_WRITE', $data); // pre-save deleted revision @touch($svdta['file']); clearstatcache(); @@ -1362,7 +1396,8 @@ function saveWikiText($id, $text, $summary, $minor = false) { // remove empty file @unlink($svdta['file']); $filesize_new = 0; - // don't remove old meta info as it should be saved, plugins can use IO_WIKIPAGE_WRITE for removing their metadata... + // 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($id); // remove empty namespaces @@ -1379,7 +1414,15 @@ function saveWikiText($id, $text, $summary, $minor = false) { $event->advise_after(); - addLogEntry($svdta['newRevision'], $svdta['id'], $svdta['changeType'], $svdta['summary'], $svdta['changeInfo'], null, $svdta['sizechange']); + addLogEntry( + $svdta['newRevision'], + $svdta['id'], + $svdta['changeType'], + $svdta['summary'], + $svdta['changeInfo'], + null, + $svdta['sizechange'] + ); // send notify mails notify($svdta['id'], 'admin', $svdta['oldRevision'], $svdta['summary'], $minor); @@ -1392,7 +1435,7 @@ function saveWikiText($id, $text, $summary, $minor = false) { if(useHeading('content')) { $pages = ft_backlinks($id, true); foreach($pages as $page) { - $cache = new cache_renderer($page, wikiFN($page), 'xhtml'); + $cache = new CacheRenderer($page, wikiFN($page), 'xhtml'); $cache->removeCache(); } } @@ -1443,7 +1486,7 @@ function notify($id, $who, $rev = '', $summary = '', $minor = false, $replace = if(!actionOK('subscribe')) return false; //subscribers enabled? if($conf['useacl'] && $INPUT->server->str('REMOTE_USER') && $minor) return false; //skip minors $data = array('id' => $id, 'addresslist' => '', 'self' => false, 'replacements' => $replace); - trigger_event( + Event::createAndTrigger( 'COMMON_NOTIFY_ADDRESSLIST', $data, array(new Subscription(), 'notifyaddresses') ); @@ -1455,8 +1498,8 @@ function notify($id, $who, $rev = '', $summary = '', $minor = false, $replace = } // prepare content - $subscription = new Subscription(); - return $subscription->send_diff($to, $tpl, $id, $rev, $summary); + $subscription = new PageSubscriptionSender(); + return $subscription->sendPageDiff($to, $tpl, $id, $rev, $summary); } /** @@ -1660,7 +1703,7 @@ function php_to_byte($value) { case 'K': $ret = intval(substr($value, 0, -1)) * 1024; break; - default; + default: $ret = intval($value); break; } @@ -1693,12 +1736,15 @@ function preg_quote_cb($string) { * @return string */ function shorten($keep, $short, $max, $min = 9, $char = '…') { - $max = $max - utf8_strlen($keep); + $max = $max - \dokuwiki\Utf8\PhpString::strlen($keep); if($max < $min) return $keep; - $len = utf8_strlen($short); + $len = \dokuwiki\Utf8\PhpString::strlen($short); if($len <= $max) return $keep.$short; $half = floor($max / 2); - return $keep.utf8_substr($short, 0, $half - 1).$char.utf8_substr($short, $len - $half); + return $keep . + \dokuwiki\Utf8\PhpString::substr($short, 0, $half - 1) . + $char . + \dokuwiki\Utf8\PhpString::substr($short, $len - $half); } /** @@ -1726,7 +1772,7 @@ function editorinfo($username, $textonly = false) { */ function userlink($username = null, $textonly = false) { global $conf, $INFO; - /** @var DokuWiki_Auth_Plugin $auth */ + /** @var AuthPlugin $auth */ global $auth; /** @var Input $INPUT */ global $INPUT; @@ -1753,11 +1799,12 @@ function userlink($username = null, $textonly = false) { if($textonly){ $data['name'] = $INFO['userinfo']['name']. ' (' . $INPUT->server->str('REMOTE_USER') . ')'; }else { - $data['name'] = '<bdi>' . hsc($INFO['userinfo']['name']) . '</bdi> (<bdi>' . hsc($INPUT->server->str('REMOTE_USER')) . '</bdi>)'; + $data['name'] = '<bdi>' . hsc($INFO['userinfo']['name']) . '</bdi> '. + '(<bdi>' . hsc($INPUT->server->str('REMOTE_USER')) . '</bdi>)'; } } - $evt = new Doku_Event('COMMON_USER_LINK', $data); + $evt = new Event('COMMON_USER_LINK', $data); if($evt->advise_before(true)) { if(empty($data['name'])) { if($auth) $info = $auth->getUserData($username); @@ -2037,7 +2084,8 @@ function set_doku_pref($pref, $val) { } $cookieVal = implode('#', $parts); } else if ($orig === false && $val !== false) { - $cookieVal = ($_COOKIE['DOKU_PREFS'] ? $_COOKIE['DOKU_PREFS'].'#' : '').rawurlencode($pref).'#'.rawurlencode($val); + $cookieVal = ($_COOKIE['DOKU_PREFS'] ? $_COOKIE['DOKU_PREFS'] . '#' : '') . + rawurlencode($pref) . '#' . rawurlencode($val); } $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir']; diff --git a/inc/confutils.php b/inc/confutils.php index 59147010f..de4d418b4 100644 --- a/inc/confutils.php +++ b/inc/confutils.php @@ -11,6 +11,9 @@ * (scheme.conf & stopwords.conf), e.g. * !gopher */ + +use dokuwiki\Extension\AuthPlugin; +use dokuwiki\Extension\Event; const DOKU_CONF_NEGATION = '!'; /** @@ -145,7 +148,7 @@ function getCdnUrls() { 'versions' => $versions, 'src' => &$src ); - $event = new Doku_Event('CONFUTIL_CDN_SELECT', $data); + $event = new Event('CONFUTIL_CDN_SELECT', $data); if($event->advise_before()) { if(!$conf['jquerycdn']) { $jqmod = md5(join('-', $versions)); @@ -155,9 +158,18 @@ function getCdnUrls() { $src[] = sprintf('https://code.jquery.com/jquery-migrate-%s.min.js', $versions['JQM_VERSION']); $src[] = sprintf('https://code.jquery.com/ui/%s/jquery-ui.min.js', $versions['JQUI_VERSION']); } elseif($conf['jquerycdn'] == 'cdnjs') { - $src[] = sprintf('https://cdnjs.cloudflare.com/ajax/libs/jquery/%s/jquery.min.js', $versions['JQ_VERSION']); - $src[] = sprintf('https://cdnjs.cloudflare.com/ajax/libs/jquery-migrate/%s/jquery-migrate.min.js', $versions['JQM_VERSION']); - $src[] = sprintf('https://cdnjs.cloudflare.com/ajax/libs/jqueryui/%s/jquery-ui.min.js', $versions['JQUI_VERSION']); + $src[] = sprintf( + 'https://cdnjs.cloudflare.com/ajax/libs/jquery/%s/jquery.min.js', + $versions['JQ_VERSION'] + ); + $src[] = sprintf( + 'https://cdnjs.cloudflare.com/ajax/libs/jquery-migrate/%s/jquery-migrate.min.js', + $versions['JQM_VERSION'] + ); + $src[] = sprintf( + 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/%s/jquery-ui.min.js', + $versions['JQUI_VERSION'] + ); } } $event->advise_after(); @@ -341,7 +353,7 @@ function actionOK($action){ static $disabled = null; if(is_null($disabled) || defined('SIMPLE_TEST')){ global $conf; - /** @var DokuWiki_Auth_Plugin $auth */ + /** @var AuthPlugin $auth */ global $auth; // prepare disabled actions array and handle legacy options diff --git a/inc/deprecated.php b/inc/deprecated.php new file mode 100644 index 000000000..d3de76080 --- /dev/null +++ b/inc/deprecated.php @@ -0,0 +1,565 @@ +<?php +// phpcs:ignoreFile -- this file violates PSR2 by definition +/** + * These classes and functions are deprecated and will be removed in future releases + */ + +use dokuwiki\Debug\DebugHelper; +use dokuwiki\Subscriptions\BulkSubscriptionSender; +use dokuwiki\Subscriptions\MediaSubscriptionSender; +use dokuwiki\Subscriptions\PageSubscriptionSender; +use dokuwiki\Subscriptions\RegistrationSubscriptionSender; +use dokuwiki\Subscriptions\SubscriberManager; + +/** + * @inheritdoc + * @deprecated 2018-05-07 + */ +class RemoteAccessDeniedException extends \dokuwiki\Remote\AccessDeniedException +{ + /** @inheritdoc */ + public function __construct($message = "", $code = 0, Throwable $previous = null) + { + dbg_deprecated(\dokuwiki\Remote\AccessDeniedException::class); + parent::__construct($message, $code, $previous); + } + +} + +/** + * @inheritdoc + * @deprecated 2018-05-07 + */ +class RemoteException extends \dokuwiki\Remote\RemoteException +{ + /** @inheritdoc */ + public function __construct($message = "", $code = 0, Throwable $previous = null) + { + dbg_deprecated(\dokuwiki\Remote\RemoteException::class); + parent::__construct($message, $code, $previous); + } + +} + +/** + * Escapes regex characters other than (, ) and / + * + * @param string $str + * @return string + * @deprecated 2018-05-04 + */ +function Doku_Lexer_Escape($str) +{ + dbg_deprecated('\\dokuwiki\\Parsing\\Lexer\\Lexer::escape()'); + return \dokuwiki\Parsing\Lexer\Lexer::escape($str); +} + +/** + * @inheritdoc + * @deprecated 2018-06-01 + */ +class setting extends \dokuwiki\plugin\config\core\Setting\Setting +{ + /** @inheritdoc */ + public function __construct($key, array $params = null) + { + dbg_deprecated(\dokuwiki\plugin\config\core\Setting\Setting::class); + parent::__construct($key, $params); + } +} + +/** + * @inheritdoc + * @deprecated 2018-06-01 + */ +class setting_authtype extends \dokuwiki\plugin\config\core\Setting\SettingAuthtype +{ + /** @inheritdoc */ + public function __construct($key, array $params = null) + { + dbg_deprecated(\dokuwiki\plugin\config\core\Setting\SettingAuthtype::class); + parent::__construct($key, $params); + } +} + +/** + * @inheritdoc + * @deprecated 2018-06-01 + */ +class setting_string extends \dokuwiki\plugin\config\core\Setting\SettingString +{ + /** @inheritdoc */ + public function __construct($key, array $params = null) + { + dbg_deprecated(\dokuwiki\plugin\config\core\Setting\SettingString::class); + parent::__construct($key, $params); + } +} + +/** + * @inheritdoc + * @deprecated 2018-06-15 + */ +class PageChangelog extends \dokuwiki\ChangeLog\PageChangeLog +{ + /** @inheritdoc */ + public function __construct($id, $chunk_size = 8192) + { + dbg_deprecated(\dokuwiki\ChangeLog\PageChangeLog::class); + parent::__construct($id, $chunk_size); + } +} + +/** + * @inheritdoc + * @deprecated 2018-06-15 + */ +class MediaChangelog extends \dokuwiki\ChangeLog\MediaChangeLog +{ + /** @inheritdoc */ + public function __construct($id, $chunk_size = 8192) + { + dbg_deprecated(\dokuwiki\ChangeLog\MediaChangeLog::class); + parent::__construct($id, $chunk_size); + } +} + +/** Behavior switch for JSON::decode() */ +define('JSON_LOOSE_TYPE', 16); + +/** Behavior switch for JSON::decode() */ +define('JSON_STRICT_TYPE', 0); + +/** + * Encode/Decode JSON + * @deprecated 2018-07-27 + */ +class JSON +{ + protected $use = 0; + + /** + * @param int $use JSON_*_TYPE flag + * @deprecated 2018-07-27 + */ + public function __construct($use = JSON_STRICT_TYPE) + { + $this->use = $use; + } + + /** + * Encode given structure to JSON + * + * @param mixed $var + * @return string + * @deprecated 2018-07-27 + */ + public function encode($var) + { + dbg_deprecated('json_encode'); + return json_encode($var); + } + + /** + * Alias for encode() + * @param $var + * @return string + * @deprecated 2018-07-27 + */ + public function enc($var) { + return $this->encode($var); + } + + /** + * Decode given string from JSON + * + * @param string $str + * @return mixed + * @deprecated 2018-07-27 + */ + public function decode($str) + { + dbg_deprecated('json_encode'); + return json_decode($str, ($this->use == JSON_LOOSE_TYPE)); + } + + /** + * Alias for decode + * + * @param $str + * @return mixed + * @deprecated 2018-07-27 + */ + public function dec($str) { + return $this->decode($str); + } +} + +/** + * @inheritdoc + * @deprecated 2019-02-19 + */ +class Input extends \dokuwiki\Input\Input { + /** + * @inheritdoc + * @deprecated 2019-02-19 + */ + public function __construct() + { + dbg_deprecated(\dokuwiki\Input\Input::class); + parent::__construct(); + } +} + +/** + * @inheritdoc + * @deprecated 2019-02-19 + */ +class PostInput extends \dokuwiki\Input\Post { + /** + * @inheritdoc + * @deprecated 2019-02-19 + */ + public function __construct() + { + dbg_deprecated(\dokuwiki\Input\Post::class); + parent::__construct(); + } +} + +/** + * @inheritdoc + * @deprecated 2019-02-19 + */ +class GetInput extends \dokuwiki\Input\Get { + /** + * @inheritdoc + * @deprecated 2019-02-19 + */ + public function __construct() + { + dbg_deprecated(\dokuwiki\Input\Get::class); + parent::__construct(); + } +} + +/** + * @inheritdoc + * @deprecated 2019-02-19 + */ +class ServerInput extends \dokuwiki\Input\Server { + /** + * @inheritdoc + * @deprecated 2019-02-19 + */ + public function __construct() + { + dbg_deprecated(\dokuwiki\Input\Server::class); + parent::__construct(); + } +} + +/** + * @inheritdoc + * @deprecated 2019-03-06 + */ +class PassHash extends \dokuwiki\PassHash { + /** + * @inheritdoc + * @deprecated 2019-03-06 + */ + public function __construct() + { + dbg_deprecated(\dokuwiki\PassHash::class); + } +} + +/** + * @deprecated since 2019-03-17 use \dokuwiki\HTTP\HTTPClientException instead! + */ +class HTTPClientException extends \dokuwiki\HTTP\HTTPClientException { + + /** + * @inheritdoc + * @deprecated 2019-03-17 + */ + public function __construct($message = '', $code = 0, $previous = null) + { + DebugHelper::dbgDeprecatedFunction(dokuwiki\HTTP\HTTPClientException::class); + parent::__construct($message, $code, $previous); + } +} + +/** + * @deprecated since 2019-03-17 use \dokuwiki\HTTP\HTTPClient instead! + */ +class HTTPClient extends \dokuwiki\HTTP\HTTPClient { + + /** + * @inheritdoc + * @deprecated 2019-03-17 + */ + public function __construct() + { + DebugHelper::dbgDeprecatedFunction(dokuwiki\HTTP\HTTPClient::class); + parent::__construct(); + } +} + +/** + * @deprecated since 2019-03-17 use \dokuwiki\HTTP\DokuHTTPClient instead! + */ +class DokuHTTPClient extends \dokuwiki\HTTP\DokuHTTPClient +{ + + /** + * @inheritdoc + * @deprecated 2019-03-17 + */ + public function __construct() + { + DebugHelper::dbgDeprecatedFunction(dokuwiki\HTTP\DokuHTTPClient::class); + parent::__construct(); + } +} + +/** + * function wrapper to process (create, trigger and destroy) an event + * + * @param string $name name for the event + * @param mixed $data event data + * @param callback $action (optional, default=NULL) default action, a php callback function + * @param bool $canPreventDefault (optional, default=true) can hooks prevent the default action + * + * @return mixed the event results value after all event processing is complete + * by default this is the return value of the default action however + * it can be set or modified by event handler hooks + * @deprecated 2018-06-15 + */ +function trigger_event($name, &$data, $action=null, $canPreventDefault=true) { + dbg_deprecated('\dokuwiki\Extension\Event::createAndTrigger'); + return \dokuwiki\Extension\Event::createAndTrigger($name, $data, $action, $canPreventDefault); +} + +/** + * @inheritdoc + * @deprecated 2018-06-15 + */ +class Doku_Plugin_Controller extends \dokuwiki\Extension\PluginController { + /** @inheritdoc */ + public function __construct() + { + dbg_deprecated(\dokuwiki\Extension\PluginController::class); + parent::__construct(); + } +} + + +/** + * Class for handling (email) subscriptions + * + * @author Adrian Lang <lang@cosmocode.de> + * @author Andreas Gohr <andi@splitbrain.org> + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * + * @deprecated 2019-04-22 Use the classes in the \dokuwiki\Subscriptions namespace instead! + */ +class Subscription { + + /** + * Check if subscription system is enabled + * + * @return bool + * + * @deprecated 2019-04-20 \dokuwiki\Subscriptions\SubscriberManager::isenabled + */ + public function isenabled() { + DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\SubscriberManager::isenabled'); + $subscriberManager = new SubscriberManager(); + return $subscriberManager->isenabled(); + } + + /** + * Recursively search for matching subscriptions + * + * This function searches all relevant subscription files for a page or + * namespace. + * + * @author Adrian Lang <lang@cosmocode.de> + * + * @param string $page The target object’s (namespace or page) id + * @param string|array $user + * @param string|array $style + * @param string|array $data + * @return array + * + * @deprecated 2019-04-20 \dokuwiki\Subscriptions\SubscriberManager::subscribers + */ + public function subscribers($page, $user = null, $style = null, $data = null) { + DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\SubscriberManager::subscribers'); + $manager = new SubscriberManager(); + return $manager->subscribers($page, $user, $style, $data); + } + + /** + * Adds a new subscription for the given page or namespace + * + * This will automatically overwrite any existent subscription for the given user on this + * *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces. + * + * @param string $id The target page or namespace, specified by id; Namespaces + * are identified by appending a colon. + * @param string $user + * @param string $style + * @param string $data + * @throws Exception when user or style is empty + * @return bool + * + * @deprecated 2019-04-20 \dokuwiki\Subscriptions\SubscriberManager::add + */ + public function add($id, $user, $style, $data = '') { + DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\SubscriberManager::add'); + $manager = new SubscriberManager(); + return $manager->add($id, $user, $style, $data); + } + + /** + * Removes a subscription for the given page or namespace + * + * This removes all subscriptions matching the given criteria on the given page or + * namespace. It will *not* modify any subscriptions that may exist in higher + * namespaces. + * + * @param string $id The target object’s (namespace or page) id + * @param string|array $user + * @param string|array $style + * @param string|array $data + * @return bool + * + * @deprecated 2019-04-20 \dokuwiki\Subscriptions\SubscriberManager::remove + */ + public function remove($id, $user = null, $style = null, $data = null) { + DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\SubscriberManager::remove'); + $manager = new SubscriberManager(); + return $manager->remove($id, $user, $style, $data); + } + + /** + * Get data for $INFO['subscribed'] + * + * $INFO['subscribed'] is either false if no subscription for the current page + * and user is in effect. Else it contains an array of arrays with the fields + * “target”, “style”, and optionally “data”. + * + * @param string $id Page ID, defaults to global $ID + * @param string $user User, defaults to $_SERVER['REMOTE_USER'] + * @return array|false + * @author Adrian Lang <lang@cosmocode.de> + * + * @deprecated 2019-04-20 \dokuwiki\Subscriptions\SubscriberManager::userSubscription + */ + public function user_subscription($id = '', $user = '') { + DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\SubscriberManager::userSubscription'); + $manager = new SubscriberManager(); + return $manager->userSubscription($id, $user); + } + + /** + * Send digest and list subscriptions + * + * This sends mails to all subscribers that have a subscription for namespaces above + * the given page if the needed $conf['subscribe_time'] has passed already. + * + * This function is called form lib/exe/indexer.php + * + * @param string $page + * @return int number of sent mails + * + * @deprecated 2019-04-20 \dokuwiki\Subscriptions\BulkSubscriptionSender::sendBulk + */ + public function send_bulk($page) { + DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\BulkSubscriptionSender::sendBulk'); + $subscriptionSender = new BulkSubscriptionSender(); + return $subscriptionSender->sendBulk($page); + } + + /** + * Send the diff for some page change + * + * @param string $subscriber_mail The target mail address + * @param string $template Mail template ('subscr_digest', 'subscr_single', 'mailtext', ...) + * @param string $id Page for which the notification is + * @param int|null $rev Old revision if any + * @param string $summary Change summary if any + * @return bool true if successfully sent + * + * @deprecated 2019-04-20 \dokuwiki\Subscriptions\PageSubscriptionSender::sendPageDiff + */ + public function send_diff($subscriber_mail, $template, $id, $rev = null, $summary = '') { + DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\PageSubscriptionSender::sendPageDiff'); + $subscriptionSender = new PageSubscriptionSender(); + return $subscriptionSender->sendPageDiff($subscriber_mail, $template, $id, $rev, $summary); + } + + /** + * Send the diff for some media change + * + * @fixme this should embed thumbnails of images in HTML version + * + * @param string $subscriber_mail The target mail address + * @param string $template Mail template ('uploadmail', ...) + * @param string $id Media file for which the notification is + * @param int|bool $rev Old revision if any + * + * @deprecated 2019-04-20 \dokuwiki\Subscriptions\MediaSubscriptionSender::sendMediaDiff + */ + public function send_media_diff($subscriber_mail, $template, $id, $rev = false) { + DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\MediaSubscriptionSender::sendMediaDiff'); + $subscriptionSender = new MediaSubscriptionSender(); + return $subscriptionSender->sendMediaDiff($subscriber_mail, $template, $id, $rev); + } + + /** + * Send a notify mail on new registration + * + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param string $login login name of the new user + * @param string $fullname full name of the new user + * @param string $email email address of the new user + * @return bool true if a mail was sent + * + * @deprecated 2019-04-20 \dokuwiki\Subscriptions\RegistrationSubscriptionSender::sendRegister + */ + public function send_register($login, $fullname, $email) { + DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\RegistrationSubscriptionSender::sendRegister'); + $subscriptionSender = new RegistrationSubscriptionSender(); + return $subscriptionSender->sendRegister($login, $fullname, $email); + } + + + /** + * Default callback for COMMON_NOTIFY_ADDRESSLIST + * + * Aggregates all email addresses of user who have subscribed the given page with 'every' style + * + * @author Steven Danz <steven-danz@kc.rr.com> + * @author Adrian Lang <lang@cosmocode.de> + * + * @todo move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead, + * use an array for the addresses within it + * + * @param array &$data Containing the entries: + * - $id (the page id), + * - $self (whether the author should be notified, + * - $addresslist (current email address list) + * - $replacements (array of additional string substitutions, @KEY@ to be replaced by value) + * + * @deprecated 2019-04-20 \dokuwiki\Subscriptions\SubscriberManager::notifyAddresses + */ + public function notifyaddresses(&$data) { + DebugHelper::dbgDeprecatedFunction('\dokuwiki\Subscriptions\SubscriberManager::notifyAddresses'); + $manager = new SubscriberManager(); + $manager->notifyAddresses($data); + } +} diff --git a/inc/events.php b/inc/events.php deleted file mode 100644 index 3ac49bf3f..000000000 --- a/inc/events.php +++ /dev/null @@ -1,275 +0,0 @@ -<?php -/** - * DokuWiki Events - * - * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) - * @author Christopher Smith <chris@jalakai.co.uk> - */ - -if(!defined('DOKU_INC')) die('meh.'); - -/** - * The event - */ -class Doku_Event { - - // public properties - public $name = ''; // READONLY event name, objects must register against this name to see the event - public $data = null; // READWRITE data relevant to the event, no standardised format (YET!) - public $result = null; // READWRITE the results of the event action, only relevant in "_AFTER" advise - // event handlers may modify this if they are preventing the default action - // to provide the after event handlers with event results - public $canPreventDefault = true; // READONLY if true, event handlers can prevent the events default action - - // private properties, event handlers can effect these through the provided methods - protected $_default = true; // whether or not to carry out the default action associated with the event - protected $_continue = true; // whether or not to continue propagating the event to other handlers - - /** - * event constructor - * - * @param string $name - * @param mixed $data - */ - function __construct($name, &$data) { - - $this->name = $name; - $this->data =& $data; - - } - - /** - * @return string - */ - function __toString() { - return $this->name; - } - - /** - * advise functions - * - * advise all registered handlers of this event - * - * if these methods are used by functions outside of this object, they must - * properly handle correct processing of any default action and issue an - * advise_after() signal. e.g. - * $evt = new Doku_Event(name, data); - * if ($evt->advise_before(canPreventDefault) { - * // default action code block - * } - * $evt->advise_after(); - * unset($evt); - * - * @param bool $enablePreventDefault - * @return bool results of processing the event, usually $this->_default - */ - function advise_before($enablePreventDefault=true) { - global $EVENT_HANDLER; - - $this->canPreventDefault = $enablePreventDefault; - $EVENT_HANDLER->process_event($this,'BEFORE'); - - return (!$enablePreventDefault || $this->_default); - } - - function advise_after() { - global $EVENT_HANDLER; - - $this->_continue = true; - $EVENT_HANDLER->process_event($this,'AFTER'); - } - - /** - * trigger - * - * - advise all registered (<event>_BEFORE) handlers that this event is about to take place - * - carry out the default action using $this->data based on $enablePrevent and - * $this->_default, all of which may have been modified by the event handlers. - * - advise all registered (<event>_AFTER) handlers that the event has taken place - * - * @param null|callable $action - * @param bool $enablePrevent - * @return mixed $event->results - * the value set by any <event>_before or <event> handlers if the default action is prevented - * or the results of the default action (as modified by <event>_after handlers) - * or NULL no action took place and no handler modified the value - */ - function trigger($action=null, $enablePrevent=true) { - - if (!is_callable($action)) { - $enablePrevent = false; - if (!is_null($action)) { - trigger_error('The default action of '.$this.' is not null but also not callable. Maybe the method is not public?', E_USER_WARNING); - } - } - - if ($this->advise_before($enablePrevent) && is_callable($action)) { - if (is_array($action)) { - list($obj,$method) = $action; - $this->result = $obj->$method($this->data); - } else { - $this->result = $action($this->data); - } - } - - $this->advise_after(); - - return $this->result; - } - - /** - * stopPropagation - * - * stop any further processing of the event by event handlers - * this function does not prevent the default action taking place - */ - public function stopPropagation() { - $this->_continue = false; - } - - /** - * may the event propagate to the next handler? - * - * @return bool - */ - public function mayPropagate() { - return $this->_continue; - } - - /** - * preventDefault - * - * prevent the default action taking place - */ - public function preventDefault() { - $this->_default = false; - } - - /** - * should the default action be executed? - * - * @return bool - */ - public function mayRunDefault() { - return $this->_default; - } -} - -/** - * Controls the registration and execution of all events, - */ -class Doku_Event_Handler { - - // public properties: none - - // private properties - protected $_hooks = array(); // array of events and their registered handlers - - /** - * event_handler - * - * constructor, loads all action plugins and calls their register() method giving them - * an opportunity to register any hooks they require - */ - function __construct() { - - // load action plugins - /** @var DokuWiki_Action_Plugin $plugin */ - $plugin = null; - $pluginlist = plugin_list('action'); - - foreach ($pluginlist as $plugin_name) { - $plugin = plugin_load('action',$plugin_name); - - if ($plugin !== null) $plugin->register($this); - } - } - - /** - * register_hook - * - * register a hook for an event - * - * @param string $event string name used by the event, (incl '_before' or '_after' for triggers) - * @param string $advise - * @param object $obj object in whose scope method is to be executed, - * if NULL, method is assumed to be a globally available function - * @param string $method event handler function - * @param mixed $param data passed to the event handler - * @param int $seq sequence number for ordering hook execution (ascending) - */ - function register_hook($event, $advise, $obj, $method, $param=null, $seq=0) { - $seq = (int)$seq; - $doSort = !isset($this->_hooks[$event.'_'.$advise][$seq]); - $this->_hooks[$event.'_'.$advise][$seq][] = array($obj, $method, $param); - - if ($doSort) { - ksort($this->_hooks[$event.'_'.$advise]); - } - } - - /** - * process the before/after event - * - * @param Doku_Event $event - * @param string $advise BEFORE or AFTER - */ - function process_event($event,$advise='') { - - $evt_name = $event->name . ($advise ? '_'.$advise : '_BEFORE'); - - if (!empty($this->_hooks[$evt_name])) { - foreach ($this->_hooks[$evt_name] as $sequenced_hooks) { - foreach ($sequenced_hooks as $hook) { - list($obj, $method, $param) = $hook; - - if (is_null($obj)) { - $method($event, $param); - } else { - $obj->$method($event, $param); - } - - if (!$event->mayPropagate()) return; - } - } - } - } - - /** - * Check if an event has any registered handlers - * - * When $advise is empty, both BEFORE and AFTER events will be considered, - * otherwise only the given advisory is checked - * - * @param string $name Name of the event - * @param string $advise BEFORE, AFTER or empty - * @return bool - */ - public function hasHandlerForEvent($name, $advise = '') { - if($advise) { - return isset($this->_hooks[$name . '_' . $advise]); - } else { - return isset($this->_hooks[$name . '_BEFORE']) || isset($this->_hooks[$name . '_AFTER']); - } - } -} - -/** - * trigger_event - * - * function wrapper to process (create, trigger and destroy) an event - * - * @param string $name name for the event - * @param mixed $data event data - * @param callback $action (optional, default=NULL) default action, a php callback function - * @param bool $canPreventDefault (optional, default=true) can hooks prevent the default action - * - * @return mixed the event results value after all event processing is complete - * by default this is the return value of the default action however - * it can be set or modified by event handler hooks - */ -function trigger_event($name, &$data, $action=null, $canPreventDefault=true) { - - $evt = new Doku_Event($name, $data); - return $evt->trigger($action, $canPreventDefault); -} diff --git a/inc/farm.php b/inc/farm.php index 0cd9d4f9c..03aa0eb30 100644 --- a/inc/farm.php +++ b/inc/farm.php @@ -22,7 +22,7 @@ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) */ -// DOKU_FARMDIR needs to be set in preload.php, here the fallback is the same as DOKU_INC would be (if it was set already) +// DOKU_FARMDIR needs to be set in preload.php, the fallback is the same as DOKU_INC would be (if it was set already) if(!defined('DOKU_FARMDIR')) define('DOKU_FARMDIR', fullpath(dirname(__FILE__).'/../').'/'); if(!defined('DOKU_CONF')) define('DOKU_CONF', farm_confpath(DOKU_FARMDIR)); if(!defined('DOKU_FARM')) define('DOKU_FARM', false); diff --git a/inc/fetch.functions.php b/inc/fetch.functions.php index b8e75eaec..63672629d 100644 --- a/inc/fetch.functions.php +++ b/inc/fetch.functions.php @@ -68,10 +68,14 @@ function sendFile($file, $mime, $dl, $cache, $public = false, $orig = null) { } //download or display? - if($dl) { - header('Content-Disposition: attachment;'.rfc2231_encode('filename', utf8_basename($orig)).';'); + if ($dl) { + header('Content-Disposition: attachment;' . rfc2231_encode( + 'filename', \dokuwiki\Utf8\PhpString::basename($orig)) . ';' + ); } else { - header('Content-Disposition: inline;'.rfc2231_encode('filename', utf8_basename($orig)).';'); + header('Content-Disposition: inline;' . rfc2231_encode( + 'filename', \dokuwiki\Utf8\PhpString::basename($orig)) . ';' + ); } //use x-sendfile header to pass the delivery to compatible webservers @@ -104,7 +108,13 @@ function sendFile($file, $mime, $dl, $cache, $public = false, $orig = null) { * @return string in the format " name*=charset'lang'value" for values WITH special characters */ function rfc2231_encode($name, $value, $charset='utf-8', $lang='en') { - $internal = preg_replace_callback('/[\x00-\x20*\'%()<>@,;:\\\\"\/[\]?=\x80-\xFF]/', function($match) { return rawurlencode($match[0]); }, $value); + $internal = preg_replace_callback( + '/[\x00-\x20*\'%()<>@,;:\\\\"\/[\]?=\x80-\xFF]/', + function ($match) { + return rawurlencode($match[0]); + }, + $value + ); if ( $value != $internal ) { return ' '.$name.'*='.$charset."'".$lang."'".$internal; } else { diff --git a/inc/form.php b/inc/form.php index 7afb0ba30..7a4d737a0 100644 --- a/inc/form.php +++ b/inc/form.php @@ -6,7 +6,9 @@ * @author Tom N Harris <tnharris@whoopdedo.org> */ -if(!defined('DOKU_INC')) die('meh.'); +// phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps +// phpcs:disable PSR2.Classes.PropertyDeclaration.Underscore + /** * Class for creating simple HTML forms. @@ -23,6 +25,11 @@ if(!defined('DOKU_INC')) die('meh.'); * * See the form_make* functions later in this file. * + * Please note that even though this class is technically deprecated (use dokuwiki\Form instead), + * it is still widely used in the core and the related form events. Until those have been rewritten, + * this will continue to be used + * + * @deprecated 2019-07-14 * @author Tom N Harris <tnharris@whoopdedo.org> */ class Doku_Form { @@ -55,7 +62,7 @@ class Doku_Form { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function __construct($params, $action=false, $method=false, $enctype=false) { + public function __construct($params, $action=false, $method=false, $enctype=false) { if(!is_array($params)) { $this->params = array('id' => $params); if ($action !== false) $this->params['action'] = $action; @@ -88,7 +95,7 @@ class Doku_Form { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function startFieldset($legend) { + public function startFieldset($legend) { if ($this->_infieldset) { $this->addElement(array('_elem'=>'closefieldset')); } @@ -101,7 +108,7 @@ class Doku_Form { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function endFieldset() { + public function endFieldset() { if ($this->_infieldset) { $this->addElement(array('_elem'=>'closefieldset')); } @@ -120,7 +127,7 @@ class Doku_Form { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function addHidden($name, $value) { + public function addHidden($name, $value) { if (is_null($value)) unset($this->_hidden[$name]); else @@ -138,7 +145,7 @@ class Doku_Form { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function addElement($elem) { + public function addElement($elem) { $this->_content[] = $elem; } @@ -152,7 +159,7 @@ class Doku_Form { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function insertElement($pos, $elem) { + public function insertElement($pos, $elem) { array_splice($this->_content, $pos, 0, array($elem)); } @@ -166,7 +173,7 @@ class Doku_Form { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function replaceElement($pos, $elem) { + public function replaceElement($pos, $elem) { $rep = array(); if (!is_null($elem)) $rep[] = $elem; array_splice($this->_content, $pos, 1, $rep); @@ -182,7 +189,7 @@ class Doku_Form { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function findElementByType($type) { + public function findElementByType($type) { foreach ($this->_content as $pos=>$elem) { if (is_array($elem) && $elem['_elem'] == $type) return $pos; @@ -200,7 +207,7 @@ class Doku_Form { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function findElementById($id) { + public function findElementById($id) { foreach ($this->_content as $pos=>$elem) { if (is_array($elem) && isset($elem['id']) && $elem['id'] == $id) return $pos; @@ -219,7 +226,7 @@ class Doku_Form { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function findElementByAttribute($name, $value) { + public function findElementByAttribute($name, $value) { foreach ($this->_content as $pos=>$elem) { if (is_array($elem) && isset($elem[$name]) && $elem[$name] == $value) return $pos; @@ -239,7 +246,7 @@ class Doku_Form { * * @author Tom N Harris <tnharris@whoopdedo.org> */ - function &getElementAt($pos) { + public function &getElementAt($pos) { if ($pos < 0) $pos = count($this->_content) + $pos; if ($pos < 0) $pos = 0; if ($pos >= count($this->_content)) $pos = count($this->_content) - 1; @@ -256,7 +263,7 @@ class Doku_Form { * * @return string html of the form */ - function getForm() { + public function getForm() { global $lang; $form = ''; $this->params['accept-charset'] = $lang['encoding']; @@ -286,7 +293,7 @@ class Doku_Form { * * wraps around getForm() */ - function printForm(){ + public function printForm(){ echo $this->getForm(); } @@ -302,7 +309,7 @@ class Doku_Form { * @author Adrian Lang <lang@cosmocode.de> */ - function addRadioSet($name, $entries) { + public function addRadioSet($name, $entries) { global $INPUT; $value = (array_key_exists($INPUT->post->str($name), $entries)) ? $INPUT->str($name) : key($entries); diff --git a/inc/fulltext.php b/inc/fulltext.php index dba11d0e4..48fe28da2 100644 --- a/inc/fulltext.php +++ b/inc/fulltext.php @@ -6,7 +6,7 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); +use dokuwiki\Extension\Event; /** * create snippets for the first few results only @@ -23,8 +23,8 @@ if(!defined('FT_SNIPPET_NUMBER')) define('FT_SNIPPET_NUMBER',15); * @param string $query * @param array $highlight * @param string $sort - * @param int|string $after only show results with an modified time after this date, accepts timestap or strtotime arguments - * @param int|string $before only show results with an modified time before this date, accepts timestap or strtotime arguments + * @param int|string $after only show results with mtime after this date, accepts timestap or strtotime arguments + * @param int|string $before only show results with mtime before this date, accepts timestap or strtotime arguments * * @return array */ @@ -41,7 +41,7 @@ function ft_pageSearch($query,&$highlight, $sort = null, $after = null, $before ]; $data['highlight'] =& $highlight; - return trigger_event('SEARCH_QUERY_FULLPAGE', $data, '_ft_pageSearch'); + return Event::createAndTrigger('SEARCH_QUERY_FULLPAGE', $data, '_ft_pageSearch'); } /** @@ -95,9 +95,9 @@ function _ft_pageSearch(&$data) { 'phrase' => $phrase, 'text' => rawWiki($id) ); - $evt = new Doku_Event('FULLTEXT_PHRASE_MATCH',$evdata); + $evt = new Event('FULLTEXT_PHRASE_MATCH',$evdata); if ($evt->advise_before() && $evt->result !== true) { - $text = utf8_strtolower($evdata['text']); + $text = \dokuwiki\Utf8\PhpString::strtolower($evdata['text']); if (strpos($text, $phrase) !== false) { $evt->result = true; } @@ -232,8 +232,8 @@ function ft_mediause($id, $ignore_perms = false){ * @param string $id page id * @param bool $in_ns match against namespace as well? * @param bool $in_title search in title? - * @param int|string $after only show results with an modified time after this date, accepts timestap or strtotime arguments - * @param int|string $before only show results with an modified time before this date, accepts timestap or strtotime arguments + * @param int|string $after only show results with mtime after this date, accepts timestap or strtotime arguments + * @param int|string $before only show results with mtime before this date, accepts timestap or strtotime arguments * * @return string[] */ @@ -246,7 +246,7 @@ function ft_pageLookup($id, $in_ns=false, $in_title=false, $after = null, $befor 'before' => $before ]; $data['has_titles'] = true; // for plugin backward compatibility check - return trigger_event('SEARCH_QUERY_PAGELOOKUP', $data, '_ft_pageLookup'); + return Event::createAndTrigger('SEARCH_QUERY_PAGELOOKUP', $data, '_ft_pageLookup'); } /** @@ -315,8 +315,8 @@ function _ft_pageLookup(&$data){ /** * @param array $results search results in the form pageid => value - * @param int|string $after only returns results with an modified time after this date, accepts timestap or strtotime arguments - * @param int|string $before only returns results with an modified time after this date, accepts timestap or strtotime arguments + * @param int|string $after only returns results with mtime after this date, accepts timestap or strtotime arguments + * @param int|string $before only returns results with mtime after this date, accepts timestap or strtotime arguments * * @return array */ @@ -407,15 +407,26 @@ function ft_snippet($id,$highlight){ 'snippet' => '', ); - $evt = new Doku_Event('FULLTEXT_SNIPPET_CREATE',$evdata); + $evt = new Event('FULLTEXT_SNIPPET_CREATE',$evdata); if ($evt->advise_before()) { $match = array(); $snippets = array(); $utf8_offset = $offset = $end = 0; - $len = utf8_strlen($text); + $len = \dokuwiki\Utf8\PhpString::strlen($text); // build a regexp from the phrases to highlight - $re1 = '('.join('|',array_map('ft_snippet_re_preprocess', array_map('preg_quote_cb',array_filter((array) $highlight)))).')'; + $re1 = '(' . + join( + '|', + array_map( + 'ft_snippet_re_preprocess', + array_map( + 'preg_quote_cb', + array_filter((array) $highlight) + ) + ) + ) . + ')'; $re2 = "$re1.{0,75}(?!\\1)$re1"; $re3 = "$re1.{0,45}(?!\\1)$re1.{0,45}(?!\\1)(?!\\2)$re1"; @@ -431,8 +442,8 @@ function ft_snippet($id,$highlight){ list($str,$idx) = $match[0]; // convert $idx (a byte offset) into a utf8 character offset - $utf8_idx = utf8_strlen(substr($text,0,$idx)); - $utf8_len = utf8_strlen($str); + $utf8_idx = \dokuwiki\Utf8\PhpString::strlen(substr($text,0,$idx)); + $utf8_len = \dokuwiki\Utf8\PhpString::strlen($str); // establish context, 100 bytes surrounding the match string // first look to see if we can go 100 either side, @@ -461,9 +472,9 @@ function ft_snippet($id,$highlight){ $end = $utf8_idx + $utf8_len + $post; // now set it to the end of this context if ($append) { - $snippets[count($snippets)-1] .= utf8_substr($text,$append,$end-$append); + $snippets[count($snippets)-1] .= \dokuwiki\Utf8\PhpString::substr($text,$append,$end-$append); } else { - $snippets[] = utf8_substr($text,$start,$end-$start); + $snippets[] = \dokuwiki\Utf8\PhpString::substr($text,$start,$end-$start); } // set $offset for next match attempt @@ -472,13 +483,17 @@ function ft_snippet($id,$highlight){ // this prevents further matching of this snippet but for possible matches of length // smaller than match length + context (at least 50 characters) this match is part of the context $utf8_offset = $utf8_idx + $utf8_len; - $offset = $idx + strlen(utf8_substr($text,$utf8_idx,$utf8_len)); - $offset = utf8_correctIdx($text,$offset); + $offset = $idx + strlen(\dokuwiki\Utf8\PhpString::substr($text,$utf8_idx,$utf8_len)); + $offset = \dokuwiki\Utf8\Clean::correctIdx($text,$offset); } $m = "\1"; $snippets = preg_replace('/'.$re1.'/iu',$m.'$1'.$m,$snippets); - $snippet = preg_replace('/'.$m.'([^'.$m.']*?)'.$m.'/iu','<strong class="search_hit">$1</strong>',hsc(join('... ',$snippets))); + $snippet = preg_replace( + '/' . $m . '([^' . $m . ']*?)' . $m . '/iu', + '<strong class="search_hit">$1</strong>', + hsc(join('... ', $snippets)) + ); $evdata['snippet'] = $snippet; } @@ -496,9 +511,7 @@ function ft_snippet($id,$highlight){ */ function ft_snippet_re_preprocess($term) { // do not process asian terms where word boundaries are not explicit - if(preg_match('/'.IDX_ASIAN.'/u',$term)){ - return $term; - } + if(\dokuwiki\Utf8\Asian::isAsianWords($term)) return $term; if (UTF8_PROPERTYSUPPORT) { // unicode word boundaries @@ -659,7 +672,8 @@ function ft_queryParser($Indexer, $query){ */ $parsed_query = ''; $parens_level = 0; - $terms = preg_split('/(-?".*?")/u', utf8_strtolower($query), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $terms = preg_split('/(-?".*?")/u', \dokuwiki\Utf8\PhpString::strtolower($query), + -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); foreach ($terms as $term) { $parsed = ''; @@ -860,9 +874,9 @@ function ft_termParser($Indexer, $term, $consider_asian = true, $phrase_mode = f $parsed = ''; if ($consider_asian) { // successive asian characters need to be searched as a phrase - $words = preg_split('/('.IDX_ASIAN.'+)/u', $term, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $words = \dokuwiki\Utf8\Asian::splitAsianWords($term); foreach ($words as $word) { - $phrase_mode = $phrase_mode ? true : preg_match('/'.IDX_ASIAN.'/u', $word); + $phrase_mode = $phrase_mode ? true : \dokuwiki\Utf8\Asian::isAsianWords($word); $parsed .= ft_termParser($Indexer, $word, false, $phrase_mode); } } else { diff --git a/inc/html.php b/inc/html.php index 4059d1286..773d55364 100644 --- a/inc/html.php +++ b/inc/html.php @@ -6,8 +6,11 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); -if(!defined('NL')) define('NL',"\n"); +use dokuwiki\ChangeLog\MediaChangeLog; +use dokuwiki\ChangeLog\PageChangeLog; +use dokuwiki\Extension\AuthPlugin; +use dokuwiki\Extension\Event; + if (!defined('SEC_EDIT_PATTERN')) { define('SEC_EDIT_PATTERN', '#<!-- EDIT({.*?}) -->#'); } @@ -51,7 +54,13 @@ function html_login($svg = false){ $form->startFieldset($lang['btn_login']); $form->addHidden('id', $ID); $form->addHidden('do', 'login'); - $form->addElement(form_makeTextField('u', ((!$INPUT->bool('http_credentials')) ? $INPUT->str('u') : ''), $lang['user'], 'focus__this', 'block')); + $form->addElement(form_makeTextField( + 'u', + ((!$INPUT->bool('http_credentials')) ? $INPUT->str('u') : ''), + $lang['user'], + 'focus__this', + 'block') + ); $form->addElement(form_makePasswordField('p', $lang['pass'], '', 'block')); if($conf['rememberme']) { $form->addElement(form_makeCheckboxField('r', '1', $lang['remember'], 'remember__me', 'simple')); @@ -126,7 +135,7 @@ function html_secedit_button($matches){ $data ['target'] = strtolower($data['target']); $data ['hid'] = strtolower($data['hid']); - return trigger_event('HTML_SECEDIT_BUTTON', $data, + return Event::createAndTrigger('HTML_SECEDIT_BUTTON', $data, 'html_secedit_get_button'); } @@ -170,7 +179,10 @@ function html_secedit_get_button($data) { function html_topbtn(){ global $lang; - $ret = '<a class="nolink" href="#dokuwiki__top"><button class="button" onclick="window.scrollTo(0, 0)" title="'.$lang['btn_top'].'">'.$lang['btn_top'].'</button></a>'; + $ret = '<a class="nolink" href="#dokuwiki__top">' . + '<button class="button" onclick="window.scrollTo(0, 0)" title="' . $lang['btn_top'] . '">' . + $lang['btn_top'] . + '</button></a>'; return $ret; } @@ -288,7 +300,7 @@ function html_show($txt=null){ }else{ if ($REV||$DATE_AT){ $data = array('rev' => &$REV, 'date_at' => &$DATE_AT); - trigger_event('HTML_SHOWREV_OUTPUT', $data, 'html_showrev'); + Event::createAndTrigger('HTML_SHOWREV_OUTPUT', $data, 'html_showrev'); } $html = p_wiki_xhtml($ID,$REV,true,$DATE_AT); $html = html_secedit($html,$secedit); @@ -343,7 +355,7 @@ function html_hilight($html,$phrases){ $regex = join('|',$phrases); if ($regex === '') return $html; - if (!utf8_check($regex)) return $html; + if (!\dokuwiki\Utf8\Clean::isUtf8($regex)) return $html; $html = @preg_replace_callback("/((<[^>]*)|$regex)/ui",'html_hilight_callback',$html); return $html; } @@ -690,7 +702,9 @@ function html_recent($first = 0, $show_changes = 'both') { print p_locale_xhtml('recent'); if(getNS($ID) != '') { - print '<div class="level1"><p>' . sprintf($lang['recent_global'], getNS($ID), wl('', 'do=recent')) . '</p></div>'; + print '<div class="level1"><p>' . + sprintf($lang['recent_global'], getNS($ID), wl('', 'do=recent')) . + '</p></div>'; } $form = new Doku_Form(array('id' => 'dw__recent', 'method' => 'GET', 'class' => 'changes')); @@ -773,7 +787,14 @@ function html_recent($first = 0, $show_changes = 'both') { } if(!empty($recent['media'])) { - $href = media_managerURL(array('tab_details' => 'history', 'image' => $recent['id'], 'ns' => getNS($recent['id'])), '&'); + $href = media_managerURL( + array( + 'tab_details' => 'history', + 'image' => $recent['id'], + 'ns' => getNS($recent['id']) + ), + '&' + ); } else { $href = wl($recent['id'], "do=revisions", false, '&'); } @@ -790,7 +811,14 @@ function html_recent($first = 0, $show_changes = 'both') { $form->addElement(form_makeCloseTag('a')); if(!empty($recent['media'])) { - $href = media_managerURL(array('tab_details' => 'view', 'image' => $recent['id'], 'ns' => getNS($recent['id'])), '&'); + $href = media_managerURL( + array( + 'tab_details' => 'view', + 'image' => $recent['id'], + 'ns' => getNS($recent['id']) + ), + '&' + ); $class = file_exists(mediaFN($recent['id'])) ? 'wikilink1' : 'wikilink2'; $form->addElement(form_makeOpenTag('a', array( 'class' => $class, @@ -897,14 +925,15 @@ function html_list_index($item){ global $ID, $conf; // prevent searchbots needlessly following links - $nofollow = ($ID != $conf['start'] || $conf['sitemap']) ? ' rel="nofollow"' : ''; + $nofollow = ($ID != $conf['start'] || $conf['sitemap']) ? 'rel="nofollow"' : ''; $ret = ''; $base = ':'.$item['id']; $base = substr($base,strrpos($base,':')+1); if($item['type']=='d'){ // FS#2766, no need for search bots to follow namespace links in the index - $ret .= '<a href="'.wl($ID,'idx='.rawurlencode($item['id'])).'" title="' . $item['id'] . '" class="idx_dir"' . $nofollow . '><strong>'; + $link = wl($ID, 'idx=' . rawurlencode($item['id'])); + $ret .= '<a href="' . $link . '" title="' . $item['id'] . '" class="idx_dir" ' . $nofollow . '><strong>'; $ret .= $base; $ret .= '</strong></a>'; }else{ @@ -1562,12 +1591,12 @@ function html_softbreak_callback($match){ // make certain characters into breaking characters by inserting a // word break opportunity (<wbr> tag) in front of them. $regex = <<< REGEX -(?(?= # start a conditional expression with a positive look ahead ... -&\#?\\w{1,6};) # ... for html entities - we don't want to split them (ok to catch some invalid combinations) -&\#?\\w{1,6}; # yes pattern - a quicker match for the html entity, since we know we have one +(?(?= # start a conditional expression with a positive look ahead ... +&\#?\\w{1,6};) # ... for html entities - we don't want to split them (ok to catch some invalid combinations) +&\#?\\w{1,6}; # yes pattern - a quicker match for the html entity, since we know we have one | -[?/,&\#;:] # no pattern - any other group of 'special' characters to insert a breaking character after -)+ # end conditional expression +[?/,&\#;:] # no pattern - any other group of 'special' characters to insert a breaking character after +)+ # end conditional expression REGEX; return preg_replace('<'.$regex.'>xu','\0<wbr>',$match[0]); @@ -1643,13 +1672,41 @@ function html_register(){ $form->startFieldset($lang['btn_register']); $form->addHidden('do', 'register'); $form->addHidden('save', '1'); - $form->addElement(form_makeTextField('login', $INPUT->post->str('login'), $lang['user'], '', 'block', $base_attrs)); + $form->addElement( + form_makeTextField( + 'login', + $INPUT->post->str('login'), + $lang['user'], + '', + 'block', + $base_attrs + ) + ); if (!$conf['autopasswd']) { $form->addElement(form_makePasswordField('pass', $lang['pass'], '', 'block', $base_attrs)); $form->addElement(form_makePasswordField('passchk', $lang['passchk'], '', 'block', $base_attrs)); } - $form->addElement(form_makeTextField('fullname', $INPUT->post->str('fullname'), $lang['fullname'], '', 'block', $base_attrs)); - $form->addElement(form_makeField('email','email', $INPUT->post->str('email'), $lang['email'], '', 'block', $email_attrs)); + $form->addElement( + form_makeTextField( + 'fullname', + $INPUT->post->str('fullname'), + $lang['fullname'], + '', + 'block', + $base_attrs + ) + ); + $form->addElement( + form_makeField( + 'email', + 'email', + $INPUT->post->str('email'), + $lang['email'], + '', + 'block', + $email_attrs + ) + ); $form->addElement(form_makeButton('submit', '', $lang['btn_register'])); $form->endFieldset(); html_form('register', $form); @@ -1668,7 +1725,7 @@ function html_updateprofile(){ global $conf; global $INPUT; global $INFO; - /** @var DokuWiki_Auth_Plugin $auth */ + /** @var AuthPlugin $auth */ global $auth; print p_locale_xhtml('updateprofile'); @@ -1680,7 +1737,16 @@ function html_updateprofile(){ $form->startFieldset($lang['profile']); $form->addHidden('do', 'profile'); $form->addHidden('save', '1'); - $form->addElement(form_makeTextField('login', $_SERVER['REMOTE_USER'], $lang['user'], '', 'block', array('size'=>'50', 'disabled'=>'disabled'))); + $form->addElement( + form_makeTextField( + 'login', + $_SERVER['REMOTE_USER'], + $lang['user'], + '', + 'block', + array('size' => '50', 'disabled' => 'disabled') + ) + ); $attr = array('size'=>'50'); if (!$auth->canDo('modName')) $attr['disabled'] = 'disabled'; $form->addElement(form_makeTextField('fullname', $fullname, $lang['fullname'], '', 'block', $attr)); @@ -1694,7 +1760,15 @@ function html_updateprofile(){ } if ($conf['profileconfirm']) { $form->addElement(form_makeTag('br')); - $form->addElement(form_makePasswordField('oldpass', $lang['oldpass'], '', 'block', array('size'=>'50', 'required' => 'required'))); + $form->addElement( + form_makePasswordField( + 'oldpass', + $lang['oldpass'], + '', + 'block', + array('size' => '50', 'required' => 'required') + ) + ); } $form->addElement(form_makeButton('submit', '', $lang['btn_save'])); $form->addElement(form_makeButton('reset', '', $lang['btn_reset'])); @@ -1707,10 +1781,27 @@ function html_updateprofile(){ $form_profiledelete->startFieldset($lang['profdeleteuser']); $form_profiledelete->addHidden('do', 'profile_delete'); $form_profiledelete->addHidden('delete', '1'); - $form_profiledelete->addElement(form_makeCheckboxField('confirm_delete', '1', $lang['profconfdelete'],'dw__confirmdelete','', array('required' => 'required'))); + $form_profiledelete->addElement( + form_makeCheckboxField( + 'confirm_delete', + '1', + $lang['profconfdelete'], + 'dw__confirmdelete', + '', + array('required' => 'required') + ) + ); if ($conf['profileconfirm']) { $form_profiledelete->addElement(form_makeTag('br')); - $form_profiledelete->addElement(form_makePasswordField('oldpass', $lang['oldpass'], '', 'block', array('size'=>'50', 'required' => 'required'))); + $form_profiledelete->addElement( + form_makePasswordField( + 'oldpass', + $lang['oldpass'], + '', + 'block', + array('size' => '50', 'required' => 'required') + ) + ); } $form_profiledelete->addElement(form_makeButton('submit', '', $lang['btn_deleteuser'])); $form_profiledelete->endFieldset(); @@ -1783,7 +1874,7 @@ function html_edit(){ if ($data['target'] !== 'section') { // Only emit event if page is writable, section edit data is valid and // edit target is not section. - trigger_event('HTML_EDIT_FORMSELECTION', $data, 'html_edit_form', true); + Event::createAndTrigger('HTML_EDIT_FORMSELECTION', $data, 'html_edit_form', true); } else { html_edit_form($data); } @@ -1803,12 +1894,35 @@ function html_edit(){ $form->addElement(form_makeCloseTag('div')); if ($wr) { $form->addElement(form_makeOpenTag('div', array('class'=>'editButtons'))); - $form->addElement(form_makeButton('submit', 'save', $lang['btn_save'], array('id'=>'edbtn__save', 'accesskey'=>'s', 'tabindex'=>'4'))); - $form->addElement(form_makeButton('submit', 'preview', $lang['btn_preview'], array('id'=>'edbtn__preview', 'accesskey'=>'p', 'tabindex'=>'5'))); + $form->addElement( + form_makeButton( + 'submit', + 'save', + $lang['btn_save'], + array('id' => 'edbtn__save', 'accesskey' => 's', 'tabindex' => '4') + ) + ); + $form->addElement( + form_makeButton( + 'submit', + 'preview', + $lang['btn_preview'], + array('id' => 'edbtn__preview', 'accesskey' => 'p', 'tabindex' => '5') + ) + ); $form->addElement(form_makeButton('submit', 'cancel', $lang['btn_cancel'], array('tabindex'=>'6'))); $form->addElement(form_makeCloseTag('div')); $form->addElement(form_makeOpenTag('div', array('class'=>'summary'))); - $form->addElement(form_makeTextField('summary', $SUM, $lang['summary'], 'edit__summary', 'nowrap', array('size'=>'50', 'tabindex'=>'2'))); + $form->addElement( + form_makeTextField( + 'summary', + $SUM, + $lang['summary'], + 'edit__summary', + 'nowrap', + array('size' => '50', 'tabindex' => '2') + ) + ); $elem = html_minoredit(); if ($elem) $form->addElement($elem); $form->addElement(form_makeCloseTag('div')); @@ -1833,8 +1947,12 @@ function html_edit(){ <div class="editBox" role="application"> <div class="toolbar group"> - <div id="tool__bar" class="tool__bar"><?php if ($wr && $data['media_manager']){?><a href="<?php echo DOKU_BASE?>lib/exe/mediamanager.php?ns=<?php echo $INFO['namespace']?>" - target="_blank"><?php echo $lang['mediaselect'] ?></a><?php }?></div> + <div id="tool__bar" class="tool__bar"><?php + if ($wr && $data['media_manager']){ + ?><a href="<?php echo DOKU_BASE?>lib/exe/mediamanager.php?ns=<?php echo $INFO['namespace']?>" + target="_blank"><?php echo $lang['mediaselect'] ?></a><?php + }?> + </div> </div> <div id="draft__status" class="draft__status"> <?php @@ -1900,7 +2018,7 @@ function html_minoredit(){ function html_debug(){ global $conf; global $lang; - /** @var DokuWiki_Auth_Plugin $auth */ + /** @var AuthPlugin $auth */ global $auth; global $INFO; @@ -2105,7 +2223,7 @@ function html_mktocitem($link, $text, $level, $hash='#'){ function html_form($name, &$form) { // Safety check in case the caller forgets. $form->endFieldset(); - trigger_event('HTML_'.strtoupper($name).'FORM_OUTPUT', $form, 'html_form_output', false); + Event::createAndTrigger('HTML_'.strtoupper($name).'FORM_OUTPUT', $form, 'html_form_output', false); } /** diff --git a/inc/indexer.php b/inc/indexer.php index e0acb2ac2..f2c8a9aa8 100644 --- a/inc/indexer.php +++ b/inc/indexer.php @@ -7,7 +7,7 @@ * @author Tom N Harris <tnharris@whoopdedo.org> */ -if(!defined('DOKU_INC')) die('meh.'); +use dokuwiki\Extension\Event; // Version tag used to force rebuild on upgrade define('INDEXER_VERSION', 8); @@ -15,41 +15,6 @@ define('INDEXER_VERSION', 8); // set the minimum token length to use in the index (note, this doesn't apply to numeric tokens) if (!defined('IDX_MINWORDLENGTH')) define('IDX_MINWORDLENGTH',2); -// Asian characters are handled as words. The following regexp defines the -// Unicode-Ranges for Asian characters -// Ranges taken from http://en.wikipedia.org/wiki/Unicode_block -// I'm no language expert. If you think some ranges are wrongly chosen or -// a range is missing, please contact me -define('IDX_ASIAN1','[\x{0E00}-\x{0E7F}]'); // Thai -define('IDX_ASIAN2','['. - '\x{2E80}-\x{3040}'. // CJK -> Hangul - '\x{309D}-\x{30A0}'. - '\x{30FD}-\x{31EF}\x{3200}-\x{D7AF}'. - '\x{F900}-\x{FAFF}'. // CJK Compatibility Ideographs - '\x{FE30}-\x{FE4F}'. // CJK Compatibility Forms - "\xF0\xA0\x80\x80-\xF0\xAA\x9B\x9F". // CJK Extension B - "\xF0\xAA\x9C\x80-\xF0\xAB\x9C\xBF". // CJK Extension C - "\xF0\xAB\x9D\x80-\xF0\xAB\xA0\x9F". // CJK Extension D - "\xF0\xAF\xA0\x80-\xF0\xAF\xAB\xBF". // CJK Compatibility Supplement - ']'); -define('IDX_ASIAN3','['. // Hiragana/Katakana (can be two characters) - '\x{3042}\x{3044}\x{3046}\x{3048}'. - '\x{304A}-\x{3062}\x{3064}-\x{3082}'. - '\x{3084}\x{3086}\x{3088}-\x{308D}'. - '\x{308F}-\x{3094}'. - '\x{30A2}\x{30A4}\x{30A6}\x{30A8}'. - '\x{30AA}-\x{30C2}\x{30C4}-\x{30E2}'. - '\x{30E4}\x{30E6}\x{30E8}-\x{30ED}'. - '\x{30EF}-\x{30F4}\x{30F7}-\x{30FA}'. - ']['. - '\x{3041}\x{3043}\x{3045}\x{3047}\x{3049}'. - '\x{3063}\x{3083}\x{3085}\x{3087}\x{308E}\x{3095}-\x{309C}'. - '\x{30A1}\x{30A3}\x{30A5}\x{30A7}\x{30A9}'. - '\x{30C3}\x{30E3}\x{30E5}\x{30E7}\x{30EE}\x{30F5}\x{30F6}\x{30FB}\x{30FC}'. - '\x{31F0}-\x{31FF}'. - ']?'); -define('IDX_ASIAN', '(?:'.IDX_ASIAN1.'|'.IDX_ASIAN2.'|'.IDX_ASIAN3.')'); - /** * Version of the indexer taking into consideration the external tokenizer. * The indexer is only compatible with data written by the same version. @@ -71,7 +36,7 @@ function idx_get_version(){ // DokuWiki version is included for the convenience of plugins $data = array('dokuwiki'=>$version); - trigger_event('INDEXER_VERSION_GET', $data, null, false); + Event::createAndTrigger('INDEXER_VERSION_GET', $data, null, false); unset($data['dokuwiki']); // this needs to be first ksort($data); foreach ($data as $plugin=>$vers) @@ -403,7 +368,7 @@ class Doku_Indexer { * * @param string $key The metadata key of which a value shall be changed * @param string $oldvalue The old value that shall be renamed - * @param string $newvalue The new value to which the old value shall be renamed, can exist (then values will be merged) + * @param string $newvalue The new value to which the old value shall be renamed, if exists values will be merged * @return bool|string If renaming the value has been successful, false or error message on error. */ public function renameMetaValue($key, $oldvalue, $newvalue) { @@ -587,12 +552,10 @@ class Doku_Indexer { $stopwords =& idx_get_stopwords(); // prepare the text to be tokenized - $evt = new Doku_Event('INDEXER_TEXT_PREPARE', $text); + $evt = new Event('INDEXER_TEXT_PREPARE', $text); if ($evt->advise_before(true)) { if (preg_match('/[^0-9A-Za-z ]/u', $text)) { - // handle asian chars as single words (may fail on older PHP version) - $asia = @preg_replace('/('.IDX_ASIAN.')/u', ' \1 ', $text); - if (!is_null($asia)) $text = $asia; // recover from regexp falure + $text = \dokuwiki\Utf8\Asian::separateAsianWords($text); } } $evt->advise_after(); @@ -607,12 +570,12 @@ class Doku_Indexer { ) ); if (preg_match('/[^0-9A-Za-z ]/u', $text)) - $text = utf8_stripspecials($text, ' ', '\._\-:'.$wc); + $text = \dokuwiki\Utf8\Clean::stripspecials($text, ' ', '\._\-:'.$wc); $wordlist = explode(' ', $text); foreach ($wordlist as $i => $word) { $wordlist[$i] = (preg_match('/[^0-9A-Za-z]/u', $word)) ? - utf8_strtolower($word) : strtolower($word); + \dokuwiki\Utf8\PhpString::strtolower($word) : strtolower($word); } foreach ($wordlist as $i => $word) { @@ -1425,7 +1388,7 @@ function idx_addPage($page, $verbose=false, $force=false) { $metadata['relation_media'] = array(); $data = compact('page', 'body', 'metadata', 'pid'); - $evt = new Doku_Event('INDEXER_PAGE_ADD', $data); + $evt = new Event('INDEXER_PAGE_ADD', $data); if ($evt->advise_before()) $data['body'] = $data['body'] . " " . rawWiki($page); $evt->advise_after(); unset($evt); @@ -1521,7 +1484,10 @@ function idx_listIndexLengths() { clearstatcache(); if (file_exists($conf['indexdir'].'/lengths.idx') && (time() < @filemtime($conf['indexdir'].'/lengths.idx') + $conf['readdircache'])) { - if (($lengths = @file($conf['indexdir'].'/lengths.idx', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)) !== false) { + if ( + ($lengths = @file($conf['indexdir'].'/lengths.idx', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)) + !== false + ) { $idx = array(); foreach ($lengths as $length) { $idx[] = (int)$length; @@ -1602,7 +1568,7 @@ function idx_indexLengths($filter) { * @return string */ function idx_cleanName($name) { - $name = utf8_romanize(trim((string)$name)); + $name = \dokuwiki\Utf8\Clean::romanize(trim((string)$name)); $name = preg_replace('#[ \./\\:-]+#', '_', $name); $name = preg_replace('/[^A-Za-z0-9_]/', '', $name); return strtolower($name); diff --git a/inc/infoutils.php b/inc/infoutils.php index efed33041..771d5845b 100644 --- a/inc/infoutils.php +++ b/inc/infoutils.php @@ -5,7 +5,8 @@ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); + +use dokuwiki\HTTP\DokuHTTPClient; if(!defined('DOKU_MESSAGEURL')){ if(in_array('ssl', stream_get_transports())) { @@ -135,16 +136,19 @@ function check(){ $mem = (int) php_to_byte(ini_get('memory_limit')); if($mem){ - if($mem === -1) { + if ($mem === -1) { msg('PHP memory is unlimited', 1); - } else if($mem < 16777216){ - msg('PHP is limited to less than 16MB RAM ('.filesize_h($mem).'). Increase memory_limit in php.ini',-1); - } else if($mem < 20971520){ - msg('PHP is limited to less than 20MB RAM ('.filesize_h($mem).'), you might encounter problems with bigger pages. Increase memory_limit in php.ini',-1); - } else if($mem < 33554432){ - msg('PHP is limited to less than 32MB RAM ('.filesize_h($mem).'), but that should be enough in most cases. If not, increase memory_limit in php.ini',0); + } else if ($mem < 16777216) { + msg('PHP is limited to less than 16MB RAM (' . filesize_h($mem) . '). + Increase memory_limit in php.ini', -1); + } else if ($mem < 20971520) { + msg('PHP is limited to less than 20MB RAM (' . filesize_h($mem) . '), + you might encounter problems with bigger pages. Increase memory_limit in php.ini', -1); + } else if ($mem < 33554432) { + msg('PHP is limited to less than 32MB RAM (' . filesize_h($mem) . '), + but that should be enough in most cases. If not, increase memory_limit in php.ini', 0); } else { - msg('More than 32MB RAM ('.filesize_h($mem).') available.',1); + msg('More than 32MB RAM (' . filesize_h($mem) . ') available.', 1); } } @@ -210,7 +214,8 @@ function check(){ if(!$loc){ msg('No valid locale is set for your PHP setup. You should fix this',-1); }elseif(stripos($loc,'utf') === false){ - msg('Your locale <code>'.hsc($loc).'</code> seems not to be a UTF-8 locale, you should fix this if you encounter problems.',0); + msg('Your locale <code>'.hsc($loc).'</code> seems not to be a UTF-8 locale, + you should fix this if you encounter problems.',0); }else{ msg('Valid locale '.hsc($loc).' found.', 1); } @@ -290,7 +295,8 @@ function check(){ if(abs($diff) < 4) { msg("Server time seems to be okay. Diff: {$diff}s", 1); } else { - msg("Your server's clock seems to be out of sync! Consider configuring a sync with a NTP server. Diff: {$diff}s"); + msg("Your server's clock seems to be out of sync! + Consider configuring a sync with a NTP server. Diff: {$diff}s"); } } @@ -336,7 +342,7 @@ function msg($message,$lvl=0,$line='',$file='',$allow=MSG_PUBLIC){ $errors[1] = 'success'; $errors[2] = 'notify'; - if($line || $file) $message.=' ['.utf8_basename($file).':'.$line.']'; + if($line || $file) $message.=' ['.\dokuwiki\Utf8\PhpString::basename($file).':'.$line.']'; if(!isset($MSG)) $MSG = array(); $MSG[]=array('lvl' => $errors[$lvl], 'msg' => $message, 'allow' => $allow); @@ -380,7 +386,8 @@ function info_msg_allowed($msg){ return $INFO['isadmin']; default: - trigger_error('invalid msg allow restriction. msg="'.$msg['msg'].'" allow='.$msg['allow'].'"', E_USER_WARNING); + trigger_error('invalid msg allow restriction. msg="'.$msg['msg'].'" allow='.$msg['allow'].'"', + E_USER_WARNING); return $INFO['isadmin']; } @@ -446,37 +453,7 @@ function dbglog($msg,$header=''){ * @triggers INFO_DEPRECATION_LOG */ function dbg_deprecated($alternative = '') { - global $conf; - global $EVENT_HANDLER; - if(!$conf['allowdebug'] && !$EVENT_HANDLER->hasHandlerForEvent('INFO_DEPRECATION_LOG')) { - // avoid any work if no one cares - return; - } - - $backtrace = debug_backtrace(); - array_shift($backtrace); - $self = $backtrace[0]; - $call = $backtrace[1]; - - $data = [ - 'trace' => $backtrace, - 'alternative' => $alternative, - 'called' => trim($self['class'] . '::' . $self['function'] . '()', ':'), - 'caller' => trim($call['class'] . '::' . $call['function'] . '()', ':'), - 'file' => $call['file'], - 'line' => $call['line'], - ]; - - $event = new Doku_Event('INFO_DEPRECATION_LOG', $data); - if($event->advise_before()) { - $msg = $event->data['called'] . ' is deprecated. It was called from '; - $msg .= $event->data['caller'] . ' in ' . $event->data['file'] . ':' . $event->data['line']; - if($event->data['alternative']) { - $msg .= ' ' . $event->data['alternative'] . ' should be used instead!'; - } - dbglog($msg); - } - $event->advise_after(); + \dokuwiki\Debug\DebugHelper::dbgDeprecatedFunction($alternative, 2); } /** diff --git a/inc/init.php b/inc/init.php index 931b8d31a..548ac80db 100644 --- a/inc/init.php +++ b/inc/init.php @@ -3,6 +3,8 @@ * Initialize some defaults needed for DokuWiki */ +use dokuwiki\Extension\Event; +use dokuwiki\Extension\EventHandler; /** * timing Dokuwiki execution @@ -104,6 +106,7 @@ if(!defined('DOKU_BASE')){ } // define whitespace +if(!defined('NL')) define ('NL',"\n"); if(!defined('DOKU_LF')) define ('DOKU_LF',"\n"); if(!defined('DOKU_TAB')) define ('DOKU_TAB',"\t"); @@ -111,9 +114,9 @@ if(!defined('DOKU_TAB')) define ('DOKU_TAB',"\t"); if (!defined('DOKU_COOKIE')) { $serverPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : ''; define('DOKU_COOKIE', 'DW' . md5(DOKU_REL . (($conf['securecookie']) ? $serverPort : ''))); + unset($serverPort); } - // define main script if(!defined('DOKU_SCRIPT')) define('DOKU_SCRIPT','doku.php'); @@ -189,9 +192,8 @@ init_paths(); init_files(); // setup plugin controller class (can be overwritten in preload.php) -$plugin_types = array('auth', 'admin','syntax','action','renderer', 'helper','remote'); global $plugin_controller_class, $plugin_controller; -if (empty($plugin_controller_class)) $plugin_controller_class = 'Doku_Plugin_Controller'; +if (empty($plugin_controller_class)) $plugin_controller_class = dokuwiki\Extension\PluginController::class; // load libraries require_once(DOKU_INC.'vendor/autoload.php'); @@ -209,17 +211,17 @@ if($conf['compression'] == 'gz' && !DOKU_HAS_GZIP) { // input handle class global $INPUT; -$INPUT = new Input(); +$INPUT = new \dokuwiki\Input\Input(); // initialize plugin controller $plugin_controller = new $plugin_controller_class(); // initialize the event handler global $EVENT_HANDLER; -$EVENT_HANDLER = new Doku_Event_Handler(); +$EVENT_HANDLER = new EventHandler(); $local = $conf['lang']; -trigger_event('INIT_LANG_LOAD', $local, 'init_lang', true); +Event::createAndTrigger('INIT_LANG_LOAD', $local, 'init_lang', true); // setup authentication system @@ -241,7 +243,13 @@ mail_setup(); function init_session() { global $conf; session_name(DOKU_SESSION_NAME); - session_set_cookie_params(DOKU_SESSION_LIFETIME, DOKU_SESSION_PATH, DOKU_SESSION_DOMAIN, ($conf['securecookie'] && is_ssl()), true); + session_set_cookie_params( + DOKU_SESSION_LIFETIME, + DOKU_SESSION_PATH, + DOKU_SESSION_DOMAIN, + ($conf['securecookie'] && is_ssl()), + true + ); // make sure the session cookie contains a valid session ID if(isset($_COOKIE[DOKU_SESSION_NAME]) && !preg_match('/^[-,a-zA-Z0-9]{22,256}$/', $_COOKIE[DOKU_SESSION_NAME])) { @@ -280,7 +288,9 @@ function init_paths(){ } // path to old changelog only needed for upgrading - $conf['changelog_old'] = init_path((isset($conf['changelog']))?($conf['changelog']):($conf['savedir'].'/changes.log')); + $conf['changelog_old'] = init_path( + (isset($conf['changelog'])) ? ($conf['changelog']) : ($conf['savedir'] . '/changes.log') + ); if ($conf['changelog_old']=='') { unset($conf['changelog_old']); } // hardcoded changelog because it is now a cache that lives in meta $conf['changelog'] = $conf['metadir'].'/_dokuwiki.changes'; @@ -449,7 +459,7 @@ function getBaseURL($abs=null){ //finish here for relative URLs if(!$abs) return $dir; - //use config option if available, trim any slash from end of baseurl to avoid multiple consecutive slashes in the path + //use config if available, trim any slash from end of baseurl to avoid multiple consecutive slashes in the path if(!empty($conf['baseurl'])) return rtrim($conf['baseurl'],'/').$dir; //split hostheader into host and port diff --git a/inc/io.php b/inc/io.php index 0ab1d560a..18aae25e7 100644 --- a/inc/io.php +++ b/inc/io.php @@ -6,7 +6,8 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); +use dokuwiki\HTTP\DokuHTTPClient; +use dokuwiki\Extension\Event; /** * Removes empty directories @@ -39,7 +40,7 @@ function io_sweepNS($id,$basedir='datadir'){ if ($ns_type!==false) { $data = array($id, $ns_type); $delone = true; // we deleted at least one dir - trigger_event('IO_NAMESPACE_DELETED', $data); + Event::createAndTrigger('IO_NAMESPACE_DELETED', $data); } } else { return $delone; } } @@ -69,7 +70,7 @@ function io_sweepNS($id,$basedir='datadir'){ function io_readWikiPage($file, $id, $rev=false) { if (empty($rev)) { $rev = false; } $data = array(array($file, true), getNS($id), noNS($id), $rev); - return trigger_event('IO_WIKIPAGE_READ', $data, '_io_readWikiPage_action', false); + return Event::createAndTrigger('IO_WIKIPAGE_READ', $data, '_io_readWikiPage_action', false); } /** @@ -189,7 +190,7 @@ function io_writeWikiPage($file, $content, $id, $rev=false) { if (empty($rev)) { $rev = false; } if ($rev===false) { io_createNamespace($id); } // create namespaces as needed $data = array(array($file, $content, false), getNS($id), noNS($id), $rev); - return trigger_event('IO_WIKIPAGE_WRITE', $data, '_io_writeWikiPage_action', false); + return Event::createAndTrigger('IO_WIKIPAGE_WRITE', $data, '_io_writeWikiPage_action', false); } /** @@ -469,7 +470,7 @@ function io_createNamespace($id, $ns_type='pages') { $missing = array_reverse($missing); // inside out foreach ($missing as $ns) { $data = array($ns, $ns_type); - trigger_event('IO_NAMESPACE_CREATED', $data); + Event::createAndTrigger('IO_NAMESPACE_CREATED', $data); } } @@ -598,7 +599,8 @@ function io_mktmpdir() { * * @param string $url url to download * @param string $file path to file or directory where to save - * @param bool $useAttachment if true: try to use name of download, uses otherwise $defaultName, false: uses $file as path to file + * @param bool $useAttachment true: try to use name of download, uses otherwise $defaultName + * false: uses $file as path to file * @param string $defaultName fallback for if using $useAttachment * @param int $maxSize maximum file size * @return bool|string if failed false, otherwise true or the name of the file in the given dir @@ -621,7 +623,7 @@ function io_download($url,$file,$useAttachment=false,$defaultName='',$maxSize=20 if (is_string($content_disposition) && preg_match('/attachment;\s*filename\s*=\s*"([^"]*)"/i', $content_disposition, $match)) { - $name = utf8_basename($match[1]); + $name = \dokuwiki\Utf8\PhpString::basename($match[1]); } } diff --git a/inc/lang/az/wordblock.txt b/inc/lang/az/wordblock.txt deleted file mode 100644 index ec8b102af..000000000 --- a/inc/lang/az/wordblock.txt +++ /dev/null @@ -1,3 +0,0 @@ -====== SPAM-ın qarşısı alındı ====== - -Sizin dəyişiklər **yaddaşa saxlanmadı**, çünki onların içində bir və ya daha çox içazəsiz sözlər var idi. Əgər siz wiki-yə spam əlavə etmək istəyirdinizsə, onda utanmırsız?! Əgər siz bunu səhv hesab edirsinizsə, onda administrator ilə əlaqə saxlayın. diff --git a/inc/lang/be/wordblock.txt b/inc/lang/be/wordblock.txt deleted file mode 100644 index a92d7524e..000000000 --- a/inc/lang/be/wordblock.txt +++ /dev/null @@ -1,3 +0,0 @@ -====== СПАМ заблакаваны ====== - -Вашы змены **не** захаваны, так як яны ўтрымліваюць адзін ці больш забароненых слоў. Калі Вы спрабавалі дадаць спам у Вікі -- ай-яй-яй! Калі Вы лічыце, што гэта нейкая памылка, звярніцеся да адміністратара вікі. diff --git a/inc/lang/gl/wordblock.txt b/inc/lang/gl/wordblock.txt deleted file mode 100644 index 686aea36c..000000000 --- a/inc/lang/gl/wordblock.txt +++ /dev/null @@ -1,3 +0,0 @@ -====== Bloqueo por Correo-lixo ====== - -Os teus trocos **non** foron gardados porque conteñen unha ou varias verbas bloqueadas. Se tentaches deixar correo-lixo no wiki -- Estívoche ben! Se consideras que é un erro, contacta co administrador deste Wiki. diff --git a/inc/lang/ru/wordblock.txt b/inc/lang/ru/wordblock.txt deleted file mode 100644 index 09c663fb3..000000000 --- a/inc/lang/ru/wordblock.txt +++ /dev/null @@ -1,3 +0,0 @@ -====== СПАМ заблокирован ====== - -Ваши изменения **не были** сохранены, так как они содержат одно или более запрещенных слов. Если Вы пытались добавить спам в Вики -- ай-яй-яй! Если Вы считаете, что это какая-то ошибка, обратитесь к администратору вики. diff --git a/inc/legacy.php b/inc/legacy.php new file mode 100644 index 000000000..fa72649b7 --- /dev/null +++ b/inc/legacy.php @@ -0,0 +1,18 @@ +<?php +/** + * We map legacy class names to the new namespaced versions here + * + * These are names that we will probably never change because they have been part of DokuWiki's + * public interface for years and renaming would break just too many plugins + */ + +class_alias('\dokuwiki\Extension\EventHandler', 'Doku_Event_Handler'); +class_alias('\dokuwiki\Extension\Event', 'Doku_Event'); + +class_alias('\dokuwiki\Extension\ActionPlugin', 'DokuWiki_Action_Plugin'); +class_alias('\dokuwiki\Extension\AdminPlugin', 'DokuWiki_Admin_Plugin'); +class_alias('\dokuwiki\Extension\AuthPlugin', 'DokuWiki_Auth_Plugin'); +class_alias('\dokuwiki\Extension\CLIPlugin', 'DokuWiki_CLI_Plugin'); +class_alias('\dokuwiki\Extension\Plugin', 'DokuWiki_Plugin'); +class_alias('\dokuwiki\Extension\RemotePlugin', 'DokuWiki_Remote_Plugin'); +class_alias('\dokuwiki\Extension\SyntaxPlugin', 'DokuWiki_Syntax_Plugin'); diff --git a/inc/load.php b/inc/load.php index 2131b9d62..f4b15af27 100644 --- a/inc/load.php +++ b/inc/load.php @@ -5,6 +5,8 @@ * @author Andreas Gohr <andi@splitbrain.org> */ +use dokuwiki\Extension\PluginController; + // setup class autoloader spl_autoload_register('load_autoload'); @@ -15,7 +17,6 @@ require_once(DOKU_INC.'inc/changelog.php'); require_once(DOKU_INC.'inc/common.php'); require_once(DOKU_INC.'inc/confutils.php'); require_once(DOKU_INC.'inc/pluginutils.php'); -require_once(DOKU_INC.'inc/events.php'); require_once(DOKU_INC.'inc/form.php'); require_once(DOKU_INC.'inc/fulltext.php'); require_once(DOKU_INC.'inc/html.php'); @@ -28,12 +29,13 @@ require_once(DOKU_INC.'inc/media.php'); require_once(DOKU_INC.'inc/pageutils.php'); require_once(DOKU_INC.'inc/parserutils.php'); require_once(DOKU_INC.'inc/search.php'); -require_once(DOKU_INC.'inc/subscription.php'); require_once(DOKU_INC.'inc/template.php'); require_once(DOKU_INC.'inc/toolbar.php'); require_once(DOKU_INC.'inc/utf8.php'); require_once(DOKU_INC.'inc/auth.php'); require_once(DOKU_INC.'inc/compatibility.php'); +require_once(DOKU_INC.'inc/deprecated.php'); +require_once(DOKU_INC.'inc/legacy.php'); /** * spl_autoload_register callback @@ -50,10 +52,7 @@ require_once(DOKU_INC.'inc/compatibility.php'); */ function load_autoload($name){ static $classes = null; - if(is_null($classes)) $classes = array( - 'DokuHTTPClient' => DOKU_INC.'inc/HTTPClient.php', - 'HTTPClient' => DOKU_INC.'inc/HTTPClient.php', - 'JSON' => DOKU_INC.'inc/JSON.php', + if($classes === null) $classes = array( 'Diff' => DOKU_INC.'inc/DifferenceEngine.php', 'UnifiedDiffFormatter' => DOKU_INC.'inc/DifferenceEngine.php', 'TableDiffFormatter' => DOKU_INC.'inc/DifferenceEngine.php', @@ -61,8 +60,6 @@ function load_autoload($name){ 'cache_parser' => DOKU_INC.'inc/cache.php', 'cache_instructions' => DOKU_INC.'inc/cache.php', 'cache_renderer' => DOKU_INC.'inc/cache.php', - 'Doku_Event' => DOKU_INC.'inc/events.php', - 'Doku_Event_Handler' => DOKU_INC.'inc/events.php', 'Input' => DOKU_INC.'inc/Input.class.php', 'JpegMeta' => DOKU_INC.'inc/JpegMeta.php', 'SimplePie' => DOKU_INC.'inc/SimplePie.php', @@ -71,29 +68,11 @@ function load_autoload($name){ 'IXR_Client' => DOKU_INC.'inc/IXR_Library.php', 'IXR_Error' => DOKU_INC.'inc/IXR_Library.php', 'IXR_IntrospectionServer' => DOKU_INC.'inc/IXR_Library.php', - 'Doku_Plugin_Controller'=> DOKU_INC.'inc/plugincontroller.class.php', - 'Doku_Parser_Mode' => DOKU_INC.'inc/parser/parser.php', - 'Doku_Parser_Mode_Plugin' => DOKU_INC.'inc/parser/parser.php', 'SafeFN' => DOKU_INC.'inc/SafeFN.class.php', 'Sitemapper' => DOKU_INC.'inc/Sitemapper.php', - 'PassHash' => DOKU_INC.'inc/PassHash.class.php', 'Mailer' => DOKU_INC.'inc/Mailer.class.php', - 'RemoteAPI' => DOKU_INC.'inc/remote.php', - 'RemoteAPICore' => DOKU_INC.'inc/RemoteAPICore.php', - 'Subscription' => DOKU_INC.'inc/subscription.php', - - 'DokuWiki_PluginInterface' => DOKU_INC.'inc/PluginInterface.php', - 'DokuWiki_PluginTrait' => DOKU_INC.'inc/PluginTrait.php', - 'DokuWiki_Plugin' => DOKU_INC.'inc/Plugin.php', - - - 'DokuWiki_Action_Plugin' => DOKU_PLUGIN.'action.php', - 'DokuWiki_Admin_Plugin' => DOKU_PLUGIN.'admin.php', - 'DokuWiki_Syntax_Plugin' => DOKU_PLUGIN.'syntax.php', - 'DokuWiki_Remote_Plugin' => DOKU_PLUGIN.'remote.php', - 'DokuWiki_Auth_Plugin' => DOKU_PLUGIN.'auth.php', - 'DokuWiki_CLI_Plugin' => DOKU_PLUGIN.'cli.php', + 'Doku_Handler' => DOKU_INC.'inc/parser/handler.php', 'Doku_Renderer' => DOKU_INC.'inc/parser/renderer.php', 'Doku_Renderer_xhtml' => DOKU_INC.'inc/parser/xhtml.php', 'Doku_Renderer_code' => DOKU_INC.'inc/parser/code.php', @@ -114,8 +93,17 @@ function load_autoload($name){ // namespace to directory conversion $name = str_replace('\\', '/', $name); + // test namespace + if(substr($name, 0, 14) === 'dokuwiki/test/') { + $file = DOKU_INC . '_test/' . substr($name, 14) . '.php'; + if(file_exists($file)) { + require $file; + return true; + } + } + // plugin namespace - if(substr($name, 0, 16) == 'dokuwiki/plugin/') { + if(substr($name, 0, 16) === 'dokuwiki/plugin/') { $name = str_replace('/test/', '/_test/', $name); // no underscore in test namespace $file = DOKU_PLUGIN . substr($name, 16) . '.php'; if(file_exists($file)) { @@ -125,7 +113,7 @@ function load_autoload($name){ } // template namespace - if(substr($name, 0, 18) == 'dokuwiki/template/') { + if(substr($name, 0, 18) === 'dokuwiki/template/') { $name = str_replace('/test/', '/_test/', $name); // no underscore in test namespace $file = DOKU_INC.'lib/tpl/' . substr($name, 18) . '.php'; if(file_exists($file)) { @@ -135,7 +123,7 @@ function load_autoload($name){ } // our own namespace - if(substr($name, 0, 9) == 'dokuwiki/') { + if(substr($name, 0, 9) === 'dokuwiki/') { $file = DOKU_INC . 'inc/' . substr($name, 9) . '.php'; if(file_exists($file)) { require $file; @@ -144,8 +132,13 @@ function load_autoload($name){ } // Plugin loading - if(preg_match('/^(auth|helper|syntax|action|admin|renderer|remote|cli)_plugin_('.DOKU_PLUGIN_NAME_REGEX.')(?:_([^_]+))?$/', - $name, $m)) { + if(preg_match( + '/^(' . implode('|', PluginController::PLUGIN_TYPES) . ')_plugin_(' . + DOKU_PLUGIN_NAME_REGEX . + ')(?:_([^_]+))?$/', + $name, + $m + )) { // try to load the wanted plugin file $c = ((count($m) === 4) ? "/{$m[3]}" : ''); $plg = DOKU_PLUGIN . "{$m[2]}/{$m[1]}$c.php"; diff --git a/inc/mail.php b/inc/mail.php index f72dbdeec..12a669dbe 100644 --- a/inc/mail.php +++ b/inc/mail.php @@ -6,10 +6,10 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); - // end of line for mail lines - RFC822 says CRLF but postfix (and other MTAs?) // think different +use dokuwiki\Extension\Event; + if(!defined('MAILHEADER_EOL')) define('MAILHEADER_EOL',"\n"); #define('MAILHEADER_ASCIIONLY',1); @@ -27,7 +27,10 @@ if(!defined('MAILHEADER_EOL')) define('MAILHEADER_EOL',"\n"); * Check if a given mail address is valid */ if (!defined('RFC2822_ATEXT')) define('RFC2822_ATEXT',"0-9a-zA-Z!#$%&'*+/=?^_`{|}~-"); -if (!defined('PREG_PATTERN_VALID_EMAIL')) define('PREG_PATTERN_VALID_EMAIL', '['.RFC2822_ATEXT.']+(?:\.['.RFC2822_ATEXT.']+)*@(?i:[0-9a-z][0-9a-z-]*\.)+(?i:[a-z]{2,63})'); +if (!defined('PREG_PATTERN_VALID_EMAIL')) define( + 'PREG_PATTERN_VALID_EMAIL', + '['.RFC2822_ATEXT.']+(?:\.['.RFC2822_ATEXT.']+)*@(?i:[0-9a-z][0-9a-z-]*\.)+(?i:[a-z]{2,63})' +); /** * Prepare mailfrom replacement patterns @@ -102,7 +105,7 @@ function mail_setup(){ function mail_send($to, $subject, $body, $from='', $cc='', $bcc='', $headers=null, $params=null){ dbg_deprecated('class Mailer::'); $message = compact('to','subject','body','from','cc','bcc','headers','params'); - return trigger_event('MAIL_MESSAGE_SEND',$message,'_mail_send_action'); + return Event::createAndTrigger('MAIL_MESSAGE_SEND',$message,'_mail_send_action'); } /** @@ -131,11 +134,11 @@ function _mail_send_action($data) { // end additional code to support event ... original mail_send() code from here if(defined('MAILHEADER_ASCIIONLY')){ - $subject = utf8_deaccent($subject); - $subject = utf8_strip($subject); + $subject = \dokuwiki\Utf8\Clean::deaccent($subject); + $subject = \dokuwiki\Utf8\Clean::strip($subject); } - if(!utf8_isASCII($subject)) { + if(!\dokuwiki\Utf8\Clean::isASCII($subject)) { $enc_subj = '=?UTF-8?Q?'.mail_quotedprintable_encode($subject,0).'?='; // Spaces must be encoded according to rfc2047. Use the "_" shorthand $enc_subj = preg_replace('/ /', '_', $enc_subj); @@ -209,7 +212,7 @@ function mail_encode_address($string,$header='',$names=true){ } // FIXME: is there a way to encode the localpart of a emailaddress? - if(!utf8_isASCII($addr)){ + if(!\dokuwiki\Utf8\Clean::isASCII($addr)){ msg(hsc("E-Mail address <$addr> is not ASCII"),-1); continue; } @@ -225,11 +228,11 @@ function mail_encode_address($string,$header='',$names=true){ $addr = "<$addr>"; if(defined('MAILHEADER_ASCIIONLY')){ - $text = utf8_deaccent($text); - $text = utf8_strip($text); + $text = \dokuwiki\Utf8\Clean::deaccent($text); + $text = \dokuwiki\Utf8\Clean::strip($text); } - if(!utf8_isASCII($text)){ + if(!\dokuwiki\Utf8\Clean::isASCII($text)){ // put the quotes outside as in =?UTF-8?Q?"Elan Ruusam=C3=A4e"?= vs "=?UTF-8?Q?Elan Ruusam=C3=A4e?=" if (preg_match('/^"(.+)"$/', $text, $matches)) { $text = '"=?UTF-8?Q?'.mail_quotedprintable_encode($matches[1], 0).'?="'; diff --git a/inc/media.php b/inc/media.php index 128466061..41fbc0b8f 100644 --- a/inc/media.php +++ b/inc/media.php @@ -6,8 +6,10 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); -if(!defined('NL')) define('NL',"\n"); +use dokuwiki\ChangeLog\MediaChangeLog; +use dokuwiki\HTTP\DokuHTTPClient; +use dokuwiki\Subscriptions\MediaSubscriptionSender; +use dokuwiki\Extension\Event; /** * Lists pages which currently use a media file selected for deletion @@ -172,7 +174,17 @@ function media_metaform($id,$auth){ $form->addElement('<div class="row">'); if($field[2] == 'text'){ - $form->addElement(form_makeField('text', $p['name'], $value, ($lang[$field[1]]) ? $lang[$field[1]] : $field[1] . ':', $p['id'], $p['class'], $p_attrs)); + $form->addElement( + form_makeField( + 'text', + $p['name'], + $value, + ($lang[$field[1]]) ? $lang[$field[1]] : $field[1] . ':', + $p['id'], + $p['class'], + $p_attrs + ) + ); }else{ $att = buildAttributes($p); $form->addElement('<label for="meta__'.$key.'">'.$lang[$field[1]].'</label>'); @@ -181,7 +193,14 @@ function media_metaform($id,$auth){ $form->addElement('</div>'.NL); } $form->addElement('<div class="buttons">'); - $form->addElement(form_makeButton('submit', '', $lang['btn_save'], array('accesskey' => 's', 'name' => 'mediado[save]'))); + $form->addElement( + form_makeButton( + 'submit', + '', + $lang['btn_save'], + array('accesskey' => 's', 'name' => 'mediado[save]') + ) + ); $form->addElement('</div>'.NL); $form->printForm(); @@ -242,13 +261,13 @@ function media_delete($id,$auth){ // trigger an event - MEDIA_DELETE_FILE $data = array(); $data['id'] = $id; - $data['name'] = utf8_basename($file); + $data['name'] = \dokuwiki\Utf8\PhpString::basename($file); $data['path'] = $file; $data['size'] = (file_exists($file)) ? filesize($file) : 0; $data['unl'] = false; $data['del'] = false; - $evt = new Doku_Event('MEDIA_DELETE_FILE',$data); + $evt = new Event('MEDIA_DELETE_FILE',$data); if ($evt->advise_before()) { $old = @filemtime($file); if(!file_exists(mediaFN($id, $old)) && file_exists($file)) { @@ -466,7 +485,7 @@ function media_save($file, $id, $ow, $auth, $move) { $data[5] = $move; // trigger event - return trigger_event('MEDIA_UPLOAD_FINISH', $data, '_media_upload_action', true); + return Event::createAndTrigger('MEDIA_UPLOAD_FINISH', $data, '_media_upload_action', true); } /** @@ -530,7 +549,15 @@ function media_upload_finish($fn_tmp, $fn, $id, $imime, $overwrite, $move = 'mov $filesize_new = filesize($fn); $sizechange = $filesize_new - $filesize_old; if($REV) { - addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_REVERT, sprintf($lang['restored'], dformat($REV)), $REV, null, $sizechange); + addMediaLogEntry( + $new, + $id, + DOKU_CHANGE_TYPE_REVERT, + sprintf($lang['restored'], dformat($REV)), + $REV, + null, + $sizechange + ); } elseif($overwrite) { addMediaLogEntry($new, $id, DOKU_CHANGE_TYPE_EDIT, '', '', null, $sizechange); } else { @@ -649,8 +676,8 @@ function media_notify($id,$file,$mime,$old_rev=false){ global $conf; if(empty($conf['notify'])) return false; //notify enabled? - $subscription = new Subscription(); - return $subscription->send_media_diff($conf['notify'], 'uploadmail', $id, $old_rev); + $subscription = new MediaSubscriptionSender(); + return $subscription->sendMediaDiff($conf['notify'], 'uploadmail', $id, $old_rev); } /** @@ -1249,7 +1276,7 @@ function media_diff($image, $ns, $auth, $fromajax = false) { $data[5] = $fromajax; // trigger event - return trigger_event('MEDIA_DIFF', $data, '_media_file_diff', true); + return Event::createAndTrigger('MEDIA_DIFF', $data, '_media_file_diff', true); } /** @@ -1487,7 +1514,7 @@ function media_searchlist($query,$ns,$auth=null,$fullscreen=false,$sort='natural 'query' => $query ); if (!blank($query)) { - $evt = new Doku_Event('MEDIA_SEARCH', $evdata); + $evt = new Event('MEDIA_SEARCH', $evdata); if ($evt->advise_before()) { $dir = utf8_encodeFN(str_replace(':','/',$evdata['ns'])); $quoted = preg_quote($evdata['query'],'/'); @@ -1716,7 +1743,7 @@ function media_printimgdetail($item, $fullscreen=false){ // output if ($fullscreen) { echo '<a id="l_:'.$item['id'].'" class="image thumb" href="'. - media_managerURL(array('image' => hsc($item['id']), 'ns' => getNS($item['id']), 'tab_details' => 'view')).'">'; + media_managerURL(['image' => hsc($item['id']), 'ns' => getNS($item['id']), 'tab_details' => 'view']).'">'; echo '<img src="'.$src.'" '.$att.' />'; echo '</a>'; } @@ -1735,7 +1762,7 @@ function media_printimgdetail($item, $fullscreen=false){ $d = $item['meta']->getField(array('IPTC.Caption','EXIF.UserComment', 'EXIF.TIFFImageDescription', 'EXIF.TIFFUserComment')); - if(utf8_strlen($d) > 250) $d = utf8_substr($d,0,250).'...'; + if(\dokuwiki\Utf8\PhpString::strlen($d) > 250) $d = \dokuwiki\Utf8\PhpString::substr($d,0,250).'...'; $k = $item['meta']->getField(array('IPTC.Keywords','IPTC.Category','xmp.dc:subject')); // print EXIF/IPTC data @@ -1900,7 +1927,16 @@ function media_searchform($ns,$query='',$fullscreen=false){ $form->addHidden($fullscreen ? 'mediado' : 'do', 'searchlist'); $form->addElement(form_makeOpenTag('p')); - $form->addElement(form_makeTextField('q', $query,$lang['searchmedia'],'','',array('title'=>sprintf($lang['searchmedia_in'],hsc($ns).':*')))); + $form->addElement( + form_makeTextField( + 'q', + $query, + $lang['searchmedia'], + '', + '', + array('title' => sprintf($lang['searchmedia_in'], hsc($ns) . ':*')) + ) + ); $form->addElement(form_makeButton('submit', '', $lang['btn_search'])); $form->addElement(form_makeCloseTag('p')); html_form('searchmedia', $form); @@ -1943,7 +1979,13 @@ function media_nstree($ns){ // find the namespace parts or insert them while ($data[$pos]['id'] != $tmp_ns) { - if ($pos >= count($data) || ($data[$pos]['level'] <= $level+1 && strnatcmp(utf8_encodeFN($data[$pos]['id']), utf8_encodeFN($tmp_ns)) > 0)) { + if ( + $pos >= count($data) || + ( + $data[$pos]['level'] <= $level+1 && + strnatcmp(utf8_encodeFN($data[$pos]['id']), utf8_encodeFN($tmp_ns)) > 0 + ) + ) { array_splice($data, $pos, 0, array(array('level' => $level+1, 'id' => $tmp_ns, 'open' => 'true'))); break; } @@ -2134,7 +2176,7 @@ function media_get_token($id,$w,$h){ if ($w) $token .= '.'.$w; if ($h) $token .= '.'.$h; - return substr(PassHash::hmac('md5', $token, auth_cookiesalt()),0,6); + return substr(\dokuwiki\PassHash::hmac('md5', $token, auth_cookiesalt()),0,6); } return ''; @@ -2353,7 +2395,12 @@ function media_resize_imageGD($ext,$from,$from_w,$from_h,$to,$to_w,$to_h,$ofs_x= $transcolorindex = @imagecolortransparent($image); if($transcolorindex >= 0 ) { //transparent color exists $transcolor = @imagecolorsforindex($image, $transcolorindex); - $transcolorindex = @imagecolorallocate($newimg, $transcolor['red'], $transcolor['green'], $transcolor['blue']); + $transcolorindex = @imagecolorallocate( + $newimg, + $transcolor['red'], + $transcolor['green'], + $transcolor['blue'] + ); @imagefill($newimg, 0, 0, $transcolorindex); @imagecolortransparent($newimg, $transcolorindex); }else{ //filling with white diff --git a/inc/pageutils.php b/inc/pageutils.php index 3f81eb7e7..bc39a6fef 100644 --- a/inc/pageutils.php +++ b/inc/pageutils.php @@ -7,6 +7,9 @@ * @todo Combine similar functions like {wiki,media,meta}FN() */ +use dokuwiki\ChangeLog\MediaChangeLog; +use dokuwiki\ChangeLog\PageChangeLog; + /** * Fetch the an ID from request * @@ -41,7 +44,8 @@ function getID($param='id',$clean=true){ if($param != 'id') { $relpath = 'lib/exe/'; } - $script = $conf['basedir'].$relpath.utf8_basename($INPUT->server->str('SCRIPT_FILENAME')); + $script = $conf['basedir'] . $relpath . + \dokuwiki\Utf8\PhpString::basename($INPUT->server->str('SCRIPT_FILENAME')); }elseif($INPUT->server->str('PATH_INFO')){ $request = $INPUT->server->str('PATH_INFO'); @@ -124,7 +128,7 @@ function cleanID($raw_id,$ascii=false){ $sepcharpat = '#\\'.$sepchar.'+#'; $id = trim((string)$raw_id); - $id = utf8_strtolower($id); + $id = \dokuwiki\Utf8\PhpString::strtolower($id); //alternative namespace seperator if($conf['useslash']){ @@ -133,13 +137,13 @@ function cleanID($raw_id,$ascii=false){ $id = strtr($id,';/',':'.$sepchar); } - if($conf['deaccent'] == 2 || $ascii) $id = utf8_romanize($id); - if($conf['deaccent'] || $ascii) $id = utf8_deaccent($id,-1); + if($conf['deaccent'] == 2 || $ascii) $id = \dokuwiki\Utf8\Clean::romanize($id); + if($conf['deaccent'] || $ascii) $id = \dokuwiki\Utf8\Clean::deaccent($id,-1); //remove specials - $id = utf8_stripspecials($id,$sepchar,'\*'); + $id = \dokuwiki\Utf8\Clean::stripspecials($id,$sepchar,'\*'); - if($ascii) $id = utf8_strip($id); + if($ascii) $id = \dokuwiki\Utf8\Clean::strip($id); //clean up $id = preg_replace($sepcharpat,$sepchar,$id); @@ -633,7 +637,7 @@ function isHiddenPage($id){ 'id' => $id, 'hidden' => false ); - trigger_event('PAGEUTILS_ID_HIDEPAGE', $data, '_isHiddenPage'); + \dokuwiki\Extension\Event::createAndTrigger('PAGEUTILS_ID_HIDEPAGE', $data, '_isHiddenPage'); return $data['hidden']; } diff --git a/inc/parser/code.php b/inc/parser/code.php index f91f1d228..cded87d6d 100644 --- a/inc/parser/code.php +++ b/inc/parser/code.php @@ -4,10 +4,8 @@ * * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); - class Doku_Renderer_code extends Doku_Renderer { - var $_codeblock = 0; + protected $_codeblock = 0; /** * Send the wanted code block to the browser @@ -18,13 +16,13 @@ class Doku_Renderer_code extends Doku_Renderer { * @param string $language * @param string $filename */ - function code($text, $language = null, $filename = '') { + public function code($text, $language = null, $filename = '') { global $INPUT; if(!$language) $language = 'txt'; $language = preg_replace(PREG_PATTERN_VALID_LANGUAGE, '', $language); if(!$filename) $filename = 'snippet.'.$language; - $filename = utf8_basename($filename); - $filename = utf8_stripspecials($filename, '_'); + $filename = \dokuwiki\Utf8\PhpString::basename($filename); + $filename = \dokuwiki\Utf8\Clean::stripspecials($filename, '_'); // send CRLF to Windows clients if(strpos($INPUT->server->str('HTTP_USER_AGENT'), 'Windows') !== false) { @@ -49,14 +47,14 @@ class Doku_Renderer_code extends Doku_Renderer { * @param string $language * @param string $filename */ - function file($text, $language = null, $filename = '') { + public function file($text, $language = null, $filename = '') { $this->code($text, $language, $filename); } /** * This should never be reached, if it is send a 404 */ - function document_end() { + public function document_end() { http_status(404); echo '404 - Not found'; exit; @@ -67,7 +65,7 @@ class Doku_Renderer_code extends Doku_Renderer { * * @returns string 'code' */ - function getFormat() { + public function getFormat() { return 'code'; } } diff --git a/inc/parser/handler.php b/inc/parser/handler.php index 12551588b..19c2aafe8 100644 --- a/inc/parser/handler.php +++ b/inc/parser/handler.php @@ -1,44 +1,78 @@ <?php -if(!defined('DOKU_INC')) die('meh.'); -if (!defined('DOKU_PARSER_EOL')) define('DOKU_PARSER_EOL',"\n"); // add this to make handling test cases simpler -class Doku_Handler { - - var $Renderer = null; +use dokuwiki\Extension\Event; +use dokuwiki\Extension\SyntaxPlugin; +use dokuwiki\Parsing\Handler\Block; +use dokuwiki\Parsing\Handler\CallWriter; +use dokuwiki\Parsing\Handler\CallWriterInterface; +use dokuwiki\Parsing\Handler\Lists; +use dokuwiki\Parsing\Handler\Nest; +use dokuwiki\Parsing\Handler\Preformatted; +use dokuwiki\Parsing\Handler\Quote; +use dokuwiki\Parsing\Handler\Table; - var $CallWriter = null; +/** + * Class Doku_Handler + */ +class Doku_Handler { + /** @var CallWriterInterface */ + protected $callWriter = null; - var $calls = array(); + /** @var array The current CallWriter will write directly to this list of calls, Parser reads it */ + public $calls = array(); - var $status = array( + /** @var array internal status holders for some modes */ + protected $status = array( 'section' => false, 'doublequote' => 0, ); - var $rewriteBlocks = true; + /** @var bool should blocks be rewritten? FIXME seems to always be true */ + protected $rewriteBlocks = true; - function __construct() { - $this->CallWriter = new Doku_Handler_CallWriter($this); + /** + * Doku_Handler constructor. + */ + public function __construct() { + $this->callWriter = new CallWriter($this); } /** - * @param string $handler - * @param mixed $args - * @param integer|string $pos + * Add a new call by passing it to the current CallWriter + * + * @param string $handler handler method name (see mode handlers below) + * @param mixed $args arguments for this call + * @param int $pos byte position in the original source file */ - function _addCall($handler, $args, $pos) { + protected function addCall($handler, $args, $pos) { $call = array($handler,$args, $pos); - $this->CallWriter->writeCall($call); + $this->callWriter->writeCall($call); } - function addPluginCall($plugin, $args, $state, $pos, $match) { + /** + * Similar to addCall, but adds a plugin call + * + * @param string $plugin name of the plugin + * @param mixed $args arguments for this call + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @param string $match matched syntax + */ + protected function addPluginCall($plugin, $args, $state, $pos, $match) { $call = array('plugin',array($plugin, $args, $state, $match), $pos); - $this->CallWriter->writeCall($call); + $this->callWriter->writeCall($call); } - function _finalize(){ - - $this->CallWriter->finalise(); + /** + * Finishes handling + * + * Called from the parser. Calls finalise() on the call writer, closes open + * sections, rewrites blocks and adds document_start and document_end calls. + * + * @triggers PARSER_HANDLER_DONE + */ + public function finalize(){ + $this->callWriter->finalise(); if ( $this->status['section'] ) { $last_call = end($this->calls); @@ -46,11 +80,11 @@ class Doku_Handler { } if ( $this->rewriteBlocks ) { - $B = new Doku_Handler_Block(); + $B = new Block(); $this->calls = $B->process($this->calls); } - trigger_event('PARSER_HANDLER_DONE',$this); + Event::createAndTrigger('PARSER_HANDLER_DONE',$this); array_unshift($this->calls,array('document_start',array(),0)); $last_call = end($this->calls); @@ -60,9 +94,10 @@ class Doku_Handler { /** * fetch the current call and advance the pointer to the next one * + * @fixme seems to be unused? * @return bool|mixed */ - function fetch() { + public function fetch() { $call = current($this->calls); if($call !== false) { next($this->calls); //advance the pointer @@ -73,23 +108,127 @@ class Doku_Handler { /** + * Internal function for parsing highlight options. + * $options is parsed for key value pairs separated by commas. + * A value might also be missing in which case the value will simple + * be set to true. Commas in strings are ignored, e.g. option="4,56" + * will work as expected and will only create one entry. + * + * @param string $options space separated list of key-value pairs, + * e.g. option1=123, option2="456" + * @return array|null Array of key-value pairs $array['key'] = 'value'; + * or null if no entries found + */ + protected function parse_highlight_options($options) { + $result = array(); + preg_match_all('/(\w+(?:="[^"]*"))|(\w+(?:=[^\s]*))|(\w+[^=\s\]])(?:\s*)/', $options, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $equal_sign = strpos($match [0], '='); + if ($equal_sign === false) { + $key = trim($match[0]); + $result [$key] = 1; + } else { + $key = substr($match[0], 0, $equal_sign); + $value = substr($match[0], $equal_sign+1); + $value = trim($value, '"'); + if (strlen($value) > 0) { + $result [$key] = $value; + } else { + $result [$key] = 1; + } + } + } + + // Check for supported options + $result = array_intersect_key( + $result, + array_flip(array( + 'enable_line_numbers', + 'start_line_numbers_at', + 'highlight_lines_extra', + 'enable_keyword_links') + ) + ); + + // Sanitize values + if(isset($result['enable_line_numbers'])) { + if($result['enable_line_numbers'] === 'false') { + $result['enable_line_numbers'] = false; + } + $result['enable_line_numbers'] = (bool) $result['enable_line_numbers']; + } + if(isset($result['highlight_lines_extra'])) { + $result['highlight_lines_extra'] = array_map('intval', explode(',', $result['highlight_lines_extra'])); + $result['highlight_lines_extra'] = array_filter($result['highlight_lines_extra']); + $result['highlight_lines_extra'] = array_unique($result['highlight_lines_extra']); + } + if(isset($result['start_line_numbers_at'])) { + $result['start_line_numbers_at'] = (int) $result['start_line_numbers_at']; + } + if(isset($result['enable_keyword_links'])) { + if($result['enable_keyword_links'] === 'false') { + $result['enable_keyword_links'] = false; + } + $result['enable_keyword_links'] = (bool) $result['enable_keyword_links']; + } + if (count($result) == 0) { + return null; + } + + return $result; + } + + /** + * Simplifies handling for the formatting tags which all behave the same + * + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @param string $name actual mode name + */ + protected function nestingTag($match, $state, $pos, $name) { + switch ( $state ) { + case DOKU_LEXER_ENTER: + $this->addCall($name.'_open', array(), $pos); + break; + case DOKU_LEXER_EXIT: + $this->addCall($name.'_close', array(), $pos); + break; + case DOKU_LEXER_UNMATCHED: + $this->addCall('cdata', array($match), $pos); + break; + } + } + + + /** + * The following methods define the handlers for the different Syntax modes + * + * The handlers are called from dokuwiki\Parsing\Lexer\Lexer\invokeParser() + * + * @todo it might make sense to move these into their own class or merge them with the + * ParserMode classes some time. + */ + // region mode handlers + + /** * Special plugin handler * * This handler is called for all modes starting with 'plugin_'. - * An additional parameter with the plugin name is passed + * An additional parameter with the plugin name is passed. The plugin's handle() + * method is called here * * @author Andreas Gohr <andi@splitbrain.org> * - * @param string|integer $match - * @param string|integer $state - * @param integer $pos - * @param $pluginname - * - * @return bool + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @param string $pluginname name of the plugin + * @return bool mode handled? */ - function plugin($match, $state, $pos, $pluginname){ + public function plugin($match, $state, $pos, $pluginname){ $data = array($match); - /** @var DokuWiki_Syntax_Plugin $plugin */ + /** @var SyntaxPlugin $plugin */ $plugin = plugin_load('syntax',$pluginname); if($plugin != null){ $data = $plugin->handle($match, $state, $pos, $this); @@ -100,16 +239,29 @@ class Doku_Handler { return true; } - function base($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function base($match, $state, $pos) { switch ( $state ) { case DOKU_LEXER_UNMATCHED: - $this->_addCall('cdata',array($match), $pos); + $this->addCall('cdata', array($match), $pos); return true; break; } + return false; } - function header($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function header($match, $state, $pos) { // get level and title $title = trim($match); $level = 7 - strspn($title,'='); @@ -117,98 +269,154 @@ class Doku_Handler { $title = trim($title,'='); $title = trim($title); - if ($this->status['section']) $this->_addCall('section_close',array(),$pos); + if ($this->status['section']) $this->addCall('section_close', array(), $pos); - $this->_addCall('header',array($title,$level,$pos), $pos); + $this->addCall('header', array($title, $level, $pos), $pos); - $this->_addCall('section_open',array($level),$pos); + $this->addCall('section_open', array($level), $pos); $this->status['section'] = true; return true; } - function notoc($match, $state, $pos) { - $this->_addCall('notoc',array(),$pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function notoc($match, $state, $pos) { + $this->addCall('notoc', array(), $pos); return true; } - function nocache($match, $state, $pos) { - $this->_addCall('nocache',array(),$pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function nocache($match, $state, $pos) { + $this->addCall('nocache', array(), $pos); return true; } - function linebreak($match, $state, $pos) { - $this->_addCall('linebreak',array(),$pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function linebreak($match, $state, $pos) { + $this->addCall('linebreak', array(), $pos); return true; } - function eol($match, $state, $pos) { - $this->_addCall('eol',array(),$pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function eol($match, $state, $pos) { + $this->addCall('eol', array(), $pos); return true; } - function hr($match, $state, $pos) { - $this->_addCall('hr',array(),$pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function hr($match, $state, $pos) { + $this->addCall('hr', array(), $pos); return true; } /** - * @param string|integer $match - * @param string|integer $state - * @param integer $pos - * @param string $name + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? */ - function _nestingTag($match, $state, $pos, $name) { - switch ( $state ) { - case DOKU_LEXER_ENTER: - $this->_addCall($name.'_open', array(), $pos); - break; - case DOKU_LEXER_EXIT: - $this->_addCall($name.'_close', array(), $pos); - break; - case DOKU_LEXER_UNMATCHED: - $this->_addCall('cdata',array($match), $pos); - break; - } - } - - function strong($match, $state, $pos) { - $this->_nestingTag($match, $state, $pos, 'strong'); + public function strong($match, $state, $pos) { + $this->nestingTag($match, $state, $pos, 'strong'); return true; } - function emphasis($match, $state, $pos) { - $this->_nestingTag($match, $state, $pos, 'emphasis'); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function emphasis($match, $state, $pos) { + $this->nestingTag($match, $state, $pos, 'emphasis'); return true; } - function underline($match, $state, $pos) { - $this->_nestingTag($match, $state, $pos, 'underline'); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function underline($match, $state, $pos) { + $this->nestingTag($match, $state, $pos, 'underline'); return true; } - function monospace($match, $state, $pos) { - $this->_nestingTag($match, $state, $pos, 'monospace'); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function monospace($match, $state, $pos) { + $this->nestingTag($match, $state, $pos, 'monospace'); return true; } - function subscript($match, $state, $pos) { - $this->_nestingTag($match, $state, $pos, 'subscript'); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function subscript($match, $state, $pos) { + $this->nestingTag($match, $state, $pos, 'subscript'); return true; } - function superscript($match, $state, $pos) { - $this->_nestingTag($match, $state, $pos, 'superscript'); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function superscript($match, $state, $pos) { + $this->nestingTag($match, $state, $pos, 'superscript'); return true; } - function deleted($match, $state, $pos) { - $this->_nestingTag($match, $state, $pos, 'deleted'); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function deleted($match, $state, $pos) { + $this->nestingTag($match, $state, $pos, 'deleted'); return true; } - - function footnote($match, $state, $pos) { -// $this->_nestingTag($match, $state, $pos, 'footnote'); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function footnote($match, $state, $pos) { if (!isset($this->_footnote)) $this->_footnote = false; switch ( $state ) { @@ -216,146 +424,185 @@ class Doku_Handler { // footnotes can not be nested - however due to limitations in lexer it can't be prevented // we will still enter a new footnote mode, we just do nothing if ($this->_footnote) { - $this->_addCall('cdata',array($match), $pos); + $this->addCall('cdata', array($match), $pos); break; } - $this->_footnote = true; - $ReWriter = new Doku_Handler_Nest($this->CallWriter,'footnote_close'); - $this->CallWriter = & $ReWriter; - $this->_addCall('footnote_open', array(), $pos); + $this->callWriter = new Nest($this->callWriter, 'footnote_close'); + $this->addCall('footnote_open', array(), $pos); break; case DOKU_LEXER_EXIT: // check whether we have already exitted the footnote mode, can happen if the modes were nested if (!$this->_footnote) { - $this->_addCall('cdata',array($match), $pos); + $this->addCall('cdata', array($match), $pos); break; } $this->_footnote = false; + $this->addCall('footnote_close', array(), $pos); - $this->_addCall('footnote_close', array(), $pos); - $this->CallWriter->process(); - $ReWriter = & $this->CallWriter; - $this->CallWriter = & $ReWriter->CallWriter; + /** @var Nest $reWriter */ + $reWriter = $this->callWriter; + $this->callWriter = $reWriter->process(); break; case DOKU_LEXER_UNMATCHED: - $this->_addCall('cdata', array($match), $pos); + $this->addCall('cdata', array($match), $pos); break; } return true; } - function listblock($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function listblock($match, $state, $pos) { switch ( $state ) { case DOKU_LEXER_ENTER: - $ReWriter = new Doku_Handler_List($this->CallWriter); - $this->CallWriter = & $ReWriter; - $this->_addCall('list_open', array($match), $pos); + $this->callWriter = new Lists($this->callWriter); + $this->addCall('list_open', array($match), $pos); break; case DOKU_LEXER_EXIT: - $this->_addCall('list_close', array(), $pos); - $this->CallWriter->process(); - $ReWriter = & $this->CallWriter; - $this->CallWriter = & $ReWriter->CallWriter; + $this->addCall('list_close', array(), $pos); + /** @var Lists $reWriter */ + $reWriter = $this->callWriter; + $this->callWriter = $reWriter->process(); break; case DOKU_LEXER_MATCHED: - $this->_addCall('list_item', array($match), $pos); + $this->addCall('list_item', array($match), $pos); break; case DOKU_LEXER_UNMATCHED: - $this->_addCall('cdata', array($match), $pos); + $this->addCall('cdata', array($match), $pos); break; } return true; } - function unformatted($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function unformatted($match, $state, $pos) { if ( $state == DOKU_LEXER_UNMATCHED ) { - $this->_addCall('unformatted',array($match), $pos); + $this->addCall('unformatted', array($match), $pos); } return true; } - function php($match, $state, $pos) { - global $conf; + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function php($match, $state, $pos) { if ( $state == DOKU_LEXER_UNMATCHED ) { - $this->_addCall('php',array($match), $pos); + $this->addCall('php', array($match), $pos); } return true; } - function phpblock($match, $state, $pos) { - global $conf; + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function phpblock($match, $state, $pos) { if ( $state == DOKU_LEXER_UNMATCHED ) { - $this->_addCall('phpblock',array($match), $pos); + $this->addCall('phpblock', array($match), $pos); } return true; } - function html($match, $state, $pos) { - global $conf; + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function html($match, $state, $pos) { if ( $state == DOKU_LEXER_UNMATCHED ) { - $this->_addCall('html',array($match), $pos); + $this->addCall('html', array($match), $pos); } return true; } - function htmlblock($match, $state, $pos) { - global $conf; + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function htmlblock($match, $state, $pos) { if ( $state == DOKU_LEXER_UNMATCHED ) { - $this->_addCall('htmlblock',array($match), $pos); + $this->addCall('htmlblock', array($match), $pos); } return true; } - function preformatted($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function preformatted($match, $state, $pos) { switch ( $state ) { case DOKU_LEXER_ENTER: - $ReWriter = new Doku_Handler_Preformatted($this->CallWriter); - $this->CallWriter = $ReWriter; - $this->_addCall('preformatted_start',array(), $pos); + $this->callWriter = new Preformatted($this->callWriter); + $this->addCall('preformatted_start', array(), $pos); break; case DOKU_LEXER_EXIT: - $this->_addCall('preformatted_end',array(), $pos); - $this->CallWriter->process(); - $ReWriter = & $this->CallWriter; - $this->CallWriter = & $ReWriter->CallWriter; + $this->addCall('preformatted_end', array(), $pos); + /** @var Preformatted $reWriter */ + $reWriter = $this->callWriter; + $this->callWriter = $reWriter->process(); break; case DOKU_LEXER_MATCHED: - $this->_addCall('preformatted_newline',array(), $pos); + $this->addCall('preformatted_newline', array(), $pos); break; case DOKU_LEXER_UNMATCHED: - $this->_addCall('preformatted_content',array($match), $pos); + $this->addCall('preformatted_content', array($match), $pos); break; } return true; } - function quote($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function quote($match, $state, $pos) { switch ( $state ) { case DOKU_LEXER_ENTER: - $ReWriter = new Doku_Handler_Quote($this->CallWriter); - $this->CallWriter = & $ReWriter; - $this->_addCall('quote_start',array($match), $pos); + $this->callWriter = new Quote($this->callWriter); + $this->addCall('quote_start', array($match), $pos); break; case DOKU_LEXER_EXIT: - $this->_addCall('quote_end',array(), $pos); - $this->CallWriter->process(); - $ReWriter = & $this->CallWriter; - $this->CallWriter = & $ReWriter->CallWriter; + $this->addCall('quote_end', array(), $pos); + /** @var Lists $reWriter */ + $reWriter = $this->callWriter; + $this->callWriter = $reWriter->process(); break; case DOKU_LEXER_MATCHED: - $this->_addCall('quote_newline',array($match), $pos); + $this->addCall('quote_newline', array($match), $pos); break; case DOKU_LEXER_UNMATCHED: - $this->_addCall('cdata',array($match), $pos); + $this->addCall('cdata', array($match), $pos); break; } @@ -364,81 +611,23 @@ class Doku_Handler { } /** - * Internal function for parsing highlight options. - * $options is parsed for key value pairs separated by commas. - * A value might also be missing in which case the value will simple - * be set to true. Commas in strings are ignored, e.g. option="4,56" - * will work as expected and will only create one entry. - * - * @param string $options space separated list of key-value pairs, - * e.g. option1=123, option2="456" - * @return array|null Array of key-value pairs $array['key'] = 'value'; - * or null if no entries found + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? */ - protected function parse_highlight_options ($options) { - $result = array(); - preg_match_all('/(\w+(?:="[^"]*"))|(\w+(?:=[^\s]*))|(\w+[^=\s\]])(?:\s*)/', $options, $matches, PREG_SET_ORDER); - foreach ($matches as $match) { - $equal_sign = strpos($match [0], '='); - if ($equal_sign === false) { - $key = trim($match[0]); - $result [$key] = 1; - } else { - $key = substr($match[0], 0, $equal_sign); - $value = substr($match[0], $equal_sign+1); - $value = trim($value, '"'); - if (strlen($value) > 0) { - $result [$key] = $value; - } else { - $result [$key] = 1; - } - } - } - - // Check for supported options - $result = array_intersect_key( - $result, - array_flip(array( - 'enable_line_numbers', - 'start_line_numbers_at', - 'highlight_lines_extra', - 'enable_keyword_links') - ) - ); - - // Sanitize values - if(isset($result['enable_line_numbers'])) { - if($result['enable_line_numbers'] === 'false') { - $result['enable_line_numbers'] = false; - } - $result['enable_line_numbers'] = (bool) $result['enable_line_numbers']; - } - if(isset($result['highlight_lines_extra'])) { - $result['highlight_lines_extra'] = array_map('intval', explode(',', $result['highlight_lines_extra'])); - $result['highlight_lines_extra'] = array_filter($result['highlight_lines_extra']); - $result['highlight_lines_extra'] = array_unique($result['highlight_lines_extra']); - } - if(isset($result['start_line_numbers_at'])) { - $result['start_line_numbers_at'] = (int) $result['start_line_numbers_at']; - } - if(isset($result['enable_keyword_links'])) { - if($result['enable_keyword_links'] === 'false') { - $result['enable_keyword_links'] = false; - } - $result['enable_keyword_links'] = (bool) $result['enable_keyword_links']; - } - if (count($result) == 0) { - return null; - } - - return $result; - } - - function file($match, $state, $pos) { + public function file($match, $state, $pos) { return $this->code($match, $state, $pos, 'file'); } - function code($match, $state, $pos, $type='code') { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @param string $type either 'code' or 'file' + * @return bool mode handled? + */ + public function code($match, $state, $pos, $type='code') { if ( $state == DOKU_LEXER_UNMATCHED ) { $matches = explode('>',$match,2); // Cut out variable options enclosed in [] @@ -455,76 +644,146 @@ class Doku_Handler { if (!empty($options[0])) { $param [] = $this->parse_highlight_options ($options[0]); } - $this->_addCall($type, $param, $pos); + $this->addCall($type, $param, $pos); } return true; } - function acronym($match, $state, $pos) { - $this->_addCall('acronym',array($match), $pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function acronym($match, $state, $pos) { + $this->addCall('acronym', array($match), $pos); return true; } - function smiley($match, $state, $pos) { - $this->_addCall('smiley',array($match), $pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function smiley($match, $state, $pos) { + $this->addCall('smiley', array($match), $pos); return true; } - function wordblock($match, $state, $pos) { - $this->_addCall('wordblock',array($match), $pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function wordblock($match, $state, $pos) { + $this->addCall('wordblock', array($match), $pos); return true; } - function entity($match, $state, $pos) { - $this->_addCall('entity',array($match), $pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function entity($match, $state, $pos) { + $this->addCall('entity', array($match), $pos); return true; } - function multiplyentity($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function multiplyentity($match, $state, $pos) { preg_match_all('/\d+/',$match,$matches); - $this->_addCall('multiplyentity',array($matches[0][0],$matches[0][1]), $pos); + $this->addCall('multiplyentity', array($matches[0][0], $matches[0][1]), $pos); return true; } - function singlequoteopening($match, $state, $pos) { - $this->_addCall('singlequoteopening',array(), $pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function singlequoteopening($match, $state, $pos) { + $this->addCall('singlequoteopening', array(), $pos); return true; } - function singlequoteclosing($match, $state, $pos) { - $this->_addCall('singlequoteclosing',array(), $pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function singlequoteclosing($match, $state, $pos) { + $this->addCall('singlequoteclosing', array(), $pos); return true; } - function apostrophe($match, $state, $pos) { - $this->_addCall('apostrophe',array(), $pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function apostrophe($match, $state, $pos) { + $this->addCall('apostrophe', array(), $pos); return true; } - function doublequoteopening($match, $state, $pos) { - $this->_addCall('doublequoteopening',array(), $pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function doublequoteopening($match, $state, $pos) { + $this->addCall('doublequoteopening', array(), $pos); $this->status['doublequote']++; return true; } - function doublequoteclosing($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function doublequoteclosing($match, $state, $pos) { if ($this->status['doublequote'] <= 0) { $this->doublequoteopening($match, $state, $pos); } else { - $this->_addCall('doublequoteclosing',array(), $pos); + $this->addCall('doublequoteclosing', array(), $pos); $this->status['doublequote'] = max(0, --$this->status['doublequote']); } return true; } - function camelcaselink($match, $state, $pos) { - $this->_addCall('camelcaselink',array($match), $pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function camelcaselink($match, $state, $pos) { + $this->addCall('camelcaselink', array($match), $pos); return true; } - /* - */ - function internallink($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function internallink($match, $state, $pos) { // Strip the opening and closing markup $link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match); @@ -543,42 +802,42 @@ class Doku_Handler { if ( link_isinterwiki($link[0]) ) { // Interwiki $interwiki = explode('>',$link[0],2); - $this->_addCall( + $this->addCall( 'interwikilink', array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]), $pos ); }elseif ( preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u',$link[0]) ) { // Windows Share - $this->_addCall( + $this->addCall( 'windowssharelink', array($link[0],$link[1]), $pos ); }elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ) { // external link (accepts all protocols) - $this->_addCall( + $this->addCall( 'externallink', array($link[0],$link[1]), $pos ); }elseif ( preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$link[0]) ) { // E-Mail (pattern above is defined in inc/mail.php) - $this->_addCall( + $this->addCall( 'emaillink', array($link[0],$link[1]), $pos ); }elseif ( preg_match('!^#.+!',$link[0]) ){ // local link - $this->_addCall( + $this->addCall( 'locallink', array(substr($link[0],1),$link[1]), $pos ); }else{ // internal link - $this->_addCall( + $this->addCall( 'internallink', array($link[0],$link[1]), $pos @@ -588,20 +847,38 @@ class Doku_Handler { return true; } - function filelink($match, $state, $pos) { - $this->_addCall('filelink',array($match, null), $pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function filelink($match, $state, $pos) { + $this->addCall('filelink', array($match, null), $pos); return true; } - function windowssharelink($match, $state, $pos) { - $this->_addCall('windowssharelink',array($match, null), $pos); + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function windowssharelink($match, $state, $pos) { + $this->addCall('windowssharelink', array($match, null), $pos); return true; } - function media($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function media($match, $state, $pos) { $p = Doku_Handler_Parse_Media($match); - $this->_addCall( + $this->addCall( $p['type'], array($p['src'], $p['title'], $p['align'], $p['width'], $p['height'], $p['cache'], $p['linking']), @@ -610,7 +887,13 @@ class Doku_Handler { return true; } - function rss($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function rss($match, $state, $pos) { $link = preg_replace(array('/^\{\{rss>/','/\}\}$/'),'',$match); // get params @@ -635,11 +918,17 @@ class Doku_Handler { $p['refresh'] = 14400; // default to 4 hours } - $this->_addCall('rss',array($link,$p),$pos); + $this->addCall('rss', array($link, $p), $pos); return true; } - function externallink($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function externallink($match, $state, $pos) { $url = $match; $title = null; @@ -653,69 +942,82 @@ class Doku_Handler { $url = 'http://'.$url; } - $this->_addCall('externallink',array($url, $title), $pos); + $this->addCall('externallink', array($url, $title), $pos); return true; } - function emaillink($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function emaillink($match, $state, $pos) { $email = preg_replace(array('/^</','/>$/'),'',$match); - $this->_addCall('emaillink',array($email, null), $pos); + $this->addCall('emaillink', array($email, null), $pos); return true; } - function table($match, $state, $pos) { + /** + * @param string $match matched syntax + * @param int $state a LEXER_STATE_* constant + * @param int $pos byte position in the original source file + * @return bool mode handled? + */ + public function table($match, $state, $pos) { switch ( $state ) { case DOKU_LEXER_ENTER: - $ReWriter = new Doku_Handler_Table($this->CallWriter); - $this->CallWriter = & $ReWriter; + $this->callWriter = new Table($this->callWriter); - $this->_addCall('table_start', array($pos + 1), $pos); + $this->addCall('table_start', array($pos + 1), $pos); if ( trim($match) == '^' ) { - $this->_addCall('tableheader', array(), $pos); + $this->addCall('tableheader', array(), $pos); } else { - $this->_addCall('tablecell', array(), $pos); + $this->addCall('tablecell', array(), $pos); } break; case DOKU_LEXER_EXIT: - $this->_addCall('table_end', array($pos), $pos); - $this->CallWriter->process(); - $ReWriter = & $this->CallWriter; - $this->CallWriter = & $ReWriter->CallWriter; + $this->addCall('table_end', array($pos), $pos); + /** @var Table $reWriter */ + $reWriter = $this->callWriter; + $this->callWriter = $reWriter->process(); break; case DOKU_LEXER_UNMATCHED: if ( trim($match) != '' ) { - $this->_addCall('cdata',array($match), $pos); + $this->addCall('cdata', array($match), $pos); } break; case DOKU_LEXER_MATCHED: if ( $match == ' ' ){ - $this->_addCall('cdata', array($match), $pos); + $this->addCall('cdata', array($match), $pos); } else if ( preg_match('/:::/',$match) ) { - $this->_addCall('rowspan', array($match), $pos); + $this->addCall('rowspan', array($match), $pos); } else if ( preg_match('/\t+/',$match) ) { - $this->_addCall('table_align', array($match), $pos); + $this->addCall('table_align', array($match), $pos); } else if ( preg_match('/ {2,}/',$match) ) { - $this->_addCall('table_align', array($match), $pos); + $this->addCall('table_align', array($match), $pos); } else if ( $match == "\n|" ) { - $this->_addCall('table_row', array(), $pos); - $this->_addCall('tablecell', array(), $pos); + $this->addCall('table_row', array(), $pos); + $this->addCall('tablecell', array(), $pos); } else if ( $match == "\n^" ) { - $this->_addCall('table_row', array(), $pos); - $this->_addCall('tableheader', array(), $pos); + $this->addCall('table_row', array(), $pos); + $this->addCall('tableheader', array(), $pos); } else if ( $match == '|' ) { - $this->_addCall('tablecell', array(), $pos); + $this->addCall('tablecell', array(), $pos); } else if ( $match == '^' ) { - $this->_addCall('tableheader', array(), $pos); + $this->addCall('tableheader', array(), $pos); } break; } return true; } + + // endregion modes } //------------------------------------------------------------------------ @@ -808,1004 +1110,3 @@ function Doku_Handler_Parse_Media($match) { return $params; } -//------------------------------------------------------------------------ -interface Doku_Handler_CallWriter_Interface { - public function writeCall($call); - public function writeCalls($calls); - public function finalise(); -} - -class Doku_Handler_CallWriter implements Doku_Handler_CallWriter_Interface { - - var $Handler; - - /** - * @param Doku_Handler $Handler - */ - function __construct(Doku_Handler $Handler) { - $this->Handler = $Handler; - } - - function writeCall($call) { - $this->Handler->calls[] = $call; - } - - function writeCalls($calls) { - $this->Handler->calls = array_merge($this->Handler->calls, $calls); - } - - // function is required, but since this call writer is first/highest in - // the chain it is not required to do anything - function finalise() { - unset($this->Handler); - } -} - -//------------------------------------------------------------------------ -/** - * Generic call writer class to handle nesting of rendering instructions - * within a render instruction. Also see nest() method of renderer base class - * - * @author Chris Smith <chris@jalakai.co.uk> - */ -class Doku_Handler_Nest implements Doku_Handler_CallWriter_Interface { - - var $CallWriter; - var $calls = array(); - - var $closingInstruction; - - /** - * constructor - * - * @param Doku_Handler_CallWriter $CallWriter the renderers current call writer - * @param string $close closing instruction name, this is required to properly terminate the - * syntax mode if the document ends without a closing pattern - */ - function __construct(Doku_Handler_CallWriter_Interface $CallWriter, $close="nest_close") { - $this->CallWriter = $CallWriter; - - $this->closingInstruction = $close; - } - - function writeCall($call) { - $this->calls[] = $call; - } - - function writeCalls($calls) { - $this->calls = array_merge($this->calls, $calls); - } - - function finalise() { - $last_call = end($this->calls); - $this->writeCall(array($this->closingInstruction,array(), $last_call[2])); - - $this->process(); - $this->CallWriter->finalise(); - unset($this->CallWriter); - } - - function process() { - // merge consecutive cdata - $unmerged_calls = $this->calls; - $this->calls = array(); - - foreach ($unmerged_calls as $call) $this->addCall($call); - - $first_call = reset($this->calls); - $this->CallWriter->writeCall(array("nest", array($this->calls), $first_call[2])); - } - - function addCall($call) { - $key = count($this->calls); - if ($key and ($call[0] == 'cdata') and ($this->calls[$key-1][0] == 'cdata')) { - $this->calls[$key-1][1][0] .= $call[1][0]; - } else if ($call[0] == 'eol') { - // do nothing (eol shouldn't be allowed, to counter preformatted fix in #1652 & #1699) - } else { - $this->calls[] = $call; - } - } -} - -class Doku_Handler_List implements Doku_Handler_CallWriter_Interface { - - var $CallWriter; - - var $calls = array(); - var $listCalls = array(); - var $listStack = array(); - - const NODE = 1; - - function __construct(Doku_Handler_CallWriter_Interface $CallWriter) { - $this->CallWriter = $CallWriter; - } - - function writeCall($call) { - $this->calls[] = $call; - } - - // Probably not needed but just in case... - function writeCalls($calls) { - $this->calls = array_merge($this->calls, $calls); -# $this->CallWriter->writeCalls($this->calls); - } - - function finalise() { - $last_call = end($this->calls); - $this->writeCall(array('list_close',array(), $last_call[2])); - - $this->process(); - $this->CallWriter->finalise(); - unset($this->CallWriter); - } - - //------------------------------------------------------------------------ - function process() { - - foreach ( $this->calls as $call ) { - switch ($call[0]) { - case 'list_item': - $this->listOpen($call); - break; - case 'list_open': - $this->listStart($call); - break; - case 'list_close': - $this->listEnd($call); - break; - default: - $this->listContent($call); - break; - } - } - - $this->CallWriter->writeCalls($this->listCalls); - } - - //------------------------------------------------------------------------ - function listStart($call) { - $depth = $this->interpretSyntax($call[1][0], $listType); - - $this->initialDepth = $depth; - // array(list type, current depth, index of current listitem_open) - $this->listStack[] = array($listType, $depth, 1); - - $this->listCalls[] = array('list'.$listType.'_open',array(),$call[2]); - $this->listCalls[] = array('listitem_open',array(1),$call[2]); - $this->listCalls[] = array('listcontent_open',array(),$call[2]); - } - - //------------------------------------------------------------------------ - function listEnd($call) { - $closeContent = true; - - while ( $list = array_pop($this->listStack) ) { - if ( $closeContent ) { - $this->listCalls[] = array('listcontent_close',array(),$call[2]); - $closeContent = false; - } - $this->listCalls[] = array('listitem_close',array(),$call[2]); - $this->listCalls[] = array('list'.$list[0].'_close', array(), $call[2]); - } - } - - //------------------------------------------------------------------------ - function listOpen($call) { - $depth = $this->interpretSyntax($call[1][0], $listType); - $end = end($this->listStack); - $key = key($this->listStack); - - // Not allowed to be shallower than initialDepth - if ( $depth < $this->initialDepth ) { - $depth = $this->initialDepth; - } - - //------------------------------------------------------------------------ - if ( $depth == $end[1] ) { - - // Just another item in the list... - if ( $listType == $end[0] ) { - $this->listCalls[] = array('listcontent_close',array(),$call[2]); - $this->listCalls[] = array('listitem_close',array(),$call[2]); - $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]); - $this->listCalls[] = array('listcontent_open',array(),$call[2]); - - // new list item, update list stack's index into current listitem_open - $this->listStack[$key][2] = count($this->listCalls) - 2; - - // Switched list type... - } else { - - $this->listCalls[] = array('listcontent_close',array(),$call[2]); - $this->listCalls[] = array('listitem_close',array(),$call[2]); - $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]); - $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); - $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); - $this->listCalls[] = array('listcontent_open',array(),$call[2]); - - array_pop($this->listStack); - $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2); - } - - //------------------------------------------------------------------------ - // Getting deeper... - } else if ( $depth > $end[1] ) { - - $this->listCalls[] = array('listcontent_close',array(),$call[2]); - $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); - $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); - $this->listCalls[] = array('listcontent_open',array(),$call[2]); - - // set the node/leaf state of this item's parent listitem_open to NODE - $this->listCalls[$this->listStack[$key][2]][1][1] = self::NODE; - - $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2); - - //------------------------------------------------------------------------ - // Getting shallower ( $depth < $end[1] ) - } else { - $this->listCalls[] = array('listcontent_close',array(),$call[2]); - $this->listCalls[] = array('listitem_close',array(),$call[2]); - $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]); - - // Throw away the end - done - array_pop($this->listStack); - - while (1) { - $end = end($this->listStack); - $key = key($this->listStack); - - if ( $end[1] <= $depth ) { - - // Normalize depths - $depth = $end[1]; - - $this->listCalls[] = array('listitem_close',array(),$call[2]); - - if ( $end[0] == $listType ) { - $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]); - $this->listCalls[] = array('listcontent_open',array(),$call[2]); - - // new list item, update list stack's index into current listitem_open - $this->listStack[$key][2] = count($this->listCalls) - 2; - - } else { - // Switching list type... - $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]); - $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); - $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); - $this->listCalls[] = array('listcontent_open',array(),$call[2]); - - array_pop($this->listStack); - $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2); - } - - break; - - // Haven't dropped down far enough yet.... ( $end[1] > $depth ) - } else { - - $this->listCalls[] = array('listitem_close',array(),$call[2]); - $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]); - - array_pop($this->listStack); - - } - - } - - } - } - - //------------------------------------------------------------------------ - function listContent($call) { - $this->listCalls[] = $call; - } - - //------------------------------------------------------------------------ - function interpretSyntax($match, & $type) { - if ( substr($match,-1) == '*' ) { - $type = 'u'; - } else { - $type = 'o'; - } - // Is the +1 needed? It used to be count(explode(...)) - // but I don't think the number is seen outside this handler - return substr_count(str_replace("\t",' ',$match), ' ') + 1; - } -} - -//------------------------------------------------------------------------ -class Doku_Handler_Preformatted implements Doku_Handler_CallWriter_Interface { - - var $CallWriter; - - var $calls = array(); - var $pos; - var $text =''; - - - - function __construct(Doku_Handler_CallWriter_Interface $CallWriter) { - $this->CallWriter = $CallWriter; - } - - function writeCall($call) { - $this->calls[] = $call; - } - - // Probably not needed but just in case... - function writeCalls($calls) { - $this->calls = array_merge($this->calls, $calls); -# $this->CallWriter->writeCalls($this->calls); - } - - function finalise() { - $last_call = end($this->calls); - $this->writeCall(array('preformatted_end',array(), $last_call[2])); - - $this->process(); - $this->CallWriter->finalise(); - unset($this->CallWriter); - } - - function process() { - foreach ( $this->calls as $call ) { - switch ($call[0]) { - case 'preformatted_start': - $this->pos = $call[2]; - break; - case 'preformatted_newline': - $this->text .= "\n"; - break; - case 'preformatted_content': - $this->text .= $call[1][0]; - break; - case 'preformatted_end': - if (trim($this->text)) { - $this->CallWriter->writeCall(array('preformatted',array($this->text),$this->pos)); - } - // see FS#1699 & FS#1652, add 'eol' instructions to ensure proper triggering of following p_open - $this->CallWriter->writeCall(array('eol',array(),$this->pos)); - $this->CallWriter->writeCall(array('eol',array(),$this->pos)); - break; - } - } - } - -} - -//------------------------------------------------------------------------ -class Doku_Handler_Quote implements Doku_Handler_CallWriter_Interface { - - var $CallWriter; - - var $calls = array(); - - var $quoteCalls = array(); - - function __construct(Doku_Handler_CallWriter_Interface $CallWriter) { - $this->CallWriter = $CallWriter; - } - - function writeCall($call) { - $this->calls[] = $call; - } - - // Probably not needed but just in case... - function writeCalls($calls) { - $this->calls = array_merge($this->calls, $calls); - } - - function finalise() { - $last_call = end($this->calls); - $this->writeCall(array('quote_end',array(), $last_call[2])); - - $this->process(); - $this->CallWriter->finalise(); - unset($this->CallWriter); - } - - function process() { - - $quoteDepth = 1; - - foreach ( $this->calls as $call ) { - switch ($call[0]) { - - case 'quote_start': - - $this->quoteCalls[] = array('quote_open',array(),$call[2]); - - case 'quote_newline': - - $quoteLength = $this->getDepth($call[1][0]); - - if ( $quoteLength > $quoteDepth ) { - $quoteDiff = $quoteLength - $quoteDepth; - for ( $i = 1; $i <= $quoteDiff; $i++ ) { - $this->quoteCalls[] = array('quote_open',array(),$call[2]); - } - } else if ( $quoteLength < $quoteDepth ) { - $quoteDiff = $quoteDepth - $quoteLength; - for ( $i = 1; $i <= $quoteDiff; $i++ ) { - $this->quoteCalls[] = array('quote_close',array(),$call[2]); - } - } else { - if ($call[0] != 'quote_start') $this->quoteCalls[] = array('linebreak',array(),$call[2]); - } - - $quoteDepth = $quoteLength; - - break; - - case 'quote_end': - - if ( $quoteDepth > 1 ) { - $quoteDiff = $quoteDepth - 1; - for ( $i = 1; $i <= $quoteDiff; $i++ ) { - $this->quoteCalls[] = array('quote_close',array(),$call[2]); - } - } - - $this->quoteCalls[] = array('quote_close',array(),$call[2]); - - $this->CallWriter->writeCalls($this->quoteCalls); - break; - - default: - $this->quoteCalls[] = $call; - break; - } - } - } - - function getDepth($marker) { - preg_match('/>{1,}/', $marker, $matches); - $quoteLength = strlen($matches[0]); - return $quoteLength; - } -} - -//------------------------------------------------------------------------ -class Doku_Handler_Table implements Doku_Handler_CallWriter_Interface { - - var $CallWriter; - - var $calls = array(); - var $tableCalls = array(); - var $maxCols = 0; - var $maxRows = 1; - var $currentCols = 0; - var $firstCell = false; - var $lastCellType = 'tablecell'; - var $inTableHead = true; - var $currentRow = array('tableheader' => 0, 'tablecell' => 0); - var $countTableHeadRows = 0; - - function __construct(Doku_Handler_CallWriter_Interface $CallWriter) { - $this->CallWriter = $CallWriter; - } - - function writeCall($call) { - $this->calls[] = $call; - } - - // Probably not needed but just in case... - function writeCalls($calls) { - $this->calls = array_merge($this->calls, $calls); - } - - function finalise() { - $last_call = end($this->calls); - $this->writeCall(array('table_end',array(), $last_call[2])); - - $this->process(); - $this->CallWriter->finalise(); - unset($this->CallWriter); - } - - //------------------------------------------------------------------------ - function process() { - foreach ( $this->calls as $call ) { - switch ( $call[0] ) { - case 'table_start': - $this->tableStart($call); - break; - case 'table_row': - $this->tableRowClose($call); - $this->tableRowOpen(array('tablerow_open',$call[1],$call[2])); - break; - case 'tableheader': - case 'tablecell': - $this->tableCell($call); - break; - case 'table_end': - $this->tableRowClose($call); - $this->tableEnd($call); - break; - default: - $this->tableDefault($call); - break; - } - } - $this->CallWriter->writeCalls($this->tableCalls); - } - - function tableStart($call) { - $this->tableCalls[] = array('table_open',$call[1],$call[2]); - $this->tableCalls[] = array('tablerow_open',array(),$call[2]); - $this->firstCell = true; - } - - function tableEnd($call) { - $this->tableCalls[] = array('table_close',$call[1],$call[2]); - $this->finalizeTable(); - } - - function tableRowOpen($call) { - $this->tableCalls[] = $call; - $this->currentCols = 0; - $this->firstCell = true; - $this->lastCellType = 'tablecell'; - $this->maxRows++; - if ($this->inTableHead) { - $this->currentRow = array('tablecell' => 0, 'tableheader' => 0); - } - } - - function tableRowClose($call) { - if ($this->inTableHead && ($this->inTableHead = $this->isTableHeadRow())) { - $this->countTableHeadRows++; - } - // Strip off final cell opening and anything after it - while ( $discard = array_pop($this->tableCalls ) ) { - - if ( $discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') { - break; - } - if (!empty($this->currentRow[$discard[0]])) { - $this->currentRow[$discard[0]]--; - } - } - $this->tableCalls[] = array('tablerow_close', array(), $call[2]); - - if ( $this->currentCols > $this->maxCols ) { - $this->maxCols = $this->currentCols; - } - } - - function isTableHeadRow() { - $td = $this->currentRow['tablecell']; - $th = $this->currentRow['tableheader']; - - if (!$th || $td > 2) return false; - if (2*$td > $th) return false; - - return true; - } - - function tableCell($call) { - if ($this->inTableHead) { - $this->currentRow[$call[0]]++; - } - if ( !$this->firstCell ) { - - // Increase the span - $lastCall = end($this->tableCalls); - - // A cell call which follows an open cell means an empty cell so span - if ( $lastCall[0] == 'tablecell_open' || $lastCall[0] == 'tableheader_open' ) { - $this->tableCalls[] = array('colspan',array(),$call[2]); - - } - - $this->tableCalls[] = array($this->lastCellType.'_close',array(),$call[2]); - $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]); - $this->lastCellType = $call[0]; - - } else { - - $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]); - $this->lastCellType = $call[0]; - $this->firstCell = false; - - } - - $this->currentCols++; - } - - function tableDefault($call) { - $this->tableCalls[] = $call; - } - - function finalizeTable() { - - // Add the max cols and rows to the table opening - if ( $this->tableCalls[0][0] == 'table_open' ) { - // Adjust to num cols not num col delimeters - $this->tableCalls[0][1][] = $this->maxCols - 1; - $this->tableCalls[0][1][] = $this->maxRows; - $this->tableCalls[0][1][] = array_shift($this->tableCalls[0][1]); - } else { - trigger_error('First element in table call list is not table_open'); - } - - $lastRow = 0; - $lastCell = 0; - $cellKey = array(); - $toDelete = array(); - - // if still in tableheader, then there can be no table header - // as all rows can't be within <THEAD> - if ($this->inTableHead) { - $this->inTableHead = false; - $this->countTableHeadRows = 0; - } - - // Look for the colspan elements and increment the colspan on the - // previous non-empty opening cell. Once done, delete all the cells - // that contain colspans - for ($key = 0 ; $key < count($this->tableCalls) ; ++$key) { - $call = $this->tableCalls[$key]; - - switch ($call[0]) { - case 'table_open' : - if($this->countTableHeadRows) { - array_splice($this->tableCalls, $key+1, 0, array( - array('tablethead_open', array(), $call[2])) - ); - } - break; - - case 'tablerow_open': - - $lastRow++; - $lastCell = 0; - break; - - case 'tablecell_open': - case 'tableheader_open': - - $lastCell++; - $cellKey[$lastRow][$lastCell] = $key; - break; - - case 'table_align': - - $prev = in_array($this->tableCalls[$key-1][0], array('tablecell_open', 'tableheader_open')); - $next = in_array($this->tableCalls[$key+1][0], array('tablecell_close', 'tableheader_close')); - // If the cell is empty, align left - if ($prev && $next) { - $this->tableCalls[$key-1][1][1] = 'left'; - - // If the previous element was a cell open, align right - } elseif ($prev) { - $this->tableCalls[$key-1][1][1] = 'right'; - - // If the next element is the close of an element, align either center or left - } elseif ( $next) { - if ( $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] == 'right' ) { - $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'center'; - } else { - $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'left'; - } - - } - - // Now convert the whitespace back to cdata - $this->tableCalls[$key][0] = 'cdata'; - break; - - case 'colspan': - - $this->tableCalls[$key-1][1][0] = false; - - for($i = $key-2; $i >= $cellKey[$lastRow][1]; $i--) { - - if ( $this->tableCalls[$i][0] == 'tablecell_open' || $this->tableCalls[$i][0] == 'tableheader_open' ) { - - if ( false !== $this->tableCalls[$i][1][0] ) { - $this->tableCalls[$i][1][0]++; - break; - } - - } - } - - $toDelete[] = $key-1; - $toDelete[] = $key; - $toDelete[] = $key+1; - break; - - case 'rowspan': - - if ( $this->tableCalls[$key-1][0] == 'cdata' ) { - // ignore rowspan if previous call was cdata (text mixed with :::) we don't have to check next call as that wont match regex - $this->tableCalls[$key][0] = 'cdata'; - - } else { - - $spanning_cell = null; - - // can't cross thead/tbody boundary - if (!$this->countTableHeadRows || ($lastRow-1 != $this->countTableHeadRows)) { - for($i = $lastRow-1; $i > 0; $i--) { - - if ( $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tablecell_open' || $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tableheader_open' ) { - - if ($this->tableCalls[$cellKey[$i][$lastCell]][1][2] >= $lastRow - $i) { - $spanning_cell = $i; - break; - } - - } - } - } - if (is_null($spanning_cell)) { - // No spanning cell found, so convert this cell to - // an empty one to avoid broken tables - $this->tableCalls[$key][0] = 'cdata'; - $this->tableCalls[$key][1][0] = ''; - break; - } - $this->tableCalls[$cellKey[$spanning_cell][$lastCell]][1][2]++; - - $this->tableCalls[$key-1][1][2] = false; - - $toDelete[] = $key-1; - $toDelete[] = $key; - $toDelete[] = $key+1; - } - break; - - case 'tablerow_close': - - // Fix broken tables by adding missing cells - $moreCalls = array(); - while (++$lastCell < $this->maxCols) { - $moreCalls[] = array('tablecell_open', array(1, null, 1), $call[2]); - $moreCalls[] = array('cdata', array(''), $call[2]); - $moreCalls[] = array('tablecell_close', array(), $call[2]); - } - $moreCallsLength = count($moreCalls); - if($moreCallsLength) { - array_splice($this->tableCalls, $key, 0, $moreCalls); - $key += $moreCallsLength; - } - - if($this->countTableHeadRows == $lastRow) { - array_splice($this->tableCalls, $key+1, 0, array( - array('tablethead_close', array(), $call[2]))); - } - break; - - } - } - - // condense cdata - $cnt = count($this->tableCalls); - for( $key = 0; $key < $cnt; $key++){ - if($this->tableCalls[$key][0] == 'cdata'){ - $ckey = $key; - $key++; - while($this->tableCalls[$key][0] == 'cdata'){ - $this->tableCalls[$ckey][1][0] .= $this->tableCalls[$key][1][0]; - $toDelete[] = $key; - $key++; - } - continue; - } - } - - foreach ( $toDelete as $delete ) { - unset($this->tableCalls[$delete]); - } - $this->tableCalls = array_values($this->tableCalls); - } -} - - -/** - * Handler for paragraphs - * - * @author Harry Fuecks <hfuecks@gmail.com> - */ -class Doku_Handler_Block { - var $calls = array(); - var $skipEol = false; - var $inParagraph = false; - - // Blocks these should not be inside paragraphs - var $blockOpen = array( - 'header', - 'listu_open','listo_open','listitem_open','listcontent_open', - 'table_open','tablerow_open','tablecell_open','tableheader_open','tablethead_open', - 'quote_open', - 'code','file','hr','preformatted','rss', - 'htmlblock','phpblock', - 'footnote_open', - ); - - var $blockClose = array( - 'header', - 'listu_close','listo_close','listitem_close','listcontent_close', - 'table_close','tablerow_close','tablecell_close','tableheader_close','tablethead_close', - 'quote_close', - 'code','file','hr','preformatted','rss', - 'htmlblock','phpblock', - 'footnote_close', - ); - - // Stacks can contain paragraphs - var $stackOpen = array( - 'section_open', - ); - - var $stackClose = array( - 'section_close', - ); - - - /** - * Constructor. Adds loaded syntax plugins to the block and stack - * arrays - * - * @author Andreas Gohr <andi@splitbrain.org> - */ - function __construct(){ - global $DOKU_PLUGINS; - //check if syntax plugins were loaded - if(empty($DOKU_PLUGINS['syntax'])) return; - foreach($DOKU_PLUGINS['syntax'] as $n => $p){ - $ptype = $p->getPType(); - if($ptype == 'block'){ - $this->blockOpen[] = 'plugin_'.$n; - $this->blockClose[] = 'plugin_'.$n; - }elseif($ptype == 'stack'){ - $this->stackOpen[] = 'plugin_'.$n; - $this->stackClose[] = 'plugin_'.$n; - } - } - } - - function openParagraph($pos){ - if ($this->inParagraph) return; - $this->calls[] = array('p_open',array(), $pos); - $this->inParagraph = true; - $this->skipEol = true; - } - - /** - * Close a paragraph if needed - * - * This function makes sure there are no empty paragraphs on the stack - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string|integer $pos - */ - function closeParagraph($pos){ - if (!$this->inParagraph) return; - // look back if there was any content - we don't want empty paragraphs - $content = ''; - $ccount = count($this->calls); - for($i=$ccount-1; $i>=0; $i--){ - if($this->calls[$i][0] == 'p_open'){ - break; - }elseif($this->calls[$i][0] == 'cdata'){ - $content .= $this->calls[$i][1][0]; - }else{ - $content = 'found markup'; - break; - } - } - - if(trim($content)==''){ - //remove the whole paragraph - //array_splice($this->calls,$i); // <- this is much slower than the loop below - for($x=$ccount; $x>$i; $x--) array_pop($this->calls); - }else{ - // remove ending linebreaks in the paragraph - $i=count($this->calls)-1; - if ($this->calls[$i][0] == 'cdata') $this->calls[$i][1][0] = rtrim($this->calls[$i][1][0],DOKU_PARSER_EOL); - $this->calls[] = array('p_close',array(), $pos); - } - - $this->inParagraph = false; - $this->skipEol = true; - } - - function addCall($call) { - $key = count($this->calls); - if ($key and ($call[0] == 'cdata') and ($this->calls[$key-1][0] == 'cdata')) { - $this->calls[$key-1][1][0] .= $call[1][0]; - } else { - $this->calls[] = $call; - } - } - - // simple version of addCall, without checking cdata - function storeCall($call) { - $this->calls[] = $call; - } - - /** - * Processes the whole instruction stack to open and close paragraphs - * - * @author Harry Fuecks <hfuecks@gmail.com> - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param array $calls - * - * @return array - */ - function process($calls) { - // open first paragraph - $this->openParagraph(0); - foreach ( $calls as $key => $call ) { - $cname = $call[0]; - if ($cname == 'plugin') { - $cname='plugin_'.$call[1][0]; - $plugin = true; - $plugin_open = (($call[1][2] == DOKU_LEXER_ENTER) || ($call[1][2] == DOKU_LEXER_SPECIAL)); - $plugin_close = (($call[1][2] == DOKU_LEXER_EXIT) || ($call[1][2] == DOKU_LEXER_SPECIAL)); - } else { - $plugin = false; - } - /* stack */ - if ( in_array($cname,$this->stackClose ) && (!$plugin || $plugin_close)) { - $this->closeParagraph($call[2]); - $this->storeCall($call); - $this->openParagraph($call[2]); - continue; - } - if ( in_array($cname,$this->stackOpen ) && (!$plugin || $plugin_open) ) { - $this->closeParagraph($call[2]); - $this->storeCall($call); - $this->openParagraph($call[2]); - continue; - } - /* block */ - // If it's a substition it opens and closes at the same call. - // To make sure next paragraph is correctly started, let close go first. - if ( in_array($cname, $this->blockClose) && (!$plugin || $plugin_close)) { - $this->closeParagraph($call[2]); - $this->storeCall($call); - $this->openParagraph($call[2]); - continue; - } - if ( in_array($cname, $this->blockOpen) && (!$plugin || $plugin_open)) { - $this->closeParagraph($call[2]); - $this->storeCall($call); - continue; - } - /* eol */ - if ( $cname == 'eol' ) { - // Check this isn't an eol instruction to skip... - if ( !$this->skipEol ) { - // Next is EOL => double eol => mark as paragraph - if ( isset($calls[$key+1]) && $calls[$key+1][0] == 'eol' ) { - $this->closeParagraph($call[2]); - $this->openParagraph($call[2]); - } else { - //if this is just a single eol make a space from it - $this->addCall(array('cdata',array(DOKU_PARSER_EOL), $call[2])); - } - } - continue; - } - /* normal */ - $this->addCall($call); - $this->skipEol = false; - } - // close last paragraph - $call = end($this->calls); - $this->closeParagraph($call[2]); - return $this->calls; - } -} - -//Setup VIM: ex: et ts=4 : diff --git a/inc/parser/lexer.php b/inc/parser/lexer.php deleted file mode 100644 index ba6a65397..000000000 --- a/inc/parser/lexer.php +++ /dev/null @@ -1,614 +0,0 @@ -<?php -/** - * Author Markus Baker: http://www.lastcraft.com - * Version adapted from Simple Test: http://sourceforge.net/projects/simpletest/ - * For an intro to the Lexer see: - * https://web.archive.org/web/20120125041816/http://www.phppatterns.com/docs/develop/simple_test_lexer_notes - * @author Marcus Baker - * @package Doku - * @subpackage Lexer - * @version $Id: lexer.php,v 1.1 2005/03/23 23:14:09 harryf Exp $ - */ - -/** - * Init path constant - */ -if(!defined('DOKU_INC')) die('meh.'); - -/**#@+ - * lexer mode constant - */ -define("DOKU_LEXER_ENTER", 1); -define("DOKU_LEXER_MATCHED", 2); -define("DOKU_LEXER_UNMATCHED", 3); -define("DOKU_LEXER_EXIT", 4); -define("DOKU_LEXER_SPECIAL", 5); -/**#@-*/ - -/** - * Compounded regular expression. Any of - * the contained patterns could match and - * when one does it's label is returned. - * - * @package Doku - * @subpackage Lexer - */ -class Doku_LexerParallelRegex { - var $_patterns; - var $_labels; - var $_regex; - var $_case; - - /** - * Constructor. Starts with no patterns. - * - * @param boolean $case True for case sensitive, false - * for insensitive. - * @access public - */ - function __construct($case) { - $this->_case = $case; - $this->_patterns = array(); - $this->_labels = array(); - $this->_regex = null; - } - - /** - * Adds a pattern with an optional label. - * - * @param mixed $pattern Perl style regex. Must be UTF-8 - * encoded. If its a string, the (, ) - * lose their meaning unless they - * form part of a lookahead or - * lookbehind assertation. - * @param bool|string $label Label of regex to be returned - * on a match. Label must be ASCII - * @access public - */ - function addPattern($pattern, $label = true) { - $count = count($this->_patterns); - $this->_patterns[$count] = $pattern; - $this->_labels[$count] = $label; - $this->_regex = null; - } - - /** - * Attempts to match all patterns at once against a string. - * - * @param string $subject String to match against. - * @param string $match First matched portion of - * subject. - * @return boolean True on success. - * @access public - */ - function match($subject, &$match) { - if (count($this->_patterns) == 0) { - return false; - } - if (! preg_match($this->_getCompoundedRegex(), $subject, $matches)) { - $match = ""; - return false; - } - - $match = $matches[0]; - $size = count($matches); - for ($i = 1; $i < $size; $i++) { - if ($matches[$i] && isset($this->_labels[$i - 1])) { - return $this->_labels[$i - 1]; - } - } - return true; - } - - /** - * Attempts to split the string against all patterns at once - * - * @param string $subject String to match against. - * @param array $split The split result: array containing, pre-match, match & post-match strings - * @return boolean True on success. - * @access public - * - * @author Christopher Smith <chris@jalakai.co.uk> - */ - function split($subject, &$split) { - if (count($this->_patterns) == 0) { - return false; - } - - if (! preg_match($this->_getCompoundedRegex(), $subject, $matches)) { - if(function_exists('preg_last_error')){ - $err = preg_last_error(); - switch($err){ - case PREG_BACKTRACK_LIMIT_ERROR: - msg('A PCRE backtrack error occured. Try to increase the pcre.backtrack_limit in php.ini',-1); - break; - case PREG_RECURSION_LIMIT_ERROR: - msg('A PCRE recursion error occured. Try to increase the pcre.recursion_limit in php.ini',-1); - break; - case PREG_BAD_UTF8_ERROR: - msg('A PCRE UTF-8 error occured. This might be caused by a faulty plugin',-1); - break; - case PREG_INTERNAL_ERROR: - msg('A PCRE internal error occured. This might be caused by a faulty plugin',-1); - break; - } - } - - $split = array($subject, "", ""); - return false; - } - - $idx = count($matches)-2; - list($pre, $post) = preg_split($this->_patterns[$idx].$this->_getPerlMatchingFlags(), $subject, 2); - $split = array($pre, $matches[0], $post); - - return isset($this->_labels[$idx]) ? $this->_labels[$idx] : true; - } - - /** - * Compounds the patterns into a single - * regular expression separated with the - * "or" operator. Caches the regex. - * Will automatically escape (, ) and / tokens. - * - * @internal array $_patterns List of patterns in order. - * @return null|string - * @access private - */ - function _getCompoundedRegex() { - if ($this->_regex == null) { - $cnt = count($this->_patterns); - for ($i = 0; $i < $cnt; $i++) { - - /* - * decompose the input pattern into "(", "(?", ")", - * "[...]", "[]..]", "[^]..]", "[...[:...:]..]", "\x"... - * elements. - */ - preg_match_all('/\\\\.|' . - '\(\?|' . - '[()]|' . - '\[\^?\]?(?:\\\\.|\[:[^]]*:\]|[^]\\\\])*\]|' . - '[^[()\\\\]+/', $this->_patterns[$i], $elts); - - $pattern = ""; - $level = 0; - - foreach ($elts[0] as $elt) { - /* - * for "(", ")" remember the nesting level, add "\" - * only to the non-"(?" ones. - */ - - switch($elt) { - case '(': - $pattern .= '\('; - break; - case ')': - if ($level > 0) - $level--; /* closing (? */ - else - $pattern .= '\\'; - $pattern .= ')'; - break; - case '(?': - $level++; - $pattern .= '(?'; - break; - default: - if (substr($elt, 0, 1) == '\\') - $pattern .= $elt; - else - $pattern .= str_replace('/', '\/', $elt); - } - } - $this->_patterns[$i] = "($pattern)"; - } - $this->_regex = "/" . implode("|", $this->_patterns) . "/" . $this->_getPerlMatchingFlags(); - } - return $this->_regex; - } - - /** - * Accessor for perl regex mode flags to use. - * @return string Perl regex flags. - * @access private - */ - function _getPerlMatchingFlags() { - return ($this->_case ? "msS" : "msSi"); - } -} - -/** - * States for a stack machine. - * @package Lexer - * @subpackage Lexer - */ -class Doku_LexerStateStack { - var $_stack; - - /** - * Constructor. Starts in named state. - * @param string $start Starting state name. - * @access public - */ - function __construct($start) { - $this->_stack = array($start); - } - - /** - * Accessor for current state. - * @return string State. - * @access public - */ - function getCurrent() { - return $this->_stack[count($this->_stack) - 1]; - } - - /** - * Adds a state to the stack and sets it - * to be the current state. - * @param string $state New state. - * @access public - */ - function enter($state) { - array_push($this->_stack, $state); - } - - /** - * Leaves the current state and reverts - * to the previous one. - * @return boolean False if we drop off - * the bottom of the list. - * @access public - */ - function leave() { - if (count($this->_stack) == 1) { - return false; - } - array_pop($this->_stack); - return true; - } -} - -/** - * Accepts text and breaks it into tokens. - * Some optimisation to make the sure the - * content is only scanned by the PHP regex - * parser once. Lexer modes must not start - * with leading underscores. - * @package Doku - * @subpackage Lexer - */ -class Doku_Lexer { - var $_regexes; - var $_parser; - var $_mode; - var $_mode_handlers; - var $_case; - - /** - * Sets up the lexer in case insensitive matching - * by default. - * @param Doku_Parser $parser Handling strategy by - * reference. - * @param string $start Starting handler. - * @param boolean $case True for case sensitive. - * @access public - */ - function __construct($parser, $start = "accept", $case = false) { - $this->_case = $case; - /** @var Doku_LexerParallelRegex[] _regexes */ - $this->_regexes = array(); - $this->_parser = $parser; - $this->_mode = new Doku_LexerStateStack($start); - $this->_mode_handlers = array(); - } - - /** - * Adds a token search pattern for a particular - * parsing mode. The pattern does not change the - * current mode. - * @param string $pattern Perl style regex, but ( and ) - * lose the usual meaning. - * @param string $mode Should only apply this - * pattern when dealing with - * this type of input. - * @access public - */ - function addPattern($pattern, $mode = "accept") { - if (! isset($this->_regexes[$mode])) { - $this->_regexes[$mode] = new Doku_LexerParallelRegex($this->_case); - } - $this->_regexes[$mode]->addPattern($pattern); - } - - /** - * Adds a pattern that will enter a new parsing - * mode. Useful for entering parenthesis, strings, - * tags, etc. - * @param string $pattern Perl style regex, but ( and ) - * lose the usual meaning. - * @param string $mode Should only apply this - * pattern when dealing with - * this type of input. - * @param string $new_mode Change parsing to this new - * nested mode. - * @access public - */ - function addEntryPattern($pattern, $mode, $new_mode) { - if (! isset($this->_regexes[$mode])) { - $this->_regexes[$mode] = new Doku_LexerParallelRegex($this->_case); - } - $this->_regexes[$mode]->addPattern($pattern, $new_mode); - } - - /** - * Adds a pattern that will exit the current mode - * and re-enter the previous one. - * @param string $pattern Perl style regex, but ( and ) - * lose the usual meaning. - * @param string $mode Mode to leave. - * @access public - */ - function addExitPattern($pattern, $mode) { - if (! isset($this->_regexes[$mode])) { - $this->_regexes[$mode] = new Doku_LexerParallelRegex($this->_case); - } - $this->_regexes[$mode]->addPattern($pattern, "__exit"); - } - - /** - * Adds a pattern that has a special mode. Acts as an entry - * and exit pattern in one go, effectively calling a special - * parser handler for this token only. - * @param string $pattern Perl style regex, but ( and ) - * lose the usual meaning. - * @param string $mode Should only apply this - * pattern when dealing with - * this type of input. - * @param string $special Use this mode for this one token. - * @access public - */ - function addSpecialPattern($pattern, $mode, $special) { - if (! isset($this->_regexes[$mode])) { - $this->_regexes[$mode] = new Doku_LexerParallelRegex($this->_case); - } - $this->_regexes[$mode]->addPattern($pattern, "_$special"); - } - - /** - * Adds a mapping from a mode to another handler. - * @param string $mode Mode to be remapped. - * @param string $handler New target handler. - * @access public - */ - function mapHandler($mode, $handler) { - $this->_mode_handlers[$mode] = $handler; - } - - /** - * Splits the page text into tokens. Will fail - * if the handlers report an error or if no - * content is consumed. If successful then each - * unparsed and parsed token invokes a call to the - * held listener. - * @param string $raw Raw HTML text. - * @return boolean True on success, else false. - * @access public - */ - function parse($raw) { - if (! isset($this->_parser)) { - return false; - } - $initialLength = strlen($raw); - $length = $initialLength; - $pos = 0; - while (is_array($parsed = $this->_reduce($raw))) { - list($unmatched, $matched, $mode) = $parsed; - $currentLength = strlen($raw); - $matchPos = $initialLength - $currentLength - strlen($matched); - if (! $this->_dispatchTokens($unmatched, $matched, $mode, $pos, $matchPos)) { - return false; - } - if ($currentLength == $length) { - return false; - } - $length = $currentLength; - $pos = $initialLength - $currentLength; - } - if (!$parsed) { - return false; - } - return $this->_invokeParser($raw, DOKU_LEXER_UNMATCHED, $pos); - } - - /** - * Sends the matched token and any leading unmatched - * text to the parser changing the lexer to a new - * mode if one is listed. - * @param string $unmatched Unmatched leading portion. - * @param string $matched Actual token match. - * @param bool|string $mode Mode after match. A boolean - * false mode causes no change. - * @param int $initialPos - * @param int $matchPos - * Current byte index location in raw doc - * thats being parsed - * @return boolean False if there was any error - * from the parser. - * @access private - */ - function _dispatchTokens($unmatched, $matched, $mode = false, $initialPos, $matchPos) { - if (! $this->_invokeParser($unmatched, DOKU_LEXER_UNMATCHED, $initialPos) ){ - return false; - } - if ($this->_isModeEnd($mode)) { - if (! $this->_invokeParser($matched, DOKU_LEXER_EXIT, $matchPos)) { - return false; - } - return $this->_mode->leave(); - } - if ($this->_isSpecialMode($mode)) { - $this->_mode->enter($this->_decodeSpecial($mode)); - if (! $this->_invokeParser($matched, DOKU_LEXER_SPECIAL, $matchPos)) { - return false; - } - return $this->_mode->leave(); - } - if (is_string($mode)) { - $this->_mode->enter($mode); - return $this->_invokeParser($matched, DOKU_LEXER_ENTER, $matchPos); - } - return $this->_invokeParser($matched, DOKU_LEXER_MATCHED, $matchPos); - } - - /** - * Tests to see if the new mode is actually to leave - * the current mode and pop an item from the matching - * mode stack. - * @param string $mode Mode to test. - * @return boolean True if this is the exit mode. - * @access private - */ - function _isModeEnd($mode) { - return ($mode === "__exit"); - } - - /** - * Test to see if the mode is one where this mode - * is entered for this token only and automatically - * leaves immediately afterwoods. - * @param string $mode Mode to test. - * @return boolean True if this is the exit mode. - * @access private - */ - function _isSpecialMode($mode) { - return (strncmp($mode, "_", 1) == 0); - } - - /** - * Strips the magic underscore marking single token - * modes. - * @param string $mode Mode to decode. - * @return string Underlying mode name. - * @access private - */ - function _decodeSpecial($mode) { - return substr($mode, 1); - } - - /** - * Calls the parser method named after the current - * mode. Empty content will be ignored. The lexer - * has a parser handler for each mode in the lexer. - * @param string $content Text parsed. - * @param boolean $is_match Token is recognised rather - * than unparsed data. - * @param int $pos Current byte index location in raw doc - * thats being parsed - * @return bool - * @access private - */ - function _invokeParser($content, $is_match, $pos) { - if (($content === "") || ($content === false)) { - return true; - } - $handler = $this->_mode->getCurrent(); - if (isset($this->_mode_handlers[$handler])) { - $handler = $this->_mode_handlers[$handler]; - } - - // modes starting with plugin_ are all handled by the same - // handler but with an additional parameter - if(substr($handler,0,7)=='plugin_'){ - list($handler,$plugin) = explode('_',$handler,2); - return $this->_parser->$handler($content, $is_match, $pos, $plugin); - } - - return $this->_parser->$handler($content, $is_match, $pos); - } - - /** - * Tries to match a chunk of text and if successful - * removes the recognised chunk and any leading - * unparsed data. Empty strings will not be matched. - * @param string $raw The subject to parse. This is the - * content that will be eaten. - * @return array Three item list of unparsed - * content followed by the - * recognised token and finally the - * action the parser is to take. - * True if no match, false if there - * is a parsing error. - * @access private - */ - function _reduce(&$raw) { - if (! isset($this->_regexes[$this->_mode->getCurrent()])) { - return false; - } - if ($raw === "") { - return true; - } - if ($action = $this->_regexes[$this->_mode->getCurrent()]->split($raw, $split)) { - list($unparsed, $match, $raw) = $split; - return array($unparsed, $match, $action); - } - return true; - } -} - -/** - * Escapes regex characters other than (, ) and / - * - * @TODO - * - * @param string $str - * - * @return mixed - */ -function Doku_Lexer_Escape($str) { - //$str = addslashes($str); - $chars = array( - '/\\\\/', - '/\./', - '/\+/', - '/\*/', - '/\?/', - '/\[/', - '/\^/', - '/\]/', - '/\$/', - '/\{/', - '/\}/', - '/\=/', - '/\!/', - '/\</', - '/\>/', - '/\|/', - '/\:/' - ); - - $escaped = array( - '\\\\\\\\', - '\.', - '\+', - '\*', - '\?', - '\[', - '\^', - '\]', - '\$', - '\{', - '\}', - '\=', - '\!', - '\<', - '\>', - '\|', - '\:' - ); - return preg_replace($chars, $escaped, $str); -} - -//Setup VIM: ex: et ts=4 sw=4 : diff --git a/inc/parser/metadata.php b/inc/parser/metadata.php index f9e05bd81..fa64ae2ec 100644 --- a/inc/parser/metadata.php +++ b/inc/parser/metadata.php @@ -1,22 +1,5 @@ <?php /** - * Renderer for metadata - * - * @author Esther Brunner <wikidesign@gmail.com> - */ -if(!defined('DOKU_INC')) die('meh.'); - -if(!defined('DOKU_LF')) { - // Some whitespace to help View > Source - define ('DOKU_LF', "\n"); -} - -if(!defined('DOKU_TAB')) { - // Some whitespace to help View > Source - define ('DOKU_TAB', "\t"); -} - -/** * The MetaData Renderer * * Metadata is additional information about a DokuWiki page that gets extracted mainly from the page's content @@ -24,8 +7,11 @@ if(!defined('DOKU_TAB')) { * $persistent. * * Some simplified rendering to $doc is done to gather the page's (text-only) abstract. + * + * @author Esther Brunner <wikidesign@gmail.com> */ -class Doku_Renderer_metadata extends Doku_Renderer { +class Doku_Renderer_metadata extends Doku_Renderer +{ /** the approximate byte lenght to capture for the abstract */ const ABSTRACT_LEN = 250; @@ -61,7 +47,8 @@ class Doku_Renderer_metadata extends Doku_Renderer { * * @return string always 'metadata' */ - function getFormat() { + public function getFormat() + { return 'metadata'; } @@ -70,19 +57,20 @@ class Doku_Renderer_metadata extends Doku_Renderer { * * Sets up some of the persistent info about the page if it doesn't exist, yet. */ - function document_start() { + public function document_start() + { global $ID; $this->headers = array(); // external pages are missing create date - if(!$this->persistent['date']['created']) { + if (!$this->persistent['date']['created']) { $this->persistent['date']['created'] = filectime(wikiFN($ID)); } - if(!isset($this->persistent['user'])) { + if (!isset($this->persistent['user'])) { $this->persistent['user'] = ''; } - if(!isset($this->persistent['creator'])) { + if (!isset($this->persistent['creator'])) { $this->persistent['creator'] = ''; } // reset metadata to persistent values @@ -94,27 +82,27 @@ class Doku_Renderer_metadata extends Doku_Renderer { * * Stores collected data in the metadata */ - function document_end() { + public function document_end() + { global $ID; // store internal info in metadata (notoc,nocache) $this->meta['internal'] = $this->info; - if(!isset($this->meta['description']['abstract'])) { + if (!isset($this->meta['description']['abstract'])) { // cut off too long abstracts $this->doc = trim($this->doc); - if(strlen($this->doc) > self::ABSTRACT_MAX) { - $this->doc = utf8_substr($this->doc, 0, self::ABSTRACT_MAX).'…'; + if (strlen($this->doc) > self::ABSTRACT_MAX) { + $this->doc = \dokuwiki\Utf8\PhpString::substr($this->doc, 0, self::ABSTRACT_MAX).'…'; } $this->meta['description']['abstract'] = $this->doc; } $this->meta['relation']['firstimage'] = $this->firstimage; - if(!isset($this->meta['date']['modified'])) { + if (!isset($this->meta['date']['modified'])) { $this->meta['date']['modified'] = filemtime(wikiFN($ID)); } - } /** @@ -125,13 +113,18 @@ class Doku_Renderer_metadata extends Doku_Renderer { * * @param $text */ - function cdata($text) { - if(!$this->capture || !$this->capturing) return; + public function cdata($text) + { + if (!$this->capture || !$this->capturing) { + return; + } $this->doc .= $text; $this->captured += strlen($text); - if($this->captured > self::ABSTRACT_LEN) $this->capture = false; + if ($this->captured > self::ABSTRACT_LEN) { + $this->capture = false; + } } /** @@ -141,11 +134,12 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string $text the text to display * @param int $level the nesting level */ - function toc_additem($id, $text, $level) { + public function toc_additem($id, $text, $level) + { global $conf; //only add items within configured levels - if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']) { + if ($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']) { // the TOC is one of our standard ul list arrays ;-) $this->meta['description']['tableofcontents'][] = array( 'hid' => $id, @@ -154,7 +148,6 @@ class Doku_Renderer_metadata extends Doku_Renderer { 'level' => $level - $conf['toptoclevel'] + 1 ); } - } /** @@ -164,8 +157,11 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param int $level header level * @param int $pos byte position in the original source */ - function header($text, $level, $pos) { - if(!isset($this->meta['title'])) $this->meta['title'] = $text; + public function header($text, $level, $pos) + { + if (!isset($this->meta['title'])) { + $this->meta['title'] = $text; + } // add the header to the TOC $hid = $this->_headerToLink($text, true); @@ -178,28 +174,32 @@ class Doku_Renderer_metadata extends Doku_Renderer { /** * Open a paragraph */ - function p_open() { + public function p_open() + { $this->cdata(DOKU_LF); } /** * Close a paragraph */ - function p_close() { + public function p_close() + { $this->cdata(DOKU_LF); } /** * Create a line break */ - function linebreak() { + public function linebreak() + { $this->cdata(DOKU_LF); } /** * Create a horizontal line */ - function hr() { + public function hr() + { $this->cdata(DOKU_LF.'----------'.DOKU_LF); } @@ -212,8 +212,9 @@ class Doku_Renderer_metadata extends Doku_Renderer { * * @author Andreas Gohr <andi@splitbrain.org> */ - function footnote_open() { - if($this->capture) { + public function footnote_open() + { + if ($this->capture) { // move current content to store // this is required to ensure safe behaviour of plugins accessed within footnotes $this->store = $this->doc; @@ -232,8 +233,9 @@ class Doku_Renderer_metadata extends Doku_Renderer { * * @author Andreas Gohr */ - function footnote_close() { - if($this->capture) { + public function footnote_close() + { + if ($this->capture) { // re-enable capturing $this->capturing = true; // restore previously rendered content @@ -245,14 +247,16 @@ class Doku_Renderer_metadata extends Doku_Renderer { /** * Open an unordered list */ - function listu_open() { + public function listu_open() + { $this->cdata(DOKU_LF); } /** * Open an ordered list */ - function listo_open() { + public function listo_open() + { $this->cdata(DOKU_LF); } @@ -262,14 +266,16 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param int $level the nesting level * @param bool $node true when a node; false when a leaf */ - function listitem_open($level,$node=false) { + public function listitem_open($level, $node=false) + { $this->cdata(str_repeat(DOKU_TAB, $level).'* '); } /** * Close a list item */ - function listitem_close() { + public function listitem_close() + { $this->cdata(DOKU_LF); } @@ -278,21 +284,24 @@ class Doku_Renderer_metadata extends Doku_Renderer { * * @param string $text */ - function preformatted($text) { + public function preformatted($text) + { $this->cdata($text); } /** * Start a block quote */ - function quote_open() { + public function quote_open() + { $this->cdata(DOKU_LF.DOKU_TAB.'"'); } /** * Stop a block quote */ - function quote_close() { + public function quote_close() + { $this->cdata('"'.DOKU_LF); } @@ -303,7 +312,8 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string $lang programming language to use for syntax highlighting * @param string $file file path label */ - function file($text, $lang = null, $file = null) { + public function file($text, $lang = null, $file = null) + { $this->cdata(DOKU_LF.$text.DOKU_LF); } @@ -314,7 +324,8 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string $language programming language to use for syntax highlighting * @param string $file file path label */ - function code($text, $language = null, $file = null) { + public function code($text, $language = null, $file = null) + { $this->cdata(DOKU_LF.$text.DOKU_LF); } @@ -325,7 +336,8 @@ class Doku_Renderer_metadata extends Doku_Renderer { * * @param string $acronym */ - function acronym($acronym) { + public function acronym($acronym) + { $this->cdata($acronym); } @@ -336,7 +348,8 @@ class Doku_Renderer_metadata extends Doku_Renderer { * * @param string $smiley */ - function smiley($smiley) { + public function smiley($smiley) + { $this->cdata($smiley); } @@ -349,7 +362,8 @@ class Doku_Renderer_metadata extends Doku_Renderer { * * @param string $entity */ - function entity($entity) { + public function entity($entity) + { $this->cdata($entity); } @@ -361,14 +375,16 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string|int $x first value * @param string|int $y second value */ - function multiplyentity($x, $y) { + public function multiplyentity($x, $y) + { $this->cdata($x.'×'.$y); } /** * Render an opening single quote char (language specific) */ - function singlequoteopening() { + public function singlequoteopening() + { global $lang; $this->cdata($lang['singlequoteopening']); } @@ -376,7 +392,8 @@ class Doku_Renderer_metadata extends Doku_Renderer { /** * Render a closing single quote char (language specific) */ - function singlequoteclosing() { + public function singlequoteclosing() + { global $lang; $this->cdata($lang['singlequoteclosing']); } @@ -384,7 +401,8 @@ class Doku_Renderer_metadata extends Doku_Renderer { /** * Render an apostrophe char (language specific) */ - function apostrophe() { + public function apostrophe() + { global $lang; $this->cdata($lang['apostrophe']); } @@ -392,7 +410,8 @@ class Doku_Renderer_metadata extends Doku_Renderer { /** * Render an opening double quote char (language specific) */ - function doublequoteopening() { + public function doublequoteopening() + { global $lang; $this->cdata($lang['doublequoteopening']); } @@ -400,7 +419,8 @@ class Doku_Renderer_metadata extends Doku_Renderer { /** * Render an closinging double quote char (language specific) */ - function doublequoteclosing() { + public function doublequoteclosing() + { global $lang; $this->cdata($lang['doublequoteclosing']); } @@ -411,7 +431,8 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string $link The link name * @see http://en.wikipedia.org/wiki/CamelCase */ - function camelcaselink($link) { + public function camelcaselink($link) + { $this->internallink($link, $link); } @@ -421,10 +442,13 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string $hash hash link identifier * @param string $name name for the link */ - function locallink($hash, $name = null) { - if(is_array($name)) { + public function locallink($hash, $name = null) + { + if (is_array($name)) { $this->_firstimage($name['src']); - if($name['type'] == 'internalmedia') $this->_recordMediaUsage($name['src']); + if ($name['type'] == 'internalmedia') { + $this->_recordMediaUsage($name['src']); + } } } @@ -434,16 +458,19 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string $id page ID to link to. eg. 'wiki:syntax' * @param string|array|null $name name for the link, array for media file */ - function internallink($id, $name = null) { + public function internallink($id, $name = null) + { global $ID; - if(is_array($name)) { + if (is_array($name)) { $this->_firstimage($name['src']); - if($name['type'] == 'internalmedia') $this->_recordMediaUsage($name['src']); + if ($name['type'] == 'internalmedia') { + $this->_recordMediaUsage($name['src']); + } } $parts = explode('?', $id, 2); - if(count($parts) === 2) { + if (count($parts) === 2) { $id = $parts[0]; } @@ -459,7 +486,7 @@ class Doku_Renderer_metadata extends Doku_Renderer { // p_set_metadata($id, $data); // add link title to summary - if($this->capture) { + if ($this->capture) { $name = $this->_getLinkTitle($name, $default, $id); $this->doc .= $name; } @@ -471,13 +498,16 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string $url full URL with scheme * @param string|array|null $name name for the link, array for media file */ - function externallink($url, $name = null) { - if(is_array($name)) { + public function externallink($url, $name = null) + { + if (is_array($name)) { $this->_firstimage($name['src']); - if($name['type'] == 'internalmedia') $this->_recordMediaUsage($name['src']); + if ($name['type'] == 'internalmedia') { + $this->_recordMediaUsage($name['src']); + } } - if($this->capture) { + if ($this->capture) { $this->doc .= $this->_getLinkTitle($name, '<'.$url.'>'); } } @@ -492,13 +522,16 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string $wikiName indentifier (shortcut) for the remote wiki * @param string $wikiUri the fragment parsed from the original link */ - function interwikilink($match, $name = null, $wikiName, $wikiUri) { - if(is_array($name)) { + public function interwikilink($match, $name, $wikiName, $wikiUri) + { + if (is_array($name)) { $this->_firstimage($name['src']); - if($name['type'] == 'internalmedia') $this->_recordMediaUsage($name['src']); + if ($name['type'] == 'internalmedia') { + $this->_recordMediaUsage($name['src']); + } } - if($this->capture) { + if ($this->capture) { list($wikiUri) = explode('#', $wikiUri, 2); $name = $this->_getLinkTitle($name, $wikiUri); $this->doc .= $name; @@ -511,15 +544,21 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string $url the link * @param string|array $name name for the link, array for media file */ - function windowssharelink($url, $name = null) { - if(is_array($name)) { + public function windowssharelink($url, $name = null) + { + if (is_array($name)) { $this->_firstimage($name['src']); - if($name['type'] == 'internalmedia') $this->_recordMediaUsage($name['src']); + if ($name['type'] == 'internalmedia') { + $this->_recordMediaUsage($name['src']); + } } - if($this->capture) { - if($name) $this->doc .= $name; - else $this->doc .= '<'.$url.'>'; + if ($this->capture) { + if ($name) { + $this->doc .= $name; + } else { + $this->doc .= '<'.$url.'>'; + } } } @@ -531,15 +570,21 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string $address Email-Address * @param string|array $name name for the link, array for media file */ - function emaillink($address, $name = null) { - if(is_array($name)) { + public function emaillink($address, $name = null) + { + if (is_array($name)) { $this->_firstimage($name['src']); - if($name['type'] == 'internalmedia') $this->_recordMediaUsage($name['src']); + if ($name['type'] == 'internalmedia') { + $this->_recordMediaUsage($name['src']); + } } - if($this->capture) { - if($name) $this->doc .= $name; - else $this->doc .= '<'.$address.'>'; + if ($this->capture) { + if ($name) { + $this->doc .= $name; + } else { + $this->doc .= '<'.$address.'>'; + } } } @@ -554,9 +599,12 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string $cache cache|recache|nocache * @param string $linking linkonly|detail|nolink */ - function internalmedia($src, $title = null, $align = null, $width = null, - $height = null, $cache = null, $linking = null) { - if($this->capture && $title) $this->doc .= '['.$title.']'; + public function internalmedia($src, $title = null, $align = null, $width = null, + $height = null, $cache = null, $linking = null) + { + if ($this->capture && $title) { + $this->doc .= '['.$title.']'; + } $this->_firstimage($src); $this->_recordMediaUsage($src); } @@ -572,9 +620,12 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string $cache cache|recache|nocache * @param string $linking linkonly|detail|nolink */ - function externalmedia($src, $title = null, $align = null, $width = null, - $height = null, $cache = null, $linking = null) { - if($this->capture && $title) $this->doc .= '['.$title.']'; + public function externalmedia($src, $title = null, $align = null, $width = null, + $height = null, $cache = null, $linking = null) + { + if ($this->capture && $title) { + $this->doc .= '['.$title.']'; + } $this->_firstimage($src); } @@ -584,7 +635,8 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param string $url URL of the feed * @param array $params Finetuning of the output */ - function rss($url, $params) { + public function rss($url, $params) + { $this->meta['relation']['haspart'][$url] = true; $this->meta['date']['valid']['age'] = @@ -605,12 +657,15 @@ class Doku_Renderer_metadata extends Doku_Renderer { * * @return mixed|string */ - function _simpleTitle($name) { + public function _simpleTitle($name) + { global $conf; - if(is_array($name)) return ''; + if (is_array($name)) { + return ''; + } - if($conf['useslash']) { + if ($conf['useslash']) { $nssep = '[:;/]'; } else { $nssep = '[:;]'; @@ -622,23 +677,6 @@ class Doku_Renderer_metadata extends Doku_Renderer { } /** - * Creates a linkid from a headline - * - * @author Andreas Gohr <andi@splitbrain.org> - * @param string $title The headline title - * @param boolean $create Create a new unique ID? - * @return string - */ - function _headerToLink($title, $create = false) { - if($create) { - return sectionID($title, $this->headers); - } else { - $check = false; - return sectionID($title, $check); - } - } - - /** * Construct a title and handle images in titles * * @author Harry Fuecks <hfuecks@gmail.com> @@ -647,17 +685,20 @@ class Doku_Renderer_metadata extends Doku_Renderer { * @param null|string $id linked page id (used to extract title from first heading) * @return string title text */ - function _getLinkTitle($title, $default, $id = null) { - if(is_array($title)) { - if($title['title']) { + public function _getLinkTitle($title, $default, $id = null) + { + if (is_array($title)) { + if ($title['title']) { return '['.$title['title'].']'; } else { return $default; } - } else if(is_null($title) || trim($title) == '') { - if(useHeading('content') && $id) { + } elseif (is_null($title) || trim($title) == '') { + if (useHeading('content') && $id) { $heading = p_get_first_heading($id, METADATA_DONT_RENDER); - if($heading) return $heading; + if ($heading) { + return $heading; + } } return $default; } else { @@ -670,15 +711,19 @@ class Doku_Renderer_metadata extends Doku_Renderer { * * @param string $src image URL or ID */ - function _firstimage($src) { - if($this->firstimage) return; + protected function _firstimage($src) + { global $ID; + if ($this->firstimage) { + return; + } + list($src) = explode('#', $src, 2); - if(!media_isexternal($src)) { + if (!media_isexternal($src)) { resolve_mediaid(getNS($ID), $src, $exists); } - if(preg_match('/.(jpe?g|gif|png)$/i', $src)) { + if (preg_match('/.(jpe?g|gif|png)$/i', $src)) { $this->firstimage = $src; } } @@ -688,11 +733,14 @@ class Doku_Renderer_metadata extends Doku_Renderer { * * @param string $src media ID */ - function _recordMediaUsage($src) { + protected function _recordMediaUsage($src) + { global $ID; list ($src) = explode('#', $src, 2); - if(media_isexternal($src)) return; + if (media_isexternal($src)) { + return; + } resolve_mediaid(getNS($ID), $src, $exists); $this->meta['relation']['media'][$src] = $exists; } diff --git a/inc/parser/parser.php b/inc/parser/parser.php index 8cff2b8be..d9fc5fb8f 100644 --- a/inc/parser/parser.php +++ b/inc/parser/parser.php @@ -1,8 +1,4 @@ <?php -if(!defined('DOKU_INC')) die('meh.'); -require_once DOKU_INC . 'inc/parser/lexer.php'; -require_once DOKU_INC . 'inc/parser/handler.php'; - /** * Define various types of modes used by the parser - they are used to @@ -13,1022 +9,49 @@ $PARSER_MODES = array( // containers are complex modes that can contain many other modes // hr breaks the principle but they shouldn't be used in tables / lists // so they are put here - 'container' => array('listblock','table','quote','hr'), + 'container' => array('listblock', 'table', 'quote', 'hr'), // some mode are allowed inside the base mode only - 'baseonly' => array('header'), + 'baseonly' => array('header'), // modes for styling text -- footnote behaves similar to styling - 'formatting' => array('strong', 'emphasis', 'underline', 'monospace', - 'subscript', 'superscript', 'deleted', 'footnote'), + 'formatting' => array( + 'strong', 'emphasis', 'underline', 'monospace', + 'subscript', 'superscript', 'deleted', 'footnote' + ), // modes where the token is simply replaced - they can not contain any // other modes - 'substition' => array('acronym','smiley','wordblock','entity', - 'camelcaselink', 'internallink','media', - 'externallink','linebreak','emaillink', - 'windowssharelink','filelink','notoc', - 'nocache','multiplyentity','quotes','rss'), + 'substition' => array( + 'acronym', 'smiley', 'wordblock', 'entity', + 'camelcaselink', 'internallink', 'media', + 'externallink', 'linebreak', 'emaillink', + 'windowssharelink', 'filelink', 'notoc', + 'nocache', 'multiplyentity', 'quotes', 'rss' + ), // modes which have a start and end token but inside which // no other modes should be applied - 'protected' => array('preformatted','code','file','php','html','htmlblock','phpblock'), + 'protected' => array('preformatted', 'code', 'file', 'php', 'html', 'htmlblock', 'phpblock'), // inside this mode no wiki markup should be applied but lineendings // and whitespace isn't preserved - 'disabled' => array('unformatted'), + 'disabled' => array('unformatted'), // used to mark paragraph boundaries - 'paragraphs' => array('eol') + 'paragraphs' => array('eol') ); -//------------------------------------------------------------------- - -/** - * Sets up the Lexer with modes and points it to the Handler - * For an intro to the Lexer see: wiki:parser - */ -class Doku_Parser { - - var $Handler; - - /** - * @var Doku_Lexer $Lexer - */ - var $Lexer; - - var $modes = array(); - - var $connected = false; - - /** - * @param Doku_Parser_Mode_base $BaseMode - */ - function addBaseMode($BaseMode) { - $this->modes['base'] = $BaseMode; - if ( !$this->Lexer ) { - $this->Lexer = new Doku_Lexer($this->Handler,'base', true); - } - $this->modes['base']->Lexer = $this->Lexer; - } - - /** - * PHP preserves order of associative elements - * Mode sequence is important - * - * @param string $name - * @param Doku_Parser_Mode_Interface $Mode - */ - function addMode($name, Doku_Parser_Mode_Interface $Mode) { - if ( !isset($this->modes['base']) ) { - $this->addBaseMode(new Doku_Parser_Mode_base()); - } - $Mode->Lexer = $this->Lexer; - $this->modes[$name] = $Mode; - } - - function connectModes() { - - if ( $this->connected ) { - return; - } - - foreach ( array_keys($this->modes) as $mode ) { - - // Base isn't connected to anything - if ( $mode == 'base' ) { - continue; - } - $this->modes[$mode]->preConnect(); - - foreach ( array_keys($this->modes) as $cm ) { - - if ( $this->modes[$cm]->accepts($mode) ) { - $this->modes[$mode]->connectTo($cm); - } - - } - - $this->modes[$mode]->postConnect(); - } - - $this->connected = true; - } - - function parse($doc) { - if ( $this->Lexer ) { - $this->connectModes(); - // Normalize CRs and pad doc - $doc = "\n".str_replace("\r\n","\n",$doc)."\n"; - $this->Lexer->parse($doc); - $this->Handler->_finalize(); - return $this->Handler->calls; - } else { - return false; - } - } - -} - -//------------------------------------------------------------------- - -/** - * Class Doku_Parser_Mode_Interface - * - * Defines a mode (syntax component) in the Parser - */ -interface Doku_Parser_Mode_Interface { - /** - * returns a number used to determine in which order modes are added - */ - public function getSort(); - - /** - * Called before any calls to connectTo - * @return void - */ - function preConnect(); - - /** - * Connects the mode - * - * @param string $mode - * @return void - */ - function connectTo($mode); - - /** - * Called after all calls to connectTo - * @return void - */ - function postConnect(); - - /** - * Check if given mode is accepted inside this mode - * - * @param string $mode - * @return bool - */ - function accepts($mode); -} - -/** - * This class and all the subclasses below are used to reduce the effort required to register - * modes with the Lexer. - * - * @author Harry Fuecks <hfuecks@gmail.com> - */ -class Doku_Parser_Mode implements Doku_Parser_Mode_Interface { - /** - * @var Doku_Lexer $Lexer - */ - var $Lexer; - var $allowedModes = array(); - - function getSort() { - trigger_error('getSort() not implemented in '.get_class($this), E_USER_WARNING); - } - - function preConnect() {} - function connectTo($mode) {} - function postConnect() {} - function accepts($mode) { - return in_array($mode, (array) $this->allowedModes ); - } -} - /** - * Basically the same as Doku_Parser_Mode but extends from DokuWiki_Plugin + * Class Doku_Parser * - * Adds additional functions to syntax plugins + * @deprecated 2018-05-04 */ -class Doku_Parser_Mode_Plugin extends DokuWiki_Plugin implements Doku_Parser_Mode_Interface { - /** - * @var Doku_Lexer $Lexer - */ - var $Lexer; - var $allowedModes = array(); - - /** - * Sort for applying this mode - * - * @return int - */ - function getSort() { - trigger_error('getSort() not implemented in '.get_class($this), E_USER_WARNING); - } - - function preConnect() {} - function connectTo($mode) {} - function postConnect() {} - function accepts($mode) { - return in_array($mode, (array) $this->allowedModes ); - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_base extends Doku_Parser_Mode { - - function __construct() { - global $PARSER_MODES; - - $this->allowedModes = array_merge ( - $PARSER_MODES['container'], - $PARSER_MODES['baseonly'], - $PARSER_MODES['paragraphs'], - $PARSER_MODES['formatting'], - $PARSER_MODES['substition'], - $PARSER_MODES['protected'], - $PARSER_MODES['disabled'] - ); - } - - function getSort() { - return 0; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_footnote extends Doku_Parser_Mode { - - function __construct() { - global $PARSER_MODES; - - $this->allowedModes = array_merge ( - $PARSER_MODES['container'], - $PARSER_MODES['formatting'], - $PARSER_MODES['substition'], - $PARSER_MODES['protected'], - $PARSER_MODES['disabled'] - ); - - unset($this->allowedModes[array_search('footnote', $this->allowedModes)]); - } - - function connectTo($mode) { - $this->Lexer->addEntryPattern( - '\x28\x28(?=.*\x29\x29)',$mode,'footnote' - ); - } - - function postConnect() { - $this->Lexer->addExitPattern( - '\x29\x29','footnote' - ); - } - - function getSort() { - return 150; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_header extends Doku_Parser_Mode { - - function connectTo($mode) { - //we're not picky about the closing ones, two are enough - $this->Lexer->addSpecialPattern( - '[ \t]*={2,}[^\n]+={2,}[ \t]*(?=\n)', - $mode, - 'header' - ); - } - - function getSort() { - return 50; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_notoc extends Doku_Parser_Mode { - - function connectTo($mode) { - $this->Lexer->addSpecialPattern('~~NOTOC~~',$mode,'notoc'); - } - - function getSort() { - return 30; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_nocache extends Doku_Parser_Mode { - - function connectTo($mode) { - $this->Lexer->addSpecialPattern('~~NOCACHE~~',$mode,'nocache'); - } - - function getSort() { - return 40; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_linebreak extends Doku_Parser_Mode { - - function connectTo($mode) { - $this->Lexer->addSpecialPattern('\x5C{2}(?:[ \t]|(?=\n))',$mode,'linebreak'); - } - - function getSort() { - return 140; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_eol extends Doku_Parser_Mode { - - function connectTo($mode) { - $badModes = array('listblock','table'); - if ( in_array($mode, $badModes) ) { - return; - } - // see FS#1652, pattern extended to swallow preceding whitespace to avoid issues with lines that only contain whitespace - $this->Lexer->addSpecialPattern('(?:^[ \t]*)?\n',$mode,'eol'); - } - - function getSort() { - return 370; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_hr extends Doku_Parser_Mode { - - function connectTo($mode) { - $this->Lexer->addSpecialPattern('\n[ \t]*-{4,}[ \t]*(?=\n)',$mode,'hr'); - } - - function getSort() { - return 160; - } -} - -//------------------------------------------------------------------- -/** - * This class sets the markup for bold (=strong), - * italic (=emphasis), underline etc. - */ -class Doku_Parser_Mode_formatting extends Doku_Parser_Mode { - var $type; - - var $formatting = array ( - 'strong' => array ( - 'entry'=>'\*\*(?=.*\*\*)', - 'exit'=>'\*\*', - 'sort'=>70 - ), - - 'emphasis'=> array ( - 'entry'=>'//(?=[^\x00]*[^:])', //hack for bugs #384 #763 #1468 - 'exit'=>'//', - 'sort'=>80 - ), - - 'underline'=> array ( - 'entry'=>'__(?=.*__)', - 'exit'=>'__', - 'sort'=>90 - ), - - 'monospace'=> array ( - 'entry'=>'\x27\x27(?=.*\x27\x27)', - 'exit'=>'\x27\x27', - 'sort'=>100 - ), - - 'subscript'=> array ( - 'entry'=>'<sub>(?=.*</sub>)', - 'exit'=>'</sub>', - 'sort'=>110 - ), - - 'superscript'=> array ( - 'entry'=>'<sup>(?=.*</sup>)', - 'exit'=>'</sup>', - 'sort'=>120 - ), - - 'deleted'=> array ( - 'entry'=>'<del>(?=.*</del>)', - 'exit'=>'</del>', - 'sort'=>130 - ), - ); - - /** - * @param string $type - */ - function __construct($type) { - global $PARSER_MODES; - - if ( !array_key_exists($type, $this->formatting) ) { - trigger_error('Invalid formatting type '.$type, E_USER_WARNING); - } - - $this->type = $type; - - // formatting may contain other formatting but not it self - $modes = $PARSER_MODES['formatting']; - $key = array_search($type, $modes); - if ( is_int($key) ) { - unset($modes[$key]); - } - - $this->allowedModes = array_merge ( - $modes, - $PARSER_MODES['substition'], - $PARSER_MODES['disabled'] - ); - } - - function connectTo($mode) { - - // Can't nest formatting in itself - if ( $mode == $this->type ) { - return; - } - - $this->Lexer->addEntryPattern( - $this->formatting[$this->type]['entry'], - $mode, - $this->type - ); - } - - function postConnect() { - - $this->Lexer->addExitPattern( - $this->formatting[$this->type]['exit'], - $this->type - ); - - } - - function getSort() { - return $this->formatting[$this->type]['sort']; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_listblock extends Doku_Parser_Mode { - - function __construct() { - global $PARSER_MODES; - - $this->allowedModes = array_merge ( - $PARSER_MODES['formatting'], - $PARSER_MODES['substition'], - $PARSER_MODES['disabled'], - $PARSER_MODES['protected'] #XXX new - ); - - // $this->allowedModes[] = 'footnote'; - } - - function connectTo($mode) { - $this->Lexer->addEntryPattern('[ \t]*\n {2,}[\-\*]',$mode,'listblock'); - $this->Lexer->addEntryPattern('[ \t]*\n\t{1,}[\-\*]',$mode,'listblock'); - - $this->Lexer->addPattern('\n {2,}[\-\*]','listblock'); - $this->Lexer->addPattern('\n\t{1,}[\-\*]','listblock'); - - } - - function postConnect() { - $this->Lexer->addExitPattern('\n','listblock'); - } - - function getSort() { - return 10; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_table extends Doku_Parser_Mode { - - function __construct() { - global $PARSER_MODES; - - $this->allowedModes = array_merge ( - $PARSER_MODES['formatting'], - $PARSER_MODES['substition'], - $PARSER_MODES['disabled'], - $PARSER_MODES['protected'] - ); - } - - function connectTo($mode) { - $this->Lexer->addEntryPattern('[\t ]*\n\^',$mode,'table'); - $this->Lexer->addEntryPattern('[\t ]*\n\|',$mode,'table'); - } - - function postConnect() { - $this->Lexer->addPattern('\n\^','table'); - $this->Lexer->addPattern('\n\|','table'); - $this->Lexer->addPattern('[\t ]*:::[\t ]*(?=[\|\^])','table'); - $this->Lexer->addPattern('[\t ]+','table'); - $this->Lexer->addPattern('\^','table'); - $this->Lexer->addPattern('\|','table'); - $this->Lexer->addExitPattern('\n','table'); - } - - function getSort() { - return 60; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_unformatted extends Doku_Parser_Mode { - - function connectTo($mode) { - $this->Lexer->addEntryPattern('<nowiki>(?=.*</nowiki>)',$mode,'unformatted'); - $this->Lexer->addEntryPattern('%%(?=.*%%)',$mode,'unformattedalt'); - } - - function postConnect() { - $this->Lexer->addExitPattern('</nowiki>','unformatted'); - $this->Lexer->addExitPattern('%%','unformattedalt'); - $this->Lexer->mapHandler('unformattedalt','unformatted'); - } - - function getSort() { - return 170; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_php extends Doku_Parser_Mode { - - function connectTo($mode) { - $this->Lexer->addEntryPattern('<php>(?=.*</php>)',$mode,'php'); - $this->Lexer->addEntryPattern('<PHP>(?=.*</PHP>)',$mode,'phpblock'); - } - - function postConnect() { - $this->Lexer->addExitPattern('</php>','php'); - $this->Lexer->addExitPattern('</PHP>','phpblock'); - } - - function getSort() { - return 180; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_html extends Doku_Parser_Mode { +class Doku_Parser extends \dokuwiki\Parsing\Parser { - function connectTo($mode) { - $this->Lexer->addEntryPattern('<html>(?=.*</html>)',$mode,'html'); - $this->Lexer->addEntryPattern('<HTML>(?=.*</HTML>)',$mode,'htmlblock'); - } - - function postConnect() { - $this->Lexer->addExitPattern('</html>','html'); - $this->Lexer->addExitPattern('</HTML>','htmlblock'); - } - - function getSort() { - return 190; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_preformatted extends Doku_Parser_Mode { - - function connectTo($mode) { - // Has hard coded awareness of lists... - $this->Lexer->addEntryPattern('\n (?![\*\-])',$mode,'preformatted'); - $this->Lexer->addEntryPattern('\n\t(?![\*\-])',$mode,'preformatted'); - - // How to effect a sub pattern with the Lexer! - $this->Lexer->addPattern('\n ','preformatted'); - $this->Lexer->addPattern('\n\t','preformatted'); - - } - - function postConnect() { - $this->Lexer->addExitPattern('\n','preformatted'); - } - - function getSort() { - return 20; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_code extends Doku_Parser_Mode { - - function connectTo($mode) { - $this->Lexer->addEntryPattern('<code\b(?=.*</code>)',$mode,'code'); - } - - function postConnect() { - $this->Lexer->addExitPattern('</code>','code'); - } - - function getSort() { - return 200; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_file extends Doku_Parser_Mode { - - function connectTo($mode) { - $this->Lexer->addEntryPattern('<file\b(?=.*</file>)',$mode,'file'); - } - - function postConnect() { - $this->Lexer->addExitPattern('</file>','file'); - } - - function getSort() { - return 210; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_quote extends Doku_Parser_Mode { - - function __construct() { - global $PARSER_MODES; - - $this->allowedModes = array_merge ( - $PARSER_MODES['formatting'], - $PARSER_MODES['substition'], - $PARSER_MODES['disabled'], - $PARSER_MODES['protected'] #XXX new - ); - #$this->allowedModes[] = 'footnote'; - #$this->allowedModes[] = 'preformatted'; - #$this->allowedModes[] = 'unformatted'; - } - - function connectTo($mode) { - $this->Lexer->addEntryPattern('\n>{1,}',$mode,'quote'); - } - - function postConnect() { - $this->Lexer->addPattern('\n>{1,}','quote'); - $this->Lexer->addExitPattern('\n','quote'); - } - - function getSort() { - return 220; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_acronym extends Doku_Parser_Mode { - // A list - var $acronyms = array(); - var $pattern = ''; - - function __construct($acronyms) { - usort($acronyms,array($this,'_compare')); - $this->acronyms = $acronyms; - } - - function preConnect() { - if(!count($this->acronyms)) return; - - $bound = '[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]'; - $acronyms = array_map('Doku_Lexer_Escape',$this->acronyms); - $this->pattern = '(?<=^|'.$bound.')(?:'.join('|',$acronyms).')(?='.$bound.')'; - } - - function connectTo($mode) { - if(!count($this->acronyms)) return; - - if ( strlen($this->pattern) > 0 ) { - $this->Lexer->addSpecialPattern($this->pattern,$mode,'acronym'); - } - } - - function getSort() { - return 240; - } - - /** - * sort callback to order by string length descending - * - * @param string $a - * @param string $b - * - * @return int - */ - function _compare($a,$b) { - $a_len = strlen($a); - $b_len = strlen($b); - if ($a_len > $b_len) { - return -1; - } else if ($a_len < $b_len) { - return 1; - } - - return 0; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_smiley extends Doku_Parser_Mode { - // A list - var $smileys = array(); - var $pattern = ''; - - function __construct($smileys) { - $this->smileys = $smileys; - } - - function preConnect() { - if(!count($this->smileys) || $this->pattern != '') return; - - $sep = ''; - foreach ( $this->smileys as $smiley ) { - $this->pattern .= $sep.'(?<=\W|^)'.Doku_Lexer_Escape($smiley).'(?=\W|$)'; - $sep = '|'; - } - } - - function connectTo($mode) { - if(!count($this->smileys)) return; - - if ( strlen($this->pattern) > 0 ) { - $this->Lexer->addSpecialPattern($this->pattern,$mode,'smiley'); - } - } - - function getSort() { - return 230; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_wordblock extends Doku_Parser_Mode { - // A list - var $badwords = array(); - var $pattern = ''; - - function __construct($badwords) { - $this->badwords = $badwords; - } - - function preConnect() { - - if ( count($this->badwords) == 0 || $this->pattern != '') { - return; - } - - $sep = ''; - foreach ( $this->badwords as $badword ) { - $this->pattern .= $sep.'(?<=\b)(?i)'.Doku_Lexer_Escape($badword).'(?-i)(?=\b)'; - $sep = '|'; - } - - } - - function connectTo($mode) { - if ( strlen($this->pattern) > 0 ) { - $this->Lexer->addSpecialPattern($this->pattern,$mode,'wordblock'); - } - } - - function getSort() { - return 250; + /** @inheritdoc */ + public function __construct(Doku_Handler $handler) { + dbg_deprecated(\dokuwiki\Parsing\Parser::class); + parent::__construct($handler); } } - -//------------------------------------------------------------------- -class Doku_Parser_Mode_entity extends Doku_Parser_Mode { - // A list - var $entities = array(); - var $pattern = ''; - - function __construct($entities) { - $this->entities = $entities; - } - - function preConnect() { - if(!count($this->entities) || $this->pattern != '') return; - - $sep = ''; - foreach ( $this->entities as $entity ) { - $this->pattern .= $sep.Doku_Lexer_Escape($entity); - $sep = '|'; - } - } - - function connectTo($mode) { - if(!count($this->entities)) return; - - if ( strlen($this->pattern) > 0 ) { - $this->Lexer->addSpecialPattern($this->pattern,$mode,'entity'); - } - } - - function getSort() { - return 260; - } -} - -//------------------------------------------------------------------- -// Implements the 640x480 replacement -class Doku_Parser_Mode_multiplyentity extends Doku_Parser_Mode { - - function connectTo($mode) { - - $this->Lexer->addSpecialPattern( - '(?<=\b)(?:[1-9]|\d{2,})[xX]\d+(?=\b)',$mode,'multiplyentity' - ); - - } - - function getSort() { - return 270; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_quotes extends Doku_Parser_Mode { - - function connectTo($mode) { - global $conf; - - $ws = '\s/\#~:+=&%@\-\x28\x29\]\[{}><"\''; // whitespace - $punc = ';,\.?!'; - - if($conf['typography'] == 2){ - $this->Lexer->addSpecialPattern( - "(?<=^|[$ws])'(?=[^$ws$punc])",$mode,'singlequoteopening' - ); - $this->Lexer->addSpecialPattern( - "(?<=^|[^$ws]|[$punc])'(?=$|[$ws$punc])",$mode,'singlequoteclosing' - ); - $this->Lexer->addSpecialPattern( - "(?<=^|[^$ws$punc])'(?=$|[^$ws$punc])",$mode,'apostrophe' - ); - } - - $this->Lexer->addSpecialPattern( - "(?<=^|[$ws])\"(?=[^$ws$punc])",$mode,'doublequoteopening' - ); - $this->Lexer->addSpecialPattern( - "\"",$mode,'doublequoteclosing' - ); - - } - - function getSort() { - return 280; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_camelcaselink extends Doku_Parser_Mode { - - function connectTo($mode) { - $this->Lexer->addSpecialPattern( - '\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b',$mode,'camelcaselink' - ); - } - - function getSort() { - return 290; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_internallink extends Doku_Parser_Mode { - - function connectTo($mode) { - // Word boundaries? - $this->Lexer->addSpecialPattern("\[\[.*?\]\](?!\])",$mode,'internallink'); - } - - function getSort() { - return 300; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_media extends Doku_Parser_Mode { - - function connectTo($mode) { - // Word boundaries? - $this->Lexer->addSpecialPattern("\{\{(?:[^\}]|(?:\}[^\}]))+\}\}",$mode,'media'); - } - - function getSort() { - return 320; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_rss extends Doku_Parser_Mode { - - function connectTo($mode) { - $this->Lexer->addSpecialPattern("\{\{rss>[^\}]+\}\}",$mode,'rss'); - } - - function getSort() { - return 310; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_externallink extends Doku_Parser_Mode { - var $schemes = array(); - var $patterns = array(); - - function preConnect() { - if(count($this->patterns)) return; - - $ltrs = '\w'; - $gunk = '/\#~:.?+=&%@!\-\[\]'; - $punc = '.:?\-;,'; - $host = $ltrs.$punc; - $any = $ltrs.$gunk.$punc; - - $this->schemes = getSchemes(); - foreach ( $this->schemes as $scheme ) { - $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.'])'; - } - - function connectTo($mode) { - - foreach ( $this->patterns as $pattern ) { - $this->Lexer->addSpecialPattern($pattern,$mode,'externallink'); - } - } - - function getSort() { - return 330; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_filelink extends Doku_Parser_Mode { - - var $pattern; - - function preConnect() { - - $ltrs = '\w'; - $gunk = '/\#~:.?+=&%@!\-'; - $punc = '.:?\-;,'; - $host = $ltrs.$punc; - $any = $ltrs.$gunk.$punc; - - $this->pattern = '\b(?i)file(?-i)://['.$any.']+?['. - $punc.']*[^'.$any.']'; - } - - function connectTo($mode) { - $this->Lexer->addSpecialPattern( - $this->pattern,$mode,'filelink'); - } - - function getSort() { - return 360; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_windowssharelink extends Doku_Parser_Mode { - - var $pattern; - - function preConnect() { - $this->pattern = "\\\\\\\\\w+?(?:\\\\[\w\-$]+)+"; - } - - function connectTo($mode) { - $this->Lexer->addSpecialPattern( - $this->pattern,$mode,'windowssharelink'); - } - - function getSort() { - return 350; - } -} - -//------------------------------------------------------------------- -class Doku_Parser_Mode_emaillink extends Doku_Parser_Mode { - - function connectTo($mode) { - // pattern below is defined in inc/mail.php - $this->Lexer->addSpecialPattern('<'.PREG_PATTERN_VALID_EMAIL.'>',$mode,'emaillink'); - } - - function getSort() { - return 340; - } -} - - -//Setup VIM: ex: et ts=4 : diff --git a/inc/parser/renderer.php b/inc/parser/renderer.php index 83b51d4b1..d00e7388c 100644 --- a/inc/parser/renderer.php +++ b/inc/parser/renderer.php @@ -5,7 +5,9 @@ * @author Harry Fuecks <hfuecks@gmail.com> * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); + +use dokuwiki\Extension\Plugin; +use dokuwiki\Extension\SyntaxPlugin; /** * Allowed chars in $language for code highlighting @@ -16,7 +18,7 @@ define('PREG_PATTERN_VALID_LANGUAGE', '#[^a-zA-Z0-9\-_]#'); /** * An empty renderer, produces no output * - * Inherits from DokuWiki_Plugin for giving additional functions to render plugins + * Inherits from dokuwiki\Plugin\DokuWiki_Plugin for giving additional functions to render plugins * * The renderer transforms the syntax instructions created by the parser and handler into the * desired output format. For each instruction a corresponding method defined in this class will @@ -24,7 +26,7 @@ define('PREG_PATTERN_VALID_LANGUAGE', '#[^a-zA-Z0-9\-_]#'); * $doc field. When all instructions are processed, the $doc field contents will be cached by * DokuWiki and sent to the user. */ -class Doku_Renderer extends DokuWiki_Plugin { +abstract class Doku_Renderer extends Plugin { /** @var array Settings, control the behavior of the renderer */ public $info = array( 'cache' => true, // may the rendered result cached? @@ -40,6 +42,9 @@ class Doku_Renderer extends DokuWiki_Plugin { /** @var array contains the interwiki configuration, set in p_render() */ public $interwiki = array(); + /** @var array the list of headers used to create unique link ids */ + protected $headers = array(); + /** * @var string the rendered document, this will be cached after the renderer ran through */ @@ -51,7 +56,8 @@ class Doku_Renderer extends DokuWiki_Plugin { * This is called before each use of the renderer object and should be used to * completely reset the state of the renderer to be reused for a new document */ - function reset() { + public function reset(){ + $this->headers = array(); } /** @@ -62,7 +68,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @return bool false if the plugin has to be instantiated */ - function isSingleton() { + public function isSingleton() { return false; } @@ -73,15 +79,12 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @return string */ - function getFormat() { - trigger_error('getFormat() not implemented in '.get_class($this), E_USER_WARNING); - return ''; - } + abstract public function getFormat(); /** * Disable caching of this renderer's output */ - function nocache() { + public function nocache() { $this->info['cache'] = false; } @@ -90,7 +93,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * * This might not be used for certain sub renderer */ - function notoc() { + public function notoc() { $this->info['toc'] = false; } @@ -104,8 +107,8 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $state matched state if any * @param string $match raw matched syntax */ - function plugin($name, $data, $state = '', $match = '') { - /** @var DokuWiki_Syntax_Plugin $plugin */ + public function plugin($name, $data, $state = '', $match = '') { + /** @var SyntaxPlugin $plugin */ $plugin = plugin_load('syntax', $name); if($plugin != null) { $plugin->render($this->getFormat(), $this, $data); @@ -118,7 +121,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @param array $instructions */ - function nest($instructions) { + public function nest($instructions) { foreach($instructions as $instruction) { // execute the callback against ourself if(method_exists($this, $instruction[0])) { @@ -133,7 +136,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * normally the syntax mode should override this instruction when instantiating Doku_Handler_Nest - * however plugins will not be able to - as their instructions require data. */ - function nest_close() { + public function nest_close() { } #region Syntax modes - sub classes will need to implement them to fill $doc @@ -141,13 +144,13 @@ class Doku_Renderer extends DokuWiki_Plugin { /** * Initialize the document */ - function document_start() { + public function document_start() { } /** * Finalize the document */ - function document_end() { + public function document_end() { } /** @@ -155,7 +158,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @return string */ - function render_TOC() { + public function render_TOC() { return ''; } @@ -166,7 +169,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $text the text to display * @param int $level the nesting level */ - function toc_additem($id, $text, $level) { + public function toc_additem($id, $text, $level) { } /** @@ -176,7 +179,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param int $level header level * @param int $pos byte position in the original source */ - function header($text, $level, $pos) { + public function header($text, $level, $pos) { } /** @@ -184,13 +187,13 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @param int $level section level (as determined by the previous header) */ - function section_open($level) { + public function section_open($level) { } /** * Close the current section */ - function section_close() { + public function section_close() { } /** @@ -198,151 +201,151 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @param string $text */ - function cdata($text) { + public function cdata($text) { } /** * Open a paragraph */ - function p_open() { + public function p_open() { } /** * Close a paragraph */ - function p_close() { + public function p_close() { } /** * Create a line break */ - function linebreak() { + public function linebreak() { } /** * Create a horizontal line */ - function hr() { + public function hr() { } /** * Start strong (bold) formatting */ - function strong_open() { + public function strong_open() { } /** * Stop strong (bold) formatting */ - function strong_close() { + public function strong_close() { } /** * Start emphasis (italics) formatting */ - function emphasis_open() { + public function emphasis_open() { } /** * Stop emphasis (italics) formatting */ - function emphasis_close() { + public function emphasis_close() { } /** * Start underline formatting */ - function underline_open() { + public function underline_open() { } /** * Stop underline formatting */ - function underline_close() { + public function underline_close() { } /** * Start monospace formatting */ - function monospace_open() { + public function monospace_open() { } /** * Stop monospace formatting */ - function monospace_close() { + public function monospace_close() { } /** * Start a subscript */ - function subscript_open() { + public function subscript_open() { } /** * Stop a subscript */ - function subscript_close() { + public function subscript_close() { } /** * Start a superscript */ - function superscript_open() { + public function superscript_open() { } /** * Stop a superscript */ - function superscript_close() { + public function superscript_close() { } /** * Start deleted (strike-through) formatting */ - function deleted_open() { + public function deleted_open() { } /** * Stop deleted (strike-through) formatting */ - function deleted_close() { + public function deleted_close() { } /** * Start a footnote */ - function footnote_open() { + public function footnote_open() { } /** * Stop a footnote */ - function footnote_close() { + public function footnote_close() { } /** * Open an unordered list */ - function listu_open() { + public function listu_open() { } /** * Close an unordered list */ - function listu_close() { + public function listu_close() { } /** * Open an ordered list */ - function listo_open() { + public function listo_open() { } /** * Close an ordered list */ - function listo_close() { + public function listo_close() { } /** @@ -351,25 +354,25 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param int $level the nesting level * @param bool $node true when a node; false when a leaf */ - function listitem_open($level,$node=false) { + public function listitem_open($level,$node=false) { } /** * Close a list item */ - function listitem_close() { + public function listitem_close() { } /** * Start the content of a list item */ - function listcontent_open() { + public function listcontent_open() { } /** * Stop the content of a list item */ - function listcontent_close() { + public function listcontent_close() { } /** @@ -379,7 +382,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @param string $text */ - function unformatted($text) { + public function unformatted($text) { $this->cdata($text); } @@ -391,7 +394,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @param string $text The PHP code */ - function php($text) { + public function php($text) { } /** @@ -402,7 +405,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @param string $text The PHP code */ - function phpblock($text) { + public function phpblock($text) { } /** @@ -412,7 +415,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @param string $text The HTML */ - function html($text) { + public function html($text) { } /** @@ -422,7 +425,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @param string $text The HTML */ - function htmlblock($text) { + public function htmlblock($text) { } /** @@ -430,19 +433,19 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @param string $text */ - function preformatted($text) { + public function preformatted($text) { } /** * Start a block quote */ - function quote_open() { + public function quote_open() { } /** * Stop a block quote */ - function quote_close() { + public function quote_close() { } /** @@ -452,7 +455,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $lang programming language to use for syntax highlighting * @param string $file file path label */ - function file($text, $lang = null, $file = null) { + public function file($text, $lang = null, $file = null) { } /** @@ -462,7 +465,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $lang programming language to use for syntax highlighting * @param string $file file path label */ - function code($text, $lang = null, $file = null) { + public function code($text, $lang = null, $file = null) { } /** @@ -472,7 +475,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @param string $acronym */ - function acronym($acronym) { + public function acronym($acronym) { } /** @@ -482,7 +485,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @param string $smiley */ - function smiley($smiley) { + public function smiley($smiley) { } /** @@ -494,7 +497,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @param string $entity */ - function entity($entity) { + public function entity($entity) { } /** @@ -505,37 +508,37 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string|int $x first value * @param string|int $y second value */ - function multiplyentity($x, $y) { + public function multiplyentity($x, $y) { } /** * Render an opening single quote char (language specific) */ - function singlequoteopening() { + public function singlequoteopening() { } /** * Render a closing single quote char (language specific) */ - function singlequoteclosing() { + public function singlequoteclosing() { } /** * Render an apostrophe char (language specific) */ - function apostrophe() { + public function apostrophe() { } /** * Render an opening double quote char (language specific) */ - function doublequoteopening() { + public function doublequoteopening() { } /** * Render an closinging double quote char (language specific) */ - function doublequoteclosing() { + public function doublequoteclosing() { } /** @@ -544,7 +547,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $link The link name * @see http://en.wikipedia.org/wiki/CamelCase */ - function camelcaselink($link) { + public function camelcaselink($link) { } /** @@ -553,7 +556,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $hash hash link identifier * @param string $name name for the link */ - function locallink($hash, $name = null) { + public function locallink($hash, $name = null) { } /** @@ -562,7 +565,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $link page ID to link to. eg. 'wiki:syntax' * @param string|array $title name for the link, array for media file */ - function internallink($link, $title = null) { + public function internallink($link, $title = null) { } /** @@ -571,7 +574,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $link full URL with scheme * @param string|array $title name for the link, array for media file */ - function externallink($link, $title = null) { + public function externallink($link, $title = null) { } /** @@ -580,7 +583,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $url URL of the feed * @param array $params Finetuning of the output */ - function rss($url, $params) { + public function rss($url, $params) { } /** @@ -593,7 +596,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $wikiName indentifier (shortcut) for the remote wiki * @param string $wikiUri the fragment parsed from the original link */ - function interwikilink($link, $title = null, $wikiName, $wikiUri) { + public function interwikilink($link, $title, $wikiName, $wikiUri) { } /** @@ -602,7 +605,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $link the link * @param string|array $title name for the link, array for media file */ - function filelink($link, $title = null) { + public function filelink($link, $title = null) { } /** @@ -611,7 +614,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $link the link * @param string|array $title name for the link, array for media file */ - function windowssharelink($link, $title = null) { + public function windowssharelink($link, $title = null) { } /** @@ -622,7 +625,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $address Email-Address * @param string|array $name name for the link, array for media file */ - function emaillink($address, $name = null) { + public function emaillink($address, $name = null) { } /** @@ -636,7 +639,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $cache cache|recache|nocache * @param string $linking linkonly|detail|nolink */ - function internalmedia($src, $title = null, $align = null, $width = null, + public function internalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null) { } @@ -651,7 +654,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $cache cache|recache|nocache * @param string $linking linkonly|detail|nolink */ - function externalmedia($src, $title = null, $align = null, $width = null, + public function externalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null) { } @@ -665,7 +668,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param int $height height of media in pixel * @param string $cache cache|recache|nocache */ - function internalmedialink($src, $title = null, $align = null, + public function internalmedialink($src, $title = null, $align = null, $width = null, $height = null, $cache = null) { } @@ -679,7 +682,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param int $height height of media in pixel * @param string $cache cache|recache|nocache */ - function externalmedialink($src, $title = null, $align = null, + public function externalmedialink($src, $title = null, $align = null, $width = null, $height = null, $cache = null) { } @@ -690,7 +693,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param int $numrows NOT IMPLEMENTED * @param int $pos byte position in the original source */ - function table_open($maxcols = null, $numrows = null, $pos = null) { + public function table_open($maxcols = null, $numrows = null, $pos = null) { } /** @@ -698,55 +701,55 @@ class Doku_Renderer extends DokuWiki_Plugin { * * @param int $pos byte position in the original source */ - function table_close($pos = null) { + public function table_close($pos = null) { } /** * Open a table header */ - function tablethead_open() { + public function tablethead_open() { } /** * Close a table header */ - function tablethead_close() { + public function tablethead_close() { } /** * Open a table body */ - function tabletbody_open() { + public function tabletbody_open() { } /** * Close a table body */ - function tabletbody_close() { + public function tabletbody_close() { } /** * Open a table footer */ - function tabletfoot_open() { + public function tabletfoot_open() { } /** * Close a table footer */ - function tabletfoot_close() { + public function tabletfoot_close() { } /** * Open a table row */ - function tablerow_open() { + public function tablerow_open() { } /** * Close a table row */ - function tablerow_close() { + public function tablerow_close() { } /** @@ -756,13 +759,13 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $align left|center|right * @param int $rowspan */ - function tableheader_open($colspan = 1, $align = null, $rowspan = 1) { + public function tableheader_open($colspan = 1, $align = null, $rowspan = 1) { } /** * Close a table header cell */ - function tableheader_close() { + public function tableheader_close() { } /** @@ -772,13 +775,13 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $align left|center|right * @param int $rowspan */ - function tablecell_open($colspan = 1, $align = null, $rowspan = 1) { + public function tablecell_open($colspan = 1, $align = null, $rowspan = 1) { } /** * Close a table cell */ - function tablecell_close() { + public function tablecell_close() { } #endregion @@ -786,6 +789,23 @@ class Doku_Renderer extends DokuWiki_Plugin { #region util functions, you probably won't need to reimplement them /** + * Creates a linkid from a headline + * + * @author Andreas Gohr <andi@splitbrain.org> + * @param string $title The headline title + * @param boolean $create Create a new unique ID? + * @return string + */ + public function _headerToLink($title, $create = false) { + if($create) { + return sectionID($title, $this->headers); + } else { + $check = false; + return sectionID($title, $check); + } + } + + /** * Removes any Namespace from the given name but keeps * casing and special chars * @@ -794,7 +814,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param string $name * @return string */ - function _simpleTitle($name) { + protected function _simpleTitle($name) { global $conf; //if there is a hash we use the ancor name only @@ -818,7 +838,7 @@ class Doku_Renderer extends DokuWiki_Plugin { * @param null|bool $exists reference which returns if an internal page exists * @return string interwikilink */ - function _resolveInterWiki(&$shortcut, $reference, &$exists = null) { + public function _resolveInterWiki(&$shortcut, $reference, &$exists = null) { //get interwiki URL if(isset($this->interwiki[$shortcut])) { $url = $this->interwiki[$shortcut]; diff --git a/inc/parser/xhtml.php b/inc/parser/xhtml.php index 0771b7abd..388e1b7ad 100644 --- a/inc/parser/xhtml.php +++ b/inc/parser/xhtml.php @@ -1,26 +1,15 @@ <?php + +use dokuwiki\ChangeLog\MediaChangeLog; + /** * Renderer for XHTML output * + * This is DokuWiki's main renderer used to display page content in the wiki + * * @author Harry Fuecks <hfuecks@gmail.com> * @author Andreas Gohr <andi@splitbrain.org> - */ -if(!defined('DOKU_INC')) die('meh.'); - -if(!defined('DOKU_LF')) { - // Some whitespace to help View > Source - define ('DOKU_LF', "\n"); -} - -if(!defined('DOKU_TAB')) { - // Some whitespace to help View > Source - define ('DOKU_TAB', "\t"); -} - -/** - * The XHTML Renderer * - * This is DokuWiki's main renderer used to display page content in the wiki */ class Doku_Renderer_xhtml extends Doku_Renderer { /** @var array store the table of contents */ @@ -28,14 +17,13 @@ class Doku_Renderer_xhtml extends Doku_Renderer { /** @var array A stack of section edit data */ protected $sectionedits = array(); - var $date_at = ''; // link pages and media against this revision + + /** @var string|int link pages and media against this revision */ + public $date_at = ''; /** @var int last section edit id, used by startSectionEdit */ protected $lastsecid = 0; - /** @var array the list of headers used to create unique link ids */ - protected $headers = array(); - /** @var array a list of footnotes, list starts at 1! */ protected $footnotes = array(); @@ -122,23 +110,22 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @return string always 'xhtml' */ - function getFormat() { + public function getFormat() { return 'xhtml'; } /** * Initialize the document */ - function document_start() { + public function document_start() { //reset some internals $this->toc = array(); - $this->headers = array(); } /** * Finalize the document */ - function document_end() { + public function document_end() { // Finish open section edits. while(count($this->sectionedits) > 0) { if($this->sectionedits[count($this->sectionedits) - 1]['start'] <= 1) { @@ -183,7 +170,11 @@ class Doku_Renderer_xhtml extends Doku_Renderer { // Prepare the TOC global $conf; - if($this->info['toc'] && is_array($this->toc) && $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads']) { + if( + $this->info['toc'] && + is_array($this->toc) && + $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads'] + ) { global $TOC; $TOC = $this->toc; } @@ -199,7 +190,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param string $text the text to display * @param int $level the nesting level */ - function toc_additem($id, $text, $level) { + public function toc_additem($id, $text, $level) { global $conf; //handle TOC @@ -215,7 +206,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param int $level header level * @param int $pos byte position in the original source */ - function header($text, $level, $pos) { + public function header($text, $level, $pos) { global $conf; if(blank($text)) return; //skip empty headlines @@ -261,14 +252,14 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @param int $level section level (as determined by the previous header) */ - function section_open($level) { + public function section_open($level) { $this->doc .= '<div class="level'.$level.'">'.DOKU_LF; } /** * Close the current section */ - function section_close() { + public function section_close() { $this->doc .= DOKU_LF.'</div>'.DOKU_LF; } @@ -277,133 +268,133 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @param $text */ - function cdata($text) { + public function cdata($text) { $this->doc .= $this->_xmlEntities($text); } /** * Open a paragraph */ - function p_open() { + public function p_open() { $this->doc .= DOKU_LF.'<p>'.DOKU_LF; } /** * Close a paragraph */ - function p_close() { + public function p_close() { $this->doc .= DOKU_LF.'</p>'.DOKU_LF; } /** * Create a line break */ - function linebreak() { + public function linebreak() { $this->doc .= '<br/>'.DOKU_LF; } /** * Create a horizontal line */ - function hr() { + public function hr() { $this->doc .= '<hr />'.DOKU_LF; } /** * Start strong (bold) formatting */ - function strong_open() { + public function strong_open() { $this->doc .= '<strong>'; } /** * Stop strong (bold) formatting */ - function strong_close() { + public function strong_close() { $this->doc .= '</strong>'; } /** * Start emphasis (italics) formatting */ - function emphasis_open() { + public function emphasis_open() { $this->doc .= '<em>'; } /** * Stop emphasis (italics) formatting */ - function emphasis_close() { + public function emphasis_close() { $this->doc .= '</em>'; } /** * Start underline formatting */ - function underline_open() { + public function underline_open() { $this->doc .= '<em class="u">'; } /** * Stop underline formatting */ - function underline_close() { + public function underline_close() { $this->doc .= '</em>'; } /** * Start monospace formatting */ - function monospace_open() { + public function monospace_open() { $this->doc .= '<code>'; } /** * Stop monospace formatting */ - function monospace_close() { + public function monospace_close() { $this->doc .= '</code>'; } /** * Start a subscript */ - function subscript_open() { + public function subscript_open() { $this->doc .= '<sub>'; } /** * Stop a subscript */ - function subscript_close() { + public function subscript_close() { $this->doc .= '</sub>'; } /** * Start a superscript */ - function superscript_open() { + public function superscript_open() { $this->doc .= '<sup>'; } /** * Stop a superscript */ - function superscript_close() { + public function superscript_close() { $this->doc .= '</sup>'; } /** * Start deleted (strike-through) formatting */ - function deleted_open() { + public function deleted_open() { $this->doc .= '<del>'; } /** * Stop deleted (strike-through) formatting */ - function deleted_close() { + public function deleted_close() { $this->doc .= '</del>'; } @@ -416,7 +407,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @author Andreas Gohr <andi@splitbrain.org> */ - function footnote_open() { + public function footnote_open() { // move current content to store and record footnote $this->store = $this->doc; @@ -431,7 +422,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @author Andreas Gohr */ - function footnote_close() { + public function footnote_close() { /** @var $fnid int takes track of seen footnotes, assures they are unique even across multiple docs FS#2841 */ static $fnid = 0; // assign new footnote id (we start at 1) @@ -462,7 +453,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input */ - function listu_open($classes = null) { + public function listu_open($classes = null) { $class = ''; if($classes !== null) { if(is_array($classes)) $classes = join(' ', $classes); @@ -474,7 +465,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { /** * Close an unordered list */ - function listu_close() { + public function listu_close() { $this->doc .= '</ul>'.DOKU_LF; } @@ -483,7 +474,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input */ - function listo_open($classes = null) { + public function listo_open($classes = null) { $class = ''; if($classes !== null) { if(is_array($classes)) $classes = join(' ', $classes); @@ -495,7 +486,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { /** * Close an ordered list */ - function listo_close() { + public function listo_close() { $this->doc .= '</ol>'.DOKU_LF; } @@ -505,7 +496,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param int $level the nesting level * @param bool $node true when a node; false when a leaf */ - function listitem_open($level, $node=false) { + public function listitem_open($level, $node=false) { $branching = $node ? ' node' : ''; $this->doc .= '<li class="level'.$level.$branching.'">'; } @@ -513,21 +504,21 @@ class Doku_Renderer_xhtml extends Doku_Renderer { /** * Close a list item */ - function listitem_close() { + public function listitem_close() { $this->doc .= '</li>'.DOKU_LF; } /** * Start the content of a list item */ - function listcontent_open() { + public function listcontent_open() { $this->doc .= '<div class="li">'; } /** * Stop the content of a list item */ - function listcontent_close() { + public function listcontent_close() { $this->doc .= '</div>'.DOKU_LF; } @@ -538,7 +529,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @param string $text */ - function unformatted($text) { + public function unformatted($text) { $this->doc .= $this->_xmlEntities($text); } @@ -550,7 +541,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @author Andreas Gohr <andi@splitbrain.org> */ - function php($text, $wrapper = 'code') { + public function php($text, $wrapper = 'code') { global $conf; if($conf['phpok']) { @@ -571,7 +562,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @param string $text The PHP code */ - function phpblock($text) { + public function phpblock($text) { $this->php($text, 'pre'); } @@ -583,7 +574,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @author Andreas Gohr <andi@splitbrain.org> */ - function html($text, $wrapper = 'code') { + public function html($text, $wrapper = 'code') { global $conf; if($conf['htmlok']) { @@ -600,21 +591,21 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @param string $text The HTML */ - function htmlblock($text) { + public function htmlblock($text) { $this->html($text, 'pre'); } /** * Start a block quote */ - function quote_open() { + public function quote_open() { $this->doc .= '<blockquote><div class="no">'.DOKU_LF; } /** * Stop a block quote */ - function quote_close() { + public function quote_close() { $this->doc .= '</div></blockquote>'.DOKU_LF; } @@ -623,7 +614,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @param string $text */ - function preformatted($text) { + public function preformatted($text) { $this->doc .= '<pre class="code">'.trim($this->_xmlEntities($text), "\n\r").'</pre>'.DOKU_LF; } @@ -635,7 +626,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param string $filename file path label * @param array $options assoziative array with additional geshi options */ - function file($text, $language = null, $filename = null, $options=null) { + public function file($text, $language = null, $filename = null, $options=null) { $this->_highlight('file', $text, $language, $filename, $options); } @@ -647,7 +638,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param string $filename file path label * @param array $options assoziative array with additional geshi options */ - function code($text, $language = null, $filename = null, $options=null) { + public function code($text, $language = null, $filename = null, $options=null) { $this->_highlight('code', $text, $language, $filename, $options); } @@ -661,7 +652,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param string $filename file path label * @param array $options assoziative array with additional geshi options */ - function _highlight($type, $text, $language = null, $filename = null, $options = null) { + public function _highlight($type, $text, $language = null, $filename = null, $options = null) { global $ID; global $lang; global $INPUT; @@ -679,7 +670,12 @@ class Doku_Renderer_xhtml extends Doku_Renderer { $offset = $INPUT->str('codeblockOffset'); } $this->doc .= '<dl class="'.$type.'">'.DOKU_LF; - $this->doc .= '<dt><a href="'.exportlink($ID, 'code', array('codeblock' => $offset+$this->_codeblock)).'" title="'.$lang['download'].'" class="'.$class.'">'; + $this->doc .= '<dt><a href="' . + exportlink( + $ID, + 'code', + array('codeblock' => $offset + $this->_codeblock) + ) . '" title="' . $lang['download'] . '" class="' . $class . '">'; $this->doc .= hsc($filename); $this->doc .= '</a></dt>'.DOKU_LF.'<dd>'; } @@ -697,7 +693,9 @@ class Doku_Renderer_xhtml extends Doku_Renderer { $class = 'code'; //we always need the code class to make the syntax highlighting apply if($type != 'code') $class .= ' '.$type; - $this->doc .= "<pre class=\"$class $language\">".p_xhtml_cached_geshi($text, $language, '', $options).'</pre>'.DOKU_LF; + $this->doc .= "<pre class=\"$class $language\">" . + p_xhtml_cached_geshi($text, $language, '', $options) . + '</pre>' . DOKU_LF; } if($filename) { @@ -714,7 +712,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @param string $acronym */ - function acronym($acronym) { + public function acronym($acronym) { if(array_key_exists($acronym, $this->acronyms)) { @@ -735,7 +733,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @param string $smiley */ - function smiley($smiley) { + public function smiley($smiley) { if(array_key_exists($smiley, $this->smileys)) { $this->doc .= '<img src="'.DOKU_BASE.'lib/images/smileys/'.$this->smileys[$smiley]. '" class="icon" alt="'. @@ -754,7 +752,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @param string $entity */ - function entity($entity) { + public function entity($entity) { if(array_key_exists($entity, $this->entities)) { $this->doc .= $this->entities[$entity]; } else { @@ -770,14 +768,14 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param string|int $x first value * @param string|int $y second value */ - function multiplyentity($x, $y) { + public function multiplyentity($x, $y) { $this->doc .= "$x×$y"; } /** * Render an opening single quote char (language specific) */ - function singlequoteopening() { + public function singlequoteopening() { global $lang; $this->doc .= $lang['singlequoteopening']; } @@ -785,7 +783,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { /** * Render a closing single quote char (language specific) */ - function singlequoteclosing() { + public function singlequoteclosing() { global $lang; $this->doc .= $lang['singlequoteclosing']; } @@ -793,7 +791,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { /** * Render an apostrophe char (language specific) */ - function apostrophe() { + public function apostrophe() { global $lang; $this->doc .= $lang['apostrophe']; } @@ -801,7 +799,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { /** * Render an opening double quote char (language specific) */ - function doublequoteopening() { + public function doublequoteopening() { global $lang; $this->doc .= $lang['doublequoteopening']; } @@ -809,7 +807,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { /** * Render an closinging double quote char (language specific) */ - function doublequoteclosing() { + public function doublequoteclosing() { global $lang; $this->doc .= $lang['doublequoteclosing']; } @@ -823,7 +821,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @see http://en.wikipedia.org/wiki/CamelCase */ - function camelcaselink($link, $returnonly = false) { + public function camelcaselink($link, $returnonly = false) { if($returnonly) { return $this->internallink($link, $link, null, true); } else { @@ -839,7 +837,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param bool $returnonly whether to return html or write to doc attribute * @return void|string writes to doc attribute or returns html depends on $returnonly */ - function locallink($hash, $name = null, $returnonly = false) { + public function locallink($hash, $name = null, $returnonly = false) { global $ID; $name = $this->_getLinkTitle($name, $hash, $isImage); $hash = $this->_headerToLink($hash); @@ -870,7 +868,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param string $linktype type to set use of headings * @return void|string writes to doc attribute or returns html depends on $returnonly */ - function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') { + public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') { global $conf; global $ID; global $INFO; @@ -961,7 +959,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param bool $returnonly whether to return html or write to doc attribute * @return void|string writes to doc attribute or returns html depends on $returnonly */ - function externallink($url, $name = null, $returnonly = false) { + public function externallink($url, $name = null, $returnonly = false) { global $conf; $name = $this->_getLinkTitle($name, $url, $isImage); @@ -1025,7 +1023,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param bool $returnonly whether to return html or write to doc attribute * @return void|string writes to doc attribute or returns html depends on $returnonly */ - function interwikilink($match, $name = null, $wikiName, $wikiUri, $returnonly = false) { + public function interwikilink($match, $name, $wikiName, $wikiUri, $returnonly = false) { global $conf; $link = array(); @@ -1080,7 +1078,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param bool $returnonly whether to return html or write to doc attribute * @return void|string writes to doc attribute or returns html depends on $returnonly */ - function windowssharelink($url, $name = null, $returnonly = false) { + public function windowssharelink($url, $name = null, $returnonly = false) { global $conf; //simple setup @@ -1120,7 +1118,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param bool $returnonly whether to return html or write to doc attribute * @return void|string writes to doc attribute or returns html depends on $returnonly */ - function emaillink($address, $name = null, $returnonly = false) { + public function emaillink($address, $name = null, $returnonly = false) { global $conf; //simple setup $link = array(); @@ -1172,7 +1170,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param bool $return return HTML instead of adding to $doc * @return void|string writes to doc attribute or returns html depends on $return */ - function internalmedia($src, $title = null, $align = null, $width = null, + public function internalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null, $return = false) { global $ID; if (strpos($src, '#') !== false) { @@ -1186,7 +1184,15 @@ class Doku_Renderer_xhtml extends Doku_Renderer { list($ext, $mime) = mimetype($src, false); if(substr($mime, 0, 5) == 'image' && $render) { - $link['url'] = ml($src, array('id' => $ID, 'cache' => $cache, 'rev'=>$this->_getLastMediaRevisionAt($src)), ($linking == 'direct')); + $link['url'] = ml( + $src, + array( + 'id' => $ID, + 'cache' => $cache, + 'rev' => $this->_getLastMediaRevisionAt($src) + ), + ($linking == 'direct') + ); } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) { // don't link movies $noLink = true; @@ -1194,7 +1200,15 @@ class Doku_Renderer_xhtml extends Doku_Renderer { // add file icons $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); $link['class'] .= ' mediafile mf_'.$class; - $link['url'] = ml($src, array('id' => $ID, 'cache' => $cache , 'rev'=>$this->_getLastMediaRevisionAt($src)), true); + $link['url'] = ml( + $src, + array( + 'id' => $ID, + 'cache' => $cache, + 'rev' => $this->_getLastMediaRevisionAt($src) + ), + true + ); if($exists) $link['title'] .= ' ('.filesize_h(filesize(mediaFN($src))).')'; } @@ -1228,7 +1242,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param bool $return return HTML instead of adding to $doc * @return void|string writes to doc attribute or returns html depends on $return */ - function externalmedia($src, $title = null, $align = null, $width = null, + public function externalmedia($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $linking = null, $return = false) { if(link_isinterwiki($src)){ list($shortcut, $reference) = explode('>', $src, 2); @@ -1275,7 +1289,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @author Andreas Gohr <andi@splitbrain.org> */ - function rss($url, $params) { + public function rss($url, $params) { global $lang; global $conf; @@ -1367,7 +1381,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param int $pos byte position in the original source * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input */ - function table_open($maxcols = null, $numrows = null, $pos = null, $classes = null) { + public function table_open($maxcols = null, $numrows = null, $pos = null, $classes = null) { // initialize the row counter used for classes $this->_counter['row_counter'] = 0; $class = 'table'; @@ -1392,7 +1406,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @param int $pos byte position in the original source */ - function table_close($pos = null) { + public function table_close($pos = null) { $this->doc .= '</table></div>'.DOKU_LF; if($pos !== null) { $this->finishSectionEdit($pos); @@ -1402,42 +1416,42 @@ class Doku_Renderer_xhtml extends Doku_Renderer { /** * Open a table header */ - function tablethead_open() { + public function tablethead_open() { $this->doc .= DOKU_TAB.'<thead>'.DOKU_LF; } /** * Close a table header */ - function tablethead_close() { + public function tablethead_close() { $this->doc .= DOKU_TAB.'</thead>'.DOKU_LF; } /** * Open a table body */ - function tabletbody_open() { + public function tabletbody_open() { $this->doc .= DOKU_TAB.'<tbody>'.DOKU_LF; } /** * Close a table body */ - function tabletbody_close() { + public function tabletbody_close() { $this->doc .= DOKU_TAB.'</tbody>'.DOKU_LF; } /** * Open a table footer */ - function tabletfoot_open() { + public function tabletfoot_open() { $this->doc .= DOKU_TAB.'<tfoot>'.DOKU_LF; } /** * Close a table footer */ - function tabletfoot_close() { + public function tabletfoot_close() { $this->doc .= DOKU_TAB.'</tfoot>'.DOKU_LF; } @@ -1446,7 +1460,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input */ - function tablerow_open($classes = null) { + public function tablerow_open($classes = null) { // initialize the cell counter used for classes $this->_counter['cell_counter'] = 0; $class = 'row'.$this->_counter['row_counter']++; @@ -1460,7 +1474,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { /** * Close a table row */ - function tablerow_close() { + public function tablerow_close() { $this->doc .= DOKU_LF.DOKU_TAB.'</tr>'.DOKU_LF; } @@ -1472,7 +1486,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param int $rowspan * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input */ - function tableheader_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { + public function tableheader_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { $class = 'class="col'.$this->_counter['cell_counter']++; if(!is_null($align)) { $class .= ' '.$align.'align'; @@ -1496,7 +1510,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { /** * Close a table header cell */ - function tableheader_close() { + public function tableheader_close() { $this->doc .= '</th>'; } @@ -1508,7 +1522,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param int $rowspan * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input */ - function tablecell_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { + public function tablecell_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) { $class = 'class="col'.$this->_counter['cell_counter']++; if(!is_null($align)) { $class .= ' '.$align.'align'; @@ -1532,7 +1546,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { /** * Close a table cell */ - function tablecell_close() { + public function tablecell_close() { $this->doc .= '</td>'; } @@ -1542,7 +1556,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @return int The current header level */ - function getLastlevel() { + public function getLastlevel() { return $this->lastlevel; } @@ -1558,7 +1572,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * * @author Andreas Gohr <andi@splitbrain.org> */ - function _formatLink($link) { + public function _formatLink($link) { //make sure the url is XHTML compliant (skip mailto) if(substr($link['url'], 0, 7) != 'mailto:') { $link['url'] = str_replace('&', '&', $link['url']); @@ -1601,7 +1615,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param bool $render should the media be embedded inline or just linked * @return string */ - function _media($src, $title = null, $align = null, $width = null, + public function _media($src, $title = null, $align = null, $width = null, $height = null, $cache = null, $render = true) { $ret = ''; @@ -1625,12 +1639,19 @@ class Doku_Renderer_xhtml extends Doku_Renderer { // return the title of the picture if(!$title) { // just show the sourcename - $title = $this->_xmlEntities(utf8_basename(noNS($src))); + $title = $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src))); } return $title; } //add image tag - $ret .= '<img src="'.ml($src, array('w' => $width, 'h' => $height, 'cache' => $cache, 'rev'=>$this->_getLastMediaRevisionAt($src))).'"'; + $ret .= '<img src="' . ml( + $src, + array( + 'w' => $width, 'h' => $height, + 'cache' => $cache, + 'rev' => $this->_getLastMediaRevisionAt($src) + ) + ) . '"'; $ret .= ' class="media'.$align.'"'; if($title) { @@ -1654,7 +1675,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { if(!$render) { // if the file is not supposed to be rendered // return the title of the file (just the sourcename if there is no title) - return $title ? $title : $this->_xmlEntities(utf8_basename(noNS($src))); + return $title ? $title : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src))); } $att = array(); @@ -1678,7 +1699,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { // return the title of the flash if(!$title) { // just show the sourcename - $title = utf8_basename(noNS($src)); + $title = \dokuwiki\Utf8\PhpString::basename(noNS($src)); } return $this->_xmlEntities($title); } @@ -1699,7 +1720,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { $ret .= $this->_xmlEntities($title); } else { // just show the sourcename - $ret .= $this->_xmlEntities(utf8_basename(noNS($src))); + $ret .= $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src))); } return $ret; @@ -1711,26 +1732,11 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param $string * @return string */ - function _xmlEntities($string) { + public function _xmlEntities($string) { return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); } - /** - * Creates a linkid from a headline - * - * @author Andreas Gohr <andi@splitbrain.org> - * @param string $title The headline title - * @param boolean $create Create a new unique ID? - * @return string - */ - function _headerToLink($title, $create = false) { - if($create) { - return sectionID($title, $this->headers); - } else { - $check = false; - return sectionID($title, $check); - } - } + /** * Construct a title and handle images in titles @@ -1743,7 +1749,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param string $linktype content|navigation * @return string HTML of the title, might be full image tag or just escaped text */ - function _getLinkTitle($title, $default, &$isImage, $id = null, $linktype = 'content') { + public function _getLinkTitle($title, $default, &$isImage, $id = null, $linktype = 'content') { $isImage = false; if(is_array($title)) { $isImage = true; @@ -1768,7 +1774,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param array $img * @return string HTML img tag or similar */ - function _imageTitle($img) { + public function _imageTitle($img) { global $ID; // some fixes on $img['src'] @@ -1803,7 +1809,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param bool $render should the media be embedded inline or just linked * @return array associative array with link config */ - function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) { + public function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) { global $conf; $link = array(); @@ -1832,7 +1838,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param array $atts - additional attributes for the <video> tag * @return string */ - function _video($src, $width, $height, $atts = null) { + public function _video($src, $width, $height, $atts = null) { // prepare width and height if(is_null($atts)) $atts = array(); $atts['width'] = (int) $width; @@ -1876,11 +1882,20 @@ class Doku_Renderer_xhtml extends Doku_Renderer { $url = ml($file, '', true, '&'); $linkType = 'internalmedia'; } - $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(utf8_basename(noNS($file))); + $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file))); $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; // alternative content (just a link to the file) - $fallback .= $this->$linkType($file, $title, null, null, null, $cache = null, $linking = 'linkonly', $return = true); + $fallback .= $this->$linkType( + $file, + $title, + null, + null, + null, + $cache = null, + $linking = 'linkonly', + $return = true + ); } // output each track if any @@ -1906,7 +1921,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @param array $atts - additional attributes for the <audio> tag * @return string */ - function _audio($src, $atts = array()) { + public function _audio($src, $atts = array()) { $files = array(); $isExternal = media_isexternal($src); @@ -1934,11 +1949,20 @@ class Doku_Renderer_xhtml extends Doku_Renderer { $url = ml($file, '', true, '&'); $linkType = 'internalmedia'; } - $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(utf8_basename(noNS($file))); + $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file))); $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL; // alternative content (just a link to the file) - $fallback .= $this->$linkType($file, $title, null, null, null, $cache = null, $linking = 'linkonly', $return = true); + $fallback .= $this->$linkType( + $file, + $title, + null, + null, + null, + $cache = null, + $linking = 'linkonly', + $return = true + ); } // finish @@ -1956,7 +1980,7 @@ class Doku_Renderer_xhtml extends Doku_Renderer { * @access protected * @return string revision ('' for current) */ - function _getLastMediaRevisionAt($media_id){ + protected function _getLastMediaRevisionAt($media_id){ if(!$this->date_at || media_isexternal($media_id)) return ''; $pagelog = new MediaChangeLog($media_id); return $pagelog->getLastRevisionAt($this->date_at); diff --git a/inc/parser/xhtmlsummary.php b/inc/parser/xhtmlsummary.php index 867b71f6a..4641bf836 100644 --- a/inc/parser/xhtmlsummary.php +++ b/inc/parser/xhtmlsummary.php @@ -1,6 +1,4 @@ <?php -if(!defined('DOKU_INC')) die('meh.'); - /** * The summary XHTML form selects either up to the first two paragraphs * it find in a page or the first section (whichever comes first) @@ -20,32 +18,25 @@ class Doku_Renderer_xhtmlsummary extends Doku_Renderer_xhtml { // Namespace these variables to // avoid clashes with parent classes - var $sum_paragraphs = 0; - var $sum_capture = true; - var $sum_inSection = false; - var $sum_summary = ''; - var $sum_pageTitle = false; + protected $sum_paragraphs = 0; + protected $sum_capture = true; + protected $sum_inSection = false; + protected $sum_summary = ''; + protected $sum_pageTitle = false; - function document_start() { + /** @inheritdoc */ + public function document_start() { $this->doc .= DOKU_LF.'<div>'.DOKU_LF; } - function document_end() { + /** @inheritdoc */ + public function document_end() { $this->doc = $this->sum_summary; $this->doc .= DOKU_LF.'</div>'.DOKU_LF; } - // FIXME not supported anymore - function toc_open() { - $this->sum_summary .= $this->doc; - } - - // FIXME not supported anymore - function toc_close() { - $this->doc = ''; - } - - function header($text, $level, $pos) { + /** @inheritdoc */ + public function header($text, $level, $pos) { if ( !$this->sum_pageTitle ) { $this->info['sum_pagetitle'] = $text; $this->sum_pageTitle = true; @@ -55,27 +46,31 @@ class Doku_Renderer_xhtmlsummary extends Doku_Renderer_xhtml { $this->doc .= "</h$level>".DOKU_LF; } - function section_open($level) { + /** @inheritdoc */ + public function section_open($level) { if ( $this->sum_capture ) { $this->sum_inSection = true; } } - function section_close() { + /** @inheritdoc */ + public function section_close() { if ( $this->sum_capture && $this->sum_inSection ) { $this->sum_summary .= $this->doc; $this->sum_capture = false; } } - function p_open() { + /** @inheritdoc */ + public function p_open() { if ( $this->sum_capture && $this->sum_paragraphs < 2 ) { $this->sum_paragraphs++; } parent :: p_open(); } - function p_close() { + /** @inheritdoc */ + public function p_close() { parent :: p_close(); if ( $this->sum_capture && $this->sum_paragraphs >= 2 ) { $this->sum_summary .= $this->doc; diff --git a/inc/parserutils.php b/inc/parserutils.php index ddd21beda..846be54db 100644 --- a/inc/parserutils.php +++ b/inc/parserutils.php @@ -7,7 +7,12 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); +use dokuwiki\Cache\CacheInstructions; +use dokuwiki\Cache\CacheRenderer; +use dokuwiki\ChangeLog\PageChangeLog; +use dokuwiki\Extension\PluginController; +use dokuwiki\Extension\Event; +use dokuwiki\Parsing\Parser; /** * How many pages shall be rendered for getting metadata during one request @@ -74,7 +79,8 @@ function p_wiki_xhtml($id, $rev='', $excuse=true,$date_at=''){ if($rev || $date_at){ if(file_exists($file)){ - $ret = p_render('xhtml',p_get_instructions(io_readWikiPage($file,$id,$rev)),$info,$date_at); //no caching on old revisions + //no caching on old revisions + $ret = p_render('xhtml',p_get_instructions(io_readWikiPage($file,$id,$rev)),$info,$date_at); }elseif($excuse){ $ret = p_locale_xhtml('norev'); } @@ -83,7 +89,7 @@ function p_wiki_xhtml($id, $rev='', $excuse=true,$date_at=''){ $ret = p_cached_output($file,'xhtml',$id); }elseif($excuse){ //check if the page once existed - $changelog = new PageChangelog($id); + $changelog = new PageChangeLog($id); if($changelog->hasRevisions()) { $ret = p_locale_xhtml('onceexisted'); } else { @@ -126,7 +132,7 @@ function p_locale_xhtml($id){ function p_cached_output($file, $format='xhtml', $id='') { global $conf; - $cache = new cache_renderer($id, $file, $format); + $cache = new CacheRenderer($id, $file, $format); if ($cache->useCache()) { $parsed = $cache->retrieveCache(false); if($conf['allowdebug'] && $format=='xhtml') { @@ -166,7 +172,7 @@ function p_cached_instructions($file,$cacheonly=false,$id='') { static $run = null; if(is_null($run)) $run = array(); - $cache = new cache_instructions($id, $file); + $cache = new CacheInstructions($id, $file); if ($cacheonly || $cache->useCache() || (isset($run[$file]) && !defined('DOKU_UNITTEST'))) { return $cache->retrieveCache(); @@ -197,11 +203,8 @@ function p_get_instructions($text){ $modes = p_get_parsermodes(); - // Create the parser - $Parser = new Doku_Parser(); - - // Add the Handler - $Parser->Handler = new Doku_Handler(); + // Create the parser and handler + $Parser = new Parser(new Doku_Handler()); //add modes to parser foreach($modes as $mode){ @@ -209,7 +212,7 @@ function p_get_instructions($text){ } // Do the parsing - trigger_event('PARSER_WIKITEXT_PREPROCESS', $text); + Event::createAndTrigger('PARSER_WIKITEXT_PREPROCESS', $text); $p = $Parser->parse($text); // dbg($p); return $p; @@ -219,7 +222,8 @@ function p_get_instructions($text){ * returns the metadata of a page * * @param string $id The id of the page the metadata should be returned from - * @param string $key The key of the metdata value that shall be read (by default everything) - separate hierarchies by " " like "date created" + * @param string $key The key of the metdata value that shall be read (by default everything) + * separate hierarchies by " " like "date created" * @param int $render If the page should be rendererd - possible values: * METADATA_DONT_RENDER, METADATA_RENDER_USING_SIMPLE_CACHE, METADATA_RENDER_USING_CACHE * METADATA_RENDER_UNLIMITED (also combined with the previous two options), @@ -255,7 +259,7 @@ function p_get_metadata($id, $key='', $render=METADATA_RENDER_USING_CACHE){ if (!$recursion && $render != METADATA_DONT_RENDER && !isset($rendered_pages[$id])&& page_exists($id)){ $recursion = true; - $cachefile = new cache_renderer($id, wikiFN($id), 'metadata'); + $cachefile = new CacheRenderer($id, wikiFN($id), 'metadata'); $do_render = false; if ($render & METADATA_RENDER_UNLIMITED || $render_count < P_GET_METADATA_RENDER_LIMIT) { @@ -306,10 +310,10 @@ function p_get_metadata($id, $key='', $render=METADATA_RENDER_USING_CACHE){ * * @see http://www.dokuwiki.org/devel:metadata#functions_to_get_and_set_metadata * - * @param String $id is the ID of a wiki page - * @param Array $data is an array with key ⇒ value pairs to be set in the metadata - * @param Boolean $render whether or not the page metadata should be generated with the renderer - * @param Boolean $persistent indicates whether or not the particular metadata value will persist through + * @param string $id is the ID of a wiki page + * @param array $data is an array with key ⇒ value pairs to be set in the metadata + * @param boolean $render whether or not the page metadata should be generated with the renderer + * @param boolean $persistent indicates whether or not the particular metadata value will persist through * the next metadata rendering. * @return boolean true on success * @@ -349,7 +353,10 @@ function p_set_metadata($id, $data, $render=false, $persistent=true){ } if($persistent) { if(isset($meta['persistent'][$key][$subkey]) && is_array($meta['persistent'][$key][$subkey])) { - $meta['persistent'][$key][$subkey] = array_replace($meta['persistent'][$key][$subkey], (array)$subvalue); + $meta['persistent'][$key][$subkey] = array_replace( + $meta['persistent'][$key][$subkey], + (array) $subvalue + ); } else { $meta['persistent'][$key][$subkey] = $subvalue; } @@ -361,10 +368,14 @@ function p_set_metadata($id, $data, $render=false, $persistent=true){ // these keys, must have subkeys - a legitimate value must be an array if (is_array($value)) { - $meta['current'][$key] = !empty($meta['current'][$key]) ? array_replace((array)$meta['current'][$key],$value) : $value; + $meta['current'][$key] = !empty($meta['current'][$key]) ? + array_replace((array)$meta['current'][$key],$value) : + $value; if ($persistent) { - $meta['persistent'][$key] = !empty($meta['persistent'][$key]) ? array_replace((array)$meta['persistent'][$key],$value) : $value; + $meta['persistent'][$key] = !empty($meta['persistent'][$key]) ? + array_replace((array)$meta['persistent'][$key],$value) : + $value; } } @@ -428,7 +439,9 @@ function p_read_metadata($id,$cache=false) { if (isset($cache_metadata[(string)$id])) return $cache_metadata[(string)$id]; $file = metaFN($id, '.meta'); - $meta = file_exists($file) ? unserialize(io_readFile($file, false)) : array('current'=>array(),'persistent'=>array()); + $meta = file_exists($file) ? + unserialize(io_readFile($file, false)) : + array('current'=>array(),'persistent'=>array()); if ($cache) { $cache_metadata[(string)$id] = $meta; @@ -481,7 +494,7 @@ function p_render_metadata($id, $orig){ // add an extra key for the event - to tell event handlers the page whose metadata this is $orig['page'] = $id; - $evt = new Doku_Event('PARSER_METADATA_RENDER', $orig); + $evt = new Event('PARSER_METADATA_RENDER', $orig); if ($evt->advise_before()) { // get instructions @@ -542,7 +555,7 @@ function p_get_parsermodes(){ global $PARSER_MODES; $obj = null; foreach($pluginlist as $p){ - /** @var DokuWiki_Syntax_Plugin $obj */ + /** @var \dokuwiki\Extension\SyntaxPlugin $obj */ if(!$obj = plugin_load('syntax',$p)) continue; //attempt to load plugin into $obj $PARSER_MODES[$obj->getType()][] = "plugin_$p"; //register mode type //add to modes @@ -566,7 +579,7 @@ function p_get_parsermodes(){ $std_modes[] = 'multiplyentity'; } foreach($std_modes as $m){ - $class = "Doku_Parser_Mode_$m"; + $class = 'dokuwiki\\Parsing\\ParserMode\\'.ucfirst($m); $obj = new $class(); $modes[] = array( 'sort' => $obj->getSort(), @@ -579,7 +592,7 @@ function p_get_parsermodes(){ $fmt_modes = array('strong','emphasis','underline','monospace', 'subscript','superscript','deleted'); foreach($fmt_modes as $m){ - $obj = new Doku_Parser_Mode_formatting($m); + $obj = new \dokuwiki\Parsing\ParserMode\Formatting($m); $modes[] = array( 'sort' => $obj->getSort(), 'mode' => $m, @@ -588,16 +601,16 @@ function p_get_parsermodes(){ } // add modes which need files - $obj = new Doku_Parser_Mode_smiley(array_keys(getSmileys())); + $obj = new \dokuwiki\Parsing\ParserMode\Smiley(array_keys(getSmileys())); $modes[] = array('sort' => $obj->getSort(), 'mode' => 'smiley','obj' => $obj ); - $obj = new Doku_Parser_Mode_acronym(array_keys(getAcronyms())); + $obj = new \dokuwiki\Parsing\ParserMode\Acronym(array_keys(getAcronyms())); $modes[] = array('sort' => $obj->getSort(), 'mode' => 'acronym','obj' => $obj ); - $obj = new Doku_Parser_Mode_entity(array_keys(getEntities())); + $obj = new \dokuwiki\Parsing\ParserMode\Entity(array_keys(getEntities())); $modes[] = array('sort' => $obj->getSort(), 'mode' => 'entity','obj' => $obj ); // add optional camelcase mode if($conf['camelcase']){ - $obj = new Doku_Parser_Mode_camelcaselink(); + $obj = new \dokuwiki\Parsing\ParserMode\Camelcaselink(); $modes[] = array('sort' => $obj->getSort(), 'mode' => 'camelcaselink','obj' => $obj ); } @@ -666,7 +679,7 @@ function p_render($mode,$instructions,&$info,$date_at=''){ // Post process and return the output $data = array($mode,& $Renderer->doc); - trigger_event('RENDERER_CONTENT_POSTPROCESS',$data); + Event::createAndTrigger('RENDERER_CONTENT_POSTPROCESS',$data); return $Renderer->doc; } @@ -680,7 +693,7 @@ function p_render($mode,$instructions,&$info,$date_at=''){ * @author Christopher Smith <chris@jalakai.co.uk> */ function p_get_renderer($mode) { - /** @var Doku_Plugin_Controller $plugin_controller */ + /** @var PluginController $plugin_controller */ global $conf, $plugin_controller; $rname = !empty($conf['renderer_'.$mode]) ? $conf['renderer_'.$mode] : $mode; diff --git a/inc/plugincontroller.class.php b/inc/plugincontroller.class.php deleted file mode 100644 index fd8cd9663..000000000 --- a/inc/plugincontroller.class.php +++ /dev/null @@ -1,347 +0,0 @@ -<?php -/** - * Class to encapsulate access to dokuwiki plugins - * - * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) - * @author Christopher Smith <chris@jalakai.co.uk> - */ - -// plugin related constants -if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); - -class Doku_Plugin_Controller { - - protected $list_bytype = array(); - protected $tmp_plugins = array(); - protected $plugin_cascade = array('default'=>array(),'local'=>array(),'protected'=>array()); - protected $last_local_config_file = ''; - - /** - * Populates the master list of plugins - */ - public function __construct() { - $this->loadConfig(); - $this->_populateMasterList(); - } - - /** - * Returns a list of available plugins of given type - * - * @param $type string, plugin_type name; - * the type of plugin to return, - * use empty string for all types - * @param $all bool; - * false to only return enabled plugins, - * true to return both enabled and disabled plugins - * - * @return array of - * - plugin names when $type = '' - * - or plugin component names when a $type is given - * - * @author Andreas Gohr <andi@splitbrain.org> - */ - public function getList($type='',$all=false){ - - // request the complete list - if (!$type) { - return $all ? array_keys($this->tmp_plugins) : array_keys(array_filter($this->tmp_plugins)); - } - - if (!isset($this->list_bytype[$type]['enabled'])) { - $this->list_bytype[$type]['enabled'] = $this->_getListByType($type,true); - } - if ($all && !isset($this->list_bytype[$type]['disabled'])) { - $this->list_bytype[$type]['disabled'] = $this->_getListByType($type,false); - } - - return $all ? array_merge($this->list_bytype[$type]['enabled'],$this->list_bytype[$type]['disabled']) : $this->list_bytype[$type]['enabled']; - } - - /** - * Loads the given plugin and creates an object of it - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param $type string type of plugin to load - * @param $name string name of the plugin to load - * @param $new bool true to return a new instance of the plugin, false to use an already loaded instance - * @param $disabled bool true to load even disabled plugins - * @return DokuWiki_PluginInterface|null the plugin object or null on failure - */ - public function load($type,$name,$new=false,$disabled=false){ - - //we keep all loaded plugins available in global scope for reuse - global $DOKU_PLUGINS; - - list($plugin, /* $component */) = $this->_splitName($name); - - // check if disabled - if(!$disabled && $this->isdisabled($plugin)){ - return null; - } - - $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; - } else { - return $DOKU_PLUGINS[$type][$name]; - } - } - - //construct class and instantiate - if (!class_exists($class, true)) { - - # the plugin might be in the wrong directory - $dir = $this->get_directory($plugin); - $inf = confToHash(DOKU_PLUGIN."$dir/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; - return $DOKU_PLUGINS[$type][$name]; - } - - /** - * Whether plugin is disabled - * - * @param string $plugin name of plugin - * @return bool true disabled, false enabled - */ - public function isdisabled($plugin) { - return empty($this->tmp_plugins[$plugin]); - } - - /** - * Disable the plugin - * - * @param string $plugin name of plugin - * @return bool true saving succeed, false saving failed - */ - public function disable($plugin) { - if(array_key_exists($plugin,$this->plugin_cascade['protected'])) return false; - $this->tmp_plugins[$plugin] = 0; - return $this->saveList(); - } - - /** - * Enable the plugin - * - * @param string $plugin name of plugin - * @return bool true saving succeed, false saving failed - */ - public function enable($plugin) { - if(array_key_exists($plugin,$this->plugin_cascade['protected'])) return false; - $this->tmp_plugins[$plugin] = 1; - return $this->saveList(); - } - - /** - * Returns directory name of plugin - * - * @param string $plugin name of plugin - * @return string name of directory - */ - public function get_directory($plugin) { - return $plugin; - } - - /** - * Returns cascade of the config files - * - * @return array with arrays of plugin configs - */ - public function getCascade() { - return $this->plugin_cascade; - } - - protected function _populateMasterList() { - global $conf; - - if ($dh = @opendir(DOKU_PLUGIN)) { - $all_plugins = array(); - while (false !== ($plugin = readdir($dh))) { - if ($plugin[0] == '.') continue; // skip hidden entries - if (is_file(DOKU_PLUGIN.$plugin)) continue; // skip files, we're only interested in directories - - if (array_key_exists($plugin,$this->tmp_plugins) && $this->tmp_plugins[$plugin] == 0){ - $all_plugins[$plugin] = 0; - - } elseif ((array_key_exists($plugin,$this->tmp_plugins) && $this->tmp_plugins[$plugin] == 1)) { - $all_plugins[$plugin] = 1; - } else { - $all_plugins[$plugin] = 1; - } - } - $this->tmp_plugins = $all_plugins; - if (!file_exists($this->last_local_config_file)) { - $this->saveList(true); - } - } - } - - /** - * Includes the plugin config $files - * and returns the entries of the $plugins array set in these files - * - * @param array $files list of files to include, latter overrides previous - * @return array with entries of the $plugins arrays of the included files - */ - protected function checkRequire($files) { - $plugins = array(); - foreach($files as $file) { - if(file_exists($file)) { - include_once($file); - } - } - return $plugins; - } - - /** - * Save the current list of plugins - * - * @param bool $forceSave; - * false to save only when config changed - * true to always save - * @return bool true saving succeed, false saving failed - */ - protected function saveList($forceSave = false) { - global $conf; - - if (empty($this->tmp_plugins)) return false; - - // Rebuild list of local settings - $local_plugins = $this->rebuildLocal(); - if($local_plugins != $this->plugin_cascade['local'] || $forceSave) { - $file = $this->last_local_config_file; - $out = "<?php\n/*\n * Local plugin enable/disable settings\n * Auto-generated through plugin/extension manager\n *\n". - " * NOTE: Plugins will not be added to this file unless there is a need to override a default setting. Plugins are\n". - " * enabled by default.\n */\n"; - foreach ($local_plugins as $plugin => $value) { - $out .= "\$plugins['$plugin'] = $value;\n"; - } - // backup current file (remove any existing backup) - if (file_exists($file)) { - $backup = $file.'.bak'; - if (file_exists($backup)) @unlink($backup); - if (!@copy($file,$backup)) return false; - if (!empty($conf['fperm'])) chmod($backup, $conf['fperm']); - } - //check if can open for writing, else restore - return io_saveFile($file,$out); - } - return false; - } - - /** - * Rebuild the set of local plugins - * - * @return array array of plugins to be saved in end($config_cascade['plugins']['local']) - */ - protected function rebuildLocal() { - //assign to local variable to avoid overwriting - $backup = $this->tmp_plugins; - //Can't do anything about protected one so rule them out completely - $local_default = array_diff_key($backup,$this->plugin_cascade['protected']); - //Diff between local+default and default - //gives us the ones we need to check and save - $diffed_ones = array_diff_key($local_default,$this->plugin_cascade['default']); - //The ones which we are sure of (list of 0s not in default) - $sure_plugins = array_filter($diffed_ones,array($this,'negate')); - //the ones in need of diff - $conflicts = array_diff_key($local_default,$diffed_ones); - //The final list - return array_merge($sure_plugins,array_diff_assoc($conflicts,$this->plugin_cascade['default'])); - } - - /** - * Build the list of plugins and cascade - * - */ - protected function loadConfig() { - global $config_cascade; - foreach(array('default','protected') as $type) { - if(array_key_exists($type,$config_cascade['plugins'])) - $this->plugin_cascade[$type] = $this->checkRequire($config_cascade['plugins'][$type]); - } - $local = $config_cascade['plugins']['local']; - $this->last_local_config_file = array_pop($local); - $this->plugin_cascade['local'] = $this->checkRequire(array($this->last_local_config_file)); - if(is_array($local)) { - $this->plugin_cascade['default'] = array_merge($this->plugin_cascade['default'],$this->checkRequire($local)); - } - $this->tmp_plugins = array_merge($this->plugin_cascade['default'],$this->plugin_cascade['local'],$this->plugin_cascade['protected']); - } - - /** - * Returns a list of available plugin components of given type - * - * @param string $type plugin_type name; the type of plugin to return, - * @param bool $enabled true to return enabled plugins, - * false to return disabled plugins - * @return array of plugin components of requested type - */ - protected function _getListByType($type, $enabled) { - $master_list = $enabled ? array_keys(array_filter($this->tmp_plugins)) : array_keys(array_filter($this->tmp_plugins,array($this,'negate'))); - $plugins = array(); - - foreach ($master_list as $plugin) { - - $basedir = $this->get_directory($plugin); - if (file_exists(DOKU_PLUGIN."$basedir/$type.php")){ - $plugins[] = $plugin; - continue; - } - - $typedir = DOKU_PLUGIN."$basedir/$type/"; - if (is_dir($typedir)) { - if ($dp = opendir($typedir)) { - while (false !== ($component = readdir($dp))) { - if (substr($component,0,1) == '.' || strtolower(substr($component, -4)) != ".php") continue; - if (is_file($typedir.$component)) { - $plugins[] = $plugin.'_'.substr($component, 0, -4); - } - } - closedir($dp); - } - } - - }//foreach - - return $plugins; - } - - /** - * Split name in a plugin name and a component name - * - * @param string $name - * @return array with - * - plugin name - * - and component name when available, otherwise empty string - */ - protected function _splitName($name) { - if (array_search($name, array_keys($this->tmp_plugins)) === false) { - return explode('_',$name,2); - } - - return array($name,''); - } - - /** - * Returns inverse boolean value of the input - * - * @param mixed $input - * @return bool inversed boolean value of input - */ - protected function negate($input) { - return !(bool) $input; - } -} diff --git a/inc/pluginutils.php b/inc/pluginutils.php index 0cd113b14..f1ad82fe6 100644 --- a/inc/pluginutils.php +++ b/inc/pluginutils.php @@ -7,8 +7,13 @@ */ // plugin related constants +use dokuwiki\Extension\AdminPlugin; +use dokuwiki\Extension\PluginController; +use dokuwiki\Extension\PluginInterface; + if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); -// note that only [a-z0-9]+ is officially supported, this is only to support plugins that don't follow these conventions, too +// note that only [a-z0-9]+ is officially supported, +// this is only to support plugins that don't follow these conventions, too if(!defined('DOKU_PLUGIN_NAME_REGEX')) define('DOKU_PLUGIN_NAME_REGEX', '[a-zA-Z0-9\x7f-\xff]+'); /** @@ -23,7 +28,7 @@ if(!defined('DOKU_PLUGIN_NAME_REGEX')) define('DOKU_PLUGIN_NAME_REGEX', '[a-zA-Z * @return array with plugin names or plugin component names */ function plugin_list($type='',$all=false) { - /** @var $plugin_controller Doku_Plugin_Controller */ + /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->getList($type,$all); } @@ -37,10 +42,10 @@ function plugin_list($type='',$all=false) { * @param $name string name of the plugin to load * @param $new bool true to return a new instance of the plugin, false to use an already loaded instance * @param $disabled bool true to load even disabled plugins - * @return DokuWiki_PluginInterface|null the plugin object or null on failure + * @return PluginInterface|null the plugin object or null on failure */ function plugin_load($type,$name,$new=false,$disabled=false) { - /** @var $plugin_controller Doku_Plugin_Controller */ + /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->load($type,$name,$new,$disabled); } @@ -52,7 +57,7 @@ function plugin_load($type,$name,$new=false,$disabled=false) { * @return bool true disabled, false enabled */ function plugin_isdisabled($plugin) { - /** @var $plugin_controller Doku_Plugin_Controller */ + /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->isdisabled($plugin); } @@ -64,7 +69,7 @@ function plugin_isdisabled($plugin) { * @return bool true saving succeed, false saving failed */ function plugin_enable($plugin) { - /** @var $plugin_controller Doku_Plugin_Controller */ + /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->enable($plugin); } @@ -76,7 +81,7 @@ function plugin_enable($plugin) { * @return bool true saving succeed, false saving failed */ function plugin_disable($plugin) { - /** @var $plugin_controller Doku_Plugin_Controller */ + /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->disable($plugin); } @@ -86,11 +91,11 @@ function plugin_disable($plugin) { * * @param string $plugin name of plugin * @return string name of directory + * @deprecated 2018-07-20 */ function plugin_directory($plugin) { - /** @var $plugin_controller Doku_Plugin_Controller */ - global $plugin_controller; - return $plugin_controller->get_directory($plugin); + dbg_deprecated('$plugin directly'); + return $plugin; } /** @@ -99,7 +104,7 @@ function plugin_directory($plugin) { * @return array with arrays of plugin configs */ function plugin_getcascade() { - /** @var $plugin_controller Doku_Plugin_Controller */ + /** @var $plugin_controller PluginController */ global $plugin_controller; return $plugin_controller->getCascade(); } @@ -120,7 +125,7 @@ function plugin_getRequestAdminPlugin(){ $pluginlist = plugin_list('admin'); if (in_array($page, $pluginlist)) { // attempt to load the plugin - /** @var $admin_plugin DokuWiki_Admin_Plugin */ + /** @var $admin_plugin AdminPlugin */ $admin_plugin = plugin_load('admin', $page); // verify if ($admin_plugin && !$admin_plugin->isAccessibleByCurrentUser()) { diff --git a/inc/search.php b/inc/search.php index e4d9c3caf..27efc65fe 100644 --- a/inc/search.php +++ b/inc/search.php @@ -6,8 +6,6 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); - /** * Recurse directory * @@ -20,7 +18,8 @@ if(!defined('DOKU_INC')) die('meh.'); * @param array $opts option array will be given to the Callback * @param string $dir Current directory beyond $base * @param int $lvl Recursion Level - * @param mixed $sort 'natural' to use natural order sorting (default); 'date' to sort by filemtime; leave empty to skip sorting. + * @param mixed $sort 'natural' to use natural order sorting (default); + * 'date' to sort by filemtime; leave empty to skip sorting. * @author Andreas Gohr <andi@splitbrain.org> */ function search(&$data,$base,$func,$opts,$dir='',$lvl=1,$sort='natural'){ @@ -212,7 +211,7 @@ function search_media(&$data,$base,$file,$type,$lvl,$opts){ return false; } - $info['file'] = utf8_basename($file); + $info['file'] = \dokuwiki\Utf8\PhpString::basename($file); $info['size'] = filesize($base.'/'.$file); $info['mtime'] = filemtime($base.'/'.$file); $info['writable'] = is_writable($base.'/'.$file); @@ -479,7 +478,8 @@ function search_universal(&$data,$base,$file,$type,$lvl,$opts){ // are we done here maybe? if($type == 'd'){ if(empty($opts['listdirs'])) return $return; - if(empty($opts['skipacl']) && !empty($opts['sneakyacl']) && $item['perm'] < AUTH_READ) return false; //neither list nor recurse + //neither list nor recurse forbidden items: + if(empty($opts['skipacl']) && !empty($opts['sneakyacl']) && $item['perm'] < AUTH_READ) return false; if(!empty($opts['dirmatch']) && !preg_match('/'.$opts['dirmatch'].'/',$file)) return $return; if(!empty($opts['nsmatch']) && !preg_match('/'.$opts['nsmatch'].'/',$item['ns'])) return $return; }else{ @@ -497,7 +497,7 @@ function search_universal(&$data,$base,$file,$type,$lvl,$opts){ $item['open'] = $return; if(!empty($opts['meta'])){ - $item['file'] = utf8_basename($file); + $item['file'] = \dokuwiki\Utf8\PhpString::basename($file); $item['size'] = filesize($base.'/'.$file); $item['mtime'] = filemtime($base.'/'.$file); $item['rev'] = $item['mtime']; diff --git a/inc/subscription.php b/inc/subscription.php deleted file mode 100644 index 74bec656d..000000000 --- a/inc/subscription.php +++ /dev/null @@ -1,693 +0,0 @@ -<?php -/** - * Class for handling (email) subscriptions - * - * @author Adrian Lang <lang@cosmocode.de> - * @author Andreas Gohr <andi@splitbrain.org> - * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) - */ -class Subscription { - - /** - * Check if subscription system is enabled - * - * @return bool - */ - public function isenabled() { - return actionOK('subscribe'); - } - - /** - * Return the subscription meta file for the given ID - * - * @author Adrian Lang <lang@cosmocode.de> - * - * @param string $id The target page or namespace, specified by id; Namespaces - * are identified by appending a colon. - * @return string - */ - protected function file($id) { - $meta_fname = '.mlist'; - if((substr($id, -1, 1) === ':')) { - $meta_froot = getNS($id); - $meta_fname = '/'.$meta_fname; - } else { - $meta_froot = $id; - } - return metaFN((string) $meta_froot, $meta_fname); - } - - /** - * Lock subscription info - * - * We don't use io_lock() her because we do not wait for the lock and use a larger stale time - * - * @author Adrian Lang <lang@cosmocode.de> - * @param string $id The target page or namespace, specified by id; Namespaces - * are identified by appending a colon. - * @return bool true, if you got a succesful lock - */ - protected function lock($id) { - global $conf; - - $lock = $conf['lockdir'].'/_subscr_'.md5($id).'.lock'; - - if(is_dir($lock) && time() - @filemtime($lock) > 60 * 5) { - // looks like a stale lock - remove it - @rmdir($lock); - } - - // try creating the lock directory - if(!@mkdir($lock, $conf['dmode'])) { - return false; - } - - if(!empty($conf['dperm'])) chmod($lock, $conf['dperm']); - return true; - } - - /** - * Unlock subscription info - * - * @author Adrian Lang <lang@cosmocode.de> - * @param string $id The target page or namespace, specified by id; Namespaces - * are identified by appending a colon. - * @return bool - */ - protected function unlock($id) { - global $conf; - $lock = $conf['lockdir'].'/_subscr_'.md5($id).'.lock'; - return @rmdir($lock); - } - - /** - * Construct a regular expression for parsing a subscription definition line - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string|array $user - * @param string|array $style - * @param string|array $data - * @return string complete regexp including delimiters - * @throws Exception when no data is passed - */ - protected function buildregex($user = null, $style = null, $data = null) { - // always work with arrays - $user = (array) $user; - $style = (array) $style; - $data = (array) $data; - - // clean - $user = array_filter(array_map('trim', $user)); - $style = array_filter(array_map('trim', $style)); - $data = array_filter(array_map('trim', $data)); - - // user names are encoded - $user = array_map('auth_nameencode', $user); - - // quote - $user = array_map('preg_quote_cb', $user); - $style = array_map('preg_quote_cb', $style); - $data = array_map('preg_quote_cb', $data); - - // join - $user = join('|', $user); - $style = join('|', $style); - $data = join('|', $data); - - // any data at all? - if($user.$style.$data === '') throw new Exception('no data passed'); - - // replace empty values, set which ones are optional - $sopt = ''; - $dopt = ''; - if($user === '') { - $user = '\S+'; - } - if($style === '') { - $style = '\S+'; - $sopt = '?'; - } - if($data === '') { - $data = '\S+'; - $dopt = '?'; - } - - // assemble - return "/^($user)(?:\\s+($style))$sopt(?:\\s+($data))$dopt$/"; - } - - /** - * Recursively search for matching subscriptions - * - * This function searches all relevant subscription files for a page or - * namespace. - * - * @author Adrian Lang <lang@cosmocode.de> - * - * @param string $page The target object’s (namespace or page) id - * @param string|array $user - * @param string|array $style - * @param string|array $data - * @return array - */ - public function subscribers($page, $user = null, $style = null, $data = null) { - if(!$this->isenabled()) return array(); - - // Construct list of files which may contain relevant subscriptions. - $files = array(':' => $this->file(':')); - do { - $files[$page] = $this->file($page); - $page = getNS(rtrim($page, ':')).':'; - } while($page !== ':'); - - $re = $this->buildregex($user, $style, $data); - - // Handle files. - $result = array(); - foreach($files as $target => $file) { - if(!file_exists($file)) continue; - - $lines = file($file); - foreach($lines as $line) { - // fix old style subscription files - if(strpos($line, ' ') === false) $line = trim($line)." every\n"; - - // check for matching entries - if(!preg_match($re, $line, $m)) continue; - - $u = rawurldecode($m[1]); // decode the user name - if(!isset($result[$target])) $result[$target] = array(); - $result[$target][$u] = array($m[2], $m[3]); // add to result - } - } - return array_reverse($result); - } - - /** - * Adds a new subscription for the given page or namespace - * - * This will automatically overwrite any existent subscription for the given user on this - * *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces. - * - * @param string $id The target page or namespace, specified by id; Namespaces - * are identified by appending a colon. - * @param string $user - * @param string $style - * @param string $data - * @throws Exception when user or style is empty - * @return bool - */ - public function add($id, $user, $style, $data = '') { - if(!$this->isenabled()) return false; - - // delete any existing subscription - $this->remove($id, $user); - - $user = auth_nameencode(trim($user)); - $style = trim($style); - $data = trim($data); - - if(!$user) throw new Exception('no subscription user given'); - if(!$style) throw new Exception('no subscription style given'); - if(!$data) $data = time(); //always add current time for new subscriptions - - $line = "$user $style $data\n"; - $file = $this->file($id); - return io_saveFile($file, $line, true); - } - - /** - * Removes a subscription for the given page or namespace - * - * This removes all subscriptions matching the given criteria on the given page or - * namespace. It will *not* modify any subscriptions that may exist in higher - * namespaces. - * - * @param string $id The target object’s (namespace or page) id - * @param string|array $user - * @param string|array $style - * @param string|array $data - * @return bool - */ - public function remove($id, $user = null, $style = null, $data = null) { - if(!$this->isenabled()) return false; - - $file = $this->file($id); - if(!file_exists($file)) return true; - - $re = $this->buildregex($user, $style, $data); - return io_deleteFromFile($file, $re, true); - } - - /** - * Get data for $INFO['subscribed'] - * - * $INFO['subscribed'] is either false if no subscription for the current page - * and user is in effect. Else it contains an array of arrays with the fields - * “target”, “style”, and optionally “data”. - * - * @param string $id Page ID, defaults to global $ID - * @param string $user User, defaults to $_SERVER['REMOTE_USER'] - * @return array - * @author Adrian Lang <lang@cosmocode.de> - */ - function user_subscription($id = '', $user = '') { - if(!$this->isenabled()) return false; - - global $ID; - /** @var Input $INPUT */ - global $INPUT; - if(!$id) $id = $ID; - if(!$user) $user = $INPUT->server->str('REMOTE_USER'); - - $subs = $this->subscribers($id, $user); - if(!count($subs)) return false; - - $result = array(); - foreach($subs as $target => $info) { - $result[] = array( - 'target' => $target, - 'style' => $info[$user][0], - 'data' => $info[$user][1] - ); - } - - return $result; - } - - /** - * Send digest and list subscriptions - * - * This sends mails to all subscribers that have a subscription for namespaces above - * the given page if the needed $conf['subscribe_time'] has passed already. - * - * This function is called form lib/exe/indexer.php - * - * @param string $page - * @return int number of sent mails - */ - public function send_bulk($page) { - if(!$this->isenabled()) return 0; - - /** @var DokuWiki_Auth_Plugin $auth */ - global $auth; - global $conf; - global $USERINFO; - /** @var Input $INPUT */ - global $INPUT; - $count = 0; - - $subscriptions = $this->subscribers($page, null, array('digest', 'list')); - - // remember current user info - $olduinfo = $USERINFO; - $olduser = $INPUT->server->str('REMOTE_USER'); - - foreach($subscriptions as $target => $users) { - if(!$this->lock($target)) continue; - - foreach($users as $user => $info) { - list($style, $lastupdate) = $info; - - $lastupdate = (int) $lastupdate; - if($lastupdate + $conf['subscribe_time'] > time()) { - // Less than the configured time period passed since last - // update. - continue; - } - - // Work as the user to make sure ACLs apply correctly - $USERINFO = $auth->getUserData($user); - $INPUT->server->set('REMOTE_USER',$user); - if($USERINFO === false) continue; - if(!$USERINFO['mail']) continue; - - if(substr($target, -1, 1) === ':') { - // subscription target is a namespace, get all changes within - $changes = getRecentsSince($lastupdate, null, getNS($target)); - } else { - // single page subscription, check ACL ourselves - if(auth_quickaclcheck($target) < AUTH_READ) continue; - $meta = p_get_metadata($target); - $changes = array($meta['last_change']); - } - - // Filter out pages only changed in small and own edits - $change_ids = array(); - foreach($changes as $rev) { - $n = 0; - while(!is_null($rev) && $rev['date'] >= $lastupdate && - ($INPUT->server->str('REMOTE_USER') === $rev['user'] || - $rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)) { - $pagelog = new PageChangeLog($rev['id']); - $rev = $pagelog->getRevisions($n++, 1); - $rev = (count($rev) > 0) ? $rev[0] : null; - } - - if(!is_null($rev) && $rev['date'] >= $lastupdate) { - // Some change was not a minor one and not by myself - $change_ids[] = $rev['id']; - } - } - - // send it - if($style === 'digest') { - foreach($change_ids as $change_id) { - $this->send_digest( - $USERINFO['mail'], $change_id, - $lastupdate - ); - $count++; - } - } elseif($style === 'list') { - $this->send_list($USERINFO['mail'], $change_ids, $target); - $count++; - } - // TODO: Handle duplicate subscriptions. - - // Update notification time. - $this->add($target, $user, $style, time()); - } - $this->unlock($target); - } - - // restore current user info - $USERINFO = $olduinfo; - $INPUT->server->set('REMOTE_USER',$olduser); - return $count; - } - - /** - * Send the diff for some page change - * - * @param string $subscriber_mail The target mail address - * @param string $template Mail template ('subscr_digest', 'subscr_single', 'mailtext', ...) - * @param string $id Page for which the notification is - * @param int|null $rev Old revision if any - * @param string $summary Change summary if any - * @return bool true if successfully sent - */ - public function send_diff($subscriber_mail, $template, $id, $rev = null, $summary = '') { - global $DIFF_INLINESTYLES; - - // prepare replacements (keys not set in hrep will be taken from trep) - $trep = array( - 'PAGE' => $id, - 'NEWPAGE' => wl($id, '', true, '&'), - 'SUMMARY' => $summary, - 'SUBSCRIBE' => wl($id, array('do' => 'subscribe'), true, '&') - ); - $hrep = array(); - - if($rev) { - $subject = 'changed'; - $trep['OLDPAGE'] = wl($id, "rev=$rev", true, '&'); - - $old_content = rawWiki($id, $rev); - $new_content = rawWiki($id); - - $df = new Diff(explode("\n", $old_content), - explode("\n", $new_content)); - $dformat = new UnifiedDiffFormatter(); - $tdiff = $dformat->format($df); - - $DIFF_INLINESTYLES = true; - $df = new Diff(explode("\n", $old_content), - explode("\n", $new_content)); - $dformat = new InlineDiffFormatter(); - $hdiff = $dformat->format($df); - $hdiff = '<table>'.$hdiff.'</table>'; - $DIFF_INLINESTYLES = false; - } else { - $subject = 'newpage'; - $trep['OLDPAGE'] = '---'; - $tdiff = rawWiki($id); - $hdiff = nl2br(hsc($tdiff)); - } - - $trep['DIFF'] = $tdiff; - $hrep['DIFF'] = $hdiff; - - $headers = array('Message-Id' => $this->getMessageID($id)); - if ($rev) { - $headers['In-Reply-To'] = $this->getMessageID($id, $rev); - } - - return $this->send( - $subscriber_mail, $subject, $id, - $template, $trep, $hrep, $headers - ); - } - - /** - * Send the diff for some media change - * - * @fixme this should embed thumbnails of images in HTML version - * - * @param string $subscriber_mail The target mail address - * @param string $template Mail template ('uploadmail', ...) - * @param string $id Media file for which the notification is - * @param int|bool $rev Old revision if any - */ - public function send_media_diff($subscriber_mail, $template, $id, $rev = false) { - global $conf; - - $file = mediaFN($id); - list($mime, /* $ext */) = mimetype($id); - - $trep = array( - 'MIME' => $mime, - 'MEDIA' => ml($id,'',true,'&',true), - 'SIZE' => filesize_h(filesize($file)), - ); - - if ($rev && $conf['mediarevisions']) { - $trep['OLD'] = ml($id, "rev=$rev", true, '&', true); - } else { - $trep['OLD'] = '---'; - } - - $headers = array('Message-Id' => $this->getMessageID($id, @filemtime($file))); - if ($rev) { - $headers['In-Reply-To'] = $this->getMessageID($id, $rev); - } - - $this->send($subscriber_mail, 'upload', $id, $template, $trep, null, $headers); - - } - - /** - * Send a notify mail on new registration - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string $login login name of the new user - * @param string $fullname full name of the new user - * @param string $email email address of the new user - * @return bool true if a mail was sent - */ - public function send_register($login, $fullname, $email) { - global $conf; - if(empty($conf['registernotify'])) return false; - - $trep = array( - 'NEWUSER' => $login, - 'NEWNAME' => $fullname, - 'NEWEMAIL' => $email, - ); - - return $this->send( - $conf['registernotify'], - 'new_user', - $login, - 'registermail', - $trep - ); - } - - /** - * Send a digest mail - * - * Sends a digest mail showing a bunch of changes of a single page. Basically the same as send_diff() - * but determines the last known revision first - * - * @author Adrian Lang <lang@cosmocode.de> - * - * @param string $subscriber_mail The target mail address - * @param string $id The ID - * @param int $lastupdate Time of the last notification - * @return bool - */ - protected function send_digest($subscriber_mail, $id, $lastupdate) { - $pagelog = new PageChangeLog($id); - $n = 0; - do { - $rev = $pagelog->getRevisions($n++, 1); - $rev = (count($rev) > 0) ? $rev[0] : null; - } while(!is_null($rev) && $rev > $lastupdate); - - return $this->send_diff( - $subscriber_mail, - 'subscr_digest', - $id, $rev - ); - } - - /** - * Send a list mail - * - * Sends a list mail showing a list of changed pages. - * - * @author Adrian Lang <lang@cosmocode.de> - * - * @param string $subscriber_mail The target mail address - * @param array $ids Array of ids - * @param string $ns_id The id of the namespace - * @return bool true if a mail was sent - */ - protected function send_list($subscriber_mail, $ids, $ns_id) { - if(count($ids) === 0) return false; - - $tlist = ''; - $hlist = '<ul>'; - foreach($ids as $id) { - $link = wl($id, array(), true); - $tlist .= '* '.$link.NL; - $hlist .= '<li><a href="'.$link.'">'.hsc($id).'</a></li>'.NL; - } - $hlist .= '</ul>'; - - $id = prettyprint_id($ns_id); - $trep = array( - 'DIFF' => rtrim($tlist), - 'PAGE' => $id, - 'SUBSCRIBE' => wl($id, array('do' => 'subscribe'), true, '&') - ); - $hrep = array( - 'DIFF' => $hlist - ); - - return $this->send( - $subscriber_mail, - 'subscribe_list', - $ns_id, - 'subscr_list', $trep, $hrep - ); - } - - /** - * Helper function for sending a mail - * - * @author Adrian Lang <lang@cosmocode.de> - * - * @param string $subscriber_mail The target mail address - * @param string $subject The lang id of the mail subject (without the - * prefix “mail_”) - * @param string $context The context of this mail, eg. page or namespace id - * @param string $template The name of the mail template - * @param array $trep Predefined parameters used to parse the - * template (in text format) - * @param array $hrep Predefined parameters used to parse the - * template (in HTML format), null to default to $trep - * @param array $headers Additional mail headers in the form 'name' => 'value' - * @return bool - */ - protected function send($subscriber_mail, $subject, $context, $template, $trep, $hrep = null, $headers = array()) { - global $lang; - global $conf; - - $text = rawLocale($template); - $subject = $lang['mail_'.$subject].' '.$context; - $mail = new Mailer(); - $mail->bcc($subscriber_mail); - $mail->subject($subject); - $mail->setBody($text, $trep, $hrep); - if(in_array($template, array('subscr_list', 'subscr_digest'))){ - $mail->from($conf['mailfromnobody']); - } - if(isset($trep['SUBSCRIBE'])) { - $mail->setHeader('List-Unsubscribe', '<'.$trep['SUBSCRIBE'].'>', false); - } - - foreach ($headers as $header => $value) { - $mail->setHeader($header, $value); - } - - return $mail->send(); - } - - /** - * Get a valid message id for a certain $id and revision (or the current revision) - * - * @param string $id The id of the page (or media file) the message id should be for - * @param string $rev The revision of the page, set to the current revision of the page $id if not set - * @return string - */ - protected function getMessageID($id, $rev = null) { - static $listid = null; - if (is_null($listid)) { - $server = parse_url(DOKU_URL, PHP_URL_HOST); - $listid = join('.', array_reverse(explode('/', DOKU_BASE))).$server; - $listid = urlencode($listid); - $listid = strtolower(trim($listid, '.')); - } - - if (is_null($rev)) { - $rev = @filemtime(wikiFN($id)); - } - - return "<$id?rev=$rev@$listid>"; - } - - /** - * Default callback for COMMON_NOTIFY_ADDRESSLIST - * - * Aggregates all email addresses of user who have subscribed the given page with 'every' style - * - * @author Steven Danz <steven-danz@kc.rr.com> - * @author Adrian Lang <lang@cosmocode.de> - * - * @todo move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead, - * use an array for the addresses within it - * - * @param array &$data Containing the entries: - * - $id (the page id), - * - $self (whether the author should be notified, - * - $addresslist (current email address list) - * - $replacements (array of additional string substitutions, @KEY@ to be replaced by value) - */ - public function notifyaddresses(&$data) { - if(!$this->isenabled()) return; - - /** @var DokuWiki_Auth_Plugin $auth */ - global $auth; - global $conf; - /** @var Input $INPUT */ - global $INPUT; - - $id = $data['id']; - $self = $data['self']; - $addresslist = $data['addresslist']; - - $subscriptions = $this->subscribers($id, null, 'every'); - - $result = array(); - foreach($subscriptions as $target => $users) { - foreach($users as $user => $info) { - $userinfo = $auth->getUserData($user); - if($userinfo === false) continue; - if(!$userinfo['mail']) continue; - if(!$self && $user == $INPUT->server->str('REMOTE_USER')) continue; //skip our own changes - - $level = auth_aclcheck($id, $user, $userinfo['grps']); - if($level >= AUTH_READ) { - if(strcasecmp($userinfo['mail'], $conf['notify']) != 0) { //skip user who get notified elsewhere - $result[$user] = $userinfo['mail']; - } - } - } - } - $data['addresslist'] = trim($addresslist.','.implode(',', $result), ','); - } -} diff --git a/inc/template.php b/inc/template.php index c333bae2e..caec0b87b 100644 --- a/inc/template.php +++ b/inc/template.php @@ -6,7 +6,8 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) die('meh.'); +use dokuwiki\Extension\AdminPlugin; +use dokuwiki\Extension\Event; /** * Access a template file @@ -80,9 +81,9 @@ function tpl_content($prependTOC = true) { $INFO['prependTOC'] = $prependTOC; ob_start(); - trigger_event('TPL_ACT_RENDER', $ACT, 'tpl_content_core'); + Event::createAndTrigger('TPL_ACT_RENDER', $ACT, 'tpl_content_core'); $html_output = ob_get_clean(); - trigger_event('TPL_CONTENT_DISPLAY', $html_output, 'ptln'); + Event::createAndTrigger('TPL_CONTENT_DISPLAY', $html_output, 'ptln'); return !empty($html_output); } @@ -142,14 +143,14 @@ function tpl_toc($return = false) { } } elseif($ACT == 'admin') { // try to load admin plugin TOC - /** @var $plugin DokuWiki_Admin_Plugin */ + /** @var $plugin AdminPlugin */ if ($plugin = plugin_getRequestAdminPlugin()) { $toc = $plugin->getTOC(); $TOC = $toc; // avoid later rebuild } } - trigger_event('TPL_TOC_RENDER', $toc, null, false); + Event::createAndTrigger('TPL_TOC_RENDER', $toc, null, false); $html = html_TOC($toc); if($return) return $html; echo $html; @@ -175,7 +176,7 @@ function tpl_admin() { if(in_array($class, $pluginlist)) { // attempt to load the plugin - /** @var $plugin DokuWiki_Admin_Plugin */ + /** @var $plugin AdminPlugin */ $plugin = plugin_load('admin', $class); } } @@ -358,7 +359,7 @@ function tpl_metaheaders($alt = true) { ); // trigger event here - trigger_event('TPL_METAHEADER_OUTPUT', $head, '_tpl_metaheaders_action', true); + Event::createAndTrigger('TPL_METAHEADER_OUTPUT', $head, '_tpl_metaheaders_action', true); return true; } @@ -603,7 +604,7 @@ function tpl_get_action($type) { $unknown = true; } - $evt = new Doku_Event('TPL_ACTION_GET', $data); + $evt = new Event('TPL_ACTION_GET', $data); if($evt->advise_before()) { //handle unknown types if($unknown) { @@ -702,7 +703,7 @@ function tpl_searchform($ajax = true, $autocomplete = true) { $searchForm->addTagClose('div'); } $searchForm->addTagClose('div'); - trigger_event('FORM_QUICKSEARCH_OUTPUT', $searchForm); + Event::createAndTrigger('FORM_QUICKSEARCH_OUTPUT', $searchForm); echo $searchForm->toHTML(); @@ -933,7 +934,7 @@ function tpl_pagetitle($id = null, $ret = false) { case 'admin' : $page_title = $lang['btn_admin']; // try to get the plugin name - /** @var $plugin DokuWiki_Admin_Plugin */ + /** @var $plugin AdminPlugin */ if ($plugin = plugin_getRequestAdminPlugin()){ $plugin_title = $plugin->getMenuText($conf['lang']); $page_title = $plugin_title ? $plugin_title : $plugin->getPluginName(); @@ -1138,7 +1139,7 @@ function tpl_img($maxwidth = 0, $maxheight = 0, $link = true, $params = null) { $p['src'] = $src; $data = array('url'=> ($link ? $url : null), 'params'=> $p); - return trigger_event('TPL_IMG_DISPLAY', $data, '_tpl_img_action', true); + return Event::createAndTrigger('TPL_IMG_DISPLAY', $data, '_tpl_img_action', true); } /** @@ -1343,7 +1344,7 @@ function tpl_mediaContent($fromajax = false, $sort='natural') { // output the content pane, wrapped in an event. if(!$fromajax) ptln('<div id="media__content">'); $data = array('do' => $do); - $evt = new Doku_Event('MEDIAMANAGER_CONTENT_OUTPUT', $data); + $evt = new Event('MEDIAMANAGER_CONTENT_OUTPUT', $data); if($evt->advise_before()) { $do = $data['do']; if($do == 'filesinuse') { @@ -1422,7 +1423,11 @@ function tpl_mediaFileDetails($image, $rev) { /** @var Input $INPUT */ global $INPUT; - $removed = (!file_exists(mediaFN($image)) && file_exists(mediaMetaFN($image, '.changes')) && $conf['mediarevisions']); + $removed = ( + !file_exists(mediaFN($image)) && + file_exists(mediaMetaFN($image, '.changes')) && + $conf['mediarevisions'] + ); if(!$image || (!file_exists(mediaFN($image)) && !$removed) || $DEL) return; if($rev && !file_exists(mediaFN($image, $rev))) $rev = false; $ns = getNS($image); @@ -1450,7 +1455,8 @@ function tpl_mediaFileDetails($image, $rev) { $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); $class = 'select mediafile mf_'.$class; $attributes = $rev ? ['rev' => $rev] : []; - $tabTitle = '<strong><a href="'.ml($image, $attributes).'" class="'.$class.'" title="'.$lang['mediaview'].'">'.$image.'</a>'.'</strong>'; + $tabTitle = '<strong><a href="'.ml($image, $attributes).'" class="'.$class.'" title="'.$lang['mediaview'].'">'. + $image.'</a>'.'</strong>'; if($opened_tab === 'view' && $rev) { printf($lang['media_viewold'], $tabTitle, dformat($rev)); } else { @@ -1862,7 +1868,7 @@ function tpl_toolsevent($toolsname, $items, $view = 'main') { ); $hook = 'TEMPLATE_' . strtoupper($toolsname) . '_DISPLAY'; - $evt = new Doku_Event($hook, $data); + $evt = new Event($hook, $data); if($evt->advise_before()) { foreach($evt->data['items'] as $k => $html) echo $html; } diff --git a/inc/toolbar.php b/inc/toolbar.php index 7cc29e866..056221aa4 100644 --- a/inc/toolbar.php +++ b/inc/toolbar.php @@ -4,9 +4,7 @@ * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> - */ - -if(!defined('DOKU_INC')) die('meh.'); + */use dokuwiki\Extension\Event; /** * Prepares and prints an JavaScript array with all toolbar buttons @@ -20,7 +18,7 @@ function toolbar_JSdefines($varname){ $menu = array(); - $evt = new Doku_Event('TOOLBAR_DEFINE', $menu); + $evt = new Event('TOOLBAR_DEFINE', $menu); if ($evt->advise_before()){ // build button array @@ -213,7 +211,30 @@ function toolbar_JSdefines($varname){ 'type' => 'picker', 'title' => $lang['qb_chars'], 'icon' => 'chars.png', - 'list' => explode(' ','À à Á á  â à ã Ä ä Ǎ ǎ Ă ă Å å Ā ā Ą ą Æ æ Ć ć Ç ç Č č Ĉ ĉ Ċ ċ Ð đ ð Ď ď È è É é Ê ê Ë ë Ě ě Ē ē Ė ė Ę ę Ģ ģ Ĝ ĝ Ğ ğ Ġ ġ Ĥ ĥ Ì ì Í í Î î Ï ï Ǐ ǐ Ī ī İ ı Į į Ĵ ĵ Ķ ķ Ĺ ĺ Ļ ļ Ľ ľ Ł ł Ŀ ŀ Ń ń Ñ ñ Ņ ņ Ň ň Ò ò Ó ó Ô ô Õ õ Ö ö Ǒ ǒ Ō ō Ő ő Œ œ Ø ø Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ş ş Š š Ŝ ŝ Ţ ţ Ť ť Ù ù Ú ú Û û Ü ü Ǔ ǔ Ŭ ŭ Ū ū Ů ů ǖ ǘ ǚ ǜ Ų ų Ű ű Ŵ ŵ Ý ý Ÿ ÿ Ŷ ŷ Ź ź Ž ž Ż ż Þ þ ß Ħ ħ ¿ ¡ ¢ £ ¤ ¥ € ¦ § ª ¬ ¯ ° ± ÷ ‰ ¼ ½ ¾ ¹ ² ³ µ ¶ † ‡ · • º ∀ ∂ ∃ Ə ə ∅ ∇ ∈ ∉ ∋ ∏ ∑ ‾ − ∗ × ⁄ √ ∝ ∞ ∠ ∧ ∨ ∩ ∪ ∫ ∴ ∼ ≅ ≈ ≠ ≡ ≤ ≥ ⊂ ⊃ ⊄ ⊆ ⊇ ⊕ ⊗ ⊥ ⋅ ◊ ℘ ℑ ℜ ℵ ♠ ♣ ♥ ♦ α β Γ γ Δ δ ε ζ η Θ θ ι κ Λ λ μ Ξ ξ Π π ρ Σ σ Τ τ υ Φ φ χ Ψ ψ Ω ω ★ ☆ ☎ ☚ ☛ ☜ ☝ ☞ ☟ ☹ ☺ ✔ ✘ „ “ ” ‚ ‘ ’ « » ‹ › — – … ← ↑ → ↓ ↔ ⇐ ⇑ ⇒ ⇓ ⇔ © ™ ® ′ ″ [ ] { } ~ ( ) % § $ # | @'), + 'list' => [ + 'À', 'à', 'Á', 'á', 'Â', 'â', 'Ã', 'ã', 'Ä', 'ä', 'Ǎ', 'ǎ', 'Ă', 'ă', 'Å', 'å', + 'Ā', 'ā', 'Ą', 'ą', 'Æ', 'æ', 'Ć', 'ć', 'Ç', 'ç', 'Č', 'č', 'Ĉ', 'ĉ', 'Ċ', 'ċ', + 'Ð', 'đ', 'ð', 'Ď', 'ď', 'È', 'è', 'É', 'é', 'Ê', 'ê', 'Ë', 'ë', 'Ě', 'ě', 'Ē', + 'ē', 'Ė', 'ė', 'Ę', 'ę', 'Ģ', 'ģ', 'Ĝ', 'ĝ', 'Ğ', 'ğ', 'Ġ', 'ġ', 'Ĥ', 'ĥ', 'Ì', + 'ì', 'Í', 'í', 'Î', 'î', 'Ï', 'ï', 'Ǐ', 'ǐ', 'Ī', 'ī', 'İ', 'ı', 'Į', 'į', 'Ĵ', + 'ĵ', 'Ķ', 'ķ', 'Ĺ', 'ĺ', 'Ļ', 'ļ', 'Ľ', 'ľ', 'Ł', 'ł', 'Ŀ', 'ŀ', 'Ń', 'ń', 'Ñ', + 'ñ', 'Ņ', 'ņ', 'Ň', 'ň', 'Ò', 'ò', 'Ó', 'ó', 'Ô', 'ô', 'Õ', 'õ', 'Ö', 'ö', 'Ǒ', + 'ǒ', 'Ō', 'ō', 'Ő', 'ő', 'Œ', 'œ', 'Ø', 'ø', 'Ŕ', 'ŕ', 'Ŗ', 'ŗ', 'Ř', 'ř', 'Ś', + 'ś', 'Ş', 'ş', 'Š', 'š', 'Ŝ', 'ŝ', 'Ţ', 'ţ', 'Ť', 'ť', 'Ù', 'ù', 'Ú', 'ú', 'Û', + 'û', 'Ü', 'ü', 'Ǔ', 'ǔ', 'Ŭ', 'ŭ', 'Ū', 'ū', 'Ů', 'ů', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'Ų', + 'ų', 'Ű', 'ű', 'Ŵ', 'ŵ', 'Ý', 'ý', 'Ÿ', 'ÿ', 'Ŷ', 'ŷ', 'Ź', 'ź', 'Ž', 'ž', 'Ż', + 'ż', 'Þ', 'þ', 'ß', 'Ħ', 'ħ', '¿', '¡', '¢', '£', '¤', '¥', '€', '¦', '§', 'ª', + '¬', '¯', '°', '±', '÷', '‰', '¼', '½', '¾', '¹', '²', '³', 'µ', '¶', '†', '‡', + '·', '•', 'º', '∀', '∂', '∃', 'Ə', 'ə', '∅', '∇', '∈', '∉', '∋', '∏', '∑', '‾', + '−', '∗', '×', '⁄', '√', '∝', '∞', '∠', '∧', '∨', '∩', '∪', '∫', '∴', '∼', '≅', + '≈', '≠', '≡', '≤', '≥', '⊂', '⊃', '⊄', '⊆', '⊇', '⊕', '⊗', '⊥', '⋅', '◊', '℘', + 'ℑ', 'ℜ', 'ℵ', '♠', '♣', '♥', '♦', 'α', 'β', 'Γ', 'γ', 'Δ', 'δ', 'ε', 'ζ', 'η', + 'Θ', 'θ', 'ι', 'κ', 'Λ', 'λ', 'μ', 'Ξ', 'ξ', 'Π', 'π', 'ρ', 'Σ', 'σ', 'Τ', 'τ', + 'υ', 'Φ', 'φ', 'χ', 'Ψ', 'ψ', 'Ω', 'ω', '★', '☆', '☎', '☚', '☛', '☜', '☝', '☞', + '☟', '☹', '☺', '✔', '✘', '„', '“', '”', '‚', '‘', '’', '«', '»', '‹', '›', '—', + '–', '…', '←', '↑', '→', '↓', '↔', '⇐', '⇑', '⇒', '⇓', '⇔', '©', '™', '®', '′', + '″', '[', ']', '{', '}', '~', '(', ')', '%', '§', '$', '#', '|', '@' + ], 'block' => false ), array( @@ -229,8 +250,7 @@ function toolbar_JSdefines($varname){ unset($evt); // use JSON to build the JavaScript array - $json = new JSON(); - print "var $varname = ".$json->encode($menu).";\n"; + print "var $varname = ".json_encode($menu).";\n"; } /** diff --git a/inc/utf8.php b/inc/utf8.php index fe1d90d9f..1227407bf 100644 --- a/inc/utf8.php +++ b/inc/utf8.php @@ -2,18 +2,25 @@ /** * UTF8 helper functions * - * @license LGPL 2.1 (http://www.gnu.org/copyleft/lesser.html) + * This file now only intitializes the UTF-8 capability detection and defines helper + * functions if needed. All actual code is in the \dokuwiki\Utf8 classes + * * @author Andreas Gohr <andi@splitbrain.org> */ +use dokuwiki\Utf8\Clean; +use dokuwiki\Utf8\Conversion; +use dokuwiki\Utf8\PhpString; +use dokuwiki\Utf8\Unicode; + /** * check for mb_string support */ -if(!defined('UTF8_MBSTRING')){ - if(function_exists('mb_substr') && !defined('UTF8_NOMBSTRING')){ - define('UTF8_MBSTRING',1); - }else{ - define('UTF8_MBSTRING',0); +if (!defined('UTF8_MBSTRING')) { + if (function_exists('mb_substr') && !defined('UTF8_NOMBSTRING')) { + define('UTF8_MBSTRING', 1); + } else { + define('UTF8_MBSTRING', 0); } } @@ -22,8 +29,8 @@ if(!defined('UTF8_MBSTRING')){ * * Without this many of the functions below will not work, so this is a minimal requirement */ -if(!defined('UTF8_PREGSUPPORT')){ - define('UTF8_PREGSUPPORT', (bool) @preg_match('/^.$/u', 'ñ')); +if (!defined('UTF8_PREGSUPPORT')) { + define('UTF8_PREGSUPPORT', (bool)@preg_match('/^.$/u', 'ñ')); } /** @@ -31,1742 +38,247 @@ if(!defined('UTF8_PREGSUPPORT')){ * * This is not required for the functions below, but might be needed in a UTF-8 aware application */ -if(!defined('UTF8_PROPERTYSUPPORT')){ - define('UTF8_PROPERTYSUPPORT', (bool) @preg_match('/^\pL$/u', 'ñ')); +if (!defined('UTF8_PROPERTYSUPPORT')) { + define('UTF8_PROPERTYSUPPORT', (bool)@preg_match('/^\pL$/u', 'ñ')); } -if(UTF8_MBSTRING){ mb_internal_encoding('UTF-8'); } - -if(!function_exists('utf8_isASCII')){ - /** - * Checks if a string contains 7bit ASCII only - * - * @author Andreas Haerter <andreas.haerter@dev.mail-node.com> - * - * @param string $str - * @return bool - */ - function utf8_isASCII($str){ - return (preg_match('/(?:[^\x00-\x7F])/', $str) !== 1); - } -} - -if(!function_exists('utf8_strip')){ - /** - * Strips all highbyte chars - * - * Returns a pure ASCII7 string - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string $str - * @return string - */ - function utf8_strip($str){ - $ascii = ''; - $len = strlen($str); - for($i=0; $i<$len; $i++){ - if(ord($str{$i}) <128){ - $ascii .= $str{$i}; - } - } - return $ascii; - } +if (UTF8_MBSTRING) { + mb_internal_encoding('UTF-8'); } -if(!function_exists('utf8_check')){ - /** - * Tries to detect if a string is in Unicode encoding - * - * @author <bmorel@ssi.fr> - * @link http://php.net/manual/en/function.utf8-encode.php - * - * @param string $Str - * @return bool - */ - function utf8_check($Str) { - $len = strlen($Str); - for ($i=0; $i<$len; $i++) { - $b = ord($Str[$i]); - if ($b < 0x80) continue; # 0bbbbbbb - elseif (($b & 0xE0) == 0xC0) $n=1; # 110bbbbb - elseif (($b & 0xF0) == 0xE0) $n=2; # 1110bbbb - elseif (($b & 0xF8) == 0xF0) $n=3; # 11110bbb - elseif (($b & 0xFC) == 0xF8) $n=4; # 111110bb - elseif (($b & 0xFE) == 0xFC) $n=5; # 1111110b - else return false; # Does not match any model - for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ? - if ((++$i == $len) || ((ord($Str[$i]) & 0xC0) != 0x80)) - return false; - } - } - return true; +if (!function_exists('utf8_isASCII')) { + /** @deprecated 2019-06-09 */ + function utf8_isASCII($str) + { + dbg_deprecated(Clean::class . '::isASCII()'); + return Clean::isASCII($str); } } -if(!function_exists('utf8_basename')){ - /** - * A locale independent basename() implementation - * - * works around a bug in PHP's basename() implementation - * - * @see basename() - * @link https://bugs.php.net/bug.php?id=37738 - * - * @param string $path A path - * @param string $suffix If the name component ends in suffix this will also be cut off - * @return string - */ - function utf8_basename($path, $suffix=''){ - $path = trim($path,'\\/'); - $rpos = max(strrpos($path, '/'), strrpos($path, '\\')); - if($rpos) $path = substr($path, $rpos+1); - - $suflen = strlen($suffix); - if($suflen && (substr($path, -$suflen) == $suffix)){ - $path = substr($path, 0, -$suflen); - } - return $path; +if (!function_exists('utf8_strip')) { + /** @deprecated 2019-06-09 */ + function utf8_strip($str) + { + dbg_deprecated(Clean::class . '::strip()'); + return Clean::strip($str); } } -if(!function_exists('utf8_strlen')){ - /** - * Unicode aware replacement for strlen() - * - * utf8_decode() converts characters that are not in ISO-8859-1 - * to '?', which, for the purpose of counting, is alright - It's - * even faster than mb_strlen. - * - * @author <chernyshevsky at hotmail dot com> - * @see strlen() - * @see utf8_decode() - * - * @param string $string - * @return int - */ - function utf8_strlen($string) { - if (function_exists('utf8_decode')) { - return strlen(utf8_decode($string)); - } elseif (UTF8_MBSTRING) { - return mb_strlen($string, 'UTF-8'); - } elseif (function_exists('iconv_strlen')) { - return iconv_strlen($string, 'UTF-8'); - } else { - return strlen($string); - } +if (!function_exists('utf8_check')) { + /** @deprecated 2019-06-09 */ + function utf8_check($str) + { + dbg_deprecated(Clean::class . '::isUtf8()'); + return Clean::isUtf8($str); } } -if(!function_exists('utf8_substr')){ - /** - * UTF-8 aware alternative to substr - * - * Return part of a string given character offset (and optionally length) - * - * @author Harry Fuecks <hfuecks@gmail.com> - * @author Chris Smith <chris@jalakai.co.uk> - * - * @param string $str - * @param int $offset number of UTF-8 characters offset (from left) - * @param int $length (optional) length in UTF-8 characters from offset - * @return string - */ - function utf8_substr($str, $offset, $length = null) { - if(UTF8_MBSTRING){ - if( $length === null ){ - return mb_substr($str, $offset); - }else{ - return mb_substr($str, $offset, $length); - } - } - - /* - * Notes: - * - * no mb string support, so we'll use pcre regex's with 'u' flag - * pcre only supports repetitions of less than 65536, in order to accept up to MAXINT values for - * offset and length, we'll repeat a group of 65535 characters when needed (ok, up to MAXINT-65536) - * - * substr documentation states false can be returned in some cases (e.g. offset > string length) - * mb_substr never returns false, it will return an empty string instead. - * - * calculating the number of characters in the string is a relatively expensive operation, so - * we only carry it out when necessary. It isn't necessary for +ve offsets and no specified length - */ - - // cast parameters to appropriate types to avoid multiple notices/warnings - $str = (string)$str; // generates E_NOTICE for PHP4 objects, but not PHP5 objects - $offset = (int)$offset; - if (!is_null($length)) $length = (int)$length; - - // handle trivial cases - if ($length === 0) return ''; - if ($offset < 0 && $length < 0 && $length < $offset) return ''; - - $offset_pattern = ''; - $length_pattern = ''; - - // normalise -ve offsets (we could use a tail anchored pattern, but they are horribly slow!) - if ($offset < 0) { - $strlen = utf8_strlen($str); // see notes - $offset = $strlen + $offset; - if ($offset < 0) $offset = 0; - } - - // establish a pattern for offset, a non-captured group equal in length to offset - if ($offset > 0) { - $Ox = (int)($offset/65535); - $Oy = $offset%65535; - - if ($Ox) $offset_pattern = '(?:.{65535}){'.$Ox.'}'; - $offset_pattern = '^(?:'.$offset_pattern.'.{'.$Oy.'})'; - } else { - $offset_pattern = '^'; // offset == 0; just anchor the pattern - } - - // establish a pattern for length - if (is_null($length)) { - $length_pattern = '(.*)$'; // the rest of the string - } else { - - if (!isset($strlen)) $strlen = utf8_strlen($str); // see notes - if ($offset > $strlen) return ''; // another trivial case - - if ($length > 0) { - - $length = min($strlen-$offset, $length); // reduce any length that would go passed the end of the string - - $Lx = (int)($length/65535); - $Ly = $length%65535; - - // +ve length requires ... a captured group of length characters - if ($Lx) $length_pattern = '(?:.{65535}){'.$Lx.'}'; - $length_pattern = '('.$length_pattern.'.{'.$Ly.'})'; - - } else if ($length < 0) { - - if ($length < ($offset - $strlen)) return ''; - - $Lx = (int)((-$length)/65535); - $Ly = (-$length)%65535; - - // -ve length requires ... capture everything except a group of -length characters - // anchored at the tail-end of the string - if ($Lx) $length_pattern = '(?:.{65535}){'.$Lx.'}'; - $length_pattern = '(.*)(?:'.$length_pattern.'.{'.$Ly.'})$'; - } - } - - if (!preg_match('#'.$offset_pattern.$length_pattern.'#us',$str,$match)) return ''; - return $match[1]; +if (!function_exists('utf8_basename')) { + /** @deprecated 2019-06-09 */ + function utf8_basename($path, $suffix = '') + { + dbg_deprecated(PhpString::class . '::basename()'); + return PhpString::basename($path, $suffix); } } -if(!function_exists('utf8_substr_replace')){ - /** - * Unicode aware replacement for substr_replace() - * - * @author Andreas Gohr <andi@splitbrain.org> - * @see substr_replace() - * - * @param string $string input string - * @param string $replacement the replacement - * @param int $start the replacing will begin at the start'th offset into string. - * @param int $length If given and is positive, it represents the length of the portion of string which is - * to be replaced. If length is zero then this function will have the effect of inserting - * replacement into string at the given start offset. - * @return string - */ - function utf8_substr_replace($string, $replacement, $start , $length=0 ){ - $ret = ''; - if($start>0) $ret .= utf8_substr($string, 0, $start); - $ret .= $replacement; - $ret .= utf8_substr($string, $start+$length); - return $ret; +if (!function_exists('utf8_strlen')) { + /** @deprecated 2019-06-09 */ + function utf8_strlen($str) + { + dbg_deprecated(PhpString::class . '::strlen()'); + return PhpString::strlen($str); } } -if(!function_exists('utf8_ltrim')){ - /** - * Unicode aware replacement for ltrim() - * - * @author Andreas Gohr <andi@splitbrain.org> - * @see ltrim() - * - * @param string $str - * @param string $charlist - * @return string - */ - function utf8_ltrim($str,$charlist=''){ - if($charlist == '') return ltrim($str); - - //quote charlist for use in a characterclass - $charlist = preg_replace('!([\\\\\\-\\]\\[/])!','\\\${1}',$charlist); - - return preg_replace('/^['.$charlist.']+/u','',$str); +if (!function_exists('utf8_substr')) { + /** @deprecated 2019-06-09 */ + function utf8_substr($str, $offset, $length = null) + { + dbg_deprecated(PhpString::class . '::substr()'); + return PhpString::substr($str, $offset, $length); } } -if(!function_exists('utf8_rtrim')){ - /** - * Unicode aware replacement for rtrim() - * - * @author Andreas Gohr <andi@splitbrain.org> - * @see rtrim() - * - * @param string $str - * @param string $charlist - * @return string - */ - function utf8_rtrim($str,$charlist=''){ - if($charlist == '') return rtrim($str); - - //quote charlist for use in a characterclass - $charlist = preg_replace('!([\\\\\\-\\]\\[/])!','\\\${1}',$charlist); - - return preg_replace('/['.$charlist.']+$/u','',$str); +if (!function_exists('utf8_substr_replace')) { + /** @deprecated 2019-06-09 */ + function utf8_substr_replace($string, $replacement, $start, $length = 0) + { + dbg_deprecated(PhpString::class . '::substr_replace()'); + return PhpString::substr_replace($string, $replacement, $start, $length); } } -if(!function_exists('utf8_trim')){ - /** - * Unicode aware replacement for trim() - * - * @author Andreas Gohr <andi@splitbrain.org> - * @see trim() - * - * @param string $str - * @param string $charlist - * @return string - */ - function utf8_trim($str,$charlist='') { - if($charlist == '') return trim($str); - - return utf8_ltrim(utf8_rtrim($str,$charlist),$charlist); +if (!function_exists('utf8_ltrim')) { + /** @deprecated 2019-06-09 */ + function utf8_ltrim($str, $charlist = '') + { + dbg_deprecated(PhpString::class . '::ltrim()'); + return PhpString::ltrim($str, $charlist); } } -if(!function_exists('utf8_strtolower')){ - /** - * This is a unicode aware replacement for strtolower() - * - * Uses mb_string extension if available - * - * @author Leo Feyer <leo@typolight.org> - * @see strtolower() - * @see utf8_strtoupper() - * - * @param string $string - * @return string - */ - function utf8_strtolower($string){ - if(UTF8_MBSTRING) { - if (class_exists("Normalizer", $autoload = false)) - return normalizer::normalize(mb_strtolower($string,'utf-8')); - else - return (mb_strtolower($string,'utf-8')); - } - global $UTF8_UPPER_TO_LOWER; - return strtr($string,$UTF8_UPPER_TO_LOWER); +if (!function_exists('utf8_rtrim')) { + /** @deprecated 2019-06-09 */ + function utf8_rtrim($str, $charlist = '') + { + dbg_deprecated(PhpString::class . '::rtrim()'); + return PhpString::rtrim($str, $charlist); } } -if(!function_exists('utf8_strtoupper')){ - /** - * This is a unicode aware replacement for strtoupper() - * - * Uses mb_string extension if available - * - * @author Leo Feyer <leo@typolight.org> - * @see strtoupper() - * @see utf8_strtoupper() - * - * @param string $string - * @return string - */ - function utf8_strtoupper($string){ - if(UTF8_MBSTRING) return mb_strtoupper($string,'utf-8'); - - global $UTF8_LOWER_TO_UPPER; - return strtr($string,$UTF8_LOWER_TO_UPPER); +if (!function_exists('utf8_trim')) { + /** @deprecated 2019-06-09 */ + function utf8_trim($str, $charlist = '') + { + dbg_deprecated(PhpString::class . '::trim()'); + return PhpString::trim($str, $charlist); } } -if(!function_exists('utf8_ucfirst')){ - /** - * UTF-8 aware alternative to ucfirst - * Make a string's first character uppercase - * - * @author Harry Fuecks - * - * @param string $str - * @return string with first character as upper case (if applicable) - */ - function utf8_ucfirst($str){ - switch ( utf8_strlen($str) ) { - case 0: - return ''; - case 1: - return utf8_strtoupper($str); - default: - preg_match('/^(.{1})(.*)$/us', $str, $matches); - return utf8_strtoupper($matches[1]).$matches[2]; - } +if (!function_exists('utf8_strtolower')) { + /** @deprecated 2019-06-09 */ + function utf8_strtolower($str) + { + dbg_deprecated(PhpString::class . '::strtolower()'); + return PhpString::strtolower($str); } } -if(!function_exists('utf8_ucwords')){ - /** - * UTF-8 aware alternative to ucwords - * Uppercase the first character of each word in a string - * - * @author Harry Fuecks - * @see http://php.net/ucwords - * - * @param string $str - * @return string with first char of each word uppercase - */ - function utf8_ucwords($str) { - // Note: [\x0c\x09\x0b\x0a\x0d\x20] matches; - // form feeds, horizontal tabs, vertical tabs, linefeeds and carriage returns - // This corresponds to the definition of a "word" defined at http://php.net/ucwords - $pattern = '/(^|([\x0c\x09\x0b\x0a\x0d\x20]+))([^\x0c\x09\x0b\x0a\x0d\x20]{1})[^\x0c\x09\x0b\x0a\x0d\x20]*/u'; - - return preg_replace_callback($pattern, 'utf8_ucwords_callback',$str); - } - - /** - * Callback function for preg_replace_callback call in utf8_ucwords - * You don't need to call this yourself - * - * @author Harry Fuecks - * @see utf8_ucwords - * @see utf8_strtoupper - * - * @param array $matches matches corresponding to a single word - * @return string with first char of the word in uppercase - */ - function utf8_ucwords_callback($matches) { - $leadingws = $matches[2]; - $ucfirst = utf8_strtoupper($matches[3]); - $ucword = utf8_substr_replace(ltrim($matches[0]),$ucfirst,0,1); - return $leadingws . $ucword; +if (!function_exists('utf8_strtoupper')) { + /** @deprecated 2019-06-09 */ + function utf8_strtoupper($str) + { + dbg_deprecated(PhpString::class . '::strtoupper()'); + return PhpString::strtoupper($str); } } -if(!function_exists('utf8_deaccent')){ - /** - * Replace accented UTF-8 characters by unaccented ASCII-7 equivalents - * - * Use the optional parameter to just deaccent lower ($case = -1) or upper ($case = 1) - * letters. Default is to deaccent both cases ($case = 0) - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string $string - * @param int $case - * @return string - */ - function utf8_deaccent($string,$case=0){ - if($case <= 0){ - global $UTF8_LOWER_ACCENTS; - $string = strtr($string,$UTF8_LOWER_ACCENTS); - } - if($case >= 0){ - global $UTF8_UPPER_ACCENTS; - $string = strtr($string,$UTF8_UPPER_ACCENTS); - } - return $string; +if (!function_exists('utf8_ucfirst')) { + /** @deprecated 2019-06-09 */ + function utf8_ucfirst($str) + { + dbg_deprecated(PhpString::class . '::ucfirst()'); + return PhpString::ucfirst($str); } } -if(!function_exists('utf8_romanize')){ - /** - * Romanize a non-latin string - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string $string - * @return string - */ - function utf8_romanize($string){ - if(utf8_isASCII($string)) return $string; //nothing to do - - global $UTF8_ROMANIZATION; - return strtr($string,$UTF8_ROMANIZATION); +if (!function_exists('utf8_ucwords')) { + /** @deprecated 2019-06-09 */ + function utf8_ucwords($str) + { + dbg_deprecated(PhpString::class . '::ucwords()'); + return PhpString::ucwords($str); } } -if(!function_exists('utf8_stripspecials')){ - /** - * Removes special characters (nonalphanumeric) from a UTF-8 string - * - * This function adds the controlchars 0x00 to 0x19 to the array of - * stripped chars (they are not included in $UTF8_SPECIAL_CHARS) - * - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string $string The UTF8 string to strip of special chars - * @param string $repl Replace special with this string - * @param string $additional Additional chars to strip (used in regexp char class) - * @return string - */ - function utf8_stripspecials($string,$repl='',$additional=''){ - global $UTF8_SPECIAL_CHARS2; - - static $specials = null; - if(is_null($specials)){ - #$specials = preg_quote(unicode_to_utf8($UTF8_SPECIAL_CHARS), '/'); - $specials = preg_quote($UTF8_SPECIAL_CHARS2, '/'); - } - - return preg_replace('/['.$additional.'\x00-\x19'.$specials.']/u',$repl,$string); +if (!function_exists('utf8_deaccent')) { + /** @deprecated 2019-06-09 */ + function utf8_deaccent($str, $case = 0) + { + dbg_deprecated(Clean::class . '::deaccent()'); + return Clean::deaccent($str, $case); } } -if(!function_exists('utf8_strpos')){ - /** - * This is an Unicode aware replacement for strpos - * - * @author Leo Feyer <leo@typolight.org> - * @see strpos() - * - * @param string $haystack - * @param string $needle - * @param integer $offset - * @return integer - */ - function utf8_strpos($haystack, $needle, $offset=0){ - $comp = 0; - $length = null; - - while (is_null($length) || $length < $offset) { - $pos = strpos($haystack, $needle, $offset + $comp); - - if ($pos === false) - return false; - - $length = utf8_strlen(substr($haystack, 0, $pos)); - - if ($length < $offset) - $comp = $pos - $length; - } - - return $length; +if (!function_exists('utf8_romanize')) { + /** @deprecated 2019-06-09 */ + function utf8_romanize($str) + { + dbg_deprecated(Clean::class . '::romanize()'); + return Clean::romanize($str); } } -if(!function_exists('utf8_tohtml')){ - /** - * Encodes UTF-8 characters to HTML entities - * - * @author Tom N Harris <tnharris@whoopdedo.org> - * @author <vpribish at shopping dot com> - * @link http://php.net/manual/en/function.utf8-decode.php - * - * @param string $str - * @param bool $all Encode non-utf8 char to HTML as well - * @return string - */ - function utf8_tohtml($str, $all = false) { - $ret = ''; - foreach (utf8_to_unicode($str) as $cp) { - if ($cp < 0x80 && !$all) - $ret .= chr($cp); - elseif ($cp < 0x100) - $ret .= "&#$cp;"; - else - $ret .= '&#x'.dechex($cp).';'; - } - return $ret; +if (!function_exists('utf8_stripspecials')) { + /** @deprecated 2019-06-09 */ + function utf8_stripspecials($str, $repl = '', $additional = '') + { + dbg_deprecated(Clean::class . '::stripspecials()'); + return Clean::stripspecials($str, $repl, $additional); } } -if(!function_exists('utf8_unhtml')){ - /** - * Decodes HTML entities to UTF-8 characters - * - * Convert any &#..; entity to a codepoint, - * The entities flag defaults to only decoding numeric entities. - * Pass HTML_ENTITIES and named entities, including & < etc. - * are handled as well. Avoids the problem that would occur if you - * had to decode "&#38;&amp;#38;" - * - * unhtmlspecialchars(utf8_unhtml($s)) -> "&&" - * utf8_unhtml(unhtmlspecialchars($s)) -> "&&#38;" - * what it should be -> "&&#38;" - * - * @author Tom N Harris <tnharris@whoopdedo.org> - * - * @param string $str UTF-8 encoded string - * @param boolean $entities Flag controlling decoding of named entities. - * @return string UTF-8 encoded string with numeric (and named) entities replaced. - */ - function utf8_unhtml($str, $entities=null) { - static $decoder = null; - if (is_null($decoder)) - $decoder = new utf8_entity_decoder(); - if (is_null($entities)) - return preg_replace_callback('/(&#([Xx])?([0-9A-Za-z]+);)/m', - 'utf8_decode_numeric', $str); - else - return preg_replace_callback('/&(#)?([Xx])?([0-9A-Za-z]+);/m', - array(&$decoder, 'decode'), $str); +if (!function_exists('utf8_strpos')) { + /** @deprecated 2019-06-09 */ + function utf8_strpos($haystack, $needle, $offset = 0) + { + dbg_deprecated(PhpString::class . '::strpos()'); + return PhpString::strpos($haystack, $needle, $offset); } } -if(!function_exists('utf8_decode_numeric')){ - /** - * Decodes numeric HTML entities to their correct UTF-8 characters - * - * @param $ent string A numeric entity - * @return string|false - */ - function utf8_decode_numeric($ent) { - switch ($ent[2]) { - case 'X': - case 'x': - $cp = hexdec($ent[3]); - break; - default: - $cp = intval($ent[3]); - break; - } - return unicode_to_utf8(array($cp)); +if (!function_exists('utf8_tohtml')) { + /** @deprecated 2019-06-09 */ + function utf8_tohtml($str, $all = false) + { + dbg_deprecated(Conversion::class . '::toHtml()'); + return Conversion::toHtml($str, $all); } } -if(!class_exists('utf8_entity_decoder')){ - /** - * Encapsulate HTML entity decoding tables - */ - class utf8_entity_decoder { - protected $table; - - /** - * Initializes the decoding tables - */ - function __construct() { - $table = get_html_translation_table(HTML_ENTITIES); - $table = array_flip($table); - $this->table = array_map(array(&$this,'makeutf8'), $table); - } - - /** - * Wrapper around unicode_to_utf8() - * - * @param string $c - * @return string|false - */ - function makeutf8($c) { - return unicode_to_utf8(array(ord($c))); - } - - /** - * Decodes any HTML entity to it's correct UTF-8 char equivalent - * - * @param string $ent An entity - * @return string|false - */ - function decode($ent) { - if ($ent[1] == '#') { - return utf8_decode_numeric($ent); - } elseif (array_key_exists($ent[0],$this->table)) { - return $this->table[$ent[0]]; - } else { - return $ent[0]; - } - } +if (!function_exists('utf8_unhtml')) { + /** @deprecated 2019-06-09 */ + function utf8_unhtml($str, $enties = false) + { + dbg_deprecated(Conversion::class . '::fromHtml()'); + return Conversion::fromHtml($str, $enties); } } -if(!function_exists('utf8_to_unicode')){ - /** - * Takes an UTF-8 string and returns an array of ints representing the - * Unicode characters. Astral planes are supported ie. the ints in the - * output can be > 0xFFFF. Occurrances of the BOM are ignored. Surrogates - * are not allowed. - * - * If $strict is set to true the function returns false if the input - * string isn't a valid UTF-8 octet sequence and raises a PHP error at - * level E_USER_WARNING - * - * Note: this function has been modified slightly in this library to - * trigger errors on encountering bad bytes - * - * @author <hsivonen@iki.fi> - * @author Harry Fuecks <hfuecks@gmail.com> - * @see unicode_to_utf8 - * @link http://hsivonen.iki.fi/php-utf8/ - * @link http://sourceforge.net/projects/phputf8/ - * - * @param string $str UTF-8 encoded string - * @param boolean $strict Check for invalid sequences? - * @return mixed array of unicode code points or false if UTF-8 invalid - */ - function utf8_to_unicode($str,$strict=false) { - $mState = 0; // cached expected number of octets after the current octet - // until the beginning of the next UTF8 character sequence - $mUcs4 = 0; // cached Unicode character - $mBytes = 1; // cached expected number of octets in the current sequence - - $out = array(); - - $len = strlen($str); - - for($i = 0; $i < $len; $i++) { - - $in = ord($str{$i}); - - if ( $mState == 0) { - - // When mState is zero we expect either a US-ASCII character or a - // multi-octet sequence. - if (0 == (0x80 & ($in))) { - // US-ASCII, pass straight through. - $out[] = $in; - $mBytes = 1; - - } else if (0xC0 == (0xE0 & ($in))) { - // First octet of 2 octet sequence - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 0x1F) << 6; - $mState = 1; - $mBytes = 2; - - } else if (0xE0 == (0xF0 & ($in))) { - // First octet of 3 octet sequence - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 0x0F) << 12; - $mState = 2; - $mBytes = 3; - - } else if (0xF0 == (0xF8 & ($in))) { - // First octet of 4 octet sequence - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 0x07) << 18; - $mState = 3; - $mBytes = 4; - - } else if (0xF8 == (0xFC & ($in))) { - /* First octet of 5 octet sequence. - * - * This is illegal because the encoded codepoint must be either - * (a) not the shortest form or - * (b) outside the Unicode range of 0-0x10FFFF. - * Rather than trying to resynchronize, we will carry on until the end - * of the sequence and let the later error handling code catch it. - */ - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 0x03) << 24; - $mState = 4; - $mBytes = 5; - - } else if (0xFC == (0xFE & ($in))) { - // First octet of 6 octet sequence, see comments for 5 octet sequence. - $mUcs4 = ($in); - $mUcs4 = ($mUcs4 & 1) << 30; - $mState = 5; - $mBytes = 6; - - } elseif($strict) { - /* Current octet is neither in the US-ASCII range nor a legal first - * octet of a multi-octet sequence. - */ - trigger_error( - 'utf8_to_unicode: Illegal sequence identifier '. - 'in UTF-8 at byte '.$i, - E_USER_WARNING - ); - return false; - - } - - } else { - - // When mState is non-zero, we expect a continuation of the multi-octet - // sequence - if (0x80 == (0xC0 & ($in))) { - - // Legal continuation. - $shift = ($mState - 1) * 6; - $tmp = $in; - $tmp = ($tmp & 0x0000003F) << $shift; - $mUcs4 |= $tmp; - - /** - * End of the multi-octet sequence. mUcs4 now contains the final - * Unicode codepoint to be output - */ - if (0 == --$mState) { - - /* - * Check for illegal sequences and codepoints. - */ - // From Unicode 3.1, non-shortest form is illegal - if (((2 == $mBytes) && ($mUcs4 < 0x0080)) || - ((3 == $mBytes) && ($mUcs4 < 0x0800)) || - ((4 == $mBytes) && ($mUcs4 < 0x10000)) || - (4 < $mBytes) || - // From Unicode 3.2, surrogate characters are illegal - (($mUcs4 & 0xFFFFF800) == 0xD800) || - // Codepoints outside the Unicode range are illegal - ($mUcs4 > 0x10FFFF)) { - - if($strict){ - trigger_error( - 'utf8_to_unicode: Illegal sequence or codepoint '. - 'in UTF-8 at byte '.$i, - E_USER_WARNING - ); - - return false; - } - - } - - if (0xFEFF != $mUcs4) { - // BOM is legal but we don't want to output it - $out[] = $mUcs4; - } - - //initialize UTF8 cache - $mState = 0; - $mUcs4 = 0; - $mBytes = 1; - } - - } elseif($strict) { - /** - *((0xC0 & (*in) != 0x80) && (mState != 0)) - * Incomplete multi-octet sequence. - */ - trigger_error( - 'utf8_to_unicode: Incomplete multi-octet '. - ' sequence in UTF-8 at byte '.$i, - E_USER_WARNING - ); - - return false; - } - } - } - return $out; +if (!function_exists('utf8_to_unicode')) { + /** @deprecated 2019-06-09 */ + function utf8_to_unicode($str, $strict = false) + { + dbg_deprecated(Unicode::class . '::fromUtf8()'); + return Unicode::fromUtf8($str, $strict); } } -if(!function_exists('unicode_to_utf8')){ - /** - * Takes an array of ints representing the Unicode characters and returns - * a UTF-8 string. Astral planes are supported ie. the ints in the - * input can be > 0xFFFF. Occurrances of the BOM are ignored. Surrogates - * are not allowed. - * - * If $strict is set to true the function returns false if the input - * array contains ints that represent surrogates or are outside the - * Unicode range and raises a PHP error at level E_USER_WARNING - * - * Note: this function has been modified slightly in this library to use - * output buffering to concatenate the UTF-8 string (faster) as well as - * reference the array by it's keys - * - * @param array $arr of unicode code points representing a string - * @param boolean $strict Check for invalid sequences? - * @return string|false UTF-8 string or false if array contains invalid code points - * - * @author <hsivonen@iki.fi> - * @author Harry Fuecks <hfuecks@gmail.com> - * @see utf8_to_unicode - * @link http://hsivonen.iki.fi/php-utf8/ - * @link http://sourceforge.net/projects/phputf8/ - */ - function unicode_to_utf8($arr,$strict=false) { - if (!is_array($arr)) return ''; - ob_start(); - - foreach (array_keys($arr) as $k) { - - if ( ($arr[$k] >= 0) && ($arr[$k] <= 0x007f) ) { - # ASCII range (including control chars) - - echo chr($arr[$k]); - - } else if ($arr[$k] <= 0x07ff) { - # 2 byte sequence - - echo chr(0xc0 | ($arr[$k] >> 6)); - echo chr(0x80 | ($arr[$k] & 0x003f)); - - } else if($arr[$k] == 0xFEFF) { - # Byte order mark (skip) - - // nop -- zap the BOM - - } else if ($arr[$k] >= 0xD800 && $arr[$k] <= 0xDFFF) { - # Test for illegal surrogates - - // found a surrogate - if($strict){ - trigger_error( - 'unicode_to_utf8: Illegal surrogate '. - 'at index: '.$k.', value: '.$arr[$k], - E_USER_WARNING - ); - return false; - } - - } else if ($arr[$k] <= 0xffff) { - # 3 byte sequence - - echo chr(0xe0 | ($arr[$k] >> 12)); - echo chr(0x80 | (($arr[$k] >> 6) & 0x003f)); - echo chr(0x80 | ($arr[$k] & 0x003f)); - - } else if ($arr[$k] <= 0x10ffff) { - # 4 byte sequence - - echo chr(0xf0 | ($arr[$k] >> 18)); - echo chr(0x80 | (($arr[$k] >> 12) & 0x3f)); - echo chr(0x80 | (($arr[$k] >> 6) & 0x3f)); - echo chr(0x80 | ($arr[$k] & 0x3f)); - - } elseif($strict) { - - trigger_error( - 'unicode_to_utf8: Codepoint out of Unicode range '. - 'at index: '.$k.', value: '.$arr[$k], - E_USER_WARNING - ); - - // out of range - return false; - } - } - - $result = ob_get_contents(); - ob_end_clean(); - return $result; +if (!function_exists('unicode_to_utf8')) { + /** @deprecated 2019-06-09 */ + function unicode_to_utf8($arr, $strict = false) + { + dbg_deprecated(Unicode::class . '::toUtf8()'); + return Unicode::toUtf8($arr, $strict); } } -if(!function_exists('utf8_to_utf16be')){ - /** - * UTF-8 to UTF-16BE conversion. - * - * Maybe really UCS-2 without mb_string due to utf8_to_unicode limits - * - * @param string $str - * @param bool $bom - * @return string - */ - function utf8_to_utf16be(&$str, $bom = false) { - $out = $bom ? "\xFE\xFF" : ''; - if(UTF8_MBSTRING) return $out.mb_convert_encoding($str,'UTF-16BE','UTF-8'); - - $uni = utf8_to_unicode($str); - foreach($uni as $cp){ - $out .= pack('n',$cp); - } - return $out; +if (!function_exists('utf8_to_utf16be')) { + /** @deprecated 2019-06-09 */ + function utf8_to_utf16be($str, $bom = false) + { + dbg_deprecated(Conversion::class . '::toUtf16be()'); + return Conversion::toUtf16be($str, $bom); } } -if(!function_exists('utf16be_to_utf8')){ - /** - * UTF-8 to UTF-16BE conversion. - * - * Maybe really UCS-2 without mb_string due to utf8_to_unicode limits - * - * @param string $str - * @return false|string - */ - function utf16be_to_utf8(&$str) { - $uni = unpack('n*',$str); - return unicode_to_utf8($uni); +if (!function_exists('utf16be_to_utf8')) { + /** @deprecated 2019-06-09 */ + function utf16be_to_utf8($str) + { + dbg_deprecated(Conversion::class . '::fromUtf16be()'); + return Conversion::fromUtf16be($str); } } -if(!function_exists('utf8_bad_replace')){ - /** - * Replace bad bytes with an alternative character - * - * ASCII character is recommended for replacement char - * - * PCRE Pattern to locate bad bytes in a UTF-8 string - * Comes from W3 FAQ: Multilingual Forms - * Note: modified to include full ASCII range including control chars - * - * @author Harry Fuecks <hfuecks@gmail.com> - * @see http://www.w3.org/International/questions/qa-forms-utf-8 - * - * @param string $str to search - * @param string $replace to replace bad bytes with (defaults to '?') - use ASCII - * @return string - */ - function utf8_bad_replace($str, $replace = '') { - $UTF8_BAD = - '([\x00-\x7F]'. # ASCII (including control chars) - '|[\xC2-\xDF][\x80-\xBF]'. # non-overlong 2-byte - '|\xE0[\xA0-\xBF][\x80-\xBF]'. # excluding overlongs - '|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'. # straight 3-byte - '|\xED[\x80-\x9F][\x80-\xBF]'. # excluding surrogates - '|\xF0[\x90-\xBF][\x80-\xBF]{2}'. # planes 1-3 - '|[\xF1-\xF3][\x80-\xBF]{3}'. # planes 4-15 - '|\xF4[\x80-\x8F][\x80-\xBF]{2}'. # plane 16 - '|(.{1}))'; # invalid byte - ob_start(); - while (preg_match('/'.$UTF8_BAD.'/S', $str, $matches)) { - if ( !isset($matches[2])) { - echo $matches[0]; - } else { - echo $replace; - } - $str = substr($str,strlen($matches[0])); - } - $result = ob_get_contents(); - ob_end_clean(); - return $result; +if (!function_exists('utf8_bad_replace')) { + /** @deprecated 2019-06-09 */ + function utf8_bad_replace($str, $replace = '') + { + dbg_deprecated(Clean::class . '::replaceBadBytes()'); + return Clean::replaceBadBytes($str, $replace); } } -if(!function_exists('utf8_correctIdx')){ - /** - * adjust a byte index into a utf8 string to a utf8 character boundary - * - * @param string $str utf8 character string - * @param int $i byte index into $str - * @param $next bool direction to search for boundary, - * false = up (current character) - * true = down (next character) - * - * @return int byte index into $str now pointing to a utf8 character boundary - * - * @author chris smith <chris@jalakai.co.uk> - */ - function utf8_correctIdx(&$str,$i,$next=false) { - - if ($i <= 0) return 0; - - $limit = strlen($str); - if ($i>=$limit) return $limit; - - if ($next) { - while (($i<$limit) && ((ord($str[$i]) & 0xC0) == 0x80)) $i++; - } else { - while ($i && ((ord($str[$i]) & 0xC0) == 0x80)) $i--; - } - - return $i; +if (!function_exists('utf8_correctIdx')) { + /** @deprecated 2019-06-09 */ + function utf8_correctIdx($str, $i, $next = false) + { + dbg_deprecated(Clean::class . '::correctIdx()'); + return Clean::correctIdx($str, $i, $next); } } - -// only needed if no mb_string available -if(!UTF8_MBSTRING){ - /** - * UTF-8 Case lookup table - * - * This lookuptable defines the upper case letters to their correspponding - * lower case letter in UTF-8 - * - * @author Andreas Gohr <andi@splitbrain.org> - */ - global $UTF8_LOWER_TO_UPPER; - if(empty($UTF8_LOWER_TO_UPPER)) $UTF8_LOWER_TO_UPPER = array( - "z"=>"Z","y"=>"Y","x"=>"X","w"=>"W","v"=>"V","u"=>"U","t"=>"T","s"=>"S","r"=>"R","q"=>"Q", - "p"=>"P","o"=>"O","n"=>"N","m"=>"M","l"=>"L","k"=>"K","j"=>"J","i"=>"I","h"=>"H","g"=>"G", - "f"=>"F","e"=>"E","d"=>"D","c"=>"C","b"=>"B","a"=>"A","ῳ"=>"ῼ","ῥ"=>"Ῥ","ῡ"=>"Ῡ","ῑ"=>"Ῑ", - "ῐ"=>"Ῐ","ῃ"=>"ῌ","ι"=>"Ι","ᾳ"=>"ᾼ","ᾱ"=>"Ᾱ","ᾰ"=>"Ᾰ","ᾧ"=>"ᾯ","ᾦ"=>"ᾮ","ᾥ"=>"ᾭ","ᾤ"=>"ᾬ", - "ᾣ"=>"ᾫ","ᾢ"=>"ᾪ","ᾡ"=>"ᾩ","ᾗ"=>"ᾟ","ᾖ"=>"ᾞ","ᾕ"=>"ᾝ","ᾔ"=>"ᾜ","ᾓ"=>"ᾛ","ᾒ"=>"ᾚ","ᾑ"=>"ᾙ", - "ᾐ"=>"ᾘ","ᾇ"=>"ᾏ","ᾆ"=>"ᾎ","ᾅ"=>"ᾍ","ᾄ"=>"ᾌ","ᾃ"=>"ᾋ","ᾂ"=>"ᾊ","ᾁ"=>"ᾉ","ᾀ"=>"ᾈ","ώ"=>"Ώ", - "ὼ"=>"Ὼ","ύ"=>"Ύ","ὺ"=>"Ὺ","ό"=>"Ό","ὸ"=>"Ὸ","ί"=>"Ί","ὶ"=>"Ὶ","ή"=>"Ή","ὴ"=>"Ὴ","έ"=>"Έ", - "ὲ"=>"Ὲ","ά"=>"Ά","ὰ"=>"Ὰ","ὧ"=>"Ὧ","ὦ"=>"Ὦ","ὥ"=>"Ὥ","ὤ"=>"Ὤ","ὣ"=>"Ὣ","ὢ"=>"Ὢ","ὡ"=>"Ὡ", - "ὗ"=>"Ὗ","ὕ"=>"Ὕ","ὓ"=>"Ὓ","ὑ"=>"Ὑ","ὅ"=>"Ὅ","ὄ"=>"Ὄ","ὃ"=>"Ὃ","ὂ"=>"Ὂ","ὁ"=>"Ὁ","ὀ"=>"Ὀ", - "ἷ"=>"Ἷ","ἶ"=>"Ἶ","ἵ"=>"Ἵ","ἴ"=>"Ἴ","ἳ"=>"Ἳ","ἲ"=>"Ἲ","ἱ"=>"Ἱ","ἰ"=>"Ἰ","ἧ"=>"Ἧ","ἦ"=>"Ἦ", - "ἥ"=>"Ἥ","ἤ"=>"Ἤ","ἣ"=>"Ἣ","ἢ"=>"Ἢ","ἡ"=>"Ἡ","ἕ"=>"Ἕ","ἔ"=>"Ἔ","ἓ"=>"Ἓ","ἒ"=>"Ἒ","ἑ"=>"Ἑ", - "ἐ"=>"Ἐ","ἇ"=>"Ἇ","ἆ"=>"Ἆ","ἅ"=>"Ἅ","ἄ"=>"Ἄ","ἃ"=>"Ἃ","ἂ"=>"Ἂ","ἁ"=>"Ἁ","ἀ"=>"Ἀ","ỹ"=>"Ỹ", - "ỷ"=>"Ỷ","ỵ"=>"Ỵ","ỳ"=>"Ỳ","ự"=>"Ự","ữ"=>"Ữ","ử"=>"Ử","ừ"=>"Ừ","ứ"=>"Ứ","ủ"=>"Ủ","ụ"=>"Ụ", - "ợ"=>"Ợ","ỡ"=>"Ỡ","ở"=>"Ở","ờ"=>"Ờ","ớ"=>"Ớ","ộ"=>"Ộ","ỗ"=>"Ỗ","ổ"=>"Ổ","ồ"=>"Ồ","ố"=>"Ố", - "ỏ"=>"Ỏ","ọ"=>"Ọ","ị"=>"Ị","ỉ"=>"Ỉ","ệ"=>"Ệ","ễ"=>"Ễ","ể"=>"Ể","ề"=>"Ề","ế"=>"Ế","ẽ"=>"Ẽ", - "ẻ"=>"Ẻ","ẹ"=>"Ẹ","ặ"=>"Ặ","ẵ"=>"Ẵ","ẳ"=>"Ẳ","ằ"=>"Ằ","ắ"=>"Ắ","ậ"=>"Ậ","ẫ"=>"Ẫ","ẩ"=>"Ẩ", - "ầ"=>"Ầ","ấ"=>"Ấ","ả"=>"Ả","ạ"=>"Ạ","ẛ"=>"Ṡ","ẕ"=>"Ẕ","ẓ"=>"Ẓ","ẑ"=>"Ẑ","ẏ"=>"Ẏ","ẍ"=>"Ẍ", - "ẋ"=>"Ẋ","ẉ"=>"Ẉ","ẇ"=>"Ẇ","ẅ"=>"Ẅ","ẃ"=>"Ẃ","ẁ"=>"Ẁ","ṿ"=>"Ṿ","ṽ"=>"Ṽ","ṻ"=>"Ṻ","ṹ"=>"Ṹ", - "ṷ"=>"Ṷ","ṵ"=>"Ṵ","ṳ"=>"Ṳ","ṱ"=>"Ṱ","ṯ"=>"Ṯ","ṭ"=>"Ṭ","ṫ"=>"Ṫ","ṩ"=>"Ṩ","ṧ"=>"Ṧ","ṥ"=>"Ṥ", - "ṣ"=>"Ṣ","ṡ"=>"Ṡ","ṟ"=>"Ṟ","ṝ"=>"Ṝ","ṛ"=>"Ṛ","ṙ"=>"Ṙ","ṗ"=>"Ṗ","ṕ"=>"Ṕ","ṓ"=>"Ṓ","ṑ"=>"Ṑ", - "ṏ"=>"Ṏ","ṍ"=>"Ṍ","ṋ"=>"Ṋ","ṉ"=>"Ṉ","ṇ"=>"Ṇ","ṅ"=>"Ṅ","ṃ"=>"Ṃ","ṁ"=>"Ṁ","ḿ"=>"Ḿ","ḽ"=>"Ḽ", - "ḻ"=>"Ḻ","ḹ"=>"Ḹ","ḷ"=>"Ḷ","ḵ"=>"Ḵ","ḳ"=>"Ḳ","ḱ"=>"Ḱ","ḯ"=>"Ḯ","ḭ"=>"Ḭ","ḫ"=>"Ḫ","ḩ"=>"Ḩ", - "ḧ"=>"Ḧ","ḥ"=>"Ḥ","ḣ"=>"Ḣ","ḡ"=>"Ḡ","ḟ"=>"Ḟ","ḝ"=>"Ḝ","ḛ"=>"Ḛ","ḙ"=>"Ḙ","ḗ"=>"Ḗ","ḕ"=>"Ḕ", - "ḓ"=>"Ḓ","ḑ"=>"Ḑ","ḏ"=>"Ḏ","ḍ"=>"Ḍ","ḋ"=>"Ḋ","ḉ"=>"Ḉ","ḇ"=>"Ḇ","ḅ"=>"Ḅ","ḃ"=>"Ḃ","ḁ"=>"Ḁ", - "ֆ"=>"Ֆ","օ"=>"Օ","ք"=>"Ք","փ"=>"Փ","ւ"=>"Ւ","ց"=>"Ց","ր"=>"Ր","տ"=>"Տ","վ"=>"Վ","ս"=>"Ս", - "ռ"=>"Ռ","ջ"=>"Ջ","պ"=>"Պ","չ"=>"Չ","ո"=>"Ո","շ"=>"Շ","ն"=>"Ն","յ"=>"Յ","մ"=>"Մ","ճ"=>"Ճ", - "ղ"=>"Ղ","ձ"=>"Ձ","հ"=>"Հ","կ"=>"Կ","ծ"=>"Ծ","խ"=>"Խ","լ"=>"Լ","ի"=>"Ի","ժ"=>"Ժ","թ"=>"Թ", - "ը"=>"Ը","է"=>"Է","զ"=>"Զ","ե"=>"Ե","դ"=>"Դ","գ"=>"Գ","բ"=>"Բ","ա"=>"Ա","ԏ"=>"Ԏ","ԍ"=>"Ԍ", - "ԋ"=>"Ԋ","ԉ"=>"Ԉ","ԇ"=>"Ԇ","ԅ"=>"Ԅ","ԃ"=>"Ԃ","ԁ"=>"Ԁ","ӹ"=>"Ӹ","ӵ"=>"Ӵ","ӳ"=>"Ӳ","ӱ"=>"Ӱ", - "ӯ"=>"Ӯ","ӭ"=>"Ӭ","ӫ"=>"Ӫ","ө"=>"Ө","ӧ"=>"Ӧ","ӥ"=>"Ӥ","ӣ"=>"Ӣ","ӡ"=>"Ӡ","ӟ"=>"Ӟ","ӝ"=>"Ӝ", - "ӛ"=>"Ӛ","ә"=>"Ә","ӗ"=>"Ӗ","ӕ"=>"Ӕ","ӓ"=>"Ӓ","ӑ"=>"Ӑ","ӎ"=>"Ӎ","ӌ"=>"Ӌ","ӊ"=>"Ӊ","ӈ"=>"Ӈ", - "ӆ"=>"Ӆ","ӄ"=>"Ӄ","ӂ"=>"Ӂ","ҿ"=>"Ҿ","ҽ"=>"Ҽ","һ"=>"Һ","ҹ"=>"Ҹ","ҷ"=>"Ҷ","ҵ"=>"Ҵ","ҳ"=>"Ҳ", - "ұ"=>"Ұ","ү"=>"Ү","ҭ"=>"Ҭ","ҫ"=>"Ҫ","ҩ"=>"Ҩ","ҧ"=>"Ҧ","ҥ"=>"Ҥ","ң"=>"Ң","ҡ"=>"Ҡ","ҟ"=>"Ҟ", - "ҝ"=>"Ҝ","қ"=>"Қ","ҙ"=>"Ҙ","җ"=>"Җ","ҕ"=>"Ҕ","ғ"=>"Ғ","ґ"=>"Ґ","ҏ"=>"Ҏ","ҍ"=>"Ҍ","ҋ"=>"Ҋ", - "ҁ"=>"Ҁ","ѿ"=>"Ѿ","ѽ"=>"Ѽ","ѻ"=>"Ѻ","ѹ"=>"Ѹ","ѷ"=>"Ѷ","ѵ"=>"Ѵ","ѳ"=>"Ѳ","ѱ"=>"Ѱ","ѯ"=>"Ѯ", - "ѭ"=>"Ѭ","ѫ"=>"Ѫ","ѩ"=>"Ѩ","ѧ"=>"Ѧ","ѥ"=>"Ѥ","ѣ"=>"Ѣ","ѡ"=>"Ѡ","џ"=>"Џ","ў"=>"Ў","ѝ"=>"Ѝ", - "ќ"=>"Ќ","ћ"=>"Ћ","њ"=>"Њ","љ"=>"Љ","ј"=>"Ј","ї"=>"Ї","і"=>"І","ѕ"=>"Ѕ","є"=>"Є","ѓ"=>"Ѓ", - "ђ"=>"Ђ","ё"=>"Ё","ѐ"=>"Ѐ","я"=>"Я","ю"=>"Ю","э"=>"Э","ь"=>"Ь","ы"=>"Ы","ъ"=>"Ъ","щ"=>"Щ", - "ш"=>"Ш","ч"=>"Ч","ц"=>"Ц","х"=>"Х","ф"=>"Ф","у"=>"У","т"=>"Т","с"=>"С","р"=>"Р","п"=>"П", - "о"=>"О","н"=>"Н","м"=>"М","л"=>"Л","к"=>"К","й"=>"Й","и"=>"И","з"=>"З","ж"=>"Ж","е"=>"Е", - "д"=>"Д","г"=>"Г","в"=>"В","б"=>"Б","а"=>"А","ϵ"=>"Ε","ϲ"=>"Σ","ϱ"=>"Ρ","ϰ"=>"Κ","ϯ"=>"Ϯ", - "ϭ"=>"Ϭ","ϫ"=>"Ϫ","ϩ"=>"Ϩ","ϧ"=>"Ϧ","ϥ"=>"Ϥ","ϣ"=>"Ϣ","ϡ"=>"Ϡ","ϟ"=>"Ϟ","ϝ"=>"Ϝ","ϛ"=>"Ϛ", - "ϙ"=>"Ϙ","ϖ"=>"Π","ϕ"=>"Φ","ϑ"=>"Θ","ϐ"=>"Β","ώ"=>"Ώ","ύ"=>"Ύ","ό"=>"Ό","ϋ"=>"Ϋ","ϊ"=>"Ϊ", - "ω"=>"Ω","ψ"=>"Ψ","χ"=>"Χ","φ"=>"Φ","υ"=>"Υ","τ"=>"Τ","σ"=>"Σ","ς"=>"Σ","ρ"=>"Ρ","π"=>"Π", - "ο"=>"Ο","ξ"=>"Ξ","ν"=>"Ν","μ"=>"Μ","λ"=>"Λ","κ"=>"Κ","ι"=>"Ι","θ"=>"Θ","η"=>"Η","ζ"=>"Ζ", - "ε"=>"Ε","δ"=>"Δ","γ"=>"Γ","β"=>"Β","α"=>"Α","ί"=>"Ί","ή"=>"Ή","έ"=>"Έ","ά"=>"Ά","ʒ"=>"Ʒ", - "ʋ"=>"Ʋ","ʊ"=>"Ʊ","ʈ"=>"Ʈ","ʃ"=>"Ʃ","ʀ"=>"Ʀ","ɵ"=>"Ɵ","ɲ"=>"Ɲ","ɯ"=>"Ɯ","ɩ"=>"Ɩ","ɨ"=>"Ɨ", - "ɣ"=>"Ɣ","ɛ"=>"Ɛ","ə"=>"Ə","ɗ"=>"Ɗ","ɖ"=>"Ɖ","ɔ"=>"Ɔ","ɓ"=>"Ɓ","ȳ"=>"Ȳ","ȱ"=>"Ȱ","ȯ"=>"Ȯ", - "ȭ"=>"Ȭ","ȫ"=>"Ȫ","ȩ"=>"Ȩ","ȧ"=>"Ȧ","ȥ"=>"Ȥ","ȣ"=>"Ȣ","ȟ"=>"Ȟ","ȝ"=>"Ȝ","ț"=>"Ț","ș"=>"Ș", - "ȗ"=>"Ȗ","ȕ"=>"Ȕ","ȓ"=>"Ȓ","ȑ"=>"Ȑ","ȏ"=>"Ȏ","ȍ"=>"Ȍ","ȋ"=>"Ȋ","ȉ"=>"Ȉ","ȇ"=>"Ȇ","ȅ"=>"Ȅ", - "ȃ"=>"Ȃ","ȁ"=>"Ȁ","ǿ"=>"Ǿ","ǽ"=>"Ǽ","ǻ"=>"Ǻ","ǹ"=>"Ǹ","ǵ"=>"Ǵ","dz"=>"Dz","ǯ"=>"Ǯ","ǭ"=>"Ǭ", - "ǫ"=>"Ǫ","ǩ"=>"Ǩ","ǧ"=>"Ǧ","ǥ"=>"Ǥ","ǣ"=>"Ǣ","ǡ"=>"Ǡ","ǟ"=>"Ǟ","ǝ"=>"Ǝ","ǜ"=>"Ǜ","ǚ"=>"Ǚ", - "ǘ"=>"Ǘ","ǖ"=>"Ǖ","ǔ"=>"Ǔ","ǒ"=>"Ǒ","ǐ"=>"Ǐ","ǎ"=>"Ǎ","nj"=>"Nj","lj"=>"Lj","dž"=>"Dž","ƿ"=>"Ƿ", - "ƽ"=>"Ƽ","ƹ"=>"Ƹ","ƶ"=>"Ƶ","ƴ"=>"Ƴ","ư"=>"Ư","ƭ"=>"Ƭ","ƨ"=>"Ƨ","ƥ"=>"Ƥ","ƣ"=>"Ƣ","ơ"=>"Ơ", - "ƞ"=>"Ƞ","ƙ"=>"Ƙ","ƕ"=>"Ƕ","ƒ"=>"Ƒ","ƌ"=>"Ƌ","ƈ"=>"Ƈ","ƅ"=>"Ƅ","ƃ"=>"Ƃ","ſ"=>"S","ž"=>"Ž", - "ż"=>"Ż","ź"=>"Ź","ŷ"=>"Ŷ","ŵ"=>"Ŵ","ų"=>"Ų","ű"=>"Ű","ů"=>"Ů","ŭ"=>"Ŭ","ū"=>"Ū","ũ"=>"Ũ", - "ŧ"=>"Ŧ","ť"=>"Ť","ţ"=>"Ţ","š"=>"Š","ş"=>"Ş","ŝ"=>"Ŝ","ś"=>"Ś","ř"=>"Ř","ŗ"=>"Ŗ","ŕ"=>"Ŕ", - "œ"=>"Œ","ő"=>"Ő","ŏ"=>"Ŏ","ō"=>"Ō","ŋ"=>"Ŋ","ň"=>"Ň","ņ"=>"Ņ","ń"=>"Ń","ł"=>"Ł","ŀ"=>"Ŀ", - "ľ"=>"Ľ","ļ"=>"Ļ","ĺ"=>"Ĺ","ķ"=>"Ķ","ĵ"=>"Ĵ","ij"=>"IJ","ı"=>"I","į"=>"Į","ĭ"=>"Ĭ","ī"=>"Ī", - "ĩ"=>"Ĩ","ħ"=>"Ħ","ĥ"=>"Ĥ","ģ"=>"Ģ","ġ"=>"Ġ","ğ"=>"Ğ","ĝ"=>"Ĝ","ě"=>"Ě","ę"=>"Ę","ė"=>"Ė", - "ĕ"=>"Ĕ","ē"=>"Ē","đ"=>"Đ","ď"=>"Ď","č"=>"Č","ċ"=>"Ċ","ĉ"=>"Ĉ","ć"=>"Ć","ą"=>"Ą","ă"=>"Ă", - "ā"=>"Ā","ÿ"=>"Ÿ","þ"=>"Þ","ý"=>"Ý","ü"=>"Ü","û"=>"Û","ú"=>"Ú","ù"=>"Ù","ø"=>"Ø","ö"=>"Ö", - "õ"=>"Õ","ô"=>"Ô","ó"=>"Ó","ò"=>"Ò","ñ"=>"Ñ","ð"=>"Ð","ï"=>"Ï","î"=>"Î","í"=>"Í","ì"=>"Ì", - "ë"=>"Ë","ê"=>"Ê","é"=>"É","è"=>"È","ç"=>"Ç","æ"=>"Æ","å"=>"Å","ä"=>"Ä","ã"=>"Ã","â"=>"Â", - "á"=>"Á","à"=>"À","µ"=>"Μ","z"=>"Z","y"=>"Y","x"=>"X","w"=>"W","v"=>"V","u"=>"U","t"=>"T", - "s"=>"S","r"=>"R","q"=>"Q","p"=>"P","o"=>"O","n"=>"N","m"=>"M","l"=>"L","k"=>"K","j"=>"J", - "i"=>"I","h"=>"H","g"=>"G","f"=>"F","e"=>"E","d"=>"D","c"=>"C","b"=>"B","a"=>"A" - ); - - /** - * UTF-8 Case lookup table - * - * This lookuptable defines the lower case letters to their corresponding - * upper case letter in UTF-8 - * - * @author Andreas Gohr <andi@splitbrain.org> - */ - global $UTF8_UPPER_TO_LOWER; - if(empty($UTF8_UPPER_TO_LOWER)) $UTF8_UPPER_TO_LOWER = array ( - "Z"=>"z","Y"=>"y","X"=>"x","W"=>"w","V"=>"v","U"=>"u","T"=>"t","S"=>"s","R"=>"r","Q"=>"q", - "P"=>"p","O"=>"o","N"=>"n","M"=>"m","L"=>"l","K"=>"k","J"=>"j","I"=>"i","H"=>"h","G"=>"g", - "F"=>"f","E"=>"e","D"=>"d","C"=>"c","B"=>"b","A"=>"a","ῼ"=>"ῳ","Ῥ"=>"ῥ","Ῡ"=>"ῡ","Ῑ"=>"ῑ", - "Ῐ"=>"ῐ","ῌ"=>"ῃ","Ι"=>"ι","ᾼ"=>"ᾳ","Ᾱ"=>"ᾱ","Ᾰ"=>"ᾰ","ᾯ"=>"ᾧ","ᾮ"=>"ᾦ","ᾭ"=>"ᾥ","ᾬ"=>"ᾤ", - "ᾫ"=>"ᾣ","ᾪ"=>"ᾢ","ᾩ"=>"ᾡ","ᾟ"=>"ᾗ","ᾞ"=>"ᾖ","ᾝ"=>"ᾕ","ᾜ"=>"ᾔ","ᾛ"=>"ᾓ","ᾚ"=>"ᾒ","ᾙ"=>"ᾑ", - "ᾘ"=>"ᾐ","ᾏ"=>"ᾇ","ᾎ"=>"ᾆ","ᾍ"=>"ᾅ","ᾌ"=>"ᾄ","ᾋ"=>"ᾃ","ᾊ"=>"ᾂ","ᾉ"=>"ᾁ","ᾈ"=>"ᾀ","Ώ"=>"ώ", - "Ὼ"=>"ὼ","Ύ"=>"ύ","Ὺ"=>"ὺ","Ό"=>"ό","Ὸ"=>"ὸ","Ί"=>"ί","Ὶ"=>"ὶ","Ή"=>"ή","Ὴ"=>"ὴ","Έ"=>"έ", - "Ὲ"=>"ὲ","Ά"=>"ά","Ὰ"=>"ὰ","Ὧ"=>"ὧ","Ὦ"=>"ὦ","Ὥ"=>"ὥ","Ὤ"=>"ὤ","Ὣ"=>"ὣ","Ὢ"=>"ὢ","Ὡ"=>"ὡ", - "Ὗ"=>"ὗ","Ὕ"=>"ὕ","Ὓ"=>"ὓ","Ὑ"=>"ὑ","Ὅ"=>"ὅ","Ὄ"=>"ὄ","Ὃ"=>"ὃ","Ὂ"=>"ὂ","Ὁ"=>"ὁ","Ὀ"=>"ὀ", - "Ἷ"=>"ἷ","Ἶ"=>"ἶ","Ἵ"=>"ἵ","Ἴ"=>"ἴ","Ἳ"=>"ἳ","Ἲ"=>"ἲ","Ἱ"=>"ἱ","Ἰ"=>"ἰ","Ἧ"=>"ἧ","Ἦ"=>"ἦ", - "Ἥ"=>"ἥ","Ἤ"=>"ἤ","Ἣ"=>"ἣ","Ἢ"=>"ἢ","Ἡ"=>"ἡ","Ἕ"=>"ἕ","Ἔ"=>"ἔ","Ἓ"=>"ἓ","Ἒ"=>"ἒ","Ἑ"=>"ἑ", - "Ἐ"=>"ἐ","Ἇ"=>"ἇ","Ἆ"=>"ἆ","Ἅ"=>"ἅ","Ἄ"=>"ἄ","Ἃ"=>"ἃ","Ἂ"=>"ἂ","Ἁ"=>"ἁ","Ἀ"=>"ἀ","Ỹ"=>"ỹ", - "Ỷ"=>"ỷ","Ỵ"=>"ỵ","Ỳ"=>"ỳ","Ự"=>"ự","Ữ"=>"ữ","Ử"=>"ử","Ừ"=>"ừ","Ứ"=>"ứ","Ủ"=>"ủ","Ụ"=>"ụ", - "Ợ"=>"ợ","Ỡ"=>"ỡ","Ở"=>"ở","Ờ"=>"ờ","Ớ"=>"ớ","Ộ"=>"ộ","Ỗ"=>"ỗ","Ổ"=>"ổ","Ồ"=>"ồ","Ố"=>"ố", - "Ỏ"=>"ỏ","Ọ"=>"ọ","Ị"=>"ị","Ỉ"=>"ỉ","Ệ"=>"ệ","Ễ"=>"ễ","Ể"=>"ể","Ề"=>"ề","Ế"=>"ế","Ẽ"=>"ẽ", - "Ẻ"=>"ẻ","Ẹ"=>"ẹ","Ặ"=>"ặ","Ẵ"=>"ẵ","Ẳ"=>"ẳ","Ằ"=>"ằ","Ắ"=>"ắ","Ậ"=>"ậ","Ẫ"=>"ẫ","Ẩ"=>"ẩ", - "Ầ"=>"ầ","Ấ"=>"ấ","Ả"=>"ả","Ạ"=>"ạ","Ṡ"=>"ẛ","Ẕ"=>"ẕ","Ẓ"=>"ẓ","Ẑ"=>"ẑ","Ẏ"=>"ẏ","Ẍ"=>"ẍ", - "Ẋ"=>"ẋ","Ẉ"=>"ẉ","Ẇ"=>"ẇ","Ẅ"=>"ẅ","Ẃ"=>"ẃ","Ẁ"=>"ẁ","Ṿ"=>"ṿ","Ṽ"=>"ṽ","Ṻ"=>"ṻ","Ṹ"=>"ṹ", - "Ṷ"=>"ṷ","Ṵ"=>"ṵ","Ṳ"=>"ṳ","Ṱ"=>"ṱ","Ṯ"=>"ṯ","Ṭ"=>"ṭ","Ṫ"=>"ṫ","Ṩ"=>"ṩ","Ṧ"=>"ṧ","Ṥ"=>"ṥ", - "Ṣ"=>"ṣ","Ṡ"=>"ṡ","Ṟ"=>"ṟ","Ṝ"=>"ṝ","Ṛ"=>"ṛ","Ṙ"=>"ṙ","Ṗ"=>"ṗ","Ṕ"=>"ṕ","Ṓ"=>"ṓ","Ṑ"=>"ṑ", - "Ṏ"=>"ṏ","Ṍ"=>"ṍ","Ṋ"=>"ṋ","Ṉ"=>"ṉ","Ṇ"=>"ṇ","Ṅ"=>"ṅ","Ṃ"=>"ṃ","Ṁ"=>"ṁ","Ḿ"=>"ḿ","Ḽ"=>"ḽ", - "Ḻ"=>"ḻ","Ḹ"=>"ḹ","Ḷ"=>"ḷ","Ḵ"=>"ḵ","Ḳ"=>"ḳ","Ḱ"=>"ḱ","Ḯ"=>"ḯ","Ḭ"=>"ḭ","Ḫ"=>"ḫ","Ḩ"=>"ḩ", - "Ḧ"=>"ḧ","Ḥ"=>"ḥ","Ḣ"=>"ḣ","Ḡ"=>"ḡ","Ḟ"=>"ḟ","Ḝ"=>"ḝ","Ḛ"=>"ḛ","Ḙ"=>"ḙ","Ḗ"=>"ḗ","Ḕ"=>"ḕ", - "Ḓ"=>"ḓ","Ḑ"=>"ḑ","Ḏ"=>"ḏ","Ḍ"=>"ḍ","Ḋ"=>"ḋ","Ḉ"=>"ḉ","Ḇ"=>"ḇ","Ḅ"=>"ḅ","Ḃ"=>"ḃ","Ḁ"=>"ḁ", - "Ֆ"=>"ֆ","Օ"=>"օ","Ք"=>"ք","Փ"=>"փ","Ւ"=>"ւ","Ց"=>"ց","Ր"=>"ր","Տ"=>"տ","Վ"=>"վ","Ս"=>"ս", - "Ռ"=>"ռ","Ջ"=>"ջ","Պ"=>"պ","Չ"=>"չ","Ո"=>"ո","Շ"=>"շ","Ն"=>"ն","Յ"=>"յ","Մ"=>"մ","Ճ"=>"ճ", - "Ղ"=>"ղ","Ձ"=>"ձ","Հ"=>"հ","Կ"=>"կ","Ծ"=>"ծ","Խ"=>"խ","Լ"=>"լ","Ի"=>"ի","Ժ"=>"ժ","Թ"=>"թ", - "Ը"=>"ը","Է"=>"է","Զ"=>"զ","Ե"=>"ե","Դ"=>"դ","Գ"=>"գ","Բ"=>"բ","Ա"=>"ա","Ԏ"=>"ԏ","Ԍ"=>"ԍ", - "Ԋ"=>"ԋ","Ԉ"=>"ԉ","Ԇ"=>"ԇ","Ԅ"=>"ԅ","Ԃ"=>"ԃ","Ԁ"=>"ԁ","Ӹ"=>"ӹ","Ӵ"=>"ӵ","Ӳ"=>"ӳ","Ӱ"=>"ӱ", - "Ӯ"=>"ӯ","Ӭ"=>"ӭ","Ӫ"=>"ӫ","Ө"=>"ө","Ӧ"=>"ӧ","Ӥ"=>"ӥ","Ӣ"=>"ӣ","Ӡ"=>"ӡ","Ӟ"=>"ӟ","Ӝ"=>"ӝ", - "Ӛ"=>"ӛ","Ә"=>"ә","Ӗ"=>"ӗ","Ӕ"=>"ӕ","Ӓ"=>"ӓ","Ӑ"=>"ӑ","Ӎ"=>"ӎ","Ӌ"=>"ӌ","Ӊ"=>"ӊ","Ӈ"=>"ӈ", - "Ӆ"=>"ӆ","Ӄ"=>"ӄ","Ӂ"=>"ӂ","Ҿ"=>"ҿ","Ҽ"=>"ҽ","Һ"=>"һ","Ҹ"=>"ҹ","Ҷ"=>"ҷ","Ҵ"=>"ҵ","Ҳ"=>"ҳ", - "Ұ"=>"ұ","Ү"=>"ү","Ҭ"=>"ҭ","Ҫ"=>"ҫ","Ҩ"=>"ҩ","Ҧ"=>"ҧ","Ҥ"=>"ҥ","Ң"=>"ң","Ҡ"=>"ҡ","Ҟ"=>"ҟ", - "Ҝ"=>"ҝ","Қ"=>"қ","Ҙ"=>"ҙ","Җ"=>"җ","Ҕ"=>"ҕ","Ғ"=>"ғ","Ґ"=>"ґ","Ҏ"=>"ҏ","Ҍ"=>"ҍ","Ҋ"=>"ҋ", - "Ҁ"=>"ҁ","Ѿ"=>"ѿ","Ѽ"=>"ѽ","Ѻ"=>"ѻ","Ѹ"=>"ѹ","Ѷ"=>"ѷ","Ѵ"=>"ѵ","Ѳ"=>"ѳ","Ѱ"=>"ѱ","Ѯ"=>"ѯ", - "Ѭ"=>"ѭ","Ѫ"=>"ѫ","Ѩ"=>"ѩ","Ѧ"=>"ѧ","Ѥ"=>"ѥ","Ѣ"=>"ѣ","Ѡ"=>"ѡ","Џ"=>"џ","Ў"=>"ў","Ѝ"=>"ѝ", - "Ќ"=>"ќ","Ћ"=>"ћ","Њ"=>"њ","Љ"=>"љ","Ј"=>"ј","Ї"=>"ї","І"=>"і","Ѕ"=>"ѕ","Є"=>"є","Ѓ"=>"ѓ", - "Ђ"=>"ђ","Ё"=>"ё","Ѐ"=>"ѐ","Я"=>"я","Ю"=>"ю","Э"=>"э","Ь"=>"ь","Ы"=>"ы","Ъ"=>"ъ","Щ"=>"щ", - "Ш"=>"ш","Ч"=>"ч","Ц"=>"ц","Х"=>"х","Ф"=>"ф","У"=>"у","Т"=>"т","С"=>"с","Р"=>"р","П"=>"п", - "О"=>"о","Н"=>"н","М"=>"м","Л"=>"л","К"=>"к","Й"=>"й","И"=>"и","З"=>"з","Ж"=>"ж","Е"=>"е", - "Д"=>"д","Г"=>"г","В"=>"в","Б"=>"б","А"=>"а","Ε"=>"ϵ","Σ"=>"ϲ","Ρ"=>"ϱ","Κ"=>"ϰ","Ϯ"=>"ϯ", - "Ϭ"=>"ϭ","Ϫ"=>"ϫ","Ϩ"=>"ϩ","Ϧ"=>"ϧ","Ϥ"=>"ϥ","Ϣ"=>"ϣ","Ϡ"=>"ϡ","Ϟ"=>"ϟ","Ϝ"=>"ϝ","Ϛ"=>"ϛ", - "Ϙ"=>"ϙ","Π"=>"ϖ","Φ"=>"ϕ","Θ"=>"ϑ","Β"=>"ϐ","Ώ"=>"ώ","Ύ"=>"ύ","Ό"=>"ό","Ϋ"=>"ϋ","Ϊ"=>"ϊ", - "Ω"=>"ω","Ψ"=>"ψ","Χ"=>"χ","Φ"=>"φ","Υ"=>"υ","Τ"=>"τ","Σ"=>"σ","Σ"=>"ς","Ρ"=>"ρ","Π"=>"π", - "Ο"=>"ο","Ξ"=>"ξ","Ν"=>"ν","Μ"=>"μ","Λ"=>"λ","Κ"=>"κ","Ι"=>"ι","Θ"=>"θ","Η"=>"η","Ζ"=>"ζ", - "Ε"=>"ε","Δ"=>"δ","Γ"=>"γ","Β"=>"β","Α"=>"α","Ί"=>"ί","Ή"=>"ή","Έ"=>"έ","Ά"=>"ά","Ʒ"=>"ʒ", - "Ʋ"=>"ʋ","Ʊ"=>"ʊ","Ʈ"=>"ʈ","Ʃ"=>"ʃ","Ʀ"=>"ʀ","Ɵ"=>"ɵ","Ɲ"=>"ɲ","Ɯ"=>"ɯ","Ɩ"=>"ɩ","Ɨ"=>"ɨ", - "Ɣ"=>"ɣ","Ɛ"=>"ɛ","Ə"=>"ə","Ɗ"=>"ɗ","Ɖ"=>"ɖ","Ɔ"=>"ɔ","Ɓ"=>"ɓ","Ȳ"=>"ȳ","Ȱ"=>"ȱ","Ȯ"=>"ȯ", - "Ȭ"=>"ȭ","Ȫ"=>"ȫ","Ȩ"=>"ȩ","Ȧ"=>"ȧ","Ȥ"=>"ȥ","Ȣ"=>"ȣ","Ȟ"=>"ȟ","Ȝ"=>"ȝ","Ț"=>"ț","Ș"=>"ș", - "Ȗ"=>"ȗ","Ȕ"=>"ȕ","Ȓ"=>"ȓ","Ȑ"=>"ȑ","Ȏ"=>"ȏ","Ȍ"=>"ȍ","Ȋ"=>"ȋ","Ȉ"=>"ȉ","Ȇ"=>"ȇ","Ȅ"=>"ȅ", - "Ȃ"=>"ȃ","Ȁ"=>"ȁ","Ǿ"=>"ǿ","Ǽ"=>"ǽ","Ǻ"=>"ǻ","Ǹ"=>"ǹ","Ǵ"=>"ǵ","Dz"=>"dz","Ǯ"=>"ǯ","Ǭ"=>"ǭ", - "Ǫ"=>"ǫ","Ǩ"=>"ǩ","Ǧ"=>"ǧ","Ǥ"=>"ǥ","Ǣ"=>"ǣ","Ǡ"=>"ǡ","Ǟ"=>"ǟ","Ǝ"=>"ǝ","Ǜ"=>"ǜ","Ǚ"=>"ǚ", - "Ǘ"=>"ǘ","Ǖ"=>"ǖ","Ǔ"=>"ǔ","Ǒ"=>"ǒ","Ǐ"=>"ǐ","Ǎ"=>"ǎ","Nj"=>"nj","Lj"=>"lj","Dž"=>"dž","Ƿ"=>"ƿ", - "Ƽ"=>"ƽ","Ƹ"=>"ƹ","Ƶ"=>"ƶ","Ƴ"=>"ƴ","Ư"=>"ư","Ƭ"=>"ƭ","Ƨ"=>"ƨ","Ƥ"=>"ƥ","Ƣ"=>"ƣ","Ơ"=>"ơ", - "Ƞ"=>"ƞ","Ƙ"=>"ƙ","Ƕ"=>"ƕ","Ƒ"=>"ƒ","Ƌ"=>"ƌ","Ƈ"=>"ƈ","Ƅ"=>"ƅ","Ƃ"=>"ƃ","S"=>"ſ","Ž"=>"ž", - "Ż"=>"ż","Ź"=>"ź","Ŷ"=>"ŷ","Ŵ"=>"ŵ","Ų"=>"ų","Ű"=>"ű","Ů"=>"ů","Ŭ"=>"ŭ","Ū"=>"ū","Ũ"=>"ũ", - "Ŧ"=>"ŧ","Ť"=>"ť","Ţ"=>"ţ","Š"=>"š","Ş"=>"ş","Ŝ"=>"ŝ","Ś"=>"ś","Ř"=>"ř","Ŗ"=>"ŗ","Ŕ"=>"ŕ", - "Œ"=>"œ","Ő"=>"ő","Ŏ"=>"ŏ","Ō"=>"ō","Ŋ"=>"ŋ","Ň"=>"ň","Ņ"=>"ņ","Ń"=>"ń","Ł"=>"ł","Ŀ"=>"ŀ", - "Ľ"=>"ľ","Ļ"=>"ļ","Ĺ"=>"ĺ","Ķ"=>"ķ","Ĵ"=>"ĵ","IJ"=>"ij","I"=>"ı","Į"=>"į","Ĭ"=>"ĭ","Ī"=>"ī", - "Ĩ"=>"ĩ","Ħ"=>"ħ","Ĥ"=>"ĥ","Ģ"=>"ģ","Ġ"=>"ġ","Ğ"=>"ğ","Ĝ"=>"ĝ","Ě"=>"ě","Ę"=>"ę","Ė"=>"ė", - "Ĕ"=>"ĕ","Ē"=>"ē","Đ"=>"đ","Ď"=>"ď","Č"=>"č","Ċ"=>"ċ","Ĉ"=>"ĉ","Ć"=>"ć","Ą"=>"ą","Ă"=>"ă", - "Ā"=>"ā","Ÿ"=>"ÿ","Þ"=>"þ","Ý"=>"ý","Ü"=>"ü","Û"=>"û","Ú"=>"ú","Ù"=>"ù","Ø"=>"ø","Ö"=>"ö", - "Õ"=>"õ","Ô"=>"ô","Ó"=>"ó","Ò"=>"ò","Ñ"=>"ñ","Ð"=>"ð","Ï"=>"ï","Î"=>"î","Í"=>"í","Ì"=>"ì", - "Ë"=>"ë","Ê"=>"ê","É"=>"é","È"=>"è","Ç"=>"ç","Æ"=>"æ","Å"=>"å","Ä"=>"ä","Ã"=>"ã","Â"=>"â", - "Á"=>"á","À"=>"à","Μ"=>"µ","Z"=>"z","Y"=>"y","X"=>"x","W"=>"w","V"=>"v","U"=>"u","T"=>"t", - "S"=>"s","R"=>"r","Q"=>"q","P"=>"p","O"=>"o","N"=>"n","M"=>"m","L"=>"l","K"=>"k","J"=>"j", - "I"=>"i","H"=>"h","G"=>"g","F"=>"f","E"=>"e","D"=>"d","C"=>"c","B"=>"b","A"=>"a" - ); -}; // end of case lookup tables - -/** - * UTF-8 lookup table for lower case accented letters - * - * This lookuptable defines replacements for accented characters from the ASCII-7 - * range. This are lower case letters only. - * - * @author Andreas Gohr <andi@splitbrain.org> - * @see utf8_deaccent() - */ -global $UTF8_LOWER_ACCENTS; -if(empty($UTF8_LOWER_ACCENTS)) $UTF8_LOWER_ACCENTS = array( - 'à' => 'a', 'ô' => 'o', 'ď' => 'd', 'ḟ' => 'f', 'ë' => 'e', 'š' => 's', 'ơ' => 'o', - 'ß' => 'ss', 'ă' => 'a', 'ř' => 'r', 'ț' => 't', 'ň' => 'n', 'ā' => 'a', 'ķ' => 'k', - 'ŝ' => 's', 'ỳ' => 'y', 'ņ' => 'n', 'ĺ' => 'l', 'ħ' => 'h', 'ṗ' => 'p', 'ó' => 'o', - 'ú' => 'u', 'ě' => 'e', 'é' => 'e', 'ç' => 'c', 'ẁ' => 'w', 'ċ' => 'c', 'õ' => 'o', - 'ṡ' => 's', 'ø' => 'o', 'ģ' => 'g', 'ŧ' => 't', 'ș' => 's', 'ė' => 'e', 'ĉ' => 'c', - 'ś' => 's', 'î' => 'i', 'ű' => 'u', 'ć' => 'c', 'ę' => 'e', 'ŵ' => 'w', 'ṫ' => 't', - 'ū' => 'u', 'č' => 'c', 'ö' => 'oe', 'è' => 'e', 'ŷ' => 'y', 'ą' => 'a', 'ł' => 'l', - 'ų' => 'u', 'ů' => 'u', 'ş' => 's', 'ğ' => 'g', 'ļ' => 'l', 'ƒ' => 'f', 'ž' => 'z', - 'ẃ' => 'w', 'ḃ' => 'b', 'å' => 'a', 'ì' => 'i', 'ï' => 'i', 'ḋ' => 'd', 'ť' => 't', - 'ŗ' => 'r', 'ä' => 'ae', 'í' => 'i', 'ŕ' => 'r', 'ê' => 'e', 'ü' => 'ue', 'ò' => 'o', - 'ē' => 'e', 'ñ' => 'n', 'ń' => 'n', 'ĥ' => 'h', 'ĝ' => 'g', 'đ' => 'd', 'ĵ' => 'j', - 'ÿ' => 'y', 'ũ' => 'u', 'ŭ' => 'u', 'ư' => 'u', 'ţ' => 't', 'ý' => 'y', 'ő' => 'o', - 'â' => 'a', 'ľ' => 'l', 'ẅ' => 'w', 'ż' => 'z', 'ī' => 'i', 'ã' => 'a', 'ġ' => 'g', - 'ṁ' => 'm', 'ō' => 'o', 'ĩ' => 'i', 'ù' => 'u', 'į' => 'i', 'ź' => 'z', 'á' => 'a', - 'û' => 'u', 'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u', 'ĕ' => 'e', -); - -/** - * UTF-8 lookup table for upper case accented letters - * - * This lookuptable defines replacements for accented characters from the ASCII-7 - * range. This are upper case letters only. - * - * @author Andreas Gohr <andi@splitbrain.org> - * @see utf8_deaccent() - */ -global $UTF8_UPPER_ACCENTS; -if(empty($UTF8_UPPER_ACCENTS)) $UTF8_UPPER_ACCENTS = array( - 'À' => 'A', 'Ô' => 'O', 'Ď' => 'D', 'Ḟ' => 'F', 'Ë' => 'E', 'Š' => 'S', 'Ơ' => 'O', - 'Ă' => 'A', 'Ř' => 'R', 'Ț' => 'T', 'Ň' => 'N', 'Ā' => 'A', 'Ķ' => 'K', - 'Ŝ' => 'S', 'Ỳ' => 'Y', 'Ņ' => 'N', 'Ĺ' => 'L', 'Ħ' => 'H', 'Ṗ' => 'P', 'Ó' => 'O', - 'Ú' => 'U', 'Ě' => 'E', 'É' => 'E', 'Ç' => 'C', 'Ẁ' => 'W', 'Ċ' => 'C', 'Õ' => 'O', - 'Ṡ' => 'S', 'Ø' => 'O', 'Ģ' => 'G', 'Ŧ' => 'T', 'Ș' => 'S', 'Ė' => 'E', 'Ĉ' => 'C', - 'Ś' => 'S', 'Î' => 'I', 'Ű' => 'U', 'Ć' => 'C', 'Ę' => 'E', 'Ŵ' => 'W', 'Ṫ' => 'T', - 'Ū' => 'U', 'Č' => 'C', 'Ö' => 'Oe', 'È' => 'E', 'Ŷ' => 'Y', 'Ą' => 'A', 'Ł' => 'L', - 'Ų' => 'U', 'Ů' => 'U', 'Ş' => 'S', 'Ğ' => 'G', 'Ļ' => 'L', 'Ƒ' => 'F', 'Ž' => 'Z', - 'Ẃ' => 'W', 'Ḃ' => 'B', 'Å' => 'A', 'Ì' => 'I', 'Ï' => 'I', 'Ḋ' => 'D', 'Ť' => 'T', - 'Ŗ' => 'R', 'Ä' => 'Ae', 'Í' => 'I', 'Ŕ' => 'R', 'Ê' => 'E', 'Ü' => 'Ue', 'Ò' => 'O', - 'Ē' => 'E', 'Ñ' => 'N', 'Ń' => 'N', 'Ĥ' => 'H', 'Ĝ' => 'G', 'Đ' => 'D', 'Ĵ' => 'J', - 'Ÿ' => 'Y', 'Ũ' => 'U', 'Ŭ' => 'U', 'Ư' => 'U', 'Ţ' => 'T', 'Ý' => 'Y', 'Ő' => 'O', - 'Â' => 'A', 'Ľ' => 'L', 'Ẅ' => 'W', 'Ż' => 'Z', 'Ī' => 'I', 'Ã' => 'A', 'Ġ' => 'G', - 'Ṁ' => 'M', 'Ō' => 'O', 'Ĩ' => 'I', 'Ù' => 'U', 'Į' => 'I', 'Ź' => 'Z', 'Á' => 'A', - 'Û' => 'U', 'Þ' => 'Th', 'Ð' => 'Dh', 'Æ' => 'Ae', 'Ĕ' => 'E', -); - -/** - * UTF-8 array of common special characters - * - * This array should contain all special characters (not a letter or digit) - * defined in the various local charsets - it's not a complete list of non-alphanum - * characters in UTF-8. It's not perfect but should match most cases of special - * chars. - * - * The controlchars 0x00 to 0x19 are _not_ included in this array. The space 0x20 is! - * These chars are _not_ in the array either: _ (0x5f), : 0x3a, . 0x2e, - 0x2d, * 0x2a - * - * @author Andreas Gohr <andi@splitbrain.org> - * @see utf8_stripspecials() - */ -global $UTF8_SPECIAL_CHARS; -if(empty($UTF8_SPECIAL_CHARS)) $UTF8_SPECIAL_CHARS = array( - 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, - 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002b, 0x002c, - 0x002f, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x005b, - 0x005c, 0x005d, 0x005e, 0x0060, 0x007b, 0x007c, 0x007d, 0x007e, - 0x007f, 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, - 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, 0x0090, 0x0091, 0x0092, - 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, - 0x009d, 0x009e, 0x009f, 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, - 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, 0x00b0, - 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, - 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, 0x00d7, 0x00f7, 0x02c7, 0x02d8, 0x02d9, - 0x02da, 0x02db, 0x02dc, 0x02dd, 0x0300, 0x0301, 0x0303, 0x0309, 0x0323, 0x0384, - 0x0385, 0x0387, 0x03c6, 0x03d1, 0x03d2, 0x03d5, 0x03d6, 0x05b0, 0x05b1, - 0x05b2, 0x05b3, 0x05b4, 0x05b5, 0x05b6, 0x05b7, 0x05b8, 0x05b9, 0x05bb, 0x05bc, - 0x05bd, 0x05be, 0x05bf, 0x05c0, 0x05c1, 0x05c2, 0x05c3, 0x05f3, 0x05f4, 0x060c, - 0x061b, 0x061f, 0x0640, 0x064b, 0x064c, 0x064d, 0x064e, 0x064f, 0x0650, 0x0651, - 0x0652, 0x066a, 0x0e3f, 0x200c, 0x200d, 0x200e, 0x200f, 0x2013, 0x2014, 0x2015, - 0x2017, 0x2018, 0x2019, 0x201a, 0x201c, 0x201d, 0x201e, 0x2020, 0x2021, 0x2022, - 0x2026, 0x2030, 0x2032, 0x2033, 0x2039, 0x203a, 0x2044, 0x20a7, 0x20aa, 0x20ab, - 0x20ac, 0x2116, 0x2118, 0x2122, 0x2126, 0x2135, 0x2190, 0x2191, 0x2192, 0x2193, - 0x2194, 0x2195, 0x21b5, 0x21d0, 0x21d1, 0x21d2, 0x21d3, 0x21d4, 0x2200, 0x2202, - 0x2203, 0x2205, 0x2206, 0x2207, 0x2208, 0x2209, 0x220b, 0x220f, 0x2211, 0x2212, - 0x2215, 0x2217, 0x2219, 0x221a, 0x221d, 0x221e, 0x2220, 0x2227, 0x2228, 0x2229, - 0x222a, 0x222b, 0x2234, 0x223c, 0x2245, 0x2248, 0x2260, 0x2261, 0x2264, 0x2265, - 0x2282, 0x2283, 0x2284, 0x2286, 0x2287, 0x2295, 0x2297, 0x22a5, 0x22c5, 0x2310, - 0x2320, 0x2321, 0x2329, 0x232a, 0x2469, 0x2500, 0x2502, 0x250c, 0x2510, 0x2514, - 0x2518, 0x251c, 0x2524, 0x252c, 0x2534, 0x253c, 0x2550, 0x2551, 0x2552, 0x2553, - 0x2554, 0x2555, 0x2556, 0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x255c, 0x255d, - 0x255e, 0x255f, 0x2560, 0x2561, 0x2562, 0x2563, 0x2564, 0x2565, 0x2566, 0x2567, - 0x2568, 0x2569, 0x256a, 0x256b, 0x256c, 0x2580, 0x2584, 0x2588, 0x258c, 0x2590, - 0x2591, 0x2592, 0x2593, 0x25a0, 0x25b2, 0x25bc, 0x25c6, 0x25ca, 0x25cf, 0x25d7, - 0x2605, 0x260e, 0x261b, 0x261e, 0x2660, 0x2663, 0x2665, 0x2666, 0x2701, 0x2702, - 0x2703, 0x2704, 0x2706, 0x2707, 0x2708, 0x2709, 0x270c, 0x270d, 0x270e, 0x270f, - 0x2710, 0x2711, 0x2712, 0x2713, 0x2714, 0x2715, 0x2716, 0x2717, 0x2718, 0x2719, - 0x271a, 0x271b, 0x271c, 0x271d, 0x271e, 0x271f, 0x2720, 0x2721, 0x2722, 0x2723, - 0x2724, 0x2725, 0x2726, 0x2727, 0x2729, 0x272a, 0x272b, 0x272c, 0x272d, 0x272e, - 0x272f, 0x2730, 0x2731, 0x2732, 0x2733, 0x2734, 0x2735, 0x2736, 0x2737, 0x2738, - 0x2739, 0x273a, 0x273b, 0x273c, 0x273d, 0x273e, 0x273f, 0x2740, 0x2741, 0x2742, - 0x2743, 0x2744, 0x2745, 0x2746, 0x2747, 0x2748, 0x2749, 0x274a, 0x274b, 0x274d, - 0x274f, 0x2750, 0x2751, 0x2752, 0x2756, 0x2758, 0x2759, 0x275a, 0x275b, 0x275c, - 0x275d, 0x275e, 0x2761, 0x2762, 0x2763, 0x2764, 0x2765, 0x2766, 0x2767, 0x277f, - 0x2789, 0x2793, 0x2794, 0x2798, 0x2799, 0x279a, 0x279b, 0x279c, 0x279d, 0x279e, - 0x279f, 0x27a0, 0x27a1, 0x27a2, 0x27a3, 0x27a4, 0x27a5, 0x27a6, 0x27a7, 0x27a8, - 0x27a9, 0x27aa, 0x27ab, 0x27ac, 0x27ad, 0x27ae, 0x27af, 0x27b1, 0x27b2, 0x27b3, - 0x27b4, 0x27b5, 0x27b6, 0x27b7, 0x27b8, 0x27b9, 0x27ba, 0x27bb, 0x27bc, 0x27bd, - 0x27be, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x300c, - 0x300d, 0x300e, 0x300f, 0x3010, 0x3011, 0x3012, 0x3014, 0x3015, 0x3016, 0x3017, - 0x3018, 0x3019, 0x301a, 0x301b, 0x3036, - 0xf6d9, 0xf6da, 0xf6db, 0xf8d7, 0xf8d8, 0xf8d9, 0xf8da, 0xf8db, 0xf8dc, - 0xf8dd, 0xf8de, 0xf8df, 0xf8e0, 0xf8e1, 0xf8e2, 0xf8e3, 0xf8e4, 0xf8e5, 0xf8e6, - 0xf8e7, 0xf8e8, 0xf8e9, 0xf8ea, 0xf8eb, 0xf8ec, 0xf8ed, 0xf8ee, 0xf8ef, 0xf8f0, - 0xf8f1, 0xf8f2, 0xf8f3, 0xf8f4, 0xf8f5, 0xf8f6, 0xf8f7, 0xf8f8, 0xf8f9, 0xf8fa, - 0xf8fb, 0xf8fc, 0xf8fd, 0xf8fe, 0xfe7c, 0xfe7d, - 0xff01, 0xff02, 0xff03, 0xff04, 0xff05, 0xff06, 0xff07, 0xff08, 0xff09, - 0xff09, 0xff0a, 0xff0b, 0xff0c, 0xff0d, 0xff0e, 0xff0f, 0xff1a, 0xff1b, 0xff1c, - 0xff1d, 0xff1e, 0xff1f, 0xff20, 0xff3b, 0xff3c, 0xff3d, 0xff3e, 0xff40, 0xff5b, - 0xff5c, 0xff5d, 0xff5e, 0xff5f, 0xff60, 0xff61, 0xff62, 0xff63, 0xff64, 0xff65, - 0xffe0, 0xffe1, 0xffe2, 0xffe3, 0xffe4, 0xffe5, 0xffe6, 0xffe8, 0xffe9, 0xffea, - 0xffeb, 0xffec, 0xffed, 0xffee, - 0x01d6fc, 0x01d6fd, 0x01d6fe, 0x01d6ff, 0x01d700, 0x01d701, 0x01d702, 0x01d703, - 0x01d704, 0x01d705, 0x01d706, 0x01d707, 0x01d708, 0x01d709, 0x01d70a, 0x01d70b, - 0x01d70c, 0x01d70d, 0x01d70e, 0x01d70f, 0x01d710, 0x01d711, 0x01d712, 0x01d713, - 0x01d714, 0x01d715, 0x01d716, 0x01d717, 0x01d718, 0x01d719, 0x01d71a, 0x01d71b, - 0xc2a0, 0xe28087, 0xe280af, 0xe281a0, 0xefbbbf, -); - -// utf8 version of above data -global $UTF8_SPECIAL_CHARS2; -if(empty($UTF8_SPECIAL_CHARS2)) $UTF8_SPECIAL_CHARS2 = - "\x1A".' !"#$%&\'()+,/;<=>?@[\]^`{|}~
�'. - '� ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½�'. - '�¿×÷ˇ˘˙˚˛˜˝̣̀́̃̉΄΅·ϖְֱֲֳִֵֶַָֹֻּֽ־ֿ�'. - '�ׁׂ׃׳״،؛؟ـًٌٍَُِّْ٪฿–—―‗‘’‚“”�'. - '��†‡•…‰′″‹›⁄₧₪₫€№℘™Ωℵ←↑→↓↔↕↵'. - '⇐⇑⇒⇓⇔∀∂∃∅∆∇∈∉∋∏∑−∕∗∙√∝∞∠∧∨�'. - '�∪∫∴∼≅≈≠≡≤≥⊂⊃⊄⊆⊇⊕⊗⊥⋅⌐⌠⌡〈〉⑩─�'. - '��┌┐└┘├┤┬┴┼═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠'. - '╡╢╣╤╥╦╧╨╩╪╫╬▀▄█▌▐░▒▓■▲▼◆◊●�'. - '�★☎☛☞♠♣♥♦✁✂✃✄✆✇✈✉✌✍✎✏✐✑✒✓✔✕�'. - '��✗✘✙✚✛✜✝✞✟✠✡✢✣✤✥✦✧✩✪✫✬✭✮✯✰✱'. - '✲✳✴✵✶✷✸✹✺✻✼✽✾✿❀❁❂❃❄❅❆❇❈❉❊❋�'. - '�❏❐❑❒❖❘❙❚❛❜❝❞❡❢❣❤❥❦❧❿➉➓➔➘➙➚�'. - '��➜➝➞➟➠➡➢➣➤➥➦➧➨➩➪➫➬➭➮➯➱➲➳➴➵➶'. - '➷➸➹➺➻➼➽➾'. - ' 、。〃〈〉《》「」『』【】〒〔〕〖〗〘〙〚〛〶'. - '�'. - '�ﹼﹽ'. - '!"#$%&'()*+,-./:;<=>?@[\]^`{|}~'. - '⦅⦆。「」、・¢£¬ ̄¦¥₩│←↑→↓■○'. - '𝛼𝛽𝛾𝛿𝜀𝜁𝜂𝜃𝜄𝜅𝜆𝜇𝜈𝜉𝜊𝜋𝜌𝜍𝜎𝜏𝜐𝜑𝜒𝜓𝜔𝜕𝜖𝜗𝜘𝜙𝜚𝜛'. - ' '; - -/** - * Romanization lookup table - * - * This lookup tables provides a way to transform strings written in a language - * different from the ones based upon latin letters into plain ASCII. - * - * Please note: this is not a scientific transliteration table. It only works - * oneway from nonlatin to ASCII and it works by simple character replacement - * only. Specialities of each language are not supported. - * - * @author Andreas Gohr <andi@splitbrain.org> - * @author Vitaly Blokhin <vitinfo@vitn.com> - * @link http://www.uconv.com/translit.htm - * @author Bisqwit <bisqwit@iki.fi> - * @link http://kanjidict.stc.cx/hiragana.php?src=2 - * @link http://www.translatum.gr/converter/greek-transliteration.htm - * @link http://en.wikipedia.org/wiki/Royal_Thai_General_System_of_Transcription - * @link http://www.btranslations.com/resources/romanization/korean.asp - * @author Arthit Suriyawongkul <arthit@gmail.com> - * @author Denis Scheither <amorphis@uni-bremen.de> - * @author Eivind Morland <eivind.morland@gmail.com> - */ -global $UTF8_ROMANIZATION; -if(empty($UTF8_ROMANIZATION)) $UTF8_ROMANIZATION = array( - // scandinavian - differs from what we do in deaccent - 'å'=>'a','Å'=>'A','ä'=>'a','Ä'=>'A','ö'=>'o','Ö'=>'O', - - //russian cyrillic - 'а'=>'a','А'=>'A','б'=>'b','Б'=>'B','в'=>'v','В'=>'V','г'=>'g','Г'=>'G', - 'д'=>'d','Д'=>'D','е'=>'e','Е'=>'E','ё'=>'jo','Ё'=>'Jo','ж'=>'zh','Ж'=>'Zh', - 'з'=>'z','З'=>'Z','и'=>'i','И'=>'I','й'=>'j','Й'=>'J','к'=>'k','К'=>'K', - 'л'=>'l','Л'=>'L','м'=>'m','М'=>'M','н'=>'n','Н'=>'N','о'=>'o','О'=>'O', - 'п'=>'p','П'=>'P','р'=>'r','Р'=>'R','с'=>'s','С'=>'S','т'=>'t','Т'=>'T', - 'у'=>'u','У'=>'U','ф'=>'f','Ф'=>'F','х'=>'x','Х'=>'X','ц'=>'c','Ц'=>'C', - 'ч'=>'ch','Ч'=>'Ch','ш'=>'sh','Ш'=>'Sh','щ'=>'sch','Щ'=>'Sch','ъ'=>'', - 'Ъ'=>'','ы'=>'y','Ы'=>'Y','ь'=>'','Ь'=>'','э'=>'eh','Э'=>'Eh','ю'=>'ju', - 'Ю'=>'Ju','я'=>'ja','Я'=>'Ja', - // Ukrainian cyrillic - 'Ґ'=>'Gh','ґ'=>'gh','Є'=>'Je','є'=>'je','І'=>'I','і'=>'i','Ї'=>'Ji','ї'=>'ji', - // Georgian - 'ა'=>'a','ბ'=>'b','გ'=>'g','დ'=>'d','ე'=>'e','ვ'=>'v','ზ'=>'z','თ'=>'th', - 'ი'=>'i','კ'=>'p','ლ'=>'l','მ'=>'m','ნ'=>'n','ო'=>'o','პ'=>'p','ჟ'=>'zh', - 'რ'=>'r','ს'=>'s','ტ'=>'t','უ'=>'u','ფ'=>'ph','ქ'=>'kh','ღ'=>'gh','ყ'=>'q', - 'შ'=>'sh','ჩ'=>'ch','ც'=>'c','ძ'=>'dh','წ'=>'w','ჭ'=>'j','ხ'=>'x','ჯ'=>'jh', - 'ჰ'=>'xh', - //Sanskrit - 'अ'=>'a','आ'=>'ah','इ'=>'i','ई'=>'ih','उ'=>'u','ऊ'=>'uh','ऋ'=>'ry', - 'ॠ'=>'ryh','ऌ'=>'ly','ॡ'=>'lyh','ए'=>'e','ऐ'=>'ay','ओ'=>'o','औ'=>'aw', - 'अं'=>'amh','अः'=>'aq','क'=>'k','ख'=>'kh','ग'=>'g','घ'=>'gh','ङ'=>'nh', - 'च'=>'c','छ'=>'ch','ज'=>'j','झ'=>'jh','ञ'=>'ny','ट'=>'tq','ठ'=>'tqh', - 'ड'=>'dq','ढ'=>'dqh','ण'=>'nq','त'=>'t','थ'=>'th','द'=>'d','ध'=>'dh', - 'न'=>'n','प'=>'p','फ'=>'ph','ब'=>'b','भ'=>'bh','म'=>'m','य'=>'z','र'=>'r', - 'ल'=>'l','व'=>'v','श'=>'sh','ष'=>'sqh','स'=>'s','ह'=>'x', - //Sanskrit diacritics - 'Ā'=>'A','Ī'=>'I','Ū'=>'U','Ṛ'=>'R','Ṝ'=>'R','Ṅ'=>'N','Ñ'=>'N','Ṭ'=>'T', - 'Ḍ'=>'D','Ṇ'=>'N','Ś'=>'S','Ṣ'=>'S','Ṁ'=>'M','Ṃ'=>'M','Ḥ'=>'H','Ḷ'=>'L','Ḹ'=>'L', - 'ā'=>'a','ī'=>'i','ū'=>'u','ṛ'=>'r','ṝ'=>'r','ṅ'=>'n','ñ'=>'n','ṭ'=>'t', - 'ḍ'=>'d','ṇ'=>'n','ś'=>'s','ṣ'=>'s','ṁ'=>'m','ṃ'=>'m','ḥ'=>'h','ḷ'=>'l','ḹ'=>'l', - //Hebrew - 'א'=>'a', 'ב'=>'b','ג'=>'g','ד'=>'d','ה'=>'h','ו'=>'v','ז'=>'z','ח'=>'kh','ט'=>'th', - 'י'=>'y','ך'=>'h','כ'=>'k','ל'=>'l','ם'=>'m','מ'=>'m','ן'=>'n','נ'=>'n', - 'ס'=>'s','ע'=>'ah','ף'=>'f','פ'=>'p','ץ'=>'c','צ'=>'c','ק'=>'q','ר'=>'r', - 'ש'=>'sh','ת'=>'t', - //Arabic - 'ا'=>'a','ب'=>'b','ت'=>'t','ث'=>'th','ج'=>'g','ح'=>'xh','خ'=>'x','د'=>'d', - 'ذ'=>'dh','ر'=>'r','ز'=>'z','س'=>'s','ش'=>'sh','ص'=>'s\'','ض'=>'d\'', - 'ط'=>'t\'','ظ'=>'z\'','ع'=>'y','غ'=>'gh','ف'=>'f','ق'=>'q','ك'=>'k', - 'ل'=>'l','م'=>'m','ن'=>'n','ه'=>'x\'','و'=>'u','ي'=>'i', - - // Japanese characters (last update: 2008-05-09) - - // Japanese hiragana - - // 3 character syllables, っ doubles the consonant after - 'っちゃ'=>'ccha','っちぇ'=>'cche','っちょ'=>'ccho','っちゅ'=>'cchu', - 'っびゃ'=>'bbya','っびぇ'=>'bbye','っびぃ'=>'bbyi','っびょ'=>'bbyo','っびゅ'=>'bbyu', - 'っぴゃ'=>'ppya','っぴぇ'=>'ppye','っぴぃ'=>'ppyi','っぴょ'=>'ppyo','っぴゅ'=>'ppyu', - 'っちゃ'=>'ccha','っちぇ'=>'cche','っち'=>'cchi','っちょ'=>'ccho','っちゅ'=>'cchu', - // 'っひゃ'=>'hya','っひぇ'=>'hye','っひぃ'=>'hyi','っひょ'=>'hyo','っひゅ'=>'hyu', - 'っきゃ'=>'kkya','っきぇ'=>'kkye','っきぃ'=>'kkyi','っきょ'=>'kkyo','っきゅ'=>'kkyu', - 'っぎゃ'=>'ggya','っぎぇ'=>'ggye','っぎぃ'=>'ggyi','っぎょ'=>'ggyo','っぎゅ'=>'ggyu', - 'っみゃ'=>'mmya','っみぇ'=>'mmye','っみぃ'=>'mmyi','っみょ'=>'mmyo','っみゅ'=>'mmyu', - 'っにゃ'=>'nnya','っにぇ'=>'nnye','っにぃ'=>'nnyi','っにょ'=>'nnyo','っにゅ'=>'nnyu', - 'っりゃ'=>'rrya','っりぇ'=>'rrye','っりぃ'=>'rryi','っりょ'=>'rryo','っりゅ'=>'rryu', - 'っしゃ'=>'ssha','っしぇ'=>'sshe','っし'=>'sshi','っしょ'=>'ssho','っしゅ'=>'sshu', - - // seperate hiragana 'n' ('n' + 'i' != 'ni', normally we would write "kon'nichi wa" but the apostrophe would be converted to _ anyway) - 'んあ'=>'n_a','んえ'=>'n_e','んい'=>'n_i','んお'=>'n_o','んう'=>'n_u', - 'んや'=>'n_ya','んよ'=>'n_yo','んゆ'=>'n_yu', - - // 2 character syllables - normal - 'ふぁ'=>'fa','ふぇ'=>'fe','ふぃ'=>'fi','ふぉ'=>'fo', - 'ちゃ'=>'cha','ちぇ'=>'che','ち'=>'chi','ちょ'=>'cho','ちゅ'=>'chu', - 'ひゃ'=>'hya','ひぇ'=>'hye','ひぃ'=>'hyi','ひょ'=>'hyo','ひゅ'=>'hyu', - 'びゃ'=>'bya','びぇ'=>'bye','びぃ'=>'byi','びょ'=>'byo','びゅ'=>'byu', - 'ぴゃ'=>'pya','ぴぇ'=>'pye','ぴぃ'=>'pyi','ぴょ'=>'pyo','ぴゅ'=>'pyu', - 'きゃ'=>'kya','きぇ'=>'kye','きぃ'=>'kyi','きょ'=>'kyo','きゅ'=>'kyu', - 'ぎゃ'=>'gya','ぎぇ'=>'gye','ぎぃ'=>'gyi','ぎょ'=>'gyo','ぎゅ'=>'gyu', - 'みゃ'=>'mya','みぇ'=>'mye','みぃ'=>'myi','みょ'=>'myo','みゅ'=>'myu', - 'にゃ'=>'nya','にぇ'=>'nye','にぃ'=>'nyi','にょ'=>'nyo','にゅ'=>'nyu', - 'りゃ'=>'rya','りぇ'=>'rye','りぃ'=>'ryi','りょ'=>'ryo','りゅ'=>'ryu', - 'しゃ'=>'sha','しぇ'=>'she','し'=>'shi','しょ'=>'sho','しゅ'=>'shu', - 'じゃ'=>'ja','じぇ'=>'je','じょ'=>'jo','じゅ'=>'ju', - 'うぇ'=>'we','うぃ'=>'wi', - 'いぇ'=>'ye', - - // 2 character syllables, っ doubles the consonant after - 'っば'=>'bba','っべ'=>'bbe','っび'=>'bbi','っぼ'=>'bbo','っぶ'=>'bbu', - 'っぱ'=>'ppa','っぺ'=>'ppe','っぴ'=>'ppi','っぽ'=>'ppo','っぷ'=>'ppu', - 'った'=>'tta','って'=>'tte','っち'=>'cchi','っと'=>'tto','っつ'=>'ttsu', - 'っだ'=>'dda','っで'=>'dde','っぢ'=>'ddi','っど'=>'ddo','っづ'=>'ddu', - 'っが'=>'gga','っげ'=>'gge','っぎ'=>'ggi','っご'=>'ggo','っぐ'=>'ggu', - 'っか'=>'kka','っけ'=>'kke','っき'=>'kki','っこ'=>'kko','っく'=>'kku', - 'っま'=>'mma','っめ'=>'mme','っみ'=>'mmi','っも'=>'mmo','っむ'=>'mmu', - 'っな'=>'nna','っね'=>'nne','っに'=>'nni','っの'=>'nno','っぬ'=>'nnu', - 'っら'=>'rra','っれ'=>'rre','っり'=>'rri','っろ'=>'rro','っる'=>'rru', - 'っさ'=>'ssa','っせ'=>'sse','っし'=>'sshi','っそ'=>'sso','っす'=>'ssu', - 'っざ'=>'zza','っぜ'=>'zze','っじ'=>'jji','っぞ'=>'zzo','っず'=>'zzu', - - // 1 character syllabels - 'あ'=>'a','え'=>'e','い'=>'i','お'=>'o','う'=>'u','ん'=>'n', - 'は'=>'ha','へ'=>'he','ひ'=>'hi','ほ'=>'ho','ふ'=>'fu', - 'ば'=>'ba','べ'=>'be','び'=>'bi','ぼ'=>'bo','ぶ'=>'bu', - 'ぱ'=>'pa','ぺ'=>'pe','ぴ'=>'pi','ぽ'=>'po','ぷ'=>'pu', - 'た'=>'ta','て'=>'te','ち'=>'chi','と'=>'to','つ'=>'tsu', - 'だ'=>'da','で'=>'de','ぢ'=>'di','ど'=>'do','づ'=>'du', - 'が'=>'ga','げ'=>'ge','ぎ'=>'gi','ご'=>'go','ぐ'=>'gu', - 'か'=>'ka','け'=>'ke','き'=>'ki','こ'=>'ko','く'=>'ku', - 'ま'=>'ma','め'=>'me','み'=>'mi','も'=>'mo','む'=>'mu', - 'な'=>'na','ね'=>'ne','に'=>'ni','の'=>'no','ぬ'=>'nu', - 'ら'=>'ra','れ'=>'re','り'=>'ri','ろ'=>'ro','る'=>'ru', - 'さ'=>'sa','せ'=>'se','し'=>'shi','そ'=>'so','す'=>'su', - 'わ'=>'wa','を'=>'wo', - 'ざ'=>'za','ぜ'=>'ze','じ'=>'ji','ぞ'=>'zo','ず'=>'zu', - 'や'=>'ya','よ'=>'yo','ゆ'=>'yu', - // old characters - 'ゑ'=>'we','ゐ'=>'wi', - - // convert what's left (probably only kicks in when something's missing above) - // 'ぁ'=>'a','ぇ'=>'e','ぃ'=>'i','ぉ'=>'o','ぅ'=>'u', - // 'ゃ'=>'ya','ょ'=>'yo','ゅ'=>'yu', - - // never seen one of those (disabled for the moment) - // 'ヴぁ'=>'va','ヴぇ'=>'ve','ヴぃ'=>'vi','ヴぉ'=>'vo','ヴ'=>'vu', - // 'でゃ'=>'dha','でぇ'=>'dhe','でぃ'=>'dhi','でょ'=>'dho','でゅ'=>'dhu', - // 'どぁ'=>'dwa','どぇ'=>'dwe','どぃ'=>'dwi','どぉ'=>'dwo','どぅ'=>'dwu', - // 'ぢゃ'=>'dya','ぢぇ'=>'dye','ぢぃ'=>'dyi','ぢょ'=>'dyo','ぢゅ'=>'dyu', - // 'ふぁ'=>'fwa','ふぇ'=>'fwe','ふぃ'=>'fwi','ふぉ'=>'fwo','ふぅ'=>'fwu', - // 'ふゃ'=>'fya','ふぇ'=>'fye','ふぃ'=>'fyi','ふょ'=>'fyo','ふゅ'=>'fyu', - // 'すぁ'=>'swa','すぇ'=>'swe','すぃ'=>'swi','すぉ'=>'swo','すぅ'=>'swu', - // 'てゃ'=>'tha','てぇ'=>'the','てぃ'=>'thi','てょ'=>'tho','てゅ'=>'thu', - // 'つゃ'=>'tsa','つぇ'=>'tse','つぃ'=>'tsi','つょ'=>'tso','つ'=>'tsu', - // 'とぁ'=>'twa','とぇ'=>'twe','とぃ'=>'twi','とぉ'=>'two','とぅ'=>'twu', - // 'ヴゃ'=>'vya','ヴぇ'=>'vye','ヴぃ'=>'vyi','ヴょ'=>'vyo','ヴゅ'=>'vyu', - // 'うぁ'=>'wha','うぇ'=>'whe','うぃ'=>'whi','うぉ'=>'who','うぅ'=>'whu', - // 'じゃ'=>'zha','じぇ'=>'zhe','じぃ'=>'zhi','じょ'=>'zho','じゅ'=>'zhu', - // 'じゃ'=>'zya','じぇ'=>'zye','じぃ'=>'zyi','じょ'=>'zyo','じゅ'=>'zyu', - - // 'spare' characters from other romanization systems - // 'だ'=>'da','で'=>'de','ぢ'=>'di','ど'=>'do','づ'=>'du', - // 'ら'=>'la','れ'=>'le','り'=>'li','ろ'=>'lo','る'=>'lu', - // 'さ'=>'sa','せ'=>'se','し'=>'si','そ'=>'so','す'=>'su', - // 'ちゃ'=>'cya','ちぇ'=>'cye','ちぃ'=>'cyi','ちょ'=>'cyo','ちゅ'=>'cyu', - //'じゃ'=>'jya','じぇ'=>'jye','じぃ'=>'jyi','じょ'=>'jyo','じゅ'=>'jyu', - //'りゃ'=>'lya','りぇ'=>'lye','りぃ'=>'lyi','りょ'=>'lyo','りゅ'=>'lyu', - //'しゃ'=>'sya','しぇ'=>'sye','しぃ'=>'syi','しょ'=>'syo','しゅ'=>'syu', - //'ちゃ'=>'tya','ちぇ'=>'tye','ちぃ'=>'tyi','ちょ'=>'tyo','ちゅ'=>'tyu', - //'し'=>'ci',,い'=>'yi','ぢ'=>'dzi', - //'っじゃ'=>'jja','っじぇ'=>'jje','っじ'=>'jji','っじょ'=>'jjo','っじゅ'=>'jju', - - - // Japanese katakana - - // 4 character syllables: ッ doubles the consonant after, ー doubles the vowel before (usualy written with macron, but we don't want that in our URLs) - 'ッビャー'=>'bbyaa','ッビェー'=>'bbyee','ッビィー'=>'bbyii','ッビョー'=>'bbyoo','ッビュー'=>'bbyuu', - 'ッピャー'=>'ppyaa','ッピェー'=>'ppyee','ッピィー'=>'ppyii','ッピョー'=>'ppyoo','ッピュー'=>'ppyuu', - 'ッキャー'=>'kkyaa','ッキェー'=>'kkyee','ッキィー'=>'kkyii','ッキョー'=>'kkyoo','ッキュー'=>'kkyuu', - 'ッギャー'=>'ggyaa','ッギェー'=>'ggyee','ッギィー'=>'ggyii','ッギョー'=>'ggyoo','ッギュー'=>'ggyuu', - 'ッミャー'=>'mmyaa','ッミェー'=>'mmyee','ッミィー'=>'mmyii','ッミョー'=>'mmyoo','ッミュー'=>'mmyuu', - 'ッニャー'=>'nnyaa','ッニェー'=>'nnyee','ッニィー'=>'nnyii','ッニョー'=>'nnyoo','ッニュー'=>'nnyuu', - 'ッリャー'=>'rryaa','ッリェー'=>'rryee','ッリィー'=>'rryii','ッリョー'=>'rryoo','ッリュー'=>'rryuu', - 'ッシャー'=>'sshaa','ッシェー'=>'sshee','ッシー'=>'sshii','ッショー'=>'sshoo','ッシュー'=>'sshuu', - 'ッチャー'=>'cchaa','ッチェー'=>'cchee','ッチー'=>'cchii','ッチョー'=>'cchoo','ッチュー'=>'cchuu', - 'ッティー'=>'ttii', - 'ッヂィー'=>'ddii', - - // 3 character syllables - doubled vowels - 'ファー'=>'faa','フェー'=>'fee','フィー'=>'fii','フォー'=>'foo', - 'フャー'=>'fyaa','フェー'=>'fyee','フィー'=>'fyii','フョー'=>'fyoo','フュー'=>'fyuu', - 'ヒャー'=>'hyaa','ヒェー'=>'hyee','ヒィー'=>'hyii','ヒョー'=>'hyoo','ヒュー'=>'hyuu', - 'ビャー'=>'byaa','ビェー'=>'byee','ビィー'=>'byii','ビョー'=>'byoo','ビュー'=>'byuu', - 'ピャー'=>'pyaa','ピェー'=>'pyee','ピィー'=>'pyii','ピョー'=>'pyoo','ピュー'=>'pyuu', - 'キャー'=>'kyaa','キェー'=>'kyee','キィー'=>'kyii','キョー'=>'kyoo','キュー'=>'kyuu', - 'ギャー'=>'gyaa','ギェー'=>'gyee','ギィー'=>'gyii','ギョー'=>'gyoo','ギュー'=>'gyuu', - 'ミャー'=>'myaa','ミェー'=>'myee','ミィー'=>'myii','ミョー'=>'myoo','ミュー'=>'myuu', - 'ニャー'=>'nyaa','ニェー'=>'nyee','ニィー'=>'nyii','ニョー'=>'nyoo','ニュー'=>'nyuu', - 'リャー'=>'ryaa','リェー'=>'ryee','リィー'=>'ryii','リョー'=>'ryoo','リュー'=>'ryuu', - 'シャー'=>'shaa','シェー'=>'shee','シー'=>'shii','ショー'=>'shoo','シュー'=>'shuu', - 'ジャー'=>'jaa','ジェー'=>'jee','ジー'=>'jii','ジョー'=>'joo','ジュー'=>'juu', - 'スァー'=>'swaa','スェー'=>'swee','スィー'=>'swii','スォー'=>'swoo','スゥー'=>'swuu', - 'デァー'=>'daa','デェー'=>'dee','ディー'=>'dii','デォー'=>'doo','デゥー'=>'duu', - 'チャー'=>'chaa','チェー'=>'chee','チー'=>'chii','チョー'=>'choo','チュー'=>'chuu', - 'ヂャー'=>'dyaa','ヂェー'=>'dyee','ヂィー'=>'dyii','ヂョー'=>'dyoo','ヂュー'=>'dyuu', - 'ツャー'=>'tsaa','ツェー'=>'tsee','ツィー'=>'tsii','ツョー'=>'tsoo','ツー'=>'tsuu', - 'トァー'=>'twaa','トェー'=>'twee','トィー'=>'twii','トォー'=>'twoo','トゥー'=>'twuu', - 'ドァー'=>'dwaa','ドェー'=>'dwee','ドィー'=>'dwii','ドォー'=>'dwoo','ドゥー'=>'dwuu', - 'ウァー'=>'whaa','ウェー'=>'whee','ウィー'=>'whii','ウォー'=>'whoo','ウゥー'=>'whuu', - 'ヴャー'=>'vyaa','ヴェー'=>'vyee','ヴィー'=>'vyii','ヴョー'=>'vyoo','ヴュー'=>'vyuu', - 'ヴァー'=>'vaa','ヴェー'=>'vee','ヴィー'=>'vii','ヴォー'=>'voo','ヴー'=>'vuu', - 'ウェー'=>'wee','ウィー'=>'wii', - 'イェー'=>'yee', - 'ティー'=>'tii', - 'ヂィー'=>'dii', - - // 3 character syllables - doubled consonants - 'ッビャ'=>'bbya','ッビェ'=>'bbye','ッビィ'=>'bbyi','ッビョ'=>'bbyo','ッビュ'=>'bbyu', - 'ッピャ'=>'ppya','ッピェ'=>'ppye','ッピィ'=>'ppyi','ッピョ'=>'ppyo','ッピュ'=>'ppyu', - 'ッキャ'=>'kkya','ッキェ'=>'kkye','ッキィ'=>'kkyi','ッキョ'=>'kkyo','ッキュ'=>'kkyu', - 'ッギャ'=>'ggya','ッギェ'=>'ggye','ッギィ'=>'ggyi','ッギョ'=>'ggyo','ッギュ'=>'ggyu', - 'ッミャ'=>'mmya','ッミェ'=>'mmye','ッミィ'=>'mmyi','ッミョ'=>'mmyo','ッミュ'=>'mmyu', - 'ッニャ'=>'nnya','ッニェ'=>'nnye','ッニィ'=>'nnyi','ッニョ'=>'nnyo','ッニュ'=>'nnyu', - 'ッリャ'=>'rrya','ッリェ'=>'rrye','ッリィ'=>'rryi','ッリョ'=>'rryo','ッリュ'=>'rryu', - 'ッシャ'=>'ssha','ッシェ'=>'sshe','ッシ'=>'sshi','ッショ'=>'ssho','ッシュ'=>'sshu', - 'ッチャ'=>'ccha','ッチェ'=>'cche','ッチ'=>'cchi','ッチョ'=>'ccho','ッチュ'=>'cchu', - 'ッティ'=>'tti', - 'ッヂィ'=>'ddi', - - // 3 character syllables - doubled vowel and consonants - 'ッバー'=>'bbaa','ッベー'=>'bbee','ッビー'=>'bbii','ッボー'=>'bboo','ッブー'=>'bbuu', - 'ッパー'=>'ppaa','ッペー'=>'ppee','ッピー'=>'ppii','ッポー'=>'ppoo','ップー'=>'ppuu', - 'ッケー'=>'kkee','ッキー'=>'kkii','ッコー'=>'kkoo','ックー'=>'kkuu','ッカー'=>'kkaa', - 'ッガー'=>'ggaa','ッゲー'=>'ggee','ッギー'=>'ggii','ッゴー'=>'ggoo','ッグー'=>'gguu', - 'ッマー'=>'maa','ッメー'=>'mee','ッミー'=>'mii','ッモー'=>'moo','ッムー'=>'muu', - 'ッナー'=>'nnaa','ッネー'=>'nnee','ッニー'=>'nnii','ッノー'=>'nnoo','ッヌー'=>'nnuu', - 'ッラー'=>'rraa','ッレー'=>'rree','ッリー'=>'rrii','ッロー'=>'rroo','ッルー'=>'rruu', - 'ッサー'=>'ssaa','ッセー'=>'ssee','ッシー'=>'sshii','ッソー'=>'ssoo','ッスー'=>'ssuu', - 'ッザー'=>'zzaa','ッゼー'=>'zzee','ッジー'=>'jjii','ッゾー'=>'zzoo','ッズー'=>'zzuu', - 'ッター'=>'ttaa','ッテー'=>'ttee','ッチー'=>'chii','ットー'=>'ttoo','ッツー'=>'ttsuu', - 'ッダー'=>'ddaa','ッデー'=>'ddee','ッヂー'=>'ddii','ッドー'=>'ddoo','ッヅー'=>'dduu', - - // 2 character syllables - normal - 'ファ'=>'fa','フェ'=>'fe','フィ'=>'fi','フォ'=>'fo','フゥ'=>'fu', - // 'フャ'=>'fya','フェ'=>'fye','フィ'=>'fyi','フョ'=>'fyo','フュ'=>'fyu', - 'フャ'=>'fa','フェ'=>'fe','フィ'=>'fi','フョ'=>'fo','フュ'=>'fu', - 'ヒャ'=>'hya','ヒェ'=>'hye','ヒィ'=>'hyi','ヒョ'=>'hyo','ヒュ'=>'hyu', - 'ビャ'=>'bya','ビェ'=>'bye','ビィ'=>'byi','ビョ'=>'byo','ビュ'=>'byu', - 'ピャ'=>'pya','ピェ'=>'pye','ピィ'=>'pyi','ピョ'=>'pyo','ピュ'=>'pyu', - 'キャ'=>'kya','キェ'=>'kye','キィ'=>'kyi','キョ'=>'kyo','キュ'=>'kyu', - 'ギャ'=>'gya','ギェ'=>'gye','ギィ'=>'gyi','ギョ'=>'gyo','ギュ'=>'gyu', - 'ミャ'=>'mya','ミェ'=>'mye','ミィ'=>'myi','ミョ'=>'myo','ミュ'=>'myu', - 'ニャ'=>'nya','ニェ'=>'nye','ニィ'=>'nyi','ニョ'=>'nyo','ニュ'=>'nyu', - 'リャ'=>'rya','リェ'=>'rye','リィ'=>'ryi','リョ'=>'ryo','リュ'=>'ryu', - 'シャ'=>'sha','シェ'=>'she','ショ'=>'sho','シュ'=>'shu', - 'ジャ'=>'ja','ジェ'=>'je','ジョ'=>'jo','ジュ'=>'ju', - 'スァ'=>'swa','スェ'=>'swe','スィ'=>'swi','スォ'=>'swo','スゥ'=>'swu', - 'デァ'=>'da','デェ'=>'de','ディ'=>'di','デォ'=>'do','デゥ'=>'du', - 'チャ'=>'cha','チェ'=>'che','チ'=>'chi','チョ'=>'cho','チュ'=>'chu', - // 'ヂャ'=>'dya','ヂェ'=>'dye','ヂィ'=>'dyi','ヂョ'=>'dyo','ヂュ'=>'dyu', - 'ツャ'=>'tsa','ツェ'=>'tse','ツィ'=>'tsi','ツョ'=>'tso','ツ'=>'tsu', - 'トァ'=>'twa','トェ'=>'twe','トィ'=>'twi','トォ'=>'two','トゥ'=>'twu', - 'ドァ'=>'dwa','ドェ'=>'dwe','ドィ'=>'dwi','ドォ'=>'dwo','ドゥ'=>'dwu', - 'ウァ'=>'wha','ウェ'=>'whe','ウィ'=>'whi','ウォ'=>'who','ウゥ'=>'whu', - 'ヴャ'=>'vya','ヴェ'=>'vye','ヴィ'=>'vyi','ヴョ'=>'vyo','ヴュ'=>'vyu', - 'ヴァ'=>'va','ヴェ'=>'ve','ヴィ'=>'vi','ヴォ'=>'vo','ヴ'=>'vu', - 'ウェ'=>'we','ウィ'=>'wi', - 'イェ'=>'ye', - 'ティ'=>'ti', - 'ヂィ'=>'di', - - // 2 character syllables - doubled vocal - 'アー'=>'aa','エー'=>'ee','イー'=>'ii','オー'=>'oo','ウー'=>'uu', - 'ダー'=>'daa','デー'=>'dee','ヂー'=>'dii','ドー'=>'doo','ヅー'=>'duu', - 'ハー'=>'haa','ヘー'=>'hee','ヒー'=>'hii','ホー'=>'hoo','フー'=>'fuu', - 'バー'=>'baa','ベー'=>'bee','ビー'=>'bii','ボー'=>'boo','ブー'=>'buu', - 'パー'=>'paa','ペー'=>'pee','ピー'=>'pii','ポー'=>'poo','プー'=>'puu', - 'ケー'=>'kee','キー'=>'kii','コー'=>'koo','クー'=>'kuu','カー'=>'kaa', - 'ガー'=>'gaa','ゲー'=>'gee','ギー'=>'gii','ゴー'=>'goo','グー'=>'guu', - 'マー'=>'maa','メー'=>'mee','ミー'=>'mii','モー'=>'moo','ムー'=>'muu', - 'ナー'=>'naa','ネー'=>'nee','ニー'=>'nii','ノー'=>'noo','ヌー'=>'nuu', - 'ラー'=>'raa','レー'=>'ree','リー'=>'rii','ロー'=>'roo','ルー'=>'ruu', - 'サー'=>'saa','セー'=>'see','シー'=>'shii','ソー'=>'soo','スー'=>'suu', - 'ザー'=>'zaa','ゼー'=>'zee','ジー'=>'jii','ゾー'=>'zoo','ズー'=>'zuu', - 'ター'=>'taa','テー'=>'tee','チー'=>'chii','トー'=>'too','ツー'=>'tsuu', - 'ワー'=>'waa','ヲー'=>'woo', - 'ヤー'=>'yaa','ヨー'=>'yoo','ユー'=>'yuu', - 'ヵー'=>'kaa','ヶー'=>'kee', - // old characters - 'ヱー'=>'wee','ヰー'=>'wii', - - // seperate katakana 'n' - 'ンア'=>'n_a','ンエ'=>'n_e','ンイ'=>'n_i','ンオ'=>'n_o','ンウ'=>'n_u', - 'ンヤ'=>'n_ya','ンヨ'=>'n_yo','ンユ'=>'n_yu', - - // 2 character syllables - doubled consonants - 'ッバ'=>'bba','ッベ'=>'bbe','ッビ'=>'bbi','ッボ'=>'bbo','ッブ'=>'bbu', - 'ッパ'=>'ppa','ッペ'=>'ppe','ッピ'=>'ppi','ッポ'=>'ppo','ップ'=>'ppu', - 'ッケ'=>'kke','ッキ'=>'kki','ッコ'=>'kko','ック'=>'kku','ッカ'=>'kka', - 'ッガ'=>'gga','ッゲ'=>'gge','ッギ'=>'ggi','ッゴ'=>'ggo','ッグ'=>'ggu', - 'ッマ'=>'ma','ッメ'=>'me','ッミ'=>'mi','ッモ'=>'mo','ッム'=>'mu', - 'ッナ'=>'nna','ッネ'=>'nne','ッニ'=>'nni','ッノ'=>'nno','ッヌ'=>'nnu', - 'ッラ'=>'rra','ッレ'=>'rre','ッリ'=>'rri','ッロ'=>'rro','ッル'=>'rru', - 'ッサ'=>'ssa','ッセ'=>'sse','ッシ'=>'sshi','ッソ'=>'sso','ッス'=>'ssu', - 'ッザ'=>'zza','ッゼ'=>'zze','ッジ'=>'jji','ッゾ'=>'zzo','ッズ'=>'zzu', - 'ッタ'=>'tta','ッテ'=>'tte','ッチ'=>'cchi','ット'=>'tto','ッツ'=>'ttsu', - 'ッダ'=>'dda','ッデ'=>'dde','ッヂ'=>'ddi','ッド'=>'ddo','ッヅ'=>'ddu', - - // 1 character syllables - 'ア'=>'a','エ'=>'e','イ'=>'i','オ'=>'o','ウ'=>'u','ン'=>'n', - 'ハ'=>'ha','ヘ'=>'he','ヒ'=>'hi','ホ'=>'ho','フ'=>'fu', - 'バ'=>'ba','ベ'=>'be','ビ'=>'bi','ボ'=>'bo','ブ'=>'bu', - 'パ'=>'pa','ペ'=>'pe','ピ'=>'pi','ポ'=>'po','プ'=>'pu', - 'ケ'=>'ke','キ'=>'ki','コ'=>'ko','ク'=>'ku','カ'=>'ka', - 'ガ'=>'ga','ゲ'=>'ge','ギ'=>'gi','ゴ'=>'go','グ'=>'gu', - 'マ'=>'ma','メ'=>'me','ミ'=>'mi','モ'=>'mo','ム'=>'mu', - 'ナ'=>'na','ネ'=>'ne','ニ'=>'ni','ノ'=>'no','ヌ'=>'nu', - 'ラ'=>'ra','レ'=>'re','リ'=>'ri','ロ'=>'ro','ル'=>'ru', - 'サ'=>'sa','セ'=>'se','シ'=>'shi','ソ'=>'so','ス'=>'su', - 'ザ'=>'za','ゼ'=>'ze','ジ'=>'ji','ゾ'=>'zo','ズ'=>'zu', - 'タ'=>'ta','テ'=>'te','チ'=>'chi','ト'=>'to','ツ'=>'tsu', - 'ダ'=>'da','デ'=>'de','ヂ'=>'di','ド'=>'do','ヅ'=>'du', - 'ワ'=>'wa','ヲ'=>'wo', - 'ヤ'=>'ya','ヨ'=>'yo','ユ'=>'yu', - 'ヵ'=>'ka','ヶ'=>'ke', - // old characters - 'ヱ'=>'we','ヰ'=>'wi', - - // convert what's left (probably only kicks in when something's missing above) - 'ァ'=>'a','ェ'=>'e','ィ'=>'i','ォ'=>'o','ゥ'=>'u', - 'ャ'=>'ya','ョ'=>'yo','ュ'=>'yu', - - // special characters - '・'=>'_','、'=>'_', - 'ー'=>'_', // when used with hiragana (seldom), this character would not be converted otherwise - - // 'ラ'=>'la','レ'=>'le','リ'=>'li','ロ'=>'lo','ル'=>'lu', - // 'チャ'=>'cya','チェ'=>'cye','チィ'=>'cyi','チョ'=>'cyo','チュ'=>'cyu', - //'デャ'=>'dha','デェ'=>'dhe','ディ'=>'dhi','デョ'=>'dho','デュ'=>'dhu', - // 'リャ'=>'lya','リェ'=>'lye','リィ'=>'lyi','リョ'=>'lyo','リュ'=>'lyu', - // 'テャ'=>'tha','テェ'=>'the','ティ'=>'thi','テョ'=>'tho','テュ'=>'thu', - //'ファ'=>'fwa','フェ'=>'fwe','フィ'=>'fwi','フォ'=>'fwo','フゥ'=>'fwu', - //'チャ'=>'tya','チェ'=>'tye','チィ'=>'tyi','チョ'=>'tyo','チュ'=>'tyu', - // 'ジャ'=>'jya','ジェ'=>'jye','ジィ'=>'jyi','ジョ'=>'jyo','ジュ'=>'jyu', - // 'ジャ'=>'zha','ジェ'=>'zhe','ジィ'=>'zhi','ジョ'=>'zho','ジュ'=>'zhu', - //'ジャ'=>'zya','ジェ'=>'zye','ジィ'=>'zyi','ジョ'=>'zyo','ジュ'=>'zyu', - //'シャ'=>'sya','シェ'=>'sye','シィ'=>'syi','ショ'=>'syo','シュ'=>'syu', - //'シ'=>'ci','フ'=>'hu',シ'=>'si','チ'=>'ti','ツ'=>'tu','イ'=>'yi','ヂ'=>'dzi', - - // "Greeklish" - 'Γ'=>'G','Δ'=>'E','Θ'=>'Th','Λ'=>'L','Ξ'=>'X','Π'=>'P','Σ'=>'S','Φ'=>'F','Ψ'=>'Ps', - 'γ'=>'g','δ'=>'e','θ'=>'th','λ'=>'l','ξ'=>'x','π'=>'p','σ'=>'s','φ'=>'f','ψ'=>'ps', - - // Thai - 'ก'=>'k','ข'=>'kh','ฃ'=>'kh','ค'=>'kh','ฅ'=>'kh','ฆ'=>'kh','ง'=>'ng','จ'=>'ch', - 'ฉ'=>'ch','ช'=>'ch','ซ'=>'s','ฌ'=>'ch','ญ'=>'y','ฎ'=>'d','ฏ'=>'t','ฐ'=>'th', - 'ฑ'=>'d','ฒ'=>'th','ณ'=>'n','ด'=>'d','ต'=>'t','ถ'=>'th','ท'=>'th','ธ'=>'th', - 'น'=>'n','บ'=>'b','ป'=>'p','ผ'=>'ph','ฝ'=>'f','พ'=>'ph','ฟ'=>'f','ภ'=>'ph', - 'ม'=>'m','ย'=>'y','ร'=>'r','ฤ'=>'rue','ฤๅ'=>'rue','ล'=>'l','ฦ'=>'lue', - 'ฦๅ'=>'lue','ว'=>'w','ศ'=>'s','ษ'=>'s','ส'=>'s','ห'=>'h','ฬ'=>'l','ฮ'=>'h', - 'ะ'=>'a','ั'=>'a','รร'=>'a','า'=>'a','ๅ'=>'a','ำ'=>'am','ํา'=>'am', - 'ิ'=>'i','ี'=>'i','ึ'=>'ue','ี'=>'ue','ุ'=>'u','ู'=>'u', - 'เ'=>'e','แ'=>'ae','โ'=>'o','อ'=>'o', - 'ียะ'=>'ia','ีย'=>'ia','ือะ'=>'uea','ือ'=>'uea','ัวะ'=>'ua','ัว'=>'ua', - 'ใ'=>'ai','ไ'=>'ai','ัย'=>'ai','าย'=>'ai','าว'=>'ao', - 'ุย'=>'ui','อย'=>'oi','ือย'=>'ueai','วย'=>'uai', - 'ิว'=>'io','็ว'=>'eo','ียว'=>'iao', - '่'=>'','้'=>'','๊'=>'','๋'=>'','็'=>'', - '์'=>'','๎'=>'','ํ'=>'','ฺ'=>'', - 'ๆ'=>'2','๏'=>'o','ฯ'=>'-','๚'=>'-','๛'=>'-', - '๐'=>'0','๑'=>'1','๒'=>'2','๓'=>'3','๔'=>'4', - '๕'=>'5','๖'=>'6','๗'=>'7','๘'=>'8','๙'=>'9', - - // Korean - 'ㄱ'=>'k','ㅋ'=>'kh','ㄲ'=>'kk','ㄷ'=>'t','ㅌ'=>'th','ㄸ'=>'tt','ㅂ'=>'p', - 'ㅍ'=>'ph','ㅃ'=>'pp','ㅈ'=>'c','ㅊ'=>'ch','ㅉ'=>'cc','ㅅ'=>'s','ㅆ'=>'ss', - 'ㅎ'=>'h','ㅇ'=>'ng','ㄴ'=>'n','ㄹ'=>'l','ㅁ'=>'m', 'ㅏ'=>'a','ㅓ'=>'e','ㅗ'=>'o', - 'ㅜ'=>'wu','ㅡ'=>'u','ㅣ'=>'i','ㅐ'=>'ay','ㅔ'=>'ey','ㅚ'=>'oy','ㅘ'=>'wa','ㅝ'=>'we', - 'ㅟ'=>'wi','ㅙ'=>'way','ㅞ'=>'wey','ㅢ'=>'uy','ㅑ'=>'ya','ㅕ'=>'ye','ㅛ'=>'oy', - 'ㅠ'=>'yu','ㅒ'=>'yay','ㅖ'=>'yey', -); - - diff --git a/install.php b/install.php index 64f60c51f..eecdac6d7 100644 --- a/install.php +++ b/install.php @@ -95,8 +95,11 @@ header('Content-Type: text/html; charset=utf-8'); print "</div>\n"; } ?> - <a style="background: transparent url(data/dont-panic-if-you-see-this-in-your-logs-it-means-your-directory-permissions-are-correct.png) left top no-repeat; - display: block; width:380px; height:73px; border:none; clear:both;" + <a style=" + background: transparent + url(data/dont-panic-if-you-see-this-in-your-logs-it-means-your-directory-permissions-are-correct.png) + left top no-repeat; + display: block; width:380px; height:73px; border:none; clear:both;" target="_blank" href="http://www.dokuwiki.org/security#web_access_security"></a> </div> @@ -170,10 +173,12 @@ function print_form($d){ <fieldset id="acldep"> <label for="superuser"><?php echo $lang['i_superuser']?></label> - <input class="text" type="text" name="d[superuser]" id="superuser" value="<?php echo $d['superuser'] ?>" /> + <input class="text" type="text" name="d[superuser]" id="superuser" + value="<?php echo $d['superuser'] ?>" /> <label for="fullname"><?php echo $lang['fullname']?></label> - <input class="text" type="text" name="d[fullname]" id="fullname" value="<?php echo $d['fullname'] ?>" /> + <input class="text" type="text" name="d[fullname]" id="fullname" + value="<?php echo $d['fullname'] ?>" /> <label for="email"><?php echo $lang['email']?></label> <input class="text" type="text" name="d[email]" id="email" value="<?php echo $d['email'] ?>" /> @@ -186,13 +191,17 @@ function print_form($d){ <label for="policy"><?php echo $lang['i_policy']?></label> <select class="text" name="d[policy]" id="policy"> - <option value="0" <?php echo ($d['policy'] == 0)?'selected="selected"':'' ?>><?php echo $lang['i_pol0']?></option> - <option value="1" <?php echo ($d['policy'] == 1)?'selected="selected"':'' ?>><?php echo $lang['i_pol1']?></option> - <option value="2" <?php echo ($d['policy'] == 2)?'selected="selected"':'' ?>><?php echo $lang['i_pol2']?></option> + <option value="0" <?php echo ($d['policy'] == 0)?'selected="selected"':'' ?>><?php + echo $lang['i_pol0']?></option> + <option value="1" <?php echo ($d['policy'] == 1)?'selected="selected"':'' ?>><?php + echo $lang['i_pol1']?></option> + <option value="2" <?php echo ($d['policy'] == 2)?'selected="selected"':'' ?>><?php + echo $lang['i_pol2']?></option> </select> <label for="allowreg"> - <input type="checkbox" name="d[allowreg]" id="allowreg" <?php echo(($d['allowreg'] ? ' checked="checked"' : ''));?> /> + <input type="checkbox" name="d[allowreg]" id="allowreg" <?php + echo(($d['allowreg'] ? ' checked="checked"' : ''));?> /> <?php echo $lang['i_allowreg']?> </label> </fieldset> @@ -217,8 +226,10 @@ function print_form($d){ <fieldset> <p><?php echo $lang['i_pop_field']?></p> <label for="pop"> - <input type="checkbox" name="d[pop]" id="pop" <?php echo(($d['pop'] ? ' checked="checked"' : ''));?> /> - <?php echo $lang['i_pop_label']?> <a href="http://www.dokuwiki.org/popularity" target="_blank"><sup>[?]</sup></a> + <input type="checkbox" name="d[pop]" id="pop" <?php + echo(($d['pop'] ? ' checked="checked"' : ''));?> /> + <?php echo $lang['i_pop_label']?> + <a href="http://www.dokuwiki.org/popularity" target="_blank"><sup>[?]</sup></a> </label> </fieldset> @@ -366,7 +377,7 @@ EOT; if ($d['acl']) { // hash the password - $phash = new PassHash(); + $phash = new \dokuwiki\PassHash(); $pass = $phash->hash_smd5($d['password']); // create users.auth.php diff --git a/lib/exe/css.php b/lib/exe/css.php index 19ae5570e..40de4b828 100644 --- a/lib/exe/css.php +++ b/lib/exe/css.php @@ -6,7 +6,10 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); +use dokuwiki\Cache\Cache; +use dokuwiki\Extension\Event; + +if(!defined('DOKU_INC')) define('DOKU_INC', __DIR__ .'/../../'); if(!defined('NOSESSION')) define('NOSESSION',true); // we do not use a session or authentication here (better caching) if(!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT',1); // we gzip ourself here if(!defined('NL')) define('NL',"\n"); @@ -67,7 +70,8 @@ function css_out(){ // load jQuery-UI theme if ($mediatype == 'screen') { - $files[DOKU_INC.'lib/scripts/jquery/jquery-ui-theme/smoothness.css'] = DOKU_BASE.'lib/scripts/jquery/jquery-ui-theme/'; + $files[DOKU_INC.'lib/scripts/jquery/jquery-ui-theme/smoothness.css'] = + DOKU_BASE.'lib/scripts/jquery/jquery-ui-theme/'; } // load plugin styles $files = array_merge($files, css_pluginstyles($mediatype)); @@ -84,7 +88,7 @@ function css_out(){ // Let plugins decide to either put more styles here or to remove some $media_files[$mediatype] = css_filewrapper($mediatype, $files); - $CSSEvt = new Doku_Event('CSS_STYLES_INCLUDED', $media_files[$mediatype]); + $CSSEvt = new Event('CSS_STYLES_INCLUDED', $media_files[$mediatype]); // Make it preventable. if ( $CSSEvt->advise_before() ) { @@ -99,8 +103,17 @@ function css_out(){ } // The generated script depends on some dynamic options - $cache = new cache('styles'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'].$INPUT->bool('preview').DOKU_BASE.$tpl.$type,'.css'); - $cache->_event = 'CSS_CACHE_USE'; + $cache = new Cache( + 'styles' . + $_SERVER['HTTP_HOST'] . + $_SERVER['SERVER_PORT'] . + $INPUT->bool('preview') . + DOKU_BASE . + $tpl . + $type, + '.css' + ); + $cache->setEvent('CSS_CACHE_USE'); // check cache age & handle conditional request // This may exit if a cache can be used @@ -114,7 +127,7 @@ function css_out(){ // plugins decide whether to include the DW default styles. // This can be done by preventing the Default. $media_files['DW_DEFAULT'] = css_filewrapper('DW_DEFAULT'); - trigger_event('CSS_STYLES_INCLUDED', $media_files['DW_DEFAULT'], 'css_defaultstyles'); + Event::createAndTrigger('CSS_STYLES_INCLUDED', $media_files['DW_DEFAULT'], 'css_defaultstyles'); // build the stylesheet foreach ($mediatypes as $mediatype) { @@ -454,18 +467,13 @@ class DokuCssFile { */ public function replacements($match) { - // not a relative url? - no adjustment required - if (preg_match('#^(/|data:|https?://)#',$match[3])) { + if (preg_match('#^(/|data:|https?://)#', $match[3])) { // not a relative url? - no adjustment required return $match[0]; - } - // a less file import? - requires a file system location - else if (substr($match[3],-5) == '.less') { + } elseif (substr($match[3], -5) == '.less') { // a less file import? - requires a file system location if ($match[3]{0} != '/') { $match[3] = $this->getRelativePath() . '/' . $match[3]; } - } - // everything else requires a url adjustment - else { + } else { // everything else requires a url adjustment $match[3] = $this->location . $match[3]; } @@ -547,18 +555,50 @@ function css_compress($css){ $css = preg_replace('/ ?: /',':',$css); // number compression - $css = preg_replace('/([: ])0+(\.\d+?)0*((?:pt|pc|in|mm|cm|em|ex|px)\b|%)(?=[^\{]*[;\}])/', '$1$2$3', $css); // "0.1em" to ".1em", "1.10em" to "1.1em" - $css = preg_replace('/([: ])\.(0)+((?:pt|pc|in|mm|cm|em|ex|px)\b|%)(?=[^\{]*[;\}])/', '$1$2', $css); // ".0em" to "0" - $css = preg_replace('/([: ]0)0*(\.0*)?((?:pt|pc|in|mm|cm|em|ex|px)(?=[^\{]*[;\}])\b|%)/', '$1', $css); // "0.0em" to "0" - $css = preg_replace('/([: ]\d+)(\.0*)((?:pt|pc|in|mm|cm|em|ex|px)(?=[^\{]*[;\}])\b|%)/', '$1$3', $css); // "1.0em" to "1em" - $css = preg_replace('/([: ])0+(\d+|\d*\.\d+)((?:pt|pc|in|mm|cm|em|ex|px)(?=[^\{]*[;\}])\b|%)/', '$1$2$3', $css); // "001em" to "1em" + $css = preg_replace( + '/([: ])0+(\.\d+?)0*((?:pt|pc|in|mm|cm|em|ex|px)\b|%)(?=[^\{]*[;\}])/', + '$1$2$3', + $css + ); // "0.1em" to ".1em", "1.10em" to "1.1em" + $css = preg_replace( + '/([: ])\.(0)+((?:pt|pc|in|mm|cm|em|ex|px)\b|%)(?=[^\{]*[;\}])/', + '$1$2', + $css + ); // ".0em" to "0" + $css = preg_replace( + '/([: ]0)0*(\.0*)?((?:pt|pc|in|mm|cm|em|ex|px)(?=[^\{]*[;\}])\b|%)/', + '$1', + $css + ); // "0.0em" to "0" + $css = preg_replace( + '/([: ]\d+)(\.0*)((?:pt|pc|in|mm|cm|em|ex|px)(?=[^\{]*[;\}])\b|%)/', + '$1$3', + $css + ); // "1.0em" to "1em" + $css = preg_replace( + '/([: ])0+(\d+|\d*\.\d+)((?:pt|pc|in|mm|cm|em|ex|px)(?=[^\{]*[;\}])\b|%)/', + '$1$2$3', + $css + ); // "001em" to "1em" // shorten attributes (1em 1em 1em 1em -> 1em) - $css = preg_replace('/(?<![\w\-])((?:margin|padding|border|border-(?:width|radius)):)([\w\.]+)( \2)+(?=[;\}]| !)/', '$1$2', $css); // "1em 1em 1em 1em" to "1em" - $css = preg_replace('/(?<![\w\-])((?:margin|padding|border|border-(?:width)):)([\w\.]+) ([\w\.]+) \2 \3(?=[;\}]| !)/', '$1$2 $3', $css); // "1em 2em 1em 2em" to "1em 2em" + $css = preg_replace( + '/(?<![\w\-])((?:margin|padding|border|border-(?:width|radius)):)([\w\.]+)( \2)+(?=[;\}]| !)/', + '$1$2', + $css + ); // "1em 1em 1em 1em" to "1em" + $css = preg_replace( + '/(?<![\w\-])((?:margin|padding|border|border-(?:width)):)([\w\.]+) ([\w\.]+) \2 \3(?=[;\}]| !)/', + '$1$2 $3', + $css + ); // "1em 2em 1em 2em" to "1em 2em" // shorten colors - $css = preg_replace("/#([0-9a-fA-F]{1})\\1([0-9a-fA-F]{1})\\2([0-9a-fA-F]{1})\\3(?=[^\{]*[;\}])/", "#\\1\\2\\3", $css); + $css = preg_replace( + "/#([0-9a-fA-F]{1})\\1([0-9a-fA-F]{1})\\2([0-9a-fA-F]{1})\\3(?=[^\{]*[;\}])/", + "#\\1\\2\\3", + $css + ); return $css; } diff --git a/lib/exe/detail.php b/lib/exe/detail.php index ec1a9b874..a6cffa770 100644 --- a/lib/exe/detail.php +++ b/lib/exe/detail.php @@ -1,4 +1,7 @@ <?php + +use dokuwiki\Extension\Event; + if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); define('DOKU_MEDIADETAIL',1); require_once(DOKU_INC.'inc/init.php'); @@ -12,31 +15,17 @@ $REV = $INPUT->int('rev'); $INFO = array_merge(pageinfo(),mediainfo()); $tmp = array(); -trigger_event('DETAIL_STARTED', $tmp); +Event::createAndTrigger('DETAIL_STARTED', $tmp); //close session session_write_close(); -if($conf['allowdebug'] && $INPUT->has('debug')){ - print '<pre>'; - foreach(explode(' ','basedir userewrite baseurl useslash') as $x){ - print '$'."conf['$x'] = '".$conf[$x]."';\n"; - } - foreach(explode(' ','DOCUMENT_ROOT HTTP_HOST SCRIPT_FILENAME PHP_SELF '. - 'REQUEST_URI SCRIPT_NAME PATH_INFO PATH_TRANSLATED') as $x){ - print '$'."_SERVER['$x'] = '".$_SERVER[$x]."';\n"; - } - print "getID('media'): ".getID('media')."\n"; - print "getID('media',false): ".getID('media',false)."\n"; - print '</pre>'; -} - $ERROR = false; // check image permissions $AUTH = auth_quickaclcheck($IMG); if($AUTH >= AUTH_READ){ // check if image exists - $SRC = mediaFN($IMG,$REV); + $SRC = mediaFN($IMG,$REV); if(!file_exists($SRC)){ //doesn't exist! http_status(404); diff --git a/lib/exe/fetch.php b/lib/exe/fetch.php index 933367e35..5c5ae899b 100644 --- a/lib/exe/fetch.php +++ b/lib/exe/fetch.php @@ -6,6 +6,8 @@ * @author Andreas Gohr <andi@splitbrain.org> */ +use dokuwiki\Extension\Event; + if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__).'/../../'); if (!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT', 1); require_once(DOKU_INC.'inc/init.php'); @@ -14,7 +16,7 @@ session_write_close(); //close session require_once(DOKU_INC.'inc/fetch.functions.php'); if (defined('SIMPLE_TEST')) { - $INPUT = new Input(); + $INPUT = new \dokuwiki\Input\Input(); } // BEGIN main @@ -56,7 +58,7 @@ if (defined('SIMPLE_TEST')) { ); // handle the file status - $evt = new Doku_Event('FETCH_MEDIA_STATUS', $data); + $evt = new Event('FETCH_MEDIA_STATUS', $data); if($evt->advise_before()) { // redirects if($data['status'] > 300 && $data['status'] <= 304) { @@ -87,7 +89,7 @@ if (defined('SIMPLE_TEST')) { } // finally send the file to the client - $evt = new Doku_Event('MEDIA_SENDFILE', $data); + $evt = new Event('MEDIA_SENDFILE', $data); if($evt->advise_before()) { sendFile($data['file'], $data['mime'], $data['download'], $data['cache'], $data['ispublic'], $data['orig']); } diff --git a/lib/exe/jquery.php b/lib/exe/jquery.php index f32aef7d3..13c0c1639 100644 --- a/lib/exe/jquery.php +++ b/lib/exe/jquery.php @@ -1,5 +1,7 @@ <?php +use dokuwiki\Cache\Cache; + if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__) . '/../../'); if(!defined('NOSESSION')) define('NOSESSION', true); // we do not use a session or authentication here (better caching) if(!defined('NL')) define('NL', "\n"); @@ -19,7 +21,7 @@ jquery_out(); * uses cache or fills it */ function jquery_out() { - $cache = new cache('jquery', '.js'); + $cache = new Cache('jquery', '.js'); $files = array( DOKU_INC . 'lib/scripts/jquery/jquery.min.js', DOKU_INC . 'lib/scripts/jquery/jquery-ui.min.js', diff --git a/lib/exe/js.php b/lib/exe/js.php index 81afaf053..ae6a6366f 100644 --- a/lib/exe/js.php +++ b/lib/exe/js.php @@ -6,7 +6,10 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); +use dokuwiki\Cache\Cache; +use dokuwiki\Extension\Event; + +if(!defined('DOKU_INC')) define('DOKU_INC', __DIR__ .'/../../'); if(!defined('NOSESSION')) define('NOSESSION',true); // we do not use a session or authentication here (better caching) if(!defined('NL')) define('NL',"\n"); if(!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT',1); // we gzip ourself here @@ -73,11 +76,11 @@ function js_out(){ } // Let plugins decide to either put more scripts here or to remove some - trigger_event('JS_SCRIPT_LIST', $files); + Event::createAndTrigger('JS_SCRIPT_LIST', $files); // The generated script depends on some dynamic options - $cache = new cache('scripts'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'].md5(serialize($files)),'.js'); - $cache->_event = 'JS_CACHE_USE'; + $cache = new Cache('scripts'.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'].md5(serialize($files)),'.js'); + $cache->setEvent('JS_CACHE_USE'); $cache_files = array_merge($files, getConfigFiles('main')); $cache_files[] = __FILE__; @@ -90,18 +93,21 @@ function js_out(){ // start output buffering and build the script ob_start(); - $json = new JSON(); // add some global variables print "var DOKU_BASE = '".DOKU_BASE."';"; print "var DOKU_TPL = '".tpl_basedir($tpl)."';"; - print "var DOKU_COOKIE_PARAM = " . $json->encode( + print "var DOKU_COOKIE_PARAM = " . json_encode( array( 'path' => empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'], 'secure' => $conf['securecookie'] && is_ssl() )).";"; // FIXME: Move those to JSINFO - print "Object.defineProperty(window, 'DOKU_UHN', { get: function() { console.warn('Using DOKU_UHN is deprecated. Please use JSINFO.useHeadingNavigation instead'); return JSINFO.useHeadingNavigation; } });"; - print "Object.defineProperty(window, 'DOKU_UHC', { get: function() { console.warn('Using DOKU_UHC is deprecated. Please use JSINFO.useHeadingContent instead'); return JSINFO.useHeadingContent; } });"; + print "Object.defineProperty(window, 'DOKU_UHN', { get: function() {". + "console.warn('Using DOKU_UHN is deprecated. Please use JSINFO.useHeadingNavigation instead');". + "return JSINFO.useHeadingNavigation; } });"; + print "Object.defineProperty(window, 'DOKU_UHC', { get: function() {". + "console.warn('Using DOKU_UHC is deprecated. Please use JSINFO.useHeadingContent instead');". + "return JSINFO.useHeadingContent; } });"; // load JS specific translations $lang['js']['plugins'] = js_pluginstrings(); @@ -109,7 +115,7 @@ function js_out(){ if(!empty($templatestrings)) { $lang['js']['template'] = $templatestrings; } - echo 'LANG = '.$json->encode($lang['js']).";\n"; + echo 'LANG = '.json_encode($lang['js']).";\n"; // load toolbar toolbar_JSdefines('toolbar'); @@ -170,7 +176,7 @@ function js_load($file){ // is it a include_once? if($match[1]){ - $base = utf8_basename($ifile); + $base = \dokuwiki\Utf8\PhpString::basename($ifile); if(array_key_exists($base, $loaded) && $loaded[$base] === true){ $data = str_replace($match[0], '' ,$data); continue; diff --git a/lib/exe/mediamanager.php b/lib/exe/mediamanager.php index 722254423..b43cff745 100644 --- a/lib/exe/mediamanager.php +++ b/lib/exe/mediamanager.php @@ -1,4 +1,7 @@ <?php + +use dokuwiki\Extension\Event; + if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); define('DOKU_MEDIAMANAGER',1); @@ -38,7 +41,7 @@ $AUTH = $INFO['perm']; // shortcut for historical reasons $tmp = array(); - trigger_event('MEDIAMANAGER_STARTED', $tmp); + Event::createAndTrigger('MEDIAMANAGER_STARTED', $tmp); session_write_close(); //close session // do not display the manager if user does not have read access diff --git a/lib/exe/xmlrpc.php b/lib/exe/xmlrpc.php index 3046f47e9..dc0438ee1 100644 --- a/lib/exe/xmlrpc.php +++ b/lib/exe/xmlrpc.php @@ -1,67 +1,15 @@ <?php -if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); - -require_once(DOKU_INC.'inc/init.php'); -session_write_close(); //close session - -if(!$conf['remote']) die((new IXR_Error(-32605, "XML-RPC server not enabled."))->getXml()); - /** - * Contains needed wrapper functions and registers all available - * XMLRPC functions. + * XMLRPC API backend */ -class dokuwiki_xmlrpc_server extends IXR_Server { - protected $remote; - - /** - * Constructor. Register methods and run Server - */ - public function __construct(){ - $this->remote = new RemoteAPI(); - $this->remote->setDateTransformation(array($this, 'toDate')); - $this->remote->setFileTransformation(array($this, 'toFile')); - parent::__construct(); - } - /** - * @param string $methodname - * @param array $args - * @return IXR_Error|mixed - */ - public function call($methodname, $args){ - try { - $result = $this->remote->call($methodname, $args); - return $result; - } catch (RemoteAccessDeniedException $e) { - if (!isset($_SERVER['REMOTE_USER'])) { - http_status(401); - return new IXR_Error(-32603, "server error. not authorized to call method $methodname"); - } else { - http_status(403); - return new IXR_Error(-32604, "server error. forbidden to call the method $methodname"); - } - } catch (RemoteException $e) { - return new IXR_Error($e->getCode(), $e->getMessage()); - } - } +use dokuwiki\Remote\XmlRpcServer; - /** - * @param string|int $data iso date(yyyy[-]mm[-]dd[ hh:mm[:ss]]) or timestamp - * @return IXR_Date - */ - public function toDate($data) { - return new IXR_Date($data); - } +if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__).'/../../'); - /** - * @param string $data - * @return IXR_Base64 - */ - public function toFile($data) { - return new IXR_Base64($data); - } -} +require_once(DOKU_INC.'inc/init.php'); +session_write_close(); //close session -$server = new dokuwiki_xmlrpc_server(); +if(!$conf['remote']) die((new IXR_Error(-32605, "XML-RPC server not enabled."))->getXml()); -// vim:ts=4:sw=4:et: +$server = new XmlRpcServer(); diff --git a/lib/plugins/acl/action.php b/lib/plugins/acl/action.php index 1d6d05b80..86e587093 100644 --- a/lib/plugins/acl/action.php +++ b/lib/plugins/acl/action.php @@ -6,13 +6,11 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - /** * Register handler */ -class action_plugin_acl extends DokuWiki_Action_Plugin { +class action_plugin_acl extends DokuWiki_Action_Plugin +{ /** * Registers a callback function for a given event @@ -20,10 +18,10 @@ class action_plugin_acl extends DokuWiki_Action_Plugin { * @param Doku_Event_Handler $controller DokuWiki's event controller object * @return void */ - public function register(Doku_Event_Handler $controller) { - - $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax_call_acl'); + public function register(Doku_Event_Handler $controller) + { + $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjaxCallAcl'); } /** @@ -33,9 +31,9 @@ class action_plugin_acl extends DokuWiki_Action_Plugin { * @param mixed $param empty * @return void */ - - public function handle_ajax_call_acl(Doku_Event $event, $param) { - if($event->data !== 'plugin_acl') { + public function handleAjaxCallAcl(Doku_Event $event, $param) + { + if ($event->data !== 'plugin_acl') { return; } $event->stopPropagation(); @@ -46,12 +44,11 @@ class action_plugin_acl extends DokuWiki_Action_Plugin { /** @var $acl admin_plugin_acl */ $acl = plugin_load('admin', 'acl'); - - if(!$acl->isAccessibleByCurrentUser()) { + if (!$acl->isAccessibleByCurrentUser()) { echo 'for admins only'; return; } - if(!checkSecurityToken()) { + if (!checkSecurityToken()) { echo 'CRSF Attack'; return; } @@ -62,26 +59,27 @@ class action_plugin_acl extends DokuWiki_Action_Plugin { $ajax = $INPUT->str('ajax'); header('Content-Type: text/html; charset=utf-8'); - if($ajax == 'info') { - $acl->_html_info(); - } elseif($ajax == 'tree') { - + if ($ajax == 'info') { + $acl->printInfo(); + } elseif ($ajax == 'tree') { $ns = $INPUT->str('ns'); - if($ns == '*') { + if ($ns == '*') { $ns = ''; } $ns = cleanID($ns); $lvl = count(explode(':', $ns)); $ns = utf8_encodeFN(str_replace(':', '/', $ns)); - $data = $acl->_get_tree($ns, $ns); + $data = $acl->makeTree($ns, $ns); - foreach(array_keys($data) as $item) { + foreach (array_keys($data) as $item) { $data[$item]['level'] = $lvl + 1; } echo html_buildlist( - $data, 'acl', array($acl, '_html_list_acl'), - array($acl, '_html_li_acl') + $data, + 'acl', + array($acl, 'makeTreeItem'), + array($acl, 'makeListItem') ); } } diff --git a/lib/plugins/acl/admin.php b/lib/plugins/acl/admin.php index 4ee0fde7e..b0b0ffc85 100644 --- a/lib/plugins/acl/admin.php +++ b/lib/plugins/acl/admin.php @@ -7,16 +7,15 @@ * @author Anika Henke <anika@selfthinker.org> (concepts) * @author Frank Schubert <frank@schokilade.de> (old version) */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); /** * All DokuWiki plugins to extend the admin function * need to inherit from this class */ -class admin_plugin_acl extends DokuWiki_Admin_Plugin { - var $acl = null; - var $ns = null; +class admin_plugin_acl extends DokuWiki_Admin_Plugin +{ + public $acl = null; + protected $ns = null; /** * The currently selected item, associative array with id and type. * Populated from (in this order): @@ -25,22 +24,24 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * $ns * $ID */ - var $current_item = null; - var $who = ''; - var $usersgroups = array(); - var $specials = array(); + protected $current_item = null; + protected $who = ''; + protected $usersgroups = array(); + protected $specials = array(); /** * return prompt for admin menu */ - function getMenuText($language) { + public function getMenuText($language) + { return $this->getLang('admin_acl'); } /** * return sort order for position in admin menu */ - function getMenuSort() { + public function getMenuSort() + { return 1; } @@ -51,7 +52,8 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * @author Andreas Gohr <andi@splitbrain.org> */ - function handle() { + public function handle() + { global $AUTH_ACL; global $ID; global $auth; @@ -62,9 +64,9 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { $AUTH_ACL = file($config_cascade['acl']['default']); // namespace given? - if($INPUT->str('ns') == '*'){ + if ($INPUT->str('ns') == '*') { $this->ns = '*'; - }else{ + } else { $this->ns = cleanID($INPUT->str('ns')); } @@ -80,78 +82,78 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { // user or group choosen? $who = trim($INPUT->str('acl_w')); - if($INPUT->str('acl_t') == '__g__' && $who){ - $this->who = '@'.ltrim($auth->cleanGroup($who),'@'); - }elseif($INPUT->str('acl_t') == '__u__' && $who){ - $this->who = ltrim($who,'@'); - if($this->who != '%USER%' && $this->who != '%GROUP%'){ #keep wildcard as is + if ($INPUT->str('acl_t') == '__g__' && $who) { + $this->who = '@'.ltrim($auth->cleanGroup($who), '@'); + } elseif ($INPUT->str('acl_t') == '__u__' && $who) { + $this->who = ltrim($who, '@'); + if ($this->who != '%USER%' && $this->who != '%GROUP%') { #keep wildcard as is $this->who = $auth->cleanUser($this->who); } - }elseif($INPUT->str('acl_t') && + } elseif ($INPUT->str('acl_t') && $INPUT->str('acl_t') != '__u__' && - $INPUT->str('acl_t') != '__g__'){ + $INPUT->str('acl_t') != '__g__') { $this->who = $INPUT->str('acl_t'); - }elseif($who){ + } elseif ($who) { $this->who = $who; } // handle modifications - if($INPUT->has('cmd') && checkSecurityToken()){ + if ($INPUT->has('cmd') && checkSecurityToken()) { $cmd = $INPUT->extract('cmd')->str('cmd'); // scope for modifications - if($this->ns){ - if($this->ns == '*'){ + if ($this->ns) { + if ($this->ns == '*') { $scope = '*'; - }else{ + } else { $scope = $this->ns.':*'; } - }else{ + } else { $scope = $ID; } - if($cmd == 'save' && $scope && $this->who && $INPUT->has('acl')){ + if ($cmd == 'save' && $scope && $this->who && $INPUT->has('acl')) { // handle additions or single modifications - $this->_acl_del($scope, $this->who); - $this->_acl_add($scope, $this->who, $INPUT->int('acl')); - }elseif($cmd == 'del' && $scope && $this->who){ + $this->deleteACL($scope, $this->who); + $this->addACL($scope, $this->who, $INPUT->int('acl')); + } elseif ($cmd == 'del' && $scope && $this->who) { // handle single deletions - $this->_acl_del($scope, $this->who); - }elseif($cmd == 'update'){ + $this->deleteACL($scope, $this->who); + } elseif ($cmd == 'update') { $acl = $INPUT->arr('acl'); // handle update of the whole file - foreach($INPUT->arr('del') as $where => $names){ + foreach ($INPUT->arr('del') as $where => $names) { // remove all rules marked for deletion - foreach($names as $who) + foreach ($names as $who) unset($acl[$where][$who]); } // prepare lines $lines = array(); // keep header - foreach($AUTH_ACL as $line){ - if($line{0} == '#'){ + foreach ($AUTH_ACL as $line) { + if ($line{0} == '#') { $lines[] = $line; - }else{ + } else { break; } } // re-add all rules - foreach($acl as $where => $opt){ - foreach($opt as $who => $perm){ + foreach ($acl as $where => $opt) { + foreach ($opt as $who => $perm) { if ($who[0]=='@') { if ($who!='@ALL') { - $who = '@'.ltrim($auth->cleanGroup($who),'@'); + $who = '@'.ltrim($auth->cleanGroup($who), '@'); } - } elseif ($who != '%USER%' && $who != '%GROUP%'){ #keep wildcard as is + } elseif ($who != '%USER%' && $who != '%GROUP%') { #keep wildcard as is $who = $auth->cleanUser($who); } - $who = auth_nameencode($who,true); + $who = auth_nameencode($who, true); $lines[] = "$where\t$who\t$perm\n"; } } // save it - io_saveFile($config_cascade['acl']['default'], join('',$lines)); + io_saveFile($config_cascade['acl']['default'], join('', $lines)); } // reload ACL config @@ -159,7 +161,7 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { } // initialize ACL array - $this->_init_acl_config(); + $this->initAclConfig(); } /** @@ -171,24 +173,25 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * @author Frank Schubert <frank@schokilade.de> * @author Andreas Gohr <andi@splitbrain.org> */ - function html() { + public function html() + { echo '<div id="acl_manager">'.NL; echo '<h1>'.$this->getLang('admin_acl').'</h1>'.NL; echo '<div class="level1">'.NL; echo '<div id="acl__tree">'.NL; - $this->_html_explorer(); + $this->makeExplorer(); echo '</div>'.NL; echo '<div id="acl__detail">'.NL; - $this->_html_detail(); + $this->printDetail(); echo '</div>'.NL; echo '</div>'.NL; echo '<div class="clearer"></div>'; echo '<h2>'.$this->getLang('current').'</h2>'.NL; echo '<div class="level2">'.NL; - $this->_html_table(); + $this->printAclTable(); echo '</div>'.NL; echo '<div class="footnotes"><div class="fn">'.NL; @@ -204,15 +207,16 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * @author Andreas Gohr <andi@splitbrain.org> */ - function _get_opts($addopts=null){ + protected function getLinkOptions($addopts = null) + { $opts = array( 'do'=>'admin', 'page'=>'acl', ); - if($this->ns) $opts['ns'] = $this->ns; - if($this->who) $opts['acl_w'] = $this->who; + if ($this->ns) $opts['ns'] = $this->ns; + if ($this->who) $opts['acl_w'] = $this->who; - if(is_null($addopts)) return $opts; + if (is_null($addopts)) return $opts; return array_merge($opts, $addopts); } @@ -221,54 +225,61 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * @author Andreas Gohr <andi@splitbrain.org> */ - function _html_explorer(){ + protected function makeExplorer() + { global $conf; global $ID; global $lang; $ns = $this->ns; - if(empty($ns)){ - $ns = dirname(str_replace(':','/',$ID)); - if($ns == '.') $ns =''; - }elseif($ns == '*'){ + if (empty($ns)) { + $ns = dirname(str_replace(':', '/', $ID)); + if ($ns == '.') $ns =''; + } elseif ($ns == '*') { $ns =''; } - $ns = utf8_encodeFN(str_replace(':','/',$ns)); + $ns = utf8_encodeFN(str_replace(':', '/', $ns)); - $data = $this->_get_tree($ns); + $data = $this->makeTree($ns); // wrap a list with the root level around the other namespaces array_unshift($data, array( 'level' => 0, 'id' => '*', 'type' => 'd', 'open' =>'true', 'label' => '['.$lang['mediaroot'].']')); - echo html_buildlist($data,'acl', - array($this,'_html_list_acl'), - array($this,'_html_li_acl')); - + echo html_buildlist( + $data, + 'acl', + array($this, 'makeTreeItem'), + array($this, 'makeListItem') + ); } /** * get a combined list of media and page files * + * also called via AJAX + * * @param string $folder an already converted filesystem folder of the current namespace - * @param string $limit limit the search to this folder + * @param string $limit limit the search to this folder + * @return array */ - function _get_tree($folder,$limit=''){ + public function makeTree($folder, $limit = '') + { global $conf; // read tree structure from pages and media $data = array(); - search($data,$conf['datadir'],'search_index',array('ns' => $folder),$limit); + search($data, $conf['datadir'], 'search_index', array('ns' => $folder), $limit); $media = array(); - search($media,$conf['mediadir'],'search_index',array('ns' => $folder, 'nofiles' => true),$limit); - $data = array_merge($data,$media); + search($media, $conf['mediadir'], 'search_index', array('ns' => $folder, 'nofiles' => true), $limit); + $data = array_merge($data, $media); unset($media); // combine by sorting and removing duplicates - usort($data,array($this,'_tree_sort')); + usort($data, array($this, 'treeSort')); $count = count($data); - if($count>0) for($i=1; $i<$count; $i++){ - if($data[$i-1]['id'] == $data[$i]['id'] && $data[$i-1]['type'] == $data[$i]['type']) { + if ($count>0) for ($i=1; $i<$count; $i++) { + if ($data[$i-1]['id'] == $data[$i]['id'] && $data[$i-1]['type'] == $data[$i]['type']) { unset($data[$i]); $i++; // duplicate found, next $i can't be a duplicate, so skip forward one } @@ -281,7 +292,8 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * Sorts the combined trees of media and page files */ - function _tree_sort($a,$b){ + public function treeSort($a, $b) + { // handle the trivial cases first if ($a['id'] == '') return -1; if ($b['id'] == '') return 1; @@ -315,6 +327,7 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { // before that other part. if (empty($a_ids)) return ($a['type'] == 'd') ? -1 : 1; if (empty($b_ids)) return ($b['type'] == 'd') ? 1 : -1; + return 0; //shouldn't happen } /** @@ -323,20 +336,21 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * @author Andreas Gohr <andi@splitbrain.org> */ - function _html_detail(){ + protected function printDetail() + { global $ID; echo '<form action="'.wl().'" method="post" accept-charset="utf-8"><div class="no">'.NL; echo '<div id="acl__user">'; echo $this->getLang('acl_perms').' '; - $inl = $this->_html_select(); - echo '<input type="text" name="acl_w" class="edit" value="'.(($inl)?'':hsc(ltrim($this->who,'@'))).'" />'.NL; + $inl = $this->makeSelect(); + echo '<input type="text" name="acl_w" class="edit" value="'.(($inl)?'':hsc(ltrim($this->who, '@'))).'" />'.NL; echo '<button type="submit">'.$this->getLang('btn_select').'</button>'.NL; echo '</div>'.NL; echo '<div id="acl__info">'; - $this->_html_info(); + $this->printInfo(); echo '</div>'; echo '<input type="hidden" name="ns" value="'.hsc($this->ns).'" />'.NL; @@ -349,23 +363,26 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { /** * Print info and editor + * + * also loaded via Ajax */ - function _html_info(){ + public function printInfo() + { global $ID; - if($this->who){ - $current = $this->_get_exact_perm(); + if ($this->who) { + $current = $this->getExactPermisson(); // explain current permissions - $this->_html_explain($current); + $this->printExplanation($current); // load editor - $this->_html_acleditor($current); - }else{ + $this->printAclEditor($current); + } else { echo '<p>'; - if($this->ns){ - printf($this->getLang('p_choose_ns'),hsc($this->ns)); - }else{ - printf($this->getLang('p_choose_id'),hsc($ID)); + if ($this->ns) { + printf($this->getLang('p_choose_ns'), hsc($this->ns)); + } else { + printf($this->getLang('p_choose_id'), hsc($ID)); } echo '</p>'; @@ -378,21 +395,22 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * @author Andreas Gohr <andi@splitbrain.org> */ - function _html_acleditor($current){ + protected function printAclEditor($current) + { global $lang; echo '<fieldset>'; - if(is_null($current)){ + if (is_null($current)) { echo '<legend>'.$this->getLang('acl_new').'</legend>'; - }else{ + } else { echo '<legend>'.$this->getLang('acl_mod').'</legend>'; } - echo $this->_html_checkboxes($current,empty($this->ns),'acl'); + echo $this->makeCheckboxes($current, empty($this->ns), 'acl'); - if(is_null($current)){ + if (is_null($current)) { echo '<button type="submit" name="cmd[save]">'.$lang['btn_save'].'</button>'.NL; - }else{ + } else { echo '<button type="submit" name="cmd[save]">'.$lang['btn_update'].'</button>'.NL; echo '<button type="submit" name="cmd[del]">'.$lang['btn_delete'].'</button>'.NL; } @@ -405,7 +423,8 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * @author Andreas Gohr <andi@splitbrain.org> */ - function _html_explain($current){ + protected function printExplanation($current) + { global $ID; global $auth; @@ -413,69 +432,69 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { $ns = $this->ns; // prepare where to check - if($ns){ - if($ns == '*'){ + if ($ns) { + if ($ns == '*') { $check='*'; - }else{ + } else { $check=$ns.':*'; } - }else{ + } else { $check = $ID; } // prepare who to check - if($who{0} == '@'){ + if ($who{0} == '@') { $user = ''; - $groups = array(ltrim($who,'@')); - }else{ + $groups = array(ltrim($who, '@')); + } else { $user = $who; $info = $auth->getUserData($user); - if($info === false){ + if ($info === false) { $groups = array(); - }else{ + } else { $groups = $info['grps']; } } // check the permissions - $perm = auth_aclcheck($check,$user,$groups); + $perm = auth_aclcheck($check, $user, $groups); // build array of named permissions $names = array(); - if($perm){ - if($ns){ - if($perm >= AUTH_DELETE) $names[] = $this->getLang('acl_perm16'); - if($perm >= AUTH_UPLOAD) $names[] = $this->getLang('acl_perm8'); - if($perm >= AUTH_CREATE) $names[] = $this->getLang('acl_perm4'); + if ($perm) { + if ($ns) { + if ($perm >= AUTH_DELETE) $names[] = $this->getLang('acl_perm16'); + if ($perm >= AUTH_UPLOAD) $names[] = $this->getLang('acl_perm8'); + if ($perm >= AUTH_CREATE) $names[] = $this->getLang('acl_perm4'); } - if($perm >= AUTH_EDIT) $names[] = $this->getLang('acl_perm2'); - if($perm >= AUTH_READ) $names[] = $this->getLang('acl_perm1'); + if ($perm >= AUTH_EDIT) $names[] = $this->getLang('acl_perm2'); + if ($perm >= AUTH_READ) $names[] = $this->getLang('acl_perm1'); $names = array_reverse($names); - }else{ + } else { $names[] = $this->getLang('acl_perm0'); } // print permission explanation echo '<p>'; - if($user){ - if($ns){ - printf($this->getLang('p_user_ns'),hsc($who),hsc($ns),join(', ',$names)); - }else{ - printf($this->getLang('p_user_id'),hsc($who),hsc($ID),join(', ',$names)); + if ($user) { + if ($ns) { + printf($this->getLang('p_user_ns'), hsc($who), hsc($ns), join(', ', $names)); + } else { + printf($this->getLang('p_user_id'), hsc($who), hsc($ID), join(', ', $names)); } - }else{ - if($ns){ - printf($this->getLang('p_group_ns'),hsc(ltrim($who,'@')),hsc($ns),join(', ',$names)); - }else{ - printf($this->getLang('p_group_id'),hsc(ltrim($who,'@')),hsc($ID),join(', ',$names)); + } else { + if ($ns) { + printf($this->getLang('p_group_ns'), hsc(ltrim($who, '@')), hsc($ns), join(', ', $names)); + } else { + printf($this->getLang('p_group_id'), hsc(ltrim($who, '@')), hsc($ID), join(', ', $names)); } } echo '</p>'; // add note if admin - if($perm == AUTH_ADMIN){ + if ($perm == AUTH_ADMIN) { echo '<p>'.$this->getLang('p_isadmin').'</p>'; - }elseif(is_null($current)){ + } elseif (is_null($current)) { echo '<p>'.$this->getLang('p_inherited').'</p>'; } } @@ -488,46 +507,57 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * @author Andreas Gohr <andi@splitbrain.org> */ - function _html_list_acl($item){ + protected function makeTreeItem($item) + { $ret = ''; // what to display - if(!empty($item['label'])){ + if (!empty($item['label'])) { $base = $item['label']; - }else{ + } else { $base = ':'.$item['id']; - $base = substr($base,strrpos($base,':')+1); + $base = substr($base, strrpos($base, ':')+1); } // highlight? - if( ($item['type']== $this->current_item['type'] && $item['id'] == $this->current_item['id'])) { + if (($item['type']== $this->current_item['type'] && $item['id'] == $this->current_item['id'])) { $cl = ' cur'; } else { $cl = ''; } // namespace or page? - if($item['type']=='d'){ - if($item['open']){ + if ($item['type']=='d') { + if ($item['open']) { $img = DOKU_BASE.'lib/images/minus.gif'; $alt = '−'; - }else{ + } else { $img = DOKU_BASE.'lib/images/plus.gif'; $alt = '+'; } $ret .= '<img src="'.$img.'" alt="'.$alt.'" />'; - $ret .= '<a href="'.wl('',$this->_get_opts(array('ns'=>$item['id'],'sectok'=>getSecurityToken()))).'" class="idx_dir'.$cl.'">'; + $ret .= '<a href="'. + wl('', $this->getLinkOptions(array('ns'=> $item['id'], 'sectok'=>getSecurityToken()))). + '" class="idx_dir'.$cl.'">'; $ret .= $base; $ret .= '</a>'; - }else{ - $ret .= '<a href="'.wl('',$this->_get_opts(array('id'=>$item['id'],'ns'=>'','sectok'=>getSecurityToken()))).'" class="wikilink1'.$cl.'">'; + } else { + $ret .= '<a href="'. + wl('', $this->getLinkOptions(array('id'=> $item['id'], 'ns'=>'', 'sectok'=>getSecurityToken()))). + '" class="wikilink1'.$cl.'">'; $ret .= noNS($item['id']); $ret .= '</a>'; } return $ret; } - - function _html_li_acl($item){ + /** + * List Item formatter + * + * @param array $item + * @return string + */ + public function makeListItem($item) + { return '<li class="level' . $item['level'] . ' ' . ($item['open'] ? 'open' : 'closed') . '">'; } @@ -538,7 +568,8 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * @author Andreas Gohr <andi@splitbrain.org> */ - function _init_acl_config(){ + public function initAclConfig() + { global $AUTH_ACL; global $conf; $acl_config=array(); @@ -547,20 +578,24 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { // get special users and groups $this->specials[] = '@ALL'; $this->specials[] = '@'.$conf['defaultgroup']; - if($conf['manager'] != '!!not set!!'){ - $this->specials = array_merge($this->specials, - array_map('trim', - explode(',',$conf['manager']))); + if ($conf['manager'] != '!!not set!!') { + $this->specials = array_merge( + $this->specials, + array_map( + 'trim', + explode(',', $conf['manager']) + ) + ); } $this->specials = array_filter($this->specials); $this->specials = array_unique($this->specials); sort($this->specials); - foreach($AUTH_ACL as $line){ - $line = trim(preg_replace('/#.*$/','',$line)); //ignore comments - if(!$line) continue; + foreach ($AUTH_ACL as $line) { + $line = trim(preg_replace('/#.*$/', '', $line)); //ignore comments + if (!$line) continue; - $acl = preg_split('/[ \t]+/',$line); + $acl = preg_split('/[ \t]+/', $line); //0 is pagename, 1 is user, 2 is acl $acl[1] = rawurldecode($acl[1]); @@ -568,7 +603,7 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { // store non-special users and groups for later selection dialog $ug = $acl[1]; - if(in_array($ug,$this->specials)) continue; + if (in_array($ug, $this->specials)) continue; $usersgroups[] = $ug; } @@ -585,14 +620,15 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * @author Andreas Gohr <andi@splitbrain.org> */ - function _html_table(){ + protected function printAclTable() + { global $lang; global $ID; echo '<form action="'.wl().'" method="post" accept-charset="utf-8"><div class="no">'.NL; - if($this->ns){ + if ($this->ns) { echo '<input type="hidden" name="ns" value="'.hsc($this->ns).'" />'.NL; - }else{ + } else { echo '<input type="hidden" name="id" value="'.hsc($ID).'" />'.NL; } echo '<input type="hidden" name="acl_w" value="'.hsc($this->who).'" />'.NL; @@ -607,29 +643,29 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { echo '<th>'.$this->getLang('perm').'<sup><a id="fnt__1" class="fn_top" href="#fn__1">1)</a></sup></th>'; echo '<th>'.$lang['btn_delete'].'</th>'; echo '</tr>'; - foreach($this->acl as $where => $set){ - foreach($set as $who => $perm){ + foreach ($this->acl as $where => $set) { + foreach ($set as $who => $perm) { echo '<tr>'; echo '<td>'; - if(substr($where,-1) == '*'){ + if (substr($where, -1) == '*') { echo '<span class="aclns">'.hsc($where).'</span>'; $ispage = false; - }else{ + } else { echo '<span class="aclpage">'.hsc($where).'</span>'; $ispage = true; } echo '</td>'; echo '<td>'; - if($who{0} == '@'){ + if ($who{0} == '@') { echo '<span class="aclgroup">'.hsc($who).'</span>'; - }else{ + } else { echo '<span class="acluser">'.hsc($who).'</span>'; } echo '</td>'; echo '<td>'; - echo $this->_html_checkboxes($perm,$ispage,'acl['.$where.']['.$who.']'); + echo $this->makeCheckboxes($perm, $ispage, 'acl['.$where.']['.$who.']'); echo '</td>'; echo '<td class="check">'; @@ -655,21 +691,22 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * @author Andreas Gohr <andi@splitbrain.org> */ - function _get_exact_perm(){ + protected function getExactPermisson() + { global $ID; - if($this->ns){ - if($this->ns == '*'){ + if ($this->ns) { + if ($this->ns == '*') { $check = '*'; - }else{ + } else { $check = $this->ns.':*'; } - }else{ + } else { $check = $ID; } - if(isset($this->acl[$check][$this->who])){ + if (isset($this->acl[$check][$this->who])) { return $this->acl[$check][$this->who]; - }else{ + } else { return null; } } @@ -679,13 +716,14 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * @author Frank Schubert <frank@schokilade.de> */ - function _acl_add($acl_scope, $acl_user, $acl_level){ + public function addACL($acl_scope, $acl_user, $acl_level) + { global $config_cascade; - $acl_user = auth_nameencode($acl_user,true); + $acl_user = auth_nameencode($acl_user, true); // max level for pagenames is edit - if(strpos($acl_scope,'*') === false) { - if($acl_level > AUTH_EDIT) $acl_level = AUTH_EDIT; + if (strpos($acl_scope, '*') === false) { + if ($acl_level > AUTH_EDIT) $acl_level = AUTH_EDIT; } $new_acl = "$acl_scope\t$acl_user\t$acl_level\n"; @@ -698,11 +736,12 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * @author Frank Schubert <frank@schokilade.de> */ - function _acl_del($acl_scope, $acl_user){ + public function deleteACL($acl_scope, $acl_user) + { global $config_cascade; - $acl_user = auth_nameencode($acl_user,true); + $acl_user = auth_nameencode($acl_user, true); - $acl_pattern = '^'.preg_quote($acl_scope,'/').'[ \t]+'.$acl_user.'[ \t]+[0-8].*$'; + $acl_pattern = '^'.preg_quote($acl_scope, '/').'[ \t]+'.$acl_user.'[ \t]+[0-8].*$'; return io_deleteFromFile($config_cascade['acl']['default'], "/$acl_pattern/", true); } @@ -713,15 +752,16 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * @author Frank Schubert <frank@schokilade.de> * @author Andreas Gohr <andi@splitbrain.org> */ - function _html_checkboxes($setperm,$ispage,$name){ + protected function makeCheckboxes($setperm, $ispage, $name) + { global $lang; static $label = 0; //number labels $ret = ''; - if($ispage && $setperm > AUTH_EDIT) $setperm = AUTH_EDIT; + if ($ispage && $setperm > AUTH_EDIT) $setperm = AUTH_EDIT; - foreach(array(AUTH_NONE,AUTH_READ,AUTH_EDIT,AUTH_CREATE,AUTH_UPLOAD,AUTH_DELETE) as $perm){ + foreach (array(AUTH_NONE,AUTH_READ,AUTH_EDIT,AUTH_CREATE,AUTH_UPLOAD,AUTH_DELETE) as $perm) { $label += 1; //general checkbox attributes @@ -730,11 +770,11 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { 'name' => $name, 'value' => $perm ); //dynamic attributes - if(!is_null($setperm) && $setperm == $perm) $atts['checked'] = 'checked'; - if($ispage && $perm > AUTH_EDIT){ + if (!is_null($setperm) && $setperm == $perm) $atts['checked'] = 'checked'; + if ($ispage && $perm > AUTH_EDIT) { $atts['disabled'] = 'disabled'; $class = ' class="disabled"'; - }else{ + } else { $class = ''; } @@ -752,21 +792,21 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { * * @author Andreas Gohr <andi@splitbrain.org> */ - function _html_select(){ + protected function makeSelect() + { $inlist = false; $usel = ''; $gsel = ''; - if($this->who && - !in_array($this->who,$this->usersgroups) && - !in_array($this->who,$this->specials)){ - - if($this->who{0} == '@'){ + if ($this->who && + !in_array($this->who, $this->usersgroups) && + !in_array($this->who, $this->specials)) { + if ($this->who{0} == '@') { $gsel = ' selected="selected"'; - }else{ + } else { $usel = ' selected="selected"'; } - }else{ + } else { $inlist = true; } @@ -775,17 +815,17 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { echo ' <option value="__u__" class="acluser"'.$usel.'>'.$this->getLang('acl_user').'</option>'.NL; if (!empty($this->specials)) { echo ' <optgroup label=" ">'.NL; - foreach($this->specials as $ug){ - if($ug == $this->who){ + foreach ($this->specials as $ug) { + if ($ug == $this->who) { $sel = ' selected="selected"'; $inlist = true; - }else{ + } else { $sel = ''; } - if($ug{0} == '@'){ + if ($ug{0} == '@') { echo ' <option value="'.hsc($ug).'" class="aclgroup"'.$sel.'>'.hsc($ug).'</option>'.NL; - }else{ + } else { echo ' <option value="'.hsc($ug).'" class="acluser"'.$sel.'>'.hsc($ug).'</option>'.NL; } } @@ -793,17 +833,17 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { } if (!empty($this->usersgroups)) { echo ' <optgroup label=" ">'.NL; - foreach($this->usersgroups as $ug){ - if($ug == $this->who){ + foreach ($this->usersgroups as $ug) { + if ($ug == $this->who) { $sel = ' selected="selected"'; $inlist = true; - }else{ + } else { $sel = ''; } - if($ug{0} == '@'){ + if ($ug{0} == '@') { echo ' <option value="'.hsc($ug).'" class="aclgroup"'.$sel.'>'.hsc($ug).'</option>'.NL; - }else{ + } else { echo ' <option value="'.hsc($ug).'" class="acluser"'.$sel.'>'.hsc($ug).'</option>'.NL; } } diff --git a/lib/plugins/acl/remote.php b/lib/plugins/acl/remote.php index 27c5c162a..31a8cde53 100644 --- a/lib/plugins/acl/remote.php +++ b/lib/plugins/acl/remote.php @@ -1,16 +1,20 @@ <?php +use dokuwiki\Remote\AccessDeniedException; + /** * Class remote_plugin_acl */ -class remote_plugin_acl extends DokuWiki_Remote_Plugin { +class remote_plugin_acl extends DokuWiki_Remote_Plugin +{ /** * Returns details about the remote plugin methods * - * @return array Information about all provided methods. {@see RemoteAPI} + * @return array Information about all provided methods. {@see dokuwiki\Remote\RemoteAPI} */ - public function _getMethods() { + public function _getMethods() + { return array( 'listAcls' => array( 'args' => array(), @@ -34,16 +38,20 @@ class remote_plugin_acl extends DokuWiki_Remote_Plugin { /** * List all ACL config entries * - * @throws RemoteAccessDeniedException + * @throws AccessDeniedException * @return dictionary {Scope: ACL}, where ACL = dictionnary {user/group: permissions_int} */ - public function listAcls(){ - if(!auth_isadmin()) { - throw new RemoteAccessDeniedException('You are not allowed to access ACLs, superuser permission is required', 114); + public function listAcls() + { + if (!auth_isadmin()) { + throw new AccessDeniedException( + 'You are not allowed to access ACLs, superuser permission is required', + 114 + ); } /** @var admin_plugin_acl $apa */ $apa = plugin_load('admin', 'acl'); - $apa->_init_acl_config(); + $apa->initAclConfig(); return $apa->acl; } @@ -53,17 +61,21 @@ class remote_plugin_acl extends DokuWiki_Remote_Plugin { * @param string $scope * @param string $user * @param int $level see also inc/auth.php - * @throws RemoteAccessDeniedException + * @throws AccessDeniedException * @return bool */ - public function addAcl($scope, $user, $level){ - if(!auth_isadmin()) { - throw new RemoteAccessDeniedException('You are not allowed to access ACLs, superuser permission is required', 114); + public function addAcl($scope, $user, $level) + { + if (!auth_isadmin()) { + throw new AccessDeniedException( + 'You are not allowed to access ACLs, superuser permission is required', + 114 + ); } /** @var admin_plugin_acl $apa */ $apa = plugin_load('admin', 'acl'); - return $apa->_acl_add($scope, $user, $level); + return $apa->addACL($scope, $user, $level); } /** @@ -71,17 +83,20 @@ class remote_plugin_acl extends DokuWiki_Remote_Plugin { * * @param string $scope * @param string $user - * @throws RemoteAccessDeniedException + * @throws AccessDeniedException * @return bool */ - public function delAcl($scope, $user){ - if(!auth_isadmin()) { - throw new RemoteAccessDeniedException('You are not allowed to access ACLs, superuser permission is required', 114); + public function delAcl($scope, $user) + { + if (!auth_isadmin()) { + throw new AccessDeniedException( + 'You are not allowed to access ACLs, superuser permission is required', + 114 + ); } /** @var admin_plugin_acl $apa */ $apa = plugin_load('admin', 'acl'); - return $apa->_acl_del($scope, $user); + return $apa->deleteACL($scope, $user); } } - diff --git a/lib/plugins/action.php b/lib/plugins/action.php index 23d94a509..a3cbec722 100644 --- a/lib/plugins/action.php +++ b/lib/plugins/action.php @@ -1,25 +1,2 @@ <?php -/** - * Action Plugin Prototype - * - * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) - * @author Christopher Smith <chris@jalakai.co.uk> - */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - -/** - * All DokuWiki plugins to interfere with the event system - * need to inherit from this class - */ -class DokuWiki_Action_Plugin extends DokuWiki_Plugin { - - /** - * Registers a callback function for a given event - * - * @param Doku_Event_Handler $controller - */ - public function register(Doku_Event_Handler $controller) { - trigger_error('register() not implemented in '.get_class($this), E_USER_WARNING); - } -} +dbg_deprecated('Autoloading. Do not require() files yourself.'); diff --git a/lib/plugins/admin.php b/lib/plugins/admin.php index 9554ce511..a3cbec722 100644 --- a/lib/plugins/admin.php +++ b/lib/plugins/admin.php @@ -1,119 +1,2 @@ <?php -/** - * Admin Plugin Prototype - * - * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) - * @author Christopher Smith <chris@jalakai.co.uk> - */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - -/** - * All DokuWiki plugins to extend the admin function - * need to inherit from this class - */ -class DokuWiki_Admin_Plugin extends DokuWiki_Plugin { - - /** - * Return the text that is displayed at the main admin menu - * (Default localized language string 'menu' is returned, override this function for setting another name) - * - * @param string $language language code - * @return string menu string - */ - public function getMenuText($language) { - $menutext = $this->getLang('menu'); - if (!$menutext) { - $info = $this->getInfo(); - $menutext = $info['name'].' ...'; - } - return $menutext; - } - - /** - * Return the path to the icon being displayed in the main admin menu. - * By default it tries to find an 'admin.svg' file in the plugin directory. - * (Override this function for setting another image) - * - * Important: you have to return a single path, monochrome SVG icon! It has to be - * under 2 Kilobytes! - * - * We recommend icons from https://materialdesignicons.com/ or to use a matching - * style. - * - * @return string full path to the icon file - */ - public function getMenuIcon() { - $plugin = $this->getPluginName(); - return DOKU_PLUGIN . $plugin . '/admin.svg'; - } - - /** - * Determine position in list in admin window - * Lower values are sorted up - * - * @return int - */ - public function getMenuSort() { - return 1000; - } - - /** - * Carry out required processing - */ - public function handle() { - trigger_error('handle() not implemented in '.get_class($this), E_USER_WARNING); - } - - /** - * Output html of the admin page - */ - public function html() { - trigger_error('html() not implemented in '.get_class($this), E_USER_WARNING); - } - - /** - * Checks if access should be granted to this admin plugin - * - * @return bool true if the current user may access this admin plugin - */ - public function isAccessibleByCurrentUser() { - $data = []; - $data['instance'] = $this; - $data['hasAccess'] = false; - - $event = new Doku_Event('ADMINPLUGIN_ACCESS_CHECK', $data); - if($event->advise_before()) { - if ($this->forAdminOnly()) { - $data['hasAccess'] = auth_isadmin(); - } else { - $data['hasAccess'] = auth_ismanager(); - } - } - $event->advise_after(); - - return $data['hasAccess']; - } - - /** - * Return true for access only by admins (config:superuser) or false if managers are allowed as well - * - * @return bool - */ - public function forAdminOnly() { - return true; - } - - /** - * Return array with ToC items. Items can be created with the html_mktocitem() - * - * @see html_mktocitem() - * @see tpl_toc() - * - * @return array - */ - public function getTOC(){ - return array(); - } -} -//Setup VIM: ex: et ts=4 : +dbg_deprecated('Autoloading. Do not require() files yourself.'); diff --git a/lib/plugins/auth.php b/lib/plugins/auth.php index 0cd965b72..a3cbec722 100644 --- a/lib/plugins/auth.php +++ b/lib/plugins/auth.php @@ -1,438 +1,2 @@ <?php -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - -/** - * Auth Plugin Prototype - * - * foundation authorisation class - * all auth classes should inherit from this class - * - * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) - * @author Chris Smith <chris@jalakai.co.uk> - * @author Jan Schumann <js@jschumann-it.com> - */ -class DokuWiki_Auth_Plugin extends DokuWiki_Plugin { - public $success = true; - - /** - * Possible things an auth backend module may be able to - * do. The things a backend can do need to be set to true - * in the constructor. - */ - protected $cando = array( - 'addUser' => false, // can Users be created? - 'delUser' => false, // can Users be deleted? - 'modLogin' => false, // can login names be changed? - 'modPass' => false, // can passwords be changed? - 'modName' => false, // can real names be changed? - 'modMail' => false, // can emails be changed? - 'modGroups' => false, // can groups be changed? - 'getUsers' => false, // can a (filtered) list of users be retrieved? - 'getUserCount' => false, // can the number of users be retrieved? - 'getGroups' => false, // can a list of available groups be retrieved? - 'external' => false, // does the module do external auth checking? - 'logout' => true, // can the user logout again? (eg. not possible with HTTP auth) - ); - - /** - * Constructor. - * - * Carry out sanity checks to ensure the object is - * able to operate. Set capabilities in $this->cando - * array here - * - * For future compatibility, sub classes should always include a call - * to parent::__constructor() in their constructors! - * - * Set $this->success to false if checks fail - * - * @author Christopher Smith <chris@jalakai.co.uk> - */ - public function __construct() { - // the base class constructor does nothing, derived class - // constructors do the real work - } - - /** - * Available Capabilities. [ DO NOT OVERRIDE ] - * - * For introspection/debugging - * - * @author Christopher Smith <chris@jalakai.co.uk> - * @return array - */ - public function getCapabilities(){ - return array_keys($this->cando); - } - - /** - * Capability check. [ DO NOT OVERRIDE ] - * - * Checks the capabilities set in the $this->cando array and - * some pseudo capabilities (shortcutting access to multiple - * ones) - * - * ususal capabilities start with lowercase letter - * shortcut capabilities start with uppercase letter - * - * @author Andreas Gohr <andi@splitbrain.org> - * @param string $cap the capability to check - * @return bool - */ - public function canDo($cap) { - switch($cap) { - case 'Profile': - // can at least one of the user's properties be changed? - return ($this->cando['modPass'] || - $this->cando['modName'] || - $this->cando['modMail']); - break; - case 'UserMod': - // can at least anything be changed? - return ($this->cando['modPass'] || - $this->cando['modName'] || - $this->cando['modMail'] || - $this->cando['modLogin'] || - $this->cando['modGroups'] || - $this->cando['modMail']); - break; - default: - // print a helping message for developers - if(!isset($this->cando[$cap])) { - msg("Check for unknown capability '$cap' - Do you use an outdated Plugin?", -1); - } - return $this->cando[$cap]; - } - } - - /** - * Trigger the AUTH_USERDATA_CHANGE event and call the modification function. [ DO NOT OVERRIDE ] - * - * You should use this function instead of calling createUser, modifyUser or - * deleteUsers directly. The event handlers can prevent the modification, for - * example for enforcing a user name schema. - * - * @author Gabriel Birke <birke@d-scribe.de> - * @param string $type Modification type ('create', 'modify', 'delete') - * @param array $params Parameters for the createUser, modifyUser or deleteUsers method. The content of this array depends on the modification type - * @return bool|null|int Result from the modification function or false if an event handler has canceled the action - */ - public function triggerUserMod($type, $params) { - $validTypes = array( - 'create' => 'createUser', - 'modify' => 'modifyUser', - 'delete' => 'deleteUsers' - ); - if(empty($validTypes[$type])) { - return false; - } - - $result = false; - $eventdata = array('type' => $type, 'params' => $params, 'modification_result' => null); - $evt = new Doku_Event('AUTH_USER_CHANGE', $eventdata); - if($evt->advise_before(true)) { - $result = call_user_func_array(array($this, $validTypes[$type]), $evt->data['params']); - $evt->data['modification_result'] = $result; - } - $evt->advise_after(); - unset($evt); - return $result; - } - - /** - * Log off the current user [ OPTIONAL ] - * - * Is run in addition to the ususal logoff method. Should - * only be needed when trustExternal is implemented. - * - * @see auth_logoff() - * @author Andreas Gohr <andi@splitbrain.org> - */ - public function logOff() { - } - - /** - * Do all authentication [ OPTIONAL ] - * - * Set $this->cando['external'] = true when implemented - * - * If this function is implemented it will be used to - * authenticate a user - all other DokuWiki internals - * will not be used for authenticating, thus - * implementing the checkPass() function is not needed - * anymore. - * - * The function can be used to authenticate against third - * party cookies or Apache auth mechanisms and replaces - * the auth_login() function - * - * The function will be called with or without a set - * username. If the Username is given it was called - * from the login form and the given credentials might - * need to be checked. If no username was given it - * the function needs to check if the user is logged in - * by other means (cookie, environment). - * - * The function needs to set some globals needed by - * DokuWiki like auth_login() does. - * - * @see auth_login() - * @author Andreas Gohr <andi@splitbrain.org> - * - * @param string $user Username - * @param string $pass Cleartext Password - * @param bool $sticky Cookie should not expire - * @return bool true on successful auth - */ - public function trustExternal($user, $pass, $sticky = false) { - /* some example: - - global $USERINFO; - global $conf; - $sticky ? $sticky = true : $sticky = false; //sanity check - - // do the checking here - - // set the globals if authed - $USERINFO['name'] = 'FIXME'; - $USERINFO['mail'] = 'FIXME'; - $USERINFO['grps'] = array('FIXME'); - $_SERVER['REMOTE_USER'] = $user; - $_SESSION[DOKU_COOKIE]['auth']['user'] = $user; - $_SESSION[DOKU_COOKIE]['auth']['pass'] = $pass; - $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO; - return true; - - */ - } - - /** - * Check user+password [ MUST BE OVERRIDDEN ] - * - * Checks if the given user exists and the given - * plaintext password is correct - * - * May be ommited if trustExternal is used. - * - * @author Andreas Gohr <andi@splitbrain.org> - * @param string $user the user name - * @param string $pass the clear text password - * @return bool - */ - public function checkPass($user, $pass) { - msg("no valid authorisation system in use", -1); - return false; - } - - /** - * Return user info [ MUST BE OVERRIDDEN ] - * - * Returns info about the given user needs to contain - * at least these fields: - * - * name string full name of the user - * mail string email address of the user - * grps array list of groups the user is in - * - * @author Andreas Gohr <andi@splitbrain.org> - * @param string $user the user name - * @param bool $requireGroups whether or not the returned data must include groups - * @return false|array containing user data or false - */ - public function getUserData($user, $requireGroups=true) { - if(!$this->cando['external']) msg("no valid authorisation system in use", -1); - return false; - } - - /** - * Create a new User [implement only where required/possible] - * - * Returns false if the user already exists, null when an error - * occurred and true if everything went well. - * - * The new user HAS TO be added to the default group by this - * function! - * - * Set addUser capability when implemented - * - * @author Andreas Gohr <andi@splitbrain.org> - * @param string $user - * @param string $pass - * @param string $name - * @param string $mail - * @param null|array $grps - * @return bool|null - */ - public function createUser($user, $pass, $name, $mail, $grps = null) { - msg("authorisation method does not allow creation of new users", -1); - return null; - } - - /** - * Modify user data [implement only where required/possible] - * - * Set the mod* capabilities according to the implemented features - * - * @author Chris Smith <chris@jalakai.co.uk> - * @param string $user nick of the user to be changed - * @param array $changes array of field/value pairs to be changed (password will be clear text) - * @return bool - */ - public function modifyUser($user, $changes) { - msg("authorisation method does not allow modifying of user data", -1); - return false; - } - - /** - * Delete one or more users [implement only where required/possible] - * - * Set delUser capability when implemented - * - * @author Chris Smith <chris@jalakai.co.uk> - * @param array $users - * @return int number of users deleted - */ - public function deleteUsers($users) { - msg("authorisation method does not allow deleting of users", -1); - return 0; - } - - /** - * Return a count of the number of user which meet $filter criteria - * [should be implemented whenever retrieveUsers is implemented] - * - * Set getUserCount capability when implemented - * - * @author Chris Smith <chris@jalakai.co.uk> - * @param array $filter array of field/pattern pairs, empty array for no filter - * @return int - */ - public function getUserCount($filter = array()) { - msg("authorisation method does not provide user counts", -1); - return 0; - } - - /** - * Bulk retrieval of user data [implement only where required/possible] - * - * Set getUsers capability when implemented - * - * @author Chris Smith <chris@jalakai.co.uk> - * @param int $start index of first user to be returned - * @param int $limit max number of users to be returned, 0 for unlimited - * @param array $filter array of field/pattern pairs, null for no filter - * @return array list of userinfo (refer getUserData for internal userinfo details) - */ - public function retrieveUsers($start = 0, $limit = 0, $filter = null) { - msg("authorisation method does not support mass retrieval of user data", -1); - return array(); - } - - /** - * Define a group [implement only where required/possible] - * - * Set addGroup capability when implemented - * - * @author Chris Smith <chris@jalakai.co.uk> - * @param string $group - * @return bool - */ - public function addGroup($group) { - msg("authorisation method does not support independent group creation", -1); - return false; - } - - /** - * Retrieve groups [implement only where required/possible] - * - * Set getGroups capability when implemented - * - * @author Chris Smith <chris@jalakai.co.uk> - * @param int $start - * @param int $limit - * @return array - */ - public function retrieveGroups($start = 0, $limit = 0) { - msg("authorisation method does not support group list retrieval", -1); - return array(); - } - - /** - * Return case sensitivity of the backend [OPTIONAL] - * - * When your backend is caseinsensitive (eg. you can login with USER and - * user) then you need to overwrite this method and return false - * - * @return bool - */ - public function isCaseSensitive() { - return true; - } - - /** - * Sanitize a given username [OPTIONAL] - * - * This function is applied to any user name that is given to - * the backend and should also be applied to any user name within - * the backend before returning it somewhere. - * - * This should be used to enforce username restrictions. - * - * @author Andreas Gohr <andi@splitbrain.org> - * @param string $user username - * @return string the cleaned username - */ - public function cleanUser($user) { - return $user; - } - - /** - * Sanitize a given groupname [OPTIONAL] - * - * This function is applied to any groupname that is given to - * the backend and should also be applied to any groupname within - * the backend before returning it somewhere. - * - * This should be used to enforce groupname restrictions. - * - * Groupnames are to be passed without a leading '@' here. - * - * @author Andreas Gohr <andi@splitbrain.org> - * @param string $group groupname - * @return string the cleaned groupname - */ - public function cleanGroup($group) { - return $group; - } - - /** - * Check Session Cache validity [implement only where required/possible] - * - * DokuWiki caches user info in the user's session for the timespan defined - * in $conf['auth_security_timeout']. - * - * This makes sure slow authentication backends do not slow down DokuWiki. - * This also means that changes to the user database will not be reflected - * on currently logged in users. - * - * To accommodate for this, the user manager plugin will touch a reference - * file whenever a change is submitted. This function compares the filetime - * of this reference file with the time stored in the session. - * - * This reference file mechanism does not reflect changes done directly in - * the backend's database through other means than the user manager plugin. - * - * Fast backends might want to return always false, to force rechecks on - * each page load. Others might want to use their own checking here. If - * unsure, do not override. - * - * @param string $user - The username - * @author Andreas Gohr <andi@splitbrain.org> - * @return bool - */ - public function useSessionCache($user) { - global $conf; - return ($_SESSION[DOKU_COOKIE]['auth']['time'] >= @filemtime($conf['cachedir'].'/sessionpurge')); - } -} +dbg_deprecated('Autoloading. Do not require() files yourself.'); diff --git a/lib/plugins/authad/action.php b/lib/plugins/authad/action.php index bc0f90c7e..a9fc01c1b 100644 --- a/lib/plugins/authad/action.php +++ b/lib/plugins/authad/action.php @@ -6,22 +6,20 @@ * @author Andreas Gohr <gohr@cosmocode.de> */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - /** * Class action_plugin_addomain */ -class action_plugin_authad extends DokuWiki_Action_Plugin { +class action_plugin_authad extends DokuWiki_Action_Plugin +{ /** * Registers a callback function for a given event */ - public function register(Doku_Event_Handler $controller) { - - $controller->register_hook('AUTH_LOGIN_CHECK', 'BEFORE', $this, 'handle_auth_login_check'); - $controller->register_hook('HTML_LOGINFORM_OUTPUT', 'BEFORE', $this, 'handle_html_loginform_output'); + public function register(Doku_Event_Handler $controller) + { + $controller->register_hook('AUTH_LOGIN_CHECK', 'BEFORE', $this, 'handleAuthLoginCheck'); + $controller->register_hook('HTML_LOGINFORM_OUTPUT', 'BEFORE', $this, 'handleHtmlLoginformOutput'); } /** @@ -30,17 +28,18 @@ class action_plugin_authad extends DokuWiki_Action_Plugin { * @param Doku_Event $event * @param array $param */ - public function handle_auth_login_check(Doku_Event &$event, $param) { + public function handleAuthLoginCheck(Doku_Event $event, $param) + { global $INPUT; /** @var auth_plugin_authad $auth */ global $auth; - if(!is_a($auth, 'auth_plugin_authad')) return; // AD not even used + if (!is_a($auth, 'auth_plugin_authad')) return; // AD not even used - if($INPUT->str('dom')) { + if ($INPUT->str('dom')) { $usr = $auth->cleanUser($event->data['user']); - $dom = $auth->_userDomain($usr); - if(!$dom) { + $dom = $auth->getUserDomain($usr); + if (!$dom) { $usr = "$usr@".$INPUT->str('dom'); } $INPUT->post->set('u', $usr); @@ -54,26 +53,27 @@ class action_plugin_authad extends DokuWiki_Action_Plugin { * @param Doku_Event $event * @param array $param */ - public function handle_html_loginform_output(Doku_Event &$event, $param) { + public function handleHtmlLoginformOutput(Doku_Event $event, $param) + { global $INPUT; /** @var auth_plugin_authad $auth */ global $auth; - if(!is_a($auth, 'auth_plugin_authad')) return; // AD not even used - $domains = $auth->_getConfiguredDomains(); - if(count($domains) <= 1) return; // no choice at all + if (!is_a($auth, 'auth_plugin_authad')) return; // AD not even used + $domains = $auth->getConfiguredDomains(); + if (count($domains) <= 1) return; // no choice at all /** @var Doku_Form $form */ $form =& $event->data; // any default? $dom = ''; - if($INPUT->has('u')) { + if ($INPUT->has('u')) { $usr = $auth->cleanUser($INPUT->str('u')); - $dom = $auth->_userDomain($usr); + $dom = $auth->getUserDomain($usr); // update user field value - if($dom) { - $usr = $auth->_userName($usr); + if ($dom) { + $usr = $auth->getUserName($usr); $pos = $form->findElementByAttribute('name', 'u'); $ele =& $form->getElementAt($pos); $ele['value'] = $usr; @@ -85,7 +85,6 @@ class action_plugin_authad extends DokuWiki_Action_Plugin { $pos = $form->findElementByAttribute('name', 'p'); $form->insertElement($pos + 1, $element); } - } -// vim:ts=4:sw=4:et:
\ No newline at end of file +// vim:ts=4:sw=4:et: diff --git a/lib/plugins/authad/auth.php b/lib/plugins/authad/auth.php index 50f708456..684a6ed69 100644 --- a/lib/plugins/authad/auth.php +++ b/lib/plugins/authad/auth.php @@ -1,9 +1,4 @@ <?php -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - -require_once(DOKU_PLUGIN.'authad/adLDAP/adLDAP.php'); -require_once(DOKU_PLUGIN.'authad/adLDAP/classes/adLDAPUtils.php'); /** * Active Directory authentication backend for DokuWiki @@ -41,7 +36,8 @@ require_once(DOKU_PLUGIN.'authad/adLDAP/classes/adLDAPUtils.php'); * @author Andreas Gohr <andi@splitbrain.org> * @author Jan Schumann <js@schumann-it.com> */ -class auth_plugin_authad extends DokuWiki_Auth_Plugin { +class auth_plugin_authad extends DokuWiki_Auth_Plugin +{ /** * @var array hold connection data for a specific AD domain @@ -66,52 +62,55 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { /** * @var array filter patterns for listing users */ - protected $_pattern = array(); + protected $pattern = array(); - protected $_actualstart = 0; + protected $actualstart = 0; - protected $_grpsusers = array(); + protected $grpsusers = array(); /** * Constructor */ - public function __construct() { + public function __construct() + { global $INPUT; parent::__construct(); + require_once(DOKU_PLUGIN.'authad/adLDAP/adLDAP.php'); + require_once(DOKU_PLUGIN.'authad/adLDAP/classes/adLDAPUtils.php'); + // we load the config early to modify it a bit here $this->loadConfig(); // additional information fields - if(isset($this->conf['additional'])) { + if (isset($this->conf['additional'])) { $this->conf['additional'] = str_replace(' ', '', $this->conf['additional']); $this->conf['additional'] = explode(',', $this->conf['additional']); } else $this->conf['additional'] = array(); // ldap extension is needed - if(!function_exists('ldap_connect')) { - if($this->conf['debug']) + if (!function_exists('ldap_connect')) { + if ($this->conf['debug']) msg("AD Auth: PHP LDAP extension not found.", -1); $this->success = false; return; } // Prepare SSO - if(!empty($_SERVER['REMOTE_USER'])) { - + if (!empty($_SERVER['REMOTE_USER'])) { // make sure the right encoding is used - if($this->getConf('sso_charset')) { + if ($this->getConf('sso_charset')) { $_SERVER['REMOTE_USER'] = iconv($this->getConf('sso_charset'), 'UTF-8', $_SERVER['REMOTE_USER']); - } elseif(!utf8_check($_SERVER['REMOTE_USER'])) { + } elseif (!\dokuwiki\Utf8\Clean::isUtf8($_SERVER['REMOTE_USER'])) { $_SERVER['REMOTE_USER'] = utf8_encode($_SERVER['REMOTE_USER']); } // trust the incoming user - if($this->conf['sso']) { + if ($this->conf['sso']) { $_SERVER['REMOTE_USER'] = $this->cleanUser($_SERVER['REMOTE_USER']); // we need to simulate a login - if(empty($_COOKIE[DOKU_COOKIE])) { + if (empty($_COOKIE[DOKU_COOKIE])) { $INPUT->set('u', $_SERVER['REMOTE_USER']); $INPUT->set('p', 'sso_only'); } @@ -130,10 +129,11 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param string $cap * @return bool */ - public function canDo($cap) { + public function canDo($cap) + { //capabilities depend on config, which may change depending on domain - $domain = $this->_userDomain($_SERVER['REMOTE_USER']); - $this->_loadServerConfig($domain); + $domain = $this->getUserDomain($_SERVER['REMOTE_USER']); + $this->loadServerConfig($domain); return parent::canDo($cap); } @@ -149,16 +149,22 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param string $pass * @return bool */ - public function checkPass($user, $pass) { - if($_SERVER['REMOTE_USER'] && + public function checkPass($user, $pass) + { + if ($_SERVER['REMOTE_USER'] && $_SERVER['REMOTE_USER'] == $user && $this->conf['sso'] ) return true; - $adldap = $this->_adldap($this->_userDomain($user)); - if(!$adldap) return false; + $adldap = $this->initAdLdap($this->getUserDomain($user)); + if (!$adldap) return false; - return $adldap->authenticate($this->_userName($user), $pass); + try { + return $adldap->authenticate($this->getUserName($user), $pass); + } catch (adLDAPException $e) { + // shouldn't really happen + return false; + } } /** @@ -186,14 +192,15 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param bool $requireGroups (optional) - ignored, groups are always supplied by this plugin * @return array */ - public function getUserData($user, $requireGroups=true) { + public function getUserData($user, $requireGroups = true) + { global $conf; global $lang; global $ID; - $adldap = $this->_adldap($this->_userDomain($user)); - if(!$adldap) return false; + $adldap = $this->initAdLdap($this->getUserDomain($user)); + if (!$adldap) return array(); - if($user == '') return array(); + if ($user == '') return array(); $fields = array('mail', 'displayname', 'samaccountname', 'lastpwd', 'pwdlastset', 'useraccountcontrol'); @@ -203,8 +210,8 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { $fields = array_filter($fields); //get info for given user - $result = $adldap->user()->info($this->_userName($user), $fields); - if($result == false){ + $result = $adldap->user()->info($this->getUserName($user), $fields); + if ($result == false) { return array(); } @@ -220,52 +227,56 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { $info['expires'] = !($result[0]['useraccountcontrol'][0] & 0x10000); //ADS_UF_DONT_EXPIRE_PASSWD // additional information - foreach($this->conf['additional'] as $field) { - if(isset($result[0][strtolower($field)])) { + foreach ($this->conf['additional'] as $field) { + if (isset($result[0][strtolower($field)])) { $info[$field] = $result[0][strtolower($field)][0]; } } // handle ActiveDirectory memberOf - $info['grps'] = $adldap->user()->groups($this->_userName($user),(bool) $this->opts['recursive_groups']); + $info['grps'] = $adldap->user()->groups($this->getUserName($user), (bool) $this->opts['recursive_groups']); - if(is_array($info['grps'])) { - foreach($info['grps'] as $ndx => $group) { + if (is_array($info['grps'])) { + foreach ($info['grps'] as $ndx => $group) { $info['grps'][$ndx] = $this->cleanGroup($group); } } // always add the default group to the list of groups - if(!is_array($info['grps']) || !in_array($conf['defaultgroup'], $info['grps'])) { + if (!is_array($info['grps']) || !in_array($conf['defaultgroup'], $info['grps'])) { $info['grps'][] = $conf['defaultgroup']; } // add the user's domain to the groups - $domain = $this->_userDomain($user); - if($domain && !in_array("domain-$domain", (array) $info['grps'])) { + $domain = $this->getUserDomain($user); + if ($domain && !in_array("domain-$domain", (array) $info['grps'])) { $info['grps'][] = $this->cleanGroup("domain-$domain"); } // check expiry time - if($info['expires'] && $this->conf['expirywarn']){ - $expiry = $adldap->user()->passwordExpiry($user); - if(is_array($expiry)){ - $info['expiresat'] = $expiry['expiryts']; - $info['expiresin'] = round(($info['expiresat'] - time())/(24*60*60)); - - // if this is the current user, warn him (once per request only) - if(($_SERVER['REMOTE_USER'] == $user) && - ($info['expiresin'] <= $this->conf['expirywarn']) && - !$this->msgshown - ) { - $msg = sprintf($this->getLang('authpwdexpire'), $info['expiresin']); - if($this->canDo('modPass')) { - $url = wl($ID, array('do'=> 'profile')); - $msg .= ' <a href="'.$url.'">'.$lang['btn_profile'].'</a>'; + if ($info['expires'] && $this->conf['expirywarn']) { + try { + $expiry = $adldap->user()->passwordExpiry($user); + if (is_array($expiry)) { + $info['expiresat'] = $expiry['expiryts']; + $info['expiresin'] = round(($info['expiresat'] - time())/(24*60*60)); + + // if this is the current user, warn him (once per request only) + if (($_SERVER['REMOTE_USER'] == $user) && + ($info['expiresin'] <= $this->conf['expirywarn']) && + !$this->msgshown + ) { + $msg = sprintf($this->getLang('authpwdexpire'), $info['expiresin']); + if ($this->canDo('modPass')) { + $url = wl($ID, array('do'=> 'profile')); + $msg .= ' <a href="'.$url.'">'.$lang['btn_profile'].'</a>'; + } + msg($msg); + $this->msgshown = true; } - msg($msg); - $this->msgshown = true; } + } catch (adLDAPException $e) { + // ignore. should usually not happen } } @@ -281,11 +292,12 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param string $group * @return string */ - public function cleanGroup($group) { + public function cleanGroup($group) + { $group = str_replace('\\', '', $group); $group = str_replace('#', '', $group); $group = preg_replace('[\s]', '_', $group); - $group = utf8_strtolower(trim($group)); + $group = \dokuwiki\Utf8\PhpString::strtolower(trim($group)); return $group; } @@ -298,27 +310,28 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param string $user * @return string */ - public function cleanUser($user) { + public function cleanUser($user) + { $domain = ''; // get NTLM or Kerberos domain part list($dom, $user) = explode('\\', $user, 2); - if(!$user) $user = $dom; - if($dom) $domain = $dom; + if (!$user) $user = $dom; + if ($dom) $domain = $dom; list($user, $dom) = explode('@', $user, 2); - if($dom) $domain = $dom; + if ($dom) $domain = $dom; // clean up both - $domain = utf8_strtolower(trim($domain)); - $user = utf8_strtolower(trim($user)); + $domain = \dokuwiki\Utf8\PhpString::strtolower(trim($domain)); + $user = \dokuwiki\Utf8\PhpString::strtolower(trim($user)); // is this a known, valid domain? if not discard - if(!is_array($this->conf[$domain])) { + if (!is_array($this->conf[$domain])) { $domain = ''; } // reattach domain - if($domain) $user = "$user@$domain"; + if ($domain) $user = "$user@$domain"; return $user; } @@ -327,7 +340,8 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * * @return bool */ - public function isCaseSensitive() { + public function isCaseSensitive() + { return false; } @@ -337,11 +351,12 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param array $filter * @return string */ - protected function _constructSearchString($filter){ - if (!$filter){ + protected function constructSearchString($filter) + { + if (!$filter) { return '*'; } - $adldapUtils = new adLDAPUtils($this->_adldap(null)); + $adldapUtils = new adLDAPUtils($this->initAdLdap(null)); $result = '*'; if (isset($filter['name'])) { $result .= ')(displayname=*' . $adldapUtils->ldapSlashes($filter['name']) . '*'; @@ -366,32 +381,41 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param array $filter $filter array of field/pattern pairs, empty array for no filter * @return int number of users */ - public function getUserCount($filter = array()) { - $adldap = $this->_adldap(null); - if(!$adldap) { + public function getUserCount($filter = array()) + { + $adldap = $this->initAdLdap(null); + if (!$adldap) { dbglog("authad/auth.php getUserCount(): _adldap not set."); return -1; } if ($filter == array()) { $result = $adldap->user()->all(); } else { - $searchString = $this->_constructSearchString($filter); + $searchString = $this->constructSearchString($filter); $result = $adldap->user()->all(false, $searchString); if (isset($filter['grps'])) { $this->users = array_fill_keys($result, false); + /** @var admin_plugin_usermanager $usermanager */ $usermanager = plugin_load("admin", "usermanager", false); $usermanager->setLastdisabled(true); - if (!isset($this->_grpsusers[$this->_filterToString($filter)])){ - $this->_fillGroupUserArray($filter,$usermanager->getStart() + 3*$usermanager->getPagesize()); - } elseif (count($this->_grpsusers[$this->_filterToString($filter)]) < $usermanager->getStart() + 3*$usermanager->getPagesize()) { - $this->_fillGroupUserArray($filter,$usermanager->getStart() + 3*$usermanager->getPagesize() - count($this->_grpsusers[$this->_filterToString($filter)])); + if (!isset($this->grpsusers[$this->filterToString($filter)])) { + $this->fillGroupUserArray($filter, $usermanager->getStart() + 3*$usermanager->getPagesize()); + } elseif (count($this->grpsusers[$this->filterToString($filter)]) < + $usermanager->getStart() + 3*$usermanager->getPagesize() + ) { + $this->fillGroupUserArray( + $filter, + $usermanager->getStart() + + 3*$usermanager->getPagesize() - + count($this->grpsusers[$this->filterToString($filter)]) + ); } - $result = $this->_grpsusers[$this->_filterToString($filter)]; + $result = $this->grpsusers[$this->filterToString($filter)]; } else { + /** @var admin_plugin_usermanager $usermanager */ $usermanager = plugin_load("admin", "usermanager", false); $usermanager->setLastdisabled(false); } - } if (!$result) { @@ -407,7 +431,8 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param array $filter * @return string */ - protected function _filterToString ($filter) { + protected function filterToString($filter) + { $result = ''; if (isset($filter['user'])) { $result .= 'user-' . $filter['user']; @@ -433,24 +458,25 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param int $numberOfAdds additional number of users requested * @return int number of Users actually add to Array */ - protected function _fillGroupUserArray($filter, $numberOfAdds){ - $this->_grpsusers[$this->_filterToString($filter)]; + protected function fillGroupUserArray($filter, $numberOfAdds) + { + $this->grpsusers[$this->filterToString($filter)]; $i = 0; $count = 0; - $this->_constructPattern($filter); + $this->constructPattern($filter); foreach ($this->users as $user => &$info) { - if($i++ < $this->_actualstart) { + if ($i++ < $this->actualstart) { continue; } - if($info === false) { + if ($info === false) { $info = $this->getUserData($user); } - if($this->_filter($user, $info)) { - $this->_grpsusers[$this->_filterToString($filter)][$user] = $info; - if(($numberOfAdds > 0) && (++$count >= $numberOfAdds)) break; + if ($this->filter($user, $info)) { + $this->grpsusers[$this->filterToString($filter)][$user] = $info; + if (($numberOfAdds > 0) && (++$count >= $numberOfAdds)) break; } } - $this->_actualstart = $i; + $this->actualstart = $i; return $count; } @@ -464,13 +490,14 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param array $filter array of field/pattern pairs, null for no filter * @return array userinfo (refer getUserData for internal userinfo details) */ - public function retrieveUsers($start = 0, $limit = 0, $filter = array()) { - $adldap = $this->_adldap(null); - if(!$adldap) return false; + public function retrieveUsers($start = 0, $limit = 0, $filter = array()) + { + $adldap = $this->initAdLdap(null); + if (!$adldap) return array(); - if(!$this->users) { + if (!$this->users) { //get info for given user - $result = $adldap->user()->all(false, $this->_constructSearchString($filter)); + $result = $adldap->user()->all(false, $this->constructSearchString($filter)); if (!$result) return array(); $this->users = array_fill_keys($result, false); } @@ -480,34 +507,40 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { $result = array(); if (!isset($filter['grps'])) { + /** @var admin_plugin_usermanager $usermanager */ $usermanager = plugin_load("admin", "usermanager", false); $usermanager->setLastdisabled(false); - $this->_constructPattern($filter); - foreach($this->users as $user => &$info) { - if($i++ < $start) { + $this->constructPattern($filter); + foreach ($this->users as $user => &$info) { + if ($i++ < $start) { continue; } - if($info === false) { + if ($info === false) { $info = $this->getUserData($user); } $result[$user] = $info; - if(($limit > 0) && (++$count >= $limit)) break; + if (($limit > 0) && (++$count >= $limit)) break; } } else { + /** @var admin_plugin_usermanager $usermanager */ $usermanager = plugin_load("admin", "usermanager", false); $usermanager->setLastdisabled(true); - if (!isset($this->_grpsusers[$this->_filterToString($filter)]) || count($this->_grpsusers[$this->_filterToString($filter)]) < ($start+$limit)) { - $this->_fillGroupUserArray($filter,$start+$limit - count($this->_grpsusers[$this->_filterToString($filter)]) +1); + if (!isset($this->grpsusers[$this->filterToString($filter)]) || + count($this->grpsusers[$this->filterToString($filter)]) < ($start+$limit) + ) { + $this->fillGroupUserArray( + $filter, + $start+$limit - count($this->grpsusers[$this->filterToString($filter)]) +1 + ); } - if (!$this->_grpsusers[$this->_filterToString($filter)]) return false; - foreach($this->_grpsusers[$this->_filterToString($filter)] as $user => &$info) { - if($i++ < $start) { + if (!$this->grpsusers[$this->filterToString($filter)]) return array(); + foreach ($this->grpsusers[$this->filterToString($filter)] as $user => &$info) { + if ($i++ < $start) { continue; } $result[$user] = $info; - if(($limit > 0) && (++$count >= $limit)) break; + if (($limit > 0) && (++$count >= $limit)) break; } - } return $result; } @@ -519,45 +552,46 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param array $changes array of field/value pairs to be changed * @return bool */ - public function modifyUser($user, $changes) { + public function modifyUser($user, $changes) + { $return = true; - $adldap = $this->_adldap($this->_userDomain($user)); - if(!$adldap) { + $adldap = $this->initAdLdap($this->getUserDomain($user)); + if (!$adldap) { msg($this->getLang('connectfail'), -1); return false; } // password changing - if(isset($changes['pass'])) { + if (isset($changes['pass'])) { try { - $return = $adldap->user()->password($this->_userName($user),$changes['pass']); + $return = $adldap->user()->password($this->getUserName($user), $changes['pass']); } catch (adLDAPException $e) { if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1); $return = false; } - if(!$return) msg($this->getLang('passchangefail'), -1); + if (!$return) msg($this->getLang('passchangefail'), -1); } // changing user data $adchanges = array(); - if(isset($changes['name'])) { + if (isset($changes['name'])) { // get first and last name $parts = explode(' ', $changes['name']); $adchanges['surname'] = array_pop($parts); $adchanges['firstname'] = join(' ', $parts); $adchanges['display_name'] = $changes['name']; } - if(isset($changes['mail'])) { + if (isset($changes['mail'])) { $adchanges['email'] = $changes['mail']; } - if(count($adchanges)) { + if (count($adchanges)) { try { - $return = $return & $adldap->user()->modify($this->_userName($user),$adchanges); + $return = $return & $adldap->user()->modify($this->getUserName($user), $adchanges); } catch (adLDAPException $e) { if ($this->conf['debug']) msg('AD Auth: '.$e->getMessage(), -1); $return = false; } - if(!$return) msg($this->getLang('userchangefail'), -1); + if (!$return) msg($this->getLang('userchangefail'), -1); } return $return; @@ -573,20 +607,21 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param string|null $domain The AD domain to use * @return adLDAP|bool true if a connection was established */ - protected function _adldap($domain) { - if(is_null($domain) && is_array($this->opts)) { + protected function initAdLdap($domain) + { + if (is_null($domain) && is_array($this->opts)) { $domain = $this->opts['domain']; } - $this->opts = $this->_loadServerConfig((string) $domain); - if(isset($this->adldap[$domain])) return $this->adldap[$domain]; + $this->opts = $this->loadServerConfig((string) $domain); + if (isset($this->adldap[$domain])) return $this->adldap[$domain]; // connect try { $this->adldap[$domain] = new adLDAP($this->opts); return $this->adldap[$domain]; - } catch(adLDAPException $e) { - if($this->conf['debug']) { + } catch (Exception $e) { + if ($this->conf['debug']) { msg('AD Auth: '.$e->getMessage(), -1); } $this->success = false; @@ -601,7 +636,8 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param string $user * @return string */ - public function _userDomain($user) { + public function getUserDomain($user) + { list(, $domain) = explode('@', $user, 2); return $domain; } @@ -612,7 +648,8 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param string $user * @return string */ - public function _userName($user) { + public function getUserName($user) + { list($name) = explode('@', $user, 2); return $name; } @@ -623,14 +660,15 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param string $domain current AD domain * @return array */ - protected function _loadServerConfig($domain) { + protected function loadServerConfig($domain) + { // prepare adLDAP standard configuration $opts = $this->conf; $opts['domain'] = $domain; // add possible domain specific configuration - if($domain && is_array($this->conf[$domain])) foreach($this->conf[$domain] as $key => $val) { + if ($domain && is_array($this->conf[$domain])) foreach ($this->conf[$domain] as $key => $val) { $opts[$key] = $val; } @@ -640,23 +678,27 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { $opts['domain_controllers'] = array_filter($opts['domain_controllers']); // compatibility with old option name - if(empty($opts['admin_username']) && !empty($opts['ad_username'])) $opts['admin_username'] = $opts['ad_username']; - if(empty($opts['admin_password']) && !empty($opts['ad_password'])) $opts['admin_password'] = $opts['ad_password']; + if (empty($opts['admin_username']) && !empty($opts['ad_username'])) { + $opts['admin_username'] = $opts['ad_username']; + } + if (empty($opts['admin_password']) && !empty($opts['ad_password'])) { + $opts['admin_password'] = $opts['ad_password']; + } $opts['admin_password'] = conf_decodeString($opts['admin_password']); // deobfuscate // we can change the password if SSL is set - if($opts['use_ssl'] || $opts['use_tls']) { + if ($opts['use_ssl'] || $opts['use_tls']) { $this->cando['modPass'] = true; } else { $this->cando['modPass'] = false; } // adLDAP expects empty user/pass as NULL, we're less strict FS#2781 - if(empty($opts['admin_username'])) $opts['admin_username'] = null; - if(empty($opts['admin_password'])) $opts['admin_password'] = null; + if (empty($opts['admin_username'])) $opts['admin_username'] = null; + if (empty($opts['admin_password'])) $opts['admin_password'] = null; // user listing needs admin priviledges - if(!empty($opts['admin_username']) && !empty($opts['admin_password'])) { + if (!empty($opts['admin_username']) && !empty($opts['admin_password'])) { $this->cando['getUsers'] = true; } else { $this->cando['getUsers'] = false; @@ -672,16 +714,17 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * * @return array associative array(key => domain) */ - public function _getConfiguredDomains() { + public function getConfiguredDomains() + { $domains = array(); - if(empty($this->conf['account_suffix'])) return $domains; // not configured yet + if (empty($this->conf['account_suffix'])) return $domains; // not configured yet // add default domain, using the name from account suffix $domains[''] = ltrim($this->conf['account_suffix'], '@'); // find additional domains - foreach($this->conf as $key => $val) { - if(is_array($val) && isset($val['account_suffix'])) { + foreach ($this->conf as $key => $val) { + if (is_array($val) && isset($val['account_suffix'])) { $domains[$key] = ltrim($val['account_suffix'], '@'); } } @@ -701,14 +744,15 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * @param array $info * @return bool */ - protected function _filter($user, $info) { - foreach($this->_pattern as $item => $pattern) { - if($item == 'user') { - if(!preg_match($pattern, $user)) return false; - } else if($item == 'grps') { - if(!count(preg_grep($pattern, $info['grps']))) return false; + protected function filter($user, $info) + { + foreach ($this->pattern as $item => $pattern) { + if ($item == 'user') { + if (!preg_match($pattern, $user)) return false; + } elseif ($item == 'grps') { + if (!count(preg_grep($pattern, $info['grps']))) return false; } else { - if(!preg_match($pattern, $info[$item])) return false; + if (!preg_match($pattern, $info[$item])) return false; } } return true; @@ -721,10 +765,11 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * * @param array $filter */ - protected function _constructPattern($filter) { - $this->_pattern = array(); - foreach($filter as $item => $pattern) { - $this->_pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters + protected function constructPattern($filter) + { + $this->pattern = array(); + foreach ($filter as $item => $pattern) { + $this->pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters } } } diff --git a/lib/plugins/authldap/auth.php b/lib/plugins/authldap/auth.php index 059611052..68d1dad60 100644 --- a/lib/plugins/authldap/auth.php +++ b/lib/plugins/authldap/auth.php @@ -1,6 +1,4 @@ <?php -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); /** * LDAP authentication backend @@ -10,8 +8,9 @@ if(!defined('DOKU_INC')) die(); * @author Chris Smith <chris@jalakaic.co.uk> * @author Jan Schumann <js@schumann-it.com> */ -class auth_plugin_authldap extends DokuWiki_Auth_Plugin { - /* @var resource $con holds the LDAP connection*/ +class auth_plugin_authldap extends DokuWiki_Auth_Plugin +{ + /* @var resource $con holds the LDAP connection */ protected $con = null; /* @var int $bound What type of connection does already exist? */ @@ -20,18 +19,19 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { /* @var array $users User data cache */ protected $users = null; - /* @var array $_pattern User filter pattern */ - protected $_pattern = null; + /* @var array $pattern User filter pattern */ + protected $pattern = null; /** * Constructor */ - public function __construct() { + public function __construct() + { parent::__construct(); // ldap extension is needed - if(!function_exists('ldap_connect')) { - $this->_debug("LDAP err: PHP LDAP extension not found.", -1, __LINE__, __FILE__); + if (!function_exists('ldap_connect')) { + $this->debug("LDAP err: PHP LDAP extension not found.", -1, __LINE__, __FILE__); $this->success = false; return; } @@ -47,73 +47,72 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { * plaintext password is correct by trying to bind * to the LDAP server * - * @author Andreas Gohr <andi@splitbrain.org> * @param string $user * @param string $pass * @return bool + * @author Andreas Gohr <andi@splitbrain.org> */ - public function checkPass($user, $pass) { + public function checkPass($user, $pass) + { // reject empty password - if(empty($pass)) return false; - if(!$this->_openLDAP()) return false; + if (empty($pass)) return false; + if (!$this->openLDAP()) return false; // indirect user bind - if($this->getConf('binddn') && $this->getConf('bindpw')) { + if ($this->getConf('binddn') && $this->getConf('bindpw')) { // use superuser credentials - if(!@ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw')))) { - $this->_debug('LDAP bind as superuser: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + if (!@ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw')))) { + $this->debug('LDAP bind as superuser: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__); return false; } $this->bound = 2; - } else if($this->getConf('binddn') && + } elseif ($this->getConf('binddn') && $this->getConf('usertree') && $this->getConf('userfilter') ) { // special bind string - $dn = $this->_makeFilter( + $dn = $this->makeFilter( $this->getConf('binddn'), - array('user'=> $user, 'server'=> $this->getConf('server')) + array('user' => $user, 'server' => $this->getConf('server')) ); - - } else if(strpos($this->getConf('usertree'), '%{user}')) { + } elseif (strpos($this->getConf('usertree'), '%{user}')) { // direct user bind - $dn = $this->_makeFilter( + $dn = $this->makeFilter( $this->getConf('usertree'), - array('user'=> $user, 'server'=> $this->getConf('server')) + array('user' => $user, 'server' => $this->getConf('server')) ); - } else { // Anonymous bind - if(!@ldap_bind($this->con)) { + if (!@ldap_bind($this->con)) { msg("LDAP: can not bind anonymously", -1); - $this->_debug('LDAP anonymous bind: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + $this->debug('LDAP anonymous bind: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__); return false; } } // Try to bind to with the dn if we have one. - if(!empty($dn)) { + if (!empty($dn)) { // User/Password bind - if(!@ldap_bind($this->con, $dn, $pass)) { - $this->_debug("LDAP: bind with $dn failed", -1, __LINE__, __FILE__); - $this->_debug('LDAP user dn bind: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + if (!@ldap_bind($this->con, $dn, $pass)) { + $this->debug("LDAP: bind with $dn failed", -1, __LINE__, __FILE__); + $this->debug('LDAP user dn bind: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__); return false; } $this->bound = 1; return true; } else { // See if we can find the user - $info = $this->_getUserData($user, true); - if(empty($info['dn'])) { + $info = $this->fetchUserData($user, true); + if (empty($info['dn'])) { return false; } else { $dn = $info['dn']; } // Try to bind with the dn provided - if(!@ldap_bind($this->con, $dn, $pass)) { - $this->_debug("LDAP: bind with $dn failed", -1, __LINE__, __FILE__); - $this->_debug('LDAP user bind: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + if (!@ldap_bind($this->con, $dn, $pass)) { + $this->debug("LDAP: bind with $dn failed", -1, __LINE__, __FILE__); + $this->debug('LDAP user bind: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__); return false; } $this->bound = 1; @@ -138,113 +137,119 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { * uid string Posix User ID * inbind bool for internal use - avoid loop in binding * - * @author Andreas Gohr <andi@splitbrain.org> - * @author Trouble - * @author Dan Allen <dan.j.allen@gmail.com> + * @param string $user + * @param bool $requireGroups (optional) - ignored, groups are always supplied by this plugin + * @return array containing user data or false * @author <evaldas.auryla@pheur.org> * @author Stephane Chazelas <stephane.chazelas@emerson.com> * @author Steffen Schoch <schoch@dsb.net> * - * @param string $user - * @param bool $requireGroups (optional) - ignored, groups are always supplied by this plugin - * @return array containing user data or false + * @author Andreas Gohr <andi@splitbrain.org> + * @author Trouble + * @author Dan Allen <dan.j.allen@gmail.com> */ - public function getUserData($user, $requireGroups=true) { - return $this->_getUserData($user); + public function getUserData($user, $requireGroups = true) + { + return $this->fetchUserData($user); } /** - * @param string $user - * @param bool $inbind authldap specific, true if in bind phase + * @param string $user + * @param bool $inbind authldap specific, true if in bind phase * @return array containing user data or false */ - protected function _getUserData($user, $inbind = false) { + protected function fetchUserData($user, $inbind = false) + { global $conf; - if(!$this->_openLDAP()) return false; + if (!$this->openLDAP()) return array(); // force superuser bind if wanted and not bound as superuser yet - if($this->getConf('binddn') && $this->getConf('bindpw') && $this->bound < 2) { + if ($this->getConf('binddn') && $this->getConf('bindpw') && $this->bound < 2) { // use superuser credentials - if(!@ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw')))) { - $this->_debug('LDAP bind as superuser: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); - return false; + if (!@ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw')))) { + $this->debug('LDAP bind as superuser: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__); + return array(); } $this->bound = 2; - } elseif($this->bound == 0 && !$inbind) { + } elseif ($this->bound == 0 && !$inbind) { // in some cases getUserData is called outside the authentication workflow // eg. for sending email notification on subscribed pages. This data might not // be accessible anonymously, so we try to rebind the current user here list($loginuser, $loginsticky, $loginpass) = auth_getCookie(); - if($loginuser && $loginpass) { + if ($loginuser && $loginpass) { $loginpass = auth_decrypt($loginpass, auth_cookiesalt(!$loginsticky, true)); $this->checkPass($loginuser, $loginpass); } } $info = array(); - $info['user'] = $user; - $this->_debug('LDAP user to find: '.htmlspecialchars($info['user']), 0, __LINE__, __FILE__); + $info['user'] = $user; + $this->debug('LDAP user to find: ' . hsc($info['user']), 0, __LINE__, __FILE__); $info['server'] = $this->getConf('server'); - $this->_debug('LDAP Server: '.htmlspecialchars($info['server']), 0, __LINE__, __FILE__); - + $this->debug('LDAP Server: ' . hsc($info['server']), 0, __LINE__, __FILE__); //get info for given user - $base = $this->_makeFilter($this->getConf('usertree'), $info); - if($this->getConf('userfilter')) { - $filter = $this->_makeFilter($this->getConf('userfilter'), $info); + $base = $this->makeFilter($this->getConf('usertree'), $info); + if ($this->getConf('userfilter')) { + $filter = $this->makeFilter($this->getConf('userfilter'), $info); } else { $filter = "(ObjectClass=*)"; } - $this->_debug('LDAP Filter: '.htmlspecialchars($filter), 0, __LINE__, __FILE__); + $this->debug('LDAP Filter: ' . hsc($filter), 0, __LINE__, __FILE__); + + $this->debug('LDAP user search: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__); + $this->debug('LDAP search at: ' . hsc($base . ' ' . $filter), 0, __LINE__, __FILE__); + $sr = $this->ldapSearch($this->con, $base, $filter, $this->getConf('userscope'), $this->getConf('attributes')); - $this->_debug('LDAP user search: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); - $this->_debug('LDAP search at: '.htmlspecialchars($base.' '.$filter), 0, __LINE__, __FILE__); - $sr = $this->_ldapsearch($this->con, $base, $filter, $this->getConf('userscope'), $this->getConf('attributes')); - $result = @ldap_get_entries($this->con, $sr); + $result = @ldap_get_entries($this->con, $sr); // if result is not an array - if(!is_array($result)) { - // no objects found - $this->_debug('LDAP search returned non-array result: '.htmlspecialchars(print($result)), -1, __LINE__, __FILE__); - return false; + if (!is_array($result)) { + // no objects found + $this->debug('LDAP search returned non-array result: ' . hsc(print($result)), -1, __LINE__, __FILE__); + return array(); } - // Don't accept more or less than one response - if ($result['count'] != 1) { - $this->_debug('LDAP search returned '.htmlspecialchars($result['count']).' results while it should return 1!', -1, __LINE__, __FILE__); - //for($i = 0; $i < $result["count"]; $i++) { - //$this->_debug('result: '.htmlspecialchars(print_r($result[$i])), 0, __LINE__, __FILE__); - //} - return false; - } - + // Don't accept more or less than one response + if ($result['count'] != 1) { + $this->debug( + 'LDAP search returned ' . hsc($result['count']) . ' results while it should return 1!', + -1, + __LINE__, + __FILE__ + ); + //for($i = 0; $i < $result["count"]; $i++) { + //$this->_debug('result: '.hsc(print_r($result[$i])), 0, __LINE__, __FILE__); + //} + return array(); + } - $this->_debug('LDAP search found single result !', 0, __LINE__, __FILE__); + $this->debug('LDAP search found single result !', 0, __LINE__, __FILE__); $user_result = $result[0]; ldap_free_result($sr); // general user info - $info['dn'] = $user_result['dn']; - $info['gid'] = $user_result['gidnumber'][0]; + $info['dn'] = $user_result['dn']; + $info['gid'] = $user_result['gidnumber'][0]; $info['mail'] = $user_result['mail'][0]; $info['name'] = $user_result['cn'][0]; $info['grps'] = array(); // overwrite if other attribs are specified. - if(is_array($this->getConf('mapping'))) { - foreach($this->getConf('mapping') as $localkey => $key) { - if(is_array($key)) { + if (is_array($this->getConf('mapping'))) { + foreach ($this->getConf('mapping') as $localkey => $key) { + if (is_array($key)) { // use regexp to clean up user_result // $key = array($key=>$regexp), only handles the first key-value $regexp = current($key); $key = key($key); - if($user_result[$key]) foreach($user_result[$key] as $grpkey => $grp) { - if($grpkey !== 'count' && preg_match($regexp, $grp, $match)) { - if($localkey == 'grps') { + if ($user_result[$key]) foreach ($user_result[$key] as $grpkey => $grp) { + if ($grpkey !== 'count' && preg_match($regexp, $grp, $match)) { + if ($localkey == 'grps') { $info[$localkey][] = $match[1]; } else { $info[$localkey] = $match[1]; @@ -259,38 +264,44 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { $user_result = array_merge($info, $user_result); //get groups for given user if grouptree is given - if($this->getConf('grouptree') || $this->getConf('groupfilter')) { - $base = $this->_makeFilter($this->getConf('grouptree'), $user_result); - $filter = $this->_makeFilter($this->getConf('groupfilter'), $user_result); - $sr = $this->_ldapsearch($this->con, $base, $filter, $this->getConf('groupscope'), array($this->getConf('groupkey'))); - $this->_debug('LDAP group search: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); - $this->_debug('LDAP search at: '.htmlspecialchars($base.' '.$filter), 0, __LINE__, __FILE__); - - if(!$sr) { + if ($this->getConf('grouptree') || $this->getConf('groupfilter')) { + $base = $this->makeFilter($this->getConf('grouptree'), $user_result); + $filter = $this->makeFilter($this->getConf('groupfilter'), $user_result); + $sr = $this->ldapSearch( + $this->con, + $base, + $filter, + $this->getConf('groupscope'), + array($this->getConf('groupkey')) + ); + $this->debug('LDAP group search: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__); + $this->debug('LDAP search at: ' . hsc($base . ' ' . $filter), 0, __LINE__, __FILE__); + + if (!$sr) { msg("LDAP: Reading group memberships failed", -1); - return false; + return array(); } $result = ldap_get_entries($this->con, $sr); ldap_free_result($sr); - if(is_array($result)) foreach($result as $grp) { - if(!empty($grp[$this->getConf('groupkey')])) { + if (is_array($result)) foreach ($result as $grp) { + if (!empty($grp[$this->getConf('groupkey')])) { $group = $grp[$this->getConf('groupkey')]; - if(is_array($group)){ + if (is_array($group)) { $group = $group[0]; } else { - $this->_debug('groupkey did not return a detailled result', 0, __LINE__, __FILE__); + $this->debug('groupkey did not return a detailled result', 0, __LINE__, __FILE__); } - if($group === '') continue; + if ($group === '') continue; - $this->_debug('LDAP usergroup: '.htmlspecialchars($group), 0, __LINE__, __FILE__); + $this->debug('LDAP usergroup: ' . hsc($group), 0, __LINE__, __FILE__); $info['grps'][] = $group; } } } // always add the default group to the list of groups - if(!$info['grps'] or !in_array($conf['defaultgroup'], $info['grps'])) { + if (!$info['grps'] or !in_array($conf['defaultgroup'], $info['grps'])) { $info['grps'][] = $conf['defaultgroup']; } return $info; @@ -299,57 +310,66 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { /** * Definition of the function modifyUser in order to modify the password * - * @param string $user nick of the user to be changed - * @param array $changes array of field/value pairs to be changed (password will be clear text) + * @param string $user nick of the user to be changed + * @param array $changes array of field/value pairs to be changed (password will be clear text) * @return bool true on success, false on error */ - - function modifyUser($user,$changes){ + public function modifyUser($user, $changes) + { // open the connection to the ldap - if(!$this->_openLDAP()){ - $this->_debug('LDAP cannot connect: '. htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + if (!$this->openLDAP()) { + $this->debug('LDAP cannot connect: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__); return false; } // find the information about the user, in particular the "dn" - $info = $this->getUserData($user,true); - if(empty($info['dn'])) { - $this->_debug('LDAP cannot find your user dn', 0, __LINE__, __FILE__); + $info = $this->getUserData($user, true); + if (empty($info['dn'])) { + $this->debug('LDAP cannot find your user dn', 0, __LINE__, __FILE__); return false; } $dn = $info['dn']; // find the old password of the user - list($loginuser,$loginsticky,$loginpass) = auth_getCookie(); + list($loginuser, $loginsticky, $loginpass) = auth_getCookie(); if ($loginuser !== null) { // the user is currently logged in $secret = auth_cookiesalt(!$loginsticky, true); - $pass = auth_decrypt($loginpass, $secret); + $pass = auth_decrypt($loginpass, $secret); // bind with the ldap - if(!@ldap_bind($this->con, $dn, $pass)){ - $this->_debug('LDAP user bind failed: '. htmlspecialchars($dn) .': '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + if (!@ldap_bind($this->con, $dn, $pass)) { + $this->debug( + 'LDAP user bind failed: ' . hsc($dn) . ': ' . hsc(ldap_error($this->con)), + 0, + __LINE__, + __FILE__ + ); return false; } } elseif ($this->getConf('binddn') && $this->getConf('bindpw')) { // we are changing the password on behalf of the user (eg: forgotten password) // bind with the superuser ldap - if (!@ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw')))){ - $this->_debug('LDAP bind as superuser: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + if (!@ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw')))) { + $this->debug('LDAP bind as superuser: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__); return false; } - } - else { + } else { return false; // no otherway } // Generate the salted hashed password for LDAP - $phash = new PassHash(); + $phash = new \dokuwiki\PassHash(); $hash = $phash->hash_ssha($changes['pass']); // change the password - if(!@ldap_mod_replace($this->con, $dn,array('userpassword' => $hash))){ - $this->_debug('LDAP mod replace failed: '. htmlspecialchars($dn) .': '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + if (!@ldap_mod_replace($this->con, $dn, array('userpassword' => $hash))) { + $this->debug( + 'LDAP mod replace failed: ' . hsc($dn) . ': ' . hsc(ldap_error($this->con)), + 0, + __LINE__, + __FILE__ + ); return false; } @@ -361,56 +381,58 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { * * @return bool */ - public function isCaseSensitive() { + public function isCaseSensitive() + { return false; } /** * Bulk retrieval of user data * - * @author Dominik Eckelmann <dokuwiki@cosmocode.de> - * @param int $start index of first user to be returned - * @param int $limit max number of users to be returned - * @param array $filter array of field/pattern pairs, null for no filter + * @param int $start index of first user to be returned + * @param int $limit max number of users to be returned + * @param array $filter array of field/pattern pairs, null for no filter * @return array of userinfo (refer getUserData for internal userinfo details) + * @author Dominik Eckelmann <dokuwiki@cosmocode.de> */ - function retrieveUsers($start = 0, $limit = 0, $filter = array()) { - if(!$this->_openLDAP()) return false; + public function retrieveUsers($start = 0, $limit = 0, $filter = array()) + { + if (!$this->openLDAP()) return array(); - if(is_null($this->users)) { + if (is_null($this->users)) { // Perform the search and grab all their details - if($this->getConf('userfilter')) { + if ($this->getConf('userfilter')) { $all_filter = str_replace('%{user}', '*', $this->getConf('userfilter')); } else { $all_filter = "(ObjectClass=*)"; } - $sr = ldap_search($this->con, $this->getConf('usertree'), $all_filter); - $entries = ldap_get_entries($this->con, $sr); + $sr = ldap_search($this->con, $this->getConf('usertree'), $all_filter); + $entries = ldap_get_entries($this->con, $sr); $users_array = array(); - $userkey = $this->getConf('userkey'); - for($i = 0; $i < $entries["count"]; $i++) { + $userkey = $this->getConf('userkey'); + for ($i = 0; $i < $entries["count"]; $i++) { array_push($users_array, $entries[$i][$userkey][0]); } asort($users_array); $result = $users_array; - if(!$result) return array(); + if (!$result) return array(); $this->users = array_fill_keys($result, false); } - $i = 0; + $i = 0; $count = 0; - $this->_constructPattern($filter); + $this->constructPattern($filter); $result = array(); - foreach($this->users as $user => &$info) { - if($i++ < $start) { + foreach ($this->users as $user => &$info) { + if ($i++ < $start) { continue; } - if($info === false) { + if ($info === false) { $info = $this->getUserData($user); } - if($this->_filter($user, $info)) { + if ($this->filter($user, $info)) { $result[$user] = $info; - if(($limit > 0) && (++$count >= $limit)) break; + if (($limit > 0) && (++$count >= $limit)) break; } } return $result; @@ -422,23 +444,24 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { * Used by auth_getUserData to make the filter * strings for grouptree and groupfilter * - * @author Troels Liebe Bentsen <tlb@rapanden.dk> - * @param string $filter ldap search filter with placeholders - * @param array $placeholders placeholders to fill in + * @param string $filter ldap search filter with placeholders + * @param array $placeholders placeholders to fill in * @return string + * @author Troels Liebe Bentsen <tlb@rapanden.dk> */ - protected function _makeFilter($filter, $placeholders) { + protected function makeFilter($filter, $placeholders) + { preg_match_all("/%{([^}]+)/", $filter, $matches, PREG_PATTERN_ORDER); //replace each match - foreach($matches[1] as $match) { + foreach ($matches[1] as $match) { //take first element if array - if(is_array($placeholders[$match])) { + if (is_array($placeholders[$match])) { $value = $placeholders[$match][0]; } else { $value = $placeholders[$match]; } - $value = $this->_filterEscape($value); - $filter = str_replace('%{'.$match.'}', $value, $filter); + $value = $this->filterEscape($value); + $filter = str_replace('%{' . $match . '}', $value, $filter); } return $filter; } @@ -446,20 +469,21 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { /** * return true if $user + $info match $filter criteria, false otherwise * + * @param string $user the user's login name + * @param array $info the user's userinfo array + * @return bool * @author Chris Smith <chris@jalakai.co.uk> * - * @param string $user the user's login name - * @param array $info the user's userinfo array - * @return bool */ - protected function _filter($user, $info) { - foreach($this->_pattern as $item => $pattern) { - if($item == 'user') { - if(!preg_match($pattern, $user)) return false; - } else if($item == 'grps') { - if(!count(preg_grep($pattern, $info['grps']))) return false; + protected function filter($user, $info) + { + foreach ($this->pattern as $item => $pattern) { + if ($item == 'user') { + if (!preg_match($pattern, $user)) return false; + } elseif ($item == 'grps') { + if (!count(preg_grep($pattern, $info['grps']))) return false; } else { - if(!preg_match($pattern, $info[$item])) return false; + if (!preg_match($pattern, $info[$item])) return false; } } return true; @@ -468,15 +492,16 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { /** * Set the filter pattern * - * @author Chris Smith <chris@jalakai.co.uk> - * * @param $filter * @return void + * @author Chris Smith <chris@jalakai.co.uk> + * */ - protected function _constructPattern($filter) { - $this->_pattern = array(); - foreach($filter as $item => $pattern) { - $this->_pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters + protected function constructPattern($filter) + { + $this->pattern = array(); + foreach ($filter as $item => $pattern) { + $this->pattern[$item] = '/' . str_replace('/', '\/', $pattern) . '/i'; // allow regex characters } } @@ -485,16 +510,17 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { * * Ported from Perl's Net::LDAP::Util escape_filter_value * - * @author Andreas Gohr - * @param string $string + * @param string $string * @return string + * @author Andreas Gohr */ - protected function _filterEscape($string) { + protected function filterEscape($string) + { // see https://github.com/adldap/adLDAP/issues/22 return preg_replace_callback( '/([\x00-\x1F\*\(\)\\\\])/', function ($matches) { - return "\\".join("", unpack("H2", $matches[1])); + return "\\" . join("", unpack("H2", $matches[1])); }, $string ); @@ -506,22 +532,23 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { * * @author Andreas Gohr <andi@splitbrain.org> */ - protected function _openLDAP() { - if($this->con) return true; // connection already established + protected function openLDAP() + { + if ($this->con) return true; // connection already established - if($this->getConf('debug')) { - ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7); + if ($this->getConf('debug')) { + ldap_set_option(null, LDAP_OPT_DEBUG_LEVEL, 7); } $this->bound = 0; - $port = $this->getConf('port'); - $bound = false; + $port = $this->getConf('port'); + $bound = false; $servers = explode(',', $this->getConf('server')); - foreach($servers as $server) { - $server = trim($server); + foreach ($servers as $server) { + $server = trim($server); $this->con = @ldap_connect($server, $port); - if(!$this->con) { + if (!$this->con) { continue; } @@ -534,62 +561,64 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { */ //set protocol version and dependend options - if($this->getConf('version')) { - if(!@ldap_set_option( - $this->con, LDAP_OPT_PROTOCOL_VERSION, + if ($this->getConf('version')) { + if (!@ldap_set_option( + $this->con, + LDAP_OPT_PROTOCOL_VERSION, $this->getConf('version') ) ) { - msg('Setting LDAP Protocol version '.$this->getConf('version').' failed', -1); - $this->_debug('LDAP version set: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + msg('Setting LDAP Protocol version ' . $this->getConf('version') . ' failed', -1); + $this->debug('LDAP version set: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__); } else { //use TLS (needs version 3) - if($this->getConf('starttls')) { - if(!@ldap_start_tls($this->con)) { + if ($this->getConf('starttls')) { + if (!@ldap_start_tls($this->con)) { msg('Starting TLS failed', -1); - $this->_debug('LDAP TLS set: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + $this->debug('LDAP TLS set: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__); } } // needs version 3 - if($this->getConf('referrals') > -1) { - if(!@ldap_set_option( - $this->con, LDAP_OPT_REFERRALS, + if ($this->getConf('referrals') > -1) { + if (!@ldap_set_option( + $this->con, + LDAP_OPT_REFERRALS, $this->getConf('referrals') ) ) { msg('Setting LDAP referrals failed', -1); - $this->_debug('LDAP referal set: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + $this->debug('LDAP referal set: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__); } } } } //set deref mode - if($this->getConf('deref')) { - if(!@ldap_set_option($this->con, LDAP_OPT_DEREF, $this->getConf('deref'))) { - msg('Setting LDAP Deref mode '.$this->getConf('deref').' failed', -1); - $this->_debug('LDAP deref set: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + if ($this->getConf('deref')) { + if (!@ldap_set_option($this->con, LDAP_OPT_DEREF, $this->getConf('deref'))) { + msg('Setting LDAP Deref mode ' . $this->getConf('deref') . ' failed', -1); + $this->debug('LDAP deref set: ' . hsc(ldap_error($this->con)), 0, __LINE__, __FILE__); } } /* As of PHP 5.3.0 we can set timeout to speedup skipping of invalid servers */ - if(defined('LDAP_OPT_NETWORK_TIMEOUT')) { + if (defined('LDAP_OPT_NETWORK_TIMEOUT')) { ldap_set_option($this->con, LDAP_OPT_NETWORK_TIMEOUT, 1); } - if($this->getConf('binddn') && $this->getConf('bindpw')) { + if ($this->getConf('binddn') && $this->getConf('bindpw')) { $bound = @ldap_bind($this->con, $this->getConf('binddn'), conf_decodeString($this->getConf('bindpw'))); $this->bound = 2; } else { $bound = @ldap_bind($this->con); } - if($bound) { + if ($bound) { break; } } - if(!$bound) { + if (!$bound) { msg("LDAP: couldn't connect to LDAP server", -1); - $this->_debug(ldap_error($this->con), 0, __LINE__, __FILE__); + $this->debug(ldap_error($this->con), 0, __LINE__, __FILE__); return false; } @@ -600,34 +629,54 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { /** * Wraps around ldap_search, ldap_list or ldap_read depending on $scope * - * @author Andreas Gohr <andi@splitbrain.org> - * @param resource $link_identifier - * @param string $base_dn - * @param string $filter - * @param string $scope can be 'base', 'one' or 'sub' + * @param resource $link_identifier + * @param string $base_dn + * @param string $filter + * @param string $scope can be 'base', 'one' or 'sub' * @param null|array $attributes - * @param int $attrsonly - * @param int $sizelimit + * @param int $attrsonly + * @param int $sizelimit * @return resource + * @author Andreas Gohr <andi@splitbrain.org> */ - protected function _ldapsearch($link_identifier, $base_dn, $filter, $scope = 'sub', $attributes = null, - $attrsonly = 0, $sizelimit = 0) { - if(is_null($attributes)) $attributes = array(); - - if($scope == 'base') { + protected function ldapSearch( + $link_identifier, + $base_dn, + $filter, + $scope = 'sub', + $attributes = null, + $attrsonly = 0, + $sizelimit = 0 + ) + { + if (is_null($attributes)) $attributes = array(); + + if ($scope == 'base') { return @ldap_read( - $link_identifier, $base_dn, $filter, $attributes, - $attrsonly, $sizelimit + $link_identifier, + $base_dn, + $filter, + $attributes, + $attrsonly, + $sizelimit ); - } elseif($scope == 'one') { + } elseif ($scope == 'one') { return @ldap_list( - $link_identifier, $base_dn, $filter, $attributes, - $attrsonly, $sizelimit + $link_identifier, + $base_dn, + $filter, + $attributes, + $attrsonly, + $sizelimit ); } else { return @ldap_search( - $link_identifier, $base_dn, $filter, $attributes, - $attrsonly, $sizelimit + $link_identifier, + $base_dn, + $filter, + $attributes, + $attrsonly, + $sizelimit ); } } @@ -636,14 +685,14 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { * Wrapper around msg() but outputs only when debug is enabled * * @param string $message - * @param int $err - * @param int $line + * @param int $err + * @param int $line * @param string $file * @return void */ - protected function _debug($message, $err, $line, $file) { - if(!$this->getConf('debug')) return; + protected function debug($message, $err, $line, $file) + { + if (!$this->getConf('debug')) return; msg($message, $err, $line, $file); } - } diff --git a/lib/plugins/authpdo/_test/sqlite.test.php b/lib/plugins/authpdo/_test/sqlite.test.php index 35b612604..89cc9f60d 100644 --- a/lib/plugins/authpdo/_test/sqlite.test.php +++ b/lib/plugins/authpdo/_test/sqlite.test.php @@ -10,8 +10,8 @@ class testable_auth_plugin_authpdo extends auth_plugin_authpdo { return 'authpdo'; } - public function _selectGroups() { - return parent::_selectGroups(); + public function selectGroups() { + return parent::selectGroups(); } public function addGroup($group) { @@ -96,7 +96,7 @@ class sqlite_plugin_authpdo_test extends DokuWikiTest { public function test_internals() { $auth = new testable_auth_plugin_authpdo(); - $groups = $auth->_selectGroups(); + $groups = $auth->selectGroups(); $this->assertArrayHasKey('user', $groups); $this->assertEquals(1, $groups['user']['gid']); $this->assertArrayHasKey('admin', $groups); @@ -104,7 +104,7 @@ class sqlite_plugin_authpdo_test extends DokuWikiTest { $ok = $auth->addGroup('test'); $this->assertTrue($ok); - $groups = $auth->_selectGroups(); + $groups = $auth->selectGroups(); $this->assertArrayHasKey('test', $groups); $this->assertEquals(3, $groups['test']['gid']); } diff --git a/lib/plugins/authpdo/auth.php b/lib/plugins/authpdo/auth.php index 12263b7b9..9c0968e30 100644 --- a/lib/plugins/authpdo/auth.php +++ b/lib/plugins/authpdo/auth.php @@ -6,13 +6,11 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - /** * Class auth_plugin_authpdo */ -class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { +class auth_plugin_authpdo extends DokuWiki_Auth_Plugin +{ /** @var PDO */ protected $pdo; @@ -23,17 +21,18 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { /** * Constructor. */ - public function __construct() { + public function __construct() + { parent::__construct(); // for compatibility - if(!class_exists('PDO')) { - $this->_debug('PDO extension for PHP not found.', -1, __LINE__); + if (!class_exists('PDO')) { + $this->debugMsg('PDO extension for PHP not found.', -1, __LINE__); $this->success = false; return; } - if(!$this->getConf('dsn')) { - $this->_debug('No DSN specified', -1, __LINE__); + if (!$this->getConf('dsn')) { + $this->debugMsg('No DSN specified', -1, __LINE__); $this->success = false; return; } @@ -49,15 +48,15 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // we want exceptions, not error codes ) ); - } catch(PDOException $e) { - $this->_debug($e); + } catch (PDOException $e) { + $this->debugMsg($e); msg($this->getLang('connectfail'), -1); $this->success = false; return; } // can Users be created? - $this->cando['addUser'] = $this->_chkcnf( + $this->cando['addUser'] = $this->checkConfig( array( 'select-user', 'select-user-groups', @@ -69,7 +68,7 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { ); // can Users be deleted? - $this->cando['delUser'] = $this->_chkcnf( + $this->cando['delUser'] = $this->checkConfig( array( 'select-user', 'select-user-groups', @@ -80,7 +79,7 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { ); // can login names be changed? - $this->cando['modLogin'] = $this->_chkcnf( + $this->cando['modLogin'] = $this->checkConfig( array( 'select-user', 'select-user-groups', @@ -89,7 +88,7 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { ); // can passwords be changed? - $this->cando['modPass'] = $this->_chkcnf( + $this->cando['modPass'] = $this->checkConfig( array( 'select-user', 'select-user-groups', @@ -98,7 +97,7 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { ); // can real names be changed? - $this->cando['modName'] = $this->_chkcnf( + $this->cando['modName'] = $this->checkConfig( array( 'select-user', 'select-user-groups', @@ -107,7 +106,7 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { ); // can real email be changed? - $this->cando['modMail'] = $this->_chkcnf( + $this->cando['modMail'] = $this->checkConfig( array( 'select-user', 'select-user-groups', @@ -116,7 +115,7 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { ); // can groups be changed? - $this->cando['modGroups'] = $this->_chkcnf( + $this->cando['modGroups'] = $this->checkConfig( array( 'select-user', 'select-user-groups', @@ -128,21 +127,21 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { ); // can a filtered list of users be retrieved? - $this->cando['getUsers'] = $this->_chkcnf( + $this->cando['getUsers'] = $this->checkConfig( array( 'list-users' ) ); // can the number of users be retrieved? - $this->cando['getUserCount'] = $this->_chkcnf( + $this->cando['getUserCount'] = $this->checkConfig( array( 'count-users' ) ); // can a list of available groups be retrieved? - $this->cando['getGroups'] = $this->_chkcnf( + $this->cando['getGroups'] = $this->checkConfig( array( 'select-groups' ) @@ -154,28 +153,29 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { /** * Check user+password * - * @param string $user the user name - * @param string $pass the clear text password + * @param string $user the user name + * @param string $pass the clear text password * @return bool */ - public function checkPass($user, $pass) { + public function checkPass($user, $pass) + { - $userdata = $this->_selectUser($user); - if($userdata == false) return false; + $userdata = $this->selectUser($user); + if ($userdata == false) return false; // password checking done in SQL? - if($this->_chkcnf(array('check-pass'))) { + if ($this->checkConfig(array('check-pass'))) { $userdata['clear'] = $pass; $userdata['hash'] = auth_cryptPassword($pass); - $result = $this->_query($this->getConf('check-pass'), $userdata); - if($result === false) return false; + $result = $this->query($this->getConf('check-pass'), $userdata); + if ($result === false) return false; return (count($result) == 1); } // we do password checking on our own - if(isset($userdata['hash'])) { + if (isset($userdata['hash'])) { // hashed password - $passhash = new PassHash(); + $passhash = new \dokuwiki\PassHash(); return $passhash->verify_hash($pass, $userdata['hash']); } else { // clear text password in the database O_o @@ -193,20 +193,21 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * mail string email addres of the user * grps array list of groups the user is in * - * @param string $user the user name - * @param bool $requireGroups whether or not the returned data must include groups + * @param string $user the user name + * @param bool $requireGroups whether or not the returned data must include groups * @return array|bool containing user data or false */ - public function getUserData($user, $requireGroups = true) { - $data = $this->_selectUser($user); - if($data == false) return false; + public function getUserData($user, $requireGroups = true) + { + $data = $this->selectUser($user); + if ($data == false) return false; - if(isset($data['hash'])) unset($data['hash']); - if(isset($data['clean'])) unset($data['clean']); + if (isset($data['hash'])) unset($data['hash']); + if (isset($data['clean'])) unset($data['clean']); - if($requireGroups) { - $data['grps'] = $this->_selectUserGroups($data); - if($data['grps'] === false) return false; + if ($requireGroups) { + $data['grps'] = $this->selectUserGroups($data); + if ($data['grps'] === false) return false; } return $data; @@ -223,23 +224,24 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * * Set addUser capability when implemented * - * @param string $user - * @param string $clear - * @param string $name - * @param string $mail - * @param null|array $grps + * @param string $user + * @param string $clear + * @param string $name + * @param string $mail + * @param null|array $grps * @return bool|null */ - public function createUser($user, $clear, $name, $mail, $grps = null) { + public function createUser($user, $clear, $name, $mail, $grps = null) + { global $conf; - if(($info = $this->getUserData($user, false)) !== false) { + if (($info = $this->getUserData($user, false)) !== false) { msg($this->getLang('userexists'), -1); return false; // user already exists } // prepare data - if($grps == null) $grps = array(); + if ($grps == null) $grps = array(); array_unshift($grps, $conf['defaultgroup']); $grps = array_unique($grps); $hash = auth_cryptPassword($clear); @@ -249,25 +251,25 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { $this->pdo->beginTransaction(); { // insert the user - $ok = $this->_query($this->getConf('insert-user'), $userdata); - if($ok === false) goto FAIL; + $ok = $this->query($this->getConf('insert-user'), $userdata); + if ($ok === false) goto FAIL; $userdata = $this->getUserData($user, false); - if($userdata === false) goto FAIL; + if ($userdata === false) goto FAIL; // create all groups that do not exist, the refetch the groups - $allgroups = $this->_selectGroups(); - foreach($grps as $group) { - if(!isset($allgroups[$group])) { + $allgroups = $this->selectGroups(); + foreach ($grps as $group) { + if (!isset($allgroups[$group])) { $ok = $this->addGroup($group); - if($ok === false) goto FAIL; + if ($ok === false) goto FAIL; } } - $allgroups = $this->_selectGroups(); + $allgroups = $this->selectGroups(); // add user to the groups - foreach($grps as $group) { - $ok = $this->_joinGroup($userdata, $allgroups[$group]); - if($ok === false) goto FAIL; + foreach ($grps as $group) { + $ok = $this->joinGroup($userdata, $allgroups[$group]); + if ($ok === false) goto FAIL; } } $this->pdo->commit(); @@ -276,7 +278,7 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { // something went wrong, rollback FAIL: $this->pdo->rollBack(); - $this->_debug('Transaction rolled back', 0, __LINE__); + $this->debugMsg('Transaction rolled back', 0, __LINE__); msg($this->getLang('writefail'), -1); return null; // return error } @@ -284,11 +286,12 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { /** * Modify user data * - * @param string $user nick of the user to be changed - * @param array $changes array of field/value pairs to be changed (password will be clear text) + * @param string $user nick of the user to be changed + * @param array $changes array of field/value pairs to be changed (password will be clear text) * @return bool */ - public function modifyUser($user, $changes) { + public function modifyUser($user, $changes) + { // secure everything in transaction $this->pdo->beginTransaction(); { @@ -297,64 +300,64 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { unset($olddata['grps']); // changing the user name? - if(isset($changes['user'])) { - if($this->getUserData($changes['user'], false)) goto FAIL; + if (isset($changes['user'])) { + if ($this->getUserData($changes['user'], false)) goto FAIL; $params = $olddata; $params['newlogin'] = $changes['user']; - $ok = $this->_query($this->getConf('update-user-login'), $params); - if($ok === false) goto FAIL; + $ok = $this->query($this->getConf('update-user-login'), $params); + if ($ok === false) goto FAIL; } // changing the password? - if(isset($changes['pass'])) { + if (isset($changes['pass'])) { $params = $olddata; $params['clear'] = $changes['pass']; $params['hash'] = auth_cryptPassword($changes['pass']); - $ok = $this->_query($this->getConf('update-user-pass'), $params); - if($ok === false) goto FAIL; + $ok = $this->query($this->getConf('update-user-pass'), $params); + if ($ok === false) goto FAIL; } // changing info? - if(isset($changes['mail']) || isset($changes['name'])) { + if (isset($changes['mail']) || isset($changes['name'])) { $params = $olddata; - if(isset($changes['mail'])) $params['mail'] = $changes['mail']; - if(isset($changes['name'])) $params['name'] = $changes['name']; + if (isset($changes['mail'])) $params['mail'] = $changes['mail']; + if (isset($changes['name'])) $params['name'] = $changes['name']; - $ok = $this->_query($this->getConf('update-user-info'), $params); - if($ok === false) goto FAIL; + $ok = $this->query($this->getConf('update-user-info'), $params); + if ($ok === false) goto FAIL; } // changing groups? - if(isset($changes['grps'])) { - $allgroups = $this->_selectGroups(); + if (isset($changes['grps'])) { + $allgroups = $this->selectGroups(); // remove membership for previous groups - foreach($oldgroups as $group) { - if(!in_array($group, $changes['grps']) && isset($allgroups[$group])) { - $ok = $this->_leaveGroup($olddata, $allgroups[$group]); - if($ok === false) goto FAIL; + foreach ($oldgroups as $group) { + if (!in_array($group, $changes['grps']) && isset($allgroups[$group])) { + $ok = $this->leaveGroup($olddata, $allgroups[$group]); + if ($ok === false) goto FAIL; } } // create all new groups that are missing $added = 0; - foreach($changes['grps'] as $group) { - if(!isset($allgroups[$group])) { + foreach ($changes['grps'] as $group) { + if (!isset($allgroups[$group])) { $ok = $this->addGroup($group); - if($ok === false) goto FAIL; + if ($ok === false) goto FAIL; $added++; } } // reload group info - if($added > 0) $allgroups = $this->_selectGroups(); + if ($added > 0) $allgroups = $this->selectGroups(); // add membership for new groups - foreach($changes['grps'] as $group) { - if(!in_array($group, $oldgroups)) { - $ok = $this->_joinGroup($olddata, $allgroups[$group]); - if($ok === false) goto FAIL; + foreach ($changes['grps'] as $group) { + if (!in_array($group, $oldgroups)) { + $ok = $this->joinGroup($olddata, $allgroups[$group]); + if ($ok === false) goto FAIL; } } } @@ -366,7 +369,7 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { // something went wrong, rollback FAIL: $this->pdo->rollBack(); - $this->_debug('Transaction rolled back', 0, __LINE__); + $this->debugMsg('Transaction rolled back', 0, __LINE__); msg($this->getLang('writefail'), -1); return false; // return error } @@ -376,13 +379,14 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * * Set delUser capability when implemented * - * @param array $users + * @param array $users * @return int number of users deleted */ - public function deleteUsers($users) { + public function deleteUsers($users) + { $count = 0; - foreach($users as $user) { - if($this->_deleteUser($user)) $count++; + foreach ($users as $user) { + if ($this->deleteUser($user)) $count++; } return $count; } @@ -392,40 +396,41 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * * Set getUsers capability when implemented * - * @param int $start index of first user to be returned - * @param int $limit max number of users to be returned - * @param array $filter array of field/pattern pairs, null for no filter + * @param int $start index of first user to be returned + * @param int $limit max number of users to be returned + * @param array $filter array of field/pattern pairs, null for no filter * @return array list of userinfo (refer getUserData for internal userinfo details) */ - public function retrieveUsers($start = 0, $limit = -1, $filter = null) { - if($limit < 0) $limit = 10000; // we don't support no limit - if(is_null($filter)) $filter = array(); - - if(isset($filter['grps'])) $filter['group'] = $filter['grps']; - foreach(array('user', 'name', 'mail', 'group') as $key) { - if(!isset($filter[$key])) { + public function retrieveUsers($start = 0, $limit = -1, $filter = null) + { + if ($limit < 0) $limit = 10000; // we don't support no limit + if (is_null($filter)) $filter = array(); + + if (isset($filter['grps'])) $filter['group'] = $filter['grps']; + foreach (array('user', 'name', 'mail', 'group') as $key) { + if (!isset($filter[$key])) { $filter[$key] = '%'; } else { $filter[$key] = '%' . $filter[$key] . '%'; } } - $filter['start'] = (int) $start; - $filter['end'] = (int) $start + $limit; - $filter['limit'] = (int) $limit; + $filter['start'] = (int)$start; + $filter['end'] = (int)$start + $limit; + $filter['limit'] = (int)$limit; - $result = $this->_query($this->getConf('list-users'), $filter); - if(!$result) return array(); + $result = $this->query($this->getConf('list-users'), $filter); + if (!$result) return array(); $users = array(); - if(is_array($result)){ - foreach($result as $row) { - if(!isset($row['user'])) { - $this->_debug("list-users statement did not return 'user' attribute", -1, __LINE__); + if (is_array($result)) { + foreach ($result as $row) { + if (!isset($row['user'])) { + $this->debugMsg("list-users statement did not return 'user' attribute", -1, __LINE__); return array(); } $users[] = $this->getUserData($row['user']); } - }else{ - $this->_debug("list-users statement did not return a list of result", -1, __LINE__); + } else { + $this->debugMsg("list-users statement did not return a list of result", -1, __LINE__); } return $users; } @@ -433,26 +438,27 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { /** * Return a count of the number of user which meet $filter criteria * - * @param array $filter array of field/pattern pairs, empty array for no filter + * @param array $filter array of field/pattern pairs, empty array for no filter * @return int */ - public function getUserCount($filter = array()) { - if(is_null($filter)) $filter = array(); + public function getUserCount($filter = array()) + { + if (is_null($filter)) $filter = array(); - if(isset($filter['grps'])) $filter['group'] = $filter['grps']; - foreach(array('user', 'name', 'mail', 'group') as $key) { - if(!isset($filter[$key])) { + if (isset($filter['grps'])) $filter['group'] = $filter['grps']; + foreach (array('user', 'name', 'mail', 'group') as $key) { + if (!isset($filter[$key])) { $filter[$key] = '%'; } else { $filter[$key] = '%' . $filter[$key] . '%'; } } - $result = $this->_query($this->getConf('count-users'), $filter); - if(!$result || !isset($result[0]['count'])) { - $this->_debug("Statement did not return 'count' attribute", -1, __LINE__); + $result = $this->query($this->getConf('count-users'), $filter); + if (!$result || !isset($result[0]['count'])) { + $this->debugMsg("Statement did not return 'count' attribute", -1, __LINE__); } - return (int) $result[0]['count']; + return (int)$result[0]['count']; } /** @@ -461,12 +467,13 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * @param string $group * @return bool */ - public function addGroup($group) { + public function addGroup($group) + { $sql = $this->getConf('insert-group'); - $result = $this->_query($sql, array(':group' => $group)); - $this->_clearGroupCache(); - if($result === false) return false; + $result = $this->query($sql, array(':group' => $group)); + $this->clearGroupCache(); + if ($result === false) return false; return true; } @@ -475,15 +482,16 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * * Set getGroups capability when implemented * - * @param int $start - * @param int $limit + * @param int $start + * @param int $limit * @return array */ - public function retrieveGroups($start = 0, $limit = 0) { - $groups = array_keys($this->_selectGroups()); - if($groups === false) return array(); + public function retrieveGroups($start = 0, $limit = 0) + { + $groups = array_keys($this->selectGroups()); + if ($groups === false) return array(); - if(!$limit) { + if (!$limit) { return array_splice($groups, $start); } else { return array_splice($groups, $start, $limit); @@ -496,38 +504,39 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * @param string $user the user name * @return bool|array user data, false on error */ - protected function _selectUser($user) { + protected function selectUser($user) + { $sql = $this->getConf('select-user'); - $result = $this->_query($sql, array(':user' => $user)); - if(!$result) return false; + $result = $this->query($sql, array(':user' => $user)); + if (!$result) return false; - if(count($result) > 1) { - $this->_debug('Found more than one matching user', -1, __LINE__); + if (count($result) > 1) { + $this->debugMsg('Found more than one matching user', -1, __LINE__); return false; } $data = array_shift($result); $dataok = true; - if(!isset($data['user'])) { - $this->_debug("Statement did not return 'user' attribute", -1, __LINE__); + if (!isset($data['user'])) { + $this->debugMsg("Statement did not return 'user' attribute", -1, __LINE__); $dataok = false; } - if(!isset($data['hash']) && !isset($data['clear']) && !$this->_chkcnf(array('check-pass'))) { - $this->_debug("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__); + if (!isset($data['hash']) && !isset($data['clear']) && !$this->checkConfig(array('check-pass'))) { + $this->debugMsg("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__); $dataok = false; } - if(!isset($data['name'])) { - $this->_debug("Statement did not return 'name' attribute", -1, __LINE__); + if (!isset($data['name'])) { + $this->debugMsg("Statement did not return 'name' attribute", -1, __LINE__); $dataok = false; } - if(!isset($data['mail'])) { - $this->_debug("Statement did not return 'mail' attribute", -1, __LINE__); + if (!isset($data['mail'])) { + $this->debugMsg("Statement did not return 'mail' attribute", -1, __LINE__); $dataok = false; } - if(!$dataok) return false; + if (!$dataok) return false; return $data; } @@ -537,22 +546,23 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * @param string $user * @return bool true when the user was deleted */ - protected function _deleteUser($user) { + protected function deleteUser($user) + { $this->pdo->beginTransaction(); { $userdata = $this->getUserData($user); - if($userdata === false) goto FAIL; - $allgroups = $this->_selectGroups(); + if ($userdata === false) goto FAIL; + $allgroups = $this->selectGroups(); // remove group memberships (ignore errors) - foreach($userdata['grps'] as $group) { - if(isset($allgroups[$group])) { - $this->_leaveGroup($userdata, $allgroups[$group]); + foreach ($userdata['grps'] as $group) { + if (isset($allgroups[$group])) { + $this->leaveGroup($userdata, $allgroups[$group]); } } - $ok = $this->_query($this->getConf('delete-user'), $userdata); - if($ok === false) goto FAIL; + $ok = $this->query($this->getConf('delete-user'), $userdata); + if ($ok === false) goto FAIL; } $this->pdo->commit(); return true; @@ -568,23 +578,24 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * @param array $userdata The userdata as returned by _selectUser() * @return array|bool list of group names, false on error */ - protected function _selectUserGroups($userdata) { + protected function selectUserGroups($userdata) + { global $conf; $sql = $this->getConf('select-user-groups'); - $result = $this->_query($sql, $userdata); - if($result === false) return false; + $result = $this->query($sql, $userdata); + if ($result === false) return false; $groups = array($conf['defaultgroup']); // always add default config - if(is_array($result)){ - foreach($result as $row) { - if(!isset($row['group'])) { - $this->_debug("No 'group' field returned in select-user-groups statement"); + if (is_array($result)) { + foreach ($result as $row) { + if (!isset($row['group'])) { + $this->debugMsg("No 'group' field returned in select-user-groups statement", -1, __LINE__); return false; } $groups[] = $row['group']; } - }else{ - $this->_debug("select-user-groups statement did not return a list of result", -1, __LINE__); + } else { + $this->debugMsg("select-user-groups statement did not return a list of result", -1, __LINE__); } $groups = array_unique($groups); @@ -597,18 +608,19 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * * @return array|bool list of all available groups and their properties */ - protected function _selectGroups() { - if($this->groupcache) return $this->groupcache; + protected function selectGroups() + { + if ($this->groupcache) return $this->groupcache; $sql = $this->getConf('select-groups'); - $result = $this->_query($sql); - if($result === false) return false; + $result = $this->query($sql); + if ($result === false) return false; $groups = array(); - if(is_array($result)){ - foreach($result as $row) { - if(!isset($row['group'])) { - $this->_debug("No 'group' field returned from select-groups statement", -1, __LINE__); + if (is_array($result)) { + foreach ($result as $row) { + if (!isset($row['group'])) { + $this->debugMsg("No 'group' field returned from select-groups statement", -1, __LINE__); return false; } @@ -616,8 +628,8 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { $group = $row['group']; $groups[$group] = $row; } - }else{ - $this->_debug("select-groups statement did not return a list of result", -1, __LINE__); + } else { + $this->debugMsg("select-groups statement did not return a list of result", -1, __LINE__); } ksort($groups); @@ -627,7 +639,8 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { /** * Remove all entries from the group cache */ - protected function _clearGroupCache() { + protected function clearGroupCache() + { $this->groupcache = null; } @@ -638,11 +651,12 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * @param array $groupdata all the group data * @return bool */ - protected function _joinGroup($userdata, $groupdata) { + protected function joinGroup($userdata, $groupdata) + { $data = array_merge($userdata, $groupdata); $sql = $this->getConf('join-group'); - $result = $this->_query($sql, $data); - if($result === false) return false; + $result = $this->query($sql, $data); + if ($result === false) return false; return true; } @@ -653,11 +667,12 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * @param array $groupdata all the group data * @return bool */ - protected function _leaveGroup($userdata, $groupdata) { + protected function leaveGroup($userdata, $groupdata) + { $data = array_merge($userdata, $groupdata); $sql = $this->getConf('leave-group'); - $result = $this->_query($sql, $data); - if($result === false) return false; + $result = $this->query($sql, $data); + if ($result === false) return false; return true; } @@ -668,10 +683,11 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * @param array $arguments Named parameters to be used in the statement * @return array|int|bool The result as associative array for SELECTs, affected rows for others, false on error */ - protected function _query($sql, $arguments = array()) { + protected function query($sql, $arguments = array()) + { $sql = trim($sql); - if(empty($sql)) { - $this->_debug('No SQL query given', -1, __LINE__); + if (empty($sql)) { + $this->debugMsg('No SQL query given', -1, __LINE__); return false; } @@ -681,13 +697,13 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { $result = false; try { // prepare parameters - we only use those that exist in the SQL - foreach($arguments as $key => $value) { - if(is_array($value)) continue; - if(is_object($value)) continue; - if($key[0] != ':') $key = ":$key"; // prefix with colon if needed - if(strpos($sql, $key) === false) continue; // skip if parameter is missing + foreach ($arguments as $key => $value) { + if (is_array($value)) continue; + if (is_object($value)) continue; + if ($key[0] != ':') $key = ":$key"; // prefix with colon if needed + if (strpos($sql, $key) === false) continue; // skip if parameter is missing - if(is_int($value)) { + if (is_int($value)) { $sth->bindValue($key, $value, PDO::PARAM_INT); } else { $sth->bindValue($key, $value); @@ -699,29 +715,29 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { // only report last line's result $hasnextrowset = true; $currentsql = $sql; - while($hasnextrowset){ - if(strtolower(substr($currentsql, 0, 6)) == 'select') { + while ($hasnextrowset) { + if (strtolower(substr($currentsql, 0, 6)) == 'select') { $result = $sth->fetchAll(); } else { $result = $sth->rowCount(); } $semi_pos = strpos($currentsql, ';'); - if($semi_pos){ - $currentsql = trim(substr($currentsql, $semi_pos+1)); + if ($semi_pos) { + $currentsql = trim(substr($currentsql, $semi_pos + 1)); } - try{ + try { $hasnextrowset = $sth->nextRowset(); // run next rowset - }catch(PDOException $rowset_e){ + } catch (PDOException $rowset_e) { $hasnextrowset = false; // driver does not support multi-rowset, should be executed in one time } } - } catch(Exception $e) { + } catch (Exception $e) { // report the caller's line $trace = debug_backtrace(); $line = $trace[0]['line']; - $dsql = $this->_debugSQL($sql, $params, !defined('DOKU_UNITTEST')); - $this->_debug($e, -1, $line); - $this->_debug("SQL: <pre>$dsql</pre>", -1, $line); + $dsql = $this->debugSQL($sql, $params, !defined('DOKU_UNITTEST')); + $this->debugMsg($e, -1, $line); + $this->debugMsg("SQL: <pre>$dsql</pre>", -1, $line); } $sth->closeCursor(); $sth = null; @@ -736,17 +752,18 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * @param int $err * @param int $line */ - protected function _debug($message, $err = 0, $line = 0) { - if(!$this->getConf('debug')) return; - if(is_a($message, 'Exception')) { + protected function debugMsg($message, $err = 0, $line = 0) + { + if (!$this->getConf('debug')) return; + if (is_a($message, 'Exception')) { $err = -1; $msg = $message->getMessage(); - if(!$line) $line = $message->getLine(); + if (!$line) $line = $message->getLine(); } else { $msg = $message; } - if(defined('DOKU_UNITTEST')) { + if (defined('DOKU_UNITTEST')) { printf("\n%s, %s:%d\n", $msg, __FILE__, $line); } else { msg('authpdo: ' . $msg, $err, $line, __FILE__); @@ -756,22 +773,23 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { /** * Check if the given config strings are set * + * @param string[] $keys + * @return bool * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net> * - * @param string[] $keys - * @return bool */ - protected function _chkcnf($keys) { - foreach($keys as $key) { + protected function checkConfig($keys) + { + foreach ($keys as $key) { $params = explode(':', $key); $key = array_shift($params); $sql = trim($this->getConf($key)); // check if sql is set - if(!$sql) return false; + if (!$sql) return false; // check if needed params are there - foreach($params as $param) { - if(strpos($sql, ":$param") === false) return false; + foreach ($params as $param) { + if (strpos($sql, ":$param") === false) return false; } } @@ -786,20 +804,21 @@ class auth_plugin_authpdo extends DokuWiki_Auth_Plugin { * @param bool $htmlescape Should the result be escaped for output in HTML? * @return string */ - protected function _debugSQL($sql, $params, $htmlescape = true) { - foreach($params as $key => $val) { - if(is_int($val)) { + protected function debugSQL($sql, $params, $htmlescape = true) + { + foreach ($params as $key => $val) { + if (is_int($val)) { $val = $this->pdo->quote($val, PDO::PARAM_INT); - } elseif(is_bool($val)) { + } elseif (is_bool($val)) { $val = $this->pdo->quote($val, PDO::PARAM_BOOL); - } elseif(is_null($val)) { + } elseif (is_null($val)) { $val = 'NULL'; } else { $val = $this->pdo->quote($val); } $sql = str_replace($key, $val, $sql); } - if($htmlescape) $sql = hsc($sql); + if ($htmlescape) $sql = hsc($sql); return $sql; } } diff --git a/lib/plugins/authpdo/conf/metadata.php b/lib/plugins/authpdo/conf/metadata.php index 7c2ee8cdc..34e60a40e 100644 --- a/lib/plugins/authpdo/conf/metadata.php +++ b/lib/plugins/authpdo/conf/metadata.php @@ -23,5 +23,3 @@ $meta['update-user-pass'] = array('', '_caution' => 'danger'); $meta['insert-group'] = array('', '_caution' => 'danger'); $meta['join-group'] = array('', '_caution' => 'danger'); $meta['leave-group'] = array('', '_caution' => 'danger'); - - diff --git a/lib/plugins/authplain/_test/escaping.test.php b/lib/plugins/authplain/_test/escaping.test.php index a38940e1a..be4d06b4e 100644 --- a/lib/plugins/authplain/_test/escaping.test.php +++ b/lib/plugins/authplain/_test/escaping.test.php @@ -114,14 +114,14 @@ class auth_plugin_authplainharness extends auth_plugin_authplain { * @param boolean $bool */ public function setPregsplit_safe($bool) { - $this->_pregsplit_safe = $bool; + $this->pregsplit_safe = $bool; } /** * @return bool|mixed */ public function getPregsplit_safe(){ - return $this->_pregsplit_safe; + return $this->pregsplit_safe; } /** @@ -129,6 +129,6 @@ class auth_plugin_authplainharness extends auth_plugin_authplain { * @return array */ public function splitUserData($line){ - return $this->_splitUserData($line); + return parent::splitUserData($line); } } diff --git a/lib/plugins/authplain/auth.php b/lib/plugins/authplain/auth.php index ac1c5d5da..421af8847 100644 --- a/lib/plugins/authplain/auth.php +++ b/lib/plugins/authplain/auth.php @@ -1,6 +1,4 @@ <?php -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); /** * Plaintext authentication backend @@ -10,15 +8,16 @@ if(!defined('DOKU_INC')) die(); * @author Chris Smith <chris@jalakai.co.uk> * @author Jan Schumann <js@schumann-it.com> */ -class auth_plugin_authplain extends DokuWiki_Auth_Plugin { +class auth_plugin_authplain extends DokuWiki_Auth_Plugin +{ /** @var array user cache */ protected $users = null; /** @var array filter pattern */ - protected $_pattern = array(); + protected $pattern = array(); /** @var bool safe version of preg_split */ - protected $_pregsplit_safe = false; + protected $pregsplit_safe = false; /** * Constructor @@ -28,14 +27,15 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * * @author Christopher Smith <chris@jalakai.co.uk> */ - public function __construct() { + public function __construct() + { parent::__construct(); global $config_cascade; - if(!@is_readable($config_cascade['plainauth.users']['default'])) { + if (!@is_readable($config_cascade['plainauth.users']['default'])) { $this->success = false; } else { - if(@is_writable($config_cascade['plainauth.users']['default'])) { + if (@is_writable($config_cascade['plainauth.users']['default'])) { $this->cando['addUser'] = true; $this->cando['delUser'] = true; $this->cando['modLogin'] = true; @@ -49,7 +49,7 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { $this->cando['getGroups'] = true; } - $this->_pregsplit_safe = version_compare(PCRE_VERSION,'6.7','>='); + $this->pregsplit_safe = version_compare(PCRE_VERSION, '6.7', '>='); } /** @@ -63,9 +63,10 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * @param string $pass * @return bool */ - public function checkPass($user, $pass) { + public function checkPass($user, $pass) + { $userinfo = $this->getUserData($user); - if($userinfo === false) return false; + if ($userinfo === false) return false; return auth_verifyPassword($pass, $this->users[$user]['pass']); } @@ -85,8 +86,9 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * @param bool $requireGroups (optional) ignored by this plugin, grps info always supplied * @return array|false */ - public function getUserData($user, $requireGroups=true) { - if($this->users === null) $this->_loadUserData(); + public function getUserData($user, $requireGroups = true) + { + if ($this->users === null) $this->loadUserData(); return isset($this->users[$user]) ? $this->users[$user] : false; } @@ -102,7 +104,8 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * @param array $grps list of groups the user is in * @return string */ - protected function _createUserLine($user, $pass, $name, $mail, $grps) { + protected function createUserLine($user, $pass, $name, $mail, $grps) + { $groups = join(',', $grps); $userline = array($user, $pass, $name, $mail, $groups); $userline = str_replace('\\', '\\\\', $userline); // escape \ as \\ @@ -130,12 +133,13 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * @param array $grps * @return bool|null|string */ - public function createUser($user, $pwd, $name, $mail, $grps = null) { + public function createUser($user, $pwd, $name, $mail, $grps = null) + { global $conf; global $config_cascade; // user mustn't already exist - if($this->getUserData($user) !== false) { + if ($this->getUserData($user) !== false) { msg($this->getLang('userexists'), -1); return false; } @@ -143,12 +147,12 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { $pass = auth_cryptPassword($pwd); // set default group if no groups specified - if(!is_array($grps)) $grps = array($conf['defaultgroup']); + if (!is_array($grps)) $grps = array($conf['defaultgroup']); // prepare user line - $userline = $this->_createUserLine($user, $pass, $name, $mail, $grps); + $userline = $this->createUserLine($user, $pass, $name, $mail, $grps); - if(!io_saveFile($config_cascade['plainauth.users']['default'], $userline, true)) { + if (!io_saveFile($config_cascade['plainauth.users']['default'], $userline, true)) { msg($this->getLang('writefail'), -1); return null; } @@ -165,38 +169,45 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * @param array $changes array of field/value pairs to be changed (password will be clear text) * @return bool */ - public function modifyUser($user, $changes) { + public function modifyUser($user, $changes) + { global $ACT; global $config_cascade; // sanity checks, user must already exist and there must be something to change - if(($userinfo = $this->getUserData($user)) === false) { + if (($userinfo = $this->getUserData($user)) === false) { msg($this->getLang('usernotexists'), -1); return false; } // don't modify protected users - if(!empty($userinfo['protected'])) { + if (!empty($userinfo['protected'])) { msg(sprintf($this->getLang('protected'), hsc($user)), -1); return false; } - if(!is_array($changes) || !count($changes)) return true; + if (!is_array($changes) || !count($changes)) return true; // update userinfo with new data, remembering to encrypt any password $newuser = $user; - foreach($changes as $field => $value) { - if($field == 'user') { + foreach ($changes as $field => $value) { + if ($field == 'user') { $newuser = $value; continue; } - if($field == 'pass') $value = auth_cryptPassword($value); + if ($field == 'pass') $value = auth_cryptPassword($value); $userinfo[$field] = $value; } - $userline = $this->_createUserLine($newuser, $userinfo['pass'], $userinfo['name'], $userinfo['mail'], $userinfo['grps']); + $userline = $this->createUserLine( + $newuser, + $userinfo['pass'], + $userinfo['name'], + $userinfo['mail'], + $userinfo['grps'] + ); - if(!io_replaceInFile($config_cascade['plainauth.users']['default'], '/^'.$user.':/', $userline, true)) { + if (!io_replaceInFile($config_cascade['plainauth.users']['default'], '/^'.$user.':/', $userline, true)) { msg('There was an error modifying your user data. You may need to register again.', -1); // FIXME, io functions should be fail-safe so existing data isn't lost $ACT = 'register'; @@ -214,24 +225,25 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * @param array $users array of users to be deleted * @return int the number of users deleted */ - public function deleteUsers($users) { + public function deleteUsers($users) + { global $config_cascade; - if(!is_array($users) || empty($users)) return 0; + if (!is_array($users) || empty($users)) return 0; - if($this->users === null) $this->_loadUserData(); + if ($this->users === null) $this->loadUserData(); $deleted = array(); - foreach($users as $user) { + foreach ($users as $user) { // don't delete protected users - if(!empty($this->users[$user]['protected'])) { + if (!empty($this->users[$user]['protected'])) { msg(sprintf($this->getLang('protected'), hsc($user)), -1); continue; } - if(isset($this->users[$user])) $deleted[] = preg_quote($user, '/'); + if (isset($this->users[$user])) $deleted[] = preg_quote($user, '/'); } - if(empty($deleted)) return 0; + if (empty($deleted)) return 0; $pattern = '/^('.join('|', $deleted).'):/'; if (!io_deleteFromFile($config_cascade['plainauth.users']['default'], $pattern, true)) { @@ -241,7 +253,7 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { // reload the user list and count the difference $count = count($this->users); - $this->_loadUserData(); + $this->loadUserData(); $count -= count($this->users); return $count; } @@ -254,17 +266,18 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * @param array $filter * @return int */ - public function getUserCount($filter = array()) { + public function getUserCount($filter = array()) + { - if($this->users === null) $this->_loadUserData(); + if ($this->users === null) $this->loadUserData(); - if(!count($filter)) return count($this->users); + if (!count($filter)) return count($this->users); $count = 0; - $this->_constructPattern($filter); + $this->constructPattern($filter); - foreach($this->users as $user => $info) { - $count += $this->_filter($user, $info); + foreach ($this->users as $user => $info) { + $count += $this->filter($user, $info); } return $count; @@ -280,23 +293,24 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * @param array $filter array of field/pattern pairs * @return array userinfo (refer getUserData for internal userinfo details) */ - public function retrieveUsers($start = 0, $limit = 0, $filter = array()) { + public function retrieveUsers($start = 0, $limit = 0, $filter = array()) + { - if($this->users === null) $this->_loadUserData(); + if ($this->users === null) $this->loadUserData(); ksort($this->users); $i = 0; $count = 0; $out = array(); - $this->_constructPattern($filter); + $this->constructPattern($filter); - foreach($this->users as $user => $info) { - if($this->_filter($user, $info)) { - if($i >= $start) { + foreach ($this->users as $user => $info) { + if ($this->filter($user, $info)) { + if ($i >= $start) { $out[$user] = $info; $count++; - if(($limit > 0) && ($count >= $limit)) break; + if (($limit > 0) && ($count >= $limit)) break; } $i++; } @@ -317,7 +331,7 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { { $groups = []; - if ($this->users === null) $this->_loadUserData(); + if ($this->users === null) $this->loadUserData(); foreach($this->users as $user => $info) { $groups = array_merge($groups, array_diff($info['grps'], $groups)); } @@ -334,7 +348,8 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * @param string $user * @return string */ - public function cleanUser($user) { + public function cleanUser($user) + { global $conf; return cleanID(str_replace(':', $conf['sepchar'], $user)); } @@ -345,7 +360,8 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * @param string $group * @return string */ - public function cleanGroup($group) { + public function cleanGroup($group) + { global $conf; return cleanID(str_replace(':', $conf['sepchar'], $group)); } @@ -357,15 +373,16 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * * @author Andreas Gohr <andi@splitbrain.org> */ - protected function _loadUserData() { + protected function loadUserData() + { global $config_cascade; - $this->users = $this->_readUserFile($config_cascade['plainauth.users']['default']); + $this->users = $this->readUserFile($config_cascade['plainauth.users']['default']); // support protected users - if(!empty($config_cascade['plainauth.users']['protected'])) { - $protected = $this->_readUserFile($config_cascade['plainauth.users']['protected']); - foreach(array_keys($protected) as $key) { + if (!empty($config_cascade['plainauth.users']['protected'])) { + $protected = $this->readUserFile($config_cascade['plainauth.users']['protected']); + foreach (array_keys($protected) as $key) { $protected[$key]['protected'] = true; } $this->users = array_merge($this->users, $protected); @@ -380,17 +397,18 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * @param string $file the file to load data from * @return array */ - protected function _readUserFile($file) { + protected function readUserFile($file) + { $users = array(); - if(!file_exists($file)) return $users; + if (!file_exists($file)) return $users; $lines = file($file); - foreach($lines as $line) { + foreach ($lines as $line) { $line = preg_replace('/#.*$/', '', $line); //ignore comments $line = trim($line); - if(empty($line)) continue; + if (empty($line)) continue; - $row = $this->_splitUserData($line); + $row = $this->splitUserData($line); $row = str_replace('\\:', ':', $row); $row = str_replace('\\\\', '\\', $row); @@ -404,22 +422,29 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { return $users; } - protected function _splitUserData($line){ + /** + * Get the user line split into it's parts + * + * @param string $line + * @return string[] + */ + protected function splitUserData($line) + { // due to a bug in PCRE 6.6, preg_split will fail with the regex we use here // refer github issues 877 & 885 - if ($this->_pregsplit_safe){ + if ($this->pregsplit_safe) { return preg_split('/(?<![^\\\\]\\\\)\:/', $line, 5); // allow for : escaped as \: } $row = array(); $piece = ''; $len = strlen($line); - for($i=0; $i<$len; $i++){ - if ($line[$i]=='\\'){ + for ($i=0; $i<$len; $i++) { + if ($line[$i]=='\\') { $piece .= $line[$i]; $i++; if ($i>=$len) break; - } else if ($line[$i]==':'){ + } elseif ($line[$i]==':') { $row[] = $piece; $piece = ''; continue; @@ -440,14 +465,15 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * @param array $info User's userinfo array * @return bool */ - protected function _filter($user, $info) { - foreach($this->_pattern as $item => $pattern) { - if($item == 'user') { - if(!preg_match($pattern, $user)) return false; - } else if($item == 'grps') { - if(!count(preg_grep($pattern, $info['grps']))) return false; + protected function filter($user, $info) + { + foreach ($this->pattern as $item => $pattern) { + if ($item == 'user') { + if (!preg_match($pattern, $user)) return false; + } elseif ($item == 'grps') { + if (!count(preg_grep($pattern, $info['grps']))) return false; } else { - if(!preg_match($pattern, $info[$item])) return false; + if (!preg_match($pattern, $info[$item])) return false; } } return true; @@ -458,10 +484,11 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * * @param array $filter */ - protected function _constructPattern($filter) { - $this->_pattern = array(); - foreach($filter as $item => $pattern) { - $this->_pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters + protected function constructPattern($filter) + { + $this->pattern = array(); + foreach ($filter as $item => $pattern) { + $this->pattern[$item] = '/'.str_replace('/', '\/', $pattern).'/i'; // allow regex characters } } } diff --git a/lib/plugins/cli.php b/lib/plugins/cli.php index 721f54779..a3cbec722 100644 --- a/lib/plugins/cli.php +++ b/lib/plugins/cli.php @@ -1,11 +1,2 @@ <?php - -/** - * Base class for CLI plugins - * - * Provides DokuWiki plugin functionality on top of phpcli - */ -abstract class DokuWiki_CLI_Plugin extends \splitbrain\phpcli\CLI implements DokuWiki_PluginInterface { - use DokuWiki_PluginTrait; - -} +dbg_deprecated('Autoloading. Do not require() files yourself.'); diff --git a/lib/plugins/config/_test/configuration.test.php b/lib/plugins/config/_test/ConfigParserTest.php index 7455461a4..b7e33a539 100644 --- a/lib/plugins/config/_test/configuration.test.php +++ b/lib/plugins/config/_test/ConfigParserTest.php @@ -1,31 +1,20 @@ <?php + +namespace dokuwiki\plugin\config\test; + +use dokuwiki\plugin\config\core\ConfigParser; + /** * @group plugin_config * @group admin_plugins * @group plugins * @group bundled_plugins */ - -class plugin_config_configuration_test extends DokuWikiTest { - - private $config = ''; - private $meta = ''; - - /** - * Load config files - */ - function __construct() { - parent::__construct(); - - $this->config = dirname(__FILE__).'/data/config.php'; - $this->meta = dirname(__FILE__).'/data/metadata.php'; - require_once(dirname(__FILE__).'/../settings/config.class.php'); - } +class ConfigParserTest extends \DokuWikiTest { function test_readconfig() { - $confmgr = new configuration($this->meta); - - $conf = $confmgr->_read_config($this->config); + $parser = new ConfigParser(); + $conf = $parser->parse(__DIR__ . '/data/config.php'); // var_dump($conf); @@ -42,9 +31,8 @@ class plugin_config_configuration_test extends DokuWikiTest { } function test_readconfig_onoff() { - $confmgr = new configuration($this->meta); - - $conf = $confmgr->_read_config($this->config); + $parser = new ConfigParser(); + $conf = $parser->parse(__DIR__ . '/data/config.php'); // var_dump($conf); diff --git a/lib/plugins/config/_test/LoaderTest.php b/lib/plugins/config/_test/LoaderTest.php new file mode 100644 index 000000000..0c315842d --- /dev/null +++ b/lib/plugins/config/_test/LoaderTest.php @@ -0,0 +1,79 @@ +<?php + +namespace dokuwiki\plugin\config\test; + +use dokuwiki\plugin\config\core\ConfigParser; +use dokuwiki\plugin\config\core\Loader; + +/** + * @group plugin_config + * @group admin_plugins + * @group plugins + * @group bundled_plugins + */ +class LoaderTest extends \DokuWikiTest { + + protected $pluginsEnabled = ['testing']; + + /** + * Ensure loading the config meta data works + */ + public function testMetaData() { + $loader = new Loader(new ConfigParser()); + + $meta = $loader->loadMeta(); + $this->assertTrue(is_array($meta)); + + // there should be some defaults + $this->assertArrayHasKey('savedir', $meta); + $this->assertEquals(['savedir', '_caution' => 'danger'], $meta['savedir']); + $this->assertArrayHasKey('proxy____port', $meta); + $this->assertEquals(['numericopt'], $meta['proxy____port']); + + // there should be plugin info + $this->assertArrayHasKey('plugin____testing____plugin_settings_name', $meta); + $this->assertEquals(['fieldset'], $meta['plugin____testing____plugin_settings_name']); + $this->assertArrayHasKey('plugin____testing____schnibble', $meta); + $this->assertEquals(['onoff'], $meta['plugin____testing____schnibble']); + } + + /** + * Ensure loading the defaults work + */ + public function testDefaults() { + $loader = new Loader(new ConfigParser()); + + $conf = $loader->loadDefaults(); + $this->assertTrue(is_array($conf)); + + // basic defaults + $this->assertArrayHasKey('title', $conf); + $this->assertEquals('DokuWiki', $conf['title']); + + // plugin defaults + $this->assertArrayHasKey('plugin____testing____schnibble', $conf); + $this->assertEquals(0, $conf['plugin____testing____schnibble']); + } + + /** + * Ensure language loading works + */ + public function testLangs() { + $loader = new Loader(new ConfigParser()); + + $lang = $loader->loadLangs(); + $this->assertTrue(is_array($lang)); + + // basics are not included in the returned array! + $this->assertArrayNotHasKey('title', $lang); + + // plugin strings + $this->assertArrayHasKey('plugin____testing____plugin_settings_name', $lang); + $this->assertEquals('Testing', $lang['plugin____testing____plugin_settings_name']); + $this->assertArrayHasKey('plugin____testing____schnibble', $lang); + $this->assertEquals( + 'Turns on the schnibble before the frobble is used', + $lang['plugin____testing____schnibble'] + ); + } +} diff --git a/lib/plugins/config/_test/Setting/AbstractSettingTest.php b/lib/plugins/config/_test/Setting/AbstractSettingTest.php new file mode 100644 index 000000000..d18f0ec17 --- /dev/null +++ b/lib/plugins/config/_test/Setting/AbstractSettingTest.php @@ -0,0 +1,99 @@ +<?php + +namespace dokuwiki\plugin\config\test\Setting; + +use dokuwiki\plugin\config\core\Setting\Setting; + +abstract class AbstractSettingTest extends \DokuWikiTest { + + /** @var string the class to test */ + protected $class; + + /** + * Sets up the proper class to test based on the test's class name + * @throws \Exception + */ + public function setUp() { + parent::setUp(); + $class = get_class($this); + $class = substr($class, strrpos($class, '\\') + 1, -4); + $class = 'dokuwiki\\plugin\\config\\core\\Setting\\' . $class; + $this->class = $class; + } + + public function testInitialBasics() { + /** @var Setting $setting */ + $setting = new $this->class('test'); + $this->assertEquals('test', $setting->getKey()); + $this->assertSame(false, $setting->isProtected()); + $this->assertSame(true, $setting->isDefault()); + $this->assertSame(false, $setting->hasError()); + $this->assertSame(false, $setting->shouldBeSaved()); + } + + public function testShouldHaveDefault() { + /** @var Setting $setting */ + $setting = new $this->class('test'); + $this->assertSame(true, $setting->shouldHaveDefault()); + } + + public function testPrettyKey() { + /** @var Setting $setting */ + $setting = new $this->class('test'); + $this->assertEquals('test', $setting->getPrettyKey(false)); + + $setting = new $this->class('test____foo'); + $this->assertEquals('test»foo', $setting->getPrettyKey(false)); + + $setting = new $this->class('test'); + $this->assertEquals( + '<a href="http://www.dokuwiki.org/config:test">test</a>', + $setting->getPrettyKey(true) + ); + + $setting = new $this->class('test____foo'); + $this->assertEquals('test»foo', $setting->getPrettyKey(true)); + + $setting = new $this->class('start'); + $this->assertEquals( + '<a href="http://www.dokuwiki.org/config:startpage">start</a>', + $setting->getPrettyKey(true) + ); + } + + public function testType() { + /** @var Setting $setting */ + $setting = new $this->class('test'); + $this->assertEquals('dokuwiki', $setting->getType()); + + $setting = new $this->class('test_foo'); + $this->assertEquals('dokuwiki', $setting->getType()); + + $setting = new $this->class('plugin____test'); + $this->assertEquals('plugin', $setting->getType()); + + $setting = new $this->class('tpl____test'); + $this->assertEquals('template', $setting->getType()); + } + + public function testCaution() { + /** @var Setting $setting */ + $setting = new $this->class('test'); + $this->assertEquals(false, $setting->caution()); + + $setting = new $this->class('test', ['_caution' => 'warning']); + $this->assertEquals('warning', $setting->caution()); + + $setting = new $this->class('test', ['_caution' => 'danger']); + $this->assertEquals('danger', $setting->caution()); + + $setting = new $this->class('test', ['_caution' => 'security']); + $this->assertEquals('security', $setting->caution()); + + $setting = new $this->class('test', ['_caution' => 'flargh']); + $this->expectException(\RuntimeException::class); + $setting->caution(); + } + + +} diff --git a/lib/plugins/config/_test/Setting/SettingArrayTest.php b/lib/plugins/config/_test/Setting/SettingArrayTest.php new file mode 100644 index 000000000..09dcf1421 --- /dev/null +++ b/lib/plugins/config/_test/Setting/SettingArrayTest.php @@ -0,0 +1,20 @@ +<?php + +namespace dokuwiki\plugin\config\test\Setting; + +/** + * @group plugin_config + * @group admin_plugins + * @group plugins + * @group bundled_plugins + */ +class SettingArrayTest extends SettingTest { + + /** @inheritdoc */ + public function dataOut() { + return [ + [ ['foo','bar'], "\$conf['test'] = array('foo', 'bar');\n"] + ]; + } + +} diff --git a/lib/plugins/config/_test/Setting/SettingNumericTest.php b/lib/plugins/config/_test/Setting/SettingNumericTest.php new file mode 100644 index 000000000..6248a06b7 --- /dev/null +++ b/lib/plugins/config/_test/Setting/SettingNumericTest.php @@ -0,0 +1,24 @@ +<?php + +namespace dokuwiki\plugin\config\test\Setting; + +/** + * @group plugin_config + * @group admin_plugins + * @group plugins + * @group bundled_plugins + */ +class SettingNumericTest extends SettingTest { + + /** @inheritdoc */ + public function dataOut() { + return [ + [42, "\$conf['test'] = 42;\n"], + [0, "\$conf['test'] = 0;\n"], + [-42, "\$conf['test'] = -42;\n"], + [-42.13, "\$conf['test'] = -42.13;\n"], + ['12*13', "\$conf['test'] = 12*13;\n"], + ]; + } + +} diff --git a/lib/plugins/config/_test/Setting/SettingNumericoptTest.php b/lib/plugins/config/_test/Setting/SettingNumericoptTest.php new file mode 100644 index 000000000..9d29f31e7 --- /dev/null +++ b/lib/plugins/config/_test/Setting/SettingNumericoptTest.php @@ -0,0 +1,23 @@ +<?php + +namespace dokuwiki\plugin\config\test\Setting; + +/** + * @group plugin_config + * @group admin_plugins + * @group plugins + * @group bundled_plugins + */ +class SettingNumericoptTest extends SettingNumericTest { + + /** @inheritdoc */ + public function dataOut() { + return array_merge( + parent::dataOut(), + [ + ['', "\$conf['test'] = '';\n"], + ] + ); + } + +} diff --git a/lib/plugins/config/_test/Setting/SettingOnoffTest.php b/lib/plugins/config/_test/Setting/SettingOnoffTest.php new file mode 100644 index 000000000..d6561bdf1 --- /dev/null +++ b/lib/plugins/config/_test/Setting/SettingOnoffTest.php @@ -0,0 +1,72 @@ +<?php + +namespace dokuwiki\plugin\config\test\Setting; + +/** + * @group plugin_config + * @group admin_plugins + * @group plugins + * @group bundled_plugins + */ +class SettingOnoffTest extends SettingTest { + + /** @inheritdoc */ + public function dataOut() { + return [ + [1, "\$conf['test'] = 1;\n"], + [0, "\$conf['test'] = 0;\n"], + + ['1', "\$conf['test'] = 1;\n"], + ['0', "\$conf['test'] = 0;\n"], + + ['on', "\$conf['test'] = 1;\n"], + ['off', "\$conf['test'] = 0;\n"], + + ['true', "\$conf['test'] = 1;\n"], + ['false', "\$conf['test'] = 0;\n"], + + ['On', "\$conf['test'] = 1;\n"], + ['Off', "\$conf['test'] = 0;\n"], + + ['True', "\$conf['test'] = 1;\n"], + ['False', "\$conf['test'] = 0;\n"], + + [true, "\$conf['test'] = 1;\n"], + [false, "\$conf['test'] = 0;\n"], + + [3, "\$conf['test'] = 1;\n"], + ['3', "\$conf['test'] = 1;\n"], + + ['', "\$conf['test'] = 0;\n"], + [' ', "\$conf['test'] = 0;\n"], + ]; + } + + /** @inheritdoc */ + public function dataShouldBeSaved() { + return [ + [0, null, false], + [1, null, false], + [0, 0, false], + [1, 1, false], + [0, 1, true], + [1, 0, true], + + ['0', '0', false], + ['1', '1', false], + ['0', '1', true], + ['1', '0', true], + + ['0', 0, false], + ['1', 1, false], + ['0', 1, true], + ['1', 0, true], + + [0, '0', false], + [1, '1', false], + [0, '1', true], + [1, '0', true], + ]; + } + +} diff --git a/lib/plugins/config/_test/Setting/SettingStringTest.php b/lib/plugins/config/_test/Setting/SettingStringTest.php new file mode 100644 index 000000000..3d6a71c9d --- /dev/null +++ b/lib/plugins/config/_test/Setting/SettingStringTest.php @@ -0,0 +1,13 @@ +<?php + +namespace dokuwiki\plugin\config\test\Setting; + +/** + * @group plugin_config + * @group admin_plugins + * @group plugins + * @group bundled_plugins + */ +class SettingStringTest extends SettingTest { + +} diff --git a/lib/plugins/config/_test/Setting/SettingTest.php b/lib/plugins/config/_test/Setting/SettingTest.php new file mode 100644 index 000000000..49e0662e0 --- /dev/null +++ b/lib/plugins/config/_test/Setting/SettingTest.php @@ -0,0 +1,70 @@ +<?php + +namespace dokuwiki\plugin\config\test\Setting; + +use dokuwiki\plugin\config\core\Setting\Setting; + +/** + * @group plugin_config + * @group admin_plugins + * @group plugins + * @group bundled_plugins + */ +class SettingTest extends AbstractSettingTest { + + /** + * Dataprovider for testOut() + * + * @return array + */ + public function dataOut() { + return [ + ['bar', "\$conf['test'] = 'bar';\n"], + ["foo'bar", "\$conf['test'] = 'foo\\'bar';\n"], + ]; + } + + /** + * Check the output + * + * @param mixed $in The value to initialize the setting with + * @param string $out The expected output (for conf[test]) + * @dataProvider dataOut + */ + public function testOut($in, $out) { + /** @var Setting $setting */ + $setting = new $this->class('test'); + $setting->initialize('ignore', $in); + + $this->assertEquals($out, $setting->out('conf')); + } + + /** + * DataProvider for testShouldBeSaved() + * + * @return array + */ + public function dataShouldBeSaved() { + return [ + ['default', null, false], + ['default', 'default', false], + ['default', 'new', true], + ]; + } + + /** + * Check if shouldBeSaved works as expected + * + * @dataProvider dataShouldBeSaved + * @param mixed $default The default value + * @param mixed $local The current local value + * @param bool $expect The expected outcome + */ + public function testShouldBeSaved($default, $local, $expect) { + /** @var Setting $setting */ + $setting = new $this->class('test'); + $setting->initialize($default, $local, null); + $this->assertSame($expect, $setting->shouldBeSaved()); + } + +} diff --git a/lib/plugins/config/_test/WriterTest.php b/lib/plugins/config/_test/WriterTest.php new file mode 100644 index 000000000..62e4a56d5 --- /dev/null +++ b/lib/plugins/config/_test/WriterTest.php @@ -0,0 +1,63 @@ +<?php + +namespace dokuwiki\plugin\config\test; +use dokuwiki\plugin\config\core\Setting\SettingString; +use dokuwiki\plugin\config\core\Writer; + +/** + * @group plugin_config + * @group admin_plugins + * @group plugins + * @group bundled_plugins + */ +class WriterTest extends \DokuWikiTest { + + public function testSave() { + global $config_cascade; + $config = end($config_cascade['main']['local']); + + $set1 = new SettingString('test1'); + $set1->initialize('foo','bar', null); + $set2 = new SettingString('test2'); + $set2->initialize('foo','foo', null); + $settings = [$set1, $set2]; + $writer = new Writer(); + + // before running, no backup should exist + $this->assertFileExists($config); + $this->assertFileNotExists("$config.bak"); + $old = filesize($config); + + /** @noinspection PhpUnhandledExceptionInspection */ + $writer->save($settings); + + // after running, both should exist + $this->assertFileExists($config); + $this->assertFileExists("$config.bak"); + $this->assertEquals($old, filesize("$config.bak"), 'backup should have size of old file'); + + // check contents + $conf = []; + include $config; + $this->assertArrayHasKey('test1', $conf); + $this->assertEquals('bar', $conf['test1']); + $this->assertArrayNotHasKey('test2', $conf); + + /** @noinspection PhpUnhandledExceptionInspection */ + $writer->save($settings); + $this->assertEquals(filesize($config), filesize("$config.bak")); + } + + public function testTouch() { + global $config_cascade; + $config = end($config_cascade['main']['local']); + $writer = new Writer(); + + $old = filemtime($config); + $this->waitForTick(true); + /** @noinspection PhpUnhandledExceptionInspection */ + $writer->touch(); + clearstatcache($config); + $this->assertGreaterThan($old, filemtime($config)); + } +} diff --git a/lib/plugins/config/admin.php b/lib/plugins/config/admin.php index 2ced6efbd..219612cf1 100644 --- a/lib/plugins/config/admin.php +++ b/lib/plugins/config/admin.php @@ -6,17 +6,11 @@ * @author Christopher Smith <chris@jalakai.co.uk> * @author Ben Coburn <btcoburn@silicodon.net> */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); -define('CM_KEYMARKER','____'); // used for settings with multiple dimensions of array indices - -define('PLUGIN_SELF',dirname(__FILE__).'/'); -define('PLUGIN_METADATA',PLUGIN_SELF.'settings/config.metadata.php'); -if(!defined('DOKU_PLUGIN_IMAGES')) define('DOKU_PLUGIN_IMAGES',DOKU_BASE.'lib/plugins/config/images/'); - -require_once(PLUGIN_SELF.'settings/config.class.php'); // main configuration class and generic settings classes -require_once(PLUGIN_SELF.'settings/extra.class.php'); // settings classes specific to these settings +use dokuwiki\plugin\config\core\Configuration; +use dokuwiki\plugin\config\core\Setting\Setting; +use dokuwiki\plugin\config\core\Setting\SettingFieldset; +use dokuwiki\plugin\config\core\Setting\SettingHidden; /** * All DokuWiki plugins to extend the admin function @@ -24,18 +18,17 @@ require_once(PLUGIN_SELF.'settings/extra.class.php'); // settings classes spec */ class admin_plugin_config extends DokuWiki_Admin_Plugin { - protected $_file = PLUGIN_METADATA; - protected $_config = null; - protected $_input = null; - protected $_changed = false; // set to true if configuration has altered - protected $_error = false; - protected $_session_started = false; - protected $_localised_prompts = false; + const IMGDIR = DOKU_BASE . 'lib/plugins/config/images/'; + + /** @var Configuration */ + protected $configuration; + + /** @var bool were there any errors in the submitted data? */ + protected $hasErrors = false; + + /** @var bool have the settings translations been loaded? */ + protected $promptsLocalized = false; - /** - * @return int - */ - public function getMenuSort() { return 100; } /** * handle user request @@ -43,44 +36,33 @@ class admin_plugin_config extends DokuWiki_Admin_Plugin { public function handle() { global $ID, $INPUT; - if(!$this->_restore_session() || $INPUT->int('save') != 1 || !checkSecurityToken()) { - $this->_close_session(); - return; - } + // always initialize the configuration + $this->configuration = new Configuration(); - if(is_null($this->_config)) { - $this->_config = new configuration($this->_file); - } - - // don't go any further if the configuration is locked - if($this->_config->locked) { - $this->_close_session(); + if(!$INPUT->bool('save') || !checkSecurityToken()) { return; } - $this->_input = $INPUT->arr('config'); - - foreach ($this->_config->setting as $key => $value){ - $input = isset($this->_input[$key]) ? $this->_input[$key] : null; - if ($this->_config->setting[$key]->update($input)) { - $this->_changed = true; + // don't go any further if the configuration is locked + if($this->configuration->isLocked()) return; + + // update settings and redirect of successful + $ok = $this->configuration->updateSettings($INPUT->arr('config')); + if($ok) { // no errors + try { + if($this->configuration->hasChanged()) { + $this->configuration->save(); + } else { + $this->configuration->touch(); + } + msg($this->getLang('updated'), 1); + } catch(Exception $e) { + msg($this->getLang('error'), -1); } - if ($this->_config->setting[$key]->error()) $this->_error = true; + send_redirect(wl($ID, array('do' => 'admin', 'page' => 'config'), true, '&')); + } else { + $this->hasErrors = true; } - - if ($this->_changed && !$this->_error) { - $this->_config->save_settings($this->getPluginName()); - - // save state & force a page reload to get the new settings to take effect - $_SESSION['PLUGIN_CONFIG'] = array('state' => 'updated', 'time' => time()); - $this->_close_session(); - send_redirect(wl($ID, array('do'=>'admin','page'=>'config'), true, '&')); - exit(); - } elseif(!$this->_error) { - $this->_config->touch_settings(); // just touch to refresh cache - } - - $this->_close_session(); } /** @@ -91,234 +73,137 @@ class admin_plugin_config extends DokuWiki_Admin_Plugin { global $lang; global $ID; - if (is_null($this->_config)) { $this->_config = new configuration($this->_file); } $this->setupLocale(true); - print $this->locale_xhtml('intro'); + echo $this->locale_xhtml('intro'); - ptln('<div id="config__manager">'); + echo '<div id="config__manager">'; - if ($this->_config->locked) - ptln('<div class="info">'.$this->getLang('locked').'</div>'); - elseif ($this->_error) - ptln('<div class="error">'.$this->getLang('error').'</div>'); - elseif ($this->_changed) - ptln('<div class="success">'.$this->getLang('updated').'</div>'); + if($this->configuration->isLocked()) { + echo '<div class="info">' . $this->getLang('locked') . '</div>'; + } // POST to script() instead of wl($ID) so config manager still works if // rewrite config is broken. Add $ID as hidden field to remember // current ID in most cases. - ptln('<form id="dw__configform" action="'.script().'" method="post">'); - ptln('<div class="no"><input type="hidden" name="id" value="'.$ID.'" /></div>'); + echo '<form id="dw__configform" action="' . script() . '" method="post">'; + echo '<div class="no"><input type="hidden" name="id" value="' . $ID . '" /></div>'; formSecurityToken(); - $this->_print_h1('dokuwiki_settings', $this->getLang('_header_dokuwiki')); + $this->printH1('dokuwiki_settings', $this->getLang('_header_dokuwiki')); - /** @var setting[] $undefined_settings */ - $undefined_settings = array(); $in_fieldset = false; $first_plugin_fieldset = true; $first_template_fieldset = true; - foreach($this->_config->setting as $setting) { - if (is_a($setting, 'setting_hidden')) { - // skip hidden (and undefined) settings - if ($allow_debug && is_a($setting, 'setting_undefined')) { - $undefined_settings[] = $setting; - } else { - continue; - } - } else if (is_a($setting, 'setting_fieldset')) { + foreach($this->configuration->getSettings() as $setting) { + if(is_a($setting, SettingHidden::class)) { + continue; + } else if(is_a($setting, settingFieldset::class)) { // config setting group - if ($in_fieldset) { - ptln(' </table>'); - ptln(' </div>'); - ptln(' </fieldset>'); + if($in_fieldset) { + echo '</table>'; + echo '</div>'; + echo '</fieldset>'; } else { $in_fieldset = true; } - if ($first_plugin_fieldset && substr($setting->_key, 0, 10)=='plugin'.CM_KEYMARKER) { - $this->_print_h1('plugin_settings', $this->getLang('_header_plugin')); + if($first_plugin_fieldset && $setting->getType() == 'plugin') { + $this->printH1('plugin_settings', $this->getLang('_header_plugin')); $first_plugin_fieldset = false; - } else if ($first_template_fieldset && substr($setting->_key, 0, 7)=='tpl'.CM_KEYMARKER) { - $this->_print_h1('template_settings', $this->getLang('_header_template')); + } else if($first_template_fieldset && $setting->getType() == 'template') { + $this->printH1('template_settings', $this->getLang('_header_template')); $first_template_fieldset = false; } - ptln(' <fieldset id="'.$setting->_key.'">'); - ptln(' <legend>'.$setting->prompt($this).'</legend>'); - ptln(' <div class="table">'); - ptln(' <table class="inline">'); + echo '<fieldset id="' . $setting->getKey() . '">'; + echo '<legend>' . $setting->prompt($this) . '</legend>'; + echo '<div class="table">'; + echo '<table class="inline">'; } else { // config settings - list($label,$input) = $setting->html($this, $this->_error); - - $class = $setting->is_default() ? ' class="default"' : ($setting->is_protected() ? ' class="protected"' : ''); - $error = $setting->error() ? ' class="value error"' : ' class="value"'; - $icon = $setting->caution() ? '<img src="'.DOKU_PLUGIN_IMAGES.$setting->caution().'.png" alt="'.$setting->caution().'" title="'.$this->getLang($setting->caution()).'" />' : ''; - - ptln(' <tr'.$class.'>'); - ptln(' <td class="label">'); - ptln(' <span class="outkey">'.$setting->_out_key(true, true).'</span>'); - ptln(' '.$icon.$label); - ptln(' </td>'); - ptln(' <td'.$error.'>'.$input.'</td>'); - ptln(' </tr>'); + list($label, $input) = $setting->html($this, $this->hasErrors); + + $class = $setting->isDefault() + ? ' class="default"' + : ($setting->isProtected() ? ' class="protected"' : ''); + $error = $setting->hasError() + ? ' class="value error"' + : ' class="value"'; + $icon = $setting->caution() + ? '<img src="' . self::IMGDIR . $setting->caution() . '.png" ' . + 'alt="' . $setting->caution() . '" title="' . $this->getLang($setting->caution()) . '" />' + : ''; + + echo '<tr' . $class . '>'; + echo '<td class="label">'; + echo '<span class="outkey">' . $setting->getPrettyKey() . '</span>'; + echo $icon . $label; + echo '</td>'; + echo '<td' . $error . '>' . $input . '</td>'; + echo '</tr>'; } } - ptln(' </table>'); - ptln(' </div>'); - if ($in_fieldset) { - ptln(' </fieldset>'); + echo '</table>'; + echo '</div>'; + if($in_fieldset) { + echo '</fieldset>'; } // show undefined settings list - if ($allow_debug && !empty($undefined_settings)) { + $undefined_settings = $this->configuration->getUndefined(); + if($allow_debug && !empty($undefined_settings)) { /** * Callback for sorting settings * - * @param setting $a - * @param setting $b + * @param Setting $a + * @param Setting $b * @return int if $a is lower/equal/higher than $b */ - function _setting_natural_comparison($a, $b) { - return strnatcmp($a->_key, $b->_key); + function settingNaturalComparison($a, $b) { + return strnatcmp($a->getKey(), $b->getKey()); } - usort($undefined_settings, '_setting_natural_comparison'); - $this->_print_h1('undefined_settings', $this->getLang('_header_undefined')); - ptln('<fieldset>'); - ptln('<div class="table">'); - ptln('<table class="inline">'); - $undefined_setting_match = array(); + usort($undefined_settings, 'settingNaturalComparison'); + $this->printH1('undefined_settings', $this->getLang('_header_undefined')); + echo '<fieldset>'; + echo '<div class="table">'; + echo '<table class="inline">'; foreach($undefined_settings as $setting) { - if (preg_match('/^(?:plugin|tpl)'.CM_KEYMARKER.'.*?'.CM_KEYMARKER.'(.*)$/', $setting->_key, $undefined_setting_match)) { - $undefined_setting_key = $undefined_setting_match[1]; - } else { - $undefined_setting_key = $setting->_key; - } - ptln(' <tr>'); - ptln(' <td class="label"><span title="$meta[\''.$undefined_setting_key.'\']">$'.$this->_config->_name.'[\''.$setting->_out_key().'\']</span></td>'); - ptln(' <td>'.$this->getLang('_msg_'.get_class($setting)).'</td>'); - ptln(' </tr>'); + list($label, $input) = $setting->html($this); + echo '<tr>'; + echo '<td class="label">' . $label . '</td>'; + echo '<td>' . $input . '</td>'; + echo '</tr>'; } - ptln('</table>'); - ptln('</div>'); - ptln('</fieldset>'); + echo '</table>'; + echo '</div>'; + echo '</fieldset>'; } // finish up form - ptln('<p>'); - ptln(' <input type="hidden" name="do" value="admin" />'); - ptln(' <input type="hidden" name="page" value="config" />'); - - if (!$this->_config->locked) { - ptln(' <input type="hidden" name="save" value="1" />'); - ptln(' <button type="submit" name="submit" accesskey="s">'.$lang['btn_save'].'</button>'); - ptln(' <button type="reset">'.$lang['btn_reset'].'</button>'); - } - - ptln('</p>'); - - ptln('</form>'); - ptln('</div>'); - } - - /** - * @return boolean true - proceed with handle, false - don't proceed - */ - protected function _restore_session() { - - // dokuwiki closes the session before act_dispatch. $_SESSION variables are all set, - // however they can't be changed without starting the session again - if (!headers_sent()) { - session_start(); - $this->_session_started = true; + echo '<p>'; + echo '<input type="hidden" name="do" value="admin" />'; + echo '<input type="hidden" name="page" value="config" />'; + + if(!$this->configuration->isLocked()) { + echo '<input type="hidden" name="save" value="1" />'; + echo '<button type="submit" name="submit" accesskey="s">' . $lang['btn_save'] . '</button>'; + echo '<button type="reset">' . $lang['btn_reset'] . '</button>'; } - if (!isset($_SESSION['PLUGIN_CONFIG'])) return true; + echo '</p>'; - $session = $_SESSION['PLUGIN_CONFIG']; - unset($_SESSION['PLUGIN_CONFIG']); - - // still valid? - if (time() - $session['time'] > 120) return true; - - switch ($session['state']) { - case 'updated' : - $this->_changed = true; - return false; - } - - return true; - } - - protected function _close_session() { - if ($this->_session_started) session_write_close(); + echo '</form>'; + echo '</div>'; } /** * @param bool $prompts */ - public function setupLocale($prompts=false) { - + public function setupLocale($prompts = false) { parent::setupLocale(); - if (!$prompts || $this->_localised_prompts) return; - - $this->_setup_localised_plugin_prompts(); - $this->_localised_prompts = true; - - } - - /** - * @return bool - */ - protected function _setup_localised_plugin_prompts() { - global $conf; - - $langfile = '/lang/'.$conf['lang'].'/settings.php'; - $enlangfile = '/lang/en/settings.php'; - - if ($dh = opendir(DOKU_PLUGIN)) { - while (false !== ($plugin = readdir($dh))) { - if ($plugin == '.' || $plugin == '..' || $plugin == 'tmp' || $plugin == 'config') continue; - if (is_file(DOKU_PLUGIN.$plugin)) continue; - - if (file_exists(DOKU_PLUGIN.$plugin.$enlangfile)){ - $lang = array(); - @include(DOKU_PLUGIN.$plugin.$enlangfile); - if ($conf['lang'] != 'en') @include(DOKU_PLUGIN.$plugin.$langfile); - foreach ($lang as $key => $value){ - $this->lang['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.$key] = $value; - } - } - - // fill in the plugin name if missing (should exist for plugins with settings) - if (!isset($this->lang['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.'plugin_settings_name'])) { - $this->lang['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.'plugin_settings_name'] = - ucwords(str_replace('_', ' ', $plugin)); - } - } - closedir($dh); - } - - // the same for the active template - $tpl = $conf['template']; - - if (file_exists(tpl_incdir().$enlangfile)){ - $lang = array(); - @include(tpl_incdir().$enlangfile); - if ($conf['lang'] != 'en') @include(tpl_incdir().$langfile); - foreach ($lang as $key => $value){ - $this->lang['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.$key] = $value; - } - } - - // fill in the template name if missing (should exist for templates with settings) - if (!isset($this->lang['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.'template_settings_name'])) { - $this->lang['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.'template_settings_name'] = - ucwords(str_replace('_', ' ', $tpl)); - } - - return true; + if(!$prompts || $this->promptsLocalized) return; + $this->lang = array_merge($this->lang, $this->configuration->getLangs()); + $this->promptsLocalized = true; } /** @@ -329,76 +214,69 @@ class admin_plugin_config extends DokuWiki_Admin_Plugin { * @return array */ public function getTOC() { - if (is_null($this->_config)) { $this->_config = new configuration($this->_file); } $this->setupLocale(true); $allow_debug = $GLOBALS['conf']['allowdebug']; // avoid global $conf; here. + $toc = array(); + $check = false; - // gather toc data - $has_undefined = false; - $toc = array('conf'=>array(), 'plugin'=>array(), 'template'=>null); - foreach($this->_config->setting as $setting) { - if (is_a($setting, 'setting_fieldset')) { - if (substr($setting->_key, 0, 10)=='plugin'.CM_KEYMARKER) { - $toc['plugin'][] = $setting; - } else if (substr($setting->_key, 0, 7)=='tpl'.CM_KEYMARKER) { - $toc['template'] = $setting; - } else { - $toc['conf'][] = $setting; - } - } else if (!$has_undefined && is_a($setting, 'setting_undefined')) { - $has_undefined = true; + // gather settings data into three sub arrays + $labels = ['dokuwiki' => [], 'plugin' => [], 'template' => []]; + foreach($this->configuration->getSettings() as $setting) { + if(is_a($setting, SettingFieldset::class)) { + $labels[$setting->getType()][] = $setting; } } - // build toc - $t = array(); - - $check = false; + // top header $title = $this->getLang('_configuration_manager'); - $t[] = html_mktocitem(sectionID($title, $check), $title, 1); - $t[] = html_mktocitem('dokuwiki_settings', $this->getLang('_header_dokuwiki'), 1); - /** @var setting $setting */ - foreach($toc['conf'] as $setting) { - $name = $setting->prompt($this); - $t[] = html_mktocitem($setting->_key, $name, 2); - } - if (!empty($toc['plugin'])) { - $t[] = html_mktocitem('plugin_settings', $this->getLang('_header_plugin'), 1); - } - foreach($toc['plugin'] as $setting) { - $name = $setting->prompt($this); - $t[] = html_mktocitem($setting->_key, $name, 2); - } - if (isset($toc['template'])) { - $t[] = html_mktocitem('template_settings', $this->getLang('_header_template'), 1); - $setting = $toc['template']; - $name = $setting->prompt($this); - $t[] = html_mktocitem($setting->_key, $name, 2); + $toc[] = html_mktocitem(sectionID($title, $check), $title, 1); + + // main entries + foreach(['dokuwiki', 'plugin', 'template'] as $section) { + if(empty($labels[$section])) continue; // no entries, skip + + // create main header + $toc[] = html_mktocitem( + $section . '_settings', + $this->getLang('_header_' . $section), + 1 + ); + + // create sub headers + foreach($labels[$section] as $setting) { + /** @var SettingFieldset $setting */ + $name = $setting->prompt($this); + $toc[] = html_mktocitem($setting->getKey(), $name, 2); + } } - if ($has_undefined && $allow_debug) { - $t[] = html_mktocitem('undefined_settings', $this->getLang('_header_undefined'), 1); + + // undefined settings if allowed + if(count($this->configuration->getUndefined()) && $allow_debug) { + $toc[] = html_mktocitem('undefined_settings', $this->getLang('_header_undefined'), 1); } - return $t; + return $toc; } /** * @param string $id * @param string $text */ - protected function _print_h1($id, $text) { - ptln('<h1 id="'.$id.'">'.$text.'</h1>'); + protected function printH1($id, $text) { + echo '<h1 id="' . $id . '">' . $text . '</h1>'; } /** * Adds a translation to this plugin's language array * + * Used by some settings to set up dynamic translations + * * @param string $key * @param string $value */ public function addLang($key, $value) { - if (!$this->localised) $this->setupLocale(); + if(!$this->localised) $this->setupLocale(); $this->lang[$key] = $value; } } diff --git a/lib/plugins/config/core/ConfigParser.php b/lib/plugins/config/core/ConfigParser.php new file mode 100644 index 000000000..9e79b96f3 --- /dev/null +++ b/lib/plugins/config/core/ConfigParser.php @@ -0,0 +1,90 @@ +<?php + +namespace dokuwiki\plugin\config\core; + +/** + * A naive PHP file parser + * + * This parses our very simple config file in PHP format. We use this instead of simply including + * the file, because we want to keep expressions such as 24*60*60 as is. + * + * @author Chris Smith <chris@jalakai.co.uk> + */ +class ConfigParser { + /** @var string variable to parse from the file */ + protected $varname = 'conf'; + /** @var string the key to mark sub arrays */ + protected $keymarker = Configuration::KEYMARKER; + + /** + * Parse the given PHP file into an array + * + * When the given files does not exist, this returns an empty array + * + * @param string $file + * @return array + */ + public function parse($file) { + if(!file_exists($file)) return array(); + + $config = array(); + $contents = @php_strip_whitespace($file); + $pattern = '/\$' . $this->varname . '\[[\'"]([^=]+)[\'"]\] ?= ?(.*?);(?=[^;]*(?:\$' . $this->varname . '|$))/s'; + $matches = array(); + preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER); + + for($i = 0; $i < count($matches); $i++) { + $value = $matches[$i][2]; + + // merge multi-dimensional array indices using the keymarker + $key = preg_replace('/.\]\[./', $this->keymarker, $matches[$i][1]); + + // handle arrays + if(preg_match('/^array ?\((.*)\)/', $value, $match)) { + $arr = explode(',', $match[1]); + + // remove quotes from quoted strings & unescape escaped data + $len = count($arr); + for($j = 0; $j < $len; $j++) { + $arr[$j] = trim($arr[$j]); + $arr[$j] = $this->readValue($arr[$j]); + } + + $value = $arr; + } else { + $value = $this->readValue($value); + } + + $config[$key] = $value; + } + + return $config; + } + + /** + * Convert php string into value + * + * @param string $value + * @return bool|string + */ + protected function readValue($value) { + $removequotes_pattern = '/^(\'|")(.*)(?<!\\\\)\1$/s'; + $unescape_pairs = array( + '\\\\' => '\\', + '\\\'' => '\'', + '\\"' => '"' + ); + + if($value == 'true') { + $value = true; + } elseif($value == 'false') { + $value = false; + } else { + // remove quotes from quoted strings & unescape escaped data + $value = preg_replace($removequotes_pattern, '$2', $value); + $value = strtr($value, $unescape_pairs); + } + return $value; + } + +} diff --git a/lib/plugins/config/core/Configuration.php b/lib/plugins/config/core/Configuration.php new file mode 100644 index 000000000..c58645c5b --- /dev/null +++ b/lib/plugins/config/core/Configuration.php @@ -0,0 +1,219 @@ +<?php + +namespace dokuwiki\plugin\config\core; + +use dokuwiki\plugin\config\core\Setting\Setting; +use dokuwiki\plugin\config\core\Setting\SettingNoClass; +use dokuwiki\plugin\config\core\Setting\SettingNoDefault; +use dokuwiki\plugin\config\core\Setting\SettingNoKnownClass; +use dokuwiki\plugin\config\core\Setting\SettingUndefined; + +/** + * Holds all the current settings and proxies the Loader and Writer + * + * @author Chris Smith <chris@jalakai.co.uk> + * @author Ben Coburn <btcoburn@silicodon.net> + * @author Andreas Gohr <andi@splitbrain.org> + */ +class Configuration { + + const KEYMARKER = '____'; + + /** @var Setting[] metadata as array of Settings objects */ + protected $settings = array(); + /** @var Setting[] undefined and problematic settings */ + protected $undefined = array(); + + /** @var array all metadata */ + protected $metadata; + /** @var array all default settings */ + protected $default; + /** @var array all local settings */ + protected $local; + /** @var array all protected settings */ + protected $protected; + + /** @var bool have the settings been changed since loading from disk? */ + protected $changed = false; + + /** @var Loader */ + protected $loader; + /** @var Writer */ + protected $writer; + + /** + * ConfigSettings constructor. + */ + public function __construct() { + $this->loader = new Loader(new ConfigParser()); + $this->writer = new Writer(); + + $this->metadata = $this->loader->loadMeta(); + $this->default = $this->loader->loadDefaults(); + $this->local = $this->loader->loadLocal(); + $this->protected = $this->loader->loadProtected(); + + $this->initSettings(); + } + + /** + * Get all settings + * + * @return Setting[] + */ + public function getSettings() { + return $this->settings; + } + + /** + * Get all unknown or problematic settings + * + * @return Setting[] + */ + public function getUndefined() { + return $this->undefined; + } + + /** + * Have the settings been changed since loading from disk? + * + * @return bool + */ + public function hasChanged() { + return $this->changed; + } + + /** + * Check if the config can be written + * + * @return bool + */ + public function isLocked() { + return $this->writer->isLocked(); + } + + /** + * Update the settings using the data provided + * + * @param array $input as posted + * @return bool true if all updates went through, false on errors + */ + public function updateSettings($input) { + $ok = true; + + foreach($this->settings as $key => $obj) { + $value = isset($input[$key]) ? $input[$key] : null; + if($obj->update($value)) { + $this->changed = true; + } + if($obj->hasError()) $ok = false; + } + + return $ok; + } + + /** + * Save the settings + * + * This save the current state as defined in this object, including the + * undefined settings + * + * @throws \Exception + */ + public function save() { + // only save the undefined settings that have not been handled in settings + $undefined = array_diff_key($this->undefined, $this->settings); + $this->writer->save(array_merge($this->settings, $undefined)); + } + + /** + * Touch the settings + * + * @throws \Exception + */ + public function touch() { + $this->writer->touch(); + } + + /** + * Load the extension language strings + * + * @return array + */ + public function getLangs() { + return $this->loader->loadLangs(); + } + + /** + * Initalizes the $settings and $undefined properties + */ + protected function initSettings() { + $keys = array_merge( + array_keys($this->metadata), + array_keys($this->default), + array_keys($this->local), + array_keys($this->protected) + ); + $keys = array_unique($keys); + + foreach($keys as $key) { + $obj = $this->instantiateClass($key); + + if($obj->shouldHaveDefault() && !isset($this->default[$key])) { + $this->undefined[$key] = new SettingNoDefault($key); + } + + $d = isset($this->default[$key]) ? $this->default[$key] : null; + $l = isset($this->local[$key]) ? $this->local[$key] : null; + $p = isset($this->protected[$key]) ? $this->protected[$key] : null; + + $obj->initialize($d, $l, $p); + } + } + + /** + * Instantiates the proper class for the given config key + * + * The class is added to the $settings or $undefined arrays and returned + * + * @param string $key + * @return Setting + */ + protected function instantiateClass($key) { + if(isset($this->metadata[$key])) { + $param = $this->metadata[$key]; + $class = $this->determineClassName(array_shift($param), $key); // first param is class + $obj = new $class($key, $param); + $this->settings[$key] = $obj; + } else { + $obj = new SettingUndefined($key); + $this->undefined[$key] = $obj; + } + return $obj; + } + + /** + * Return the class to load + * + * @param string $class the class name as given in the meta file + * @param string $key the settings key + * @return string + */ + protected function determineClassName($class, $key) { + // try namespaced class first + if(is_string($class)) { + $modern = str_replace('_', '', ucwords($class, '_')); + $modern = '\\dokuwiki\\plugin\\config\\core\\Setting\\Setting' . $modern; + if($modern && class_exists($modern)) return $modern; + // try class as given + if(class_exists($class)) return $class; + // class wasn't found add to errors + $this->undefined[$key] = new SettingNoKnownClass($key); + } else { + // no class given, add to errors + $this->undefined[$key] = new SettingNoClass($key); + } + return '\\dokuwiki\\plugin\\config\\core\\Setting\\Setting'; + } + +} diff --git a/lib/plugins/config/core/Loader.php b/lib/plugins/config/core/Loader.php new file mode 100644 index 000000000..90ad0f50e --- /dev/null +++ b/lib/plugins/config/core/Loader.php @@ -0,0 +1,269 @@ +<?php + +namespace dokuwiki\plugin\config\core; + +use dokuwiki\Extension\Event; + +/** + * Configuration loader + * + * Loads configuration meta data and settings from the various files. Honors the + * configuration cascade and installed plugins. + */ +class Loader { + /** @var ConfigParser */ + protected $parser; + + /** @var string[] list of enabled plugins */ + protected $plugins; + /** @var string current template */ + protected $template; + + /** + * Loader constructor. + * @param ConfigParser $parser + * @triggers PLUGIN_CONFIG_PLUGINLIST + */ + public function __construct(ConfigParser $parser) { + global $conf; + $this->parser = $parser; + $this->plugins = plugin_list(); + $this->template = $conf['template']; + // allow plugins to remove configurable plugins + Event::createAndTrigger('PLUGIN_CONFIG_PLUGINLIST', $this->plugins); + } + + /** + * Read the settings meta data + * + * Reads the main file, plugins and template settings meta data + * + * @return array + */ + public function loadMeta() { + // load main file + $meta = array(); + include DOKU_PLUGIN . 'config/settings/config.metadata.php'; + + // plugins + foreach($this->plugins as $plugin) { + $meta = array_merge( + $meta, + $this->loadExtensionMeta( + DOKU_PLUGIN . $plugin . '/conf/metadata.php', + 'plugin', + $plugin + ) + ); + } + + // current template + $meta = array_merge( + $meta, + $this->loadExtensionMeta( + tpl_incdir() . '/conf/metadata.php', + 'tpl', + $this->template + ) + ); + + return $meta; + } + + /** + * Read the default values + * + * Reads the main file, plugins and template defaults + * + * @return array + */ + public function loadDefaults() { + // load main files + global $config_cascade; + $conf = $this->loadConfigs($config_cascade['main']['default']); + + // plugins + foreach($this->plugins as $plugin) { + $conf = array_merge( + $conf, + $this->loadExtensionConf( + DOKU_PLUGIN . $plugin . '/conf/default.php', + 'plugin', + $plugin + ) + ); + } + + // current template + $conf = array_merge( + $conf, + $this->loadExtensionConf( + tpl_incdir() . '/conf/default.php', + 'tpl', + $this->template + ) + ); + + return $conf; + } + + /** + * Reads the language strings + * + * Only reads extensions, main one is loaded the usual way + * + * @return array + */ + public function loadLangs() { + $lang = array(); + + // plugins + foreach($this->plugins as $plugin) { + $lang = array_merge( + $lang, + $this->loadExtensionLang( + DOKU_PLUGIN . $plugin . '/', + 'plugin', + $plugin + ) + ); + } + + // current template + $lang = array_merge( + $lang, + $this->loadExtensionLang( + tpl_incdir() . '/', + 'tpl', + $this->template + ) + ); + + return $lang; + } + + /** + * Read the local settings + * + * @return array + */ + public function loadLocal() { + global $config_cascade; + return $this->loadConfigs($config_cascade['main']['local']); + } + + /** + * Read the protected settings + * + * @return array + */ + public function loadProtected() { + global $config_cascade; + return $this->loadConfigs($config_cascade['main']['protected']); + } + + /** + * Read the config values from the given files + * + * @param string[] $files paths to config php's + * @return array + */ + protected function loadConfigs($files) { + $conf = array(); + foreach($files as $file) { + $conf = array_merge($conf, $this->parser->parse($file)); + } + return $conf; + } + + /** + * Read settings file from an extension + * + * This is used to read the settings.php files of plugins and templates + * + * @param string $file php file to read + * @param string $type should be 'plugin' or 'tpl' + * @param string $extname name of the extension + * @return array + */ + protected function loadExtensionMeta($file, $type, $extname) { + if(!file_exists($file)) return array(); + $prefix = $type . Configuration::KEYMARKER . $extname . Configuration::KEYMARKER; + + // include file + $meta = array(); + include $file; + if(empty($meta)) return array(); + + // read data + $data = array(); + $data[$prefix . $type . '_settings_name'] = ['fieldset']; + foreach($meta as $key => $value) { + if($value[0] == 'fieldset') continue; //plugins only get one fieldset + $data[$prefix . $key] = $value; + } + + return $data; + } + + /** + * Read a default file from an extension + * + * This is used to read the default.php files of plugins and templates + * + * @param string $file php file to read + * @param string $type should be 'plugin' or 'tpl' + * @param string $extname name of the extension + * @return array + */ + protected function loadExtensionConf($file, $type, $extname) { + if(!file_exists($file)) return array(); + $prefix = $type . Configuration::KEYMARKER . $extname . Configuration::KEYMARKER; + + // parse file + $conf = $this->parser->parse($file); + if(empty($conf)) return array(); + + // read data + $data = array(); + foreach($conf as $key => $value) { + $data[$prefix . $key] = $value; + } + + return $data; + } + + /** + * Read the language file of an extension + * + * @param string $dir directory of the extension + * @param string $type should be 'plugin' or 'tpl' + * @param string $extname name of the extension + * @return array + */ + protected function loadExtensionLang($dir, $type, $extname) { + global $conf; + $ll = $conf['lang']; + $prefix = $type . Configuration::KEYMARKER . $extname . Configuration::KEYMARKER; + + // include files + $lang = array(); + if(file_exists($dir . 'lang/en/settings.php')) { + include $dir . 'lang/en/settings.php'; + } + if($ll != 'en' && file_exists($dir . 'lang/' . $ll . '/settings.php')) { + include $dir . 'lang/' . $ll . '/settings.php'; + } + + // set up correct keys + $strings = array(); + foreach($lang as $key => $val) { + $strings[$prefix . $key] = $val; + } + + // add fieldset key + $strings[$prefix . $type . '_settings_name'] = ucwords(str_replace('_', ' ', $extname)); + + return $strings; + } +} diff --git a/lib/plugins/config/core/Setting/Setting.php b/lib/plugins/config/core/Setting/Setting.php new file mode 100644 index 000000000..d64f68417 --- /dev/null +++ b/lib/plugins/config/core/Setting/Setting.php @@ -0,0 +1,294 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +use dokuwiki\plugin\config\core\Configuration; + +/** + * Class Setting + */ +class Setting { + /** @var string unique identifier of this setting */ + protected $key = ''; + + /** @var mixed the default value of this setting */ + protected $default = null; + /** @var mixed the local value of this setting */ + protected $local = null; + /** @var mixed the protected value of this setting */ + protected $protected = null; + + /** @var array valid alerts, images matching the alerts are in the plugin's images directory */ + static protected $validCautions = array('warning', 'danger', 'security'); + + protected $pattern = ''; + protected $error = false; // only used by those classes which error check + protected $input = null; // only used by those classes which error check + protected $caution = null; // used by any setting to provide an alert along with the setting + + /** + * Constructor. + * + * The given parameters will be set up as class properties + * + * @see initialize() to set the actual value of the setting + * + * @param string $key + * @param array|null $params array with metadata of setting + */ + public function __construct($key, $params = null) { + $this->key = $key; + + if(is_array($params)) { + foreach($params as $property => $value) { + $property = trim($property, '_'); // we don't use underscores anymore + $this->$property = $value; + } + } + } + + /** + * Set the current values for the setting $key + * + * This is used to initialize the setting with the data read form the config files. + * + * @see update() to set a new value + * @param mixed $default default setting value + * @param mixed $local local setting value + * @param mixed $protected protected setting value + */ + public function initialize($default = null, $local = null, $protected = null) { + $this->default = $this->cleanValue($default); + $this->local = $this->cleanValue($local); + $this->protected = $this->cleanValue($protected); + } + + /** + * update changed setting with validated user provided value $input + * - if changed value fails validation check, save it to $this->input (to allow echoing later) + * - if changed value passes validation check, set $this->local to the new value + * + * @param mixed $input the new value + * @return boolean true if changed, false otherwise + */ + public function update($input) { + if(is_null($input)) return false; + if($this->isProtected()) return false; + $input = $this->cleanValue($input); + + $value = is_null($this->local) ? $this->default : $this->local; + if($value == $input) return false; + + // validate new value + if($this->pattern && !preg_match($this->pattern, $input)) { + $this->error = true; + $this->input = $input; + return false; + } + + // update local copy of this setting with new value + $this->local = $input; + + // setting ready for update + return true; + } + + /** + * Clean a value read from a config before using it internally + * + * Default implementation returns $value as is. Subclasses can override. + * Note: null should always be returned as null! + * + * This is applied in initialize() and update() + * + * @param mixed $value + * @return mixed + */ + protected function cleanValue($value) { + return $value; + } + + /** + * Should this type of config have a default? + * + * @return bool + */ + public function shouldHaveDefault() { + return true; + } + + /** + * Get this setting's unique key + * + * @return string + */ + public function getKey() { + return $this->key; + } + + /** + * Get the key of this setting marked up human readable + * + * @param bool $url link to dokuwiki.org manual? + * @return string + */ + public function getPrettyKey($url = true) { + $out = str_replace(Configuration::KEYMARKER, "»", $this->key); + if($url && !strstr($out, '»')) {//provide no urls for plugins, etc. + if($out == 'start') { + // exception, because this config name is clashing with our actual start page + return '<a href="http://www.dokuwiki.org/config:startpage">' . $out . '</a>'; + } else { + return '<a href="http://www.dokuwiki.org/config:' . $out . '">' . $out . '</a>'; + } + } + return $out; + } + + /** + * Returns setting key as an array key separator + * + * This is used to create form output + * + * @return string key + */ + public function getArrayKey() { + return str_replace(Configuration::KEYMARKER, "']['", $this->key); + } + + /** + * What type of configuration is this + * + * Returns one of + * + * 'plugin' for plugin configuration + * 'template' for template configuration + * 'dokuwiki' for core configuration + * + * @return string + */ + public function getType() { + if(substr($this->getKey(), 0, 10) == 'plugin' . Configuration::KEYMARKER) { + return 'plugin'; + } else if(substr($this->getKey(), 0, 7) == 'tpl' . Configuration::KEYMARKER) { + return 'template'; + } else { + return 'dokuwiki'; + } + } + + /** + * Build html for label and input of setting + * + * @param \admin_plugin_config $plugin object of config plugin + * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting + * @return string[] with content array(string $label_html, string $input_html) + */ + public function html(\admin_plugin_config $plugin, $echo = false) { + $disable = ''; + + if($this->isProtected()) { + $value = $this->protected; + $disable = 'disabled="disabled"'; + } else { + if($echo && $this->error) { + $value = $this->input; + } else { + $value = is_null($this->local) ? $this->default : $this->local; + } + } + + $key = htmlspecialchars($this->key); + $value = formText($value); + + $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>'; + $input = '<textarea rows="3" cols="40" id="config___' . $key . + '" name="config[' . $key . ']" class="edit" ' . $disable . '>' . $value . '</textarea>'; + return array($label, $input); + } + + /** + * Should the current local value be saved? + * + * @see out() to run when this returns true + * @return bool + */ + public function shouldBeSaved() { + if($this->isProtected()) return false; + if($this->local === null) return false; + if($this->default == $this->local) return false; + return true; + } + + /** + * Generate string to save local setting value to file according to $fmt + * + * @see shouldBeSaved() to check if this should be called + * @param string $var name of variable + * @param string $fmt save format + * @return string + */ + public function out($var, $fmt = 'php') { + if($fmt != 'php') return ''; + + $tr = array("\\" => '\\\\', "'" => '\\\''); // escape the value + $out = '$' . $var . "['" . $this->getArrayKey() . "'] = '" . strtr(cleanText($this->local), $tr) . "';\n"; + + return $out; + } + + /** + * Returns the localized prompt + * + * @param \admin_plugin_config $plugin object of config plugin + * @return string text + */ + public function prompt(\admin_plugin_config $plugin) { + $prompt = $plugin->getLang($this->key); + if(!$prompt) $prompt = htmlspecialchars(str_replace(array('____', '_'), ' ', $this->key)); + return $prompt; + } + + /** + * Is setting protected + * + * @return bool + */ + public function isProtected() { + return !is_null($this->protected); + } + + /** + * Is setting the default? + * + * @return bool + */ + public function isDefault() { + return !$this->isProtected() && is_null($this->local); + } + + /** + * Has an error? + * + * @return bool + */ + public function hasError() { + return $this->error; + } + + /** + * Returns caution + * + * @return false|string caution string, otherwise false for invalid caution + */ + public function caution() { + if(empty($this->caution)) return false; + if(!in_array($this->caution, Setting::$validCautions)) { + throw new \RuntimeException( + 'Invalid caution string (' . $this->caution . ') in metadata for setting "' . $this->key . '"' + ); + } + return $this->caution; + } + +} diff --git a/lib/plugins/config/core/Setting/SettingArray.php b/lib/plugins/config/core/Setting/SettingArray.php new file mode 100644 index 000000000..c48dc760b --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingArray.php @@ -0,0 +1,105 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_array + */ +class SettingArray extends Setting { + + /** + * Create an array from a string + * + * @param string $string + * @return array + */ + protected function fromString($string) { + $array = explode(',', $string); + $array = array_map('trim', $array); + $array = array_filter($array); + $array = array_unique($array); + return $array; + } + + /** + * Create a string from an array + * + * @param array $array + * @return string + */ + protected function fromArray($array) { + return join(', ', (array) $array); + } + + /** + * update setting with user provided value $input + * if value fails error check, save it + * + * @param string $input + * @return bool true if changed, false otherwise (incl. on error) + */ + public function update($input) { + if(is_null($input)) return false; + if($this->isProtected()) return false; + + $input = $this->fromString($input); + + $value = is_null($this->local) ? $this->default : $this->local; + if($value == $input) return false; + + foreach($input as $item) { + if($this->pattern && !preg_match($this->pattern, $item)) { + $this->error = true; + $this->input = $input; + return false; + } + } + + $this->local = $input; + return true; + } + + /** + * Escaping + * + * @param string $string + * @return string + */ + protected function escape($string) { + $tr = array("\\" => '\\\\', "'" => '\\\''); + return "'" . strtr(cleanText($string), $tr) . "'"; + } + + /** @inheritdoc */ + public function out($var, $fmt = 'php') { + if($fmt != 'php') return ''; + + $vals = array_map(array($this, 'escape'), $this->local); + $out = '$' . $var . "['" . $this->getArrayKey() . "'] = array(" . join(', ', $vals) . ");\n"; + return $out; + } + + /** @inheritdoc */ + public function html(\admin_plugin_config $plugin, $echo = false) { + $disable = ''; + + if($this->isProtected()) { + $value = $this->protected; + $disable = 'disabled="disabled"'; + } else { + if($echo && $this->error) { + $value = $this->input; + } else { + $value = is_null($this->local) ? $this->default : $this->local; + } + } + + $key = htmlspecialchars($this->key); + $value = htmlspecialchars($this->fromArray($value)); + + $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>'; + $input = '<input id="config___' . $key . '" name="config[' . $key . + ']" type="text" class="edit" value="' . $value . '" ' . $disable . '/>'; + return array($label, $input); + } +} diff --git a/lib/plugins/config/core/Setting/SettingAuthtype.php b/lib/plugins/config/core/Setting/SettingAuthtype.php new file mode 100644 index 000000000..3a6df6fe5 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingAuthtype.php @@ -0,0 +1,60 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_authtype + */ +class SettingAuthtype extends SettingMultichoice { + + /** @inheritdoc */ + public function initialize($default = null, $local = null, $protected = null) { + /** @var $plugin_controller \dokuwiki\Extension\PluginController */ + global $plugin_controller; + + // retrieve auth types provided by plugins + foreach($plugin_controller->getList('auth') as $plugin) { + $this->choices[] = $plugin; + } + + parent::initialize($default, $local, $protected); + } + + /** @inheritdoc */ + public function update($input) { + /** @var $plugin_controller \dokuwiki\Extension\PluginController */ + global $plugin_controller; + + // is an update possible/requested? + $local = $this->local; // save this, parent::update() may change it + if(!parent::update($input)) return false; // nothing changed or an error caught by parent + $this->local = $local; // restore original, more error checking to come + + // attempt to load the plugin + $auth_plugin = $plugin_controller->load('auth', $input); + + // @TODO: throw an error in plugin controller instead of returning null + if(is_null($auth_plugin)) { + $this->error = true; + msg('Cannot load Auth Plugin "' . $input . '"', -1); + return false; + } + + // verify proper instantiation (is this really a plugin?) @TODO use instanceof? implement interface? + if(is_object($auth_plugin) && !method_exists($auth_plugin, 'getPluginName')) { + $this->error = true; + msg('Cannot create Auth Plugin "' . $input . '"', -1); + return false; + } + + // did we change the auth type? logout + global $conf; + if($conf['authtype'] != $input) { + msg('Authentication system changed. Please re-login.'); + auth_logoff(); + } + + $this->local = $input; + return true; + } +} diff --git a/lib/plugins/config/core/Setting/SettingCompression.php b/lib/plugins/config/core/Setting/SettingCompression.php new file mode 100644 index 000000000..f97d82801 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingCompression.php @@ -0,0 +1,21 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_compression + */ +class SettingCompression extends SettingMultichoice { + + protected $choices = array('0'); // 0 = no compression, always supported + + /** @inheritdoc */ + public function initialize($default = null, $local = null, $protected = null) { + + // populate _choices with the compression methods supported by this php installation + if(function_exists('gzopen')) $this->choices[] = 'gz'; + if(function_exists('bzopen')) $this->choices[] = 'bz2'; + + parent::initialize($default, $local, $protected); + } +} diff --git a/lib/plugins/config/core/Setting/SettingDirchoice.php b/lib/plugins/config/core/Setting/SettingDirchoice.php new file mode 100644 index 000000000..dfb27f5f4 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingDirchoice.php @@ -0,0 +1,33 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_dirchoice + */ +class SettingDirchoice extends SettingMultichoice { + + protected $dir = ''; + + /** @inheritdoc */ + public function initialize($default = null, $local = null, $protected = null) { + + // populate $this->_choices with a list of directories + $list = array(); + + if($dh = @opendir($this->dir)) { + while(false !== ($entry = readdir($dh))) { + if($entry == '.' || $entry == '..') continue; + if($this->pattern && !preg_match($this->pattern, $entry)) continue; + + $file = (is_link($this->dir . $entry)) ? readlink($this->dir . $entry) : $this->dir . $entry; + if(is_dir($file)) $list[] = $entry; + } + closedir($dh); + } + sort($list); + $this->choices = $list; + + parent::initialize($default, $local, $protected); + } +} diff --git a/lib/plugins/config/core/Setting/SettingDisableactions.php b/lib/plugins/config/core/Setting/SettingDisableactions.php new file mode 100644 index 000000000..2553175bd --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingDisableactions.php @@ -0,0 +1,23 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_disableactions + */ +class SettingDisableactions extends SettingMulticheckbox { + + /** @inheritdoc */ + public function html(\admin_plugin_config $plugin, $echo = false) { + global $lang; + + // make some language adjustments (there must be a better way) + // transfer some DokuWiki language strings to the plugin + $plugin->addLang($this->key . '_revisions', $lang['btn_revs']); + foreach($this->choices as $choice) { + if(isset($lang['btn_' . $choice])) $plugin->addLang($this->key . '_' . $choice, $lang['btn_' . $choice]); + } + + return parent::html($plugin, $echo); + } +} diff --git a/lib/plugins/config/core/Setting/SettingEmail.php b/lib/plugins/config/core/Setting/SettingEmail.php new file mode 100644 index 000000000..25a0c0e75 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingEmail.php @@ -0,0 +1,58 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_email + */ +class SettingEmail extends SettingString { + protected $multiple = false; + protected $placeholders = false; + + /** @inheritdoc */ + public function update($input) { + if(is_null($input)) return false; + if($this->isProtected()) return false; + + $value = is_null($this->local) ? $this->default : $this->local; + if($value == $input) return false; + if($input === '') { + $this->local = $input; + return true; + } + $mail = $input; + + if($this->placeholders) { + // replace variables with pseudo values + $mail = str_replace('@USER@', 'joe', $mail); + $mail = str_replace('@NAME@', 'Joe Schmoe', $mail); + $mail = str_replace('@MAIL@', 'joe@example.com', $mail); + } + + // multiple mail addresses? + if($this->multiple) { + $mails = array_filter(array_map('trim', explode(',', $mail))); + } else { + $mails = array($mail); + } + + // check them all + foreach($mails as $mail) { + // only check the address part + if(preg_match('#(.*?)<(.*?)>#', $mail, $matches)) { + $addr = $matches[2]; + } else { + $addr = $mail; + } + + if(!mail_isvalid($addr)) { + $this->error = true; + $this->input = $input; + return false; + } + } + + $this->local = $input; + return true; + } +} diff --git a/lib/plugins/config/core/Setting/SettingFieldset.php b/lib/plugins/config/core/Setting/SettingFieldset.php new file mode 100644 index 000000000..4e8618967 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingFieldset.php @@ -0,0 +1,17 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * A do-nothing class used to detect the 'fieldset' type. + * + * Used to start a new settings "display-group". + */ +class SettingFieldset extends Setting { + + /** @inheritdoc */ + public function shouldHaveDefault() { + return false; + } + +} diff --git a/lib/plugins/config/core/Setting/SettingHidden.php b/lib/plugins/config/core/Setting/SettingHidden.php new file mode 100644 index 000000000..ca8a03eb9 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingHidden.php @@ -0,0 +1,10 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_hidden + */ +class SettingHidden extends Setting { + // Used to explicitly ignore a setting in the configuration manager. +} diff --git a/lib/plugins/config/core/Setting/SettingImConvert.php b/lib/plugins/config/core/Setting/SettingImConvert.php new file mode 100644 index 000000000..8740d94c8 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingImConvert.php @@ -0,0 +1,28 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_im_convert + */ +class SettingImConvert extends SettingString { + + /** @inheritdoc */ + public function update($input) { + if($this->isProtected()) return false; + + $input = trim($input); + + $value = is_null($this->local) ? $this->default : $this->local; + if($value == $input) return false; + + if($input && !file_exists($input)) { + $this->error = true; + $this->input = $input; + return false; + } + + $this->local = $input; + return true; + } +} diff --git a/lib/plugins/config/core/Setting/SettingLicense.php b/lib/plugins/config/core/Setting/SettingLicense.php new file mode 100644 index 000000000..8dacf8e25 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingLicense.php @@ -0,0 +1,23 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_license + */ +class SettingLicense extends SettingMultichoice { + + protected $choices = array(''); // none choosen + + /** @inheritdoc */ + public function initialize($default = null, $local = null, $protected = null) { + global $license; + + foreach($license as $key => $data) { + $this->choices[] = $key; + $this->lang[$this->key . '_o_' . $key] = $data['name']; // stored in setting + } + + parent::initialize($default, $local, $protected); + } +} diff --git a/lib/plugins/config/core/Setting/SettingMulticheckbox.php b/lib/plugins/config/core/Setting/SettingMulticheckbox.php new file mode 100644 index 000000000..df212cca0 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingMulticheckbox.php @@ -0,0 +1,163 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_multicheckbox + */ +class SettingMulticheckbox extends SettingString { + + protected $choices = array(); + protected $combine = array(); + protected $other = 'always'; + + /** @inheritdoc */ + public function update($input) { + if($this->isProtected()) return false; + + // split any combined values + convert from array to comma separated string + $input = ($input) ? $input : array(); + $input = $this->array2str($input); + + $value = is_null($this->local) ? $this->default : $this->local; + if($value == $input) return false; + + if($this->pattern && !preg_match($this->pattern, $input)) { + $this->error = true; + $this->input = $input; + return false; + } + + $this->local = $input; + return true; + } + + /** @inheritdoc */ + public function html(\admin_plugin_config $plugin, $echo = false) { + + $disable = ''; + + if($this->isProtected()) { + $value = $this->protected; + $disable = 'disabled="disabled"'; + } else { + if($echo && $this->error) { + $value = $this->input; + } else { + $value = is_null($this->local) ? $this->default : $this->local; + } + } + + $key = htmlspecialchars($this->key); + + // convert from comma separated list into array + combine complimentary actions + $value = $this->str2array($value); + $default = $this->str2array($this->default); + + $input = ''; + foreach($this->choices as $choice) { + $idx = array_search($choice, $value); + $idx_default = array_search($choice, $default); + + $checked = ($idx !== false) ? 'checked="checked"' : ''; + + // @todo ideally this would be handled using a second class of "default" + $class = (($idx !== false) == (false !== $idx_default)) ? " selectiondefault" : ""; + + $prompt = ($plugin->getLang($this->key . '_' . $choice) ? + $plugin->getLang($this->key . '_' . $choice) : htmlspecialchars($choice)); + + $input .= '<div class="selection' . $class . '">' . "\n"; + $input .= '<label for="config___' . $key . '_' . $choice . '">' . $prompt . "</label>\n"; + $input .= '<input id="config___' . $key . '_' . $choice . '" name="config[' . $key . + '][]" type="checkbox" class="checkbox" value="' . $choice . '" ' . $disable . ' ' . $checked . "/>\n"; + $input .= "</div>\n"; + + // remove this action from the disabledactions array + if($idx !== false) unset($value[$idx]); + if($idx_default !== false) unset($default[$idx_default]); + } + + // handle any remaining values + if($this->other != 'never') { + $other = join(',', $value); + // test equivalent to ($this->_other == 'always' || ($other && $this->_other == 'exists') + // use != 'exists' rather than == 'always' to ensure invalid values default to 'always' + if($this->other != 'exists' || $other) { + + $class = ( + (count($default) == count($value)) && + (count($value) == count(array_intersect($value, $default))) + ) ? + " selectiondefault" : ""; + + $input .= '<div class="other' . $class . '">' . "\n"; + $input .= '<label for="config___' . $key . '_other">' . + $plugin->getLang($key . '_other') . + "</label>\n"; + $input .= '<input id="config___' . $key . '_other" name="config[' . $key . + '][other]" type="text" class="edit" value="' . htmlspecialchars($other) . + '" ' . $disable . " />\n"; + $input .= "</div>\n"; + } + } + $label = '<label>' . $this->prompt($plugin) . '</label>'; + return array($label, $input); + } + + /** + * convert comma separated list to an array and combine any complimentary values + * + * @param string $str + * @return array + */ + protected function str2array($str) { + $array = explode(',', $str); + + if(!empty($this->combine)) { + foreach($this->combine as $key => $combinators) { + $idx = array(); + foreach($combinators as $val) { + if(($idx[] = array_search($val, $array)) === false) break; + } + + if(count($idx) && $idx[count($idx) - 1] !== false) { + foreach($idx as $i) unset($array[$i]); + $array[] = $key; + } + } + } + + return $array; + } + + /** + * convert array of values + other back to a comma separated list, incl. splitting any combined values + * + * @param array $input + * @return string + */ + protected function array2str($input) { + + // handle other + $other = trim($input['other']); + $other = !empty($other) ? explode(',', str_replace(' ', '', $input['other'])) : array(); + unset($input['other']); + + $array = array_unique(array_merge($input, $other)); + + // deconstruct any combinations + if(!empty($this->combine)) { + foreach($this->combine as $key => $combinators) { + + $idx = array_search($key, $array); + if($idx !== false) { + unset($array[$idx]); + $array = array_merge($array, $combinators); + } + } + } + + return join(',', array_unique($array)); + } +} diff --git a/lib/plugins/config/core/Setting/SettingMultichoice.php b/lib/plugins/config/core/Setting/SettingMultichoice.php new file mode 100644 index 000000000..3a50857e0 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingMultichoice.php @@ -0,0 +1,71 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_multichoice + */ +class SettingMultichoice extends SettingString { + protected $choices = array(); + public $lang; //some custom language strings are stored in setting + + /** @inheritdoc */ + public function html(\admin_plugin_config $plugin, $echo = false) { + $disable = ''; + $nochoice = ''; + + if($this->isProtected()) { + $value = $this->protected; + $disable = ' disabled="disabled"'; + } else { + $value = is_null($this->local) ? $this->default : $this->local; + } + + // ensure current value is included + if(!in_array($value, $this->choices)) { + $this->choices[] = $value; + } + // disable if no other choices + if(!$this->isProtected() && count($this->choices) <= 1) { + $disable = ' disabled="disabled"'; + $nochoice = $plugin->getLang('nochoice'); + } + + $key = htmlspecialchars($this->key); + + $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>'; + + $input = "<div class=\"input\">\n"; + $input .= '<select class="edit" id="config___' . $key . '" name="config[' . $key . ']"' . $disable . '>' . "\n"; + foreach($this->choices as $choice) { + $selected = ($value == $choice) ? ' selected="selected"' : ''; + $option = $plugin->getLang($this->key . '_o_' . $choice); + if(!$option && isset($this->lang[$this->key . '_o_' . $choice])) { + $option = $this->lang[$this->key . '_o_' . $choice]; + } + if(!$option) $option = $choice; + + $choice = htmlspecialchars($choice); + $option = htmlspecialchars($option); + $input .= ' <option value="' . $choice . '"' . $selected . ' >' . $option . '</option>' . "\n"; + } + $input .= "</select> $nochoice \n"; + $input .= "</div>\n"; + + return array($label, $input); + } + + /** @inheritdoc */ + public function update($input) { + if(is_null($input)) return false; + if($this->isProtected()) return false; + + $value = is_null($this->local) ? $this->default : $this->local; + if($value == $input) return false; + + if(!in_array($input, $this->choices)) return false; + + $this->local = $input; + return true; + } +} diff --git a/lib/plugins/config/core/Setting/SettingNoClass.php b/lib/plugins/config/core/Setting/SettingNoClass.php new file mode 100644 index 000000000..8efff216a --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingNoClass.php @@ -0,0 +1,12 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_no_class + * A do-nothing class used to detect settings with a missing setting class. + * Used internaly to hide undefined settings, and generate the undefined settings list. + */ +class SettingNoClass extends SettingUndefined { + protected $errorMessage = '_msg_setting_no_class'; +} diff --git a/lib/plugins/config/core/Setting/SettingNoDefault.php b/lib/plugins/config/core/Setting/SettingNoDefault.php new file mode 100644 index 000000000..07b8412dd --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingNoDefault.php @@ -0,0 +1,13 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_no_default + * + * A do-nothing class used to detect settings with no default value. + * Used internaly to hide undefined settings, and generate the undefined settings list. + */ +class SettingNoDefault extends SettingUndefined { + protected $errorMessage = '_msg_setting_no_default'; +} diff --git a/lib/plugins/config/core/Setting/SettingNoKnownClass.php b/lib/plugins/config/core/Setting/SettingNoKnownClass.php new file mode 100644 index 000000000..3c527e1ee --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingNoKnownClass.php @@ -0,0 +1,11 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * A do-nothing class used to detect settings with a missing setting class. + * Used internaly to hide undefined settings, and generate the undefined settings list. + */ +class SettingNoKnownClass extends SettingUndefined { + protected $errorMessage = '_msg_setting_no_known_class'; +} diff --git a/lib/plugins/config/core/Setting/SettingNumeric.php b/lib/plugins/config/core/Setting/SettingNumeric.php new file mode 100644 index 000000000..8a6b17956 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingNumeric.php @@ -0,0 +1,42 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_numeric + */ +class SettingNumeric extends SettingString { + // This allows for many PHP syntax errors... + // var $_pattern = '/^[-+\/*0-9 ]*$/'; + // much more restrictive, but should eliminate syntax errors. + protected $pattern = '/^[-+]? *[0-9]+ *(?:[-+*] *[0-9]+ *)*$/'; + protected $min = null; + protected $max = null; + + /** @inheritdoc */ + public function update($input) { + $local = $this->local; + $valid = parent::update($input); + if($valid && !(is_null($this->min) && is_null($this->max))) { + $numeric_local = (int) eval('return ' . $this->local . ';'); + if((!is_null($this->min) && $numeric_local < $this->min) || + (!is_null($this->max) && $numeric_local > $this->max)) { + $this->error = true; + $this->input = $input; + $this->local = $local; + $valid = false; + } + } + return $valid; + } + + /** @inheritdoc */ + public function out($var, $fmt = 'php') { + if($fmt != 'php') return ''; + + $local = $this->local === '' ? "''" : $this->local; + $out = '$' . $var . "['" . $this->getArrayKey() . "'] = " . $local . ";\n"; + + return $out; + } +} diff --git a/lib/plugins/config/core/Setting/SettingNumericopt.php b/lib/plugins/config/core/Setting/SettingNumericopt.php new file mode 100644 index 000000000..a486e187f --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingNumericopt.php @@ -0,0 +1,25 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_numericopt + */ +class SettingNumericopt extends SettingNumeric { + // just allow an empty config + protected $pattern = '/^(|[-]?[0-9]+(?:[-+*][0-9]+)*)$/'; + + /** + * @inheritdoc + * Empty string is valid for numericopt + */ + public function update($input) { + if($input === '') { + if($input == $this->local) return false; + $this->local = $input; + return true; + } + + return parent::update($input); + } +} diff --git a/lib/plugins/config/core/Setting/SettingOnoff.php b/lib/plugins/config/core/Setting/SettingOnoff.php new file mode 100644 index 000000000..780778b48 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingOnoff.php @@ -0,0 +1,57 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_onoff + */ +class SettingOnoff extends SettingNumeric { + + /** + * We treat the strings 'false' and 'off' as false + * @inheritdoc + */ + protected function cleanValue($value) { + if($value === null) return null; + + if(is_string($value)) { + if(strtolower($value) === 'false') return 0; + if(strtolower($value) === 'off') return 0; + if(trim($value) === '') return 0; + } + + return (int) (bool) $value; + } + + /** @inheritdoc */ + public function html(\admin_plugin_config $plugin, $echo = false) { + $disable = ''; + + if($this->isProtected()) { + $value = $this->protected; + $disable = ' disabled="disabled"'; + } else { + $value = is_null($this->local) ? $this->default : $this->local; + } + + $key = htmlspecialchars($this->key); + $checked = ($value) ? ' checked="checked"' : ''; + + $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>'; + $input = '<div class="input"><input id="config___' . $key . '" name="config[' . $key . + ']" type="checkbox" class="checkbox" value="1"' . $checked . $disable . '/></div>'; + return array($label, $input); + } + + /** @inheritdoc */ + public function update($input) { + if($this->isProtected()) return false; + + $input = ($input) ? 1 : 0; + $value = is_null($this->local) ? $this->default : $this->local; + if($value == $input) return false; + + $this->local = $input; + return true; + } +} diff --git a/lib/plugins/config/core/Setting/SettingPassword.php b/lib/plugins/config/core/Setting/SettingPassword.php new file mode 100644 index 000000000..9d9c53377 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingPassword.php @@ -0,0 +1,39 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_password + */ +class SettingPassword extends SettingString { + + protected $code = 'plain'; // mechanism to be used to obscure passwords + + /** @inheritdoc */ + public function update($input) { + if($this->isProtected()) return false; + if(!$input) return false; + + if($this->pattern && !preg_match($this->pattern, $input)) { + $this->error = true; + $this->input = $input; + return false; + } + + $this->local = conf_encodeString($input, $this->code); + return true; + } + + /** @inheritdoc */ + public function html(\admin_plugin_config $plugin, $echo = false) { + + $disable = $this->isProtected() ? 'disabled="disabled"' : ''; + + $key = htmlspecialchars($this->key); + + $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>'; + $input = '<input id="config___' . $key . '" name="config[' . $key . + ']" autocomplete="off" type="password" class="edit" value="" ' . $disable . ' />'; + return array($label, $input); + } +} diff --git a/lib/plugins/config/core/Setting/SettingRegex.php b/lib/plugins/config/core/Setting/SettingRegex.php new file mode 100644 index 000000000..b38f0a560 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingRegex.php @@ -0,0 +1,34 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_regex + */ +class SettingRegex extends SettingString { + + protected $delimiter = '/'; // regex delimiter to be used in testing input + protected $pregflags = 'ui'; // regex pattern modifiers to be used in testing input + + /** @inheritdoc */ + public function update($input) { + + // let parent do basic checks, value, not changed, etc. + $local = $this->local; + if(!parent::update($input)) return false; + $this->local = $local; + + // see if the regex compiles and runs (we don't check for effectiveness) + $regex = $this->delimiter . $input . $this->delimiter . $this->pregflags; + $lastError = error_get_last(); + @preg_match($regex, 'testdata'); + if(preg_last_error() != PREG_NO_ERROR || error_get_last() != $lastError) { + $this->input = $input; + $this->error = true; + return false; + } + + $this->local = $input; + return true; + } +} diff --git a/lib/plugins/config/core/Setting/SettingRenderer.php b/lib/plugins/config/core/Setting/SettingRenderer.php new file mode 100644 index 000000000..37ba9c70a --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingRenderer.php @@ -0,0 +1,56 @@ +<?php +/** + * additional setting classes specific to these settings + * + * @author Chris Smith <chris@jalakai.co.uk> + */ + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_renderer + */ +class SettingRenderer extends SettingMultichoice { + protected $prompts = array(); + protected $format = null; + + /** @inheritdoc */ + public function initialize($default = null, $local = null, $protected = null) { + $format = $this->format; + + foreach(plugin_list('renderer') as $plugin) { + $renderer = plugin_load('renderer', $plugin); + if(method_exists($renderer, 'canRender') && $renderer->canRender($format)) { + $this->choices[] = $plugin; + + $info = $renderer->getInfo(); + $this->prompts[$plugin] = $info['name']; + } + } + + parent::initialize($default, $local, $protected); + } + + /** @inheritdoc */ + public function html(\admin_plugin_config $plugin, $echo = false) { + + // make some language adjustments (there must be a better way) + // transfer some plugin names to the config plugin + foreach($this->choices as $choice) { + if(!$plugin->getLang($this->key . '_o_' . $choice)) { + if(!isset($this->prompts[$choice])) { + $plugin->addLang( + $this->key . '_o_' . $choice, + sprintf($plugin->getLang('renderer__core'), $choice) + ); + } else { + $plugin->addLang( + $this->key . '_o_' . $choice, + sprintf($plugin->getLang('renderer__plugin'), $this->prompts[$choice]) + ); + } + } + } + return parent::html($plugin, $echo); + } +} diff --git a/lib/plugins/config/core/Setting/SettingSavedir.php b/lib/plugins/config/core/Setting/SettingSavedir.php new file mode 100644 index 000000000..43e428dd3 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingSavedir.php @@ -0,0 +1,26 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_savedir + */ +class SettingSavedir extends SettingString { + + /** @inheritdoc */ + public function update($input) { + if($this->isProtected()) return false; + + $value = is_null($this->local) ? $this->default : $this->local; + if($value == $input) return false; + + if(!init_path($input)) { + $this->error = true; + $this->input = $input; + return false; + } + + $this->local = $input; + return true; + } +} diff --git a/lib/plugins/config/core/Setting/SettingSepchar.php b/lib/plugins/config/core/Setting/SettingSepchar.php new file mode 100644 index 000000000..2d64eb08b --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingSepchar.php @@ -0,0 +1,18 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_sepchar + */ +class SettingSepchar extends SettingMultichoice { + + /** @inheritdoc */ + public function __construct($key, $param = null) { + $str = '_-.'; + for($i = 0; $i < strlen($str); $i++) $this->choices[] = $str{$i}; + + // call foundation class constructor + parent::__construct($key, $param); + } +} diff --git a/lib/plugins/config/core/Setting/SettingString.php b/lib/plugins/config/core/Setting/SettingString.php new file mode 100644 index 000000000..b819407b7 --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingString.php @@ -0,0 +1,32 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +/** + * Class setting_string + */ +class SettingString extends Setting { + /** @inheritdoc */ + public function html(\admin_plugin_config $plugin, $echo = false) { + $disable = ''; + + if($this->isProtected()) { + $value = $this->protected; + $disable = 'disabled="disabled"'; + } else { + if($echo && $this->error) { + $value = $this->input; + } else { + $value = is_null($this->local) ? $this->default : $this->local; + } + } + + $key = htmlspecialchars($this->key); + $value = htmlspecialchars($value); + + $label = '<label for="config___' . $key . '">' . $this->prompt($plugin) . '</label>'; + $input = '<input id="config___' . $key . '" name="config[' . $key . + ']" type="text" class="edit" value="' . $value . '" ' . $disable . '/>'; + return array($label, $input); + } +} diff --git a/lib/plugins/config/core/Setting/SettingUndefined.php b/lib/plugins/config/core/Setting/SettingUndefined.php new file mode 100644 index 000000000..fa46a9f1d --- /dev/null +++ b/lib/plugins/config/core/Setting/SettingUndefined.php @@ -0,0 +1,40 @@ +<?php + +namespace dokuwiki\plugin\config\core\Setting; + +use dokuwiki\plugin\config\core\Configuration; + +/** + * A do-nothing class used to detect settings with no metadata entry. + * Used internaly to hide undefined settings, and generate the undefined settings list. + */ +class SettingUndefined extends SettingHidden { + + protected $errorMessage = '_msg_setting_undefined'; + + /** @inheritdoc */ + public function shouldHaveDefault() { + return false; + } + + /** @inheritdoc */ + public function html(\admin_plugin_config $plugin, $echo = false) { + // determine the name the meta key would be called + if(preg_match( + '/^(?:plugin|tpl)' . Configuration::KEYMARKER . '.*?' . Configuration::KEYMARKER . '(.*)$/', + $this->getKey(), + $undefined_setting_match + )) { + $undefined_setting_key = $undefined_setting_match[1]; + } else { + $undefined_setting_key = $this->getKey(); + } + + $label = '<span title="$meta[\'' . $undefined_setting_key . '\']">$' . + 'conf' . '[\'' . $this->getArrayKey() . '\']</span>'; + $input = $plugin->getLang($this->errorMessage); + + return array($label, $input); + } + +} diff --git a/lib/plugins/config/core/Writer.php b/lib/plugins/config/core/Writer.php new file mode 100644 index 000000000..6dee223ac --- /dev/null +++ b/lib/plugins/config/core/Writer.php @@ -0,0 +1,116 @@ +<?php + +namespace dokuwiki\plugin\config\core; +use dokuwiki\plugin\config\core\Setting\Setting; + +/** + * Writes the settings to the correct local file + */ +class Writer { + /** @var string header info */ + protected $header = 'Dokuwiki\'s Main Configuration File - Local Settings'; + + /** @var string the file where the config will be saved to */ + protected $savefile; + + /** + * Writer constructor. + */ + public function __construct() { + global $config_cascade; + $this->savefile = end($config_cascade['main']['local']); + } + + /** + * Save the given settings + * + * @param Setting[] $settings + * @throws \Exception + */ + public function save($settings) { + global $conf; + if($this->isLocked()) throw new \Exception('no save'); + + // backup current file (remove any existing backup) + if(file_exists($this->savefile)) { + if(file_exists($this->savefile . '.bak')) @unlink($this->savefile . '.bak'); + if(!io_rename($this->savefile, $this->savefile . '.bak')) throw new \Exception('no backup'); + } + + if(!$fh = @fopen($this->savefile, 'wb')) { + io_rename($this->savefile . '.bak', $this->savefile); // problem opening, restore the backup + throw new \Exception('no save'); + } + + $out = $this->getHeader(); + foreach($settings as $setting) { + if($setting->shouldBeSaved()) { + $out .= $setting->out('conf', 'php'); + } + } + + fwrite($fh, $out); + fclose($fh); + if($conf['fperm']) chmod($this->savefile, $conf['fperm']); + $this->opcacheUpdate($this->savefile); + } + + /** + * Update last modified time stamp of the config file + * + * Will invalidate all DokuWiki caches + * + * @throws \Exception when the config isn't writable + */ + public function touch() { + if($this->isLocked()) throw new \Exception('no save'); + @touch($this->savefile); + $this->opcacheUpdate($this->savefile); + } + + /** + * Invalidate the opcache of the given file + * + * @todo this should probably be moved to core + * @param string $file + */ + protected function opcacheUpdate($file) { + if(!function_exists('opcache_invalidate')) return; + opcache_invalidate($file); + } + + /** + * Configuration is considered locked if there is no local settings filename + * or the directory its in is not writable or the file exists and is not writable + * + * @return bool true: locked, false: writable + */ + public function isLocked() { + if(!$this->savefile) return true; + if(!is_writable(dirname($this->savefile))) return true; + if(file_exists($this->savefile) && !is_writable($this->savefile)) return true; + return false; + } + + /** + * Returns the PHP intro header for the config file + * + * @return string + */ + protected function getHeader() { + return join( + "\n", + array( + '<?php', + '/*', + ' * ' . $this->header, + ' * Auto-generated by config plugin', + ' * Run for user: ' . $_SERVER['REMOTE_USER'], + ' * Date: ' . date('r'), + ' */', + '', + '' + ) + ); + } +} diff --git a/lib/plugins/config/lang/en/lang.php b/lib/plugins/config/lang/en/lang.php index 63a49e36b..c48885c8d 100644 --- a/lib/plugins/config/lang/en/lang.php +++ b/lib/plugins/config/lang/en/lang.php @@ -46,6 +46,7 @@ $lang['_network'] = 'Network'; /* --- Undefined Setting Messages --- */ $lang['_msg_setting_undefined'] = 'No setting metadata.'; $lang['_msg_setting_no_class'] = 'No setting class.'; +$lang['_msg_setting_no_known_class'] = 'Setting class not available.'; $lang['_msg_setting_no_default'] = 'No default value.'; /* -------------------- Config Options --------------------------- */ diff --git a/lib/plugins/config/settings/config.class.php b/lib/plugins/config/settings/config.class.php deleted file mode 100644 index 09b9a2bec..000000000 --- a/lib/plugins/config/settings/config.class.php +++ /dev/null @@ -1,1431 +0,0 @@ -<?php -/** - * Configuration Class and generic setting classes - * - * @author Chris Smith <chris@jalakai.co.uk> - * @author Ben Coburn <btcoburn@silicodon.net> - */ - - -if(!defined('CM_KEYMARKER')) define('CM_KEYMARKER','____'); - -if (!class_exists('configuration')) { - /** - * Class configuration - */ - class configuration { - - var $_name = 'conf'; // name of the config variable found in the files (overridden by $config['varname']) - var $_format = 'php'; // format of the config file, supported formats - php (overridden by $config['format']) - var $_heading = ''; // heading string written at top of config file - don't include comment indicators - var $_loaded = false; // set to true after configuration files are loaded - var $_metadata = array(); // holds metadata describing the settings - /** @var setting[] */ - var $setting = array(); // array of setting objects - var $locked = false; // configuration is considered locked if it can't be updated - var $show_disabled_plugins = false; - - // configuration filenames - var $_default_files = array(); - var $_local_files = array(); // updated configuration is written to the first file - var $_protected_files = array(); - - var $_plugin_list = null; - - /** - * constructor - * - * @param string $datafile path to config metadata file - */ - public function __construct($datafile) { - global $conf, $config_cascade; - - if (!file_exists($datafile)) { - msg('No configuration metadata found at - '.htmlspecialchars($datafile),-1); - return; - } - $meta = array(); - include($datafile); - - if (isset($config['varname'])) $this->_name = $config['varname']; - if (isset($config['format'])) $this->_format = $config['format']; - if (isset($config['heading'])) $this->_heading = $config['heading']; - - $this->_default_files = $config_cascade['main']['default']; - $this->_local_files = $config_cascade['main']['local']; - $this->_protected_files = $config_cascade['main']['protected']; - - $this->locked = $this->_is_locked(); - $this->_metadata = array_merge($meta, $this->get_plugintpl_metadata($conf['template'])); - $this->retrieve_settings(); - } - - /** - * Retrieve and stores settings in setting[] attribute - */ - public function retrieve_settings() { - global $conf; - $no_default_check = array('setting_fieldset', 'setting_undefined', 'setting_no_class'); - - if (!$this->_loaded) { - $default = array_merge($this->get_plugintpl_default($conf['template']), $this->_read_config_group($this->_default_files)); - $local = $this->_read_config_group($this->_local_files); - $protected = $this->_read_config_group($this->_protected_files); - - $keys = array_merge(array_keys($this->_metadata),array_keys($default), array_keys($local), array_keys($protected)); - $keys = array_unique($keys); - - $param = null; - foreach ($keys as $key) { - if (isset($this->_metadata[$key])) { - $class = $this->_metadata[$key][0]; - - if($class && class_exists('setting_'.$class)){ - $class = 'setting_'.$class; - } else { - if($class != '') { - $this->setting[] = new setting_no_class($key,$param); - } - $class = 'setting'; - } - - $param = $this->_metadata[$key]; - array_shift($param); - } else { - $class = 'setting_undefined'; - $param = null; - } - - if (!in_array($class, $no_default_check) && !isset($default[$key])) { - $this->setting[] = new setting_no_default($key,$param); - } - - $this->setting[$key] = new $class($key,$param); - - $d = array_key_exists($key, $default) ? $default[$key] : null; - $l = array_key_exists($key, $local) ? $local[$key] : null; - $p = array_key_exists($key, $protected) ? $protected[$key] : null; - - $this->setting[$key]->initialize($d,$l,$p); - } - - $this->_loaded = true; - } - } - - /** - * Stores setting[] array to file - * - * @param string $id Name of plugin, which saves the settings - * @param string $header Text at the top of the rewritten settings file - * @param bool $backup backup current file? (remove any existing backup) - * @return bool succesful? - */ - public function save_settings($id, $header='', $backup=true) { - global $conf; - - if ($this->locked) return false; - - // write back to the last file in the local config cascade - $file = end($this->_local_files); - - // backup current file (remove any existing backup) - if (file_exists($file) && $backup) { - if (file_exists($file.'.bak')) @unlink($file.'.bak'); - if (!io_rename($file, $file.'.bak')) return false; - } - - if (!$fh = @fopen($file, 'wb')) { - io_rename($file.'.bak', $file); // problem opening, restore the backup - return false; - } - - if (empty($header)) $header = $this->_heading; - - $out = $this->_out_header($id,$header); - - foreach ($this->setting as $setting) { - $out .= $setting->out($this->_name, $this->_format); - } - - $out .= $this->_out_footer(); - - @fwrite($fh, $out); - fclose($fh); - if($conf['fperm']) chmod($file, $conf['fperm']); - return true; - } - - /** - * Update last modified time stamp of the config file - * - * @return bool - */ - public function touch_settings(){ - if ($this->locked) return false; - $file = end($this->_local_files); - return @touch($file); - } - - /** - * Read and merge given config files - * - * @param array $files file paths - * @return array config settings - */ - protected function _read_config_group($files) { - $config = array(); - foreach ($files as $file) { - $config = array_merge($config, $this->_read_config($file)); - } - - return $config; - } - - /** - * Return an array of config settings - * - * @param string $file file path - * @return array config settings - */ - function _read_config($file) { - - if (!$file) return array(); - - $config = array(); - - if ($this->_format == 'php') { - - if(file_exists($file)){ - $contents = @php_strip_whitespace($file); - }else{ - $contents = ''; - } - $pattern = '/\$'.$this->_name.'\[[\'"]([^=]+)[\'"]\] ?= ?(.*?);(?=[^;]*(?:\$'.$this->_name.'|$))/s'; - $matches=array(); - preg_match_all($pattern,$contents,$matches,PREG_SET_ORDER); - - for ($i=0; $i<count($matches); $i++) { - $value = $matches[$i][2]; - - // correct issues with the incoming data - // FIXME ... for now merge multi-dimensional array indices using ____ - $key = preg_replace('/.\]\[./',CM_KEYMARKER,$matches[$i][1]); - - // handle arrays - if(preg_match('/^array ?\((.*)\)/', $value, $match)){ - $arr = explode(',', $match[1]); - - // remove quotes from quoted strings & unescape escaped data - $len = count($arr); - for($j=0; $j<$len; $j++){ - $arr[$j] = trim($arr[$j]); - $arr[$j] = $this->_readValue($arr[$j]); - } - - $value = $arr; - }else{ - $value = $this->_readValue($value); - } - - $config[$key] = $value; - } - } - - return $config; - } - - /** - * Convert php string into value - * - * @param string $value - * @return bool|string - */ - protected function _readValue($value) { - $removequotes_pattern = '/^(\'|")(.*)(?<!\\\\)\1$/s'; - $unescape_pairs = array( - '\\\\' => '\\', - '\\\'' => '\'', - '\\"' => '"' - ); - - if($value == 'true') { - $value = true; - } elseif($value == 'false') { - $value = false; - } else { - // remove quotes from quoted strings & unescape escaped data - $value = preg_replace($removequotes_pattern,'$2',$value); - $value = strtr($value, $unescape_pairs); - } - return $value; - } - - /** - * Returns header of rewritten settings file - * - * @param string $id plugin name of which generated this output - * @param string $header additional text for at top of the file - * @return string text of header - */ - protected function _out_header($id, $header) { - $out = ''; - if ($this->_format == 'php') { - $out .= '<'.'?php'."\n". - "/*\n". - " * ".$header."\n". - " * Auto-generated by ".$id." plugin\n". - " * Run for user: ".$_SERVER['REMOTE_USER']."\n". - " * Date: ".date('r')."\n". - " */\n\n"; - } - - return $out; - } - - /** - * Returns footer of rewritten settings file - * - * @return string text of footer - */ - protected function _out_footer() { - $out = ''; - if ($this->_format == 'php') { - $out .= "\n// end auto-generated content\n"; - } - - return $out; - } - - /** - * Configuration is considered locked if there is no local settings filename - * or the directory its in is not writable or the file exists and is not writable - * - * @return bool true: locked, false: writable - */ - protected function _is_locked() { - if (!$this->_local_files) return true; - - $local = $this->_local_files[0]; - - if (!is_writable(dirname($local))) return true; - if (file_exists($local) && !is_writable($local)) return true; - - return false; - } - - /** - * not used ... conf's contents are an array! - * reduce any multidimensional settings to one dimension using CM_KEYMARKER - * - * @param $conf - * @param string $prefix - * @return array - */ - protected function _flatten($conf,$prefix='') { - - $out = array(); - - foreach($conf as $key => $value) { - if (!is_array($value)) { - $out[$prefix.$key] = $value; - continue; - } - - $tmp = $this->_flatten($value,$prefix.$key.CM_KEYMARKER); - $out = array_merge($out,$tmp); - } - - return $out; - } - - /** - * Returns array of plugin names - * - * @return array plugin names - * @triggers PLUGIN_CONFIG_PLUGINLIST event - */ - function get_plugin_list() { - if (is_null($this->_plugin_list)) { - $list = plugin_list('',$this->show_disabled_plugins); - - // remove this plugin from the list - $idx = array_search('config',$list); - unset($list[$idx]); - sort($list); // Sort plugin list alphabetically for display - - trigger_event('PLUGIN_CONFIG_PLUGINLIST',$list); - $this->_plugin_list = $list; - } - - return $this->_plugin_list; - } - - /** - * load metadata for plugin and template settings - * - * @param string $tpl name of active template - * @return array metadata of settings - */ - function get_plugintpl_metadata($tpl){ - $file = '/conf/metadata.php'; - $class = '/conf/settings.class.php'; - $metadata = array(); - - foreach ($this->get_plugin_list() as $plugin) { - $plugin_dir = plugin_directory($plugin); - if (file_exists(DOKU_PLUGIN.$plugin_dir.$file)){ - $meta = array(); - @include(DOKU_PLUGIN.$plugin_dir.$file); - @include(DOKU_PLUGIN.$plugin_dir.$class); - if (!empty($meta)) { - $metadata['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.'plugin_settings_name'] = array('fieldset'); - } - foreach ($meta as $key => $value){ - if ($value[0]=='fieldset') { continue; } //plugins only get one fieldset - $metadata['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.$key] = $value; - } - } - } - - // the same for the active template - if (file_exists(tpl_incdir().$file)){ - $meta = array(); - @include(tpl_incdir().$file); - @include(tpl_incdir().$class); - if (!empty($meta)) { - $metadata['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.'template_settings_name'] = array('fieldset'); - } - foreach ($meta as $key => $value){ - if ($value[0]=='fieldset') { continue; } //template only gets one fieldset - $metadata['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.$key] = $value; - } - } - - return $metadata; - } - - /** - * Load default settings for plugins and templates - * - * @param string $tpl name of active template - * @return array default settings - */ - function get_plugintpl_default($tpl){ - $file = '/conf/default.php'; - $default = array(); - - foreach ($this->get_plugin_list() as $plugin) { - $plugin_dir = plugin_directory($plugin); - if (file_exists(DOKU_PLUGIN.$plugin_dir.$file)){ - $conf = $this->_read_config(DOKU_PLUGIN.$plugin_dir.$file); - foreach ($conf as $key => $value){ - $default['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.$key] = $value; - } - } - } - - // the same for the active template - if (file_exists(tpl_incdir().$file)){ - $conf = $this->_read_config(tpl_incdir().$file); - foreach ($conf as $key => $value){ - $default['tpl'.CM_KEYMARKER.$tpl.CM_KEYMARKER.$key] = $value; - } - } - - return $default; - } - - } -} - -if (!class_exists('setting')) { - /** - * Class setting - */ - class setting { - - var $_key = ''; - var $_default = null; - var $_local = null; - var $_protected = null; - - var $_pattern = ''; - var $_error = false; // only used by those classes which error check - var $_input = null; // only used by those classes which error check - var $_caution = null; // used by any setting to provide an alert along with the setting - // valid alerts, 'warning', 'danger', 'security' - // images matching the alerts are in the plugin's images directory - - static protected $_validCautions = array('warning','danger','security'); - - /** - * @param string $key - * @param array|null $params array with metadata of setting - */ - public function __construct($key, $params=null) { - $this->_key = $key; - - if (is_array($params)) { - foreach($params as $property => $value) { - $this->$property = $value; - } - } - } - - /** - * Receives current values for the setting $key - * - * @param mixed $default default setting value - * @param mixed $local local setting value - * @param mixed $protected protected setting value - */ - public function initialize($default, $local, $protected) { - if (isset($default)) $this->_default = $default; - if (isset($local)) $this->_local = $local; - if (isset($protected)) $this->_protected = $protected; - } - - /** - * update changed setting with user provided value $input - * - if changed value fails error check, save it to $this->_input (to allow echoing later) - * - if changed value passes error check, set $this->_local to the new value - * - * @param mixed $input the new value - * @return boolean true if changed, false otherwise (also on error) - */ - public function update($input) { - if (is_null($input)) return false; - if ($this->is_protected()) return false; - - $value = is_null($this->_local) ? $this->_default : $this->_local; - if ($value == $input) return false; - - if ($this->_pattern && !preg_match($this->_pattern,$input)) { - $this->_error = true; - $this->_input = $input; - return false; - } - - $this->_local = $input; - return true; - } - - /** - * Build html for label and input of setting - * - * @param admin_plugin_config $plugin object of config plugin - * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting - * @return string[] with content array(string $label_html, string $input_html) - */ - public function html(admin_plugin_config $plugin, $echo=false) { - $disable = ''; - - if ($this->is_protected()) { - $value = $this->_protected; - $disable = 'disabled="disabled"'; - } else { - if ($echo && $this->_error) { - $value = $this->_input; - } else { - $value = is_null($this->_local) ? $this->_default : $this->_local; - } - } - - $key = htmlspecialchars($this->_key); - $value = formText($value); - - $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>'; - $input = '<textarea rows="3" cols="40" id="config___'.$key.'" name="config['.$key.']" class="edit" '.$disable.'>'.$value.'</textarea>'; - return array($label,$input); - } - - /** - * Generate string to save setting value to file according to $fmt - * - * @param string $var name of variable - * @param string $fmt save format - * @return string - */ - public function out($var, $fmt='php') { - - if ($this->is_protected()) return ''; - if (is_null($this->_local) || ($this->_default == $this->_local)) return ''; - - $out = ''; - - if ($fmt=='php') { - $tr = array("\\" => '\\\\', "'" => '\\\''); - - $out = '$'.$var."['".$this->_out_key()."'] = '".strtr( cleanText($this->_local), $tr)."';\n"; - } - - return $out; - } - - /** - * Returns the localized prompt - * - * @param admin_plugin_config $plugin object of config plugin - * @return string text - */ - public function prompt(admin_plugin_config $plugin) { - $prompt = $plugin->getLang($this->_key); - if (!$prompt) $prompt = htmlspecialchars(str_replace(array('____','_'),' ',$this->_key)); - return $prompt; - } - - /** - * Is setting protected - * - * @return bool - */ - public function is_protected() { return !is_null($this->_protected); } - - /** - * Is setting the default? - * - * @return bool - */ - public function is_default() { return !$this->is_protected() && is_null($this->_local); } - - /** - * Has an error? - * - * @return bool - */ - public function error() { return $this->_error; } - - /** - * Returns caution - * - * @return false|string caution string, otherwise false for invalid caution - */ - public function caution() { - if (!empty($this->_caution)) { - if (!in_array($this->_caution, setting::$_validCautions)) { - trigger_error('Invalid caution string ('.$this->_caution.') in metadata for setting "'.$this->_key.'"', E_USER_WARNING); - return false; - } - return $this->_caution; - } - // compatibility with previous cautionList - // TODO: check if any plugins use; remove - if (!empty($this->_cautionList[$this->_key])) { - $this->_caution = $this->_cautionList[$this->_key]; - unset($this->_cautionList); - - return $this->caution(); - } - return false; - } - - /** - * Returns setting key, eventually with referer to config: namespace at dokuwiki.org - * - * @param bool $pretty create nice key - * @param bool $url provide url to config: namespace - * @return string key - */ - public function _out_key($pretty=false,$url=false) { - if($pretty){ - $out = str_replace(CM_KEYMARKER,"»",$this->_key); - if ($url && !strstr($out,'»')) {//provide no urls for plugins, etc. - if ($out == 'start') //one exception - return '<a href="http://www.dokuwiki.org/config:startpage">'.$out.'</a>'; - else - return '<a href="http://www.dokuwiki.org/config:'.$out.'">'.$out.'</a>'; - } - return $out; - }else{ - return str_replace(CM_KEYMARKER,"']['",$this->_key); - } - } - } -} - - -if (!class_exists('setting_array')) { - /** - * Class setting_array - */ - class setting_array extends setting { - - /** - * Create an array from a string - * - * @param string $string - * @return array - */ - protected function _from_string($string){ - $array = explode(',', $string); - $array = array_map('trim', $array); - $array = array_filter($array); - $array = array_unique($array); - return $array; - } - - /** - * Create a string from an array - * - * @param array $array - * @return string - */ - protected function _from_array($array){ - return join(', ', (array) $array); - } - - /** - * update setting with user provided value $input - * if value fails error check, save it - * - * @param string $input - * @return bool true if changed, false otherwise (incl. on error) - */ - function update($input) { - if (is_null($input)) return false; - if ($this->is_protected()) return false; - - $input = $this->_from_string($input); - - $value = is_null($this->_local) ? $this->_default : $this->_local; - if ($value == $input) return false; - - foreach($input as $item){ - if ($this->_pattern && !preg_match($this->_pattern,$item)) { - $this->_error = true; - $this->_input = $input; - return false; - } - } - - $this->_local = $input; - return true; - } - - /** - * Escaping - * - * @param string $string - * @return string - */ - protected function _escape($string) { - $tr = array("\\" => '\\\\', "'" => '\\\''); - return "'".strtr( cleanText($string), $tr)."'"; - } - - /** - * Generate string to save setting value to file according to $fmt - * - * @param string $var name of variable - * @param string $fmt save format - * @return string - */ - function out($var, $fmt='php') { - - if ($this->is_protected()) return ''; - if (is_null($this->_local) || ($this->_default == $this->_local)) return ''; - - $out = ''; - - if ($fmt=='php') { - $vals = array_map(array($this, '_escape'), $this->_local); - $out = '$'.$var."['".$this->_out_key()."'] = array(".join(', ',$vals).");\n"; - } - - return $out; - } - - /** - * Build html for label and input of setting - * - * @param admin_plugin_config $plugin object of config plugin - * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting - * @return string[] with content array(string $label_html, string $input_html) - */ - function html(admin_plugin_config $plugin, $echo=false) { - $disable = ''; - - if ($this->is_protected()) { - $value = $this->_protected; - $disable = 'disabled="disabled"'; - } else { - if ($echo && $this->_error) { - $value = $this->_input; - } else { - $value = is_null($this->_local) ? $this->_default : $this->_local; - } - } - - $key = htmlspecialchars($this->_key); - $value = htmlspecialchars($this->_from_array($value)); - - $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>'; - $input = '<input id="config___'.$key.'" name="config['.$key.']" type="text" class="edit" value="'.$value.'" '.$disable.'/>'; - return array($label,$input); - } - } -} - -if (!class_exists('setting_string')) { - /** - * Class setting_string - */ - class setting_string extends setting { - /** - * Build html for label and input of setting - * - * @param admin_plugin_config $plugin object of config plugin - * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting - * @return string[] with content array(string $label_html, string $input_html) - */ - function html(admin_plugin_config $plugin, $echo=false) { - $disable = ''; - - if ($this->is_protected()) { - $value = $this->_protected; - $disable = 'disabled="disabled"'; - } else { - if ($echo && $this->_error) { - $value = $this->_input; - } else { - $value = is_null($this->_local) ? $this->_default : $this->_local; - } - } - - $key = htmlspecialchars($this->_key); - $value = htmlspecialchars($value); - - $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>'; - $input = '<input id="config___'.$key.'" name="config['.$key.']" type="text" class="edit" value="'.$value.'" '.$disable.'/>'; - return array($label,$input); - } - } -} - -if (!class_exists('setting_password')) { - /** - * Class setting_password - */ - class setting_password extends setting_string { - - var $_code = 'plain'; // mechanism to be used to obscure passwords - - /** - * update changed setting with user provided value $input - * - if changed value fails error check, save it to $this->_input (to allow echoing later) - * - if changed value passes error check, set $this->_local to the new value - * - * @param mixed $input the new value - * @return boolean true if changed, false otherwise (also on error) - */ - function update($input) { - if ($this->is_protected()) return false; - if (!$input) return false; - - if ($this->_pattern && !preg_match($this->_pattern,$input)) { - $this->_error = true; - $this->_input = $input; - return false; - } - - $this->_local = conf_encodeString($input,$this->_code); - return true; - } - - /** - * Build html for label and input of setting - * - * @param admin_plugin_config $plugin object of config plugin - * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting - * @return string[] with content array(string $label_html, string $input_html) - */ - function html(admin_plugin_config $plugin, $echo=false) { - - $disable = $this->is_protected() ? 'disabled="disabled"' : ''; - - $key = htmlspecialchars($this->_key); - - $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>'; - $input = '<input id="config___'.$key.'" name="config['.$key.']" autocomplete="off" type="password" class="edit" value="" '.$disable.' />'; - return array($label,$input); - } - } -} - -if (!class_exists('setting_email')) { - /** - * Class setting_email - */ - class setting_email extends setting_string { - var $_multiple = false; - var $_placeholders = false; - - /** - * update setting with user provided value $input - * if value fails error check, save it - * - * @param mixed $input - * @return boolean true if changed, false otherwise (incl. on error) - */ - function update($input) { - if (is_null($input)) return false; - if ($this->is_protected()) return false; - - $value = is_null($this->_local) ? $this->_default : $this->_local; - if ($value == $input) return false; - if($input === ''){ - $this->_local = $input; - return true; - } - $mail = $input; - - if($this->_placeholders){ - // replace variables with pseudo values - $mail = str_replace('@USER@','joe',$mail); - $mail = str_replace('@NAME@','Joe Schmoe',$mail); - $mail = str_replace('@MAIL@','joe@example.com',$mail); - } - - // multiple mail addresses? - if ($this->_multiple) { - $mails = array_filter(array_map('trim', explode(',', $mail))); - } else { - $mails = array($mail); - } - - // check them all - foreach ($mails as $mail) { - // only check the address part - if(preg_match('#(.*?)<(.*?)>#', $mail, $matches)){ - $addr = $matches[2]; - }else{ - $addr = $mail; - } - - if (!mail_isvalid($addr)) { - $this->_error = true; - $this->_input = $input; - return false; - } - } - - $this->_local = $input; - return true; - } - } -} - -if (!class_exists('setting_numeric')) { - /** - * Class setting_numeric - */ - class setting_numeric extends setting_string { - // This allows for many PHP syntax errors... - // var $_pattern = '/^[-+\/*0-9 ]*$/'; - // much more restrictive, but should eliminate syntax errors. - var $_pattern = '/^[-+]? *[0-9]+ *(?:[-+*] *[0-9]+ *)*$/'; - var $_min = null; - var $_max = null; - - /** - * update changed setting with user provided value $input - * - if changed value fails error check, save it to $this->_input (to allow echoing later) - * - if changed value passes error check, set $this->_local to the new value - * - * @param mixed $input the new value - * @return boolean true if changed, false otherwise (also on error) - */ - function update($input) { - $local = $this->_local; - $valid = parent::update($input); - if ($valid && !(is_null($this->_min) && is_null($this->_max))) { - $numeric_local = (int) eval('return '.$this->_local.';'); - if ((!is_null($this->_min) && $numeric_local < $this->_min) || - (!is_null($this->_max) && $numeric_local > $this->_max)) { - $this->_error = true; - $this->_input = $input; - $this->_local = $local; - $valid = false; - } - } - return $valid; - } - - /** - * Generate string to save setting value to file according to $fmt - * - * @param string $var name of variable - * @param string $fmt save format - * @return string - */ - function out($var, $fmt='php') { - - if ($this->is_protected()) return ''; - if (is_null($this->_local) || ($this->_default == $this->_local)) return ''; - - $out = ''; - - if ($fmt=='php') { - $local = $this->_local === '' ? "''" : $this->_local; - $out .= '$'.$var."['".$this->_out_key()."'] = ".$local.";\n"; - } - - return $out; - } - } -} - -if (!class_exists('setting_numericopt')) { - /** - * Class setting_numericopt - */ - class setting_numericopt extends setting_numeric { - // just allow an empty config - var $_pattern = '/^(|[-]?[0-9]+(?:[-+*][0-9]+)*)$/'; - - - /** - * Empty string is valid for numericopt - * - * @param mixed $input - * - * @return bool - */ - function update($input) { - if ($input === '') { - return true; - } - - return parent::update($input); - } - } -} - -if (!class_exists('setting_onoff')) { - /** - * Class setting_onoff - */ - class setting_onoff extends setting_numeric { - /** - * Build html for label and input of setting - * - * @param admin_plugin_config $plugin object of config plugin - * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting - * @return string[] with content array(string $label_html, string $input_html) - */ - function html(admin_plugin_config $plugin, $echo = false) { - $disable = ''; - - if ($this->is_protected()) { - $value = $this->_protected; - $disable = ' disabled="disabled"'; - } else { - $value = is_null($this->_local) ? $this->_default : $this->_local; - } - - $key = htmlspecialchars($this->_key); - $checked = ($value) ? ' checked="checked"' : ''; - - $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>'; - $input = '<div class="input"><input id="config___'.$key.'" name="config['.$key.']" type="checkbox" class="checkbox" value="1"'.$checked.$disable.'/></div>'; - return array($label,$input); - } - - /** - * update changed setting with user provided value $input - * - if changed value fails error check, save it to $this->_input (to allow echoing later) - * - if changed value passes error check, set $this->_local to the new value - * - * @param mixed $input the new value - * @return boolean true if changed, false otherwise (also on error) - */ - function update($input) { - if ($this->is_protected()) return false; - - $input = ($input) ? 1 : 0; - $value = is_null($this->_local) ? $this->_default : $this->_local; - if ($value == $input) return false; - - $this->_local = $input; - return true; - } - } -} - -if (!class_exists('setting_multichoice')) { - /** - * Class setting_multichoice - */ - class setting_multichoice extends setting_string { - var $_choices = array(); - var $lang; //some custom language strings are stored in setting - - /** - * Build html for label and input of setting - * - * @param admin_plugin_config $plugin object of config plugin - * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting - * @return string[] with content array(string $label_html, string $input_html) - */ - function html(admin_plugin_config $plugin, $echo = false) { - $disable = ''; - $nochoice = ''; - - if ($this->is_protected()) { - $value = $this->_protected; - $disable = ' disabled="disabled"'; - } else { - $value = is_null($this->_local) ? $this->_default : $this->_local; - } - - // ensure current value is included - if (!in_array($value, $this->_choices)) { - $this->_choices[] = $value; - } - // disable if no other choices - if (!$this->is_protected() && count($this->_choices) <= 1) { - $disable = ' disabled="disabled"'; - $nochoice = $plugin->getLang('nochoice'); - } - - $key = htmlspecialchars($this->_key); - - $label = '<label for="config___'.$key.'">'.$this->prompt($plugin).'</label>'; - - $input = "<div class=\"input\">\n"; - $input .= '<select class="edit" id="config___'.$key.'" name="config['.$key.']"'.$disable.'>'."\n"; - foreach ($this->_choices as $choice) { - $selected = ($value == $choice) ? ' selected="selected"' : ''; - $option = $plugin->getLang($this->_key.'_o_'.$choice); - if (!$option && isset($this->lang[$this->_key.'_o_'.$choice])) $option = $this->lang[$this->_key.'_o_'.$choice]; - if (!$option) $option = $choice; - - $choice = htmlspecialchars($choice); - $option = htmlspecialchars($option); - $input .= ' <option value="'.$choice.'"'.$selected.' >'.$option.'</option>'."\n"; - } - $input .= "</select> $nochoice \n"; - $input .= "</div>\n"; - - return array($label,$input); - } - - /** - * update changed setting with user provided value $input - * - if changed value fails error check, save it to $this->_input (to allow echoing later) - * - if changed value passes error check, set $this->_local to the new value - * - * @param mixed $input the new value - * @return boolean true if changed, false otherwise (also on error) - */ - function update($input) { - if (is_null($input)) return false; - if ($this->is_protected()) return false; - - $value = is_null($this->_local) ? $this->_default : $this->_local; - if ($value == $input) return false; - - if (!in_array($input, $this->_choices)) return false; - - $this->_local = $input; - return true; - } - } -} - - -if (!class_exists('setting_dirchoice')) { - /** - * Class setting_dirchoice - */ - class setting_dirchoice extends setting_multichoice { - - var $_dir = ''; - - /** - * Receives current values for the setting $key - * - * @param mixed $default default setting value - * @param mixed $local local setting value - * @param mixed $protected protected setting value - */ - function initialize($default,$local,$protected) { - - // populate $this->_choices with a list of directories - $list = array(); - - if ($dh = @opendir($this->_dir)) { - while (false !== ($entry = readdir($dh))) { - if ($entry == '.' || $entry == '..') continue; - if ($this->_pattern && !preg_match($this->_pattern,$entry)) continue; - - $file = (is_link($this->_dir.$entry)) ? readlink($this->_dir.$entry) : $this->_dir.$entry; - if (is_dir($file)) $list[] = $entry; - } - closedir($dh); - } - sort($list); - $this->_choices = $list; - - parent::initialize($default,$local,$protected); - } - } -} - - -if (!class_exists('setting_hidden')) { - /** - * Class setting_hidden - */ - class setting_hidden extends setting { - // Used to explicitly ignore a setting in the configuration manager. - } -} - -if (!class_exists('setting_fieldset')) { - /** - * Class setting_fieldset - */ - class setting_fieldset extends setting { - // A do-nothing class used to detect the 'fieldset' type. - // Used to start a new settings "display-group". - } -} - -if (!class_exists('setting_undefined')) { - /** - * Class setting_undefined - */ - class setting_undefined extends setting_hidden { - // A do-nothing class used to detect settings with no metadata entry. - // Used internaly to hide undefined settings, and generate the undefined settings list. - } -} - -if (!class_exists('setting_no_class')) { - /** - * Class setting_no_class - */ - class setting_no_class extends setting_undefined { - // A do-nothing class used to detect settings with a missing setting class. - // Used internaly to hide undefined settings, and generate the undefined settings list. - } -} - -if (!class_exists('setting_no_default')) { - /** - * Class setting_no_default - */ - class setting_no_default extends setting_undefined { - // A do-nothing class used to detect settings with no default value. - // Used internaly to hide undefined settings, and generate the undefined settings list. - } -} - -if (!class_exists('setting_multicheckbox')) { - /** - * Class setting_multicheckbox - */ - class setting_multicheckbox extends setting_string { - - var $_choices = array(); - var $_combine = array(); - var $_other = 'always'; - - /** - * update changed setting with user provided value $input - * - if changed value fails error check, save it to $this->_input (to allow echoing later) - * - if changed value passes error check, set $this->_local to the new value - * - * @param mixed $input the new value - * @return boolean true if changed, false otherwise (also on error) - */ - function update($input) { - if ($this->is_protected()) return false; - - // split any combined values + convert from array to comma separated string - $input = ($input) ? $input : array(); - $input = $this->_array2str($input); - - $value = is_null($this->_local) ? $this->_default : $this->_local; - if ($value == $input) return false; - - if ($this->_pattern && !preg_match($this->_pattern,$input)) { - $this->_error = true; - $this->_input = $input; - return false; - } - - $this->_local = $input; - return true; - } - - /** - * Build html for label and input of setting - * - * @param admin_plugin_config $plugin object of config plugin - * @param bool $echo true: show input value, when error occurred, otherwise the stored setting - * @return string[] with content array(string $label_html, string $input_html) - */ - function html(admin_plugin_config $plugin, $echo=false) { - - $disable = ''; - - if ($this->is_protected()) { - $value = $this->_protected; - $disable = 'disabled="disabled"'; - } else { - if ($echo && $this->_error) { - $value = $this->_input; - } else { - $value = is_null($this->_local) ? $this->_default : $this->_local; - } - } - - $key = htmlspecialchars($this->_key); - - // convert from comma separated list into array + combine complimentary actions - $value = $this->_str2array($value); - $default = $this->_str2array($this->_default); - - $input = ''; - foreach ($this->_choices as $choice) { - $idx = array_search($choice, $value); - $idx_default = array_search($choice,$default); - - $checked = ($idx !== false) ? 'checked="checked"' : ''; - - // @todo ideally this would be handled using a second class of "default" - $class = (($idx !== false) == (false !== $idx_default)) ? " selectiondefault" : ""; - - $prompt = ($plugin->getLang($this->_key.'_'.$choice) ? - $plugin->getLang($this->_key.'_'.$choice) : htmlspecialchars($choice)); - - $input .= '<div class="selection'.$class.'">'."\n"; - $input .= '<label for="config___'.$key.'_'.$choice.'">'.$prompt."</label>\n"; - $input .= '<input id="config___'.$key.'_'.$choice.'" name="config['.$key.'][]" type="checkbox" class="checkbox" value="'.$choice.'" '.$disable.' '.$checked."/>\n"; - $input .= "</div>\n"; - - // remove this action from the disabledactions array - if ($idx !== false) unset($value[$idx]); - if ($idx_default !== false) unset($default[$idx_default]); - } - - // handle any remaining values - if ($this->_other != 'never'){ - $other = join(',',$value); - // test equivalent to ($this->_other == 'always' || ($other && $this->_other == 'exists') - // use != 'exists' rather than == 'always' to ensure invalid values default to 'always' - if ($this->_other != 'exists' || $other) { - - $class = ((count($default) == count($value)) && (count($value) == count(array_intersect($value,$default)))) ? - " selectiondefault" : ""; - - $input .= '<div class="other'.$class.'">'."\n"; - $input .= '<label for="config___'.$key.'_other">'.$plugin->getLang($key.'_other')."</label>\n"; - $input .= '<input id="config___'.$key.'_other" name="config['.$key.'][other]" type="text" class="edit" value="'.htmlspecialchars($other).'" '.$disable." />\n"; - $input .= "</div>\n"; - } - } - $label = '<label>'.$this->prompt($plugin).'</label>'; - return array($label,$input); - } - - /** - * convert comma separated list to an array and combine any complimentary values - * - * @param string $str - * @return array - */ - function _str2array($str) { - $array = explode(',',$str); - - if (!empty($this->_combine)) { - foreach ($this->_combine as $key => $combinators) { - $idx = array(); - foreach ($combinators as $val) { - if (($idx[] = array_search($val, $array)) === false) break; - } - - if (count($idx) && $idx[count($idx)-1] !== false) { - foreach ($idx as $i) unset($array[$i]); - $array[] = $key; - } - } - } - - return $array; - } - - /** - * convert array of values + other back to a comma separated list, incl. splitting any combined values - * - * @param array $input - * @return string - */ - function _array2str($input) { - - // handle other - $other = trim($input['other']); - $other = !empty($other) ? explode(',',str_replace(' ','',$input['other'])) : array(); - unset($input['other']); - - $array = array_unique(array_merge($input, $other)); - - // deconstruct any combinations - if (!empty($this->_combine)) { - foreach ($this->_combine as $key => $combinators) { - - $idx = array_search($key,$array); - if ($idx !== false) { - unset($array[$idx]); - $array = array_merge($array, $combinators); - } - } - } - - return join(',',array_unique($array)); - } - } -} - -if (!class_exists('setting_regex')){ - /** - * Class setting_regex - */ - class setting_regex extends setting_string { - - var $_delimiter = '/'; // regex delimiter to be used in testing input - var $_pregflags = 'ui'; // regex pattern modifiers to be used in testing input - - /** - * update changed setting with user provided value $input - * - if changed value fails error check, save it to $this->_input (to allow echoing later) - * - if changed value passes error check, set $this->_local to the new value - * - * @param mixed $input the new value - * @return boolean true if changed, false otherwise (incl. on error) - */ - function update($input) { - - // let parent do basic checks, value, not changed, etc. - $local = $this->_local; - if (!parent::update($input)) return false; - $this->_local = $local; - - // see if the regex compiles and runs (we don't check for effectiveness) - $regex = $this->_delimiter . $input . $this->_delimiter . $this->_pregflags; - $lastError = error_get_last(); - @preg_match($regex,'testdata'); - if (preg_last_error() != PREG_NO_ERROR || error_get_last() != $lastError) { - $this->_input = $input; - $this->_error = true; - return false; - } - - $this->_local = $input; - return true; - } - } -} diff --git a/lib/plugins/config/settings/config.metadata.php b/lib/plugins/config/settings/config.metadata.php index 1d7eea9a5..9a9b6d901 100644 --- a/lib/plugins/config/settings/config.metadata.php +++ b/lib/plugins/config/settings/config.metadata.php @@ -2,16 +2,15 @@ /** * Metadata for configuration manager plugin * - * Note: This file should be included within a function to ensure it - * doesn't clash with the settings it is describing. + * Note: This file is loaded in Loader::loadMeta(). * * Format: * $meta[<setting name>] = array(<handler class id>,<param name> => <param value>); * * <handler class id> is the handler class name without the "setting_" prefix * - * Defined classes: - * Generic (source: settings/config.class.php) + * Defined classes (see core/Setting/*): + * Generic * ------------------------------------------- * '' - default class ('setting'), textarea, minimal input validation, setting output in quotes * 'string' - single line text input, minimal input validation, setting output in quotes @@ -38,7 +37,7 @@ * to see if will compile & run as a regex. in addition to _pattern, also accepts _delimiter * (default '/') and _pregflags (default 'ui') * - * Single Setting (source: settings/extra.class.php) + * Single Setting * ------------------------------------------------- * 'savedir' - as 'setting', input tested against initpath() (inc/init.php) * 'sepchar' - as multichoice, selection constructed from string of valid values @@ -76,26 +75,10 @@ * 'never' as it will not discard unknown/other values. * optional for 'multicheckbox', ignored by others * + * The order of the settings influences the order in which they apppear in the config manager * * @author Chris Smith <chris@jalakai.co.uk> */ -// ---------------[ settings for settings ]------------------------------ -$config['format'] = 'php'; // format of setting files, supported formats: php -$config['varname'] = 'conf'; // name of the config variable, sans $ - -// this string is written at the top of the rewritten settings file, -// !! do not include any comment indicators !! -// this value can be overriden when calling save_settings() method -$config['heading'] = 'Dokuwiki\'s Main Configuration File - Local Settings'; - -// test value (FIXME, remove before publishing) -//$meta['test'] = array('multichoice','_choices' => array('')); - -// --------------[ setting metadata ]------------------------------------ -// - for description of format and fields see top of file -// - order the settings in the order you wish them to appear -// - any settings not mentioned will come after the last setting listed and -// will use the default class with no parameters $meta['_basic'] = array('fieldset'); $meta['title'] = array('string'); @@ -122,7 +105,10 @@ $meta['fullpath'] = array('onoff','_caution' => 'security'); $meta['typography'] = array('multichoice','_choices' => array(0,1,2)); $meta['dformat'] = array('string'); $meta['signature'] = array('string'); -$meta['showuseras'] = array('multichoice','_choices' => array('loginname','username','username_link','email','email_link')); +$meta['showuseras'] = array( + 'multichoice', + '_choices' => array('loginname', 'username', 'username_link', 'email', 'email_link') +); $meta['toptoclevel'] = array('multichoice','_choices' => array(1,2,3,4,5)); // 5 toc levels $meta['tocminheads'] = array('multichoice','_choices' => array(0,1,2,3,4,5,10,15,20)); $meta['maxtoclevel'] = array('multichoice','_choices' => array(0,1,2,3,4,5)); @@ -146,9 +132,29 @@ $meta['superuser'] = array('string','_caution' => 'danger'); $meta['manager'] = array('string'); $meta['profileconfirm'] = array('onoff'); $meta['rememberme'] = array('onoff'); -$meta['disableactions'] = array('disableactions', - '_choices' => array('backlink','index','recent','revisions','search','subscription','register','resendpwd','profile','profile_delete','edit','wikicode','check', 'rss'), - '_combine' => array('subscription' => array('subscribe','unsubscribe'), 'wikicode' => array('source','export_raw'))); +$meta['disableactions'] = array( + 'disableactions', + '_choices' => array( + 'backlink', + 'index', + 'recent', + 'revisions', + 'search', + 'subscription', + 'register', + 'resendpwd', + 'profile', + 'profile_delete', + 'edit', + 'wikicode', + 'check', + 'rss' + ), + '_combine' => array( + 'subscription' => array('subscribe', 'unsubscribe'), + 'wikicode' => array('source', 'export_raw') + ) +); $meta['auth_security_timeout'] = array('numeric'); $meta['securecookie'] = array('onoff'); $meta['remote'] = array('onoff','_caution' => 'security'); diff --git a/lib/plugins/config/settings/extra.class.php b/lib/plugins/config/settings/extra.class.php deleted file mode 100644 index 41af42247..000000000 --- a/lib/plugins/config/settings/extra.class.php +++ /dev/null @@ -1,309 +0,0 @@ -<?php -/** - * additional setting classes specific to these settings - * - * @author Chris Smith <chris@jalakai.co.uk> - */ - -if (!class_exists('setting_sepchar')) { - /** - * Class setting_sepchar - */ - class setting_sepchar extends setting_multichoice { - - /** - * @param string $key - * @param array|null $param array with metadata of setting - */ - function __construct($key,$param=null) { - $str = '_-.'; - for ($i=0;$i<strlen($str);$i++) $this->_choices[] = $str{$i}; - - // call foundation class constructor - parent::__construct($key,$param); - } - } -} - -if (!class_exists('setting_savedir')) { - /** - * Class setting_savedir - */ - class setting_savedir extends setting_string { - - /** - * update changed setting with user provided value $input - * - if changed value fails error check, save it to $this->_input (to allow echoing later) - * - if changed value passes error check, set $this->_local to the new value - * - * @param mixed $input the new value - * @return boolean true if changed, false otherwise (also on error) - */ - function update($input) { - if ($this->is_protected()) return false; - - $value = is_null($this->_local) ? $this->_default : $this->_local; - if ($value == $input) return false; - - if (!init_path($input)) { - $this->_error = true; - $this->_input = $input; - return false; - } - - $this->_local = $input; - return true; - } - } -} - -if (!class_exists('setting_authtype')) { - /** - * Class setting_authtype - */ - class setting_authtype extends setting_multichoice { - - /** - * Receives current values for the setting $key - * - * @param mixed $default default setting value - * @param mixed $local local setting value - * @param mixed $protected protected setting value - */ - function initialize($default,$local,$protected) { - /** @var $plugin_controller Doku_Plugin_Controller */ - global $plugin_controller; - - // retrieve auth types provided by plugins - foreach ($plugin_controller->getList('auth') as $plugin) { - $this->_choices[] = $plugin; - } - - parent::initialize($default,$local,$protected); - } - - /** - * update changed setting with user provided value $input - * - if changed value fails error check, save it to $this->_input (to allow echoing later) - * - if changed value passes error check, set $this->_local to the new value - * - * @param mixed $input the new value - * @return boolean true if changed, false otherwise (also on error) - */ - function update($input) { - /** @var $plugin_controller Doku_Plugin_Controller */ - global $plugin_controller; - - // is an update possible/requested? - $local = $this->_local; // save this, parent::update() may change it - if (!parent::update($input)) return false; // nothing changed or an error caught by parent - $this->_local = $local; // restore original, more error checking to come - - // attempt to load the plugin - $auth_plugin = $plugin_controller->load('auth', $input); - - // @TODO: throw an error in plugin controller instead of returning null - if (is_null($auth_plugin)) { - $this->_error = true; - msg('Cannot load Auth Plugin "' . $input . '"', -1); - return false; - } - - // verify proper instantiation (is this really a plugin?) @TODO use instanceof? implement interface? - if (is_object($auth_plugin) && !method_exists($auth_plugin, 'getPluginName')) { - $this->_error = true; - msg('Cannot create Auth Plugin "' . $input . '"', -1); - return false; - } - - // did we change the auth type? logout - global $conf; - if($conf['authtype'] != $input) { - msg('Authentication system changed. Please re-login.'); - auth_logoff(); - } - - $this->_local = $input; - return true; - } - } -} - -if (!class_exists('setting_im_convert')) { - /** - * Class setting_im_convert - */ - class setting_im_convert extends setting_string { - - /** - * update changed setting with user provided value $input - * - if changed value fails error check, save it to $this->_input (to allow echoing later) - * - if changed value passes error check, set $this->_local to the new value - * - * @param mixed $input the new value - * @return boolean true if changed, false otherwise (also on error) - */ - function update($input) { - if ($this->is_protected()) return false; - - $input = trim($input); - - $value = is_null($this->_local) ? $this->_default : $this->_local; - if ($value == $input) return false; - - if ($input && !file_exists($input)) { - $this->_error = true; - $this->_input = $input; - return false; - } - - $this->_local = $input; - return true; - } - } -} - -if (!class_exists('setting_disableactions')) { - /** - * Class setting_disableactions - */ - class setting_disableactions extends setting_multicheckbox { - - /** - * Build html for label and input of setting - * - * @param admin_plugin_config $plugin object of config plugin - * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting - * @return array with content array(string $label_html, string $input_html) - */ - function html(admin_plugin_config $plugin, $echo=false) { - global $lang; - - // make some language adjustments (there must be a better way) - // transfer some DokuWiki language strings to the plugin - $plugin->addLang($this->_key.'_revisions', $lang['btn_revs']); - foreach ($this->_choices as $choice) { - if (isset($lang['btn_'.$choice])) $plugin->addLang($this->_key.'_'.$choice, $lang['btn_'.$choice]); - } - - return parent::html($plugin, $echo); - } - } -} - -if (!class_exists('setting_compression')) { - /** - * Class setting_compression - */ - class setting_compression extends setting_multichoice { - - var $_choices = array('0'); // 0 = no compression, always supported - - /** - * Receives current values for the setting $key - * - * @param mixed $default default setting value - * @param mixed $local local setting value - * @param mixed $protected protected setting value - */ - function initialize($default,$local,$protected) { - - // populate _choices with the compression methods supported by this php installation - if (function_exists('gzopen')) $this->_choices[] = 'gz'; - if (function_exists('bzopen')) $this->_choices[] = 'bz2'; - - parent::initialize($default,$local,$protected); - } - } -} - -if (!class_exists('setting_license')) { - /** - * Class setting_license - */ - class setting_license extends setting_multichoice { - - var $_choices = array(''); // none choosen - - /** - * Receives current values for the setting $key - * - * @param mixed $default default setting value - * @param mixed $local local setting value - * @param mixed $protected protected setting value - */ - function initialize($default,$local,$protected) { - global $license; - - foreach($license as $key => $data){ - $this->_choices[] = $key; - $this->lang[$this->_key.'_o_'.$key] = $data['name']; // stored in setting - } - - parent::initialize($default,$local,$protected); - } - } -} - - -if (!class_exists('setting_renderer')) { - /** - * Class setting_renderer - */ - class setting_renderer extends setting_multichoice { - var $_prompts = array(); - var $_format = null; - - /** - * Receives current values for the setting $key - * - * @param mixed $default default setting value - * @param mixed $local local setting value - * @param mixed $protected protected setting value - */ - function initialize($default,$local,$protected) { - $format = $this->_format; - - foreach (plugin_list('renderer') as $plugin) { - $renderer = plugin_load('renderer',$plugin); - if (method_exists($renderer,'canRender') && $renderer->canRender($format)) { - $this->_choices[] = $plugin; - - $info = $renderer->getInfo(); - $this->_prompts[$plugin] = $info['name']; - } - } - - parent::initialize($default,$local,$protected); - } - - /** - * Build html for label and input of setting - * - * @param admin_plugin_config $plugin object of config plugin - * @param bool $echo true: show inputted value, when error occurred, otherwise the stored setting - * @return array with content array(string $label_html, string $input_html) - */ - function html(admin_plugin_config $plugin, $echo=false) { - - // make some language adjustments (there must be a better way) - // transfer some plugin names to the config plugin - foreach($this->_choices as $choice) { - if(!$plugin->getLang($this->_key . '_o_' . $choice)) { - if(!isset($this->_prompts[$choice])) { - $plugin->addLang( - $this->_key . '_o_' . $choice, - sprintf($plugin->getLang('renderer__core'), $choice) - ); - } else { - $plugin->addLang( - $this->_key . '_o_' . $choice, - sprintf($plugin->getLang('renderer__plugin'), $this->_prompts[$choice]) - ); - } - } - } - return parent::html($plugin, $echo); - } - } -} diff --git a/lib/plugins/extension/_test/extension.test.php b/lib/plugins/extension/_test/extension.test.php index d4f13201d..1f8e2fca7 100644 --- a/lib/plugins/extension/_test/extension.test.php +++ b/lib/plugins/extension/_test/extension.test.php @@ -6,8 +6,8 @@ * makes protected methods accessible */ class mock_helper_plugin_extension_extension extends helper_plugin_extension_extension { - public function find_folders(&$result, $base, $default_type = 'plugin', $dir = '') { - return parent::find_folders($result, $base, $default_type, $dir); + public function findFolders(&$result, $base, $default_type = 'plugin', $dir = '') { + return parent::findFolders($result, $base, $default_type, $dir); } } @@ -78,7 +78,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $tdir = dirname(__FILE__).'/testdata'; $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/plugin1", 'plugin'); + $ok = $extension->findFolders($result, "$tdir/plugin1", 'plugin'); $this->assertTrue($ok); $this->assertEquals(0, count($result['new'])); $this->assertEquals(1, count($result['old'])); @@ -86,7 +86,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('plugin1', $this->extdir($result['old'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/plugin2", 'plugin'); + $ok = $extension->findFolders($result, "$tdir/plugin2", 'plugin'); $this->assertTrue($ok); $this->assertEquals(1, count($result['new'])); $this->assertEquals('plugin', $result['new'][0]['type']); @@ -94,7 +94,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('plugin2', $this->extdir($result['new'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/plgsub3", 'plugin'); + $ok = $extension->findFolders($result, "$tdir/plgsub3", 'plugin'); $this->assertTrue($ok); $this->assertEquals(0, count($result['new'])); $this->assertEquals(1, count($result['old'])); @@ -102,7 +102,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('plgsub3/plugin3', $this->extdir($result['old'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/plgsub4", 'plugin'); + $ok = $extension->findFolders($result, "$tdir/plgsub4", 'plugin'); $this->assertTrue($ok); $this->assertEquals(1, count($result['new'])); $this->assertEquals('plugin', $result['new'][0]['type']); @@ -110,7 +110,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('plgsub4/plugin4', $this->extdir($result['new'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/plgfoo5", 'plugin'); + $ok = $extension->findFolders($result, "$tdir/plgfoo5", 'plugin'); $this->assertTrue($ok); $this->assertEquals(1, count($result['new'])); $this->assertEquals('plugin', $result['new'][0]['type']); @@ -118,7 +118,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('plgfoo5', $this->extdir($result['new'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/plgsub6/plgfoo6", 'plugin'); + $ok = $extension->findFolders($result, "$tdir/plgsub6/plgfoo6", 'plugin'); $this->assertTrue($ok); $this->assertEquals(1, count($result['new'])); $this->assertEquals('plugin', $result['new'][0]['type']); @@ -126,7 +126,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('plgsub6/plgfoo6', $this->extdir($result['new'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/either1", 'plugin'); + $ok = $extension->findFolders($result, "$tdir/either1", 'plugin'); $this->assertTrue($ok); $this->assertEquals(0, count($result['new'])); $this->assertEquals(1, count($result['old'])); @@ -134,7 +134,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('either1', $this->extdir($result['old'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/eithersub2/either2", 'plugin'); + $ok = $extension->findFolders($result, "$tdir/eithersub2/either2", 'plugin'); $this->assertTrue($ok); $this->assertEquals(0, count($result['new'])); $this->assertEquals(1, count($result['old'])); @@ -147,7 +147,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $tdir = dirname(__FILE__).'/testdata'; $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/template1", 'template'); + $ok = $extension->findFolders($result, "$tdir/template1", 'template'); $this->assertTrue($ok); $this->assertEquals(0, count($result['new'])); $this->assertEquals(1, count($result['old'])); @@ -155,7 +155,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('template1', $this->extdir($result['old'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/template2", 'template'); + $ok = $extension->findFolders($result, "$tdir/template2", 'template'); $this->assertTrue($ok); $this->assertEquals(1, count($result['new'])); $this->assertEquals('template', $result['new'][0]['type']); @@ -163,7 +163,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('template2', $this->extdir($result['new'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/tplsub3", 'template'); + $ok = $extension->findFolders($result, "$tdir/tplsub3", 'template'); $this->assertTrue($ok); $this->assertEquals(0, count($result['new'])); $this->assertEquals(1, count($result['old'])); @@ -171,7 +171,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('tplsub3/template3', $this->extdir($result['old'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/tplsub4", 'template'); + $ok = $extension->findFolders($result, "$tdir/tplsub4", 'template'); $this->assertTrue($ok); $this->assertEquals(1, count($result['new'])); $this->assertEquals('template', $result['new'][0]['type']); @@ -179,7 +179,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('tplsub4/template4', $this->extdir($result['new'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/tplfoo5", 'template'); + $ok = $extension->findFolders($result, "$tdir/tplfoo5", 'template'); $this->assertTrue($ok); $this->assertEquals(1, count($result['new'])); $this->assertEquals('template', $result['new'][0]['type']); @@ -187,7 +187,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('tplfoo5', $this->extdir($result['new'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/tplsub6/tplfoo6", 'template'); + $ok = $extension->findFolders($result, "$tdir/tplsub6/tplfoo6", 'template'); $this->assertTrue($ok); $this->assertEquals(1, count($result['new'])); $this->assertEquals('template', $result['new'][0]['type']); @@ -195,7 +195,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('tplsub6/tplfoo6', $this->extdir($result['new'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/either1", 'template'); + $ok = $extension->findFolders($result, "$tdir/either1", 'template'); $this->assertTrue($ok); $this->assertEquals(0, count($result['new'])); $this->assertEquals(1, count($result['old'])); @@ -203,7 +203,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('either1', $this->extdir($result['old'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/eithersub2/either2", 'template'); + $ok = $extension->findFolders($result, "$tdir/eithersub2/either2", 'template'); $this->assertTrue($ok); $this->assertEquals(0, count($result['new'])); $this->assertEquals(1, count($result['old'])); @@ -216,7 +216,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $tdir = dirname(__FILE__).'/testdata'; $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/template1"); + $ok = $extension->findFolders($result, "$tdir/template1"); $this->assertTrue($ok); $this->assertEquals(0, count($result['new'])); $this->assertEquals(1, count($result['old'])); @@ -224,7 +224,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('template1', $this->extdir($result['old'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/template2"); + $ok = $extension->findFolders($result, "$tdir/template2"); $this->assertTrue($ok); $this->assertEquals(1, count($result['new'])); $this->assertEquals('template', $result['new'][0]['type']); @@ -232,7 +232,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('template2', $this->extdir($result['new'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/tplsub3"); + $ok = $extension->findFolders($result, "$tdir/tplsub3"); $this->assertTrue($ok); $this->assertEquals(0, count($result['new'])); $this->assertEquals(1, count($result['old'])); @@ -240,7 +240,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('tplsub3/template3', $this->extdir($result['old'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/tplsub4"); + $ok = $extension->findFolders($result, "$tdir/tplsub4"); $this->assertTrue($ok); $this->assertEquals(1, count($result['new'])); $this->assertEquals('template', $result['new'][0]['type']); @@ -248,7 +248,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('tplsub4/template4', $this->extdir($result['new'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/tplfoo5"); + $ok = $extension->findFolders($result, "$tdir/tplfoo5"); $this->assertTrue($ok); $this->assertEquals(1, count($result['new'])); $this->assertEquals('template', $result['new'][0]['type']); @@ -256,7 +256,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('tplfoo5', $this->extdir($result['new'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/tplsub6/tplfoo6"); + $ok = $extension->findFolders($result, "$tdir/tplsub6/tplfoo6"); $this->assertTrue($ok); $this->assertEquals(1, count($result['new'])); $this->assertEquals('template', $result['new'][0]['type']); @@ -264,7 +264,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('tplsub6/tplfoo6', $this->extdir($result['new'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/either1"); + $ok = $extension->findFolders($result, "$tdir/either1"); $this->assertTrue($ok); $this->assertEquals(0, count($result['new'])); $this->assertEquals(1, count($result['old'])); @@ -272,7 +272,7 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $this->assertEquals('either1', $this->extdir($result['old'][0]['tmp'])); $result = array('old' => array(), 'new' => array()); - $ok = $extension->find_folders($result, "$tdir/eithersub2/either2"); + $ok = $extension->findFolders($result, "$tdir/eithersub2/either2"); $this->assertTrue($ok); $this->assertEquals(0, count($result['new'])); $this->assertEquals(1, count($result['old'])); @@ -292,4 +292,4 @@ class helper_plugin_extension_extension_test extends DokuWikiTest { $dir = trim(substr($dir, $len), '/'); return $dir; } -}
\ No newline at end of file +} diff --git a/lib/plugins/extension/action.php b/lib/plugins/extension/action.php index 4af84f8df..3255f24b0 100644 --- a/lib/plugins/extension/action.php +++ b/lib/plugins/extension/action.php @@ -5,10 +5,8 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - -class action_plugin_extension extends DokuWiki_Action_Plugin { +class action_plugin_extension extends DokuWiki_Action_Plugin +{ /** * Registers a callback function for a given event @@ -16,10 +14,10 @@ class action_plugin_extension extends DokuWiki_Action_Plugin { * @param Doku_Event_Handler $controller DokuWiki's event controller object * @return void */ - public function register(Doku_Event_Handler $controller) { + public function register(Doku_Event_Handler $controller) + { $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'info'); - } /** @@ -28,24 +26,25 @@ class action_plugin_extension extends DokuWiki_Action_Plugin { * @param Doku_Event $event * @param $param */ - public function info(Doku_Event &$event, $param) { + public function info(Doku_Event &$event, $param) + { global $USERINFO; global $INPUT; - if($event->data != 'plugin_extension') return; + if ($event->data != 'plugin_extension') return; $event->preventDefault(); $event->stopPropagation(); /** @var admin_plugin_extension $admin */ $admin = plugin_load('admin', 'extension'); - if(!$admin->isAccessibleByCurrentUser()) { + if (!$admin->isAccessibleByCurrentUser()) { http_status(403); echo 'Forbidden'; exit; } $ext = $INPUT->str('ext'); - if(!$ext) { + if (!$ext) { http_status(400); echo 'no extension given'; return; @@ -56,10 +55,9 @@ class action_plugin_extension extends DokuWiki_Action_Plugin { $extension->setExtension($ext); $act = $INPUT->str('act'); - switch($act) { + switch ($act) { case 'enable': case 'disable': - $json = new JSON(); $extension->$act(); //enables/disables $reverse = ($act == 'disable') ? 'enable' : 'disable'; @@ -71,7 +69,7 @@ class action_plugin_extension extends DokuWiki_Action_Plugin { ); header('Content-Type: application/json'); - echo $json->encode($return); + json_encode($return); break; case 'info': @@ -79,9 +77,7 @@ class action_plugin_extension extends DokuWiki_Action_Plugin { /** @var helper_plugin_extension_list $list */ $list = plugin_load('helper', 'extension_list'); header('Content-Type: text/html; charset=utf-8'); - echo $list->make_info($extension); + echo $list->makeInfo($extension); } } - } - diff --git a/lib/plugins/extension/admin.php b/lib/plugins/extension/admin.php index 1f1d4b513..421b7138f 100644 --- a/lib/plugins/extension/admin.php +++ b/lib/plugins/extension/admin.php @@ -6,13 +6,11 @@ * @author Michael Hamann <michael@content-space.de> */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - /** * Admin part of the extension manager */ -class admin_plugin_extension extends DokuWiki_Admin_Plugin { +class admin_plugin_extension extends DokuWiki_Admin_Plugin +{ protected $infoFor = null; /** @var helper_plugin_extension_gui */ protected $gui; @@ -22,28 +20,32 @@ class admin_plugin_extension extends DokuWiki_Admin_Plugin { * * loads additional helpers */ - public function __construct() { + public function __construct() + { $this->gui = plugin_load('helper', 'extension_gui'); } /** * @return int sort number in admin menu */ - public function getMenuSort() { + public function getMenuSort() + { return 0; } /** * @return bool true if only access for superuser, false is for superusers and moderators */ - public function forAdminOnly() { + public function forAdminOnly() + { return true; } /** * Execute the requested action(s) and initialize the plugin repository */ - public function handle() { + public function handle() + { global $INPUT; // initialize the remote repository /* @var helper_plugin_extension_repository $repository */ @@ -54,7 +56,7 @@ class admin_plugin_extension extends DokuWiki_Admin_Plugin { msg($this->getLang('repo_error').' [<a href="'.$url.'">'.$this->getLang('repo_retry').'</a>]', -1); } - if(!in_array('ssl', stream_get_transports())) { + if (!in_array('ssl', stream_get_transports())) { msg($this->getLang('nossl'), -1); } @@ -62,42 +64,60 @@ class admin_plugin_extension extends DokuWiki_Admin_Plugin { $extension = $this->loadHelper('extension_extension'); try { - if($INPUT->post->has('fn') && checkSecurityToken()) { + if ($INPUT->post->has('fn') && checkSecurityToken()) { $actions = $INPUT->post->arr('fn'); - foreach($actions as $action => $extensions) { - foreach($extensions as $extname => $label) { - switch($action) { + foreach ($actions as $action => $extensions) { + foreach ($extensions as $extname => $label) { + switch ($action) { case 'install': case 'reinstall': case 'update': $extension->setExtension($extname); $installed = $extension->installOrUpdate(); - foreach($installed as $ext => $info) { - msg(sprintf($this->getLang('msg_'.$info['type'].'_'.$info['action'].'_success'), $info['base']), 1); + foreach ($installed as $ext => $info) { + msg( + sprintf( + $this->getLang('msg_' . $info['type'] . '_' . $info['action'] . '_success'), + $info['base'] + ), + 1 + ); } break; case 'uninstall': $extension->setExtension($extname); $status = $extension->uninstall(); - if($status) { - msg(sprintf($this->getLang('msg_delete_success'), hsc($extension->getDisplayName())), 1); + if ($status) { + msg( + sprintf( + $this->getLang('msg_delete_success'), + hsc($extension->getDisplayName()) + ), + 1 + ); } else { - msg(sprintf($this->getLang('msg_delete_failed'), hsc($extension->getDisplayName())), -1); + msg( + sprintf( + $this->getLang('msg_delete_failed'), + hsc($extension->getDisplayName()) + ), + -1 + ); } break; - case 'enable'; + case 'enable': $extension->setExtension($extname); $status = $extension->enable(); - if($status !== true) { + if ($status !== true) { msg($status, -1); } else { msg(sprintf($this->getLang('msg_enabled'), hsc($extension->getDisplayName())), 1); } break; - case 'disable'; + case 'disable': $extension->setExtension($extname); $status = $extension->disable(); - if($status !== true) { + if ($status !== true) { msg($status, -1); } else { msg(sprintf($this->getLang('msg_disabled'), hsc($extension->getDisplayName())), 1); @@ -107,37 +127,36 @@ class admin_plugin_extension extends DokuWiki_Admin_Plugin { } } send_redirect($this->gui->tabURL('', array(), '&', true)); - } elseif($INPUT->post->str('installurl') && checkSecurityToken()) { + } elseif ($INPUT->post->str('installurl') && checkSecurityToken()) { $installed = $extension->installFromURL($INPUT->post->str('installurl')); - foreach($installed as $ext => $info) { + foreach ($installed as $ext => $info) { msg(sprintf($this->getLang('msg_'.$info['type'].'_'.$info['action'].'_success'), $info['base']), 1); } send_redirect($this->gui->tabURL('', array(), '&', true)); - } elseif(isset($_FILES['installfile']) && checkSecurityToken()) { + } elseif (isset($_FILES['installfile']) && checkSecurityToken()) { $installed = $extension->installFromUpload('installfile'); - foreach($installed as $ext => $info) { + foreach ($installed as $ext => $info) { msg(sprintf($this->getLang('msg_'.$info['type'].'_'.$info['action'].'_success'), $info['base']), 1); } send_redirect($this->gui->tabURL('', array(), '&', true)); } - - } catch(Exception $e) { + } catch (Exception $e) { msg($e->getMessage(), -1); send_redirect($this->gui->tabURL('', array(), '&', true)); } - } /** * Render HTML output */ - public function html() { + public function html() + { ptln('<h1>'.$this->getLang('menu').'</h1>'); ptln('<div id="extension__manager">'); $this->gui->tabNavigation(); - switch($this->gui->currentTab()) { + switch ($this->gui->currentTab()) { case 'search': $this->gui->tabSearch(); break; diff --git a/lib/plugins/extension/helper/extension.php b/lib/plugins/extension/helper/extension.php index 7266ed8e6..0186620d0 100644 --- a/lib/plugins/extension/helper/extension.php +++ b/lib/plugins/extension/helper/extension.php @@ -6,14 +6,14 @@ * @author Michael Hamann <michael@content-space.de> */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); -if(!defined('DOKU_TPLLIB')) define('DOKU_TPLLIB', DOKU_INC.'lib/tpl/'); +use dokuwiki\HTTP\DokuHTTPClient; +use dokuwiki\Extension\PluginController; /** * Class helper_plugin_extension_extension represents a single extension (plugin or template) */ -class helper_plugin_extension_extension extends DokuWiki_Plugin { +class helper_plugin_extension_extension extends DokuWiki_Plugin +{ private $id; private $base; private $is_template = false; @@ -26,13 +26,25 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { /** @var array list of temporary directories */ private $temporary = array(); + /** @var string where templates are installed to */ + private $tpllib = ''; + + /** + * helper_plugin_extension_extension constructor. + */ + public function __construct() + { + $this->tpllib = dirname(tpl_incdir()).'/'; + } + /** * Destructor * * deletes any dangling temporary directories */ - public function __destruct() { - foreach($this->temporary as $dir){ + public function __destruct() + { + foreach ($this->temporary as $dir) { io_rmdir($dir, true); } } @@ -40,7 +52,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { /** * @return bool false, this component is not a singleton */ - public function isSingleton() { + public function isSingleton() + { return false; } @@ -50,12 +63,13 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * @param string $id The id of the extension (prefixed with template: for templates) * @return bool If some (local or remote) data was found */ - public function setExtension($id) { + public function setExtension($id) + { $id = cleanID($id); $this->id = $id; $this->base = $id; - if(substr($id, 0 , 9) == 'template:'){ + if (substr($id, 0, 9) == 'template:') { $this->base = substr($id, 9); $this->is_template = true; } else { @@ -85,7 +99,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return bool If the extension is installed locally */ - public function isInstalled() { + public function isInstalled() + { return is_dir($this->getInstallDir()); } @@ -94,8 +109,9 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return bool */ - public function isGitControlled() { - if(!$this->isInstalled()) return false; + public function isGitControlled() + { + if (!$this->isInstalled()) return false; return is_dir($this->getInstallDir().'/.git'); } @@ -104,12 +120,16 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return bool If the extension is bundled */ - public function isBundled() { + public function isBundled() + { if (!empty($this->remoteInfo['bundled'])) return $this->remoteInfo['bundled']; - return in_array($this->id, - array( - 'authad', 'authldap', 'authmysql', 'authpdo', 'authpgsql', 'authplain', 'acl', 'info', 'extension', - 'revert', 'popularity', 'config', 'safefnrecode', 'styling', 'testing', 'template:dokuwiki' + return in_array( + $this->id, + array( + 'authad', 'authldap', 'authmysql', 'authpdo', + 'authpgsql', 'authplain', 'acl', 'info', 'extension', + 'revert', 'popularity', 'config', 'safefnrecode', 'styling', + 'testing', 'template:dokuwiki' ) ); } @@ -119,12 +139,13 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return bool if the extension is protected */ - public function isProtected() { + public function isProtected() + { // never allow deinstalling the current auth plugin: global $conf; if ($this->id == $conf['authtype']) return true; - /** @var Doku_Plugin_Controller $plugin_controller */ + /** @var PluginController $plugin_controller */ global $plugin_controller; $cascade = $plugin_controller->getCascade(); return (isset($cascade['protected'][$this->id]) && $cascade['protected'][$this->id]); @@ -135,7 +156,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return bool If the extension is installed in the correct directory */ - public function isInWrongFolder() { + public function isInWrongFolder() + { return $this->base != $this->getBase(); } @@ -144,13 +166,14 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return bool If the extension is enabled */ - public function isEnabled() { + public function isEnabled() + { global $conf; - if($this->isTemplate()){ + if ($this->isTemplate()) { return ($conf['template'] == $this->getBase()); } - /* @var Doku_Plugin_Controller $plugin_controller */ + /* @var PluginController $plugin_controller */ global $plugin_controller; return !$plugin_controller->isdisabled($this->base); } @@ -160,9 +183,10 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return bool If an update is available */ - public function updateAvailable() { - if(!$this->isInstalled()) return false; - if($this->isBundled()) return false; + public function updateAvailable() + { + if (!$this->isInstalled()) return false; + if ($this->isBundled()) return false; $lastupdate = $this->getLastUpdate(); if ($lastupdate === false) return false; $installed = $this->getInstalledVersion(); @@ -175,7 +199,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return bool If this extension is a template */ - public function isTemplate() { + public function isTemplate() + { return $this->is_template; } @@ -186,7 +211,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string */ - public function getID() { + public function getID() + { return $this->id; } @@ -195,7 +221,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string The name of the installation directory */ - public function getInstallName() { + public function getInstallName() + { return $this->base; } @@ -205,7 +232,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string The basename */ - public function getBase() { + public function getBase() + { if (!empty($this->localInfo['base'])) return $this->localInfo['base']; return $this->base; } @@ -215,7 +243,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string The display name */ - public function getDisplayName() { + public function getDisplayName() + { if (!empty($this->localInfo['name'])) return $this->localInfo['name']; if (!empty($this->remoteInfo['name'])) return $this->remoteInfo['name']; return $this->base; @@ -226,7 +255,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The name of the author or false if there is none */ - public function getAuthor() { + public function getAuthor() + { if (!empty($this->localInfo['author'])) return $this->localInfo['author']; if (!empty($this->remoteInfo['author'])) return $this->remoteInfo['author']; return false; @@ -237,7 +267,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The email address or false if there is none */ - public function getEmail() { + public function getEmail() + { // email is only in the local data if (!empty($this->localInfo['email'])) return $this->localInfo['email']; return false; @@ -248,7 +279,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The md5sum of the email if there is any, false otherwise */ - public function getEmailID() { + public function getEmailID() + { if (!empty($this->remoteInfo['emailid'])) return $this->remoteInfo['emailid']; if (!empty($this->localInfo['email'])) return md5($this->localInfo['email']); return false; @@ -259,7 +291,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string The description */ - public function getDescription() { + public function getDescription() + { if (!empty($this->localInfo['desc'])) return $this->localInfo['desc']; if (!empty($this->remoteInfo['description'])) return $this->remoteInfo['description']; return ''; @@ -270,7 +303,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string The URL */ - public function getURL() { + public function getURL() + { if (!empty($this->localInfo['url'])) return $this->localInfo['url']; return 'https://www.dokuwiki.org/'.($this->isTemplate() ? 'template' : 'plugin').':'.$this->getBase(); } @@ -280,7 +314,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The version, usually in the form yyyy-mm-dd if there is any */ - public function getInstalledVersion() { + public function getInstalledVersion() + { if (!empty($this->localInfo['date'])) return $this->localInfo['date']; if ($this->isInstalled()) return $this->getLang('unknownversion'); return false; @@ -291,7 +326,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The date of the last update or false if not available */ - public function getUpdateDate() { + public function getUpdateDate() + { if (!empty($this->managerData['updated'])) return $this->managerData['updated']; return $this->getInstallDate(); } @@ -301,7 +337,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The date of the installation or false if not available */ - public function getInstallDate() { + public function getInstallDate() + { if (!empty($this->managerData['installed'])) return $this->managerData['installed']; return false; } @@ -311,7 +348,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return array The base names of the dependencies */ - public function getDependencies() { + public function getDependencies() + { if (!empty($this->remoteInfo['dependencies'])) return $this->remoteInfo['dependencies']; return array(); } @@ -321,8 +359,9 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return array The base names of the missing dependencies */ - public function getMissingDependencies() { - /* @var Doku_Plugin_Controller $plugin_controller */ + public function getMissingDependencies() + { + /* @var PluginController $plugin_controller */ global $plugin_controller; $dependencies = $this->getDependencies(); $missing_dependencies = array(); @@ -339,7 +378,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return array The names of the conflicting extensions */ - public function getConflicts() { + public function getConflicts() + { if (!empty($this->remoteInfo['conflicts'])) return $this->remoteInfo['conflicts']; return array(); } @@ -349,7 +389,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return array The names of similar extensions */ - public function getSimilarExtensions() { + public function getSimilarExtensions() + { if (!empty($this->remoteInfo['similar'])) return $this->remoteInfo['similar']; return array(); } @@ -359,7 +400,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return array The names of the tags of the extension */ - public function getTags() { + public function getTags() + { if (!empty($this->remoteInfo['tags'])) return $this->remoteInfo['tags']; return array(); } @@ -369,7 +411,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return float|bool The popularity information or false if it isn't available */ - public function getPopularity() { + public function getPopularity() + { if (!empty($this->remoteInfo['popularity'])) return $this->remoteInfo['popularity']; return false; } @@ -380,7 +423,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The security warning if there is any, false otherwise */ - public function getSecurityWarning() { + public function getSecurityWarning() + { if (!empty($this->remoteInfo['securitywarning'])) return $this->remoteInfo['securitywarning']; return false; } @@ -390,7 +434,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The security issue if there is any, false otherwise */ - public function getSecurityIssue() { + public function getSecurityIssue() + { if (!empty($this->remoteInfo['securityissue'])) return $this->remoteInfo['securityissue']; return false; } @@ -400,7 +445,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The screenshot URL if there is any, false otherwise */ - public function getScreenshotURL() { + public function getScreenshotURL() + { if (!empty($this->remoteInfo['screenshoturl'])) return $this->remoteInfo['screenshoturl']; return false; } @@ -410,7 +456,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The thumbnail URL if there is any, false otherwise */ - public function getThumbnailURL() { + public function getThumbnailURL() + { if (!empty($this->remoteInfo['thumbnailurl'])) return $this->remoteInfo['thumbnailurl']; return false; } @@ -419,7 +466,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The previously used download URL, false if the extension has been installed manually */ - public function getLastDownloadURL() { + public function getLastDownloadURL() + { if (!empty($this->managerData['downloadurl'])) return $this->managerData['downloadurl']; return false; } @@ -429,7 +477,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The download URL if there is any, false otherwise */ - public function getDownloadURL() { + public function getDownloadURL() + { if (!empty($this->remoteInfo['downloadurl'])) return $this->remoteInfo['downloadurl']; return false; } @@ -439,7 +488,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return bool If the download URL has changed */ - public function hasDownloadURLChanged() { + public function hasDownloadURLChanged() + { $lasturl = $this->getLastDownloadURL(); $currenturl = $this->getDownloadURL(); return ($lasturl && $currenturl && $lasturl != $currenturl); @@ -450,7 +500,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The bug tracker URL if there is any, false otherwise */ - public function getBugtrackerURL() { + public function getBugtrackerURL() + { if (!empty($this->remoteInfo['bugtracker'])) return $this->remoteInfo['bugtracker']; return false; } @@ -460,7 +511,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The URL of the source repository if there is any, false otherwise */ - public function getSourcerepoURL() { + public function getSourcerepoURL() + { if (!empty($this->remoteInfo['sourcerepo'])) return $this->remoteInfo['sourcerepo']; return false; } @@ -470,7 +522,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The donation URL if there is any, false otherwise */ - public function getDonationURL() { + public function getDonationURL() + { if (!empty($this->remoteInfo['donationurl'])) return $this->remoteInfo['donationurl']; return false; } @@ -480,7 +533,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return array The type(s) as array of strings */ - public function getTypes() { + public function getTypes() + { if (!empty($this->remoteInfo['types'])) return $this->remoteInfo['types']; if ($this->isTemplate()) return array(32 => 'template'); return array(); @@ -491,7 +545,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return array The versions in the form yyyy-mm-dd => ('label' => label, 'implicit' => implicit) */ - public function getCompatibleVersions() { + public function getCompatibleVersions() + { if (!empty($this->remoteInfo['compatible'])) return $this->remoteInfo['compatible']; return array(); } @@ -501,7 +556,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string|bool The last available update in the form yyyy-mm-dd if there is any, false otherwise */ - public function getLastUpdate() { + public function getLastUpdate() + { if (!empty($this->remoteInfo['lastupdate'])) return $this->remoteInfo['lastupdate']; return false; } @@ -511,9 +567,10 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string The base path of the extension */ - public function getInstallDir() { + public function getInstallDir() + { if ($this->isTemplate()) { - return DOKU_TPLLIB.$this->base; + return $this->tpllib.$this->base; } else { return DOKU_PLUGIN.$this->base; } @@ -524,7 +581,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return string One of "none", "manual", "git" or "automatic" */ - public function getInstallType() { + public function getInstallType() + { if (!$this->isInstalled()) return 'none'; if (!empty($this->managerData)) return 'automatic'; if (is_dir($this->getInstallDir().'/.git')) return 'git'; @@ -536,17 +594,17 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return bool|string True or error string */ - public function canModify() { - if($this->isInstalled()) { - if(!is_writable($this->getInstallDir())) { + public function canModify() + { + if ($this->isInstalled()) { + if (!is_writable($this->getInstallDir())) { return 'noperms'; } } - if($this->isTemplate() && !is_writable(DOKU_TPLLIB)) { + if ($this->isTemplate() && !is_writable($this->tpllib)) { return 'notplperms'; - - } elseif(!is_writable(DOKU_PLUGIN)) { + } elseif (!is_writable(DOKU_PLUGIN)) { return 'nopluginperms'; } return true; @@ -559,20 +617,21 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * @throws Exception when something goes wrong * @return array The list of installed extensions */ - public function installFromUpload($field){ - if($_FILES[$field]['error']){ + public function installFromUpload($field) + { + if ($_FILES[$field]['error']) { throw new Exception($this->getLang('msg_upload_failed').' ('.$_FILES[$field]['error'].')'); } $tmp = $this->mkTmpDir(); - if(!$tmp) throw new Exception($this->getLang('error_dircreate')); + if (!$tmp) throw new Exception($this->getLang('error_dircreate')); // filename may contain the plugin name for old style plugins... $basename = basename($_FILES[$field]['name']); $basename = preg_replace('/\.(tar\.gz|tar\.bz|tar\.bz2|tar|tgz|tbz|zip)$/', '', $basename); $basename = preg_replace('/[\W]+/', '', $basename); - if(!move_uploaded_file($_FILES[$field]['tmp_name'], "$tmp/upload.archive")){ + if (!move_uploaded_file($_FILES[$field]['tmp_name'], "$tmp/upload.archive")) { throw new Exception($this->getLang('msg_upload_failed')); } @@ -582,7 +641,7 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { $this->removeDeletedfiles($installed); // purge cache $this->purgeCache(); - }catch (Exception $e){ + } catch (Exception $e) { throw $e; } return $installed; @@ -595,7 +654,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * @throws Exception when something goes wrong * @return array The list of installed extensions */ - public function installFromURL($url){ + public function installFromURL($url) + { try { $path = $this->download($url); $installed = $this->installArchive($path, true); @@ -604,7 +664,7 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { // purge cache $this->purgeCache(); - }catch (Exception $e){ + } catch (Exception $e) { throw $e; } return $installed; @@ -616,7 +676,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * @throws \Exception when something goes wrong * @return array The list of installed extensions */ - public function installOrUpdate() { + public function installOrUpdate() + { $url = $this->getDownloadURL(); $path = $this->download($url); $installed = $this->installArchive($path, $this->isInstalled(), $this->getBase()); @@ -637,7 +698,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return bool If the plugin was sucessfully uninstalled */ - public function uninstall() { + public function uninstall() + { $this->purgeCache(); return io_rmdir($this->getInstallDir(), true); } @@ -647,12 +709,13 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return bool|string True or an error message */ - public function enable() { + public function enable() + { if ($this->isTemplate()) return $this->getLang('notimplemented'); if (!$this->isInstalled()) return $this->getLang('notinstalled'); if ($this->isEnabled()) return $this->getLang('alreadyenabled'); - /* @var Doku_Plugin_Controller $plugin_controller */ + /* @var PluginController $plugin_controller */ global $plugin_controller; if ($plugin_controller->enable($this->base)) { $this->purgeCache(); @@ -667,10 +730,11 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return bool|string True or an error message */ - public function disable() { + public function disable() + { if ($this->isTemplate()) return $this->getLang('notimplemented'); - /* @var Doku_Plugin_Controller $plugin_controller */ + /* @var PluginController $plugin_controller */ global $plugin_controller; if (!$this->isInstalled()) return $this->getLang('notinstalled'); if (!$this->isEnabled()) return $this->getLang('alreadydisabled'); @@ -685,7 +749,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { /** * Purge the cache by touching the main configuration file */ - protected function purgeCache() { + protected function purgeCache() + { global $config_cascade; // expire dokuwiki caches @@ -696,7 +761,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { /** * Read local extension data either from info.txt or getInfo() */ - protected function readLocalData() { + protected function readLocalData() + { if ($this->isTemplate()) { $infopath = $this->getInstallDir().'/template.info.txt'; } else { @@ -706,19 +772,18 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { if (is_readable($infopath)) { $this->localInfo = confToHash($infopath); } elseif (!$this->isTemplate() && $this->isEnabled()) { - global $plugin_types; $path = $this->getInstallDir().'/'; $plugin = null; - foreach($plugin_types as $type) { - if(file_exists($path.$type.'.php')) { + foreach (PluginController::PLUGIN_TYPES as $type) { + if (file_exists($path.$type.'.php')) { $plugin = plugin_load($type, $this->base); if ($plugin) break; } - if($dh = @opendir($path.$type.'/')) { - while(false !== ($cp = readdir($dh))) { - if($cp == '.' || $cp == '..' || strtolower(substr($cp, -4)) != '.php') continue; + if ($dh = @opendir($path.$type.'/')) { + while (false !== ($cp = readdir($dh))) { + if ($cp == '.' || $cp == '..' || strtolower(substr($cp, -4)) != '.php') continue; $plugin = plugin_load($type, $this->base.'_'.substr($cp, 0, -4)); if ($plugin) break; @@ -741,21 +806,22 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * @param string $url Where the extension was downloaded from. (empty for manual installs via upload) * @param array $installed Optional list of installed plugins */ - protected function updateManagerData($url = '', $installed = null) { + protected function updateManagerData($url = '', $installed = null) + { $origID = $this->getID(); - if(is_null($installed)) { + if (is_null($installed)) { $installed = array($origID); } - foreach($installed as $ext => $info) { - if($this->getID() != $ext) $this->setExtension($ext); - if($url) { + foreach ($installed as $ext => $info) { + if ($this->getID() != $ext) $this->setExtension($ext); + if ($url) { $this->managerData['downloadurl'] = $url; - } elseif(isset($this->managerData['downloadurl'])) { + } elseif (isset($this->managerData['downloadurl'])) { unset($this->managerData['downloadurl']); } - if(isset($this->managerData['installed'])) { + if (isset($this->managerData['installed'])) { $this->managerData['updated'] = date('r'); } else { $this->managerData['installed'] = date('r'); @@ -763,23 +829,24 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { $this->writeManagerData(); } - if($this->getID() != $origID) $this->setExtension($origID); + if ($this->getID() != $origID) $this->setExtension($origID); } /** * Read the manager.dat file */ - protected function readManagerData() { + protected function readManagerData() + { $managerpath = $this->getInstallDir().'/manager.dat'; if (is_readable($managerpath)) { $file = @file($managerpath); - if(!empty($file)) { - foreach($file as $line) { + if (!empty($file)) { + foreach ($file as $line) { list($key, $value) = explode('=', trim($line, DOKU_LF), 2); $key = trim($key); $value = trim($value); // backwards compatible with old plugin manager - if($key == 'url') $key = 'downloadurl'; + if ($key == 'url') $key = 'downloadurl'; $this->managerData[$key] = $value; } } @@ -789,7 +856,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { /** * Write the manager.data file */ - protected function writeManagerData() { + protected function writeManagerData() + { $managerpath = $this->getInstallDir().'/manager.dat'; $data = ''; foreach ($this->managerData as $k => $v) { @@ -805,9 +873,10 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @return false|string */ - protected function mkTmpDir(){ + protected function mkTmpDir() + { $dir = io_mktmpdir(); - if(!$dir) return false; + if (!$dir) return false; $this->temporary[] = $dir; return $dir; } @@ -844,7 +913,7 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { if (is_string($content_disposition) && preg_match('/attachment;\s*filename\s*=\s*"([^"]*)"/i', $content_disposition, $match)) { - $name = utf8_basename($match[1]); + $name = \dokuwiki\Utf8\PhpString::basename($match[1]); } } @@ -872,27 +941,28 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * @throws Exception when something goes wrong * @return string The path where the archive was saved */ - public function download($url) { + public function download($url) + { // check the url - if(!preg_match('/https?:\/\//i', $url)){ + if (!preg_match('/https?:\/\//i', $url)) { throw new Exception($this->getLang('error_badurl')); } // try to get the file from the path (used as plugin name fallback) $file = parse_url($url, PHP_URL_PATH); - if(is_null($file)){ + if (is_null($file)) { $file = md5($url); - }else{ - $file = utf8_basename($file); + } else { + $file = \dokuwiki\Utf8\PhpString::basename($file); } // create tmp directory for download - if(!($tmp = $this->mkTmpDir())) { + if (!($tmp = $this->mkTmpDir())) { throw new Exception($this->getLang('error_dircreate')); } // download - if(!$file = $this->downloadToFile($url, $tmp.'/', $file)) { + if (!$file = $this->downloadToFile($url, $tmp.'/', $file)) { io_rmdir($tmp, true); throw new Exception(sprintf($this->getLang('error_download'), '<bdi>'.hsc($url).'</bdi>')); } @@ -907,16 +977,17 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * @throws Exception when something went wrong * @return array list of installed extensions */ - public function installArchive($file, $overwrite=false, $base = '') { + public function installArchive($file, $overwrite = false, $base = '') + { $installed_extensions = array(); // create tmp directory for decompression - if(!($tmp = $this->mkTmpDir())) { + if (!($tmp = $this->mkTmpDir())) { throw new Exception($this->getLang('error_dircreate')); } // add default base folder if specified to handle case where zip doesn't contain this - if($base && !@mkdir($tmp.'/'.$base)) { + if ($base && !@mkdir($tmp.'/'.$base)) { throw new Exception($this->getLang('error_dircreate')); } @@ -927,33 +998,33 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { // move the folder(s) to lib/.. $result = array('old'=>array(), 'new'=>array()); $default = ($this->isTemplate() ? 'template' : 'plugin'); - if(!$this->find_folders($result, $tmp.'/'.$base, $default)) { + if (!$this->findFolders($result, $tmp.'/'.$base, $default)) { throw new Exception($this->getLang('error_findfolder')); } // choose correct result array - if(count($result['new'])) { + if (count($result['new'])) { $install = $result['new']; - }else{ + } else { $install = $result['old']; } - if(!count($install)){ + if (!count($install)) { throw new Exception($this->getLang('error_findfolder')); } // now install all found items - foreach($install as $item) { + foreach ($install as $item) { // where to install? - if($item['type'] == 'template') { - $target_base_dir = DOKU_TPLLIB; - }else{ + if ($item['type'] == 'template') { + $target_base_dir = $this->tpllib; + } else { $target_base_dir = DOKU_PLUGIN; } - if(!empty($item['base'])) { + if (!empty($item['base'])) { // use base set in info.txt - } elseif($base && count($install) == 1) { + } elseif ($base && count($install) == 1) { $item['base'] = $base; } else { // default - use directory as found in zip @@ -964,7 +1035,7 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { // check to make sure we aren't overwriting anything $target = $target_base_dir.$item['base']; - if(!$overwrite && file_exists($target)) { + if (!$overwrite && file_exists($target)) { // TODO remember our settings, ask the user to confirm overwrite continue; } @@ -972,10 +1043,10 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { $action = file_exists($target) ? 'update' : 'install'; // copy action - if($this->dircopy($item['tmp'], $target)) { + if ($this->dircopy($item['tmp'], $target)) { // return info $id = $item['base']; - if($item['type'] == 'template') { + if ($item['type'] == 'template') { $id = 'template:'.$id; } $installed_extensions[$id] = array( @@ -989,7 +1060,7 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { } // cleanup - if($tmp) io_rmdir($tmp, true); + if ($tmp) io_rmdir($tmp, true); return $installed_extensions; } @@ -1015,20 +1086,20 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * @param string $subdir - a subdirectory. do not set. used by recursion * @return bool - false on error */ - protected function find_folders(&$result, $directory, $default_type='plugin', $subdir='') { + protected function findFolders(&$result, $directory, $default_type = 'plugin', $subdir = '') + { $this_dir = "$directory$subdir"; $dh = @opendir($this_dir); - if(!$dh) return false; + if (!$dh) return false; $found_dirs = array(); $found_files = 0; $found_template_parts = 0; while (false !== ($f = readdir($dh))) { - if($f == '.' || $f == '..') continue; + if ($f == '.' || $f == '..') continue; - if(is_dir("$this_dir/$f")) { + if (is_dir("$this_dir/$f")) { $found_dirs[] = "$subdir/$f"; - } else { // it's a file -> check for config $found_files++; @@ -1057,11 +1128,11 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { closedir($dh); // files where found but no info.txt - use old method - if($found_files){ + if ($found_files) { $info = array(); $info['tmp'] = $this_dir; // does this look like a template or should we use the default type? - if($found_template_parts >= 2) { + if ($found_template_parts >= 2) { $info['type'] = 'template'; } else { $info['type'] = $default_type; @@ -1073,7 +1144,7 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { // we have no files yet -> recurse foreach ($found_dirs as $found_dir) { - $this->find_folders($result, $directory, $default_type, "$found_dir"); + $this->findFolders($result, $directory, $default_type, "$found_dir"); } return true; } @@ -1088,13 +1159,13 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * @throws Exception * @return bool */ - private function decompress($file, $target) { + private function decompress($file, $target) + { // decompression library doesn't like target folders ending in "/" - if(substr($target, -1) == "/") $target = substr($target, 0, -1); - - $ext = $this->guess_archive($file); - if(in_array($ext, array('tar', 'bz', 'gz'))) { + if (substr($target, -1) == "/") $target = substr($target, 0, -1); + $ext = $this->guessArchiveType($file); + if (in_array($ext, array('tar', 'bz', 'gz'))) { try { $tar = new \splitbrain\PHPArchive\Tar(); $tar->open($file); @@ -1104,8 +1175,7 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { } return true; - } elseif($ext == 'zip') { - + } elseif ($ext == 'zip') { try { $zip = new \splitbrain\PHPArchive\Zip(); $zip->open($file); @@ -1131,15 +1201,16 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * @param string $file The file to analyze * @return string|false false if the file can't be read, otherwise an "extension" */ - private function guess_archive($file) { + private function guessArchiveType($file) + { $fh = fopen($file, 'rb'); - if(!$fh) return false; + if (!$fh) return false; $magic = fread($fh, 5); fclose($fh); - if(strpos($magic, "\x42\x5a") === 0) return 'bz'; - if(strpos($magic, "\x1f\x8b") === 0) return 'gz'; - if(strpos($magic, "\x50\x4b\x03\x04") === 0) return 'zip'; + if (strpos($magic, "\x42\x5a") === 0) return 'bz'; + if (strpos($magic, "\x1f\x8b") === 0) return 'gz'; + if (strpos($magic, "\x50\x4b\x03\x04") === 0) return 'zip'; return 'tar'; } @@ -1150,27 +1221,27 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * @param string $dst filename path to file * @return bool|int|string */ - private function dircopy($src, $dst) { + private function dircopy($src, $dst) + { global $conf; - if(is_dir($src)) { - if(!$dh = @opendir($src)) return false; + if (is_dir($src)) { + if (!$dh = @opendir($src)) return false; - if($ok = io_mkdir_p($dst)) { + if ($ok = io_mkdir_p($dst)) { while ($ok && (false !== ($f = readdir($dh)))) { - if($f == '..' || $f == '.') continue; + if ($f == '..' || $f == '.') continue; $ok = $this->dircopy("$src/$f", "$dst/$f"); } } closedir($dh); return $ok; - } else { $exists = file_exists($dst); - if(!@copy($src, $dst)) return false; - if(!$exists && !empty($conf['fperm'])) chmod($dst, $conf['fperm']); + if (!@copy($src, $dst)) return false; + if (!$exists && !empty($conf['fperm'])) chmod($dst, $conf['fperm']); @touch($dst, filemtime($src)); } @@ -1182,29 +1253,30 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * * @param array $installed */ - private function removeDeletedfiles($installed) { - foreach($installed as $id => $extension) { + private function removeDeletedfiles($installed) + { + foreach ($installed as $id => $extension) { // only on update - if($extension['action'] == 'install') continue; + if ($extension['action'] == 'install') continue; // get definition file - if($extension['type'] == 'template') { - $extensiondir = DOKU_TPLLIB; - }else{ + if ($extension['type'] == 'template') { + $extensiondir = $this->tpllib; + } else { $extensiondir = DOKU_PLUGIN; } $extensiondir = $extensiondir . $extension['base'] .'/'; $definitionfile = $extensiondir . 'deleted.files'; - if(!file_exists($definitionfile)) continue; + if (!file_exists($definitionfile)) continue; // delete the old files $list = file($definitionfile); - foreach($list as $line) { + foreach ($list as $line) { $line = trim(preg_replace('/#.*$/', '', $line)); - if(!$line) continue; + if (!$line) continue; $file = $extensiondir . $line; - if(!file_exists($file)) continue; + if (!file_exists($file)) continue; io_rmdir($file, true); } diff --git a/lib/plugins/extension/helper/gui.php b/lib/plugins/extension/helper/gui.php index 4ec6fec85..aa9e2ec45 100644 --- a/lib/plugins/extension/helper/gui.php +++ b/lib/plugins/extension/helper/gui.php @@ -6,13 +6,13 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); +use dokuwiki\Extension\PluginController; /** * Class helper_plugin_extension_list takes care of the overall GUI */ -class helper_plugin_extension_gui extends DokuWiki_Plugin { +class helper_plugin_extension_gui extends DokuWiki_Plugin +{ protected $tabs = array('plugins', 'templates', 'search', 'install'); @@ -24,7 +24,8 @@ class helper_plugin_extension_gui extends DokuWiki_Plugin { * * initializes requested info window */ - public function __construct() { + public function __construct() + { global $INPUT; $this->infoFor = $INPUT->str('info'); } @@ -32,8 +33,9 @@ class helper_plugin_extension_gui extends DokuWiki_Plugin { /** * display the plugin tab */ - public function tabPlugins() { - /* @var Doku_Plugin_Controller $plugin_controller */ + public function tabPlugins() + { + /* @var PluginController $plugin_controller */ global $plugin_controller; echo '<div class="panelHeader">'; @@ -46,19 +48,20 @@ class helper_plugin_extension_gui extends DokuWiki_Plugin { $extension = $this->loadHelper('extension_extension'); /* @var helper_plugin_extension_list $list */ $list = $this->loadHelper('extension_list'); - $list->start_form(); - foreach($pluginlist as $name) { + $list->startForm(); + foreach ($pluginlist as $name) { $extension->setExtension($name); - $list->add_row($extension, $extension->getID() == $this->infoFor); + $list->addRow($extension, $extension->getID() == $this->infoFor); } - $list->end_form(); + $list->endForm(); $list->render(); } /** * Display the template tab */ - public function tabTemplates() { + public function tabTemplates() + { echo '<div class="panelHeader">'; echo $this->locale_xhtml('intro_templates'); echo '</div>'; @@ -72,19 +75,20 @@ class helper_plugin_extension_gui extends DokuWiki_Plugin { $extension = $this->loadHelper('extension_extension'); /* @var helper_plugin_extension_list $list */ $list = $this->loadHelper('extension_list'); - $list->start_form(); - foreach($tpllist as $name) { + $list->startForm(); + foreach ($tpllist as $name) { $extension->setExtension("template:$name"); - $list->add_row($extension, $extension->getID() == $this->infoFor); + $list->addRow($extension, $extension->getID() == $this->infoFor); } - $list->end_form(); + $list->endForm(); $list->render(); } /** * Display the search tab */ - public function tabSearch() { + public function tabSearch() + { global $INPUT; echo '<div class="panelHeader">'; echo $this->locale_xhtml('intro_search'); @@ -95,7 +99,7 @@ class helper_plugin_extension_gui extends DokuWiki_Plugin { $form->addElement(form_makeButton('submit', '', $this->getLang('search'))); $form->printForm(); - if(!$INPUT->bool('q')) return; + if (!$INPUT->bool('q')) return; /* @var helper_plugin_extension_repository $repository FIXME should we use some gloabl instance? */ $repository = $this->loadHelper('extension_repository'); @@ -105,29 +109,35 @@ class helper_plugin_extension_gui extends DokuWiki_Plugin { $extension = $this->loadHelper('extension_extension'); /* @var helper_plugin_extension_list $list */ $list = $this->loadHelper('extension_list'); - $list->start_form(); - if($result){ - foreach($result as $name) { + $list->startForm(); + if ($result) { + foreach ($result as $name) { $extension->setExtension($name); - $list->add_row($extension, $extension->getID() == $this->infoFor); + $list->addRow($extension, $extension->getID() == $this->infoFor); } } else { - $list->nothing_found(); + $list->nothingFound(); } - $list->end_form(); + $list->endForm(); $list->render(); - } /** * Display the template tab */ - public function tabInstall() { + public function tabInstall() + { echo '<div class="panelHeader">'; echo $this->locale_xhtml('intro_install'); echo '</div>'; - $form = new Doku_Form(array('action' => $this->tabURL('', array(), '&'), 'enctype' => 'multipart/form-data', 'class' => 'install')); + $form = new Doku_Form( + array( + 'action' => $this->tabURL('', array(), '&'), + 'enctype' => 'multipart/form-data', + 'class' => 'install' + ) + ); $form->addElement(form_makeTextField('installurl', '', $this->getLang('install_url'), '', 'block')); $form->addElement(form_makeFileField('installfile', $this->getLang('install_upload'), '', 'block')); $form->addElement(form_makeButton('submit', '', $this->getLang('btn_install'))); @@ -139,11 +149,12 @@ class helper_plugin_extension_gui extends DokuWiki_Plugin { * * @fixme style active one */ - public function tabNavigation() { + public function tabNavigation() + { echo '<ul class="tabs">'; - foreach($this->tabs as $tab) { + foreach ($this->tabs as $tab) { $url = $this->tabURL($tab); - if($this->currentTab() == $tab) { + if ($this->currentTab() == $tab) { $class = ' active'; } else { $class = ''; @@ -158,11 +169,12 @@ class helper_plugin_extension_gui extends DokuWiki_Plugin { * * @return string */ - public function currentTab() { + public function currentTab() + { global $INPUT; $tab = $INPUT->str('tab', 'plugins', true); - if(!in_array($tab, $this->tabs)) $tab = 'plugins'; + if (!in_array($tab, $this->tabs)) $tab = 'plugins'; return $tab; } @@ -175,19 +187,19 @@ class helper_plugin_extension_gui extends DokuWiki_Plugin { * @param bool $absolute create absolute URLs? * @return string */ - public function tabURL($tab = '', $params = array(), $sep = '&', $absolute = false) { + public function tabURL($tab = '', $params = array(), $sep = '&', $absolute = false) + { global $ID; global $INPUT; - if(!$tab) $tab = $this->currentTab(); + if (!$tab) $tab = $this->currentTab(); $defaults = array( 'do' => 'admin', 'page' => 'extension', 'tab' => $tab, ); - if($tab == 'search') $defaults['q'] = $INPUT->str('q'); + if ($tab == 'search') $defaults['q'] = $INPUT->str('q'); return wl($ID, array_merge($defaults, $params), $absolute, $sep); } - } diff --git a/lib/plugins/extension/helper/list.php b/lib/plugins/extension/helper/list.php index 656b4ea09..d895941cb 100644 --- a/lib/plugins/extension/helper/list.php +++ b/lib/plugins/extension/helper/list.php @@ -6,13 +6,11 @@ * @author Michael Hamann <michael@content-space.de> */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - /** * Class helper_plugin_extension_list takes care of creating a HTML list of extensions */ -class helper_plugin_extension_list extends DokuWiki_Plugin { +class helper_plugin_extension_list extends DokuWiki_Plugin +{ protected $form = ''; /** @var helper_plugin_extension_gui */ protected $gui; @@ -22,30 +20,38 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * * loads additional helpers */ - public function __construct(){ + public function __construct() + { $this->gui = plugin_load('helper', 'extension_gui'); } - function start_form() { + /** + * Initialize the extension table form + */ + public function startForm() + { $this->form .= '<form id="extension__list" accept-charset="utf-8" method="post" action="">'; $hidden = array( 'do'=>'admin', 'page'=>'extension', 'sectok'=>getSecurityToken() ); - $this->add_hidden($hidden); + $this->addHidden($hidden); $this->form .= '<ul class="extensionList">'; } + /** * Build single row of extension table + * * @param helper_plugin_extension_extension $extension The extension that shall be added * @param bool $showinfo Show the info area */ - function add_row(helper_plugin_extension_extension $extension, $showinfo = false) { - $this->start_row($extension); - $this->populate_column('legend', $this->make_legend($extension, $showinfo)); - $this->populate_column('actions', $this->make_actions($extension)); - $this->end_row(); + public function addRow(helper_plugin_extension_extension $extension, $showinfo = false) + { + $this->startRow($extension); + $this->populateColumn('legend', $this->makeLegend($extension, $showinfo)); + $this->populateColumn('actions', $this->makeActions($extension)); + $this->endRow(); } /** @@ -55,7 +61,8 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param string $header The content of the header * @param int $level The level of the header */ - function add_header($id, $header, $level = 2) { + public function addHeader($id, $header, $level = 2) + { $this->form .='<h'.$level.' id="'.$id.'">'.hsc($header).'</h'.$level.'>'.DOKU_LF; } @@ -64,17 +71,20 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * * @param string $data The content */ - function add_p($data) { + public function addParagraph($data) + { $this->form .= '<p>'.hsc($data).'</p>'.DOKU_LF; } /** * Add hidden fields to the form with the given data - * @param array $array + * + * @param array $data key-value list of fields and their values to add */ - function add_hidden(array $array) { + public function addHidden(array $data) + { $this->form .= '<div class="no">'; - foreach ($array as $key => $value) { + foreach ($data as $key => $value) { $this->form .= '<input type="hidden" name="'.hsc($key).'" value="'.hsc($value).'" />'; } $this->form .= '</div>'.DOKU_LF; @@ -83,7 +93,8 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { /** * Add closing tags */ - function end_form() { + public function endForm() + { $this->form .= '</ul>'; $this->form .= '</form>'.DOKU_LF; } @@ -91,7 +102,8 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { /** * Show message when no results are found */ - function nothing_found() { + public function nothingFound() + { global $lang; $this->form .= '<li class="notfound">'.$lang['nothingfound'].'</li>'; } @@ -99,7 +111,8 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { /** * Print the form */ - function render() { + public function render() + { echo $this->form; } @@ -108,8 +121,10 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * * @param helper_plugin_extension_extension $extension The extension */ - private function start_row(helper_plugin_extension_extension $extension) { - $this->form .= '<li id="extensionplugin__'.hsc($extension->getID()).'" class="'.$this->make_class($extension).'">'; + private function startRow(helper_plugin_extension_extension $extension) + { + $this->form .= '<li id="extensionplugin__'.hsc($extension->getID()). + '" class="'.$this->makeClass($extension).'">'; } /** @@ -117,14 +132,16 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param string $class The class name * @param string $html The content */ - private function populate_column($class, $html) { + private function populateColumn($class, $html) + { $this->form .= '<div class="'.$class.' col">'.$html.'</div>'.DOKU_LF; } /** * End the row */ - private function end_row() { + private function endRow() + { $this->form .= '</li>'.DOKU_LF; } @@ -134,7 +151,8 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param helper_plugin_extension_extension $extension The extension * @return string The HTML code */ - function make_homepagelink(helper_plugin_extension_extension $extension) { + public function makeHomepageLink(helper_plugin_extension_extension $extension) + { $text = $this->getLang('homepage_link'); $url = hsc($extension->getURL()); return '<a href="'.$url.'" title="'.$url.'" class ="urlextern">'.$text.'</a> '; @@ -146,15 +164,16 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param helper_plugin_extension_extension $extension The extension object * @return string The class name */ - function make_class(helper_plugin_extension_extension $extension) { + public function makeClass(helper_plugin_extension_extension $extension) + { $class = ($extension->isTemplate()) ? 'template' : 'plugin'; - if($extension->isInstalled()) { + if ($extension->isInstalled()) { $class.=' installed'; $class.= ($extension->isEnabled()) ? ' enabled':' disabled'; - if($extension->updateAvailable()) $class .= ' updatable'; + if ($extension->updateAvailable()) $class .= ' updatable'; } - if(!$extension->canModify()) $class.= ' notselect'; - if($extension->isProtected()) $class.= ' protected'; + if (!$extension->canModify()) $class.= ' notselect'; + if ($extension->isProtected()) $class.= ' protected'; //if($this->showinfo) $class.= ' showinfo'; return $class; } @@ -165,17 +184,16 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param helper_plugin_extension_extension $extension The extension object * @return string The HTML code of the link */ - function make_author(helper_plugin_extension_extension $extension) { - global $ID; - - if($extension->getAuthor()) { - + public function makeAuthor(helper_plugin_extension_extension $extension) + { + if ($extension->getAuthor()) { $mailid = $extension->getEmailID(); - if($mailid){ + if ($mailid) { $url = $this->gui->tabURL('search', array('q' => 'authorid:'.$mailid)); - return '<bdi><a href="'.$url.'" class="author" title="'.$this->getLang('author_hint').'" ><img src="//www.gravatar.com/avatar/'.$mailid.'?s=20&d=mm" width="20" height="20" alt="" /> '.hsc($extension->getAuthor()).'</a></bdi>'; - - }else{ + return '<bdi><a href="'.$url.'" class="author" title="'.$this->getLang('author_hint').'" >'. + '<img src="//www.gravatar.com/avatar/'.$mailid.'?s=20&d=mm" width="20" height="20" alt="" /> '. + hsc($extension->getAuthor()).'</a></bdi>'; + } else { return '<bdi><span class="author">'.hsc($extension->getAuthor()).'</span></bdi>'; } } @@ -188,11 +206,12 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param helper_plugin_extension_extension $extension The extension object * @return string The HTML code */ - function make_screenshot(helper_plugin_extension_extension $extension) { + public function makeScreenshot(helper_plugin_extension_extension $extension) + { $screen = $extension->getScreenshotURL(); $thumb = $extension->getThumbnailURL(); - if($screen) { + if ($screen) { // use protocol independent URLs for images coming from us #595 $screen = str_replace('http://www.dokuwiki.org', '//www.dokuwiki.org', $screen); $thumb = str_replace('http://www.dokuwiki.org', '//www.dokuwiki.org', $thumb); @@ -201,11 +220,12 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { $img = '<a href="'.hsc($screen).'" target="_blank" class="extension_screenshot">'. '<img alt="'.$title.'" width="120" height="70" src="'.hsc($thumb).'" />'. '</a>'; - } elseif($extension->isTemplate()) { - $img = '<img alt="" width="120" height="70" src="'.DOKU_BASE.'lib/plugins/extension/images/template.png" />'; - + } elseif ($extension->isTemplate()) { + $img = '<img alt="" width="120" height="70" src="'.DOKU_BASE. + 'lib/plugins/extension/images/template.png" />'; } else { - $img = '<img alt="" width="120" height="70" src="'.DOKU_BASE.'lib/plugins/extension/images/plugin.png" />'; + $img = '<img alt="" width="120" height="70" src="'.DOKU_BASE. + 'lib/plugins/extension/images/plugin.png" />'; } return '<div class="screenshot" >'.$img.'<span></span></div>'.DOKU_LF; } @@ -217,41 +237,51 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param bool $showinfo Show the info section * @return string The HTML code */ - function make_legend(helper_plugin_extension_extension $extension, $showinfo = false) { + public function makeLegend(helper_plugin_extension_extension $extension, $showinfo = false) + { $return = '<div>'; $return .= '<h2>'; - $return .= sprintf($this->getLang('extensionby'), '<bdi>'.hsc($extension->getDisplayName()).'</bdi>', $this->make_author($extension)); + $return .= sprintf( + $this->getLang('extensionby'), + '<bdi>'.hsc($extension->getDisplayName()).'</bdi>', + $this->makeAuthor($extension) + ); $return .= '</h2>'.DOKU_LF; - $return .= $this->make_screenshot($extension); + $return .= $this->makeScreenshot($extension); $popularity = $extension->getPopularity(); if ($popularity !== false && !$extension->isBundled()) { $popularityText = sprintf($this->getLang('popularity'), round($popularity*100, 2)); - $return .= '<div class="popularity" title="'.$popularityText.'"><div style="width: '.($popularity * 100).'%;"><span class="a11y">'.$popularityText.'</span></div></div>'.DOKU_LF; + $return .= '<div class="popularity" title="'.$popularityText.'">'. + '<div style="width: '.($popularity * 100).'%;">'. + '<span class="a11y">'.$popularityText.'</span>'. + '</div></div>'.DOKU_LF; } - if($extension->getDescription()) { + if ($extension->getDescription()) { $return .= '<p><bdi>'; $return .= hsc($extension->getDescription()).' '; $return .= '</bdi></p>'.DOKU_LF; } - $return .= $this->make_linkbar($extension); + $return .= $this->makeLinkbar($extension); - if($showinfo){ + if ($showinfo) { $url = $this->gui->tabURL(''); $class = 'close'; - }else{ + } else { $url = $this->gui->tabURL('', array('info' => $extension->getID())); $class = ''; } - $return .= ' <a href="'.$url.'#extensionplugin__'.$extension->getID().'" class="info '.$class.'" title="'.$this->getLang('btn_info').'" data-extid="'.$extension->getID().'">'.$this->getLang('btn_info').'</a>'; + $return .= ' <a href="'.$url.'#extensionplugin__'.$extension->getID(). + '" class="info '.$class.'" title="'.$this->getLang('btn_info'). + '" data-extid="'.$extension->getID().'">'.$this->getLang('btn_info').'</a>'; if ($showinfo) { - $return .= $this->make_info($extension); + $return .= $this->makeInfo($extension); } - $return .= $this->make_noticearea($extension); + $return .= $this->makeNoticeArea($extension); $return .= '</div>'.DOKU_LF; return $return; } @@ -262,17 +292,20 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param helper_plugin_extension_extension $extension The extension instance * @return string The HTML code */ - function make_linkbar(helper_plugin_extension_extension $extension) { + public function makeLinkbar(helper_plugin_extension_extension $extension) + { $return = '<div class="linkbar">'; - $return .= $this->make_homepagelink($extension); + $return .= $this->makeHomepageLink($extension); if ($extension->getBugtrackerURL()) { - $return .= ' <a href="'.hsc($extension->getBugtrackerURL()).'" title="'.hsc($extension->getBugtrackerURL()).'" class ="bugs">'.$this->getLang('bugs_features').'</a> '; + $return .= ' <a href="'.hsc($extension->getBugtrackerURL()). + '" title="'.hsc($extension->getBugtrackerURL()).'" class ="bugs">'. + $this->getLang('bugs_features').'</a> '; } - if ($extension->getTags()){ + if ($extension->getTags()) { $first = true; $return .= '<span class="tags">'.$this->getLang('tags').' '; foreach ($extension->getTags() as $tag) { - if (!$first){ + if (!$first) { $return .= ', '; } else { $first = false; @@ -292,37 +325,49 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param helper_plugin_extension_extension $extension The extension * @return string The HTML code */ - function make_noticearea(helper_plugin_extension_extension $extension) { + public function makeNoticeArea(helper_plugin_extension_extension $extension) + { $return = ''; $missing_dependencies = $extension->getMissingDependencies(); - if(!empty($missing_dependencies)) { - $return .= '<div class="msg error">'. - sprintf($this->getLang('missing_dependency'), '<bdi>'.implode(', ', /*array_map(array($this->helper, 'make_extensionsearchlink'),*/ $missing_dependencies).'</bdi>'). + if (!empty($missing_dependencies)) { + $return .= '<div class="msg error">' . + sprintf( + $this->getLang('missing_dependency'), + '<bdi>' . implode(', ', $missing_dependencies) . '</bdi>' + ) . '</div>'; } - if($extension->isInWrongFolder()) { - $return .= '<div class="msg error">'. - sprintf($this->getLang('wrong_folder'), '<bdi>'.hsc($extension->getInstallName()).'</bdi>', '<bdi>'.hsc($extension->getBase()).'</bdi>'). + if ($extension->isInWrongFolder()) { + $return .= '<div class="msg error">' . + sprintf( + $this->getLang('wrong_folder'), + '<bdi>' . hsc($extension->getInstallName()) . '</bdi>', + '<bdi>' . hsc($extension->getBase()) . '</bdi>' + ) . '</div>'; } - if(($securityissue = $extension->getSecurityIssue()) !== false) { + if (($securityissue = $extension->getSecurityIssue()) !== false) { $return .= '<div class="msg error">'. sprintf($this->getLang('security_issue'), '<bdi>'.hsc($securityissue).'</bdi>'). '</div>'; } - if(($securitywarning = $extension->getSecurityWarning()) !== false) { + if (($securitywarning = $extension->getSecurityWarning()) !== false) { $return .= '<div class="msg notify">'. sprintf($this->getLang('security_warning'), '<bdi>'.hsc($securitywarning).'</bdi>'). '</div>'; } - if($extension->updateAvailable()) { + if ($extension->updateAvailable()) { $return .= '<div class="msg notify">'. sprintf($this->getLang('update_available'), hsc($extension->getLastUpdate())). '</div>'; } - if($extension->hasDownloadURLChanged()) { - $return .= '<div class="msg notify">'. - sprintf($this->getLang('url_change'), '<bdi>'.hsc($extension->getDownloadURL()).'</bdi>', '<bdi>'.hsc($extension->getLastDownloadURL()).'</bdi>'). + if ($extension->hasDownloadURLChanged()) { + $return .= '<div class="msg notify">' . + sprintf( + $this->getLang('url_change'), + '<bdi>' . hsc($extension->getDownloadURL()) . '</bdi>', + '<bdi>' . hsc($extension->getLastDownloadURL()) . '</bdi>' + ) . '</div>'; } return $return.DOKU_LF; @@ -336,13 +381,14 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param string $url * @return string HTML link */ - function shortlink($url){ + public function shortlink($url) + { $link = parse_url($url); $base = $link['host']; - if(!empty($link['port'])) $base .= $base.':'.$link['port']; + if (!empty($link['port'])) $base .= $base.':'.$link['port']; $long = $link['path']; - if(!empty($link['query'])) $long .= $link['query']; + if (!empty($link['query'])) $long .= $link['query']; $name = shorten($base, $long, 55); @@ -355,17 +401,19 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param helper_plugin_extension_extension $extension The extension * @return string The HTML code */ - function make_info(helper_plugin_extension_extension $extension) { + public function makeInfo(helper_plugin_extension_extension $extension) + { $default = $this->getLang('unknown'); $return = '<dl class="details">'; $return .= '<dt>'.$this->getLang('status').'</dt>'; - $return .= '<dd>'.$this->make_status($extension).'</dd>'; + $return .= '<dd>'.$this->makeStatus($extension).'</dd>'; if ($extension->getDonationURL()) { $return .= '<dt>'.$this->getLang('donate').'</dt>'; $return .= '<dd>'; - $return .= '<a href="'.$extension->getDonationURL().'" class="donate">'.$this->getLang('donate_action').'</a>'; + $return .= '<a href="'.$extension->getDonationURL().'" class="donate">'. + $this->getLang('donate_action').'</a>'; $return .= '</dd>'; } @@ -407,7 +455,7 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { $return .= ($extension->getTypes() ? hsc(implode(', ', $extension->getTypes())) : $default); $return .= '</bdi></dd>'; - if(!$extension->isBundled() && $extension->getCompatibleVersions()) { + if (!$extension->isBundled() && $extension->getCompatibleVersions()) { $return .= '<dt>'.$this->getLang('compatible').'</dt>'; $return .= '<dd>'; foreach ($extension->getCompatibleVersions() as $date => $version) { @@ -416,24 +464,24 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { $return = rtrim($return, ', '); $return .= '</dd>'; } - if($extension->getDependencies()) { + if ($extension->getDependencies()) { $return .= '<dt>'.$this->getLang('depends').'</dt>'; $return .= '<dd>'; - $return .= $this->make_linklist($extension->getDependencies()); + $return .= $this->makeLinkList($extension->getDependencies()); $return .= '</dd>'; } - if($extension->getSimilarExtensions()) { + if ($extension->getSimilarExtensions()) { $return .= '<dt>'.$this->getLang('similar').'</dt>'; $return .= '<dd>'; - $return .= $this->make_linklist($extension->getSimilarExtensions()); + $return .= $this->makeLinkList($extension->getSimilarExtensions()); $return .= '</dd>'; } - if($extension->getConflicts()) { + if ($extension->getConflicts()) { $return .= '<dt>'.$this->getLang('conflicts').'</dt>'; $return .= '<dd>'; - $return .= $this->make_linklist($extension->getConflicts()); + $return .= $this->makeLinkList($extension->getConflicts()); $return .= '</dd>'; } $return .= '</dl>'.DOKU_LF; @@ -446,10 +494,12 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param array $ext The extensions * @return string The HTML code */ - function make_linklist($ext) { + public function makeLinkList($ext) + { $return = ''; foreach ($ext as $link) { - $return .= '<bdi><a href="'.$this->gui->tabURL('search', array('q'=>'ext:'.$link)).'">'.hsc($link).'</a></bdi>, '; + $return .= '<bdi><a href="'. + $this->gui->tabURL('search', array('q'=>'ext:'.$link)).'">'.hsc($link).'</a></bdi>, '; } return rtrim($return, ', '); } @@ -460,7 +510,8 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param helper_plugin_extension_extension $extension The extension * @return string The HTML code */ - function make_actions(helper_plugin_extension_extension $extension) { + public function makeActions(helper_plugin_extension_extension $extension) + { global $conf; $return = ''; $errors = ''; @@ -468,48 +519,52 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { if ($extension->isInstalled()) { if (($canmod = $extension->canModify()) === true) { if (!$extension->isProtected()) { - $return .= $this->make_action('uninstall', $extension); + $return .= $this->makeAction('uninstall', $extension); } if ($extension->getDownloadURL()) { if ($extension->updateAvailable()) { - $return .= $this->make_action('update', $extension); + $return .= $this->makeAction('update', $extension); } else { - $return .= $this->make_action('reinstall', $extension); + $return .= $this->makeAction('reinstall', $extension); } } - }else{ + } else { $errors .= '<p class="permerror">'.$this->getLang($canmod).'</p>'; } if (!$extension->isProtected() && !$extension->isTemplate()) { // no enable/disable for templates if ($extension->isEnabled()) { - $return .= $this->make_action('disable', $extension); + $return .= $this->makeAction('disable', $extension); } else { - $return .= $this->make_action('enable', $extension); + $return .= $this->makeAction('enable', $extension); } } - if ($extension->isGitControlled()){ + if ($extension->isGitControlled()) { $errors .= '<p class="permerror">'.$this->getLang('git').'</p>'; } - if ($extension->isEnabled() && in_array('Auth', $extension->getTypes()) && $conf['authtype'] != $extension->getID()) { + if ($extension->isEnabled() && + in_array('Auth', $extension->getTypes()) && + $conf['authtype'] != $extension->getID() + ) { $errors .= '<p class="permerror">'.$this->getLang('auth').'</p>'; } - - }else{ + } else { if (($canmod = $extension->canModify()) === true) { if ($extension->getDownloadURL()) { - $return .= $this->make_action('install', $extension); + $return .= $this->makeAction('install', $extension); } - }else{ + } else { $errors .= '<div class="permerror">'.$this->getLang($canmod).'</div>'; } } if (!$extension->isInstalled() && $extension->getDownloadURL()) { $return .= ' <span class="version">'.$this->getLang('available_version').' '; - $return .= ($extension->getLastUpdate() ? hsc($extension->getLastUpdate()) : $this->getLang('unknown')).'</span>'; + $return .= ($extension->getLastUpdate() + ? hsc($extension->getLastUpdate()) + : $this->getLang('unknown')).'</span>'; } return $return.' '.$errors.DOKU_LF; @@ -522,7 +577,8 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param helper_plugin_extension_extension $extension The extension * @return string The HTML code */ - function make_action($action, $extension) { + public function makeAction($action, $extension) + { $title = ''; switch ($action) { @@ -535,7 +591,8 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { $classes = 'button '.$action; $name = 'fn['.$action.']['.hsc($extension->getID()).']'; - return '<button class="'.$classes.'" name="'.$name.'" type="submit" '.$title.'>'.$this->getLang('btn_'.$action).'</button> '; + return '<button class="'.$classes.'" name="'.$name.'" type="submit" '.$title.'>'. + $this->getLang('btn_'.$action).'</button> '; } /** @@ -544,7 +601,8 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { * @param helper_plugin_extension_extension $extension The extension * @return string The description of all relevant statusses */ - function make_status(helper_plugin_extension_extension $extension) { + public function makeStatus(helper_plugin_extension_extension $extension) + { $status = array(); @@ -553,15 +611,16 @@ class helper_plugin_extension_list extends DokuWiki_Plugin { if ($extension->isProtected()) { $status[] = $this->getLang('status_protected'); } else { - $status[] = $extension->isEnabled() ? $this->getLang('status_enabled') : $this->getLang('status_disabled'); + $status[] = $extension->isEnabled() + ? $this->getLang('status_enabled') + : $this->getLang('status_disabled'); } } else { $status[] = $this->getLang('status_not_installed'); } - if(!$extension->canModify()) $status[] = $this->getLang('status_unmodifiable'); - if($extension->isBundled()) $status[] = $this->getLang('status_bundled'); + if (!$extension->canModify()) $status[] = $this->getLang('status_unmodifiable'); + if ($extension->isBundled()) $status[] = $this->getLang('status_bundled'); $status[] = $extension->isTemplate() ? $this->getLang('status_template') : $this->getLang('status_plugin'); return join(', ', $status); } - } diff --git a/lib/plugins/extension/helper/repository.php b/lib/plugins/extension/helper/repository.php index 99deb5b27..712baa05c 100644 --- a/lib/plugins/extension/helper/repository.php +++ b/lib/plugins/extension/helper/repository.php @@ -6,34 +6,39 @@ * @author Michael Hamann <michael@content-space.de> */ -#define('EXTENSION_REPOSITORY_API', 'http://localhost/dokuwiki/lib/plugins/pluginrepo/api.php'); - -if (!defined('EXTENSION_REPOSITORY_API_ENDPOINT')) - define('EXTENSION_REPOSITORY_API', 'http://www.dokuwiki.org/lib/plugins/pluginrepo/api.php'); - -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); +use dokuwiki\Cache\Cache; +use dokuwiki\HTTP\DokuHTTPClient; +use dokuwiki\Extension\PluginController; /** * Class helper_plugin_extension_repository provides access to the extension repository on dokuwiki.org */ -class helper_plugin_extension_repository extends DokuWiki_Plugin { +class helper_plugin_extension_repository extends DokuWiki_Plugin +{ + + const EXTENSION_REPOSITORY_API = 'http://www.dokuwiki.org/lib/plugins/pluginrepo/api.php'; + private $loaded_extensions = array(); private $has_access = null; + /** * Initialize the repository (cache), fetches data for all installed plugins */ - public function init() { - /* @var Doku_Plugin_Controller $plugin_controller */ + public function init() + { + /* @var PluginController $plugin_controller */ global $plugin_controller; if ($this->hasAccess()) { $list = $plugin_controller->getList('', true); $request_data = array('fmt' => 'php'); $request_needed = false; foreach ($list as $name) { - $cache = new cache('##extension_manager##'.$name, '.repo'); + $cache = new Cache('##extension_manager##'.$name, '.repo'); - if (!isset($this->loaded_extensions[$name]) && $this->hasAccess() && !$cache->useCache(array('age' => 3600 * 24))) { + if (!isset($this->loaded_extensions[$name]) && + $this->hasAccess() && + !$cache->useCache(array('age' => 3600 * 24)) + ) { $this->loaded_extensions[$name] = true; $request_data['ext'][] = $name; $request_needed = true; @@ -42,11 +47,11 @@ class helper_plugin_extension_repository extends DokuWiki_Plugin { if ($request_needed) { $httpclient = new DokuHTTPClient(); - $data = $httpclient->post(EXTENSION_REPOSITORY_API, $request_data); + $data = $httpclient->post(self::EXTENSION_REPOSITORY_API, $request_data); if ($data !== false) { $extensions = unserialize($data); foreach ($extensions as $extension) { - $cache = new cache('##extension_manager##'.$extension['plugin'], '.repo'); + $cache = new Cache('##extension_manager##'.$extension['plugin'], '.repo'); $cache->storeCache(serialize($extension)); } } else { @@ -64,12 +69,12 @@ class helper_plugin_extension_repository extends DokuWiki_Plugin { */ public function hasAccess($usecache = true) { if ($this->has_access === null) { - $cache = new cache('##extension_manager###hasAccess', '.repo'); + $cache = new Cache('##extension_manager###hasAccess', '.repo'); if (!$cache->useCache(array('age' => 60*10, 'purge' => !$usecache))) { $httpclient = new DokuHTTPClient(); $httpclient->timeout = 5; - $data = $httpclient->get(EXTENSION_REPOSITORY_API.'?cmd=ping'); + $data = $httpclient->get(self::EXTENSION_REPOSITORY_API.'?cmd=ping'); if ($data !== false) { $this->has_access = true; $cache->storeCache(1); @@ -90,13 +95,17 @@ class helper_plugin_extension_repository extends DokuWiki_Plugin { * @param string $name The plugin name to get the data for, template names need to be prefix by 'template:' * @return array The data or null if nothing was found (possibly no repository access) */ - public function getData($name) { - $cache = new cache('##extension_manager##'.$name, '.repo'); - - if (!isset($this->loaded_extensions[$name]) && $this->hasAccess() && !$cache->useCache(array('age' => 3600 * 24))) { + public function getData($name) + { + $cache = new Cache('##extension_manager##'.$name, '.repo'); + + if (!isset($this->loaded_extensions[$name]) && + $this->hasAccess() && + !$cache->useCache(array('age' => 3600 * 24)) + ) { $this->loaded_extensions[$name] = true; $httpclient = new DokuHTTPClient(); - $data = $httpclient->get(EXTENSION_REPOSITORY_API.'?fmt=php&ext[]='.urlencode($name)); + $data = $httpclient->get(self::EXTENSION_REPOSITORY_API.'?fmt=php&ext[]='.urlencode($name)); if ($data !== false) { $result = unserialize($data); $cache->storeCache(serialize($result[0])); @@ -117,21 +126,22 @@ class helper_plugin_extension_repository extends DokuWiki_Plugin { * @param string $q the query string * @return array a list of matching extensions */ - public function search($q){ - $query = $this->parse_query($q); + public function search($q) + { + $query = $this->parseQuery($q); $query['fmt'] = 'php'; $httpclient = new DokuHTTPClient(); - $data = $httpclient->post(EXTENSION_REPOSITORY_API, $query); + $data = $httpclient->post(self::EXTENSION_REPOSITORY_API, $query); if ($data === false) return array(); $result = unserialize($data); $ids = array(); // store cache info for each extension - foreach($result as $ext){ + foreach ($result as $ext) { $name = $ext['plugin']; - $cache = new cache('##extension_manager##'.$name, '.repo'); + $cache = new Cache('##extension_manager##'.$name, '.repo'); $cache->storeCache(serialize($ext)); $ids[] = $name; } @@ -145,7 +155,8 @@ class helper_plugin_extension_repository extends DokuWiki_Plugin { * @param string $q * @return array */ - protected function parse_query($q){ + protected function parseQuery($q) + { $parameters = array( 'tag' => array(), 'mail' => array(), @@ -154,29 +165,29 @@ class helper_plugin_extension_repository extends DokuWiki_Plugin { ); // extract tags - if(preg_match_all('/(^|\s)(tag:([\S]+))/', $q, $matches, PREG_SET_ORDER)){ - foreach($matches as $m){ + if (preg_match_all('/(^|\s)(tag:([\S]+))/', $q, $matches, PREG_SET_ORDER)) { + foreach ($matches as $m) { $q = str_replace($m[2], '', $q); $parameters['tag'][] = $m[3]; } } // extract author ids - if(preg_match_all('/(^|\s)(authorid:([\S]+))/', $q, $matches, PREG_SET_ORDER)){ - foreach($matches as $m){ + if (preg_match_all('/(^|\s)(authorid:([\S]+))/', $q, $matches, PREG_SET_ORDER)) { + foreach ($matches as $m) { $q = str_replace($m[2], '', $q); $parameters['mail'][] = $m[3]; } } // extract extensions - if(preg_match_all('/(^|\s)(ext:([\S]+))/', $q, $matches, PREG_SET_ORDER)){ - foreach($matches as $m){ + if (preg_match_all('/(^|\s)(ext:([\S]+))/', $q, $matches, PREG_SET_ORDER)) { + foreach ($matches as $m) { $q = str_replace($m[2], '', $q); $parameters['ext'][] = $m[3]; } } // extract types - if(preg_match_all('/(^|\s)(type:([\S]+))/', $q, $matches, PREG_SET_ORDER)){ - foreach($matches as $m){ + if (preg_match_all('/(^|\s)(type:([\S]+))/', $q, $matches, PREG_SET_ORDER)) { + foreach ($matches as $m) { $q = str_replace($m[2], '', $q); $parameters['type'][] = $m[3]; } diff --git a/lib/plugins/info/syntax.php b/lib/plugins/info/syntax.php index 773256faf..3fa35e733 100644 --- a/lib/plugins/info/syntax.php +++ b/lib/plugins/info/syntax.php @@ -6,33 +6,30 @@ * @author Andreas Gohr <andi@splitbrain.org> * @author Esther Brunner <wikidesign@gmail.com> */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - -/** - * All DokuWiki plugins to extend the parser/rendering mechanism - * need to inherit from this class - */ -class syntax_plugin_info extends DokuWiki_Syntax_Plugin { +class syntax_plugin_info extends DokuWiki_Syntax_Plugin +{ /** * What kind of syntax are we? */ - function getType(){ + public function getType() + { return 'substition'; } /** * What about paragraphs? */ - function getPType(){ + public function getPType() + { return 'block'; } /** * Where to sort in? */ - function getSort(){ + public function getSort() + { return 155; } @@ -40,8 +37,9 @@ class syntax_plugin_info extends DokuWiki_Syntax_Plugin { /** * Connect pattern to lexer */ - function connectTo($mode) { - $this->Lexer->addSpecialPattern('~~INFO:\w+~~',$mode,'plugin_info'); + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern('~~INFO:\w+~~', $mode, 'plugin_info'); } /** @@ -53,8 +51,9 @@ class syntax_plugin_info extends DokuWiki_Syntax_Plugin { * @param Doku_Handler $handler The Doku_Handler object * @return array Return an array with all data you want to use in render */ - function handle($match, $state, $pos, Doku_Handler $handler){ - $match = substr($match,7,-2); //strip ~~INFO: from start and ~~ from end + public function handle($match, $state, $pos, Doku_Handler $handler) + { + $match = substr($match, 7, -2); //strip ~~INFO: from start and ~~ from end return array(strtolower($match)); } @@ -66,40 +65,41 @@ class syntax_plugin_info extends DokuWiki_Syntax_Plugin { * @param array $data data created by handler() * @return boolean rendered correctly? */ - function render($format, Doku_Renderer $renderer, $data) { - if($format == 'xhtml'){ + public function render($format, Doku_Renderer $renderer, $data) + { + if ($format == 'xhtml') { /** @var Doku_Renderer_xhtml $renderer */ //handle various info stuff - switch ($data[0]){ + switch ($data[0]) { case 'syntaxmodes': - $renderer->doc .= $this->_syntaxmodes_xhtml(); + $renderer->doc .= $this->renderSyntaxModes(); break; case 'syntaxtypes': - $renderer->doc .= $this->_syntaxtypes_xhtml(); + $renderer->doc .= $this->renderSyntaxTypes(); break; case 'syntaxplugins': - $this->_plugins_xhtml('syntax', $renderer); + $this->renderPlugins('syntax', $renderer); break; case 'adminplugins': - $this->_plugins_xhtml('admin', $renderer); + $this->renderPlugins('admin', $renderer); break; case 'actionplugins': - $this->_plugins_xhtml('action', $renderer); + $this->renderPlugins('action', $renderer); break; case 'rendererplugins': - $this->_plugins_xhtml('renderer', $renderer); + $this->renderPlugins('renderer', $renderer); break; case 'helperplugins': - $this->_plugins_xhtml('helper', $renderer); + $this->renderPlugins('helper', $renderer); break; case 'authplugins': - $this->_plugins_xhtml('auth', $renderer); + $this->renderPlugins('auth', $renderer); break; case 'remoteplugins': - $this->_plugins_xhtml('remote', $renderer); + $this->renderPlugins('remote', $renderer); break; case 'helpermethods': - $this->_helpermethods_xhtml($renderer); + $this->renderHelperMethods($renderer); break; default: $renderer->doc .= "no info about ".htmlspecialchars($data[0]); @@ -117,7 +117,8 @@ class syntax_plugin_info extends DokuWiki_Syntax_Plugin { * @param string $type * @param Doku_Renderer_xhtml $renderer */ - function _plugins_xhtml($type, Doku_Renderer_xhtml $renderer){ + protected function renderPlugins($type, Doku_Renderer_xhtml $renderer) + { global $lang; $renderer->doc .= '<ul>'; @@ -125,24 +126,24 @@ class syntax_plugin_info extends DokuWiki_Syntax_Plugin { $plginfo = array(); // remove subparts - foreach($plugins as $p){ - if (!$po = plugin_load($type,$p)) continue; - list($name,/* $part */) = explode('_',$p,2); + foreach ($plugins as $p) { + if (!$po = plugin_load($type, $p)) continue; + list($name,/* $part */) = explode('_', $p, 2); $plginfo[$name] = $po->getInfo(); } // list them - foreach($plginfo as $info){ + foreach ($plginfo as $info) { $renderer->doc .= '<li><div class="li">'; - $renderer->externallink($info['url'],$info['name']); + $renderer->externallink($info['url'], $info['name']); $renderer->doc .= ' '; $renderer->doc .= '<em>'.$info['date'].'</em>'; $renderer->doc .= ' '; $renderer->doc .= $lang['by']; $renderer->doc .= ' '; - $renderer->emaillink($info['email'],$info['author']); + $renderer->emaillink($info['email'], $info['author']); $renderer->doc .= '<br />'; - $renderer->doc .= strtr(hsc($info['desc']),array("\n"=>"<br />")); + $renderer->doc .= strtr(hsc($info['desc']), array("\n"=>"<br />")); $renderer->doc .= '</div></li>'; unset($po); } @@ -157,39 +158,40 @@ class syntax_plugin_info extends DokuWiki_Syntax_Plugin { * * @param Doku_Renderer_xhtml $renderer */ - function _helpermethods_xhtml(Doku_Renderer_xhtml $renderer){ + protected function renderHelperMethods(Doku_Renderer_xhtml $renderer) + { $plugins = plugin_list('helper'); - foreach($plugins as $p){ - if (!$po = plugin_load('helper',$p)) continue; + foreach ($plugins as $p) { + if (!$po = plugin_load('helper', $p)) continue; if (!method_exists($po, 'getMethods')) continue; $methods = $po->getMethods(); $info = $po->getInfo(); - $hid = $this->_addToTOC($info['name'], 2, $renderer); + $hid = $this->addToToc($info['name'], 2, $renderer); $doc = '<h2><a name="'.$hid.'" id="'.$hid.'">'.hsc($info['name']).'</a></h2>'; $doc .= '<div class="level2">'; $doc .= '<p>'.strtr(hsc($info['desc']), array("\n"=>"<br />")).'</p>'; $doc .= '<pre class="code">$'.$p." = plugin_load('helper', '".$p."');</pre>"; $doc .= '</div>'; - foreach ($methods as $method){ + foreach ($methods as $method) { $title = '$'.$p.'->'.$method['name'].'()'; - $hid = $this->_addToTOC($title, 3, $renderer); + $hid = $this->addToToc($title, 3, $renderer); $doc .= '<h3><a name="'.$hid.'" id="'.$hid.'">'.hsc($title).'</a></h3>'; $doc .= '<div class="level3">'; $doc .= '<div class="table"><table class="inline"><tbody>'; $doc .= '<tr><th>Description</th><td colspan="2">'.$method['desc']. '</td></tr>'; - if ($method['params']){ + if ($method['params']) { $c = count($method['params']); $doc .= '<tr><th rowspan="'.$c.'">Parameters</th><td>'; $params = array(); - foreach ($method['params'] as $desc => $type){ + foreach ($method['params'] as $desc => $type) { $params[] = hsc($desc).'</td><td>'.hsc($type); } $doc .= join($params, '</td></tr><tr><td>').'</td></tr>'; } - if ($method['return']){ + if ($method['return']) { $doc .= '<tr><th>Return value</th><td>'.hsc(key($method['return'])). '</td><td>'.hsc(current($method['return'])).'</td></tr>'; } @@ -207,18 +209,19 @@ class syntax_plugin_info extends DokuWiki_Syntax_Plugin { * * @return string */ - function _syntaxtypes_xhtml(){ + protected function renderSyntaxTypes() + { global $PARSER_MODES; $doc = ''; $doc .= '<div class="table"><table class="inline"><tbody>'; - foreach($PARSER_MODES as $mode => $modes){ + foreach ($PARSER_MODES as $mode => $modes) { $doc .= '<tr>'; $doc .= '<td class="leftalign">'; $doc .= $mode; $doc .= '</td>'; $doc .= '<td class="leftalign">'; - $doc .= join(', ',$modes); + $doc .= join(', ', $modes); $doc .= '</td>'; $doc .= '</tr>'; } @@ -231,29 +234,30 @@ class syntax_plugin_info extends DokuWiki_Syntax_Plugin { * * @return string */ - function _syntaxmodes_xhtml(){ + protected function renderSyntaxModes() + { $modes = p_get_parsermodes(); $compactmodes = array(); - foreach($modes as $mode){ + foreach ($modes as $mode) { $compactmodes[$mode['sort']][] = $mode['mode']; } $doc = ''; $doc .= '<div class="table"><table class="inline"><tbody>'; - foreach($compactmodes as $sort => $modes){ + foreach ($compactmodes as $sort => $modes) { $rowspan = ''; - if(count($modes) > 1) { + if (count($modes) > 1) { $rowspan = ' rowspan="'.count($modes).'"'; } - foreach($modes as $index => $mode) { + foreach ($modes as $index => $mode) { $doc .= '<tr>'; $doc .= '<td class="leftalign">'; $doc .= $mode; $doc .= '</td>'; - if($index === 0) { + if ($index === 0) { $doc .= '<td class="rightalign" '.$rowspan.'>'; $doc .= $sort; $doc .= '</td>'; @@ -274,11 +278,12 @@ class syntax_plugin_info extends DokuWiki_Syntax_Plugin { * @param Doku_Renderer_xhtml $renderer * @return string */ - protected function _addToTOC($text, $level, Doku_Renderer_xhtml $renderer){ + protected function addToToc($text, $level, Doku_Renderer_xhtml $renderer) + { global $conf; $hid = ''; - if (($level >= $conf['toptoclevel']) && ($level <= $conf['maxtoclevel'])){ + if (($level >= $conf['toptoclevel']) && ($level <= $conf['maxtoclevel'])) { $hid = $renderer->_headerToLink($text, true); $renderer->toc[] = array( 'hid' => $hid, diff --git a/lib/plugins/popularity/action.php b/lib/plugins/popularity/action.php index d5ec0f5c5..fac610735 100644 --- a/lib/plugins/popularity/action.php +++ b/lib/plugins/popularity/action.php @@ -5,44 +5,49 @@ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) */ -require_once(DOKU_PLUGIN.'action.php'); -require_once(DOKU_PLUGIN.'popularity/admin.php'); - -class action_plugin_popularity extends Dokuwiki_Action_Plugin { +class action_plugin_popularity extends DokuWiki_Action_Plugin +{ /** * @var helper_plugin_popularity */ - var $helper; + protected $helper; - function __construct(){ + public function __construct() + { $this->helper = $this->loadHelper('popularity', false); } - /** - * Register its handlers with the dokuwiki's event controller - */ - function register(Doku_Event_Handler $controller) { - $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, '_autosubmit', array()); + /** @inheritdoc */ + public function register(Doku_Event_Handler $controller) + { + $controller->register_hook('INDEXER_TASKS_RUN', 'AFTER', $this, 'autosubmit', array()); } - function _autosubmit(Doku_Event &$event, $param){ + /** + * Event handler + * + * @param Doku_Event $event + * @param $param + */ + public function autosubmit(Doku_Event &$event, $param) + { //Do we have to send the data now - if ( !$this->helper->isAutosubmitEnabled() || $this->_isTooEarlyToSubmit() ){ + if (!$this->helper->isAutosubmitEnabled() || $this->isTooEarlyToSubmit()) { return; } //Actually send it - $status = $this->helper->sendData( $this->helper->gatherAsString() ); + $status = $this->helper->sendData($this->helper->gatherAsString()); - if ( $status !== '' ){ + if ($status !== '') { //If an error occured, log it - io_saveFile( $this->helper->autosubmitErrorFile, $status ); + io_saveFile($this->helper->autosubmitErrorFile, $status); } else { //If the data has been sent successfully, previous log of errors are useless @unlink($this->helper->autosubmitErrorFile); //Update the last time we sent data - touch ( $this->helper->autosubmitFile ); + touch($this->helper->autosubmitFile); } $event->stopPropagation(); @@ -53,7 +58,8 @@ class action_plugin_popularity extends Dokuwiki_Action_Plugin { * Check if it's time to send autosubmit data * (we should have check if autosubmit is enabled first) */ - function _isTooEarlyToSubmit(){ + protected function isTooEarlyToSubmit() + { $lastSubmit = $this->helper->lastSentTime(); return $lastSubmit + 24*60*60*30 > time(); } diff --git a/lib/plugins/popularity/admin.php b/lib/plugins/popularity/admin.php index 0cf174e0d..61d8cc3bf 100644 --- a/lib/plugins/popularity/admin.php +++ b/lib/plugins/popularity/admin.php @@ -5,43 +5,44 @@ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Gohr <andi@splitbrain.org> */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); +class admin_plugin_popularity extends DokuWiki_Admin_Plugin +{ -/** - * All DokuWiki plugins to extend the admin function - * need to inherit from this class - */ -class admin_plugin_popularity extends DokuWiki_Admin_Plugin { + /** @var helper_plugin_popularity */ + protected $helper; + protected $sentStatus = null; /** - * @var helper_plugin_popularity + * admin_plugin_popularity constructor. */ - var $helper; - var $sentStatus = null; - - function __construct(){ + public function __construct() + { $this->helper = $this->loadHelper('popularity', false); } /** * return prompt for admin menu + * @param $language + * @return string */ - function getMenuText($language) { + public function getMenuText($language) + { return $this->getLang('name'); } /** * return sort order for position in admin menu */ - function getMenuSort() { + public function getMenuSort() + { return 2000; } /** * Accessible for managers */ - function forAdminOnly() { + public function forAdminOnly() + { return false; } @@ -49,18 +50,19 @@ class admin_plugin_popularity extends DokuWiki_Admin_Plugin { /** * handle user request */ - function handle() { + public function handle() + { global $INPUT; //Send the data - if ( $INPUT->has('data') ){ - $this->sentStatus = $this->helper->sendData( $INPUT->str('data') ); - if ( $this->sentStatus === '' ){ + if ($INPUT->has('data')) { + $this->sentStatus = $this->helper->sendData($INPUT->str('data')); + if ($this->sentStatus === '') { //Update the last time we sent the data - touch ( $this->helper->popularityLastSubmitFile ); + touch($this->helper->popularityLastSubmitFile); } //Deal with the autosubmit option - $this->_enableAutosubmit( $INPUT->has('autosubmit') ); + $this->enableAutosubmit($INPUT->has('autosubmit')); } } @@ -68,9 +70,10 @@ class admin_plugin_popularity extends DokuWiki_Admin_Plugin { * Enable or disable autosubmit * @param bool $enable If TRUE, it will enable autosubmit. Else, it will disable it. */ - function _enableAutosubmit( $enable ){ - if ( $enable ){ - io_saveFile( $this->helper->autosubmitFile, ' '); + protected function enableAutosubmit($enable) + { + if ($enable) { + io_saveFile($this->helper->autosubmitFile, ' '); } else { @unlink($this->helper->autosubmitFile); } @@ -79,17 +82,18 @@ class admin_plugin_popularity extends DokuWiki_Admin_Plugin { /** * Output HTML form */ - function html() { + public function html() + { global $INPUT; - if ( ! $INPUT->has('data') ){ + if (! $INPUT->has('data')) { echo $this->locale_xhtml('intro'); //If there was an error the last time we tried to autosubmit, warn the user - if ( $this->helper->isAutoSubmitEnabled() ){ - if ( file_exists($this->helper->autosubmitErrorFile) ){ + if ($this->helper->isAutoSubmitEnabled()) { + if (file_exists($this->helper->autosubmitErrorFile)) { echo $this->getLang('autosubmitError'); - echo io_readFile( $this->helper->autosubmitErrorFile ); + echo io_readFile($this->helper->autosubmitErrorFile); } } @@ -98,12 +102,12 @@ class admin_plugin_popularity extends DokuWiki_Admin_Plugin { //Print the last time the data was sent $lastSent = $this->helper->lastSentTime(); - if ( $lastSent !== 0 ){ + if ($lastSent !== 0) { echo $this->getLang('lastSent') . ' ' . datetime_h($lastSent); } } else { //If we just submitted the form - if ( $this->sentStatus === '' ){ + if ($this->sentStatus === '') { //If we successfully sent the data echo $this->locale_xhtml('submitted'); } else { @@ -122,9 +126,10 @@ class admin_plugin_popularity extends DokuWiki_Admin_Plugin { * @param string $data The popularity data, if it has already been computed. NULL otherwise. * @return string The form, as an html string */ - function buildForm($submissionMode, $data = null){ + protected function buildForm($submissionMode, $data = null) + { $url = ($submissionMode === 'browser' ? $this->helper->submitUrl : script()); - if ( is_null($data) ){ + if (is_null($data)) { $data = $this->helper->gatherAsString(); } @@ -135,7 +140,7 @@ class admin_plugin_popularity extends DokuWiki_Admin_Plugin { .'</textarea><br />'; //If we submit via the server, we give the opportunity to suscribe to the autosubmission option - if ( $submissionMode !== 'browser' ){ + if ($submissionMode !== 'browser') { $form .= '<label for="autosubmit">' .'<input type="checkbox" name="autosubmit" id="autosubmit" ' .($this->helper->isAutosubmitEnabled() ? 'checked' : '' ) diff --git a/lib/plugins/popularity/helper.php b/lib/plugins/popularity/helper.php index b81ab7005..4537976ae 100644 --- a/lib/plugins/popularity/helper.php +++ b/lib/plugins/popularity/helper.php @@ -1,36 +1,43 @@ <?php + +use dokuwiki\HTTP\DokuHTTPClient; +use dokuwiki\Extension\Event; + /** * Popularity Feedback Plugin * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) */ - -class helper_plugin_popularity extends Dokuwiki_Plugin { +class helper_plugin_popularity extends Dokuwiki_Plugin +{ /** * The url where the data should be sent */ - var $submitUrl = 'http://update.dokuwiki.org/popularity.php'; + public $submitUrl = 'http://update.dokuwiki.org/popularity.php'; /** * Name of the file which determine if the the autosubmit is enabled, * and when it was submited for the last time */ - var $autosubmitFile; + public $autosubmitFile; /** * File where the last error which happened when we tried to autosubmit, will be log */ - var $autosubmitErrorFile; + public $autosubmitErrorFile; /** * Name of the file which determine when the popularity data was manually * submitted for the last time * (If this file doesn't exist, the data has never been sent) */ - var $popularityLastSubmitFile; + public $popularityLastSubmitFile; - - function __construct(){ + /** + * helper_plugin_popularity constructor. + */ + public function __construct() + { global $conf; $this->autosubmitFile = $conf['cachedir'].'/autosubmit.txt'; $this->autosubmitErrorFile = $conf['cachedir'].'/autosubmitError.txt'; @@ -38,46 +45,12 @@ class helper_plugin_popularity extends Dokuwiki_Plugin { } /** - * Return methods of this helper - * - * @return array with methods description - */ - function getMethods(){ - $result = array(); - $result[] = array( - 'name' => 'isAutoSubmitEnabled', - 'desc' => 'Check if autosubmit is enabled', - 'params' => array(), - 'return' => array('result' => 'bool') - ); - $result[] = array( - 'name' => 'sendData', - 'desc' => 'Send the popularity data', - 'params' => array('data' => 'string'), - 'return' => array() - ); - $result[] = array( - 'name' => 'gatherAsString', - 'desc' => 'Gather the popularity data', - 'params' => array(), - 'return' => array('data' => 'string') - ); - $result[] = array( - 'name' => 'lastSentTime', - 'desc' => 'Compute the last time popularity data was sent', - 'params' => array(), - 'return' => array('data' => 'int') - ); - return $result; - - } - - /** * Check if autosubmit is enabled * * @return boolean TRUE if we should send data once a month, FALSE otherwise */ - function isAutoSubmitEnabled(){ + public function isAutoSubmitEnabled() + { return file_exists($this->autosubmitFile); } @@ -87,11 +60,12 @@ class helper_plugin_popularity extends Dokuwiki_Plugin { * @param string $data The popularity data * @return string An empty string if everything worked fine, a string describing the error otherwise */ - function sendData($data){ + public function sendData($data) + { $error = ''; $httpClient = new DokuHTTPClient(); $status = $httpClient->sendRequest($this->submitUrl, array('data' => $data), 'POST'); - if ( ! $status ){ + if (! $status) { $error = $httpClient->error; } return $error; @@ -102,7 +76,8 @@ class helper_plugin_popularity extends Dokuwiki_Plugin { * * @return int */ - function lastSentTime(){ + public function lastSentTime() + { $manualSubmission = @filemtime($this->popularityLastSubmitFile); $autoSubmission = @filemtime($this->autosubmitFile); @@ -114,13 +89,14 @@ class helper_plugin_popularity extends Dokuwiki_Plugin { * * @return string The popularity data as a string */ - function gatherAsString(){ - $data = $this->_gather(); + public function gatherAsString() + { + $data = $this->gather(); $string = ''; - foreach($data as $key => $val){ - if(is_array($val)) foreach($val as $v){ + foreach ($data as $key => $val) { + if (is_array($val)) foreach ($val as $v) { $string .= hsc($key)."\t".hsc($v)."\n"; - }else{ + } else { $string .= hsc($key)."\t".hsc($val)."\n"; } } @@ -132,7 +108,8 @@ class helper_plugin_popularity extends Dokuwiki_Plugin { * * @return array The popularity data as an array */ - function _gather(){ + protected function gather() + { global $conf; /** @var $auth DokuWiki_Auth_Plugin */ global $auth; @@ -156,81 +133,81 @@ class helper_plugin_popularity extends Dokuwiki_Plugin { // number and size of pages $list = array(); - search($list,$conf['datadir'],array($this,'_search_count'),array('all'=>false),''); + search($list, $conf['datadir'], array($this, 'searchCountCallback'), array('all'=>false), ''); $data['page_count'] = $list['file_count']; $data['page_size'] = $list['file_size']; $data['page_biggest'] = $list['file_max']; $data['page_smallest'] = $list['file_min']; $data['page_nscount'] = $list['dir_count']; $data['page_nsnest'] = $list['dir_nest']; - if($list['file_count']) $data['page_avg'] = $list['file_size'] / $list['file_count']; + if ($list['file_count']) $data['page_avg'] = $list['file_size'] / $list['file_count']; $data['page_oldest'] = $list['file_oldest']; unset($list); // number and size of media $list = array(); - search($list,$conf['mediadir'],array($this,'_search_count'),array('all'=>true)); + search($list, $conf['mediadir'], array($this, 'searchCountCallback'), array('all'=>true)); $data['media_count'] = $list['file_count']; $data['media_size'] = $list['file_size']; $data['media_biggest'] = $list['file_max']; $data['media_smallest'] = $list['file_min']; $data['media_nscount'] = $list['dir_count']; $data['media_nsnest'] = $list['dir_nest']; - if($list['file_count']) $data['media_avg'] = $list['file_size'] / $list['file_count']; + if ($list['file_count']) $data['media_avg'] = $list['file_size'] / $list['file_count']; unset($list); // number and size of cache $list = array(); - search($list,$conf['cachedir'],array($this,'_search_count'),array('all'=>true)); + search($list, $conf['cachedir'], array($this, 'searchCountCallback'), array('all'=>true)); $data['cache_count'] = $list['file_count']; $data['cache_size'] = $list['file_size']; $data['cache_biggest'] = $list['file_max']; $data['cache_smallest'] = $list['file_min']; - if($list['file_count']) $data['cache_avg'] = $list['file_size'] / $list['file_count']; + if ($list['file_count']) $data['cache_avg'] = $list['file_size'] / $list['file_count']; unset($list); // number and size of index $list = array(); - search($list,$conf['indexdir'],array($this,'_search_count'),array('all'=>true)); + search($list, $conf['indexdir'], array($this, 'searchCountCallback'), array('all'=>true)); $data['index_count'] = $list['file_count']; $data['index_size'] = $list['file_size']; $data['index_biggest'] = $list['file_max']; $data['index_smallest'] = $list['file_min']; - if($list['file_count']) $data['index_avg'] = $list['file_size'] / $list['file_count']; + if ($list['file_count']) $data['index_avg'] = $list['file_size'] / $list['file_count']; unset($list); // number and size of meta $list = array(); - search($list,$conf['metadir'],array($this,'_search_count'),array('all'=>true)); + search($list, $conf['metadir'], array($this, 'searchCountCallback'), array('all'=>true)); $data['meta_count'] = $list['file_count']; $data['meta_size'] = $list['file_size']; $data['meta_biggest'] = $list['file_max']; $data['meta_smallest'] = $list['file_min']; - if($list['file_count']) $data['meta_avg'] = $list['file_size'] / $list['file_count']; + if ($list['file_count']) $data['meta_avg'] = $list['file_size'] / $list['file_count']; unset($list); // number and size of attic $list = array(); - search($list,$conf['olddir'],array($this,'_search_count'),array('all'=>true)); + search($list, $conf['olddir'], array($this, 'searchCountCallback'), array('all'=>true)); $data['attic_count'] = $list['file_count']; $data['attic_size'] = $list['file_size']; $data['attic_biggest'] = $list['file_max']; $data['attic_smallest'] = $list['file_min']; - if($list['file_count']) $data['attic_avg'] = $list['file_size'] / $list['file_count']; + if ($list['file_count']) $data['attic_avg'] = $list['file_size'] / $list['file_count']; $data['attic_oldest'] = $list['file_oldest']; unset($list); // user count - if($auth && $auth->canDo('getUserCount')){ + if ($auth && $auth->canDo('getUserCount')) { $data['user_count'] = $auth->getUserCount(); } // calculate edits per day $list = @file($conf['metadir'].'/_dokuwiki.changes'); $count = count($list); - if($count > 2){ - $first = (int) substr(array_shift($list),0,10); - $last = (int) substr(array_pop($list),0,10); + if ($count > 2) { + $first = (int) substr(array_shift($list), 0, 10); + $last = (int) substr(array_pop($list), 0, 10); $dur = ($last - $first)/(60*60*24); // number of days in the changelog $data['edits_per_day'] = $count/$dur; } @@ -240,7 +217,7 @@ class helper_plugin_popularity extends Dokuwiki_Plugin { $data['plugin'] = plugin_list(); // pcre info - if(defined('PCRE_VERSION')) $data['pcre_version'] = PCRE_VERSION; + if (defined('PCRE_VERSION')) $data['pcre_version'] = PCRE_VERSION; $data['pcre_backtrack'] = ini_get('pcre.backtrack_limit'); $data['pcre_recursion'] = ini_get('pcre.recursion_limit'); @@ -249,27 +226,33 @@ class helper_plugin_popularity extends Dokuwiki_Plugin { $data['webserver'] = $_SERVER['SERVER_SOFTWARE']; $data['php_version'] = phpversion(); $data['php_sapi'] = php_sapi_name(); - $data['php_memory'] = $this->_to_byte(ini_get('memory_limit')); + $data['php_memory'] = php_to_byte(ini_get('memory_limit')); $data['php_exectime'] = $phptime; $data['php_extension'] = get_loaded_extensions(); // plugin usage data - $this->_add_plugin_usage_data($data); + $this->addPluginUsageData($data); return $data; } - protected function _add_plugin_usage_data(&$data){ + /** + * Triggers event to let plugins add their own data + * + * @param $data + */ + protected function addPluginUsageData(&$data) + { $pluginsData = array(); - trigger_event('PLUGIN_POPULARITY_DATA_SETUP', $pluginsData); - foreach($pluginsData as $plugin => $d){ - if ( is_array($d) ) { - foreach($d as $key => $value){ - $data['plugin_' . $plugin . '_' . $key] = $value; - } - } else { - $data['plugin_' . $plugin] = $d; - } + Event::createAndTrigger('PLUGIN_POPULARITY_DATA_SETUP', $pluginsData); + foreach ($pluginsData as $plugin => $d) { + if (is_array($d)) { + foreach ($d as $key => $value) { + $data['plugin_' . $plugin . '_' . $key] = $value; + } + } else { + $data['plugin_' . $plugin] = $d; + } } } @@ -284,57 +267,26 @@ class helper_plugin_popularity extends Dokuwiki_Plugin { * @param array $opts option array as given to search() * @return bool */ - function _search_count(&$data,$base,$file,$type,$lvl,$opts){ + public function searchCountCallback(&$data, $base, $file, $type, $lvl, $opts) + { // traverse - if($type == 'd'){ - if($data['dir_nest'] < $lvl) $data['dir_nest'] = $lvl; + if ($type == 'd') { + if ($data['dir_nest'] < $lvl) $data['dir_nest'] = $lvl; $data['dir_count']++; return true; } //only search txt files if 'all' option not set - if($opts['all'] || substr($file,-4) == '.txt'){ + if ($opts['all'] || substr($file, -4) == '.txt') { $size = filesize($base.'/'.$file); $date = filemtime($base.'/'.$file); $data['file_count']++; $data['file_size'] += $size; - if(!isset($data['file_min']) || $data['file_min'] > $size) $data['file_min'] = $size; - if($data['file_max'] < $size) $data['file_max'] = $size; - if(!isset($data['file_oldest']) || $data['file_oldest'] > $date) $data['file_oldest'] = $date; + if (!isset($data['file_min']) || $data['file_min'] > $size) $data['file_min'] = $size; + if ($data['file_max'] < $size) $data['file_max'] = $size; + if (!isset($data['file_oldest']) || $data['file_oldest'] > $date) $data['file_oldest'] = $date; } return false; } - - /** - * Convert php.ini shorthands to byte - * - * @author <gilthans dot NO dot SPAM at gmail dot com> - * @link http://php.net/manual/en/ini.core.php#79564 - * - * @param string $v - * @return int|string - */ - function _to_byte($v){ - $l = substr($v, -1); - $ret = substr($v, 0, -1); - switch(strtoupper($l)){ - /** @noinspection PhpMissingBreakStatementInspection */ - case 'P': - $ret *= 1024; - /** @noinspection PhpMissingBreakStatementInspection */ - case 'T': - $ret *= 1024; - /** @noinspection PhpMissingBreakStatementInspection */ - case 'G': - $ret *= 1024; - /** @noinspection PhpMissingBreakStatementInspection */ - case 'M': - $ret *= 1024; - case 'K': - $ret *= 1024; - break; - } - return $ret; - } } diff --git a/lib/plugins/remote.php b/lib/plugins/remote.php index c2253dbd5..a3cbec722 100644 --- a/lib/plugins/remote.php +++ b/lib/plugins/remote.php @@ -1,104 +1,2 @@ <?php - -/** - * Class DokuWiki_Remote_Plugin - */ -abstract class DokuWiki_Remote_Plugin extends DokuWiki_Plugin { - - private $api; - - /** - * Constructor - */ - public function __construct() { - $this->api = new RemoteAPI(); - } - - /** - * Get all available methods with remote access. - * - * By default it exports all public methods of a remote plugin. Methods beginning - * with an underscore are skipped. - * - * @return array Information about all provided methods. {@see RemoteAPI}. - */ - public function _getMethods() { - $result = array(); - - $reflection = new \ReflectionClass($this); - foreach($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { - // skip parent methods, only methods further down are exported - $declaredin = $method->getDeclaringClass()->name; - if($declaredin == 'DokuWiki_Plugin' || $declaredin == 'DokuWiki_Remote_Plugin') continue; - $method_name = $method->name; - if(substr($method_name, 0, 1) == '_') continue; - - // strip asterisks - $doc = $method->getDocComment(); - $doc = preg_replace( - array('/^[ \t]*\/\*+[ \t]*/m', '/[ \t]*\*+[ \t]*/m', '/\*+\/\s*$/m','/\s*\/\s*$/m'), - array('', '', '', ''), - $doc - ); - - // prepare data - $data = array(); - $data['name'] = $method_name; - $data['public'] = 0; - $data['doc'] = $doc; - $data['args'] = array(); - - // get parameter type from doc block type hint - foreach($method->getParameters() as $parameter) { - $name = $parameter->name; - $type = 'string'; // we default to string - if(preg_match('/^@param[ \t]+([\w|\[\]]+)[ \t]\$'.$name.'/m', $doc, $m)){ - $type = $this->cleanTypeHint($m[1]); - } - $data['args'][] = $type; - } - - // get return type from doc block type hint - if(preg_match('/^@return[ \t]+([\w|\[\]]+)/m', $doc, $m)){ - $data['return'] = $this->cleanTypeHint($m[1]); - } else { - $data['return'] = 'string'; - } - - // add to result - $result[$method_name] = $data; - } - - return $result; - } - - /** - * Matches the given type hint against the valid options for the remote API - * - * @param string $hint - * @return string - */ - protected function cleanTypeHint($hint) { - $types = explode('|', $hint); - foreach($types as $t) { - if(substr($t, -2) == '[]') { - return 'array'; - } - if($t == 'boolean') { - return 'bool'; - } - if(in_array($t, array('array', 'string', 'int', 'double', 'bool', 'null', 'date', 'file'))) { - return $t; - } - } - return 'string'; - } - - /** - * @return RemoteAPI - */ - protected function getApi() { - return $this->api; - } - -} +dbg_deprecated('Autoloading. Do not require() files yourself.'); diff --git a/lib/plugins/revert/admin.php b/lib/plugins/revert/admin.php index 1a0300585..2d11dc05a 100644 --- a/lib/plugins/revert/admin.php +++ b/lib/plugins/revert/admin.php @@ -1,66 +1,73 @@ <?php -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); + +use dokuwiki\ChangeLog\PageChangeLog; /** * All DokuWiki plugins to extend the admin function * need to inherit from this class */ -class admin_plugin_revert extends DokuWiki_Admin_Plugin { - var $cmd; +class admin_plugin_revert extends DokuWiki_Admin_Plugin +{ + protected $cmd; // some vars which might need tuning later - var $max_lines = 800; // lines to read from changelog - var $max_revs = 20; // numer of old revisions to check + protected $max_lines = 800; // lines to read from changelog + protected $max_revs = 20; // numer of old revisions to check /** * Constructor */ - function __construct(){ + public function __construct() + { $this->setupLocale(); } /** * access for managers */ - function forAdminOnly(){ + public function forAdminOnly() + { return false; } /** * return sort order for position in admin menu */ - function getMenuSort() { + public function getMenuSort() + { return 40; } /** * handle user request */ - function handle() { + public function handle() + { } /** * output appropriate html */ - function html() { + public function html() + { global $INPUT; echo $this->locale_xhtml('intro'); - $this->_searchform(); + $this->printSearchForm(); - if(is_array($INPUT->param('revert')) && checkSecurityToken()){ - $this->_revert($INPUT->arr('revert'),$INPUT->str('filter')); - }elseif($INPUT->has('filter')){ - $this->_list($INPUT->str('filter')); + if (is_array($INPUT->param('revert')) && checkSecurityToken()) { + $this->revertEdits($INPUT->arr('revert'), $INPUT->str('filter')); + } elseif ($INPUT->has('filter')) { + $this->listEdits($INPUT->str('filter')); } } /** * Display the form for searching spam pages */ - function _searchform(){ + protected function printSearchForm() + { global $lang, $INPUT; echo '<form action="" method="post"><div class="no">'; echo '<label>'.$this->getLang('filter').': </label>'; @@ -73,31 +80,32 @@ class admin_plugin_revert extends DokuWiki_Admin_Plugin { /** * Start the reversion process */ - function _revert($revert,$filter){ + protected function revertEdits($revert, $filter) + { echo '<hr /><br />'; echo '<p>'.$this->getLang('revstart').'</p>'; echo '<ul>'; - foreach($revert as $id){ + foreach ($revert as $id) { global $REV; // find the last non-spammy revision $data = ''; $pagelog = new PageChangeLog($id); $old = $pagelog->getRevisions(0, $this->max_revs); - if(count($old)){ - foreach($old as $REV){ - $data = rawWiki($id,$REV); - if(strpos($data,$filter) === false) break; + if (count($old)) { + foreach ($old as $REV) { + $data = rawWiki($id, $REV); + if (strpos($data, $filter) === false) break; } } - if($data){ - saveWikiText($id,$data,'old revision restored',false); - printf('<li><div class="li">'.$this->getLang('reverted').'</div></li>',$id,$REV); - }else{ - saveWikiText($id,'','',false); - printf('<li><div class="li">'.$this->getLang('removed').'</div></li>',$id); + if ($data) { + saveWikiText($id, $data, 'old revision restored', false); + printf('<li><div class="li">'.$this->getLang('reverted').'</div></li>', $id, $REV); + } else { + saveWikiText($id, '', '', false); + printf('<li><div class="li">'.$this->getLang('removed').'</div></li>', $id); } @set_time_limit(10); flush(); @@ -110,7 +118,8 @@ class admin_plugin_revert extends DokuWiki_Admin_Plugin { /** * List recent edits matching the given filter */ - function _list($filter){ + protected function listEdits($filter) + { global $conf; global $lang; echo '<hr /><br />'; @@ -118,13 +127,13 @@ class admin_plugin_revert extends DokuWiki_Admin_Plugin { echo '<input type="hidden" name="filter" value="'.hsc($filter).'" />'; formSecurityToken(); - $recents = getRecents(0,$this->max_lines); + $recents = getRecents(0, $this->max_lines); echo '<ul>'; $cnt = 0; - foreach($recents as $recent){ - if($filter){ - if(strpos(rawWiki($recent['id']),$filter) === false) continue; + foreach ($recents as $recent) { + if ($filter) { + if (strpos(rawWiki($recent['id']), $filter) === false) continue; } $cnt++; @@ -132,10 +141,11 @@ class admin_plugin_revert extends DokuWiki_Admin_Plugin { echo ($recent['type']===DOKU_CHANGE_TYPE_MINOR_EDIT) ? '<li class="minor">' : '<li>'; echo '<div class="li">'; - echo '<input type="checkbox" name="revert[]" value="'.hsc($recent['id']).'" checked="checked" id="revert__'.$cnt.'" />'; + echo '<input type="checkbox" name="revert[]" value="'.hsc($recent['id']). + '" checked="checked" id="revert__'.$cnt.'" />'; echo ' <label for="revert__'.$cnt.'">'.$date.'</label> '; - echo '<a href="'.wl($recent['id'],"do=diff").'">'; + echo '<a href="'.wl($recent['id'], "do=diff").'">'; $p = array(); $p['src'] = DOKU_BASE.'lib/images/diff.png'; $p['width'] = 15; @@ -146,7 +156,7 @@ class admin_plugin_revert extends DokuWiki_Admin_Plugin { echo "<img $att />"; echo '</a> '; - echo '<a href="'.wl($recent['id'],"do=revisions").'">'; + echo '<a href="'.wl($recent['id'], "do=revisions").'">'; $p = array(); $p['src'] = DOKU_BASE.'lib/images/history.png'; $p['width'] = 12; @@ -157,7 +167,7 @@ class admin_plugin_revert extends DokuWiki_Admin_Plugin { echo "<img $att />"; echo '</a> '; - echo html_wikilink(':'.$recent['id'],(useHeading('navigation'))?null:$recent['id']); + echo html_wikilink(':'.$recent['id'], (useHeading('navigation'))?null:$recent['id']); echo ' – '.htmlspecialchars($recent['sum']); echo ' <span class="user">'; @@ -174,11 +184,10 @@ class admin_plugin_revert extends DokuWiki_Admin_Plugin { echo '<p>'; echo '<button type="submit">'.$this->getLang('revert').'</button> '; - printf($this->getLang('note2'),hsc($filter)); + printf($this->getLang('note2'), hsc($filter)); echo '</p>'; echo '</div></form>'; } - } //Setup VIM: ex: et ts=4 : diff --git a/lib/plugins/safefnrecode/action.php b/lib/plugins/safefnrecode/action.php index 9127f8df2..952d95c90 100644 --- a/lib/plugins/safefnrecode/action.php +++ b/lib/plugins/safefnrecode/action.php @@ -6,63 +6,63 @@ * @author Andreas Gohr <andi@splitbrain.org> */ -// must be run within Dokuwiki -if (!defined('DOKU_INC')) die(); - -require_once DOKU_PLUGIN.'action.php'; - -class action_plugin_safefnrecode extends DokuWiki_Action_Plugin { - - public function register(Doku_Event_Handler $controller) { - - $controller->register_hook('INDEXER_TASKS_RUN', 'BEFORE', $this, 'handle_indexer_tasks_run'); +class action_plugin_safefnrecode extends DokuWiki_Action_Plugin +{ + /** @inheritdoc */ + public function register(Doku_Event_Handler $controller) + { + $controller->register_hook('INDEXER_TASKS_RUN', 'BEFORE', $this, 'handleIndexerTasksRun'); } - public function handle_indexer_tasks_run(Doku_Event &$event, $param) { + /** + * Handle indexer event + * + * @param Doku_Event $event + * @param $param + */ + public function handleIndexerTasksRun(Doku_Event $event, $param) + { global $conf; - if($conf['fnencode'] != 'safe') return; + if ($conf['fnencode'] != 'safe') return; - if(!file_exists($conf['datadir'].'_safefn.recoded')){ + if (!file_exists($conf['datadir'].'_safefn.recoded')) { $this->recode($conf['datadir']); touch($conf['datadir'].'_safefn.recoded'); } - if(!file_exists($conf['olddir'].'_safefn.recoded')){ + if (!file_exists($conf['olddir'].'_safefn.recoded')) { $this->recode($conf['olddir']); touch($conf['olddir'].'_safefn.recoded'); } - if(!file_exists($conf['metadir'].'_safefn.recoded')){ + if (!file_exists($conf['metadir'].'_safefn.recoded')) { $this->recode($conf['metadir']); touch($conf['metadir'].'_safefn.recoded'); } - if(!file_exists($conf['mediadir'].'_safefn.recoded')){ + if (!file_exists($conf['mediadir'].'_safefn.recoded')) { $this->recode($conf['mediadir']); touch($conf['mediadir'].'_safefn.recoded'); } - } /** * Recursive function to rename all safe encoded files to use the new * square bracket post indicator */ - private function recode($dir){ + private function recode($dir) + { $dh = opendir($dir); - if(!$dh) return; + if (!$dh) return; while (($file = readdir($dh)) !== false) { - if($file == '.' || $file == '..') continue; # cur and upper dir - if(is_dir("$dir/$file")) $this->recode("$dir/$file"); #recurse - if(strpos($file,'%') === false) continue; # no encoding used - $new = preg_replace('/(%[^\]]*?)\./','\1]',$file); # new post indicator - if(preg_match('/%[^\]]+$/',$new)) $new .= ']'; # fix end FS#2122 - rename("$dir/$file","$dir/$new"); # rename it + if ($file == '.' || $file == '..') continue; # cur and upper dir + if (is_dir("$dir/$file")) $this->recode("$dir/$file"); #recurse + if (strpos($file, '%') === false) continue; # no encoding used + $new = preg_replace('/(%[^\]]*?)\./', '\1]', $file); # new post indicator + if (preg_match('/%[^\]]+$/', $new)) $new .= ']'; # fix end FS#2122 + rename("$dir/$file", "$dir/$new"); # rename it } closedir($dh); } - } - -// vim:ts=4:sw=4:et: diff --git a/lib/plugins/styling/action.php b/lib/plugins/styling/action.php index 2190fd61d..46245ca75 100644 --- a/lib/plugins/styling/action.php +++ b/lib/plugins/styling/action.php @@ -5,19 +5,8 @@ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html * @author Andreas Gohr <andi@splitbrain.org> */ - -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - -/** - * Class action_plugin_styling - * - * This handles all the save actions and loading the interface - * - * All this usually would be done within an admin plugin, but we want to have this available outside - * the admin interface using our floating dialog. - */ -class action_plugin_styling extends DokuWiki_Action_Plugin { +class action_plugin_styling extends DokuWiki_Action_Plugin +{ /** * Registers a callback functions @@ -25,8 +14,9 @@ class action_plugin_styling extends DokuWiki_Action_Plugin { * @param Doku_Event_Handler $controller DokuWiki's event controller object * @return void */ - public function register(Doku_Event_Handler $controller) { - $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handle_header'); + public function register(Doku_Event_Handler $controller) + { + $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'handleHeader'); } /** @@ -37,26 +27,25 @@ class action_plugin_styling extends DokuWiki_Action_Plugin { * handler was registered] * @return void */ - public function handle_header(Doku_Event &$event, $param) { + public function handleHeader(Doku_Event &$event, $param) + { global $ACT; global $INPUT; - if($ACT != 'admin' || $INPUT->str('page') != 'styling') return; + if ($ACT != 'admin' || $INPUT->str('page') != 'styling') return; /** @var admin_plugin_styling $admin */ $admin = plugin_load('admin', 'styling'); - if(!$admin->isAccessibleByCurrentUser()) return; + if (!$admin->isAccessibleByCurrentUser()) return; // set preview $len = count($event->data['link']); - for($i = 0; $i < $len; $i++) { - if( - $event->data['link'][$i]['rel'] == 'stylesheet' && + for ($i = 0; $i < $len; $i++) { + if ($event->data['link'][$i]['rel'] == 'stylesheet' && strpos($event->data['link'][$i]['href'], 'lib/exe/css.php') !== false ) { $event->data['link'][$i]['href'] .= '&preview=1&tseed='.time(); } } } - } // vim:ts=4:sw=4:et: diff --git a/lib/plugins/styling/admin.php b/lib/plugins/styling/admin.php index e02f06604..41d7d1adf 100644 --- a/lib/plugins/styling/admin.php +++ b/lib/plugins/styling/admin.php @@ -5,45 +5,46 @@ * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html * @author Andreas Gohr <andi@splitbrain.org> */ - -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - -class admin_plugin_styling extends DokuWiki_Admin_Plugin { +class admin_plugin_styling extends DokuWiki_Admin_Plugin +{ public $ispopup = false; /** * @return int sort number in admin menu */ - public function getMenuSort() { + public function getMenuSort() + { return 1000; } /** * @return bool true if only access for superuser, false is for superusers and moderators */ - public function forAdminOnly() { + public function forAdminOnly() + { return true; } /** * handle the different actions (also called from ajax) */ - public function handle() { + public function handle() + { global $INPUT; $run = $INPUT->extract('run')->str('run'); - if(!$run) return; - $run = "run_$run"; + if (!$run) return; + $run = 'run'.ucfirst($run); $this->$run(); } /** * Render HTML output, e.g. helpful text and a form */ - public function html() { + public function html() + { $class = 'nopopup'; - if($this->ispopup) $class = 'ispopup page'; + if ($this->ispopup) $class = 'ispopup page'; echo '<div id="plugin__styling" class="'.$class.'">'; ptln('<h1>'.$this->getLang('menu').'</h1>'); @@ -54,7 +55,8 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { /** * Create the actual editing form */ - public function form() { + public function form() + { global $conf; global $ID; @@ -62,13 +64,13 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { $styleini = $styleUtil->cssStyleini(); $replacements = $styleini['replacements']; - if($this->ispopup) { + if ($this->ispopup) { $target = DOKU_BASE.'lib/plugins/styling/popup.php'; } else { $target = wl($ID, array('do' => 'admin', 'page' => 'styling')); } - if(empty($replacements)) { + if (empty($replacements)) { echo '<p class="error">'.$this->getLang('error').'</p>'; } else { echo $this->locale_xhtml('intro'); @@ -76,21 +78,24 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { echo '<form class="styling" method="post" action="'.$target.'">'; echo '<table><tbody>'; - foreach($replacements as $key => $value) { + foreach ($replacements as $key => $value) { $name = tpl_getLang($key); - if(empty($name)) $name = $this->getLang($key); - if(empty($name)) $name = $key; + if (empty($name)) $name = $this->getLang($key); + if (empty($name)) $name = $key; echo '<tr>'; echo '<td><label for="tpl__'.hsc($key).'">'.$name.'</label></td>'; - echo '<td><input type="text" name="tpl['.hsc($key).']" id="tpl__'.hsc($key).'" value="'.hsc($value).'" '.$this->colorClass($key).' dir="ltr" /></td>'; + echo '<td><input type="text" name="tpl['.hsc($key).']" id="tpl__'.hsc($key).'" + value="'.hsc($value).'" '.$this->colorClass($key).' dir="ltr" /></td>'; echo '</tr>'; } echo '</tbody></table>'; echo '<p>'; - echo '<button type="submit" name="run[preview]" class="btn_preview primary">'.$this->getLang('btn_preview').'</button> '; - echo '<button type="submit" name="run[reset]">'.$this->getLang('btn_reset').'</button>'; #FIXME only if preview.ini exists + echo '<button type="submit" name="run[preview]" class="btn_preview primary">'. + $this->getLang('btn_preview').'</button> '; + #FIXME only if preview.ini exists: + echo '<button type="submit" name="run[reset]">'.$this->getLang('btn_reset').'</button>'; echo '</p>'; echo '<p>'; @@ -98,20 +103,21 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { echo '</p>'; echo '<p>'; - echo '<button type="submit" name="run[revert]">'.$this->getLang('btn_revert').'</button>'; #FIXME only if local.ini exists + #FIXME only if local.ini exists: + echo '<button type="submit" name="run[revert]">'.$this->getLang('btn_revert').'</button>'; echo '</p>'; echo '</form>'; echo tpl_locale_xhtml('style'); - } } /** * set the color class attribute */ - protected function colorClass($key) { + protected function colorClass($key) + { static $colors = array( 'text', 'background', @@ -127,7 +133,7 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { 'missing', ); - if(preg_match('/colou?r/', $key) || in_array(trim($key,'_'), $colors)) { + if (preg_match('/colou?r/', $key) || in_array(trim($key, '_'), $colors)) { return 'class="color"'; } else { return ''; @@ -137,7 +143,8 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { /** * saves the preview.ini (alos called from ajax directly) */ - public function run_preview() { + public function runPreview() + { global $conf; $ini = $conf['cachedir'].'/preview.ini'; io_saveFile($ini, $this->makeini()); @@ -146,7 +153,8 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { /** * deletes the preview.ini */ - protected function run_reset() { + protected function runReset() + { global $conf; $ini = $conf['cachedir'].'/preview.ini'; io_saveFile($ini, ''); @@ -155,17 +163,19 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { /** * deletes the local style.ini replacements */ - protected function run_revert() { - $this->replaceini(''); - $this->run_reset(); + protected function runRevert() + { + $this->replaceIni(''); + $this->runReset(); } /** * save the local style.ini replacements */ - protected function run_save() { - $this->replaceini($this->makeini()); - $this->run_reset(); + protected function runSave() + { + $this->replaceIni($this->makeini()); + $this->runReset(); } /** @@ -173,13 +183,14 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { * * @return string */ - protected function makeini() { + protected function makeini() + { global $INPUT; $ini = "[replacements]\n"; $ini .= ";These overwrites have been generated from the Template styling Admin interface\n"; $ini .= ";Any values in this section will be overwritten by that tool again\n"; - foreach($INPUT->arr('tpl') as $key => $val) { + foreach ($INPUT->arr('tpl') as $key => $val) { $ini .= $key.' = "'.addslashes($val).'"'."\n"; } @@ -191,10 +202,11 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { * * @param string $new the new ini contents */ - protected function replaceini($new) { + protected function replaceIni($new) + { global $conf; $ini = DOKU_CONF."tpl/".$conf['template']."/style.ini"; - if(file_exists($ini)) { + if (file_exists($ini)) { $old = io_readFile($ini); $old = preg_replace('/\[replacements\]\n.*?(\n\[.*]|$)/s', '\\1', $old); $old = trim($old); @@ -205,7 +217,6 @@ class admin_plugin_styling extends DokuWiki_Admin_Plugin { io_makeFileDir($ini); io_saveFile($ini, "$old\n\n$new"); } - } // vim:ts=4:sw=4:et: diff --git a/lib/plugins/styling/popup.php b/lib/plugins/styling/popup.php index 4a1735ccc..079062e43 100644 --- a/lib/plugins/styling/popup.php +++ b/lib/plugins/styling/popup.php @@ -1,5 +1,6 @@ <?php -if(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__) . '/../../../'); +// phpcs:disable PSR1.Files.SideEffects +if (!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__) . '/../../../'); require_once(DOKU_INC . 'inc/init.php'); //close session session_write_close(); @@ -8,7 +9,7 @@ header('X-UA-Compatible: IE=edge,chrome=1'); /** @var admin_plugin_styling $plugin */ $plugin = plugin_load('admin', 'styling'); -if(!$plugin->isAccessibleByCurrentUser()) die('only admins allowed'); +if (!$plugin->isAccessibleByCurrentUser()) die('only admins allowed'); $plugin->ispopup = true; // handle posts diff --git a/lib/plugins/syntax.php b/lib/plugins/syntax.php index 9e2913d78..a3cbec722 100644 --- a/lib/plugins/syntax.php +++ b/lib/plugins/syntax.php @@ -1,134 +1,2 @@ <?php -/** - * Syntax Plugin Prototype - * - * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) - * @author Andreas Gohr <andi@splitbrain.org> - */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - -/** - * All DokuWiki plugins to extend the parser/rendering mechanism - * need to inherit from this class - */ -class DokuWiki_Syntax_Plugin extends Doku_Parser_Mode_Plugin { - - var $allowedModesSetup = false; - - /** - * Syntax Type - * - * Needs to return one of the mode types defined in $PARSER_MODES in parser.php - * - * @return string - */ - function getType(){ - trigger_error('getType() not implemented in '.get_class($this), E_USER_WARNING); - return ''; - } - - /** - * Allowed Mode Types - * - * Defines the mode types for other dokuwiki markup that maybe nested within the - * plugin's own markup. Needs to return an array of one or more of the mode types - * defined in $PARSER_MODES in parser.php - * - * @return array - */ - function getAllowedTypes() { - return array(); - } - - /** - * Paragraph Type - * - * Defines how this syntax is handled regarding paragraphs. This is important - * for correct XHTML nesting. Should return one of the following: - * - * 'normal' - The plugin can be used inside paragraphs - * 'block' - Open paragraphs need to be closed before plugin output - * 'stack' - Special case. Plugin wraps other paragraphs. - * - * @see Doku_Handler_Block - * - * @return string - */ - function getPType(){ - return 'normal'; - } - - /** - * Handler to prepare matched data for the rendering process - * - * This function can only pass data to render() via its return value - render() - * may be not be run during the object's current life. - * - * Usually you should only need the $match param. - * - * @param string $match The text matched by the patterns - * @param int $state The lexer state for the match - * @param int $pos The character position of the matched text - * @param Doku_Handler $handler The Doku_Handler object - * @return bool|array Return an array with all data you want to use in render, false don't add an instruction - */ - function handle($match, $state, $pos, Doku_Handler $handler){ - trigger_error('handle() not implemented in '.get_class($this), E_USER_WARNING); - } - - /** - * Handles the actual output creation. - * - * The function must not assume any other of the classes methods have been run - * during the object's current life. The only reliable data it receives are its - * parameters. - * - * The function should always check for the given output format and return false - * when a format isn't supported. - * - * $renderer contains a reference to the renderer object which is - * currently handling the rendering. You need to use it for writing - * the output. How this is done depends on the renderer used (specified - * by $format - * - * The contents of the $data array depends on what the handler() function above - * created - * - * @param string $format output format being rendered - * @param Doku_Renderer $renderer the current renderer object - * @param array $data data created by handler() - * @return boolean rendered correctly? (however, returned value is not used at the moment) - */ - function render($format, Doku_Renderer $renderer, $data) { - trigger_error('render() not implemented in '.get_class($this), E_USER_WARNING); - - } - - /** - * There should be no need to override this function - * - * @param string $mode - * @return bool - */ - function accepts($mode) { - - if (!$this->allowedModesSetup) { - global $PARSER_MODES; - - $allowedModeTypes = $this->getAllowedTypes(); - foreach($allowedModeTypes as $mt) { - $this->allowedModes = array_merge($this->allowedModes, $PARSER_MODES[$mt]); - } - - $idx = array_search(substr(get_class($this), 7), (array) $this->allowedModes); - if ($idx !== false) { - unset($this->allowedModes[$idx]); - } - $this->allowedModesSetup = true; - } - - return parent::accepts($mode); - } -} -//Setup VIM: ex: et ts=4 : +dbg_deprecated('Autoloading. Do not require() files yourself.'); diff --git a/lib/plugins/testing/action.php b/lib/plugins/testing/action.php index a242ab0b7..09b84262e 100644 --- a/lib/plugins/testing/action.php +++ b/lib/plugins/testing/action.php @@ -1,4 +1,7 @@ <?php + +use dokuwiki\Extension\Event; + /** * Plugin for testing the test system * @@ -8,13 +11,14 @@ */ class action_plugin_testing extends DokuWiki_Action_Plugin { - function register(Doku_Event_Handler $controller) { + /** @inheritdoc */ + public function register(Doku_Event_Handler $controller) { $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'dokuwikiStarted'); } - function dokuwikiStarted() { + public function dokuwikiStarted() { $param = array(); - trigger_event('TESTING_PLUGIN_INSTALLED', $param); + Event::createAndTrigger('TESTING_PLUGIN_INSTALLED', $param); msg('The testing plugin is enabled and should be disabled.',-1); } } diff --git a/lib/plugins/testing/conf/default.php b/lib/plugins/testing/conf/default.php new file mode 100644 index 000000000..392233dd8 --- /dev/null +++ b/lib/plugins/testing/conf/default.php @@ -0,0 +1,7 @@ +<?php +/** + * Default options + * + * They don't do anything and are just there for testing config reading + */ +$conf['schnibble'] = 0; diff --git a/lib/plugins/testing/conf/metadata.php b/lib/plugins/testing/conf/metadata.php new file mode 100644 index 000000000..377da63eb --- /dev/null +++ b/lib/plugins/testing/conf/metadata.php @@ -0,0 +1,7 @@ +<?php +/** + * Option Metadata + * + * They don't do anything and are just there for testing config reading + */ +$meta['schnibble'] = array('onoff'); diff --git a/lib/plugins/testing/lang/en/settings.php b/lib/plugins/testing/lang/en/settings.php new file mode 100644 index 000000000..e717c9021 --- /dev/null +++ b/lib/plugins/testing/lang/en/settings.php @@ -0,0 +1,5 @@ +<?php +/** + * Default options texts + */ +$lang['schnibble'] = 'Turns on the schnibble before the frobble is used'; diff --git a/lib/plugins/usermanager/_test/mocks.class.php b/lib/plugins/usermanager/_test/mocks.class.php index e524e451b..75ac51422 100644 --- a/lib/plugins/usermanager/_test/mocks.class.php +++ b/lib/plugins/usermanager/_test/mocks.class.php @@ -16,30 +16,30 @@ class admin_mock_usermanager extends admin_plugin_usermanager { public $lang; public function getImportFailures() { - return $this->_import_failures; + return $this->import_failures; } public function tryExport() { ob_start(); - $this->_export(); + $this->exportCSV(); return ob_get_clean(); } public function tryImport() { - return $this->_import(); + return $this->importCSV(); } // no need to send email notifications (mostly) - protected function _notifyUser($user, $password, $status_alert=true) { + protected function notifyUser($user, $password, $status_alert=true) { if ($this->mock_email_notifications) { $this->mock_email_notifications_sent++; return true; } else { - return parent::_notifyUser($user, $password, $status_alert); + return parent::notifyUser($user, $password, $status_alert); } } - protected function _isUploadedFile($file) { + protected function isUploadedFile($file) { return file_exists($file); } } diff --git a/lib/plugins/usermanager/admin.php b/lib/plugins/usermanager/admin.php index 3148971ce..423467133 100644 --- a/lib/plugins/usermanager/admin.php +++ b/lib/plugins/usermanager/admin.php @@ -10,52 +10,49 @@ * @author neolao <neolao@neolao.com> * @author Chris Smith <chris@jalakai.co.uk> */ -// must be run within Dokuwiki -if(!defined('DOKU_INC')) die(); - -if(!defined('DOKU_PLUGIN_IMAGES')) define('DOKU_PLUGIN_IMAGES',DOKU_BASE.'lib/plugins/usermanager/images/'); /** * All DokuWiki plugins to extend the admin function * need to inherit from this class */ -class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { - - protected $_auth = null; // auth object - protected $_user_total = 0; // number of registered users - protected $_filter = array(); // user selection filter(s) - protected $_start = 0; // index of first user to be displayed - protected $_last = 0; // index of the last user to be displayed - protected $_pagesize = 20; // number of users to list on one page - protected $_edit_user = ''; // set to user selected for editing - protected $_edit_userdata = array(); - protected $_disabled = ''; // if disabled set to explanatory string - protected $_import_failures = array(); - protected $_lastdisabled = false; // set to true if last user is unknown and last button is hence buggy +class admin_plugin_usermanager extends DokuWiki_Admin_Plugin +{ + const IMAGE_DIR = DOKU_BASE.'lib/plugins/usermanager/images/'; + + protected $auth = null; // auth object + protected $users_total = 0; // number of registered users + protected $filter = array(); // user selection filter(s) + protected $start = 0; // index of first user to be displayed + protected $last = 0; // index of the last user to be displayed + protected $pagesize = 20; // number of users to list on one page + protected $edit_user = ''; // set to user selected for editing + protected $edit_userdata = array(); + protected $disabled = ''; // if disabled set to explanatory string + protected $import_failures = array(); + protected $lastdisabled = false; // set to true if last user is unknown and last button is hence buggy /** * Constructor */ - public function __construct(){ + public function __construct() + { /** @var DokuWiki_Auth_Plugin $auth */ global $auth; $this->setupLocale(); if (!isset($auth)) { - $this->_disabled = $this->lang['noauth']; - } else if (!$auth->canDo('getUsers')) { - $this->_disabled = $this->lang['nosupport']; + $this->disabled = $this->lang['noauth']; + } elseif (!$auth->canDo('getUsers')) { + $this->disabled = $this->lang['nosupport']; } else { - // we're good to go - $this->_auth = & $auth; - + $this->auth = & $auth; } // attempt to retrieve any import failures from the session - if (!empty($_SESSION['import_failures'])){ - $this->_import_failures = $_SESSION['import_failures']; + if (!empty($_SESSION['import_failures'])) { + $this->import_failures = $_SESSION['import_failures']; } } @@ -65,12 +62,13 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * @param string $language * @return string */ - public function getMenuText($language) { + public function getMenuText($language) + { - if (!is_null($this->_auth)) + if (!is_null($this->auth)) return parent::getMenuText($language); - return $this->getLang('menu').' '.$this->_disabled; + return $this->getLang('menu').' '.$this->disabled; } /** @@ -78,29 +76,33 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * * @return int */ - public function getMenuSort() { + public function getMenuSort() + { return 2; } /** * @return int current start value for pageination */ - public function getStart() { - return $this->_start; + public function getStart() + { + return $this->start; } /** * @return int number of users per page */ - public function getPagesize() { - return $this->_pagesize; + public function getPagesize() + { + return $this->pagesize; } /** * @param boolean $lastdisabled */ - public function setLastdisabled($lastdisabled) { - $this->_lastdisabled = $lastdisabled; + public function setLastdisabled($lastdisabled) + { + $this->lastdisabled = $lastdisabled; } /** @@ -108,9 +110,10 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * * @return bool */ - public function handle() { + public function handle() + { global $INPUT; - if (is_null($this->_auth)) return false; + if (is_null($this->auth)) return false; // extract the command and any specific parameters // submit button name is of the form - fn[cmd][param(s)] @@ -125,33 +128,56 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { } if ($cmd != "search") { - $this->_start = $INPUT->int('start', 0); - $this->_filter = $this->_retrieveFilter(); + $this->start = $INPUT->int('start', 0); + $this->filter = $this->retrieveFilter(); } - switch($cmd){ - case "add" : $this->_addUser(); break; - case "delete" : $this->_deleteUser(); break; - case "modify" : $this->_modifyUser(); break; - case "edit" : $this->_editUser($param); break; - case "search" : $this->_setFilter($param); - $this->_start = 0; - break; - case "export" : $this->_export(); break; - case "import" : $this->_import(); break; - case "importfails" : $this->_downloadImportFailures(); break; + switch ($cmd) { + case "add": + $this->addUser(); + break; + case "delete": + $this->deleteUser(); + break; + case "modify": + $this->modifyUser(); + break; + case "edit": + $this->editUser($param); + break; + case "search": + $this->setFilter($param); + $this->start = 0; + break; + case "export": + $this->exportCSV(); + break; + case "import": + $this->importCSV(); + break; + case "importfails": + $this->downloadImportFailures(); + break; } - $this->_user_total = $this->_auth->canDo('getUserCount') ? $this->_auth->getUserCount($this->_filter) : -1; + $this->users_total = $this->auth->canDo('getUserCount') ? $this->auth->getUserCount($this->filter) : -1; // page handling - switch($cmd){ - case 'start' : $this->_start = 0; break; - case 'prev' : $this->_start -= $this->_pagesize; break; - case 'next' : $this->_start += $this->_pagesize; break; - case 'last' : $this->_start = $this->_user_total; break; + switch ($cmd) { + case 'start': + $this->start = 0; + break; + case 'prev': + $this->start -= $this->pagesize; + break; + case 'next': + $this->start += $this->pagesize; + break; + case 'last': + $this->start = $this->users_total; + break; } - $this->_validatePagination(); + $this->validatePagination(); return true; } @@ -160,21 +186,22 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * * @return bool */ - public function html() { + public function html() + { global $ID; - if(is_null($this->_auth)) { + if (is_null($this->auth)) { print $this->lang['badauth']; return false; } - $user_list = $this->_auth->retrieveUsers($this->_start, $this->_pagesize, $this->_filter); + $user_list = $this->auth->retrieveUsers($this->start, $this->pagesize, $this->filter); - $page_buttons = $this->_pagination(); - $delete_disable = $this->_auth->canDo('delUser') ? '' : 'disabled="disabled"'; + $page_buttons = $this->pagination(); + $delete_disable = $this->auth->canDo('delUser') ? '' : 'disabled="disabled"'; - $editable = $this->_auth->canDo('UserMod'); - $export_label = empty($this->_filter) ? $this->lang['export_all'] : $this->lang['export_filtered']; + $editable = $this->auth->canDo('UserMod'); + $export_label = empty($this->filter) ? $this->lang['export_all'] : $this->lang['export_filtered']; print $this->locale_xhtml('intro'); print $this->locale_xhtml('list'); @@ -182,13 +209,21 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { ptln("<div id=\"user__manager\">"); ptln("<div class=\"level2\">"); - if ($this->_user_total > 0) { - ptln("<p>".sprintf($this->lang['summary'],$this->_start+1,$this->_last,$this->_user_total,$this->_auth->getUserCount())."</p>"); + if ($this->users_total > 0) { + ptln( + "<p>" . sprintf( + $this->lang['summary'], + $this->start + 1, + $this->last, + $this->users_total, + $this->auth->getUserCount() + ) . "</p>" + ); } else { - if($this->_user_total < 0) { + if ($this->users_total < 0) { $allUserTotal = 0; } else { - $allUserTotal = $this->_auth->getUserCount(); + $allUserTotal = $this->auth->getUserCount(); } ptln("<p>".sprintf($this->lang['nonefound'], $allUserTotal)."</p>"); } @@ -198,19 +233,29 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { ptln(" <table class=\"inline\">"); ptln(" <thead>"); ptln(" <tr>"); - ptln(" <th> </th><th>".$this->lang["user_id"]."</th><th>".$this->lang["user_name"]."</th><th>".$this->lang["user_mail"]."</th><th>".$this->lang["user_groups"]."</th>"); + ptln(" <th> </th> + <th>".$this->lang["user_id"]."</th> + <th>".$this->lang["user_name"]."</th> + <th>".$this->lang["user_mail"]."</th> + <th>".$this->lang["user_groups"]."</th>"); ptln(" </tr>"); ptln(" <tr>"); - ptln(" <td class=\"rightalign\"><input type=\"image\" src=\"".DOKU_PLUGIN_IMAGES."search.png\" name=\"fn[search][new]\" title=\"".$this->lang['search_prompt']."\" alt=\"".$this->lang['search']."\" class=\"button\" /></td>"); - ptln(" <td><input type=\"text\" name=\"userid\" class=\"edit\" value=\"".$this->_htmlFilter('user')."\" /></td>"); - ptln(" <td><input type=\"text\" name=\"username\" class=\"edit\" value=\"".$this->_htmlFilter('name')."\" /></td>"); - ptln(" <td><input type=\"text\" name=\"usermail\" class=\"edit\" value=\"".$this->_htmlFilter('mail')."\" /></td>"); - ptln(" <td><input type=\"text\" name=\"usergroups\" class=\"edit\" value=\"".$this->_htmlFilter('grps')."\" /></td>"); + ptln(" <td class=\"rightalign\"><input type=\"image\" src=\"". + self::IMAGE_DIR."search.png\" name=\"fn[search][new]\" title=\"". + $this->lang['search_prompt']."\" alt=\"".$this->lang['search']."\" class=\"button\" /></td>"); + ptln(" <td><input type=\"text\" name=\"userid\" class=\"edit\" value=\"". + $this->htmlFilter('user')."\" /></td>"); + ptln(" <td><input type=\"text\" name=\"username\" class=\"edit\" value=\"". + $this->htmlFilter('name')."\" /></td>"); + ptln(" <td><input type=\"text\" name=\"usermail\" class=\"edit\" value=\"". + $this->htmlFilter('mail')."\" /></td>"); + ptln(" <td><input type=\"text\" name=\"usergroups\" class=\"edit\" value=\"". + $this->htmlFilter('grps')."\" /></td>"); ptln(" </tr>"); ptln(" </thead>"); - if ($this->_user_total) { + if ($this->users_total) { ptln(" <tbody>"); foreach ($user_list as $user => $userinfo) { extract($userinfo); @@ -220,11 +265,12 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * @var string $mail * @var array $grps */ - $groups = join(', ',$grps); + $groups = join(', ', $grps); ptln(" <tr class=\"user_info\">"); - ptln(" <td class=\"centeralign\"><input type=\"checkbox\" name=\"delete[".hsc($user)."]\" ".$delete_disable." /></td>"); + ptln(" <td class=\"centeralign\"><input type=\"checkbox\" name=\"delete[".hsc($user). + "]\" ".$delete_disable." /></td>"); if ($editable) { - ptln(" <td><a href=\"".wl($ID,array('fn[edit]['.$user.']' => 1, + ptln(" <td><a href=\"".wl($ID, array('fn[edit]['.$user.']' => 1, 'do' => 'admin', 'page' => 'usermanager', 'sectok' => getSecurityToken())). @@ -241,22 +287,27 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { ptln(" <tbody>"); ptln(" <tr><td colspan=\"5\" class=\"centeralign\">"); ptln(" <span class=\"medialeft\">"); - ptln(" <button type=\"submit\" name=\"fn[delete]\" id=\"usrmgr__del\" ".$delete_disable.">".$this->lang['delete_selected']."</button>"); + ptln(" <button type=\"submit\" name=\"fn[delete]\" id=\"usrmgr__del\" ".$delete_disable.">". + $this->lang['delete_selected']."</button>"); ptln(" </span>"); ptln(" <span class=\"mediaright\">"); - ptln(" <button type=\"submit\" name=\"fn[start]\" ".$page_buttons['start'].">".$this->lang['start']."</button>"); - ptln(" <button type=\"submit\" name=\"fn[prev]\" ".$page_buttons['prev'].">".$this->lang['prev']."</button>"); - ptln(" <button type=\"submit\" name=\"fn[next]\" ".$page_buttons['next'].">".$this->lang['next']."</button>"); - ptln(" <button type=\"submit\" name=\"fn[last]\" ".$page_buttons['last'].">".$this->lang['last']."</button>"); + ptln(" <button type=\"submit\" name=\"fn[start]\" ".$page_buttons['start'].">". + $this->lang['start']."</button>"); + ptln(" <button type=\"submit\" name=\"fn[prev]\" ".$page_buttons['prev'].">". + $this->lang['prev']."</button>"); + ptln(" <button type=\"submit\" name=\"fn[next]\" ".$page_buttons['next'].">". + $this->lang['next']."</button>"); + ptln(" <button type=\"submit\" name=\"fn[last]\" ".$page_buttons['last'].">". + $this->lang['last']."</button>"); ptln(" </span>"); - if (!empty($this->_filter)) { + if (!empty($this->filter)) { ptln(" <button type=\"submit\" name=\"fn[search][clear]\">".$this->lang['clear']."</button>"); } ptln(" <button type=\"submit\" name=\"fn[export]\">".$export_label."</button>"); ptln(" <input type=\"hidden\" name=\"do\" value=\"admin\" />"); ptln(" <input type=\"hidden\" name=\"page\" value=\"usermanager\" />"); - $this->_htmlFilterSettings(2); + $this->htmlFilterSettings(2); ptln(" </td></tr>"); ptln(" </tbody>"); @@ -266,32 +317,32 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { ptln("</form>"); ptln("</div>"); - $style = $this->_edit_user ? " class=\"edit_user\"" : ""; + $style = $this->edit_user ? " class=\"edit_user\"" : ""; - if ($this->_auth->canDo('addUser')) { + if ($this->auth->canDo('addUser')) { ptln("<div".$style.">"); print $this->locale_xhtml('add'); ptln(" <div class=\"level2\">"); - $this->_htmlUserForm('add',null,array(),4); + $this->htmlUserForm('add', null, array(), 4); ptln(" </div>"); ptln("</div>"); } - if($this->_edit_user && $this->_auth->canDo('UserMod')){ + if ($this->edit_user && $this->auth->canDo('UserMod')) { ptln("<div".$style." id=\"scroll__here\">"); print $this->locale_xhtml('edit'); ptln(" <div class=\"level2\">"); - $this->_htmlUserForm('modify',$this->_edit_user,$this->_edit_userdata,4); + $this->htmlUserForm('modify', $this->edit_user, $this->edit_userdata, 4); ptln(" </div>"); ptln("</div>"); } - if ($this->_auth->canDo('addUser')) { - $this->_htmlImportForm(); + if ($this->auth->canDo('addUser')) { + $this->htmlImportForm(); } ptln("</div>"); return true; @@ -323,7 +374,8 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * @param array $userdata array with name, mail, pass and grps * @param int $indent */ - protected function _htmlUserForm($cmd,$user='',$userdata=array(),$indent=0) { + protected function htmlUserForm($cmd, $user = '', $userdata = array(), $indent = 0) + { global $conf; global $ID; global $lang; @@ -333,28 +385,76 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { if ($user) { extract($userdata); - if (!empty($grps)) $groups = join(',',$grps); + if (!empty($grps)) $groups = join(',', $grps); } else { - $notes[] = sprintf($this->lang['note_group'],$conf['defaultgroup']); + $notes[] = sprintf($this->lang['note_group'], $conf['defaultgroup']); } - ptln("<form action=\"".wl($ID)."\" method=\"post\">",$indent); + ptln("<form action=\"".wl($ID)."\" method=\"post\">", $indent); formSecurityToken(); - ptln(" <div class=\"table\">",$indent); - ptln(" <table class=\"inline\">",$indent); - ptln(" <thead>",$indent); - ptln(" <tr><th>".$this->lang["field"]."</th><th>".$this->lang["value"]."</th></tr>",$indent); - ptln(" </thead>",$indent); - ptln(" <tbody>",$indent); - - $this->_htmlInputField($cmd."_userid", "userid", $this->lang["user_id"], $user, $this->_auth->canDo("modLogin"), true, $indent+6); - $this->_htmlInputField($cmd."_userpass", "userpass", $this->lang["user_pass"], "", $this->_auth->canDo("modPass"), false, $indent+6); - $this->_htmlInputField($cmd."_userpass2", "userpass2", $lang["passchk"], "", $this->_auth->canDo("modPass"), false, $indent+6); - $this->_htmlInputField($cmd."_username", "username", $this->lang["user_name"], $name, $this->_auth->canDo("modName"), true, $indent+6); - $this->_htmlInputField($cmd."_usermail", "usermail", $this->lang["user_mail"], $mail, $this->_auth->canDo("modMail"), true, $indent+6); - $this->_htmlInputField($cmd."_usergroups","usergroups",$this->lang["user_groups"],$groups,$this->_auth->canDo("modGroups"), false, $indent+6); - - if ($this->_auth->canDo("modPass")) { + ptln(" <div class=\"table\">", $indent); + ptln(" <table class=\"inline\">", $indent); + ptln(" <thead>", $indent); + ptln(" <tr><th>".$this->lang["field"]."</th><th>".$this->lang["value"]."</th></tr>", $indent); + ptln(" </thead>", $indent); + ptln(" <tbody>", $indent); + + $this->htmlInputField( + $cmd . "_userid", + "userid", + $this->lang["user_id"], + $user, + $this->auth->canDo("modLogin"), + true, + $indent + 6 + ); + $this->htmlInputField( + $cmd . "_userpass", + "userpass", + $this->lang["user_pass"], + "", + $this->auth->canDo("modPass"), + false, + $indent + 6 + ); + $this->htmlInputField( + $cmd . "_userpass2", + "userpass2", + $lang["passchk"], + "", + $this->auth->canDo("modPass"), + false, + $indent + 6 + ); + $this->htmlInputField( + $cmd . "_username", + "username", + $this->lang["user_name"], + $name, + $this->auth->canDo("modName"), + true, + $indent + 6 + ); + $this->htmlInputField( + $cmd . "_usermail", + "usermail", + $this->lang["user_mail"], + $mail, + $this->auth->canDo("modMail"), + true, + $indent + 6 + ); + $this->htmlInputField( + $cmd . "_usergroups", + "usergroups", + $this->lang["user_groups"], + $groups, + $this->auth->canDo("modGroups"), + false, + $indent + 6 + ); + + if ($this->auth->canDo("modPass")) { if ($cmd == 'add') { $notes[] = $this->lang['note_pass']; } @@ -362,37 +462,40 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { $notes[] = $this->lang['note_notify']; } - ptln("<tr><td><label for=\"".$cmd."_usernotify\" >".$this->lang["user_notify"].": </label></td><td><input type=\"checkbox\" id=\"".$cmd."_usernotify\" name=\"usernotify\" value=\"1\" /></td></tr>", $indent); + ptln("<tr><td><label for=\"".$cmd."_usernotify\" >". + $this->lang["user_notify"].": </label></td> + <td><input type=\"checkbox\" id=\"".$cmd."_usernotify\" name=\"usernotify\" value=\"1\" /> + </td></tr>", $indent); } - ptln(" </tbody>",$indent); - ptln(" <tbody>",$indent); - ptln(" <tr>",$indent); - ptln(" <td colspan=\"2\">",$indent); - ptln(" <input type=\"hidden\" name=\"do\" value=\"admin\" />",$indent); - ptln(" <input type=\"hidden\" name=\"page\" value=\"usermanager\" />",$indent); + ptln(" </tbody>", $indent); + ptln(" <tbody>", $indent); + ptln(" <tr>", $indent); + ptln(" <td colspan=\"2\">", $indent); + ptln(" <input type=\"hidden\" name=\"do\" value=\"admin\" />", $indent); + ptln(" <input type=\"hidden\" name=\"page\" value=\"usermanager\" />", $indent); // save current $user, we need this to access details if the name is changed if ($user) - ptln(" <input type=\"hidden\" name=\"userid_old\" value=\"".hsc($user)."\" />",$indent); + ptln(" <input type=\"hidden\" name=\"userid_old\" value=\"".hsc($user)."\" />", $indent); - $this->_htmlFilterSettings($indent+10); + $this->htmlFilterSettings($indent+10); - ptln(" <button type=\"submit\" name=\"fn[".$cmd."]\">".$this->lang[$cmd]."</button>",$indent); - ptln(" </td>",$indent); - ptln(" </tr>",$indent); - ptln(" </tbody>",$indent); - ptln(" </table>",$indent); + ptln(" <button type=\"submit\" name=\"fn[".$cmd."]\">".$this->lang[$cmd]."</button>", $indent); + ptln(" </td>", $indent); + ptln(" </tr>", $indent); + ptln(" </tbody>", $indent); + ptln(" </table>", $indent); if ($notes) { ptln(" <ul class=\"notes\">"); foreach ($notes as $note) { - ptln(" <li><span class=\"li\">".$note."</li>",$indent); + ptln(" <li><span class=\"li\">".$note."</li>", $indent); } ptln(" </ul>"); } - ptln(" </div>",$indent); - ptln("</form>",$indent); + ptln(" </div>", $indent); + ptln("</form>", $indent); } /** @@ -406,17 +509,18 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * @param bool $required is this field required? * @param int $indent */ - protected function _htmlInputField($id, $name, $label, $value, $cando, $required, $indent=0) { + protected function htmlInputField($id, $name, $label, $value, $cando, $required, $indent = 0) + { $class = $cando ? '' : ' class="disabled"'; - echo str_pad('',$indent); + echo str_pad('', $indent); - if($name == 'userpass' || $name == 'userpass2'){ + if ($name == 'userpass' || $name == 'userpass2') { $fieldtype = 'password'; $autocomp = 'autocomplete="off"'; - }elseif($name == 'usermail'){ + } elseif ($name == 'usermail') { $fieldtype = 'email'; $autocomp = ''; - }else{ + } else { $fieldtype = 'text'; $autocomp = ''; } @@ -425,13 +529,15 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { echo "<tr $class>"; echo "<td><label for=\"$id\" >$label: </label></td>"; echo "<td>"; - if($cando){ + if ($cando) { $req = ''; - if($required) $req = 'required="required"'; - echo "<input type=\"$fieldtype\" id=\"$id\" name=\"$name\" value=\"$value\" class=\"edit\" $autocomp $req />"; - }else{ + if ($required) $req = 'required="required"'; + echo "<input type=\"$fieldtype\" id=\"$id\" name=\"$name\" + value=\"$value\" class=\"edit\" $autocomp $req />"; + } else { echo "<input type=\"hidden\" name=\"$name\" value=\"$value\" />"; - echo "<input type=\"$fieldtype\" id=\"$id\" name=\"$name\" value=\"$value\" class=\"edit disabled\" disabled=\"disabled\" />"; + echo "<input type=\"$fieldtype\" id=\"$id\" name=\"$name\" + value=\"$value\" class=\"edit disabled\" disabled=\"disabled\" />"; } echo "</td>"; echo "</tr>"; @@ -443,9 +549,10 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * @param string $key name of search field * @return string html escaped value */ - protected function _htmlFilter($key) { - if (empty($this->_filter)) return ''; - return (isset($this->_filter[$key]) ? hsc($this->_filter[$key]) : ''); + protected function htmlFilter($key) + { + if (empty($this->filter)) return ''; + return (isset($this->filter[$key]) ? hsc($this->filter[$key]) : ''); } /** @@ -453,12 +560,13 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * * @param int $indent */ - protected function _htmlFilterSettings($indent=0) { + protected function htmlFilterSettings($indent = 0) + { - ptln("<input type=\"hidden\" name=\"start\" value=\"".$this->_start."\" />",$indent); + ptln("<input type=\"hidden\" name=\"start\" value=\"".$this->start."\" />", $indent); - foreach ($this->_filter as $key => $filter) { - ptln("<input type=\"hidden\" name=\"filter[".$key."]\" value=\"".hsc($filter)."\" />",$indent); + foreach ($this->filter as $key => $filter) { + ptln("<input type=\"hidden\" name=\"filter[".$key."]\" value=\"".hsc($filter)."\" />", $indent); } } @@ -467,57 +575,57 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * * @param int $indent */ - protected function _htmlImportForm($indent=0) { + protected function htmlImportForm($indent = 0) + { global $ID; - $failure_download_link = wl($ID,array('do'=>'admin','page'=>'usermanager','fn[importfails]'=>1)); + $failure_download_link = wl($ID, array('do'=>'admin','page'=>'usermanager','fn[importfails]'=>1)); - ptln('<div class="level2 import_users">',$indent); + ptln('<div class="level2 import_users">', $indent); print $this->locale_xhtml('import'); - ptln(' <form action="'.wl($ID).'" method="post" enctype="multipart/form-data">',$indent); + ptln(' <form action="'.wl($ID).'" method="post" enctype="multipart/form-data">', $indent); formSecurityToken(); - ptln(' <label>'.$this->lang['import_userlistcsv'].'<input type="file" name="import" /></label>',$indent); - ptln(' <button type="submit" name="fn[import]">'.$this->lang['import'].'</button>',$indent); - ptln(' <input type="hidden" name="do" value="admin" />',$indent); - ptln(' <input type="hidden" name="page" value="usermanager" />',$indent); + ptln(' <label>'.$this->lang['import_userlistcsv'].'<input type="file" name="import" /></label>', $indent); + ptln(' <button type="submit" name="fn[import]">'.$this->lang['import'].'</button>', $indent); + ptln(' <input type="hidden" name="do" value="admin" />', $indent); + ptln(' <input type="hidden" name="page" value="usermanager" />', $indent); - $this->_htmlFilterSettings($indent+4); - ptln(' </form>',$indent); + $this->htmlFilterSettings($indent+4); + ptln(' </form>', $indent); ptln('</div>'); // list failures from the previous import - if ($this->_import_failures) { - $digits = strlen(count($this->_import_failures)); - ptln('<div class="level3 import_failures">',$indent); + if ($this->import_failures) { + $digits = strlen(count($this->import_failures)); + ptln('<div class="level3 import_failures">', $indent); ptln(' <h3>'.$this->lang['import_header'].'</h3>'); - ptln(' <table class="import_failures">',$indent); - ptln(' <thead>',$indent); - ptln(' <tr>',$indent); - ptln(' <th class="line">'.$this->lang['line'].'</th>',$indent); - ptln(' <th class="error">'.$this->lang['error'].'</th>',$indent); - ptln(' <th class="userid">'.$this->lang['user_id'].'</th>',$indent); - ptln(' <th class="username">'.$this->lang['user_name'].'</th>',$indent); - ptln(' <th class="usermail">'.$this->lang['user_mail'].'</th>',$indent); - ptln(' <th class="usergroups">'.$this->lang['user_groups'].'</th>',$indent); - ptln(' </tr>',$indent); - ptln(' </thead>',$indent); - ptln(' <tbody>',$indent); - foreach ($this->_import_failures as $line => $failure) { - ptln(' <tr>',$indent); - ptln(' <td class="lineno"> '.sprintf('%0'.$digits.'d',$line).' </td>',$indent); + ptln(' <table class="import_failures">', $indent); + ptln(' <thead>', $indent); + ptln(' <tr>', $indent); + ptln(' <th class="line">'.$this->lang['line'].'</th>', $indent); + ptln(' <th class="error">'.$this->lang['error'].'</th>', $indent); + ptln(' <th class="userid">'.$this->lang['user_id'].'</th>', $indent); + ptln(' <th class="username">'.$this->lang['user_name'].'</th>', $indent); + ptln(' <th class="usermail">'.$this->lang['user_mail'].'</th>', $indent); + ptln(' <th class="usergroups">'.$this->lang['user_groups'].'</th>', $indent); + ptln(' </tr>', $indent); + ptln(' </thead>', $indent); + ptln(' <tbody>', $indent); + foreach ($this->import_failures as $line => $failure) { + ptln(' <tr>', $indent); + ptln(' <td class="lineno"> '.sprintf('%0'.$digits.'d', $line).' </td>', $indent); ptln(' <td class="error">' .$failure['error'].' </td>', $indent); - ptln(' <td class="field userid"> '.hsc($failure['user'][0]).' </td>',$indent); - ptln(' <td class="field username"> '.hsc($failure['user'][2]).' </td>',$indent); - ptln(' <td class="field usermail"> '.hsc($failure['user'][3]).' </td>',$indent); - ptln(' <td class="field usergroups"> '.hsc($failure['user'][4]).' </td>',$indent); - ptln(' </tr>',$indent); + ptln(' <td class="field userid"> '.hsc($failure['user'][0]).' </td>', $indent); + ptln(' <td class="field username"> '.hsc($failure['user'][2]).' </td>', $indent); + ptln(' <td class="field usermail"> '.hsc($failure['user'][3]).' </td>', $indent); + ptln(' <td class="field usergroups"> '.hsc($failure['user'][4]).' </td>', $indent); + ptln(' </tr>', $indent); } - ptln(' </tbody>',$indent); - ptln(' </table>',$indent); + ptln(' </tbody>', $indent); + ptln(' </table>', $indent); ptln(' <p><a href="'.$failure_download_link.'">'.$this->lang['import_downloadfailures'].'</a></p>'); ptln('</div>'); } - } /** @@ -525,17 +633,18 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * * @return bool whether succesful */ - protected function _addUser(){ + protected function addUser() + { global $INPUT; if (!checkSecurityToken()) return false; - if (!$this->_auth->canDo('addUser')) return false; + if (!$this->auth->canDo('addUser')) return false; - list($user,$pass,$name,$mail,$grps,$passconfirm) = $this->_retrieveUser(); + list($user,$pass,$name,$mail,$grps,$passconfirm) = $this->retrieveUser(); if (empty($user)) return false; - if ($this->_auth->canDo('modPass')){ - if (empty($pass)){ - if($INPUT->has('usernotify')){ + if ($this->auth->canDo('modPass')) { + if (empty($pass)) { + if ($INPUT->has('usernotify')) { $pass = auth_pwgen($user); } else { msg($this->lang['add_fail'], -1); @@ -543,54 +652,53 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { return false; } } else { - if (!$this->_verifyPassword($pass,$passconfirm)) { + if (!$this->verifyPassword($pass, $passconfirm)) { msg($this->lang['add_fail'], -1); msg($this->lang['addUser_error_pass_not_identical'], -1); return false; } } } else { - if (!empty($pass)){ + if (!empty($pass)) { msg($this->lang['add_fail'], -1); msg($this->lang['addUser_error_modPass_disabled'], -1); return false; } } - if ($this->_auth->canDo('modName')){ - if (empty($name)){ + if ($this->auth->canDo('modName')) { + if (empty($name)) { msg($this->lang['add_fail'], -1); msg($this->lang['addUser_error_name_missing'], -1); return false; } } else { - if (!empty($name)){ + if (!empty($name)) { msg($this->lang['add_fail'], -1); msg($this->lang['addUser_error_modName_disabled'], -1); return false; } } - if ($this->_auth->canDo('modMail')){ - if (empty($mail)){ + if ($this->auth->canDo('modMail')) { + if (empty($mail)) { msg($this->lang['add_fail'], -1); msg($this->lang['addUser_error_mail_missing'], -1); return false; } } else { - if (!empty($mail)){ + if (!empty($mail)) { msg($this->lang['add_fail'], -1); msg($this->lang['addUser_error_modMail_disabled'], -1); return false; } } - if ($ok = $this->_auth->triggerUserMod('create', array($user,$pass,$name,$mail,$grps))) { - + if ($ok = $this->auth->triggerUserMod('create', array($user, $pass, $name, $mail, $grps))) { msg($this->lang['add_ok'], 1); if ($INPUT->has('usernotify') && $pass) { - $this->_notifyUser($user,$pass); + $this->notifyUser($user, $pass); } } else { msg($this->lang['add_fail'], -1); @@ -605,33 +713,34 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * * @return bool whether succesful */ - protected function _deleteUser(){ + protected function deleteUser() + { global $conf, $INPUT; if (!checkSecurityToken()) return false; - if (!$this->_auth->canDo('delUser')) return false; + if (!$this->auth->canDo('delUser')) return false; $selected = $INPUT->arr('delete'); if (empty($selected)) return false; $selected = array_keys($selected); - if(in_array($_SERVER['REMOTE_USER'], $selected)) { + if (in_array($_SERVER['REMOTE_USER'], $selected)) { msg("You can't delete yourself!", -1); return false; } - $count = $this->_auth->triggerUserMod('delete', array($selected)); + $count = $this->auth->triggerUserMod('delete', array($selected)); if ($count == count($selected)) { $text = str_replace('%d', $count, $this->lang['delete_ok']); msg("$text.", 1); } else { $part1 = str_replace('%d', $count, $this->lang['delete_ok']); $part2 = str_replace('%d', (count($selected)-$count), $this->lang['delete_fail']); - msg("$part1, $part2",-1); + msg("$part1, $part2", -1); } // invalidate all sessions - io_saveFile($conf['cachedir'].'/sessionpurge',time()); + io_saveFile($conf['cachedir'].'/sessionpurge', time()); return true; } @@ -642,20 +751,21 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * @param string $param id of the user * @return bool whether succesful */ - protected function _editUser($param) { + protected function editUser($param) + { if (!checkSecurityToken()) return false; - if (!$this->_auth->canDo('UserMod')) return false; - $user = $this->_auth->cleanUser(preg_replace('/.*[:\/]/','',$param)); - $userdata = $this->_auth->getUserData($user); + if (!$this->auth->canDo('UserMod')) return false; + $user = $this->auth->cleanUser(preg_replace('/.*[:\/]/', '', $param)); + $userdata = $this->auth->getUserData($user); // no user found? if (!$userdata) { - msg($this->lang['edit_usermissing'],-1); + msg($this->lang['edit_usermissing'], -1); return false; } - $this->_edit_user = $user; - $this->_edit_userdata = $userdata; + $this->edit_user = $user; + $this->edit_userdata = $userdata; return true; } @@ -665,39 +775,39 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * * @return bool whether succesful */ - protected function _modifyUser(){ + protected function modifyUser() + { global $conf, $INPUT; if (!checkSecurityToken()) return false; - if (!$this->_auth->canDo('UserMod')) return false; + if (!$this->auth->canDo('UserMod')) return false; // get currently valid user data - $olduser = $this->_auth->cleanUser(preg_replace('/.*[:\/]/','',$INPUT->str('userid_old'))); - $oldinfo = $this->_auth->getUserData($olduser); + $olduser = $this->auth->cleanUser(preg_replace('/.*[:\/]/', '', $INPUT->str('userid_old'))); + $oldinfo = $this->auth->getUserData($olduser); // get new user data subject to change - list($newuser,$newpass,$newname,$newmail,$newgrps,$passconfirm) = $this->_retrieveUser(); + list($newuser,$newpass,$newname,$newmail,$newgrps,$passconfirm) = $this->retrieveUser(); if (empty($newuser)) return false; $changes = array(); if ($newuser != $olduser) { - - if (!$this->_auth->canDo('modLogin')) { // sanity check, shouldn't be possible - msg($this->lang['update_fail'],-1); + if (!$this->auth->canDo('modLogin')) { // sanity check, shouldn't be possible + msg($this->lang['update_fail'], -1); return false; } // check if $newuser already exists - if ($this->_auth->getUserData($newuser)) { - msg(sprintf($this->lang['update_exists'],$newuser),-1); + if ($this->auth->getUserData($newuser)) { + msg(sprintf($this->lang['update_exists'], $newuser), -1); $re_edit = true; } else { $changes['user'] = $newuser; } } - if ($this->_auth->canDo('modPass')) { + if ($this->auth->canDo('modPass')) { if ($newpass || $passconfirm) { - if ($this->_verifyPassword($newpass,$passconfirm)) { + if ($this->verifyPassword($newpass, $passconfirm)) { $changes['pass'] = $newpass; } else { return false; @@ -710,33 +820,32 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { } } - if (!empty($newname) && $this->_auth->canDo('modName') && $newname != $oldinfo['name']) { + if (!empty($newname) && $this->auth->canDo('modName') && $newname != $oldinfo['name']) { $changes['name'] = $newname; } - if (!empty($newmail) && $this->_auth->canDo('modMail') && $newmail != $oldinfo['mail']) { + if (!empty($newmail) && $this->auth->canDo('modMail') && $newmail != $oldinfo['mail']) { $changes['mail'] = $newmail; } - if (!empty($newgrps) && $this->_auth->canDo('modGroups') && $newgrps != $oldinfo['grps']) { + if (!empty($newgrps) && $this->auth->canDo('modGroups') && $newgrps != $oldinfo['grps']) { $changes['grps'] = $newgrps; } - if ($ok = $this->_auth->triggerUserMod('modify', array($olduser, $changes))) { - msg($this->lang['update_ok'],1); + if ($ok = $this->auth->triggerUserMod('modify', array($olduser, $changes))) { + msg($this->lang['update_ok'], 1); if ($INPUT->has('usernotify') && !empty($changes['pass'])) { $notify = empty($changes['user']) ? $olduser : $newuser; - $this->_notifyUser($notify,$changes['pass']); + $this->notifyUser($notify, $changes['pass']); } // invalidate all sessions - io_saveFile($conf['cachedir'].'/sessionpurge',time()); - + io_saveFile($conf['cachedir'].'/sessionpurge', time()); } else { - msg($this->lang['update_fail'],-1); + msg($this->lang['update_fail'], -1); } if (!empty($re_edit)) { - $this->_editUser($olduser); + $this->editUser($olduser); } return $ok; @@ -750,9 +859,10 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * @param bool $status_alert whether status alert should be shown * @return bool whether succesful */ - protected function _notifyUser($user, $password, $status_alert=true) { + protected function notifyUser($user, $password, $status_alert = true) + { - if ($sent = auth_sendPassword($user,$password)) { + if ($sent = auth_sendPassword($user, $password)) { if ($status_alert) { msg($this->lang['notify_ok'], 1); } @@ -773,7 +883,8 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * @param string $confirm repeated password for confirmation * @return bool true if meets requirements, false otherwise */ - protected function _verifyPassword($password, $confirm) { + protected function verifyPassword($password, $confirm) + { global $lang; if (empty($password) && empty($confirm)) { @@ -797,7 +908,8 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * @param bool $clean whether the cleanUser method of the authentication backend is applied * @return array (user, password, full name, email, array(groups)) */ - protected function _retrieveUser($clean=true) { + protected function retrieveUser($clean = true) + { /** @var DokuWiki_Auth_Plugin $auth */ global $auth; global $INPUT; @@ -807,14 +919,14 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { $user[1] = $INPUT->str('userpass'); $user[2] = $INPUT->str('username'); $user[3] = $INPUT->str('usermail'); - $user[4] = explode(',',$INPUT->str('usergroups')); + $user[4] = explode(',', $INPUT->str('usergroups')); $user[5] = $INPUT->str('userpass2'); // repeated password for confirmation - $user[4] = array_map('trim',$user[4]); - if($clean) $user[4] = array_map(array($auth,'cleanGroup'),$user[4]); + $user[4] = array_map('trim', $user[4]); + if ($clean) $user[4] = array_map(array($auth,'cleanGroup'), $user[4]); $user[4] = array_filter($user[4]); $user[4] = array_unique($user[4]); - if(!count($user[4])) $user[4] = null; + if (!count($user[4])) $user[4] = null; return $user; } @@ -824,17 +936,18 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * * @param string $op 'new' or 'clear' */ - protected function _setFilter($op) { + protected function setFilter($op) + { - $this->_filter = array(); + $this->filter = array(); if ($op == 'new') { - list($user,/* $pass */,$name,$mail,$grps) = $this->_retrieveUser(false); + list($user,/* $pass */,$name,$mail,$grps) = $this->retrieveUser(false); - if (!empty($user)) $this->_filter['user'] = $user; - if (!empty($name)) $this->_filter['name'] = $name; - if (!empty($mail)) $this->_filter['mail'] = $mail; - if (!empty($grps)) $this->_filter['grps'] = join('|',$grps); + if (!empty($user)) $this->filter['user'] = $user; + if (!empty($name)) $this->filter['name'] = $name; + if (!empty($mail)) $this->filter['mail'] = $mail; + if (!empty($grps)) $this->filter['grps'] = join('|', $grps); } } @@ -843,7 +956,8 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * * @return array */ - protected function _retrieveFilter() { + protected function retrieveFilter() + { global $INPUT; $t_filter = $INPUT->arr('filter'); @@ -862,14 +976,15 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { /** * Validate and improve the pagination values */ - protected function _validatePagination() { + protected function validatePagination() + { - if ($this->_start >= $this->_user_total) { - $this->_start = $this->_user_total - $this->_pagesize; + if ($this->start >= $this->users_total) { + $this->start = $this->users_total - $this->pagesize; } - if ($this->_start < 0) $this->_start = 0; + if ($this->start < 0) $this->start = 0; - $this->_last = min($this->_user_total, $this->_start + $this->_pagesize); + $this->last = min($this->users_total, $this->start + $this->pagesize); } /** @@ -877,21 +992,23 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * * @return array with enable/disable attributes */ - protected function _pagination() { + protected function pagination() + { $disabled = 'disabled="disabled"'; $buttons = array(); - $buttons['start'] = $buttons['prev'] = ($this->_start == 0) ? $disabled : ''; + $buttons['start'] = $buttons['prev'] = ($this->start == 0) ? $disabled : ''; - if ($this->_user_total == -1) { + if ($this->users_total == -1) { $buttons['last'] = $disabled; $buttons['next'] = ''; } else { - $buttons['last'] = $buttons['next'] = (($this->_start + $this->_pagesize) >= $this->_user_total) ? $disabled : ''; + $buttons['last'] = $buttons['next'] = + (($this->start + $this->pagesize) >= $this->users_total) ? $disabled : ''; } - if ($this->_lastdisabled) { + if ($this->lastdisabled) { $buttons['last'] = $disabled; } @@ -901,9 +1018,10 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { /** * Export a list of users in csv format using the current filter criteria */ - protected function _export() { + protected function exportCSV() + { // list of users for export - based on current filter criteria - $user_list = $this->_auth->retrieveUsers(0, 0, $this->_filter); + $user_list = $this->auth->retrieveUsers(0, 0, $this->filter); $column_headings = array( $this->lang["user_id"], $this->lang["user_name"], @@ -920,14 +1038,16 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { # header('Content-type: text/plain;charset=utf-8'); // output the csv - $fd = fopen('php://output','w'); + $fd = fopen('php://output', 'w'); fputcsv($fd, $column_headings); foreach ($user_list as $user => $info) { - $line = array($user, $info['name'], $info['mail'], join(',',$info['grps'])); + $line = array($user, $info['name'], $info['mail'], join(',', $info['grps'])); fputcsv($fd, $line); } fclose($fd); - if (defined('DOKU_UNITTEST')){ return; } + if (defined('DOKU_UNITTEST')) { + return; + } die; } @@ -939,25 +1059,28 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * * @return bool whether successful */ - protected function _import() { + protected function importCSV() + { // check we are allowed to add users if (!checkSecurityToken()) return false; - if (!$this->_auth->canDo('addUser')) return false; + if (!$this->auth->canDo('addUser')) return false; // check file uploaded ok. - if (empty($_FILES['import']['size']) || !empty($_FILES['import']['error']) && $this->_isUploadedFile($_FILES['import']['tmp_name'])) { - msg($this->lang['import_error_upload'],-1); + if (empty($_FILES['import']['size']) || + !empty($_FILES['import']['error']) && $this->isUploadedFile($_FILES['import']['tmp_name']) + ) { + msg($this->lang['import_error_upload'], -1); return false; } // retrieve users from the file - $this->_import_failures = array(); + $this->import_failures = array(); $import_success_count = 0; $import_fail_count = 0; $line = 0; - $fd = fopen($_FILES['import']['tmp_name'],'r'); + $fd = fopen($_FILES['import']['tmp_name'], 'r'); if ($fd) { - while($csv = fgets($fd)){ - if (!utf8_check($csv)) { + while ($csv = fgets($fd)) { + if (!\dokuwiki\Utf8\Clean::isUtf8($csv)) { $csv = utf8_encode($csv); } $raw = str_getcsv($csv); @@ -969,35 +1092,42 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { if (count($raw) < 4) { // need at least four fields $import_fail_count++; $error = sprintf($this->lang['import_error_fields'], count($raw)); - $this->_import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv); + $this->import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv); continue; } - array_splice($raw,1,0,auth_pwgen()); // splice in a generated password - $clean = $this->_cleanImportUser($raw, $error); - if ($clean && $this->_addImportUser($clean, $error)) { - $sent = $this->_notifyUser($clean[0],$clean[1],false); - if (!$sent){ - msg(sprintf($this->lang['import_notify_fail'],$clean[0],$clean[3]),-1); + array_splice($raw, 1, 0, auth_pwgen()); // splice in a generated password + $clean = $this->cleanImportUser($raw, $error); + if ($clean && $this->importUser($clean, $error)) { + $sent = $this->notifyUser($clean[0], $clean[1], false); + if (!$sent) { + msg(sprintf($this->lang['import_notify_fail'], $clean[0], $clean[3]), -1); } $import_success_count++; } else { $import_fail_count++; array_splice($raw, 1, 1); // remove the spliced in password - $this->_import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv); + $this->import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv); } } - msg(sprintf($this->lang['import_success_count'], ($import_success_count+$import_fail_count), $import_success_count),($import_success_count ? 1 : -1)); + msg( + sprintf( + $this->lang['import_success_count'], + ($import_success_count + $import_fail_count), + $import_success_count + ), + ($import_success_count ? 1 : -1) + ); if ($import_fail_count) { - msg(sprintf($this->lang['import_failure_count'], $import_fail_count),-1); + msg(sprintf($this->lang['import_failure_count'], $import_fail_count), -1); } } else { - msg($this->lang['import_error_readfail'],-1); + msg($this->lang['import_error_readfail'], -1); } // save import failures into the session if (!headers_sent()) { session_start(); - $_SESSION['import_failures'] = $this->_import_failures; + $_SESSION['import_failures'] = $this->import_failures; session_write_close(); } return true; @@ -1010,17 +1140,18 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * @param string $error * @return array|false cleaned data or false */ - protected function _cleanImportUser($candidate, & $error){ + protected function cleanImportUser($candidate, & $error) + { global $INPUT; - // kludgy .... + // FIXME kludgy .... $INPUT->set('userid', $candidate[0]); $INPUT->set('userpass', $candidate[1]); $INPUT->set('username', $candidate[2]); $INPUT->set('usermail', $candidate[3]); $INPUT->set('usergroups', $candidate[4]); - $cleaned = $this->_retrieveUser(); + $cleaned = $this->retrieveUser(); list($user,/* $pass */,$name,$mail,/* $grps */) = $cleaned; if (empty($user)) { $error = $this->lang['import_error_baduserid']; @@ -1029,12 +1160,12 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { // no need to check password, handled elsewhere - if (!($this->_auth->canDo('modName') xor empty($name))){ + if (!($this->auth->canDo('modName') xor empty($name))) { $error = $this->lang['import_error_badname']; return false; } - if ($this->_auth->canDo('modMail')) { + if ($this->auth->canDo('modMail')) { if (empty($mail) || !mail_isvalid($mail)) { $error = $this->lang['import_error_badmail']; return false; @@ -1058,8 +1189,9 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * @param string &$error reference catched error message * @return bool whether successful */ - protected function _addImportUser($user, & $error){ - if (!$this->_auth->triggerUserMod('create', $user)) { + protected function importUser($user, &$error) + { + if (!$this->auth->triggerUserMod('create', $user)) { $error = $this->lang['import_error_create']; return false; } @@ -1070,7 +1202,8 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { /** * Downloads failures as csv file */ - protected function _downloadImportFailures(){ + protected function downloadImportFailures() + { // ============================================================================================== // GENERATE OUTPUT @@ -1081,8 +1214,8 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { # header('Content-type: text/plain;charset=utf-8'); // output the csv - $fd = fopen('php://output','w'); - foreach ($this->_import_failures as $fail) { + $fd = fopen('php://output', 'w'); + foreach ($this->import_failures as $fail) { fputs($fd, $fail['orig']); } fclose($fd); @@ -1095,7 +1228,8 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { * @param string $file filename * @return bool */ - protected function _isUploadedFile($file) { + protected function isUploadedFile($file) + { return is_uploaded_file($file); } } diff --git a/lib/tpl/dokuwiki/images/pagetools-build.php b/lib/tpl/dokuwiki/images/pagetools-build.php index eaceeb257..e19d750c7 100644 --- a/lib/tpl/dokuwiki/images/pagetools-build.php +++ b/lib/tpl/dokuwiki/images/pagetools-build.php @@ -1,4 +1,5 @@ <?php +// phpcs:ignoreFile -- deprecated and will be removed /** * This script generates a sprite from the unprocessed pagetool icons by combining them * and overlaying a color layer for the active state. @@ -10,6 +11,7 @@ * The final sprite is optimized with optipng if available. * * @author Andreas Gohr <andi@splitbrain.org> + * @deprecated 2018-06-15 we no longer use PNG based icons * @todo Maybe add some more error checking */ $GAMMA = 0.8; diff --git a/lib/tpl/dokuwiki/tpl_footer.php b/lib/tpl/dokuwiki/tpl_footer.php index 34e8b90f6..c7a04e155 100644 --- a/lib/tpl/dokuwiki/tpl_footer.php +++ b/lib/tpl/dokuwiki/tpl_footer.php @@ -25,7 +25,8 @@ if (!defined('DOKU_INC')) die(); <a href="//jigsaw.w3.org/css-validator/check/referer?profile=css3" title="Valid CSS" <?php echo $target?>><img src="<?php echo tpl_basedir(); ?>images/button-css.png" width="80" height="15" alt="Valid CSS" /></a> <a href="https://dokuwiki.org/" title="Driven by DokuWiki" <?php echo $target?>><img - src="<?php echo tpl_basedir(); ?>images/button-dw.png" width="80" height="15" alt="Driven by DokuWiki" /></a> + src="<?php echo tpl_basedir(); ?>images/button-dw.png" width="80" height="15" + alt="Driven by DokuWiki" /></a> </div> </div></div><!-- /footer --> diff --git a/lib/tpl/index.php b/lib/tpl/index.php index 8b021511c..a3e9a8849 100644 --- a/lib/tpl/index.php +++ b/lib/tpl/index.php @@ -8,6 +8,7 @@ * @author Andreas Gohr <andi@splitbrain.org> * @author Anika Henke <anika@selfthinker.org> */ +// phpcs:disable PSR1.Files.SideEffects if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); if(!defined('NOSESSION')) define('NOSESSION',1); require_once(DOKU_INC.'inc/init.php'); @@ -44,8 +45,6 @@ require_once(DOKU_INC.'inc/init.php'); <body> <?php // get merged style.ini -define('SIMPLE_TEST', true); // hack to prevent css output and headers -require_once(DOKU_INC.'lib/exe/css.php'); $styleUtils = new \dokuwiki\StyleUtils($conf['template']); $ini = $styleUtils->cssStyleini(); |