ajout de la partie slam dans le dossier web
This commit is contained in:
8
ap23/web/doku/inc/.htaccess
Normal file
8
ap23/web/doku/inc/.htaccess
Normal file
@@ -0,0 +1,8 @@
|
||||
## no access to the inc directory
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</IfModule>
|
25
ap23/web/doku/inc/Action/AbstractAclAction.php
Normal file
25
ap23/web/doku/inc/Action/AbstractAclAction.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAclRequiredException;
|
||||
|
||||
/**
|
||||
* Class AbstractAclAction
|
||||
*
|
||||
* An action that requires the ACL subsystem to be enabled (eg. useacl=1)
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
abstract class AbstractAclAction extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions() {
|
||||
parent::checkPreconditions();
|
||||
global $conf;
|
||||
global $auth;
|
||||
if(!$conf['useacl']) throw new ActionAclRequiredException();
|
||||
if(!$auth) throw new ActionAclRequiredException();
|
||||
}
|
||||
|
||||
}
|
88
ap23/web/doku/inc/Action/AbstractAction.php
Normal file
88
ap23/web/doku/inc/Action/AbstractAction.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
use dokuwiki\Action\Exception\FatalException;
|
||||
|
||||
/**
|
||||
* Class AbstractAction
|
||||
*
|
||||
* Base class for all actions
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
abstract class AbstractAction {
|
||||
|
||||
/** @var string holds the name of the action (lowercase class name, no namespace) */
|
||||
protected $actionname;
|
||||
|
||||
/**
|
||||
* AbstractAction constructor.
|
||||
*
|
||||
* @param string $actionname the name of this action (see getActionName() for caveats)
|
||||
*/
|
||||
public function __construct($actionname = '') {
|
||||
if($actionname !== '') {
|
||||
$this->actionname = $actionname;
|
||||
} else {
|
||||
// http://stackoverflow.com/a/27457689/172068
|
||||
$this->actionname = strtolower(substr(strrchr(get_class($this), '\\'), 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the minimum permission needed
|
||||
*
|
||||
* This needs to return one of the AUTH_* constants. It will be checked against
|
||||
* the current user and page after checkPermissions() ran through. If it fails,
|
||||
* the user will be shown the Denied action.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function minimumPermission();
|
||||
|
||||
/**
|
||||
* Check conditions are met to run this action
|
||||
*
|
||||
* @throws ActionException
|
||||
* @return void
|
||||
*/
|
||||
public function checkPreconditions() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process data
|
||||
*
|
||||
* This runs before any output is sent to the browser.
|
||||
*
|
||||
* Throw an Exception if a different action should be run after this step.
|
||||
*
|
||||
* @throws ActionException
|
||||
* @return void
|
||||
*/
|
||||
public function preProcess() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Output whatever content is wanted within tpl_content();
|
||||
*
|
||||
* @fixme we may want to return a Ui class here
|
||||
*/
|
||||
public function tplContent() {
|
||||
throw new FatalException('No content for Action ' . $this->actionname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this action
|
||||
*
|
||||
* This is usually the lowercased class name, but may differ for some actions.
|
||||
* eg. the export_ modes or for the Plugin action.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getActionName() {
|
||||
return $this->actionname;
|
||||
}
|
||||
}
|
28
ap23/web/doku/inc/Action/AbstractAliasAction.php
Normal file
28
ap23/web/doku/inc/Action/AbstractAliasAction.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\FatalException;
|
||||
|
||||
/**
|
||||
* Class AbstractAliasAction
|
||||
*
|
||||
* An action that is an alias for another action. Skips the minimumPermission check
|
||||
*
|
||||
* Be sure to implement preProcess() and throw an ActionAbort exception
|
||||
* with the proper action.
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
abstract class AbstractAliasAction extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
public function preProcess() {
|
||||
throw new FatalException('Alias Actions need to implement preProcess to load the aliased action');
|
||||
}
|
||||
|
||||
}
|
25
ap23/web/doku/inc/Action/AbstractUserAction.php
Normal file
25
ap23/web/doku/inc/Action/AbstractUserAction.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionUserRequiredException;
|
||||
|
||||
/**
|
||||
* Class AbstractUserAction
|
||||
*
|
||||
* An action that requires a logged in user
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
abstract class AbstractUserAction extends AbstractAclAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions() {
|
||||
parent::checkPreconditions();
|
||||
global $INPUT;
|
||||
if(!$INPUT->server->str('REMOTE_USER')) {
|
||||
throw new ActionUserRequiredException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
45
ap23/web/doku/inc/Action/Admin.php
Normal file
45
ap23/web/doku/inc/Action/Admin.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
|
||||
/**
|
||||
* Class Admin
|
||||
*
|
||||
* Action to show the admin interface or admin plugins
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Admin extends AbstractUserAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_READ; // let in check later
|
||||
}
|
||||
|
||||
public function checkPreconditions() {
|
||||
parent::checkPreconditions();
|
||||
}
|
||||
|
||||
public function preProcess() {
|
||||
global $INPUT;
|
||||
global $INFO;
|
||||
|
||||
// retrieve admin plugin name from $_REQUEST['page']
|
||||
if(($page = $INPUT->str('page', '', true)) != '') {
|
||||
/** @var $plugin \dokuwiki\Extension\AdminPlugin */
|
||||
if($plugin = plugin_getRequestAdminPlugin()) { // FIXME this method does also permission checking
|
||||
if(!$plugin->isAccessibleByCurrentUser()) {
|
||||
throw new ActionException('denied');
|
||||
}
|
||||
$plugin->handle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function tplContent() {
|
||||
tpl_admin();
|
||||
}
|
||||
|
||||
}
|
24
ap23/web/doku/inc/Action/Backlink.php
Normal file
24
ap23/web/doku/inc/Action/Backlink.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Backlink
|
||||
*
|
||||
* Shows which pages link to the current page
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Backlink extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
html_backlinks();
|
||||
}
|
||||
|
||||
}
|
25
ap23/web/doku/inc/Action/Cancel.php
Normal file
25
ap23/web/doku/inc/Action/Cancel.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
|
||||
/**
|
||||
* Class Cancel
|
||||
*
|
||||
* Alias for show. Aborts editing
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Cancel extends AbstractAliasAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
global $ID;
|
||||
unlock($ID);
|
||||
|
||||
// continue with draftdel -> redirect -> show
|
||||
throw new ActionAbort('draftdel');
|
||||
}
|
||||
|
||||
}
|
26
ap23/web/doku/inc/Action/Check.php
Normal file
26
ap23/web/doku/inc/Action/Check.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
|
||||
/**
|
||||
* Class Check
|
||||
*
|
||||
* Adds some debugging info before aborting to show
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Check extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
public function preProcess() {
|
||||
check();
|
||||
throw new ActionAbort();
|
||||
}
|
||||
|
||||
}
|
34
ap23/web/doku/inc/Action/Conflict.php
Normal file
34
ap23/web/doku/inc/Action/Conflict.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Conflict
|
||||
*
|
||||
* Show the conflict resolution screen
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Conflict extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
global $INFO;
|
||||
if($INFO['exists']) {
|
||||
return AUTH_EDIT;
|
||||
} else {
|
||||
return AUTH_CREATE;
|
||||
}
|
||||
}
|
||||
|
||||
public function tplContent() {
|
||||
global $PRE;
|
||||
global $TEXT;
|
||||
global $SUF;
|
||||
global $SUM;
|
||||
|
||||
html_conflict(con($PRE, $TEXT, $SUF), $SUM);
|
||||
html_diff(con($PRE, $TEXT, $SUF), false);
|
||||
}
|
||||
|
||||
}
|
23
ap23/web/doku/inc/Action/Denied.php
Normal file
23
ap23/web/doku/inc/Action/Denied.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Denied
|
||||
*
|
||||
* Show the access denied screen
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Denied extends AbstractAclAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
public function tplContent() {
|
||||
html_denied();
|
||||
}
|
||||
|
||||
}
|
35
ap23/web/doku/inc/Action/Diff.php
Normal file
35
ap23/web/doku/inc/Action/Diff.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Diff
|
||||
*
|
||||
* Show the differences between two revisions
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Diff extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
global $INPUT;
|
||||
|
||||
// store the selected diff type in cookie
|
||||
$difftype = $INPUT->str('difftype');
|
||||
if(!empty($difftype)) {
|
||||
set_doku_pref('difftype', $difftype);
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
html_diff();
|
||||
}
|
||||
|
||||
}
|
39
ap23/web/doku/inc/Action/Draft.php
Normal file
39
ap23/web/doku/inc/Action/Draft.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
|
||||
/**
|
||||
* Class Draft
|
||||
*
|
||||
* Screen to see and recover a draft
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
* @fixme combine with Recover?
|
||||
*/
|
||||
class Draft extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
global $INFO;
|
||||
if($INFO['exists']) {
|
||||
return AUTH_EDIT;
|
||||
} else {
|
||||
return AUTH_CREATE;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions() {
|
||||
parent::checkPreconditions();
|
||||
global $INFO;
|
||||
if(!file_exists($INFO['draft'])) throw new ActionException('edit');
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
html_draft();
|
||||
}
|
||||
|
||||
}
|
38
ap23/web/doku/inc/Action/Draftdel.php
Normal file
38
ap23/web/doku/inc/Action/Draftdel.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
|
||||
/**
|
||||
* Class Draftdel
|
||||
*
|
||||
* Delete a draft
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Draftdel extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_EDIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing draft for the current page and user if any
|
||||
*
|
||||
* Redirects to show, afterwards.
|
||||
*
|
||||
* @throws ActionAbort
|
||||
*/
|
||||
public function preProcess() {
|
||||
global $INFO, $ID;
|
||||
$draft = new \dokuwiki\Draft($ID, $INFO['client']);
|
||||
if ($draft->isDraftAvailable()) {
|
||||
$draft->deleteDraft();
|
||||
}
|
||||
|
||||
throw new ActionAbort('redirect');
|
||||
}
|
||||
|
||||
}
|
91
ap23/web/doku/inc/Action/Edit.php
Normal file
91
ap23/web/doku/inc/Action/Edit.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
|
||||
/**
|
||||
* Class Edit
|
||||
*
|
||||
* Handle editing
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Edit extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
global $INFO;
|
||||
if($INFO['exists']) {
|
||||
return AUTH_READ; // we check again below
|
||||
} else {
|
||||
return AUTH_CREATE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc falls back to 'source' if page not writable
|
||||
*/
|
||||
public function checkPreconditions() {
|
||||
parent::checkPreconditions();
|
||||
global $INFO;
|
||||
|
||||
// no edit permission? view source
|
||||
if($INFO['exists'] && !$INFO['writable']) {
|
||||
throw new ActionAbort('source');
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
global $ID;
|
||||
global $INFO;
|
||||
|
||||
global $TEXT;
|
||||
global $RANGE;
|
||||
global $PRE;
|
||||
global $SUF;
|
||||
global $REV;
|
||||
global $SUM;
|
||||
global $lang;
|
||||
global $DATE;
|
||||
|
||||
if(!isset($TEXT)) {
|
||||
if($INFO['exists']) {
|
||||
if($RANGE) {
|
||||
list($PRE, $TEXT, $SUF) = rawWikiSlices($RANGE, $ID, $REV);
|
||||
} else {
|
||||
$TEXT = rawWiki($ID, $REV);
|
||||
}
|
||||
} else {
|
||||
$TEXT = pageTemplate($ID);
|
||||
}
|
||||
}
|
||||
|
||||
//set summary default
|
||||
if(!$SUM) {
|
||||
if($REV) {
|
||||
$SUM = sprintf($lang['restored'], dformat($REV));
|
||||
} elseif(!$INFO['exists']) {
|
||||
$SUM = $lang['created'];
|
||||
}
|
||||
}
|
||||
|
||||
// Use the date of the newest revision, not of the revision we edit
|
||||
// This is used for conflict detection
|
||||
if(!$DATE) $DATE = @filemtime(wikiFN($ID));
|
||||
|
||||
//check if locked by anyone - if not lock for my self
|
||||
$lockedby = checklock($ID);
|
||||
if($lockedby) {
|
||||
throw new ActionAbort('locked');
|
||||
};
|
||||
lock($ID);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
html_edit();
|
||||
}
|
||||
|
||||
}
|
20
ap23/web/doku/inc/Action/Exception/ActionAbort.php
Normal file
20
ap23/web/doku/inc/Action/Exception/ActionAbort.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class ActionAbort
|
||||
*
|
||||
* Strictly speaking not an Exception but an expected execution path. Used to
|
||||
* signal when one action is done and another should take over.
|
||||
*
|
||||
* If you want to signal the same but under some error condition use ActionException
|
||||
* or one of it's decendants.
|
||||
*
|
||||
* The message will NOT be shown to the enduser
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class ActionAbort extends ActionException {
|
||||
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class ActionAclRequiredException
|
||||
*
|
||||
* Thrown by AbstractACLAction when an action requires that the ACL subsystem is
|
||||
* enabled but it isn't. You should not use it
|
||||
*
|
||||
* The message will NOT be shown to the enduser
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class ActionAclRequiredException extends ActionException {
|
||||
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class ActionDisabledException
|
||||
*
|
||||
* Thrown when the requested action has been disabled. Eg. through the 'disableactions'
|
||||
* config setting. You should probably not use it.
|
||||
*
|
||||
* The message will NOT be shown to the enduser, but a generic information will be shown.
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class ActionDisabledException extends ActionException {
|
||||
|
||||
}
|
66
ap23/web/doku/inc/Action/Exception/ActionException.php
Normal file
66
ap23/web/doku/inc/Action/Exception/ActionException.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class ActionException
|
||||
*
|
||||
* This exception and its subclasses signal that the current action should be
|
||||
* aborted and a different action should be used instead. The new action can
|
||||
* be given as parameter in the constructor. Defaults to 'show'
|
||||
*
|
||||
* The message will NOT be shown to the enduser
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class ActionException extends \Exception {
|
||||
|
||||
/** @var string the new action */
|
||||
protected $newaction;
|
||||
|
||||
/** @var bool should the exception's message be shown to the user? */
|
||||
protected $displayToUser = false;
|
||||
|
||||
/**
|
||||
* ActionException constructor.
|
||||
*
|
||||
* When no new action is given 'show' is assumed. For requests that originated in a POST,
|
||||
* a 'redirect' is used which will cause a redirect to the 'show' action.
|
||||
*
|
||||
* @param string|null $newaction the action that should be used next
|
||||
* @param string $message optional message, will not be shown except for some dub classes
|
||||
*/
|
||||
public function __construct($newaction = null, $message = '') {
|
||||
global $INPUT;
|
||||
parent::__construct($message);
|
||||
if(is_null($newaction)) {
|
||||
if(strtolower($INPUT->server->str('REQUEST_METHOD')) == 'post') {
|
||||
$newaction = 'redirect';
|
||||
} else {
|
||||
$newaction = 'show';
|
||||
}
|
||||
}
|
||||
|
||||
$this->newaction = $newaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action to use next
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNewAction() {
|
||||
return $this->newaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this Exception's message be shown to the user?
|
||||
*
|
||||
* @param null|bool $set when null is given, the current setting is not changed
|
||||
* @return bool
|
||||
*/
|
||||
public function displayToUser($set = null) {
|
||||
if(!is_null($set)) $this->displayToUser = $set;
|
||||
return $set;
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class ActionUserRequiredException
|
||||
*
|
||||
* Thrown by AbstractUserAction when an action requires that a user is logged
|
||||
* in but it isn't. You should not use it.
|
||||
*
|
||||
* The message will NOT be shown to the enduser
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class ActionUserRequiredException extends ActionException {
|
||||
|
||||
}
|
26
ap23/web/doku/inc/Action/Exception/FatalException.php
Normal file
26
ap23/web/doku/inc/Action/Exception/FatalException.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class FatalException
|
||||
*
|
||||
* A fatal exception during handling the action
|
||||
*
|
||||
* Will abort all handling and display some info to the user. The HTTP status code
|
||||
* can be defined.
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class FatalException extends \Exception {
|
||||
/**
|
||||
* FatalException constructor.
|
||||
*
|
||||
* @param string $message the message to send
|
||||
* @param int $status the HTTP status to send
|
||||
* @param null|\Exception $previous previous exception
|
||||
*/
|
||||
public function __construct($message = 'A fatal error occured', $status = 500, $previous = null) {
|
||||
parent::__construct($message, $status, $previous);
|
||||
}
|
||||
}
|
15
ap23/web/doku/inc/Action/Exception/NoActionException.php
Normal file
15
ap23/web/doku/inc/Action/Exception/NoActionException.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action\Exception;
|
||||
|
||||
/**
|
||||
* Class NoActionException
|
||||
*
|
||||
* Thrown in the ActionRouter when a wanted action can not be found. Triggers
|
||||
* the unknown action event
|
||||
*
|
||||
* @package dokuwiki\Action\Exception
|
||||
*/
|
||||
class NoActionException extends \Exception {
|
||||
|
||||
}
|
113
ap23/web/doku/inc/Action/Export.php
Normal file
113
ap23/web/doku/inc/Action/Export.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
/**
|
||||
* Class Export
|
||||
*
|
||||
* Handle exporting by calling the appropriate renderer
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Export extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a wiki page for various formats
|
||||
*
|
||||
* Triggers ACTION_EXPORT_POSTPROCESS
|
||||
*
|
||||
* Event data:
|
||||
* data['id'] -- page id
|
||||
* data['mode'] -- requested export mode
|
||||
* data['headers'] -- export headers
|
||||
* data['output'] -- export output
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @author Michael Klier <chi@chimeric.de>
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function preProcess() {
|
||||
global $ID;
|
||||
global $REV;
|
||||
global $conf;
|
||||
global $lang;
|
||||
|
||||
$pre = '';
|
||||
$post = '';
|
||||
$headers = array();
|
||||
|
||||
// search engines: never cache exported docs! (Google only currently)
|
||||
$headers['X-Robots-Tag'] = 'noindex';
|
||||
|
||||
$mode = substr($this->actionname, 7);
|
||||
switch($mode) {
|
||||
case 'raw':
|
||||
$headers['Content-Type'] = 'text/plain; charset=utf-8';
|
||||
$headers['Content-Disposition'] = 'attachment; filename=' . noNS($ID) . '.txt';
|
||||
$output = rawWiki($ID, $REV);
|
||||
break;
|
||||
case 'xhtml':
|
||||
$pre .= '<!DOCTYPE html>' . DOKU_LF;
|
||||
$pre .= '<html lang="' . $conf['lang'] . '" dir="' . $lang['direction'] . '">' . DOKU_LF;
|
||||
$pre .= '<head>' . DOKU_LF;
|
||||
$pre .= ' <meta charset="utf-8" />' . DOKU_LF; // FIXME improve wrapper
|
||||
$pre .= ' <title>' . $ID . '</title>' . DOKU_LF;
|
||||
|
||||
// get metaheaders
|
||||
ob_start();
|
||||
tpl_metaheaders();
|
||||
$pre .= ob_get_clean();
|
||||
|
||||
$pre .= '</head>' . DOKU_LF;
|
||||
$pre .= '<body>' . DOKU_LF;
|
||||
$pre .= '<div class="dokuwiki export">' . DOKU_LF;
|
||||
|
||||
// get toc
|
||||
$pre .= tpl_toc(true);
|
||||
|
||||
$headers['Content-Type'] = 'text/html; charset=utf-8';
|
||||
$output = p_wiki_xhtml($ID, $REV, false);
|
||||
|
||||
$post .= '</div>' . DOKU_LF;
|
||||
$post .= '</body>' . DOKU_LF;
|
||||
$post .= '</html>' . DOKU_LF;
|
||||
break;
|
||||
case 'xhtmlbody':
|
||||
$headers['Content-Type'] = 'text/html; charset=utf-8';
|
||||
$output = p_wiki_xhtml($ID, $REV, false);
|
||||
break;
|
||||
default:
|
||||
$output = p_cached_output(wikiFN($ID, $REV), $mode, $ID);
|
||||
$headers = p_get_metadata($ID, "format $mode");
|
||||
break;
|
||||
}
|
||||
|
||||
// prepare event data
|
||||
$data = array();
|
||||
$data['id'] = $ID;
|
||||
$data['mode'] = $mode;
|
||||
$data['headers'] = $headers;
|
||||
$data['output'] =& $output;
|
||||
|
||||
Event::createAndTrigger('ACTION_EXPORT_POSTPROCESS', $data);
|
||||
|
||||
if(!empty($data['output'])) {
|
||||
if(is_array($data['headers'])) foreach($data['headers'] as $key => $val) {
|
||||
header("$key: $val");
|
||||
}
|
||||
print $pre . $data['output'] . $post;
|
||||
exit;
|
||||
}
|
||||
|
||||
throw new ActionAbort();
|
||||
}
|
||||
|
||||
}
|
25
ap23/web/doku/inc/Action/Index.php
Normal file
25
ap23/web/doku/inc/Action/Index.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Index
|
||||
*
|
||||
* Show the human readable sitemap. Do not confuse with Sitemap
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Index extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
global $IDX;
|
||||
html_index($IDX);
|
||||
}
|
||||
|
||||
}
|
25
ap23/web/doku/inc/Action/Locked.php
Normal file
25
ap23/web/doku/inc/Action/Locked.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Locked
|
||||
*
|
||||
* Show a locked screen when a page is locked
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Locked extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
html_locked();
|
||||
html_edit();
|
||||
}
|
||||
|
||||
}
|
36
ap23/web/doku/inc/Action/Login.php
Normal file
36
ap23/web/doku/inc/Action/Login.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
|
||||
/**
|
||||
* Class Login
|
||||
*
|
||||
* The login form. Actual logins are handled in inc/auth.php
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Login extends AbstractAclAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions() {
|
||||
global $INPUT;
|
||||
parent::checkPreconditions();
|
||||
if($INPUT->server->has('REMOTE_USER')) {
|
||||
// nothing to do
|
||||
throw new ActionException();
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
html_login();
|
||||
}
|
||||
|
||||
}
|
50
ap23/web/doku/inc/Action/Logout.php
Normal file
50
ap23/web/doku/inc/Action/Logout.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
|
||||
/**
|
||||
* Class Logout
|
||||
*
|
||||
* Log out a user
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Logout extends AbstractUserAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions() {
|
||||
parent::checkPreconditions();
|
||||
|
||||
/** @var \dokuwiki\Extension\AuthPlugin $auth */
|
||||
global $auth;
|
||||
if(!$auth->canDo('logout')) throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
global $ID;
|
||||
global $INPUT;
|
||||
|
||||
// when logging out during an edit session, unlock the page
|
||||
$lockedby = checklock($ID);
|
||||
if($lockedby == $INPUT->server->str('REMOTE_USER')) {
|
||||
unlock($ID);
|
||||
}
|
||||
|
||||
// do the logout stuff and redirect to login
|
||||
auth_logoff();
|
||||
send_redirect(wl($ID, array('do' => 'login'), true, '&'));
|
||||
|
||||
// should never be reached
|
||||
throw new ActionException('login');
|
||||
}
|
||||
|
||||
}
|
24
ap23/web/doku/inc/Action/Media.php
Normal file
24
ap23/web/doku/inc/Action/Media.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Media
|
||||
*
|
||||
* The full screen media manager
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Media extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
tpl_media();
|
||||
}
|
||||
|
||||
}
|
32
ap23/web/doku/inc/Action/Plugin.php
Normal file
32
ap23/web/doku/inc/Action/Plugin.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Plugin
|
||||
*
|
||||
* Used to run action plugins
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Plugin extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs nothing but a warning unless an action plugin overwrites it
|
||||
*
|
||||
* @inheritdoc
|
||||
* @triggers TPL_ACT_UNKNOWN
|
||||
*/
|
||||
public function tplContent() {
|
||||
$evt = new \dokuwiki\Extension\Event('TPL_ACT_UNKNOWN', $this->actionname);
|
||||
if($evt->advise_before()) {
|
||||
msg('Failed to handle action: ' . hsc($this->actionname), -1);
|
||||
}
|
||||
$evt->advise_after();
|
||||
}
|
||||
}
|
42
ap23/web/doku/inc/Action/Preview.php
Normal file
42
ap23/web/doku/inc/Action/Preview.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Preview
|
||||
*
|
||||
* preview during editing
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Preview extends Edit {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
header('X-XSS-Protection: 0');
|
||||
$this->savedraft();
|
||||
parent::preProcess();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
global $TEXT;
|
||||
html_edit();
|
||||
html_show($TEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a draft on preview
|
||||
*/
|
||||
protected function savedraft() {
|
||||
global $ID, $INFO;
|
||||
$draft = new \dokuwiki\Draft($ID, $INFO['client']);
|
||||
if (!$draft->saveDraft()) {
|
||||
$errors = $draft->getErrors();
|
||||
foreach ($errors as $error) {
|
||||
msg(hsc($error), -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
45
ap23/web/doku/inc/Action/Profile.php
Normal file
45
ap23/web/doku/inc/Action/Profile.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
|
||||
/**
|
||||
* Class Profile
|
||||
*
|
||||
* Handle the profile form
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Profile extends AbstractUserAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions() {
|
||||
parent::checkPreconditions();
|
||||
|
||||
/** @var \dokuwiki\Extension\AuthPlugin $auth */
|
||||
global $auth;
|
||||
if(!$auth->canDo('Profile')) throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
global $lang;
|
||||
if(updateprofile()) {
|
||||
msg($lang['profchanged'], 1);
|
||||
throw new ActionAbort('show');
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
html_updateprofile();
|
||||
}
|
||||
|
||||
}
|
42
ap23/web/doku/inc/Action/ProfileDelete.php
Normal file
42
ap23/web/doku/inc/Action/ProfileDelete.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
|
||||
/**
|
||||
* Class ProfileDelete
|
||||
*
|
||||
* Delete a user account
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class ProfileDelete extends AbstractUserAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions() {
|
||||
parent::checkPreconditions();
|
||||
|
||||
/** @var \dokuwiki\Extension\AuthPlugin $auth */
|
||||
global $auth;
|
||||
if(!$auth->canDo('delUser')) throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
global $lang;
|
||||
if(auth_deleteprofile()) {
|
||||
msg($lang['profdeleted'], 1);
|
||||
throw new ActionAbort('show');
|
||||
} else {
|
||||
throw new ActionAbort('profile');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
40
ap23/web/doku/inc/Action/Recent.php
Normal file
40
ap23/web/doku/inc/Action/Recent.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Recent
|
||||
*
|
||||
* The recent changes view
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Recent extends AbstractAction {
|
||||
|
||||
/** @var string what type of changes to show */
|
||||
protected $showType = 'both';
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
global $INPUT;
|
||||
$show_changes = $INPUT->str('show_changes');
|
||||
if(!empty($show_changes)) {
|
||||
set_doku_pref('show_changes', $show_changes);
|
||||
$this->showType = $show_changes;
|
||||
} else {
|
||||
$this->showType = get_doku_pref('show_changes', 'both');
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
global $INPUT;
|
||||
html_recent((int) $INPUT->extract('first')->int('first'), $this->showType);
|
||||
}
|
||||
|
||||
}
|
21
ap23/web/doku/inc/Action/Recover.php
Normal file
21
ap23/web/doku/inc/Action/Recover.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
|
||||
/**
|
||||
* Class Recover
|
||||
*
|
||||
* Recover a draft
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Recover extends AbstractAliasAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
throw new ActionAbort('edit');
|
||||
}
|
||||
|
||||
}
|
65
ap23/web/doku/inc/Action/Redirect.php
Normal file
65
ap23/web/doku/inc/Action/Redirect.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
/**
|
||||
* Class Redirect
|
||||
*
|
||||
* Used to redirect to the current page with the last edited section as a target if found
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Redirect extends AbstractAliasAction {
|
||||
|
||||
/**
|
||||
* Redirect to the show action, trying to jump to the previously edited section
|
||||
*
|
||||
* @triggers ACTION_SHOW_REDIRECT
|
||||
* @throws ActionAbort
|
||||
*/
|
||||
public function preProcess() {
|
||||
global $PRE;
|
||||
global $TEXT;
|
||||
global $INPUT;
|
||||
global $ID;
|
||||
global $ACT;
|
||||
|
||||
$opts = array(
|
||||
'id' => $ID,
|
||||
'preact' => $ACT
|
||||
);
|
||||
//get section name when coming from section edit
|
||||
if($INPUT->has('hid')) {
|
||||
// Use explicitly transmitted header id
|
||||
$opts['fragment'] = $INPUT->str('hid');
|
||||
} else if($PRE && preg_match('/^\s*==+([^=\n]+)/', $TEXT, $match)) {
|
||||
// Fallback to old mechanism
|
||||
$check = false; //Byref
|
||||
$opts['fragment'] = sectionID($match[0], $check);
|
||||
}
|
||||
|
||||
// execute the redirect
|
||||
Event::createAndTrigger('ACTION_SHOW_REDIRECT', $opts, array($this, 'redirect'));
|
||||
|
||||
// should never be reached
|
||||
throw new ActionAbort('show');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the redirect
|
||||
*
|
||||
* Default action for ACTION_SHOW_REDIRECT
|
||||
*
|
||||
* @param array $opts id and fragment for the redirect and the preact
|
||||
*/
|
||||
public function redirect($opts) {
|
||||
$go = wl($opts['id'], '', true, '&');
|
||||
if(isset($opts['fragment'])) $go .= '#' . $opts['fragment'];
|
||||
|
||||
//show it
|
||||
send_redirect($go);
|
||||
}
|
||||
}
|
45
ap23/web/doku/inc/Action/Register.php
Normal file
45
ap23/web/doku/inc/Action/Register.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
|
||||
/**
|
||||
* Class Register
|
||||
*
|
||||
* Self registering a new user
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Register extends AbstractAclAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions() {
|
||||
parent::checkPreconditions();
|
||||
|
||||
/** @var \dokuwiki\Extension\AuthPlugin $auth */
|
||||
global $auth;
|
||||
global $conf;
|
||||
if(isset($conf['openregister']) && !$conf['openregister']) throw new ActionDisabledException();
|
||||
if(!$auth->canDo('addUser')) throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
if(register()) { // FIXME could be moved from auth to here
|
||||
throw new ActionAbort('login');
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
html_register();
|
||||
}
|
||||
|
||||
}
|
177
ap23/web/doku/inc/Action/Resendpwd.php
Normal file
177
ap23/web/doku/inc/Action/Resendpwd.php
Normal file
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
|
||||
/**
|
||||
* Class Resendpwd
|
||||
*
|
||||
* Handle password recovery
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Resendpwd extends AbstractAclAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions() {
|
||||
parent::checkPreconditions();
|
||||
|
||||
/** @var \dokuwiki\Extension\AuthPlugin $auth */
|
||||
global $auth;
|
||||
global $conf;
|
||||
if(isset($conf['resendpasswd']) && !$conf['resendpasswd']) throw new ActionDisabledException(); //legacy option
|
||||
if(!$auth->canDo('modPass')) throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
if($this->resendpwd()) {
|
||||
throw new ActionAbort('login');
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
html_resendpwd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a new password
|
||||
*
|
||||
* This function handles both phases of the password reset:
|
||||
*
|
||||
* - handling the first request of password reset
|
||||
* - validating the password reset auth token
|
||||
*
|
||||
* @author Benoit Chesneau <benoit@bchesneau.info>
|
||||
* @author Chris Smith <chris@jalakai.co.uk>
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @fixme this should be split up into multiple methods
|
||||
* @return bool true on success, false on any error
|
||||
*/
|
||||
protected function resendpwd() {
|
||||
global $lang;
|
||||
global $conf;
|
||||
/* @var \dokuwiki\Extension\AuthPlugin $auth */
|
||||
global $auth;
|
||||
global $INPUT;
|
||||
|
||||
if(!actionOK('resendpwd')) {
|
||||
msg($lang['resendna'], -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
$token = preg_replace('/[^a-f0-9]+/', '', $INPUT->str('pwauth'));
|
||||
|
||||
if($token) {
|
||||
// we're in token phase - get user info from token
|
||||
|
||||
$tfile = $conf['cachedir'] . '/' . $token[0] . '/' . $token . '.pwauth';
|
||||
if(!file_exists($tfile)) {
|
||||
msg($lang['resendpwdbadauth'], -1);
|
||||
$INPUT->remove('pwauth');
|
||||
return false;
|
||||
}
|
||||
// token is only valid for 3 days
|
||||
if((time() - filemtime($tfile)) > (3 * 60 * 60 * 24)) {
|
||||
msg($lang['resendpwdbadauth'], -1);
|
||||
$INPUT->remove('pwauth');
|
||||
@unlink($tfile);
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = io_readfile($tfile);
|
||||
$userinfo = $auth->getUserData($user, $requireGroups = false);
|
||||
if(!$userinfo['mail']) {
|
||||
msg($lang['resendpwdnouser'], -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!$conf['autopasswd']) { // we let the user choose a password
|
||||
$pass = $INPUT->str('pass');
|
||||
|
||||
// password given correctly?
|
||||
if(!$pass) return false;
|
||||
if($pass != $INPUT->str('passchk')) {
|
||||
msg($lang['regbadpass'], -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
// change it
|
||||
if(!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) {
|
||||
msg($lang['proffail'], -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} else { // autogenerate the password and send by mail
|
||||
|
||||
$pass = auth_pwgen($user);
|
||||
if(!$auth->triggerUserMod('modify', array($user, array('pass' => $pass)))) {
|
||||
msg($lang['proffail'], -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(auth_sendPassword($user, $pass)) {
|
||||
msg($lang['resendpwdsuccess'], 1);
|
||||
} else {
|
||||
msg($lang['regmailfail'], -1);
|
||||
}
|
||||
}
|
||||
|
||||
@unlink($tfile);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// we're in request phase
|
||||
|
||||
if(!$INPUT->post->bool('save')) return false;
|
||||
|
||||
if(!$INPUT->post->str('login')) {
|
||||
msg($lang['resendpwdmissing'], -1);
|
||||
return false;
|
||||
} else {
|
||||
$user = trim($auth->cleanUser($INPUT->post->str('login')));
|
||||
}
|
||||
|
||||
$userinfo = $auth->getUserData($user, $requireGroups = false);
|
||||
if(!$userinfo['mail']) {
|
||||
msg($lang['resendpwdnouser'], -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
// generate auth token
|
||||
$token = md5(auth_randombytes(16)); // random secret
|
||||
$tfile = $conf['cachedir'] . '/' . $token[0] . '/' . $token . '.pwauth';
|
||||
$url = wl('', array('do' => 'resendpwd', 'pwauth' => $token), true, '&');
|
||||
|
||||
io_saveFile($tfile, $user);
|
||||
|
||||
$text = rawLocale('pwconfirm');
|
||||
$trep = array(
|
||||
'FULLNAME' => $userinfo['name'],
|
||||
'LOGIN' => $user,
|
||||
'CONFIRM' => $url
|
||||
);
|
||||
|
||||
$mail = new \Mailer();
|
||||
$mail->to($userinfo['name'] . ' <' . $userinfo['mail'] . '>');
|
||||
$mail->subject($lang['regpwmail']);
|
||||
$mail->setBody($text, $trep);
|
||||
if($mail->send()) {
|
||||
msg($lang['resendpwdconfirm'], 1);
|
||||
} else {
|
||||
msg($lang['regmailfail'], -1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// never reached
|
||||
}
|
||||
|
||||
}
|
60
ap23/web/doku/inc/Action/Revert.php
Normal file
60
ap23/web/doku/inc/Action/Revert.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
|
||||
/**
|
||||
* Class Revert
|
||||
*
|
||||
* Quick revert to an old revision
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Revert extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_EDIT;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @inheritdoc
|
||||
* @throws ActionAbort
|
||||
* @throws ActionException
|
||||
* @todo check for writability of the current page ($INFO might do it wrong and check the attic version)
|
||||
*/
|
||||
public function preProcess() {
|
||||
if(!checkSecurityToken()) throw new ActionException();
|
||||
|
||||
global $ID;
|
||||
global $REV;
|
||||
global $lang;
|
||||
|
||||
// when no revision is given, delete current one
|
||||
// FIXME this feature is not exposed in the GUI currently
|
||||
$text = '';
|
||||
$sum = $lang['deleted'];
|
||||
if($REV) {
|
||||
$text = rawWiki($ID, $REV);
|
||||
if(!$text) throw new ActionException(); //something went wrong
|
||||
$sum = sprintf($lang['restored'], dformat($REV));
|
||||
}
|
||||
|
||||
// spam check
|
||||
if(checkwordblock($text)) {
|
||||
msg($lang['wordblock'], -1);
|
||||
throw new ActionException('edit');
|
||||
}
|
||||
|
||||
saveWikiText($ID, $text, $sum, false);
|
||||
msg($sum, 1);
|
||||
$REV = '';
|
||||
|
||||
// continue with draftdel -> redirect -> show
|
||||
throw new ActionAbort('draftdel');
|
||||
}
|
||||
|
||||
}
|
24
ap23/web/doku/inc/Action/Revisions.php
Normal file
24
ap23/web/doku/inc/Action/Revisions.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Revisions
|
||||
*
|
||||
* Show the list of old revisions of the current page
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Revisions extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
global $INPUT;
|
||||
html_revisions($INPUT->int('first'));
|
||||
}
|
||||
}
|
60
ap23/web/doku/inc/Action/Save.php
Normal file
60
ap23/web/doku/inc/Action/Save.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
|
||||
/**
|
||||
* Class Save
|
||||
*
|
||||
* Save at the end of an edit session
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Save extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
global $INFO;
|
||||
if($INFO['exists']) {
|
||||
return AUTH_EDIT;
|
||||
} else {
|
||||
return AUTH_CREATE;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
if(!checkSecurityToken()) throw new ActionException('preview');
|
||||
|
||||
global $ID;
|
||||
global $DATE;
|
||||
global $PRE;
|
||||
global $TEXT;
|
||||
global $SUF;
|
||||
global $SUM;
|
||||
global $lang;
|
||||
global $INFO;
|
||||
global $INPUT;
|
||||
|
||||
//spam check
|
||||
if(checkwordblock()) {
|
||||
msg($lang['wordblock'], -1);
|
||||
throw new ActionException('edit');
|
||||
}
|
||||
//conflict check
|
||||
if($DATE != 0 && $INFO['meta']['date']['modified'] > $DATE) {
|
||||
throw new ActionException('conflict');
|
||||
}
|
||||
|
||||
//save it
|
||||
saveWikiText($ID, con($PRE, $TEXT, $SUF, true), $SUM, $INPUT->bool('minor')); //use pretty mode for con
|
||||
//unlock it
|
||||
unlock($ID);
|
||||
|
||||
// continue with draftdel -> redirect -> show
|
||||
throw new ActionAbort('draftdel');
|
||||
}
|
||||
|
||||
}
|
135
ap23/web/doku/inc/Action/Search.php
Normal file
135
ap23/web/doku/inc/Action/Search.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
|
||||
/**
|
||||
* Class Search
|
||||
*
|
||||
* Search for pages and content
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Search extends AbstractAction {
|
||||
|
||||
protected $pageLookupResults = array();
|
||||
protected $fullTextResults = array();
|
||||
protected $highlight = array();
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* we only search if a search word was given
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function checkPreconditions() {
|
||||
parent::checkPreconditions();
|
||||
}
|
||||
|
||||
public function preProcess()
|
||||
{
|
||||
global $QUERY, $ID, $conf, $INPUT;
|
||||
$s = cleanID($QUERY);
|
||||
|
||||
if ($ID !== $conf['start'] && !$INPUT->has('q')) {
|
||||
parse_str($INPUT->server->str('QUERY_STRING'), $urlParts);
|
||||
$urlParts['q'] = $urlParts['id'];
|
||||
unset($urlParts['id']);
|
||||
$url = wl($ID, $urlParts, true, '&');
|
||||
send_redirect($url);
|
||||
}
|
||||
|
||||
if ($s === '') throw new ActionAbort();
|
||||
$this->adjustGlobalQuery();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
$this->execute();
|
||||
|
||||
$search = new \dokuwiki\Ui\Search($this->pageLookupResults, $this->fullTextResults, $this->highlight);
|
||||
$search->show();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* run the search
|
||||
*/
|
||||
protected function execute()
|
||||
{
|
||||
global $INPUT, $QUERY;
|
||||
$after = $INPUT->str('min');
|
||||
$before = $INPUT->str('max');
|
||||
$this->pageLookupResults = ft_pageLookup($QUERY, true, useHeading('navigation'), $after, $before);
|
||||
$this->fullTextResults = ft_pageSearch($QUERY, $highlight, $INPUT->str('srt'), $after, $before);
|
||||
$this->highlight = $highlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the global query accordingly to the config search_nslimit and search_fragment
|
||||
*
|
||||
* This will only do something if the search didn't originate from the form on the searchpage itself
|
||||
*/
|
||||
protected function adjustGlobalQuery()
|
||||
{
|
||||
global $conf, $INPUT, $QUERY, $ID;
|
||||
|
||||
if ($INPUT->bool('sf')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$Indexer = idx_get_indexer();
|
||||
$parsedQuery = ft_queryParser($Indexer, $QUERY);
|
||||
|
||||
if (empty($parsedQuery['ns']) && empty($parsedQuery['notns'])) {
|
||||
if ($conf['search_nslimit'] > 0) {
|
||||
if (getNS($ID) !== false) {
|
||||
$nsParts = explode(':', getNS($ID));
|
||||
$ns = implode(':', array_slice($nsParts, 0, $conf['search_nslimit']));
|
||||
$QUERY .= " @$ns";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($conf['search_fragment'] !== 'exact') {
|
||||
if (empty(array_diff($parsedQuery['words'], $parsedQuery['and']))) {
|
||||
if (strpos($QUERY, '*') === false) {
|
||||
$queryParts = explode(' ', $QUERY);
|
||||
$queryParts = array_map(function ($part) {
|
||||
if (strpos($part, '@') === 0) {
|
||||
return $part;
|
||||
}
|
||||
if (strpos($part, 'ns:') === 0) {
|
||||
return $part;
|
||||
}
|
||||
if (strpos($part, '^') === 0) {
|
||||
return $part;
|
||||
}
|
||||
if (strpos($part, '-ns:') === 0) {
|
||||
return $part;
|
||||
}
|
||||
|
||||
global $conf;
|
||||
|
||||
if ($conf['search_fragment'] === 'starts_with') {
|
||||
return $part . '*';
|
||||
}
|
||||
if ($conf['search_fragment'] === 'ends_with') {
|
||||
return '*' . $part;
|
||||
}
|
||||
|
||||
return '*' . $part . '*';
|
||||
|
||||
}, $queryParts);
|
||||
$QUERY = implode(' ', $queryParts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
ap23/web/doku/inc/Action/Show.php
Normal file
36
ap23/web/doku/inc/Action/Show.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by IntelliJ IDEA.
|
||||
* User: andi
|
||||
* Date: 2/10/17
|
||||
* Time: 4:32 PM
|
||||
*/
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Show
|
||||
*
|
||||
* The default action of showing a page
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Show extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
global $ID;
|
||||
unlock($ID);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
html_show();
|
||||
}
|
||||
|
||||
}
|
66
ap23/web/doku/inc/Action/Sitemap.php
Normal file
66
ap23/web/doku/inc/Action/Sitemap.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\FatalException;
|
||||
use dokuwiki\Sitemap\Mapper;
|
||||
|
||||
/**
|
||||
* Class Sitemap
|
||||
*
|
||||
* Generate an XML sitemap for search engines. Do not confuse with Index
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Sitemap extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle sitemap delivery
|
||||
*
|
||||
* @author Michael Hamann <michael@content-space.de>
|
||||
* @throws FatalException
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function preProcess() {
|
||||
global $conf;
|
||||
|
||||
if($conf['sitemap'] < 1 || !is_numeric($conf['sitemap'])) {
|
||||
throw new FatalException('Sitemap generation is disabled', 404);
|
||||
}
|
||||
|
||||
$sitemap = Mapper::getFilePath();
|
||||
if(Mapper::sitemapIsCompressed()) {
|
||||
$mime = 'application/x-gzip';
|
||||
} else {
|
||||
$mime = 'application/xml; charset=utf-8';
|
||||
}
|
||||
|
||||
// Check if sitemap file exists, otherwise create it
|
||||
if(!is_readable($sitemap)) {
|
||||
Mapper::generate();
|
||||
}
|
||||
|
||||
if(is_readable($sitemap)) {
|
||||
// Send headers
|
||||
header('Content-Type: ' . $mime);
|
||||
header('Content-Disposition: attachment; filename=' . \dokuwiki\Utf8\PhpString::basename($sitemap));
|
||||
|
||||
http_conditionalRequest(filemtime($sitemap));
|
||||
|
||||
// Send file
|
||||
//use x-sendfile header to pass the delivery to compatible webservers
|
||||
http_sendfile($sitemap);
|
||||
|
||||
readfile($sitemap);
|
||||
exit;
|
||||
}
|
||||
|
||||
throw new FatalException('Could not read the sitemap file - bad permissions?');
|
||||
}
|
||||
|
||||
}
|
36
ap23/web/doku/inc/Action/Source.php
Normal file
36
ap23/web/doku/inc/Action/Source.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
/**
|
||||
* Class Source
|
||||
*
|
||||
* Show the source of a page
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Source extends AbstractAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
global $TEXT;
|
||||
global $INFO;
|
||||
global $ID;
|
||||
global $REV;
|
||||
|
||||
if($INFO['exists']) {
|
||||
$TEXT = rawWiki($ID, $REV);
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
html_edit();
|
||||
}
|
||||
|
||||
}
|
168
ap23/web/doku/inc/Action/Subscribe.php
Normal file
168
ap23/web/doku/inc/Action/Subscribe.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Action;
|
||||
|
||||
use dokuwiki\Action\Exception\ActionAbort;
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
use dokuwiki\Subscriptions\SubscriberManager;
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
/**
|
||||
* Class Subscribe
|
||||
*
|
||||
* E-Mail subscription handling
|
||||
*
|
||||
* @package dokuwiki\Action
|
||||
*/
|
||||
class Subscribe extends AbstractUserAction {
|
||||
|
||||
/** @inheritdoc */
|
||||
public function minimumPermission() {
|
||||
return AUTH_READ;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function checkPreconditions() {
|
||||
parent::checkPreconditions();
|
||||
|
||||
global $conf;
|
||||
if(isset($conf['subscribers']) && !$conf['subscribers']) throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function preProcess() {
|
||||
try {
|
||||
$this->handleSubscribeData();
|
||||
} catch(ActionAbort $e) {
|
||||
throw $e;
|
||||
} catch(\Exception $e) {
|
||||
msg($e->getMessage(), -1);
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function tplContent() {
|
||||
tpl_subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle page 'subscribe'
|
||||
*
|
||||
* @author Adrian Lang <lang@cosmocode.de>
|
||||
* @throws \Exception if (un)subscribing fails
|
||||
* @throws ActionAbort when (un)subscribing worked
|
||||
*/
|
||||
protected function handleSubscribeData() {
|
||||
global $lang;
|
||||
global $INFO;
|
||||
global $INPUT;
|
||||
|
||||
// get and preprocess data.
|
||||
$params = array();
|
||||
foreach(array('target', 'style', 'action') as $param) {
|
||||
if($INPUT->has("sub_$param")) {
|
||||
$params[$param] = $INPUT->str("sub_$param");
|
||||
}
|
||||
}
|
||||
|
||||
// any action given? if not just return and show the subscription page
|
||||
if(empty($params['action']) || !checkSecurityToken()) return;
|
||||
|
||||
// Handle POST data, may throw exception.
|
||||
Event::createAndTrigger('ACTION_HANDLE_SUBSCRIBE', $params, array($this, 'handlePostData'));
|
||||
|
||||
$target = $params['target'];
|
||||
$style = $params['style'];
|
||||
$action = $params['action'];
|
||||
|
||||
// Perform action.
|
||||
$subManager = new SubscriberManager();
|
||||
if($action === 'unsubscribe') {
|
||||
$ok = $subManager->remove($target, $INPUT->server->str('REMOTE_USER'), $style);
|
||||
} else {
|
||||
$ok = $subManager->add($target, $INPUT->server->str('REMOTE_USER'), $style);
|
||||
}
|
||||
|
||||
if($ok) {
|
||||
msg(
|
||||
sprintf(
|
||||
$lang["subscr_{$action}_success"], hsc($INFO['userinfo']['name']),
|
||||
prettyprint_id($target)
|
||||
), 1
|
||||
);
|
||||
throw new ActionAbort('redirect');
|
||||
}
|
||||
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
$lang["subscr_{$action}_error"],
|
||||
hsc($INFO['userinfo']['name']),
|
||||
prettyprint_id($target)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate POST data
|
||||
*
|
||||
* Validates POST data for a subscribe or unsubscribe request. This is the
|
||||
* default action for the event ACTION_HANDLE_SUBSCRIBE.
|
||||
*
|
||||
* @author Adrian Lang <lang@cosmocode.de>
|
||||
*
|
||||
* @param array &$params the parameters: target, style and action
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handlePostData(&$params) {
|
||||
global $INFO;
|
||||
global $lang;
|
||||
global $INPUT;
|
||||
|
||||
// Get and validate parameters.
|
||||
if(!isset($params['target'])) {
|
||||
throw new \Exception('no subscription target given');
|
||||
}
|
||||
$target = $params['target'];
|
||||
$valid_styles = array('every', 'digest');
|
||||
if(substr($target, -1, 1) === ':') {
|
||||
// Allow “list” subscribe style since the target is a namespace.
|
||||
$valid_styles[] = 'list';
|
||||
}
|
||||
$style = valid_input_set(
|
||||
'style', $valid_styles, $params,
|
||||
'invalid subscription style given'
|
||||
);
|
||||
$action = valid_input_set(
|
||||
'action', array('subscribe', 'unsubscribe'),
|
||||
$params, 'invalid subscription action given'
|
||||
);
|
||||
|
||||
// Check other conditions.
|
||||
if($action === 'subscribe') {
|
||||
if($INFO['userinfo']['mail'] === '') {
|
||||
throw new \Exception($lang['subscr_subscribe_noaddress']);
|
||||
}
|
||||
} elseif($action === 'unsubscribe') {
|
||||
$is = false;
|
||||
foreach($INFO['subscribed'] as $subscr) {
|
||||
if($subscr['target'] === $target) {
|
||||
$is = true;
|
||||
}
|
||||
}
|
||||
if($is === false) {
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
$lang['subscr_not_subscribed'],
|
||||
$INPUT->server->str('REMOTE_USER'),
|
||||
prettyprint_id($target)
|
||||
)
|
||||
);
|
||||
}
|
||||
// subscription_set deletes a subscription if style = null.
|
||||
$style = null;
|
||||
}
|
||||
|
||||
$params = compact('target', 'style', 'action');
|
||||
}
|
||||
|
||||
}
|
228
ap23/web/doku/inc/ActionRouter.php
Normal file
228
ap23/web/doku/inc/ActionRouter.php
Normal file
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki;
|
||||
|
||||
use dokuwiki\Action\AbstractAction;
|
||||
use dokuwiki\Action\Exception\ActionDisabledException;
|
||||
use dokuwiki\Action\Exception\ActionException;
|
||||
use dokuwiki\Action\Exception\FatalException;
|
||||
use dokuwiki\Action\Exception\NoActionException;
|
||||
use dokuwiki\Action\Plugin;
|
||||
|
||||
/**
|
||||
* Class ActionRouter
|
||||
* @package dokuwiki
|
||||
*/
|
||||
class ActionRouter {
|
||||
|
||||
/** @var AbstractAction */
|
||||
protected $action;
|
||||
|
||||
/** @var ActionRouter */
|
||||
protected static $instance = null;
|
||||
|
||||
/** @var int transition counter */
|
||||
protected $transitions = 0;
|
||||
|
||||
/** maximum loop */
|
||||
const MAX_TRANSITIONS = 5;
|
||||
|
||||
/** @var string[] the actions disabled in the configuration */
|
||||
protected $disabled;
|
||||
|
||||
/**
|
||||
* ActionRouter constructor. Singleton, thus protected!
|
||||
*
|
||||
* Sets up the correct action based on the $ACT global. Writes back
|
||||
* the selected action to $ACT
|
||||
*/
|
||||
protected function __construct() {
|
||||
global $ACT;
|
||||
global $conf;
|
||||
|
||||
$this->disabled = explode(',', $conf['disableactions']);
|
||||
$this->disabled = array_map('trim', $this->disabled);
|
||||
$this->transitions = 0;
|
||||
|
||||
$ACT = act_clean($ACT);
|
||||
$this->setupAction($ACT);
|
||||
$ACT = $this->action->getActionName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance
|
||||
*
|
||||
* @param bool $reinit
|
||||
* @return ActionRouter
|
||||
*/
|
||||
public static function getInstance($reinit = false) {
|
||||
if((self::$instance === null) || $reinit) {
|
||||
self::$instance = new ActionRouter();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the given action
|
||||
*
|
||||
* Instantiates the right class, runs permission checks and pre-processing and
|
||||
* sets $action
|
||||
*
|
||||
* @param string $actionname this is passed as a reference to $ACT, for plugin backward compatibility
|
||||
* @triggers ACTION_ACT_PREPROCESS
|
||||
*/
|
||||
protected function setupAction(&$actionname) {
|
||||
$presetup = $actionname;
|
||||
|
||||
try {
|
||||
// give plugins an opportunity to process the actionname
|
||||
$evt = new Extension\Event('ACTION_ACT_PREPROCESS', $actionname);
|
||||
if ($evt->advise_before()) {
|
||||
$this->action = $this->loadAction($actionname);
|
||||
$this->checkAction($this->action);
|
||||
$this->action->preProcess();
|
||||
} else {
|
||||
// event said the action should be kept, assume action plugin will handle it later
|
||||
$this->action = new Plugin($actionname);
|
||||
}
|
||||
$evt->advise_after();
|
||||
|
||||
} catch(ActionException $e) {
|
||||
// we should have gotten a new action
|
||||
$actionname = $e->getNewAction();
|
||||
|
||||
// this one should trigger a user message
|
||||
if(is_a($e, ActionDisabledException::class)) {
|
||||
msg('Action disabled: ' . hsc($presetup), -1);
|
||||
}
|
||||
|
||||
// some actions may request the display of a message
|
||||
if($e->displayToUser()) {
|
||||
msg(hsc($e->getMessage()), -1);
|
||||
}
|
||||
|
||||
// do setup for new action
|
||||
$this->transitionAction($presetup, $actionname);
|
||||
|
||||
} catch(NoActionException $e) {
|
||||
msg('Action unknown: ' . hsc($actionname), -1);
|
||||
$actionname = 'show';
|
||||
$this->transitionAction($presetup, $actionname);
|
||||
} catch(\Exception $e) {
|
||||
$this->handleFatalException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transitions from one action to another
|
||||
*
|
||||
* Basically just calls setupAction() again but does some checks before.
|
||||
*
|
||||
* @param string $from current action name
|
||||
* @param string $to new action name
|
||||
* @param null|ActionException $e any previous exception that caused the transition
|
||||
*/
|
||||
protected function transitionAction($from, $to, $e = null) {
|
||||
$this->transitions++;
|
||||
|
||||
// no infinite recursion
|
||||
if($from == $to) {
|
||||
$this->handleFatalException(new FatalException('Infinite loop in actions', 500, $e));
|
||||
}
|
||||
|
||||
// larger loops will be caught here
|
||||
if($this->transitions >= self::MAX_TRANSITIONS) {
|
||||
$this->handleFatalException(new FatalException('Maximum action transitions reached', 500, $e));
|
||||
}
|
||||
|
||||
// do the recursion
|
||||
$this->setupAction($to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts all processing with a message
|
||||
*
|
||||
* When a FataException instanc is passed, the code is treated as Status code
|
||||
*
|
||||
* @param \Exception|FatalException $e
|
||||
* @throws FatalException during unit testing
|
||||
*/
|
||||
protected function handleFatalException(\Exception $e) {
|
||||
if(is_a($e, FatalException::class)) {
|
||||
http_status($e->getCode());
|
||||
} else {
|
||||
http_status(500);
|
||||
}
|
||||
if(defined('DOKU_UNITTEST')) {
|
||||
throw $e;
|
||||
}
|
||||
$msg = 'Something unforeseen has happened: ' . $e->getMessage();
|
||||
nice_die(hsc($msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the given action
|
||||
*
|
||||
* This translates the given name to a class name by uppercasing the first letter.
|
||||
* Underscores translate to camelcase names. For actions with underscores, the different
|
||||
* parts are removed beginning from the end until a matching class is found. The instatiated
|
||||
* Action will always have the full original action set as Name
|
||||
*
|
||||
* Example: 'export_raw' -> ExportRaw then 'export' -> 'Export'
|
||||
*
|
||||
* @param $actionname
|
||||
* @return AbstractAction
|
||||
* @throws NoActionException
|
||||
*/
|
||||
public function loadAction($actionname) {
|
||||
$actionname = strtolower($actionname); // FIXME is this needed here? should we run a cleanup somewhere else?
|
||||
$parts = explode('_', $actionname);
|
||||
while(!empty($parts)) {
|
||||
$load = join('_', $parts);
|
||||
$class = 'dokuwiki\\Action\\' . str_replace('_', '', ucwords($load, '_'));
|
||||
if(class_exists($class)) {
|
||||
return new $class($actionname);
|
||||
}
|
||||
array_pop($parts);
|
||||
}
|
||||
|
||||
throw new NoActionException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the checks to see if this action can be executed
|
||||
*
|
||||
* @param AbstractAction $action
|
||||
* @throws ActionDisabledException
|
||||
* @throws ActionException
|
||||
*/
|
||||
public function checkAction(AbstractAction $action) {
|
||||
global $INFO;
|
||||
global $ID;
|
||||
|
||||
if(in_array($action->getActionName(), $this->disabled)) {
|
||||
throw new ActionDisabledException();
|
||||
}
|
||||
|
||||
$action->checkPreconditions();
|
||||
|
||||
if(isset($INFO)) {
|
||||
$perm = $INFO['perm'];
|
||||
} else {
|
||||
$perm = auth_quickaclcheck($ID);
|
||||
}
|
||||
|
||||
if($perm < $action->minimumPermission()) {
|
||||
throw new ActionException('denied');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action handling the current request
|
||||
*
|
||||
* @return AbstractAction
|
||||
*/
|
||||
public function getAction() {
|
||||
return $this->action;
|
||||
}
|
||||
}
|
438
ap23/web/doku/inc/Ajax.php
Normal file
438
ap23/web/doku/inc/Ajax.php
Normal file
@@ -0,0 +1,438 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki;
|
||||
|
||||
/**
|
||||
* Manage all builtin AJAX calls
|
||||
*
|
||||
* @todo The calls should be refactored out to their own proper classes
|
||||
* @package dokuwiki
|
||||
*/
|
||||
class Ajax {
|
||||
|
||||
/**
|
||||
* Execute the given call
|
||||
*
|
||||
* @param string $call name of the ajax call
|
||||
*/
|
||||
public function __construct($call) {
|
||||
$callfn = 'call' . ucfirst($call);
|
||||
if(method_exists($this, $callfn)) {
|
||||
$this->$callfn();
|
||||
} else {
|
||||
$evt = new Extension\Event('AJAX_CALL_UNKNOWN', $call);
|
||||
if($evt->advise_before()) {
|
||||
print "AJAX call '" . hsc($call) . "' unknown!\n";
|
||||
} else {
|
||||
$evt->advise_after();
|
||||
unset($evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for matching pagenames
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
protected function callQsearch() {
|
||||
global $lang;
|
||||
global $INPUT;
|
||||
|
||||
$maxnumbersuggestions = 50;
|
||||
|
||||
$query = $INPUT->post->str('q');
|
||||
if(empty($query)) $query = $INPUT->get->str('q');
|
||||
if(empty($query)) return;
|
||||
|
||||
$query = urldecode($query);
|
||||
|
||||
$data = ft_pageLookup($query, true, useHeading('navigation'));
|
||||
|
||||
if(!count($data)) return;
|
||||
|
||||
print '<strong>' . $lang['quickhits'] . '</strong>';
|
||||
print '<ul>';
|
||||
$counter = 0;
|
||||
foreach($data as $id => $title) {
|
||||
if(useHeading('navigation')) {
|
||||
$name = $title;
|
||||
} else {
|
||||
$ns = getNS($id);
|
||||
if($ns) {
|
||||
$name = noNS($id) . ' (' . $ns . ')';
|
||||
} else {
|
||||
$name = $id;
|
||||
}
|
||||
}
|
||||
echo '<li>' . html_wikilink(':' . $id, $name) . '</li>';
|
||||
|
||||
$counter++;
|
||||
if($counter > $maxnumbersuggestions) {
|
||||
echo '<li>...</li>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
print '</ul>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Support OpenSearch suggestions
|
||||
*
|
||||
* @link http://www.opensearch.org/Specifications/OpenSearch/Extensions/Suggestions/1.0
|
||||
* @author Mike Frysinger <vapier@gentoo.org>
|
||||
*/
|
||||
protected function callSuggestions() {
|
||||
global $INPUT;
|
||||
|
||||
$query = cleanID($INPUT->post->str('q'));
|
||||
if(empty($query)) $query = cleanID($INPUT->get->str('q'));
|
||||
if(empty($query)) return;
|
||||
|
||||
$data = ft_pageLookup($query);
|
||||
if(!count($data)) return;
|
||||
$data = array_keys($data);
|
||||
|
||||
// limit results to 15 hits
|
||||
$data = array_slice($data, 0, 15);
|
||||
$data = array_map('trim', $data);
|
||||
$data = array_map('noNS', $data);
|
||||
$data = array_unique($data);
|
||||
sort($data);
|
||||
|
||||
/* now construct a json */
|
||||
$suggestions = array(
|
||||
$query, // the original query
|
||||
$data, // some suggestions
|
||||
array(), // no description
|
||||
array() // no urls
|
||||
);
|
||||
|
||||
header('Content-Type: application/x-suggestions+json');
|
||||
print json_encode($suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh a page lock and save draft
|
||||
*
|
||||
* Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
protected function callLock() {
|
||||
global $ID;
|
||||
global $INFO;
|
||||
global $INPUT;
|
||||
|
||||
$ID = cleanID($INPUT->post->str('id'));
|
||||
if(empty($ID)) return;
|
||||
|
||||
$INFO = pageinfo();
|
||||
|
||||
$response = [
|
||||
'errors' => [],
|
||||
'lock' => '0',
|
||||
'draft' => '',
|
||||
];
|
||||
if(!$INFO['writable']) {
|
||||
$response['errors'][] = 'Permission to write this page has been denied.';
|
||||
echo json_encode($response);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!checklock($ID)) {
|
||||
lock($ID);
|
||||
$response['lock'] = '1';
|
||||
}
|
||||
|
||||
$draft = new Draft($ID, $INFO['client']);
|
||||
if ($draft->saveDraft()) {
|
||||
$response['draft'] = $draft->getDraftMessage();
|
||||
} else {
|
||||
$response['errors'] = array_merge($response['errors'], $draft->getErrors());
|
||||
}
|
||||
echo json_encode($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a draft
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
protected function callDraftdel() {
|
||||
global $INPUT;
|
||||
$id = cleanID($INPUT->str('id'));
|
||||
if(empty($id)) return;
|
||||
|
||||
$client = $_SERVER['REMOTE_USER'];
|
||||
if(!$client) $client = clientIP(true);
|
||||
|
||||
$cname = getCacheName($client . $id, '.draft');
|
||||
@unlink($cname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return subnamespaces for the Mediamanager
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
protected function callMedians() {
|
||||
global $conf;
|
||||
global $INPUT;
|
||||
|
||||
// wanted namespace
|
||||
$ns = cleanID($INPUT->post->str('ns'));
|
||||
$dir = utf8_encodeFN(str_replace(':', '/', $ns));
|
||||
|
||||
$lvl = count(explode(':', $ns));
|
||||
|
||||
$data = array();
|
||||
search($data, $conf['mediadir'], 'search_index', array('nofiles' => true), $dir);
|
||||
foreach(array_keys($data) as $item) {
|
||||
$data[$item]['level'] = $lvl + 1;
|
||||
}
|
||||
echo html_buildlist($data, 'idx', 'media_nstree_item', 'media_nstree_li');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of files for the Mediamanager
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
protected function callMedialist() {
|
||||
global $NS;
|
||||
global $INPUT;
|
||||
|
||||
$NS = cleanID($INPUT->post->str('ns'));
|
||||
$sort = $INPUT->post->bool('recent') ? 'date' : 'natural';
|
||||
if($INPUT->post->str('do') == 'media') {
|
||||
tpl_mediaFileList();
|
||||
} else {
|
||||
tpl_mediaContent(true, $sort);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content of the right column
|
||||
* (image details) for the Mediamanager
|
||||
*
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
protected function callMediadetails() {
|
||||
global $IMG, $JUMPTO, $REV, $fullscreen, $INPUT;
|
||||
$fullscreen = true;
|
||||
require_once(DOKU_INC . 'lib/exe/mediamanager.php');
|
||||
|
||||
$image = '';
|
||||
if($INPUT->has('image')) $image = cleanID($INPUT->str('image'));
|
||||
if(isset($IMG)) $image = $IMG;
|
||||
if(isset($JUMPTO)) $image = $JUMPTO;
|
||||
$rev = false;
|
||||
if(isset($REV) && !$JUMPTO) $rev = $REV;
|
||||
|
||||
html_msgarea();
|
||||
tpl_mediaFileDetails($image, $rev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns image diff representation for mediamanager
|
||||
*
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
protected function callMediadiff() {
|
||||
global $NS;
|
||||
global $INPUT;
|
||||
|
||||
$image = '';
|
||||
if($INPUT->has('image')) $image = cleanID($INPUT->str('image'));
|
||||
$NS = getNS($image);
|
||||
$auth = auth_quickaclcheck("$NS:*");
|
||||
media_diff($image, $NS, $auth, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages file uploads
|
||||
*
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
protected function callMediaupload() {
|
||||
global $NS, $MSG, $INPUT;
|
||||
|
||||
$id = '';
|
||||
if(isset($_FILES['qqfile']['tmp_name'])) {
|
||||
$id = $INPUT->post->str('mediaid', $_FILES['qqfile']['name']);
|
||||
} elseif($INPUT->get->has('qqfile')) {
|
||||
$id = $INPUT->get->str('qqfile');
|
||||
}
|
||||
|
||||
$id = cleanID($id);
|
||||
|
||||
$NS = $INPUT->str('ns');
|
||||
$ns = $NS . ':' . getNS($id);
|
||||
|
||||
$AUTH = auth_quickaclcheck("$ns:*");
|
||||
if($AUTH >= AUTH_UPLOAD) {
|
||||
io_createNamespace("$ns:xxx", 'media');
|
||||
}
|
||||
|
||||
if(isset($_FILES['qqfile']['error']) && $_FILES['qqfile']['error']) unset($_FILES['qqfile']);
|
||||
|
||||
$res = false;
|
||||
if(isset($_FILES['qqfile']['tmp_name'])) $res = media_upload($NS, $AUTH, $_FILES['qqfile']);
|
||||
if($INPUT->get->has('qqfile')) $res = media_upload_xhr($NS, $AUTH);
|
||||
|
||||
if($res) {
|
||||
$result = array(
|
||||
'success' => true,
|
||||
'link' => media_managerURL(array('ns' => $ns, 'image' => $NS . ':' . $id), '&'),
|
||||
'id' => $NS . ':' . $id,
|
||||
'ns' => $NS
|
||||
);
|
||||
} else {
|
||||
$error = '';
|
||||
if(isset($MSG)) {
|
||||
foreach($MSG as $msg) {
|
||||
$error .= $msg['msg'];
|
||||
}
|
||||
}
|
||||
$result = array(
|
||||
'error' => $error,
|
||||
'ns' => $NS
|
||||
);
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return sub index for index view
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
protected function callIndex() {
|
||||
global $conf;
|
||||
global $INPUT;
|
||||
|
||||
// wanted namespace
|
||||
$ns = cleanID($INPUT->post->str('idx'));
|
||||
$dir = utf8_encodeFN(str_replace(':', '/', $ns));
|
||||
|
||||
$lvl = count(explode(':', $ns));
|
||||
|
||||
$data = array();
|
||||
search($data, $conf['datadir'], 'search_index', array('ns' => $ns), $dir);
|
||||
foreach(array_keys($data) as $item) {
|
||||
$data[$item]['level'] = $lvl + 1;
|
||||
}
|
||||
echo html_buildlist($data, 'idx', 'html_list_index', 'html_li_index');
|
||||
}
|
||||
|
||||
/**
|
||||
* List matching namespaces and pages for the link wizard
|
||||
*
|
||||
* @author Andreas Gohr <gohr@cosmocode.de>
|
||||
*/
|
||||
protected function callLinkwiz() {
|
||||
global $conf;
|
||||
global $lang;
|
||||
global $INPUT;
|
||||
|
||||
$q = ltrim(trim($INPUT->post->str('q')), ':');
|
||||
$id = noNS($q);
|
||||
$ns = getNS($q);
|
||||
|
||||
$ns = cleanID($ns);
|
||||
$id = cleanID($id);
|
||||
|
||||
$nsd = utf8_encodeFN(str_replace(':', '/', $ns));
|
||||
|
||||
$data = array();
|
||||
if($q !== '' && $ns === '') {
|
||||
|
||||
// use index to lookup matching pages
|
||||
$pages = ft_pageLookup($id, true);
|
||||
|
||||
// result contains matches in pages and namespaces
|
||||
// we now extract the matching namespaces to show
|
||||
// them seperately
|
||||
$dirs = array();
|
||||
|
||||
foreach($pages as $pid => $title) {
|
||||
if(strpos(noNS($pid), $id) === false) {
|
||||
// match was in the namespace
|
||||
$dirs[getNS($pid)] = 1; // assoc array avoids dupes
|
||||
} else {
|
||||
// it is a matching page, add it to the result
|
||||
$data[] = array(
|
||||
'id' => $pid,
|
||||
'title' => $title,
|
||||
'type' => 'f',
|
||||
);
|
||||
}
|
||||
unset($pages[$pid]);
|
||||
}
|
||||
foreach($dirs as $dir => $junk) {
|
||||
$data[] = array(
|
||||
'id' => $dir,
|
||||
'type' => 'd',
|
||||
);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$opts = array(
|
||||
'depth' => 1,
|
||||
'listfiles' => true,
|
||||
'listdirs' => true,
|
||||
'pagesonly' => true,
|
||||
'firsthead' => true,
|
||||
'sneakyacl' => $conf['sneaky_index'],
|
||||
);
|
||||
if($id) $opts['filematch'] = '^.*\/' . $id;
|
||||
if($id) $opts['dirmatch'] = '^.*\/' . $id;
|
||||
search($data, $conf['datadir'], 'search_universal', $opts, $nsd);
|
||||
|
||||
// add back to upper
|
||||
if($ns) {
|
||||
array_unshift(
|
||||
$data, array(
|
||||
'id' => getNS($ns),
|
||||
'type' => 'u',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// fixme sort results in a useful way ?
|
||||
|
||||
if(!count($data)) {
|
||||
echo $lang['nothingfound'];
|
||||
exit;
|
||||
}
|
||||
|
||||
// output the found data
|
||||
$even = 1;
|
||||
foreach($data as $item) {
|
||||
$even *= -1; //zebra
|
||||
|
||||
if(($item['type'] == 'd' || $item['type'] == 'u') && $item['id'] !== '') $item['id'] .= ':';
|
||||
$link = wl($item['id']);
|
||||
|
||||
echo '<div class="' . (($even > 0) ? 'even' : 'odd') . ' type_' . $item['type'] . '">';
|
||||
|
||||
if($item['type'] == 'u') {
|
||||
$name = $lang['upperns'];
|
||||
} else {
|
||||
$name = hsc($item['id']);
|
||||
}
|
||||
|
||||
echo '<a href="' . $link . '" title="' . hsc($item['id']) . '" class="wikilink1">' . $name . '</a>';
|
||||
|
||||
if(!blank($item['title'])) {
|
||||
echo '<span>' . hsc($item['title']) . '</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
240
ap23/web/doku/inc/Cache/Cache.php
Normal file
240
ap23/web/doku/inc/Cache/Cache.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Cache;
|
||||
|
||||
use dokuwiki\Debug\PropertyDeprecationHelper;
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
/**
|
||||
* Generic handling of caching
|
||||
*/
|
||||
class Cache
|
||||
{
|
||||
use PropertyDeprecationHelper;
|
||||
|
||||
public $key = ''; // primary identifier for this item
|
||||
public $ext = ''; // file ext for cache data, secondary identifier for this item
|
||||
public $cache = ''; // cache file name
|
||||
public $depends = array(); // 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->getEvent()) {
|
||||
return $this->stats(
|
||||
Event::createAndTrigger(
|
||||
$this->getEvent(),
|
||||
$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;
|
||||
}
|
||||
}
|
46
ap23/web/doku/inc/Cache/CacheInstructions.php
Normal file
46
ap23/web/doku/inc/Cache/CacheInstructions.php
Normal file
@@ -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));
|
||||
}
|
||||
}
|
64
ap23/web/doku/inc/Cache/CacheParser.php
Normal file
64
ap23/web/doku/inc/Cache/CacheParser.php
Normal file
@@ -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->setEvent('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();
|
||||
}
|
||||
|
||||
}
|
94
ap23/web/doku/inc/Cache/CacheRenderer.php
Normal file
94
ap23/web/doku/inc/Cache/CacheRenderer.php
Normal file
@@ -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();
|
||||
}
|
||||
}
|
666
ap23/web/doku/inc/ChangeLog/ChangeLog.php
Normal file
666
ap23/web/doku/inc/ChangeLog/ChangeLog.php
Normal file
@@ -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);
|
||||
}
|
||||
}
|
30
ap23/web/doku/inc/ChangeLog/MediaChangeLog.php
Normal file
30
ap23/web/doku/inc/ChangeLog/MediaChangeLog.php
Normal file
@@ -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);
|
||||
}
|
||||
}
|
30
ap23/web/doku/inc/ChangeLog/PageChangeLog.php
Normal file
30
ap23/web/doku/inc/ChangeLog/PageChangeLog.php
Normal file
@@ -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);
|
||||
}
|
||||
}
|
167
ap23/web/doku/inc/Debug/DebugHelper.php
Normal file
167
ap23/web/doku/inc/Debug/DebugHelper.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?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(
|
||||
(!empty($self['class']) ? ($self['class'] . '::') : '') .
|
||||
$self['function'] . '()', ':'),
|
||||
trim(
|
||||
(!empty($call['class']) ? ($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']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a custom deprecation event
|
||||
*
|
||||
* Usually dbgDeprecatedFunction() or dbgDeprecatedProperty() should be used instead.
|
||||
* This method is intended only for those situation where they are not applicable.
|
||||
*
|
||||
* @param string $alternative
|
||||
* @param string $deprecatedThing
|
||||
* @param string $caller
|
||||
* @param string $file
|
||||
* @param int $line
|
||||
* @param int $callerOffset How many lines should be removed from the beginning of the backtrace
|
||||
*/
|
||||
public static function dbgCustomDeprecationEvent(
|
||||
$alternative,
|
||||
$deprecatedThing,
|
||||
$caller,
|
||||
$file,
|
||||
$line,
|
||||
$callerOffset = 1
|
||||
) {
|
||||
global $conf;
|
||||
/** @var EventHandler $EVENT_HANDLER */
|
||||
global $EVENT_HANDLER;
|
||||
if (!$conf['allowdebug'] && !$EVENT_HANDLER->hasHandlerForEvent(self::INFO_DEPRECATION_LOG_EVENT)) {
|
||||
// avoid any work if no one cares
|
||||
return;
|
||||
}
|
||||
|
||||
$backtrace = array_slice(debug_backtrace(), $callerOffset);
|
||||
|
||||
self::triggerDeprecationEvent(
|
||||
$backtrace,
|
||||
$alternative,
|
||||
$deprecatedThing,
|
||||
$caller,
|
||||
$file,
|
||||
$line
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $backtrace
|
||||
* @param string $alternative
|
||||
* @param string $deprecatedThing
|
||||
* @param string $caller
|
||||
* @param string $file
|
||||
* @param int $line
|
||||
*/
|
||||
private static function triggerDeprecationEvent(
|
||||
array $backtrace,
|
||||
$alternative,
|
||||
$deprecatedThing,
|
||||
$caller,
|
||||
$file,
|
||||
$line
|
||||
) {
|
||||
$data = [
|
||||
'trace' => $backtrace,
|
||||
'alternative' => $alternative,
|
||||
'called' => $deprecatedThing,
|
||||
'caller' => $caller,
|
||||
'file' => $file,
|
||||
'line' => $line,
|
||||
];
|
||||
$event = new 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();
|
||||
}
|
||||
}
|
134
ap23/web/doku/inc/Debug/PropertyDeprecationHelper.php
Normal file
134
ap23/web/doku/inc/Debug/PropertyDeprecationHelper.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
1544
ap23/web/doku/inc/DifferenceEngine.php
Normal file
1544
ap23/web/doku/inc/DifferenceEngine.php
Normal file
File diff suppressed because it is too large
Load Diff
165
ap23/web/doku/inc/Draft.php
Normal file
165
ap23/web/doku/inc/Draft.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki;
|
||||
|
||||
/**
|
||||
* Class Draft
|
||||
*
|
||||
* @package dokuwiki
|
||||
*/
|
||||
class Draft
|
||||
{
|
||||
|
||||
protected $errors = [];
|
||||
protected $cname;
|
||||
protected $id;
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* Draft constructor.
|
||||
*
|
||||
* @param string $ID the page id for this draft
|
||||
* @param string $client the client identification (username or ip or similar) for this draft
|
||||
*/
|
||||
public function __construct($ID, $client)
|
||||
{
|
||||
$this->id = $ID;
|
||||
$this->client = $client;
|
||||
$this->cname = getCacheName($client.$ID, '.draft');
|
||||
if(file_exists($this->cname) && file_exists(wikiFN($ID))) {
|
||||
if (filemtime($this->cname) < filemtime(wikiFN($ID))) {
|
||||
// remove stale draft
|
||||
$this->deleteDraft();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename for this draft (whether or not it exists)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDraftFilename()
|
||||
{
|
||||
return $this->cname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this draft exists on the filesystem
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDraftAvailable()
|
||||
{
|
||||
return file_exists($this->cname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a draft of a current edit session
|
||||
*
|
||||
* The draft will not be saved if
|
||||
* - drafts are deactivated in the config
|
||||
* - or the editarea is empty and there are no event handlers registered
|
||||
* - or the event is prevented
|
||||
*
|
||||
* @triggers DRAFT_SAVE
|
||||
*
|
||||
* @return bool whether has the draft been saved
|
||||
*/
|
||||
public function saveDraft()
|
||||
{
|
||||
global $INPUT, $INFO, $EVENT_HANDLER, $conf;
|
||||
if (!$conf['usedraft']) {
|
||||
return false;
|
||||
}
|
||||
if (!$INPUT->post->has('wikitext') &&
|
||||
!$EVENT_HANDLER->hasHandlerForEvent('DRAFT_SAVE')) {
|
||||
return false;
|
||||
}
|
||||
$draft = [
|
||||
'id' => $this->id,
|
||||
'prefix' => substr($INPUT->post->str('prefix'), 0, -1),
|
||||
'text' => $INPUT->post->str('wikitext'),
|
||||
'suffix' => $INPUT->post->str('suffix'),
|
||||
'date' => $INPUT->post->int('date'),
|
||||
'client' => $this->client,
|
||||
'cname' => $this->cname,
|
||||
'errors' => [],
|
||||
];
|
||||
$event = new Extension\Event('DRAFT_SAVE', $draft);
|
||||
if ($event->advise_before()) {
|
||||
$draft['hasBeenSaved'] = io_saveFile($draft['cname'], serialize($draft));
|
||||
if ($draft['hasBeenSaved']) {
|
||||
$INFO['draft'] = $draft['cname'];
|
||||
}
|
||||
} else {
|
||||
$draft['hasBeenSaved'] = false;
|
||||
}
|
||||
$event->advise_after();
|
||||
|
||||
$this->errors = $draft['errors'];
|
||||
|
||||
return $draft['hasBeenSaved'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text from the draft file
|
||||
*
|
||||
* @throws \RuntimeException if the draft file doesn't exist
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDraftText()
|
||||
{
|
||||
if (!file_exists($this->cname)) {
|
||||
throw new \RuntimeException(
|
||||
"Draft for page $this->id and user $this->client doesn't exist at $this->cname."
|
||||
);
|
||||
}
|
||||
$draft = unserialize(io_readFile($this->cname,false));
|
||||
return cleanText(con($draft['prefix'],$draft['text'],$draft['suffix'],true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the draft from the filesystem
|
||||
*
|
||||
* Also sets $INFO['draft'] to null
|
||||
*/
|
||||
public function deleteDraft()
|
||||
{
|
||||
global $INFO;
|
||||
@unlink($this->cname);
|
||||
$INFO['draft'] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted message stating when the draft was saved
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDraftMessage()
|
||||
{
|
||||
global $lang;
|
||||
return $lang['draftdate'] . ' ' . dformat(filemtime($this->cname));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the errors that occured when saving the draft
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getErrors()
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp when this draft was saved
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDraftDate()
|
||||
{
|
||||
return filemtime($this->cname);
|
||||
}
|
||||
}
|
22
ap23/web/doku/inc/Extension/ActionPlugin.php
Normal file
22
ap23/web/doku/inc/Extension/ActionPlugin.php
Normal file
@@ -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);
|
||||
}
|
123
ap23/web/doku/inc/Extension/AdminPlugin.php
Normal file
123
ap23/web/doku/inc/Extension/AdminPlugin.php
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
461
ap23/web/doku/inc/Extension/AuthPlugin.php
Normal file
461
ap23/web/doku/inc/Extension/AuthPlugin.php
Normal file
@@ -0,0 +1,461 @@
|
||||
<?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 (except this
|
||||
* function returns null, in which case, DokuWiki will
|
||||
* still run auth_login as a fallback, which may call
|
||||
* checkPass()). If this function is not returning null,
|
||||
* implementing checkPass() is not needed here anymore.
|
||||
*
|
||||
* The function can be used to authenticate against third
|
||||
* party cookies or Apache auth mechanisms and replaces
|
||||
* the auth_login() function
|
||||
*
|
||||
* The function will be called with or without a set
|
||||
* username. If the Username is given it was called
|
||||
* from the login form and the given credentials might
|
||||
* need to be checked. If no username was given it
|
||||
* the function needs to check if the user is logged in
|
||||
* by other means (cookie, environment).
|
||||
*
|
||||
* The function needs to set some globals needed by
|
||||
* DokuWiki like auth_login() does.
|
||||
*
|
||||
* @see auth_login()
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
* @param string $user Username
|
||||
* @param string $pass Cleartext Password
|
||||
* @param bool $sticky Cookie should not expire
|
||||
* @return bool true on successful auth,
|
||||
* null on unknown result (fallback to checkPass)
|
||||
*/
|
||||
public function trustExternal($user, $pass, $sticky = false)
|
||||
{
|
||||
/* some example:
|
||||
|
||||
global $USERINFO;
|
||||
global $conf;
|
||||
$sticky ? $sticky = true : $sticky = false; //sanity check
|
||||
|
||||
// do the checking here
|
||||
|
||||
// set the globals if authed
|
||||
$USERINFO['name'] = 'FIXME';
|
||||
$USERINFO['mail'] = 'FIXME';
|
||||
$USERINFO['grps'] = array('FIXME');
|
||||
$_SERVER['REMOTE_USER'] = $user;
|
||||
$_SESSION[DOKU_COOKIE]['auth']['user'] = $user;
|
||||
$_SESSION[DOKU_COOKIE]['auth']['pass'] = $pass;
|
||||
$_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO;
|
||||
return true;
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user+password [ MUST BE OVERRIDDEN ]
|
||||
*
|
||||
* Checks if the given user exists and the given
|
||||
* plaintext password is correct
|
||||
*
|
||||
* May be ommited if trustExternal is used.
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @param string $user the user name
|
||||
* @param string $pass the clear text password
|
||||
* @return bool
|
||||
*/
|
||||
public function checkPass($user, $pass)
|
||||
{
|
||||
msg("no valid authorisation system in use", -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return user info [ MUST BE OVERRIDDEN ]
|
||||
*
|
||||
* Returns info about the given user needs to contain
|
||||
* at least these fields:
|
||||
*
|
||||
* name string full name of the user
|
||||
* mail string email address of the user
|
||||
* grps array list of groups the user is in
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @param string $user the user name
|
||||
* @param bool $requireGroups whether or not the returned data must include groups
|
||||
* @return false|array containing user data or false
|
||||
*/
|
||||
public function getUserData($user, $requireGroups = true)
|
||||
{
|
||||
if (!$this->cando['external']) msg("no valid authorisation system in use", -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new User [implement only where required/possible]
|
||||
*
|
||||
* Returns false if the user already exists, null when an error
|
||||
* occurred and true if everything went well.
|
||||
*
|
||||
* The new user HAS TO be added to the default group by this
|
||||
* function!
|
||||
*
|
||||
* Set addUser capability when implemented
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @param string $user
|
||||
* @param string $pass
|
||||
* @param string $name
|
||||
* @param string $mail
|
||||
* @param null|array $grps
|
||||
* @return bool|null
|
||||
*/
|
||||
public function createUser($user, $pass, $name, $mail, $grps = null)
|
||||
{
|
||||
msg("authorisation method does not allow creation of new users", -1);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify user data [implement only where required/possible]
|
||||
*
|
||||
* Set the mod* capabilities according to the implemented features
|
||||
*
|
||||
* @author Chris Smith <chris@jalakai.co.uk>
|
||||
* @param string $user nick of the user to be changed
|
||||
* @param array $changes array of field/value pairs to be changed (password will be clear text)
|
||||
* @return bool
|
||||
*/
|
||||
public function modifyUser($user, $changes)
|
||||
{
|
||||
msg("authorisation method does not allow modifying of user data", -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete one or more users [implement only where required/possible]
|
||||
*
|
||||
* Set delUser capability when implemented
|
||||
*
|
||||
* @author Chris Smith <chris@jalakai.co.uk>
|
||||
* @param array $users
|
||||
* @return int number of users deleted
|
||||
*/
|
||||
public function deleteUsers($users)
|
||||
{
|
||||
msg("authorisation method does not allow deleting of users", -1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a count of the number of user which meet $filter criteria
|
||||
* [should be implemented whenever retrieveUsers is implemented]
|
||||
*
|
||||
* Set getUserCount capability when implemented
|
||||
*
|
||||
* @author Chris Smith <chris@jalakai.co.uk>
|
||||
* @param array $filter array of field/pattern pairs, empty array for no filter
|
||||
* @return int
|
||||
*/
|
||||
public function getUserCount($filter = 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'));
|
||||
}
|
||||
}
|
13
ap23/web/doku/inc/Extension/CLIPlugin.php
Normal file
13
ap23/web/doku/inc/Extension/CLIPlugin.php
Normal file
@@ -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;
|
||||
}
|
197
ap23/web/doku/inc/Extension/Event.php
Normal file
197
ap23/web/doku/inc/Extension/Event.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?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)) {
|
||||
$this->result = call_user_func_array($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);
|
||||
}
|
||||
}
|
108
ap23/web/doku/inc/Extension/EventHandler.php
Normal file
108
ap23/web/doku/inc/Extension/EventHandler.php
Normal file
@@ -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']);
|
||||
}
|
||||
}
|
13
ap23/web/doku/inc/Extension/Plugin.php
Normal file
13
ap23/web/doku/inc/Extension/Plugin.php
Normal file
@@ -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;
|
||||
}
|
393
ap23/web/doku/inc/Extension/PluginController.php
Normal file
393
ap23/web/doku/inc/Extension/PluginController.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
162
ap23/web/doku/inc/Extension/PluginInterface.php
Normal file
162
ap23/web/doku/inc/Extension/PluginInterface.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?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\Extension\PluginTrait
|
||||
*
|
||||
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||||
* @author Christopher Smith <chris@jalakai.co.uk>
|
||||
*/
|
||||
interface PluginInterface
|
||||
{
|
||||
/**
|
||||
* General Info
|
||||
*
|
||||
* Needs to return a associative array with the following values:
|
||||
*
|
||||
* base - the plugin's base name (eg. the directory it needs to be installed in)
|
||||
* author - Author of the plugin
|
||||
* email - Email address to contact the author
|
||||
* date - Last modified date of the plugin in YYYY-MM-DD format
|
||||
* name - Name of the plugin
|
||||
* desc - Short description of the plugin (Text only)
|
||||
* url - Website with more information on the plugin (eg. syntax description)
|
||||
*/
|
||||
public function getInfo();
|
||||
|
||||
/**
|
||||
* The type of the plugin inferred from the class name
|
||||
*
|
||||
* @return string plugin type
|
||||
*/
|
||||
public function getPluginType();
|
||||
|
||||
/**
|
||||
* The name of the plugin inferred from the class name
|
||||
*
|
||||
* @return string plugin name
|
||||
*/
|
||||
public function getPluginName();
|
||||
|
||||
/**
|
||||
* The component part of the plugin inferred from the class name
|
||||
*
|
||||
* @return string component name
|
||||
*/
|
||||
public function getPluginComponent();
|
||||
|
||||
/**
|
||||
* Access plugin language strings
|
||||
*
|
||||
* to try to minimise unnecessary loading of the strings when the plugin doesn't require them
|
||||
* e.g. when info plugin is querying plugins for information about themselves.
|
||||
*
|
||||
* @param string $id id of the string to be retrieved
|
||||
* @return string in appropriate language or english if not available
|
||||
*/
|
||||
public function getLang($id);
|
||||
|
||||
/**
|
||||
* retrieve a language dependent file and pass to xhtml renderer for display
|
||||
* plugin equivalent of p_locale_xhtml()
|
||||
*
|
||||
* @param string $id id of language dependent wiki page
|
||||
* @return string parsed contents of the wiki page in xhtml format
|
||||
*/
|
||||
public function locale_xhtml($id);
|
||||
|
||||
/**
|
||||
* Prepends appropriate path for a language dependent filename
|
||||
* plugin equivalent of localFN()
|
||||
*
|
||||
* @param string $id id of localization file
|
||||
* @param string $ext The file extension (usually txt)
|
||||
* @return string wiki text
|
||||
*/
|
||||
public function localFN($id, $ext = 'txt');
|
||||
|
||||
/**
|
||||
* Reads all the plugins language dependent strings into $this->lang
|
||||
* this function is automatically called by getLang()
|
||||
*
|
||||
* @todo this could be made protected and be moved to the trait only
|
||||
*/
|
||||
public function setupLocale();
|
||||
|
||||
/**
|
||||
* use this function to access plugin configuration variables
|
||||
*
|
||||
* @param string $setting the setting to access
|
||||
* @param mixed $notset what to return if the setting is not available
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConf($setting, $notset = false);
|
||||
|
||||
/**
|
||||
* merges the plugin's default settings with any local settings
|
||||
* this function is automatically called through getConf()
|
||||
*
|
||||
* @todo this could be made protected and be moved to the trait only
|
||||
*/
|
||||
public function loadConfig();
|
||||
|
||||
/**
|
||||
* Loads a given helper plugin (if enabled)
|
||||
*
|
||||
* @author Esther Brunner <wikidesign@gmail.com>
|
||||
*
|
||||
* @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 PluginInterface|null helper plugin object
|
||||
*/
|
||||
public function loadHelper($name, $msg = true);
|
||||
|
||||
/**
|
||||
* email
|
||||
* standardised function to generate an email link according to obfuscation settings
|
||||
*
|
||||
* @param string $email
|
||||
* @param string $name
|
||||
* @param string $class
|
||||
* @param string $more
|
||||
* @return string html
|
||||
*/
|
||||
public function email($email, $name = '', $class = '', $more = '');
|
||||
|
||||
/**
|
||||
* external_link
|
||||
* standardised function to generate an external link according to conf settings
|
||||
*
|
||||
* @param string $link
|
||||
* @param string $title
|
||||
* @param string $class
|
||||
* @param string $target
|
||||
* @param string $more
|
||||
* @return string
|
||||
*/
|
||||
public function external_link($link, $title = '', $class = '', $target = '', $more = '');
|
||||
|
||||
/**
|
||||
* output text string through the parser, allows dokuwiki markup to be used
|
||||
* very ineffecient for small pieces of data - try not to use
|
||||
*
|
||||
* @param string $text wiki markup to parse
|
||||
* @param string $format output format
|
||||
* @return null|string
|
||||
*/
|
||||
public function render_text($text, $format = 'xhtml');
|
||||
|
||||
/**
|
||||
* Allow the plugin to prevent DokuWiki from reusing an instance
|
||||
*
|
||||
* @return bool false if the plugin has to be instantiated
|
||||
*/
|
||||
public function isSingleton();
|
||||
}
|
||||
|
||||
|
||||
|
256
ap23/web/doku/inc/Extension/PluginTrait.php
Normal file
256
ap23/web/doku/inc/Extension/PluginTrait.php
Normal file
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Extension;
|
||||
|
||||
/**
|
||||
* Provides standard DokuWiki plugin behaviour
|
||||
*/
|
||||
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()
|
||||
protected $configloaded = false; // set to true by loadConfig() after loading plugin configuration variables
|
||||
protected $conf = array(); // array to hold plugin settings, best accessed via ->getConf()
|
||||
|
||||
/**
|
||||
* @see PluginInterface::getInfo()
|
||||
*/
|
||||
public function getInfo()
|
||||
{
|
||||
$parts = explode('_', get_class($this));
|
||||
$info = DOKU_PLUGIN . '/' . $parts[2] . '/plugin.info.txt';
|
||||
if (file_exists($info)) return confToHash($info);
|
||||
|
||||
msg(
|
||||
'getInfo() not implemented in ' . get_class($this) . ' and ' . $info . ' not found.<br />' .
|
||||
'Verify you\'re running the latest version of the plugin. If the problem persists, send a ' .
|
||||
'bug report to the author of the ' . $parts[2] . ' plugin.', -1
|
||||
);
|
||||
return array(
|
||||
'date' => '0000-00-00',
|
||||
'name' => $parts[2] . ' plugin',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginInterface::isSingleton()
|
||||
*/
|
||||
public function isSingleton()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginInterface::loadHelper()
|
||||
*/
|
||||
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);
|
||||
return $obj;
|
||||
}
|
||||
|
||||
// region introspection methods
|
||||
|
||||
/**
|
||||
* @see PluginInterface::getPluginType()
|
||||
*/
|
||||
public function getPluginType()
|
||||
{
|
||||
list($t) = explode('_', get_class($this), 2);
|
||||
return $t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginInterface::getPluginName()
|
||||
*/
|
||||
public function getPluginName()
|
||||
{
|
||||
list(/* $t */, /* $p */, $n) = explode('_', get_class($this), 4);
|
||||
return $n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginInterface::getPluginComponent()
|
||||
*/
|
||||
public function getPluginComponent()
|
||||
{
|
||||
list(/* $t */, /* $p */, /* $n */, $c) = explode('_', get_class($this), 4);
|
||||
return (isset($c) ? $c : '');
|
||||
}
|
||||
|
||||
// endregion
|
||||
// region localization methods
|
||||
|
||||
/**
|
||||
* @see PluginInterface::getLang()
|
||||
*/
|
||||
public function getLang($id)
|
||||
{
|
||||
if (!$this->localised) $this->setupLocale();
|
||||
|
||||
return (isset($this->lang[$id]) ? $this->lang[$id] : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginInterface::locale_xhtml()
|
||||
*/
|
||||
public function locale_xhtml($id)
|
||||
{
|
||||
return p_cached_output($this->localFN($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginInterface::localFN()
|
||||
*/
|
||||
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)) {
|
||||
$file = DOKU_PLUGIN . $plugin . '/lang/' . $conf['lang'] . '/' . $id . '.' . $ext;
|
||||
if (!file_exists($file)) {
|
||||
//fall back to english
|
||||
$file = DOKU_PLUGIN . $plugin . '/lang/en/' . $id . '.' . $ext;
|
||||
}
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginInterface::setupLocale()
|
||||
*/
|
||||
public function setupLocale()
|
||||
{
|
||||
if ($this->localised) return;
|
||||
|
||||
global $conf, $config_cascade; // definitely don't invoke "global $lang"
|
||||
$path = DOKU_PLUGIN . $this->getPluginName() . '/lang/';
|
||||
|
||||
$lang = array();
|
||||
|
||||
// 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')) {
|
||||
include($config_file . $this->getPluginName() . '/en/lang.php');
|
||||
}
|
||||
}
|
||||
|
||||
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')) {
|
||||
include($config_file . $this->getPluginName() . '/' . $conf['lang'] . '/lang.php');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->lang = $lang;
|
||||
$this->localised = true;
|
||||
}
|
||||
|
||||
// endregion
|
||||
// region configuration methods
|
||||
|
||||
/**
|
||||
* @see PluginInterface::getConf()
|
||||
*/
|
||||
public function getConf($setting, $notset = false)
|
||||
{
|
||||
|
||||
if (!$this->configloaded) {
|
||||
$this->loadConfig();
|
||||
}
|
||||
|
||||
if (isset($this->conf[$setting])) {
|
||||
return $this->conf[$setting];
|
||||
} else {
|
||||
return $notset;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginInterface::loadConfig()
|
||||
*/
|
||||
public function loadConfig()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$defaults = $this->readDefaultSettings();
|
||||
$plugin = $this->getPluginName();
|
||||
|
||||
foreach ($defaults as $key => $value) {
|
||||
if (isset($conf['plugin'][$plugin][$key])) continue;
|
||||
$conf['plugin'][$plugin][$key] = $value;
|
||||
}
|
||||
|
||||
$this->configloaded = true;
|
||||
$this->conf =& $conf['plugin'][$plugin];
|
||||
}
|
||||
|
||||
/**
|
||||
* read the plugin's default configuration settings from conf/default.php
|
||||
* this function is automatically called through getConf()
|
||||
*
|
||||
* @return array setting => value
|
||||
*/
|
||||
protected function readDefaultSettings()
|
||||
{
|
||||
|
||||
$path = DOKU_PLUGIN . $this->getPluginName() . '/conf/';
|
||||
$conf = array();
|
||||
|
||||
if (file_exists($path . 'default.php')) {
|
||||
include($path . 'default.php');
|
||||
}
|
||||
|
||||
return $conf;
|
||||
}
|
||||
|
||||
// endregion
|
||||
// region output methods
|
||||
|
||||
/**
|
||||
* @see PluginInterface::email()
|
||||
*/
|
||||
public function email($email, $name = '', $class = '', $more = '')
|
||||
{
|
||||
if (!$email) return $name;
|
||||
$email = obfuscate($email);
|
||||
if (!$name) $name = $email;
|
||||
$class = "class='" . ($class ? $class : 'mail') . "'";
|
||||
return "<a href='mailto:$email' $class title='$email' $more>$name</a>";
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginInterface::external_link()
|
||||
*/
|
||||
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 ($class) $class = " class='$class'";
|
||||
if ($target) $target = " target='$target'";
|
||||
if ($more) $more = " " . trim($more);
|
||||
|
||||
return "<a href='$link'$class$target$more>$title</a>";
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginInterface::render_text()
|
||||
*/
|
||||
public function render_text($text, $format = 'xhtml')
|
||||
{
|
||||
return p_render($format, p_get_instructions($text), $info);
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
122
ap23/web/doku/inc/Extension/RemotePlugin.php
Normal file
122
ap23/web/doku/inc/Extension/RemotePlugin.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
132
ap23/web/doku/inc/Extension/SyntaxPlugin.php
Normal file
132
ap23/web/doku/inc/Extension/SyntaxPlugin.php
Normal file
@@ -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);
|
||||
}
|
||||
}
|
27
ap23/web/doku/inc/FeedParser.php
Normal file
27
ap23/web/doku/inc/FeedParser.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* We override some methods of the original SimplePie class here
|
||||
*/
|
||||
class FeedParser extends SimplePie {
|
||||
|
||||
/**
|
||||
* Constructor. Set some defaults
|
||||
*/
|
||||
public function __construct(){
|
||||
parent::__construct();
|
||||
$this->enable_cache(false);
|
||||
$this->set_file_class(\dokuwiki\FeedParserFile::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Backward compatibility for older plugins
|
||||
*
|
||||
* phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
* @param string $url
|
||||
*/
|
||||
public function feed_url($url){
|
||||
$this->set_feed_url($url);
|
||||
}
|
||||
}
|
||||
|
||||
|
62
ap23/web/doku/inc/FeedParserFile.php
Normal file
62
ap23/web/doku/inc/FeedParserFile.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
34
ap23/web/doku/inc/Form/ButtonElement.php
Normal file
34
ap23/web/doku/inc/Form/ButtonElement.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class ButtonElement
|
||||
*
|
||||
* Represents a simple button
|
||||
*
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class ButtonElement extends Element {
|
||||
|
||||
/** @var string HTML content */
|
||||
protected $content = '';
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $content HTML content of the button. You have to escape it yourself.
|
||||
*/
|
||||
public function __construct($name, $content = '') {
|
||||
parent::__construct('button', array('name' => $name, 'value' => 1));
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML representation of this element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHTML() {
|
||||
return '<button ' . buildAttributes($this->attrs(), true) . '>'.$this->content.'</button>';
|
||||
}
|
||||
|
||||
}
|
62
ap23/web/doku/inc/Form/CheckableElement.php
Normal file
62
ap23/web/doku/inc/Form/CheckableElement.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class CheckableElement
|
||||
*
|
||||
* For Radio- and Checkboxes
|
||||
*
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class CheckableElement extends InputElement {
|
||||
|
||||
/**
|
||||
* @param string $type The type of this element
|
||||
* @param string $name The name of this form element
|
||||
* @param string $label The label text for this element
|
||||
*/
|
||||
public function __construct($type, $name, $label) {
|
||||
parent::__construct($type, $name, $label);
|
||||
// default value is 1
|
||||
$this->attr('value', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the useInput flag and sets the checked attribute accordingly
|
||||
*/
|
||||
protected function prefillInput() {
|
||||
global $INPUT;
|
||||
list($name, $key) = $this->getInputName();
|
||||
$myvalue = $this->val();
|
||||
|
||||
if(!$INPUT->has($name)) return;
|
||||
|
||||
if($key === null) {
|
||||
// no key - single value
|
||||
$value = $INPUT->str($name);
|
||||
if($value == $myvalue) {
|
||||
$this->attr('checked', 'checked');
|
||||
} else {
|
||||
$this->rmattr('checked');
|
||||
}
|
||||
} else {
|
||||
// we have an array, there might be several values in it
|
||||
$input = $INPUT->arr($name);
|
||||
if(isset($input[$key])) {
|
||||
$this->rmattr('checked');
|
||||
|
||||
// values seem to be in another sub array
|
||||
if(is_array($input[$key])) {
|
||||
$input = $input[$key];
|
||||
}
|
||||
|
||||
foreach($input as $value) {
|
||||
if($value == $myvalue) {
|
||||
$this->attr('checked', 'checked');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
198
ap23/web/doku/inc/Form/DropdownElement.php
Normal file
198
ap23/web/doku/inc/Form/DropdownElement.php
Normal file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class DropdownElement
|
||||
*
|
||||
* Represents a HTML select. Please note that this does not support multiple selected options!
|
||||
*
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class DropdownElement extends InputElement {
|
||||
|
||||
/** @var array OptGroup[] */
|
||||
protected $optGroups = array();
|
||||
|
||||
/**
|
||||
* @param string $name The name of this form element
|
||||
* @param array $options The available options
|
||||
* @param string $label The label text for this element (will be autoescaped)
|
||||
*/
|
||||
public function __construct($name, $options, $label = '') {
|
||||
parent::__construct('dropdown', $name, $label);
|
||||
$this->rmattr('type');
|
||||
$this->optGroups[''] = new OptGroup(null, $options);
|
||||
$this->val('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an `<optgroup>` and respective options
|
||||
*
|
||||
* @param string $label
|
||||
* @param array $options
|
||||
* @return OptGroup a reference to the added optgroup
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function addOptGroup($label, $options) {
|
||||
if (empty($label)) {
|
||||
throw new \InvalidArgumentException(hsc('<optgroup> must have a label!'));
|
||||
}
|
||||
$this->optGroups[$label] = new OptGroup($label, $options);
|
||||
return end($this->optGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or get the optgroups of an Dropdown-Element.
|
||||
*
|
||||
* optgroups have to be given as associative array
|
||||
* * the key being the label of the group
|
||||
* * the value being an array of options as defined in @see OptGroup::options()
|
||||
*
|
||||
* @param null|array $optGroups
|
||||
* @return OptGroup[]|DropdownElement
|
||||
*/
|
||||
public function optGroups($optGroups = null) {
|
||||
if($optGroups === null) {
|
||||
return $this->optGroups;
|
||||
}
|
||||
if (!is_array($optGroups)) {
|
||||
throw new \InvalidArgumentException(hsc('Argument must be an associative array of label => [options]!'));
|
||||
}
|
||||
$this->optGroups = array();
|
||||
foreach ($optGroups as $label => $options) {
|
||||
$this->addOptGroup($label, $options);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the options of the Dropdown
|
||||
*
|
||||
* Options can be given as associative array (value => label) or as an
|
||||
* indexd array (label = value) or as an array of arrays. In the latter
|
||||
* case an element has to look as follows:
|
||||
* option-value => array (
|
||||
* 'label' => option-label,
|
||||
* 'attrs' => array (
|
||||
* attr-key => attr-value, ...
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param null|array $options
|
||||
* @return $this|array
|
||||
*/
|
||||
public function options($options = null) {
|
||||
if ($options === null) {
|
||||
return $this->optGroups['']->options();
|
||||
}
|
||||
$this->optGroups[''] = new OptGroup(null, $options);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets an attribute
|
||||
*
|
||||
* When no $value is given, the current content of the attribute is returned.
|
||||
* An empty string is returned for unset attributes.
|
||||
*
|
||||
* When a $value is given, the content is set to that value and the Element
|
||||
* itself is returned for easy chaining
|
||||
*
|
||||
* @param string $name Name of the attribute to access
|
||||
* @param null|string $value New value to set
|
||||
* @return string|$this
|
||||
*/
|
||||
public function attr($name, $value = null) {
|
||||
if(strtolower($name) == 'multiple') {
|
||||
throw new \InvalidArgumentException(
|
||||
'Sorry, the dropdown element does not support the "multiple" attribute'
|
||||
);
|
||||
}
|
||||
return parent::attr($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the current value
|
||||
*
|
||||
* When setting a value that is not defined in the options, the value is ignored
|
||||
* and the first option's value is selected instead
|
||||
*
|
||||
* @param null|string $value The value to set
|
||||
* @return $this|string
|
||||
*/
|
||||
public function val($value = null) {
|
||||
if($value === null) return $this->value;
|
||||
|
||||
$value_exists = $this->setValueInOptGroups($value);
|
||||
|
||||
if($value_exists) {
|
||||
$this->value = $value;
|
||||
} else {
|
||||
// unknown value set, select first option instead
|
||||
$this->value = $this->getFirstOption();
|
||||
$this->setValueInOptGroups($this->value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first options as it will be rendered in HTML
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFirstOption() {
|
||||
$options = $this->options();
|
||||
if (!empty($options)) {
|
||||
$keys = array_keys($options);
|
||||
return (string) array_shift($keys);
|
||||
}
|
||||
foreach ($this->optGroups as $optGroup) {
|
||||
$options = $optGroup->options();
|
||||
if (!empty($options)) {
|
||||
$keys = array_keys($options);
|
||||
return (string) array_shift($keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value in the OptGroups, including the optgroup for the options without optgroup.
|
||||
*
|
||||
* @param string $value
|
||||
* @return bool
|
||||
*/
|
||||
protected function setValueInOptGroups($value) {
|
||||
$value_exists = false;
|
||||
/** @var OptGroup $optGroup */
|
||||
foreach ($this->optGroups as $optGroup) {
|
||||
$value_exists = $optGroup->storeValue($value) || $value_exists;
|
||||
if ($value_exists) {
|
||||
$value = null;
|
||||
}
|
||||
}
|
||||
return $value_exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the HTML for the select it self
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function mainElementHTML() {
|
||||
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 .= '</select>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
}
|
151
ap23/web/doku/inc/Form/Element.php
Normal file
151
ap23/web/doku/inc/Form/Element.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class Element
|
||||
*
|
||||
* The basic building block of a form
|
||||
*
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
abstract class Element {
|
||||
|
||||
/**
|
||||
* @var array the attributes of this element
|
||||
*/
|
||||
protected $attributes = array();
|
||||
|
||||
/**
|
||||
* @var string The type of this element
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* @param string $type The type of this element
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct($type, $attributes = array()) {
|
||||
$this->type = $type;
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of this element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets an attribute
|
||||
*
|
||||
* When no $value is given, the current content of the attribute is returned.
|
||||
* An empty string is returned for unset attributes.
|
||||
*
|
||||
* When a $value is given, the content is set to that value and the Element
|
||||
* itself is returned for easy chaining
|
||||
*
|
||||
* @param string $name Name of the attribute to access
|
||||
* @param null|string $value New value to set
|
||||
* @return string|$this
|
||||
*/
|
||||
public function attr($name, $value = null) {
|
||||
// set
|
||||
if($value !== null) {
|
||||
$this->attributes[$name] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// get
|
||||
if(isset($this->attributes[$name])) {
|
||||
return $this->attributes[$name];
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given attribute if it exists
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
public function rmattr($name) {
|
||||
if(isset($this->attributes[$name])) {
|
||||
unset($this->attributes[$name]);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or adds a all given attributes at once
|
||||
*
|
||||
* @param array|null $attributes
|
||||
* @return array|$this
|
||||
*/
|
||||
public function attrs($attributes = null) {
|
||||
// set
|
||||
if($attributes) {
|
||||
foreach((array) $attributes as $key => $val) {
|
||||
$this->attr($key, $val);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
// get
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a class to the class attribute
|
||||
*
|
||||
* This is the preferred method of setting the element's class
|
||||
*
|
||||
* @param string $class the new class to add
|
||||
* @return $this
|
||||
*/
|
||||
public function addClass($class) {
|
||||
$classes = explode(' ', $this->attr('class'));
|
||||
$classes[] = $class;
|
||||
$classes = array_unique($classes);
|
||||
$classes = array_filter($classes);
|
||||
$this->attr('class', join(' ', $classes));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the element's ID
|
||||
*
|
||||
* This is the preferred way of setting the element's ID
|
||||
*
|
||||
* @param null|string $id
|
||||
* @return string|$this
|
||||
*/
|
||||
public function id($id = null) {
|
||||
if(strpos($id, '__') === false) {
|
||||
throw new \InvalidArgumentException('IDs in DokuWiki have to contain two subsequent underscores');
|
||||
}
|
||||
|
||||
return $this->attr('id', $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the element's value
|
||||
*
|
||||
* This is the preferred way of setting the element's value
|
||||
*
|
||||
* @param null|string $value
|
||||
* @return string|$this
|
||||
*/
|
||||
public function val($value = null) {
|
||||
return $this->attr('value', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML representation of this element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function toHTML();
|
||||
}
|
30
ap23/web/doku/inc/Form/FieldsetCloseElement.php
Normal file
30
ap23/web/doku/inc/Form/FieldsetCloseElement.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class FieldsetCloseElement
|
||||
*
|
||||
* Closes an open Fieldset
|
||||
*
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class FieldsetCloseElement extends TagCloseElement {
|
||||
|
||||
/**
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct($attributes = array()) {
|
||||
parent::__construct('', $attributes);
|
||||
$this->type = 'fieldsetclose';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The HTML representation of this element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHTML() {
|
||||
return '</fieldset>';
|
||||
}
|
||||
}
|
36
ap23/web/doku/inc/Form/FieldsetOpenElement.php
Normal file
36
ap23/web/doku/inc/Form/FieldsetOpenElement.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class FieldsetOpenElement
|
||||
*
|
||||
* Opens a Fieldset with an optional legend
|
||||
*
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class FieldsetOpenElement extends TagOpenElement {
|
||||
|
||||
/**
|
||||
* @param string $legend
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct($legend='', $attributes = array()) {
|
||||
// this is a bit messy and we just do it for the nicer class hierarchy
|
||||
// the parent would expect the tag in $value but we're storing the
|
||||
// legend there, so we have to set the type manually
|
||||
parent::__construct($legend, $attributes);
|
||||
$this->type = 'fieldsetopen';
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML representation of this element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHTML() {
|
||||
$html = '<fieldset '.buildAttributes($this->attrs()).'>';
|
||||
$legend = $this->val();
|
||||
if($legend) $html .= DOKU_LF.'<legend>'.hsc($legend).'</legend>';
|
||||
return $html;
|
||||
}
|
||||
}
|
462
ap23/web/doku/inc/Form/Form.php
Normal file
462
ap23/web/doku/inc/Form/Form.php
Normal file
@@ -0,0 +1,462 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class Form
|
||||
*
|
||||
* Represents the whole Form. This is what you work on, and add Elements to
|
||||
*
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class Form extends Element {
|
||||
|
||||
/**
|
||||
* @var array name value pairs for hidden values
|
||||
*/
|
||||
protected $hidden = array();
|
||||
|
||||
/**
|
||||
* @var Element[] the elements of the form
|
||||
*/
|
||||
protected $elements = array();
|
||||
|
||||
/**
|
||||
* Creates a new, empty form with some default attributes
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param bool $unsafe if true, then the security token is ommited
|
||||
*/
|
||||
public function __construct($attributes = array(), $unsafe = false) {
|
||||
global $ID;
|
||||
|
||||
parent::__construct('form', $attributes);
|
||||
|
||||
// use the current URL as default action
|
||||
if(!$this->attr('action')) {
|
||||
$get = $_GET;
|
||||
if(isset($get['id'])) unset($get['id']);
|
||||
$self = wl($ID, $get, false, '&'); //attributes are escaped later
|
||||
$this->attr('action', $self);
|
||||
}
|
||||
|
||||
// post is default
|
||||
if(!$this->attr('method')) {
|
||||
$this->attr('method', 'post');
|
||||
}
|
||||
|
||||
// we like UTF-8
|
||||
if(!$this->attr('accept-charset')) {
|
||||
$this->attr('accept-charset', 'utf-8');
|
||||
}
|
||||
|
||||
// add the security token by default
|
||||
if (!$unsafe) {
|
||||
$this->setHiddenField('sectok', getSecurityToken());
|
||||
}
|
||||
|
||||
// identify this as a new form based form in HTML
|
||||
$this->addClass('doku_form');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a hidden field
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setHiddenField($name, $value) {
|
||||
$this->hidden[$name] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
#region element query function
|
||||
|
||||
/**
|
||||
* Returns the numbers of elements in the form
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function elementCount() {
|
||||
return count($this->elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param Element $element
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
public function getElementPosition(Element $element)
|
||||
{
|
||||
return array_search($element, $this->elements, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the element at a position.
|
||||
* A position out-of-bounds will return either the
|
||||
* first (underflow) or last (overflow) element.
|
||||
*
|
||||
* @param int $pos
|
||||
* @return Element
|
||||
*/
|
||||
public function getElementAt($pos) {
|
||||
if($pos < 0) $pos = count($this->elements) + $pos;
|
||||
if($pos < 0) $pos = 0;
|
||||
if($pos >= count($this->elements)) $pos = count($this->elements) - 1;
|
||||
return $this->elements[$pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the first of a type of element
|
||||
*
|
||||
* @param string $type Element type to look for.
|
||||
* @param int $offset search from this position onward
|
||||
* @return false|int position of element if found, otherwise false
|
||||
*/
|
||||
public function findPositionByType($type, $offset = 0) {
|
||||
$len = $this->elementCount();
|
||||
for($pos = $offset; $pos < $len; $pos++) {
|
||||
if($this->elements[$pos]->getType() == $type) {
|
||||
return $pos;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the first element matching the attribute
|
||||
*
|
||||
* @param string $name Name of the attribute
|
||||
* @param string $value Value the attribute should have
|
||||
* @param int $offset search from this position onward
|
||||
* @return false|int position of element if found, otherwise false
|
||||
*/
|
||||
public function findPositionByAttribute($name, $value, $offset = 0) {
|
||||
$len = $this->elementCount();
|
||||
for($pos = $offset; $pos < $len; $pos++) {
|
||||
if($this->elements[$pos]->attr($name) == $value) {
|
||||
return $pos;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Element positioning functions
|
||||
|
||||
/**
|
||||
* Adds or inserts an element to the form
|
||||
*
|
||||
* @param Element $element
|
||||
* @param int $pos 0-based position in the form, -1 for at the end
|
||||
* @return Element
|
||||
*/
|
||||
public function addElement(Element $element, $pos = -1) {
|
||||
if(is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
|
||||
'You can\'t add a form to a form'
|
||||
);
|
||||
if($pos < 0) {
|
||||
$this->elements[] = $element;
|
||||
} else {
|
||||
array_splice($this->elements, $pos, 0, array($element));
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an existing element with a new one
|
||||
*
|
||||
* @param Element $element the new element
|
||||
* @param int $pos 0-based position of the element to replace
|
||||
*/
|
||||
public function replaceElement(Element $element, $pos) {
|
||||
if(is_a($element, '\dokuwiki\Form\Form')) throw new \InvalidArgumentException(
|
||||
'You can\'t add a form to a form'
|
||||
);
|
||||
array_splice($this->elements, $pos, 1, array($element));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element from the form completely
|
||||
*
|
||||
* @param int $pos 0-based position of the element to remove
|
||||
*/
|
||||
public function removeElement($pos) {
|
||||
array_splice($this->elements, $pos, 1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Element adding functions
|
||||
|
||||
/**
|
||||
* Adds a text input field
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $label
|
||||
* @param int $pos
|
||||
* @return InputElement
|
||||
*/
|
||||
public function addTextInput($name, $label = '', $pos = -1) {
|
||||
return $this->addElement(new InputElement('text', $name, $label), $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a password input field
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $label
|
||||
* @param int $pos
|
||||
* @return InputElement
|
||||
*/
|
||||
public function addPasswordInput($name, $label = '', $pos = -1) {
|
||||
return $this->addElement(new InputElement('password', $name, $label), $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a radio button field
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $label
|
||||
* @param int $pos
|
||||
* @return CheckableElement
|
||||
*/
|
||||
public function addRadioButton($name, $label = '', $pos = -1) {
|
||||
return $this->addElement(new CheckableElement('radio', $name, $label), $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a checkbox field
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $label
|
||||
* @param int $pos
|
||||
* @return CheckableElement
|
||||
*/
|
||||
public function addCheckbox($name, $label = '', $pos = -1) {
|
||||
return $this->addElement(new CheckableElement('checkbox', $name, $label), $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dropdown field
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $options
|
||||
* @param string $label
|
||||
* @param int $pos
|
||||
* @return DropdownElement
|
||||
*/
|
||||
public function addDropdown($name, $options, $label = '', $pos = -1) {
|
||||
return $this->addElement(new DropdownElement($name, $options, $label), $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a textarea field
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $label
|
||||
* @param int $pos
|
||||
* @return TextareaElement
|
||||
*/
|
||||
public function addTextarea($name, $label = '', $pos = -1) {
|
||||
return $this->addElement(new TextareaElement($name, $label), $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a simple button, escapes the content for you
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $content
|
||||
* @param int $pos
|
||||
* @return Element
|
||||
*/
|
||||
public function addButton($name, $content, $pos = -1) {
|
||||
return $this->addElement(new ButtonElement($name, hsc($content)), $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a simple button, allows HTML for content
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $html
|
||||
* @param int $pos
|
||||
* @return Element
|
||||
*/
|
||||
public function addButtonHTML($name, $html, $pos = -1) {
|
||||
return $this->addElement(new ButtonElement($name, $html), $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a label referencing another input element, escapes the label for you
|
||||
*
|
||||
* @param string $label
|
||||
* @param string $for
|
||||
* @param int $pos
|
||||
* @return Element
|
||||
*/
|
||||
public function addLabel($label, $for='', $pos = -1) {
|
||||
return $this->addLabelHTML(hsc($label), $for, $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a label referencing another input element, allows HTML for content
|
||||
*
|
||||
* @param string $content
|
||||
* @param string|Element $for
|
||||
* @param int $pos
|
||||
* @return Element
|
||||
*/
|
||||
public function addLabelHTML($content, $for='', $pos = -1) {
|
||||
$element = new LabelElement(hsc($content));
|
||||
|
||||
if(is_a($for, '\dokuwiki\Form\Element')) {
|
||||
/** @var Element $for */
|
||||
$for = $for->id();
|
||||
}
|
||||
$for = (string) $for;
|
||||
if($for !== '') {
|
||||
$element->attr('for', $for);
|
||||
}
|
||||
|
||||
return $this->addElement($element, $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add fixed HTML to the form
|
||||
*
|
||||
* @param string $html
|
||||
* @param int $pos
|
||||
* @return HTMLElement
|
||||
*/
|
||||
public function addHTML($html, $pos = -1) {
|
||||
return $this->addElement(new HTMLElement($html), $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a closed HTML tag to the form
|
||||
*
|
||||
* @param string $tag
|
||||
* @param int $pos
|
||||
* @return TagElement
|
||||
*/
|
||||
public function addTag($tag, $pos = -1) {
|
||||
return $this->addElement(new TagElement($tag), $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an open HTML tag to the form
|
||||
*
|
||||
* Be sure to close it again!
|
||||
*
|
||||
* @param string $tag
|
||||
* @param int $pos
|
||||
* @return TagOpenElement
|
||||
*/
|
||||
public function addTagOpen($tag, $pos = -1) {
|
||||
return $this->addElement(new TagOpenElement($tag), $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a closing HTML tag to the form
|
||||
*
|
||||
* Be sure it had been opened before
|
||||
*
|
||||
* @param string $tag
|
||||
* @param int $pos
|
||||
* @return TagCloseElement
|
||||
*/
|
||||
public function addTagClose($tag, $pos = -1) {
|
||||
return $this->addElement(new TagCloseElement($tag), $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a Fieldset
|
||||
*
|
||||
* @param string $legend
|
||||
* @param int $pos
|
||||
* @return FieldsetOpenElement
|
||||
*/
|
||||
public function addFieldsetOpen($legend = '', $pos = -1) {
|
||||
return $this->addElement(new FieldsetOpenElement($legend), $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a fieldset
|
||||
*
|
||||
* @param int $pos
|
||||
* @return TagCloseElement
|
||||
*/
|
||||
public function addFieldsetClose($pos = -1) {
|
||||
return $this->addElement(new FieldsetCloseElement(), $pos);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/**
|
||||
* Adjust the elements so that fieldset open and closes are matching
|
||||
*/
|
||||
protected function balanceFieldsets() {
|
||||
$lastclose = 0;
|
||||
$isopen = false;
|
||||
$len = count($this->elements);
|
||||
|
||||
for($pos = 0; $pos < $len; $pos++) {
|
||||
$type = $this->elements[$pos]->getType();
|
||||
if($type == 'fieldsetopen') {
|
||||
if($isopen) {
|
||||
//close previous fieldset
|
||||
$this->addFieldsetClose($pos);
|
||||
$lastclose = $pos + 1;
|
||||
$pos++;
|
||||
$len++;
|
||||
}
|
||||
$isopen = true;
|
||||
} else if($type == 'fieldsetclose') {
|
||||
if(!$isopen) {
|
||||
// make sure there was a fieldsetopen
|
||||
// either right after the last close or at the begining
|
||||
$this->addFieldsetOpen('', $lastclose);
|
||||
$len++;
|
||||
$pos++;
|
||||
}
|
||||
$lastclose = $pos;
|
||||
$isopen = false;
|
||||
}
|
||||
}
|
||||
|
||||
// close open fieldset at the end
|
||||
if($isopen) {
|
||||
$this->addFieldsetClose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML representation of the whole form
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHTML() {
|
||||
$this->balanceFieldsets();
|
||||
|
||||
$html = '<form ' . buildAttributes($this->attrs()) . '>';
|
||||
|
||||
foreach($this->hidden as $name => $value) {
|
||||
$html .= '<input type="hidden" name="' . $name . '" value="' . formText($value) . '" />';
|
||||
}
|
||||
|
||||
foreach($this->elements as $element) {
|
||||
$html .= $element->toHTML();
|
||||
}
|
||||
|
||||
$html .= '</form>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
29
ap23/web/doku/inc/Form/HTMLElement.php
Normal file
29
ap23/web/doku/inc/Form/HTMLElement.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class HTMLElement
|
||||
*
|
||||
* Holds arbitrary HTML that is added as is to the Form
|
||||
*
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class HTMLElement extends ValueElement {
|
||||
|
||||
|
||||
/**
|
||||
* @param string $html
|
||||
*/
|
||||
public function __construct($html) {
|
||||
parent::__construct('html', $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML representation of this element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHTML() {
|
||||
return $this->val();
|
||||
}
|
||||
}
|
159
ap23/web/doku/inc/Form/InputElement.php
Normal file
159
ap23/web/doku/inc/Form/InputElement.php
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class InputElement
|
||||
*
|
||||
* Base class for all input elements. Uses a wrapping label when label
|
||||
* text is given.
|
||||
*
|
||||
* @todo figure out how to make wrapping or related label configurable
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class InputElement extends Element {
|
||||
/**
|
||||
* @var LabelElement
|
||||
*/
|
||||
protected $label = null;
|
||||
|
||||
/**
|
||||
* @var bool if the element should reflect posted values
|
||||
*/
|
||||
protected $useInput = true;
|
||||
|
||||
/**
|
||||
* @param string $type The type of this element
|
||||
* @param string $name The name of this form element
|
||||
* @param string $label The label text for this element (will be autoescaped)
|
||||
*/
|
||||
public function __construct($type, $name, $label = '') {
|
||||
parent::__construct($type, array('name' => $name));
|
||||
$this->attr('name', $name);
|
||||
$this->attr('type', $type);
|
||||
if($label) $this->label = new LabelElement($label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label element if there's one set
|
||||
*
|
||||
* @return LabelElement|null
|
||||
*/
|
||||
public function getLabel() {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the user sent input be used to initialize the input field
|
||||
*
|
||||
* The default is true. Any set values will be overwritten by the INPUT
|
||||
* provided values.
|
||||
*
|
||||
* @param bool $useinput
|
||||
* @return $this
|
||||
*/
|
||||
public function useInput($useinput) {
|
||||
$this->useInput = (bool) $useinput;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the element's ID
|
||||
*
|
||||
* @param null|string $id
|
||||
* @return string|$this
|
||||
*/
|
||||
public function id($id = null) {
|
||||
if($this->label) $this->label->attr('for', $id);
|
||||
return parent::id($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a class to the class attribute
|
||||
*
|
||||
* This is the preferred method of setting the element's class
|
||||
*
|
||||
* @param string $class the new class to add
|
||||
* @return $this
|
||||
*/
|
||||
public function addClass($class) {
|
||||
if($this->label) $this->label->addClass($class);
|
||||
return parent::addClass($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures out how to access the value for this field from INPUT data
|
||||
*
|
||||
* The element's name could have been given as a simple string ('foo')
|
||||
* or in array notation ('foo[bar]').
|
||||
*
|
||||
* Note: this function only handles one level of arrays. If your data
|
||||
* is nested deeper, you should call useInput(false) and set the
|
||||
* correct value yourself
|
||||
*
|
||||
* @return array name and array key (null if not an array)
|
||||
*/
|
||||
protected function getInputName() {
|
||||
$name = $this->attr('name');
|
||||
parse_str("$name=1", $parsed);
|
||||
|
||||
$name = array_keys($parsed);
|
||||
$name = array_shift($name);
|
||||
|
||||
if(is_array($parsed[$name])) {
|
||||
$key = array_keys($parsed[$name]);
|
||||
$key = array_shift($key);
|
||||
} else {
|
||||
$key = null;
|
||||
}
|
||||
|
||||
return array($name, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the useInput flag and set the value attribute accordingly
|
||||
*/
|
||||
protected function prefillInput() {
|
||||
global $INPUT;
|
||||
|
||||
list($name, $key) = $this->getInputName();
|
||||
if(!$INPUT->has($name)) return;
|
||||
|
||||
if($key === null) {
|
||||
$value = $INPUT->str($name);
|
||||
} else {
|
||||
$value = $INPUT->arr($name);
|
||||
if(isset($value[$key])) {
|
||||
$value = $value[$key];
|
||||
} else {
|
||||
$value = '';
|
||||
}
|
||||
}
|
||||
$this->val($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML representation of this element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function mainElementHTML() {
|
||||
if($this->useInput) $this->prefillInput();
|
||||
return '<input ' . buildAttributes($this->attrs()) . ' />';
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML representation of this element wrapped in a label
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHTML() {
|
||||
if($this->label) {
|
||||
return '<label ' . buildAttributes($this->label->attrs()) . '>' . DOKU_LF .
|
||||
'<span>' . hsc($this->label->val()) . '</span>' . DOKU_LF .
|
||||
$this->mainElementHTML() . DOKU_LF .
|
||||
'</label>';
|
||||
} else {
|
||||
return $this->mainElementHTML();
|
||||
}
|
||||
}
|
||||
}
|
27
ap23/web/doku/inc/Form/LabelElement.php
Normal file
27
ap23/web/doku/inc/Form/LabelElement.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class Label
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class LabelElement extends ValueElement {
|
||||
|
||||
/**
|
||||
* Creates a new Label
|
||||
*
|
||||
* @param string $label This is is raw HTML and will not be escaped
|
||||
*/
|
||||
public function __construct($label) {
|
||||
parent::__construct('label', $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML representation of this element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHTML() {
|
||||
return '<label ' . buildAttributes($this->attrs()) . '>' . $this->val() . '</label>';
|
||||
}
|
||||
}
|
181
ap23/web/doku/inc/Form/LegacyForm.php
Normal file
181
ap23/web/doku/inc/Form/LegacyForm.php
Normal file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class LegacyForm
|
||||
*
|
||||
* Provides a compatibility layer to the old Doku_Form API
|
||||
*
|
||||
* This can be used to work with the modern API on forms provided by old events for
|
||||
* example. When you start new forms, just use Form\Form
|
||||
*
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class LegacyForm extends Form {
|
||||
|
||||
/**
|
||||
* Creates a new modern form from an old legacy Doku_Form
|
||||
*
|
||||
* @param \Doku_Form $oldform
|
||||
*/
|
||||
public function __construct(\Doku_Form $oldform) {
|
||||
parent::__construct($oldform->params);
|
||||
|
||||
$this->hidden = $oldform->_hidden;
|
||||
|
||||
foreach($oldform->_content as $element) {
|
||||
list($ctl, $attr) = $this->parseLegacyAttr($element);
|
||||
|
||||
if(is_array($element)) {
|
||||
switch($ctl['elem']) {
|
||||
case 'wikitext':
|
||||
$this->addTextarea('wikitext')
|
||||
->attrs($attr)
|
||||
->id('wiki__text')
|
||||
->val($ctl['text'])
|
||||
->addClass($ctl['class']);
|
||||
break;
|
||||
case 'textfield':
|
||||
$this->addTextInput($ctl['name'], $ctl['text'])
|
||||
->attrs($attr)
|
||||
->id($ctl['id'])
|
||||
->addClass($ctl['class']);
|
||||
break;
|
||||
case 'passwordfield':
|
||||
$this->addPasswordInput($ctl['name'], $ctl['text'])
|
||||
->attrs($attr)
|
||||
->id($ctl['id'])
|
||||
->addClass($ctl['class']);
|
||||
break;
|
||||
case 'checkboxfield':
|
||||
$this->addCheckbox($ctl['name'], $ctl['text'])
|
||||
->attrs($attr)
|
||||
->id($ctl['id'])
|
||||
->addClass($ctl['class']);
|
||||
break;
|
||||
case 'radiofield':
|
||||
$this->addRadioButton($ctl['name'], $ctl['text'])
|
||||
->attrs($attr)
|
||||
->id($ctl['id'])
|
||||
->addClass($ctl['class']);
|
||||
break;
|
||||
case 'tag':
|
||||
$this->addTag($ctl['tag'])
|
||||
->attrs($attr)
|
||||
->attr('name', $ctl['name'])
|
||||
->id($ctl['id'])
|
||||
->addClass($ctl['class']);
|
||||
break;
|
||||
case 'opentag':
|
||||
$this->addTagOpen($ctl['tag'])
|
||||
->attrs($attr)
|
||||
->attr('name', $ctl['name'])
|
||||
->id($ctl['id'])
|
||||
->addClass($ctl['class']);
|
||||
break;
|
||||
case 'closetag':
|
||||
$this->addTagClose($ctl['tag']);
|
||||
break;
|
||||
case 'openfieldset':
|
||||
$this->addFieldsetOpen($ctl['legend'])
|
||||
->attrs($attr)
|
||||
->attr('name', $ctl['name'])
|
||||
->id($ctl['id'])
|
||||
->addClass($ctl['class']);
|
||||
break;
|
||||
case 'closefieldset':
|
||||
$this->addFieldsetClose();
|
||||
break;
|
||||
case 'button':
|
||||
case 'field':
|
||||
case 'fieldright':
|
||||
case 'filefield':
|
||||
case 'menufield':
|
||||
case 'listboxfield':
|
||||
throw new \UnexpectedValueException('Unsupported legacy field ' . $ctl['elem']);
|
||||
break;
|
||||
default:
|
||||
throw new \UnexpectedValueException('Unknown legacy field ' . $ctl['elem']);
|
||||
|
||||
}
|
||||
} else {
|
||||
$this->addHTML($element);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses out what is the elements attributes and what is control info
|
||||
*
|
||||
* @param array $legacy
|
||||
* @return array
|
||||
*/
|
||||
protected function parseLegacyAttr($legacy) {
|
||||
$attributes = array();
|
||||
$control = array();
|
||||
|
||||
foreach($legacy as $key => $val) {
|
||||
if($key[0] == '_') {
|
||||
$control[substr($key, 1)] = $val;
|
||||
} elseif($key == 'name') {
|
||||
$control[$key] = $val;
|
||||
} elseif($key == 'id') {
|
||||
$control[$key] = $val;
|
||||
} else {
|
||||
$attributes[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
return array($control, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates our types to the legacy types
|
||||
*
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
protected function legacyType($type) {
|
||||
static $types = array(
|
||||
'text' => 'textfield',
|
||||
'password' => 'passwordfield',
|
||||
'checkbox' => 'checkboxfield',
|
||||
'radio' => 'radiofield',
|
||||
'tagopen' => 'opentag',
|
||||
'tagclose' => 'closetag',
|
||||
'fieldsetopen' => 'openfieldset',
|
||||
'fieldsetclose' => 'closefieldset',
|
||||
);
|
||||
if(isset($types[$type])) return $types[$type];
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an old legacy form from this modern form's data
|
||||
*
|
||||
* @return \Doku_Form
|
||||
*/
|
||||
public function toLegacy() {
|
||||
$this->balanceFieldsets();
|
||||
|
||||
$legacy = new \Doku_Form($this->attrs());
|
||||
$legacy->_hidden = $this->hidden;
|
||||
foreach($this->elements as $element) {
|
||||
if(is_a($element, 'dokuwiki\Form\HTMLElement')) {
|
||||
$legacy->_content[] = $element->toHTML();
|
||||
} elseif(is_a($element, 'dokuwiki\Form\InputElement')) {
|
||||
/** @var InputElement $element */
|
||||
$data = $element->attrs();
|
||||
$data['_elem'] = $this->legacyType($element->getType());
|
||||
$label = $element->getLabel();
|
||||
if($label) {
|
||||
$data['_class'] = $label->attr('class');
|
||||
}
|
||||
$legacy->_content[] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
return $legacy;
|
||||
}
|
||||
}
|
106
ap23/web/doku/inc/Form/OptGroup.php
Normal file
106
ap23/web/doku/inc/Form/OptGroup.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
|
||||
class OptGroup extends Element {
|
||||
protected $options = array();
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @param string $label The label text for this element (will be autoescaped)
|
||||
* @param array $options The available options
|
||||
*/
|
||||
public function __construct($label, $options) {
|
||||
parent::__construct('optGroup', array('label' => $label));
|
||||
$this->options($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the given value so it can be used during rendering
|
||||
*
|
||||
* This is intended to be only called from within @see DropdownElement::val()
|
||||
*
|
||||
* @param string $value
|
||||
* @return bool true if an option with the given value exists, false otherwise
|
||||
*/
|
||||
public function storeValue($value) {
|
||||
$this->value = $value;
|
||||
return isset($this->options[$value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the options of the optgroup
|
||||
*
|
||||
* Options can be given as associative array (value => label) or as an
|
||||
* indexd array (label = value) or as an array of arrays. In the latter
|
||||
* case an element has to look as follows:
|
||||
* option-value => array (
|
||||
* 'label' => option-label,
|
||||
* 'attrs' => array (
|
||||
* attr-key => attr-value, ...
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param null|array $options
|
||||
* @return $this|array
|
||||
*/
|
||||
public function options($options = null) {
|
||||
if($options === null) return $this->options;
|
||||
if(!is_array($options)) throw new \InvalidArgumentException('Options have to be an array');
|
||||
$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('attrs', $val) && is_array($val['attrs']) && key_exists('selected', $val['attrs'])) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Please use function "DropdownElement::val()" to set the selected option'
|
||||
);
|
||||
}
|
||||
$this->options[$key] = $val;
|
||||
} elseif(is_int($key)) {
|
||||
$this->options[$val] = array('label' => (string) $val);
|
||||
} else {
|
||||
$this->options[$key] = array('label' => (string) $val);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The HTML representation of this element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHTML() {
|
||||
if ($this->attributes['label'] === null) {
|
||||
return $this->renderOptions();
|
||||
}
|
||||
$html = '<optgroup '. buildAttributes($this->attrs()) . '>';
|
||||
$html .= $this->renderOptions();
|
||||
$html .= '</optgroup>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function renderOptions() {
|
||||
$html = '';
|
||||
foreach($this->options as $key => $val) {
|
||||
$selected = ((string)$key === (string)$this->value) ? ' selected="selected"' : '';
|
||||
$attrs = '';
|
||||
if (!empty($val['attrs']) && is_array($val['attrs'])) {
|
||||
$attrs = buildAttributes($val['attrs']);
|
||||
}
|
||||
$html .= '<option' . $selected . ' value="' . hsc($key) . '" '.$attrs.'>';
|
||||
$html .= hsc($val['label']);
|
||||
$html .= '</option>';
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
}
|
88
ap23/web/doku/inc/Form/TagCloseElement.php
Normal file
88
ap23/web/doku/inc/Form/TagCloseElement.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class TagCloseElement
|
||||
*
|
||||
* Creates an HTML close tag. You have to make sure it has been opened
|
||||
* before or this will produce invalid HTML
|
||||
*
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class TagCloseElement extends ValueElement {
|
||||
|
||||
/**
|
||||
* @param string $tag
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct($tag, $attributes = array()) {
|
||||
parent::__construct('tagclose', $tag, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* do not call this
|
||||
*
|
||||
* @param string $class
|
||||
* @return void
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function addClass($class) {
|
||||
throw new \BadMethodCallException('You can\t add classes to closing tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* do not call this
|
||||
*
|
||||
* @param null|string $id
|
||||
* @return string
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function id($id = null) {
|
||||
if ($id === null) {
|
||||
return '';
|
||||
} else {
|
||||
throw new \BadMethodCallException('You can\t add ID to closing tag');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* do not call this
|
||||
*
|
||||
* @param string $name
|
||||
* @param null|string $value
|
||||
* @return string
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function attr($name, $value = null) {
|
||||
if ($value === null) {
|
||||
return '';
|
||||
} else {
|
||||
throw new \BadMethodCallException('You can\t add attributes to closing tag');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* do not call this
|
||||
*
|
||||
* @param array|null $attributes
|
||||
* @return array
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function attrs($attributes = null) {
|
||||
if ($attributes === null) {
|
||||
return array();
|
||||
} else {
|
||||
throw new \BadMethodCallException('You can\t add attributes to closing tag');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML representation of this element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHTML() {
|
||||
return '</'.$this->val().'>';
|
||||
}
|
||||
|
||||
}
|
29
ap23/web/doku/inc/Form/TagElement.php
Normal file
29
ap23/web/doku/inc/Form/TagElement.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class TagElement
|
||||
*
|
||||
* Creates a self closing HTML tag
|
||||
*
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class TagElement extends ValueElement {
|
||||
|
||||
/**
|
||||
* @param string $tag
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct($tag, $attributes = array()) {
|
||||
parent::__construct('tag', $tag, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML representation of this element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHTML() {
|
||||
return '<'.$this->val().' '.buildAttributes($this->attrs()).' />';
|
||||
}
|
||||
}
|
30
ap23/web/doku/inc/Form/TagOpenElement.php
Normal file
30
ap23/web/doku/inc/Form/TagOpenElement.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class TagOpenElement
|
||||
*
|
||||
* Creates an open HTML tag. You have to make sure you close it
|
||||
* again or this will produce invalid HTML
|
||||
*
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class TagOpenElement extends ValueElement {
|
||||
|
||||
/**
|
||||
* @param string $tag
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct($tag, $attributes = array()) {
|
||||
parent::__construct('tagopen', $tag, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML representation of this element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHTML() {
|
||||
return '<'.$this->val().' '.buildAttributes($this->attrs()).'>';
|
||||
}
|
||||
}
|
51
ap23/web/doku/inc/Form/TextareaElement.php
Normal file
51
ap23/web/doku/inc/Form/TextareaElement.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class TextareaElement
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
class TextareaElement extends InputElement {
|
||||
|
||||
/**
|
||||
* @var string the actual text within the area
|
||||
*/
|
||||
protected $text;
|
||||
|
||||
/**
|
||||
* @param string $name The name of this form element
|
||||
* @param string $label The label text for this element
|
||||
*/
|
||||
public function __construct($name, $label) {
|
||||
parent::__construct('textarea', $name, $label);
|
||||
$this->attr('dir', 'auto');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the element's value
|
||||
*
|
||||
* This is the preferred way of setting the element's value
|
||||
*
|
||||
* @param null|string $value
|
||||
* @return string|$this
|
||||
*/
|
||||
public function val($value = null) {
|
||||
if($value !== null) {
|
||||
$this->text = cleanText($value);
|
||||
return $this;
|
||||
}
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML representation of this element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function mainElementHTML() {
|
||||
if($this->useInput) $this->prefillInput();
|
||||
return '<textarea ' . buildAttributes($this->attrs()) . '>' .
|
||||
formText($this->val()) . '</textarea>';
|
||||
}
|
||||
|
||||
}
|
45
ap23/web/doku/inc/Form/ValueElement.php
Normal file
45
ap23/web/doku/inc/Form/ValueElement.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Form;
|
||||
|
||||
/**
|
||||
* Class ValueElement
|
||||
*
|
||||
* Just like an Element but it's value is not part of its attributes
|
||||
*
|
||||
* What the value is (tag name, content, etc) is defined by the actual implementations
|
||||
*
|
||||
* @package dokuwiki\Form
|
||||
*/
|
||||
abstract class ValueElement extends Element {
|
||||
|
||||
/**
|
||||
* @var string holds the element's value
|
||||
*/
|
||||
protected $value = '';
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param string $value
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct($type, $value, $attributes = array()) {
|
||||
parent::__construct($type, $attributes);
|
||||
$this->val($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the element's value
|
||||
*
|
||||
* @param null|string $value
|
||||
* @return string|$this
|
||||
*/
|
||||
public function val($value = null) {
|
||||
if($value !== null) {
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
}
|
77
ap23/web/doku/inc/HTTP/DokuHTTPClient.php
Normal file
77
ap23/web/doku/inc/HTTP/DokuHTTPClient.php
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
885
ap23/web/doku/inc/HTTP/HTTPClient.php
Normal file
885
ap23/web/doku/inc/HTTP/HTTPClient.php
Normal file
@@ -0,0 +1,885 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\HTTP;
|
||||
|
||||
define('HTTP_NL',"\r\n");
|
||||
|
||||
|
||||
/**
|
||||
* This class implements a basic HTTP client
|
||||
*
|
||||
* It supports POST and GET, Proxy usage, basic authentication,
|
||||
* handles cookies and referers. It is based upon the httpclient
|
||||
* function from the VideoDB project.
|
||||
*
|
||||
* @link http://www.splitbrain.org/go/videodb
|
||||
* @author Andreas Goetz <cpuidle@gmx.de>
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @author Tobias Sarnowski <sarnowski@new-thoughts.org>
|
||||
*/
|
||||
class HTTPClient {
|
||||
//set these if you like
|
||||
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
|
||||
public $error;
|
||||
public $redirect_count;
|
||||
|
||||
// read these after a successful request
|
||||
public $status;
|
||||
public $resp_body;
|
||||
public $resp_headers;
|
||||
|
||||
// set these to do basic authentication
|
||||
public $user;
|
||||
public $pass;
|
||||
|
||||
// set these if you need to use a 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
|
||||
protected static $connections = array();
|
||||
|
||||
// what we use as boundary on multipart/form-data posts
|
||||
protected $boundary = '---DokuWikiHTTPClient--4523452351';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
public function __construct(){
|
||||
$this->agent = 'Mozilla/4.0 (compatible; DokuWiki HTTP Client; '.PHP_OS.')';
|
||||
$this->timeout = 15;
|
||||
$this->cookies = array();
|
||||
$this->referer = '';
|
||||
$this->max_redirect = 3;
|
||||
$this->redirect_count = 0;
|
||||
$this->status = 0;
|
||||
$this->headers = array();
|
||||
$this->http = '1.0';
|
||||
$this->debug = false;
|
||||
$this->max_bodysize = 0;
|
||||
$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,*/*';
|
||||
$this->headers['Accept-Language'] = 'en-us';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple function to do a GET request
|
||||
*
|
||||
* Returns the wanted page or false on an error;
|
||||
*
|
||||
* @param string $url The URL to fetch
|
||||
* @param bool $sloppy304 Return body on 304 not modified
|
||||
* @return false|string response body, false on error
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
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;
|
||||
return $this->resp_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple function to do a GET request with given parameters
|
||||
*
|
||||
* Returns the wanted page or false on an error.
|
||||
*
|
||||
* This is a convenience wrapper around get(). The given parameters
|
||||
* will be correctly encoded and added to the given base URL.
|
||||
*
|
||||
* @param string $url The URL to fetch
|
||||
* @param array $data Associative array of parameters
|
||||
* @param bool $sloppy304 Return body on 304 not modified
|
||||
* @return false|string response body, false on error
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
public function dget($url,$data,$sloppy304=false){
|
||||
if(strpos($url,'?')){
|
||||
$url .= '&';
|
||||
}else{
|
||||
$url .= '?';
|
||||
}
|
||||
$url .= $this->postEncode($data);
|
||||
return $this->get($url,$sloppy304);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple function to do a POST request
|
||||
*
|
||||
* Returns the resulting page or false on an error;
|
||||
*
|
||||
* @param string $url The URL to fetch
|
||||
* @param array $data Associative array of parameters
|
||||
* @return false|string response body, false on error
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an HTTP request
|
||||
*
|
||||
* This method handles the whole HTTP communication. It respects set proxy settings,
|
||||
* builds the request headers, follows redirects and parses the response.
|
||||
*
|
||||
* Post data should be passed as associative array. When passed as string it will be
|
||||
* sent as is. You will need to setup your own Content-Type header then.
|
||||
*
|
||||
* @param string $url - the complete URL
|
||||
* @param mixed $data - the post data either as array or raw data
|
||||
* @param string $method - HTTP Method usually GET or POST.
|
||||
* @return bool - true on success
|
||||
*
|
||||
* @author Andreas Goetz <cpuidle@gmx.de>
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
public function sendRequest($url,$data='',$method='GET'){
|
||||
$this->start = $this->time();
|
||||
$this->error = '';
|
||||
$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'){
|
||||
unset($this->headers['Accept-encoding']);
|
||||
}
|
||||
|
||||
// parse URL into bits
|
||||
$uri = parse_url($url);
|
||||
$server = $uri['host'];
|
||||
$path = $uri['path'];
|
||||
if(empty($path)) $path = '/';
|
||||
if(!empty($uri['query'])) $path .= '?'.$uri['query'];
|
||||
if(!empty($uri['port'])) $port = $uri['port'];
|
||||
if(isset($uri['user'])) $this->user = $uri['user'];
|
||||
if(isset($uri['pass'])) $this->pass = $uri['pass'];
|
||||
|
||||
// proxy setup
|
||||
if($this->useProxyForUrl($url)){
|
||||
$request_url = $url;
|
||||
$server = $this->proxy_host;
|
||||
$port = $this->proxy_port;
|
||||
if (empty($port)) $port = 8080;
|
||||
$use_tls = $this->proxy_ssl;
|
||||
}else{
|
||||
$request_url = $path;
|
||||
if (!isset($port)) $port = ($uri['scheme'] == 'https') ? 443 : 80;
|
||||
$use_tls = ($uri['scheme'] == 'https');
|
||||
}
|
||||
|
||||
// add SSL stream prefix if needed - needs SSL support in PHP
|
||||
if($use_tls) {
|
||||
if(!in_array('ssl', stream_get_transports())) {
|
||||
$this->status = -200;
|
||||
$this->error = 'This PHP version does not support SSL - cannot connect to server';
|
||||
}
|
||||
$server = 'ssl://'.$server;
|
||||
}
|
||||
|
||||
// prepare headers
|
||||
$headers = $this->headers;
|
||||
$headers['Host'] = $uri['host'];
|
||||
if(!empty($uri['port'])) $headers['Host'].= ':'.$uri['port'];
|
||||
$headers['User-Agent'] = $this->agent;
|
||||
$headers['Referer'] = $this->referer;
|
||||
|
||||
if($method == 'POST'){
|
||||
if(is_array($data)){
|
||||
if (empty($headers['Content-Type'])) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}elseif($method == 'GET'){
|
||||
$data = ''; //no data allowed on GET requests
|
||||
}
|
||||
|
||||
$contentlength = strlen($data);
|
||||
if($contentlength) {
|
||||
$headers['Content-Length'] = $contentlength;
|
||||
}
|
||||
|
||||
if($this->user) {
|
||||
$headers['Authorization'] = 'Basic '.base64_encode($this->user.':'.$this->pass);
|
||||
}
|
||||
if($this->proxy_user) {
|
||||
$headers['Proxy-Authorization'] = 'Basic '.base64_encode($this->proxy_user.':'.$this->proxy_pass);
|
||||
}
|
||||
|
||||
// already connected?
|
||||
$connectionId = $this->uniqueConnectionId($server,$port);
|
||||
$this->debug('connection pool', self::$connections);
|
||||
$socket = null;
|
||||
if (isset(self::$connections[$connectionId])) {
|
||||
$this->debug('reusing connection', $connectionId);
|
||||
$socket = self::$connections[$connectionId];
|
||||
}
|
||||
if (is_null($socket) || feof($socket)) {
|
||||
$this->debug('opening connection', $connectionId);
|
||||
// open socket
|
||||
$socket = @fsockopen($server,$port,$errno, $errstr, $this->timeout);
|
||||
if (!$socket){
|
||||
$this->status = -100;
|
||||
$this->error = "Could not connect to $server:$port\n$errstr ($errno)";
|
||||
return false;
|
||||
}
|
||||
|
||||
// try establish a CONNECT tunnel for SSL
|
||||
try {
|
||||
if($this->ssltunnel($socket, $request_url)){
|
||||
// no keep alive for tunnels
|
||||
$this->keep_alive = false;
|
||||
// tunnel is authed already
|
||||
if(isset($headers['Proxy-Authentication'])) unset($headers['Proxy-Authentication']);
|
||||
}
|
||||
} catch (HTTPClientException $e) {
|
||||
$this->status = $e->getCode();
|
||||
$this->error = $e->getMessage();
|
||||
fclose($socket);
|
||||
return false;
|
||||
}
|
||||
|
||||
// keep alive?
|
||||
if ($this->keep_alive) {
|
||||
self::$connections[$connectionId] = $socket;
|
||||
} else {
|
||||
unset(self::$connections[$connectionId]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->keep_alive && !$this->useProxyForUrl($request_url)) {
|
||||
// RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive
|
||||
// connection token to a proxy server. We still do keep the connection the
|
||||
// proxy alive (well except for CONNECT tunnels)
|
||||
$headers['Connection'] = 'Keep-Alive';
|
||||
} else {
|
||||
$headers['Connection'] = 'Close';
|
||||
}
|
||||
|
||||
try {
|
||||
//set non-blocking
|
||||
stream_set_blocking($socket, 0);
|
||||
|
||||
// build request
|
||||
$request = "$method $request_url HTTP/".$this->http.HTTP_NL;
|
||||
$request .= $this->buildHeaders($headers);
|
||||
$request .= $this->getCookies();
|
||||
$request .= HTTP_NL;
|
||||
$request .= $data;
|
||||
|
||||
$this->debug('request',$request);
|
||||
$this->sendData($socket, $request, 'request');
|
||||
|
||||
// read headers from socket
|
||||
$r_headers = '';
|
||||
do{
|
||||
$r_line = $this->readLine($socket, 'headers');
|
||||
$r_headers .= $r_line;
|
||||
}while($r_line != "\r\n" && $r_line != "\n");
|
||||
|
||||
$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)){
|
||||
if($match[1] > $this->max_bodysize){
|
||||
if ($this->max_bodysize_abort)
|
||||
throw new HTTPClientException('Reported content length exceeds allowed response size');
|
||||
else
|
||||
$this->error = 'Reported content length exceeds allowed response size';
|
||||
}
|
||||
}
|
||||
|
||||
// get Status
|
||||
if (!preg_match('/^HTTP\/(\d\.\d)\s*(\d+).*?\n/s', $r_headers, $m))
|
||||
throw new HTTPClientException('Server returned bad answer '.$r_headers);
|
||||
|
||||
$this->status = $m[2];
|
||||
|
||||
// handle headers and cookies
|
||||
$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);
|
||||
list($key,$val) = explode('=',$cookie,2);
|
||||
$key = trim($key);
|
||||
if($val == 'deleted'){
|
||||
if(isset($this->cookies[$key])){
|
||||
unset($this->cookies[$key]);
|
||||
}
|
||||
}elseif($key){
|
||||
$this->cookies[$key] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->debug('Object headers',$this->resp_headers);
|
||||
|
||||
// check server status code to follow redirect
|
||||
if($this->status == 301 || $this->status == 302 ){
|
||||
if (empty($this->resp_headers['location'])){
|
||||
throw new HTTPClientException('Redirect but no Location Header found');
|
||||
}elseif($this->redirect_count == $this->max_redirect){
|
||||
throw new HTTPClientException('Maximum number of redirects exceeded');
|
||||
}else{
|
||||
// close the connection because we don't handle content retrieval here
|
||||
// that's the easiest way to clean up the connection
|
||||
fclose($socket);
|
||||
unset(self::$connections[$connectionId]);
|
||||
|
||||
$this->redirect_count++;
|
||||
$this->referer = $url;
|
||||
// handle non-RFC-compliant relative redirects
|
||||
if (!preg_match('/^http/i', $this->resp_headers['location'])){
|
||||
if($this->resp_headers['location'][0] != '/'){
|
||||
$this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].':'.$uri['port'].
|
||||
dirname($uri['path']).'/'.$this->resp_headers['location'];
|
||||
}else{
|
||||
$this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].':'.$uri['port'].
|
||||
$this->resp_headers['location'];
|
||||
}
|
||||
}
|
||||
// perform redirected request, always via GET (required by RFC)
|
||||
return $this->sendRequest($this->resp_headers['location'],array(),'GET');
|
||||
}
|
||||
}
|
||||
|
||||
// check if headers are as expected
|
||||
if($this->header_regexp && !preg_match($this->header_regexp,$r_headers))
|
||||
throw new HTTPClientException('The received headers did not match the given regexp');
|
||||
|
||||
//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'
|
||||
)
|
||||
) {
|
||||
$abort = false;
|
||||
do {
|
||||
$chunk_size = '';
|
||||
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
|
||||
$chunk_size = hexdec($chunk_size);
|
||||
|
||||
if($this->max_bodysize && $chunk_size+strlen($r_body) > $this->max_bodysize){
|
||||
if ($this->max_bodysize_abort)
|
||||
throw new HTTPClientException('Allowed response size exceeded');
|
||||
$this->error = 'Allowed response size exceeded';
|
||||
$chunk_size = $this->max_bodysize - strlen($r_body);
|
||||
$abort = true;
|
||||
}
|
||||
|
||||
if ($chunk_size > 0) {
|
||||
$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'])){
|
||||
/* RFC 2616
|
||||
* If a message is received with both a Transfer-Encoding header field and a Content-Length
|
||||
* header field, the latter MUST be ignored.
|
||||
*/
|
||||
|
||||
// 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;
|
||||
}else{
|
||||
$length = $this->resp_headers['content-length'];
|
||||
}
|
||||
|
||||
$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);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
// recheck body size, we might have read max_bodysize+1 or even the whole body, so we abort late here
|
||||
if($this->max_bodysize){
|
||||
if(strlen($r_body) > $this->max_bodysize){
|
||||
if ($this->max_bodysize_abort) {
|
||||
throw new HTTPClientException('Allowed response size exceeded');
|
||||
} else {
|
||||
$this->error = 'Allowed response size exceeded';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (HTTPClientException $err) {
|
||||
$this->error = $err->getMessage();
|
||||
if ($err->getCode())
|
||||
$this->status = $err->getCode();
|
||||
unset(self::$connections[$connectionId]);
|
||||
fclose($socket);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->keep_alive ||
|
||||
(isset($this->resp_headers['connection']) && $this->resp_headers['connection'] == 'Close')) {
|
||||
// close socket
|
||||
fclose($socket);
|
||||
unset(self::$connections[$connectionId]);
|
||||
}
|
||||
|
||||
// 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_body = @gzinflate(substr($r_body, 10));
|
||||
if($this->resp_body === false){
|
||||
$this->error = 'Failed to decompress gzip encoded content';
|
||||
$this->resp_body = $r_body;
|
||||
}
|
||||
}else{
|
||||
$this->resp_body = $r_body;
|
||||
}
|
||||
|
||||
$this->debug('response body',$this->resp_body);
|
||||
$this->redirect_count = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to establish a CONNECT tunnel via Proxy
|
||||
*
|
||||
* Protocol, Servername and Port will be stripped from the request URL when a successful CONNECT happened
|
||||
*
|
||||
* @param resource &$socket
|
||||
* @param string &$requesturl
|
||||
* @throws HTTPClientException when a tunnel is needed but could not be established
|
||||
* @return bool true if a tunnel was established
|
||||
*/
|
||||
protected function ssltunnel(&$socket, &$requesturl){
|
||||
if(!$this->useProxyForUrl($requesturl)) return false;
|
||||
$requestinfo = parse_url($requesturl);
|
||||
if($requestinfo['scheme'] != 'https') return false;
|
||||
if(!$requestinfo['port']) $requestinfo['port'] = 443;
|
||||
|
||||
// build request
|
||||
$request = "CONNECT {$requestinfo['host']}:{$requestinfo['port']} HTTP/1.0".HTTP_NL;
|
||||
$request .= "Host: {$requestinfo['host']}".HTTP_NL;
|
||||
if($this->proxy_user) {
|
||||
$request .= 'Proxy-Authorization: Basic '.base64_encode($this->proxy_user.':'.$this->proxy_pass).HTTP_NL;
|
||||
}
|
||||
$request .= HTTP_NL;
|
||||
|
||||
$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_headers .= $r_line;
|
||||
}while($r_line != "\r\n" && $r_line != "\n");
|
||||
|
||||
$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']);
|
||||
|
||||
// SSLv3 is broken, use only TLS connections.
|
||||
// @link https://bugs.php.net/69195
|
||||
if (PHP_VERSION_ID >= 50600 && PHP_VERSION_ID <= 50606) {
|
||||
$cryptoMethod = STREAM_CRYPTO_METHOD_TLS_CLIENT;
|
||||
} else {
|
||||
// actually means neither SSLv2 nor SSLv3
|
||||
$cryptoMethod = STREAM_CRYPTO_METHOD_SSLv23_CLIENT;
|
||||
}
|
||||
|
||||
if (@stream_socket_enable_crypto($socket, true, $cryptoMethod)) {
|
||||
$requesturl = $requestinfo['path'].
|
||||
(!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 establish secure proxy connection', -150);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely write data to a socket
|
||||
*
|
||||
* @param resource $socket An open socket handle
|
||||
* @param string $data The data to write
|
||||
* @param string $message Description of what is being read
|
||||
* @throws HTTPClientException
|
||||
*
|
||||
* @author Tom N Harris <tnharris@whoopdedo.org>
|
||||
*/
|
||||
protected function sendData($socket, $data, $message) {
|
||||
// send request
|
||||
$towrite = strlen($data);
|
||||
$written = 0;
|
||||
while($written < $towrite){
|
||||
// check timeout
|
||||
$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))
|
||||
throw new HTTPClientException("Socket disconnected while writing $message");
|
||||
|
||||
// select parameters
|
||||
$sel_r = null;
|
||||
$sel_w = array($socket);
|
||||
$sel_e = null;
|
||||
// wait for stream ready or timeout (1sec)
|
||||
if(@stream_select($sel_r,$sel_w,$sel_e,1) === false){
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// write to stream
|
||||
$nbytes = fwrite($socket, substr($data,$written,4096));
|
||||
if($nbytes === false)
|
||||
throw new HTTPClientException("Failed writing to socket while sending $message", -100);
|
||||
$written += $nbytes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely read data from a socket
|
||||
*
|
||||
* Reads up to a given number of bytes or throws an exception if the
|
||||
* response times out or ends prematurely.
|
||||
*
|
||||
* @param resource $socket An open socket handle in non-blocking mode
|
||||
* @param int $nbytes Number of bytes to read
|
||||
* @param string $message Description of what is being read
|
||||
* @param bool $ignore_eof End-of-file is not an error if this is set
|
||||
* @throws HTTPClientException
|
||||
* @return string
|
||||
*
|
||||
* @author Tom N Harris <tnharris@whoopdedo.org>
|
||||
*/
|
||||
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;
|
||||
if ($time_used > $this->timeout)
|
||||
throw new HTTPClientException(
|
||||
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");
|
||||
break;
|
||||
}
|
||||
|
||||
if ($to_read > 0) {
|
||||
// select parameters
|
||||
$sel_r = array($socket);
|
||||
$sel_w = null;
|
||||
$sel_e = null;
|
||||
// wait for stream ready or timeout (1sec)
|
||||
if(@stream_select($sel_r,$sel_w,$sel_e,1) === false){
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
$bytes = fread($socket, $to_read);
|
||||
if($bytes === false)
|
||||
throw new HTTPClientException("Failed reading from socket while reading $message", -100);
|
||||
$r_data .= $bytes;
|
||||
$to_read -= strlen($bytes);
|
||||
}
|
||||
} while ($to_read > 0 && strlen($r_data) < $nbytes);
|
||||
return $r_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely read a \n-terminated line from a socket
|
||||
*
|
||||
* Always returns a complete line, including the terminating \n.
|
||||
*
|
||||
* @param resource $socket An open socket handle in non-blocking mode
|
||||
* @param string $message Description of what is being read
|
||||
* @throws HTTPClientException
|
||||
* @return string
|
||||
*
|
||||
* @author Tom N Harris <tnharris@whoopdedo.org>
|
||||
*/
|
||||
protected function readLine($socket, $message) {
|
||||
$r_data = '';
|
||||
do {
|
||||
$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);
|
||||
if(feof($socket))
|
||||
throw new HTTPClientException("Premature End of File (socket) while reading $message");
|
||||
|
||||
// select parameters
|
||||
$sel_r = array($socket);
|
||||
$sel_w = null;
|
||||
$sel_e = null;
|
||||
// wait for stream ready or timeout (1sec)
|
||||
if(@stream_select($sel_r,$sel_w,$sel_e,1) === false){
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
$r_data = fgets($socket, 1024);
|
||||
} while (!preg_match('/\n$/',$r_data));
|
||||
return $r_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* print debug info
|
||||
*
|
||||
* Uses _debug_text or _debug_html depending on the SAPI name
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
* @param string $info
|
||||
* @param mixed $var
|
||||
*/
|
||||
protected function debug($info,$var=null){
|
||||
if(!$this->debug) return;
|
||||
if(php_sapi_name() == 'cli'){
|
||||
$this->debugText($info, $var);
|
||||
}else{
|
||||
$this->debugHtml($info, $var);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* print debug info as HTML
|
||||
*
|
||||
* @param string $info
|
||||
* @param mixed $var
|
||||
*/
|
||||
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);
|
||||
$content = htmlspecialchars(ob_get_contents());
|
||||
ob_end_clean();
|
||||
print '<pre>'.$content.'</pre>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* prints debug info as plain text
|
||||
*
|
||||
* @param string $info
|
||||
* @param mixed $var
|
||||
*/
|
||||
protected function debugText($info, $var=null){
|
||||
print '*'.$info.'* '.($this->time() - $this->start)."s\n";
|
||||
if(!is_null($var)) print_r($var);
|
||||
print "\n-----------------------------------------------\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current timestamp in microsecond resolution
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
protected static function time(){
|
||||
list($usec, $sec) = explode(" ", microtime());
|
||||
return ((float)$usec + (float)$sec);
|
||||
}
|
||||
|
||||
/**
|
||||
* convert given header string to Header array
|
||||
*
|
||||
* All Keys are lowercased.
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
* @param string $string
|
||||
* @return array
|
||||
*/
|
||||
protected function parseHeaders($string){
|
||||
$headers = array();
|
||||
$lines = explode("\n",$string);
|
||||
array_shift($lines); //skip first line (status)
|
||||
foreach($lines as $line){
|
||||
@list($key, $val) = explode(':',$line,2);
|
||||
$key = trim($key);
|
||||
$val = trim($val);
|
||||
$key = strtolower($key);
|
||||
if(!$key) continue;
|
||||
if(isset($headers[$key])){
|
||||
if(is_array($headers[$key])){
|
||||
$headers[$key][] = $val;
|
||||
}else{
|
||||
$headers[$key] = array($headers[$key],$val);
|
||||
}
|
||||
}else{
|
||||
$headers[$key] = $val;
|
||||
}
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert given header array to header string
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
* @param array $headers
|
||||
* @return string
|
||||
*/
|
||||
protected function buildHeaders($headers){
|
||||
$string = '';
|
||||
foreach($headers as $key => $value){
|
||||
if($value === '') continue;
|
||||
$string .= $key.': '.$value.HTTP_NL;
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* get cookies as http header string
|
||||
*
|
||||
* @author Andreas Goetz <cpuidle@gmx.de>
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getCookies(){
|
||||
$headers = '';
|
||||
foreach ($this->cookies as $key => $val){
|
||||
$headers .= "$key=$val; ";
|
||||
}
|
||||
$headers = substr($headers, 0, -2);
|
||||
if ($headers) $headers = "Cookie: $headers".HTTP_NL;
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode data for posting
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
protected function postEncode($data){
|
||||
return http_build_query($data,'','&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode data for posting using multipart encoding
|
||||
*
|
||||
* @fixme use of urlencode might be wrong here
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
protected function postMultipartEncode($data){
|
||||
$boundary = '--'.$this->boundary;
|
||||
$out = '';
|
||||
foreach($data as $key => $val){
|
||||
$out .= $boundary.HTTP_NL;
|
||||
if(!is_array($val)){
|
||||
$out .= 'Content-Disposition: form-data; name="'.urlencode($key).'"'.HTTP_NL;
|
||||
$out .= HTTP_NL; // end of headers
|
||||
$out .= $val;
|
||||
$out .= HTTP_NL;
|
||||
}else{
|
||||
$out .= 'Content-Disposition: form-data; name="'.urlencode($key).'"';
|
||||
if($val['filename']) $out .= '; filename="'.urlencode($val['filename']).'"';
|
||||
$out .= HTTP_NL;
|
||||
if($val['mimetype']) $out .= 'Content-Type: '.$val['mimetype'].HTTP_NL;
|
||||
$out .= HTTP_NL; // end of headers
|
||||
$out .= $val['body'];
|
||||
$out .= HTTP_NL;
|
||||
}
|
||||
}
|
||||
$out .= "$boundary--".HTTP_NL;
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique identifier for a connection.
|
||||
*
|
||||
* @param string $server
|
||||
* @param string $port
|
||||
* @return string unique identifier
|
||||
*/
|
||||
protected function uniqueConnectionId($server, $port) {
|
||||
return "$server:$port";
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the Proxy be used for the given URL?
|
||||
*
|
||||
* Checks the exceptions
|
||||
*
|
||||
* @param string $url
|
||||
* @return bool
|
||||
*/
|
||||
protected function useProxyForUrl($url) {
|
||||
return $this->proxy_host && (!$this->proxy_except || !preg_match('/' . $this->proxy_except . '/i', $url));
|
||||
}
|
||||
}
|
10
ap23/web/doku/inc/HTTP/HTTPClientException.php
Normal file
10
ap23/web/doku/inc/HTTP/HTTPClientException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\HTTP;
|
||||
|
||||
use Exception;
|
||||
|
||||
class HTTPClientException extends Exception
|
||||
{
|
||||
|
||||
}
|
1135
ap23/web/doku/inc/IXR_Library.php
Normal file
1135
ap23/web/doku/inc/IXR_Library.php
Normal file
File diff suppressed because it is too large
Load Diff
29
ap23/web/doku/inc/Input/Get.php
Normal file
29
ap23/web/doku/inc/Input/Get.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
287
ap23/web/doku/inc/Input/Input.php
Normal file
287
ap23/web/doku/inc/Input/Input.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
30
ap23/web/doku/inc/Input/Post.php
Normal file
30
ap23/web/doku/inc/Input/Post.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
19
ap23/web/doku/inc/Input/Server.php
Normal file
19
ap23/web/doku/inc/Input/Server.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
3188
ap23/web/doku/inc/JpegMeta.php
Normal file
3188
ap23/web/doku/inc/JpegMeta.php
Normal file
File diff suppressed because it is too large
Load Diff
777
ap23/web/doku/inc/Mailer.class.php
Normal file
777
ap23/web/doku/inc/Mailer.class.php
Normal file
@@ -0,0 +1,777 @@
|
||||
<?php
|
||||
/**
|
||||
* A class to build and send multi part mails (with HTML content and embedded
|
||||
* attachments). All mails are assumed to be in UTF-8 encoding.
|
||||
*
|
||||
* Attachments are handled in memory so this shouldn't be used to send huge
|
||||
* files, but then again mail shouldn't be used to send huge files either.
|
||||
*
|
||||
* @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");
|
||||
#define('MAILHEADER_ASCIIONLY',1);
|
||||
|
||||
/**
|
||||
* Mail Handling
|
||||
*/
|
||||
class Mailer {
|
||||
|
||||
protected $headers = array();
|
||||
protected $attach = array();
|
||||
protected $html = '';
|
||||
protected $text = '';
|
||||
|
||||
protected $boundary = '';
|
||||
protected $partid = '';
|
||||
protected $sendparam = null;
|
||||
|
||||
protected $allowhtml = true;
|
||||
|
||||
protected $replacements = array('text'=> array(), 'html' => array());
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Initializes the boundary strings, part counters and token replacements
|
||||
*/
|
||||
public function __construct() {
|
||||
global $conf;
|
||||
/* @var Input $INPUT */
|
||||
global $INPUT;
|
||||
|
||||
$server = parse_url(DOKU_URL, PHP_URL_HOST);
|
||||
if(strpos($server,'.') === false) $server .= '.localhost';
|
||||
|
||||
$this->partid = substr(md5(uniqid(mt_rand(), true)),0, 8).'@'.$server;
|
||||
$this->boundary = '__________'.md5(uniqid(mt_rand(), true));
|
||||
|
||||
$listid = implode('.', array_reverse(explode('/', DOKU_BASE))).$server;
|
||||
$listid = strtolower(trim($listid, '.'));
|
||||
|
||||
$this->allowhtml = (bool)$conf['htmlmail'];
|
||||
|
||||
// add some default headers for mailfiltering FS#2247
|
||||
if(!empty($conf['mailreturnpath'])) {
|
||||
$this->setHeader('Return-Path', $conf['mailreturnpath']);
|
||||
}
|
||||
$this->setHeader('X-Mailer', 'DokuWiki');
|
||||
$this->setHeader('X-DokuWiki-User', $INPUT->server->str('REMOTE_USER'));
|
||||
$this->setHeader('X-DokuWiki-Title', $conf['title']);
|
||||
$this->setHeader('X-DokuWiki-Server', $server);
|
||||
$this->setHeader('X-Auto-Response-Suppress', 'OOF');
|
||||
$this->setHeader('List-Id', $conf['title'].' <'.$listid.'>');
|
||||
$this->setHeader('Date', date('r'), false);
|
||||
|
||||
$this->prepareTokenReplacements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a file
|
||||
*
|
||||
* @param string $path Path to the file to attach
|
||||
* @param string $mime Mimetype of the attached file
|
||||
* @param string $name The filename to use
|
||||
* @param string $embed Unique key to reference this file from the HTML part
|
||||
*/
|
||||
public function attachFile($path, $mime, $name = '', $embed = '') {
|
||||
if(!$name) {
|
||||
$name = \dokuwiki\Utf8\PhpString::basename($path);
|
||||
}
|
||||
|
||||
$this->attach[] = array(
|
||||
'data' => file_get_contents($path),
|
||||
'mime' => $mime,
|
||||
'name' => $name,
|
||||
'embed' => $embed
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a file
|
||||
*
|
||||
* @param string $data The file contents to attach
|
||||
* @param string $mime Mimetype of the attached file
|
||||
* @param string $name The filename to use
|
||||
* @param string $embed Unique key to reference this file from the HTML part
|
||||
*/
|
||||
public function attachContent($data, $mime, $name = '', $embed = '') {
|
||||
if(!$name) {
|
||||
list(, $ext) = explode('/', $mime);
|
||||
$name = count($this->attach).".$ext";
|
||||
}
|
||||
|
||||
$this->attach[] = array(
|
||||
'data' => $data,
|
||||
'mime' => $mime,
|
||||
'name' => $name,
|
||||
'embed' => $embed
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function to automatically embed images referenced in HTML templates
|
||||
*
|
||||
* @param array $matches
|
||||
* @return string placeholder
|
||||
*/
|
||||
protected function autoEmbedCallBack($matches) {
|
||||
static $embeds = 0;
|
||||
$embeds++;
|
||||
|
||||
// get file and mime type
|
||||
$media = cleanID($matches[1]);
|
||||
list(, $mime) = mimetype($media);
|
||||
$file = mediaFN($media);
|
||||
if(!file_exists($file)) return $matches[0]; //bad reference, keep as is
|
||||
|
||||
// attach it and set placeholder
|
||||
$this->attachFile($file, $mime, '', 'autoembed'.$embeds);
|
||||
return '%%autoembed'.$embeds.'%%';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an arbitrary header to the mail
|
||||
*
|
||||
* If an empy value is passed, the header is removed
|
||||
*
|
||||
* @param string $header the header name (no trailing colon!)
|
||||
* @param string|string[] $value the value of the header
|
||||
* @param bool $clean remove all non-ASCII chars and line feeds?
|
||||
*/
|
||||
public function setHeader($header, $value, $clean = true) {
|
||||
$header = str_replace(' ', '-', ucwords(strtolower(str_replace('-', ' ', $header)))); // streamline casing
|
||||
if($clean) {
|
||||
$header = preg_replace('/[^a-zA-Z0-9_ \-\.\+\@]+/', '', $header);
|
||||
$value = preg_replace('/[^a-zA-Z0-9_ \-\.\+\@<>]+/', '', $value);
|
||||
}
|
||||
|
||||
// empty value deletes
|
||||
if(is_array($value)){
|
||||
$value = array_map('trim', $value);
|
||||
$value = array_filter($value);
|
||||
if(!$value) $value = '';
|
||||
}else{
|
||||
$value = trim($value);
|
||||
}
|
||||
if($value === '') {
|
||||
if(isset($this->headers[$header])) unset($this->headers[$header]);
|
||||
} else {
|
||||
$this->headers[$header] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set additional parameters to be passed to sendmail
|
||||
*
|
||||
* Whatever is set here is directly passed to PHP's mail() command as last
|
||||
* parameter. Depending on the PHP setup this might break mailing alltogether
|
||||
*
|
||||
* @param string $param
|
||||
*/
|
||||
public function setParameters($param) {
|
||||
$this->sendparam = $param;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text and HTML body and apply replacements
|
||||
*
|
||||
* This function applies a whole bunch of default replacements in addition
|
||||
* to the ones specified as parameters
|
||||
*
|
||||
* If you pass the HTML part or HTML replacements yourself you have to make
|
||||
* sure you encode all HTML special chars correctly
|
||||
*
|
||||
* @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 (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
|
||||
*/
|
||||
public function setBody($text, $textrep = null, $htmlrep = null, $html = null, $wrap = true) {
|
||||
|
||||
$htmlrep = (array)$htmlrep;
|
||||
$textrep = (array)$textrep;
|
||||
|
||||
// create HTML from text if not given
|
||||
if($html === null) {
|
||||
$html = $text;
|
||||
$html = hsc($html);
|
||||
$html = preg_replace('/^----+$/m', '<hr >', $html);
|
||||
$html = nl2br($html);
|
||||
}
|
||||
if($wrap) {
|
||||
$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, $wrapper);
|
||||
}
|
||||
|
||||
if(strpos($text, '@EMAILSIGNATURE@') === false) {
|
||||
$text .= '@EMAILSIGNATURE@';
|
||||
}
|
||||
|
||||
// copy over all replacements missing for HTML (autolink URLs)
|
||||
foreach($textrep as $key => $value) {
|
||||
if(isset($htmlrep[$key])) continue;
|
||||
if(media_isexternal($value)) {
|
||||
$htmlrep[$key] = '<a href="'.hsc($value).'">'.hsc($value).'</a>';
|
||||
} else {
|
||||
$htmlrep[$key] = hsc($value);
|
||||
}
|
||||
}
|
||||
|
||||
// embed media from templates
|
||||
$html = preg_replace_callback(
|
||||
'/@MEDIA\(([^\)]+)\)@/',
|
||||
array($this, 'autoEmbedCallBack'), $html
|
||||
);
|
||||
|
||||
// add default token replacements
|
||||
$trep = array_merge($this->replacements['text'], (array)$textrep);
|
||||
$hrep = array_merge($this->replacements['html'], (array)$htmlrep);
|
||||
|
||||
// Apply replacements
|
||||
foreach($trep as $key => $substitution) {
|
||||
$text = str_replace('@'.strtoupper($key).'@', $substitution, $text);
|
||||
}
|
||||
foreach($hrep as $key => $substitution) {
|
||||
$html = str_replace('@'.strtoupper($key).'@', $substitution, $html);
|
||||
}
|
||||
|
||||
$this->setHTML($html);
|
||||
$this->setText($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HTML part of the mail
|
||||
*
|
||||
* Placeholders can be used to reference embedded attachments
|
||||
*
|
||||
* You probably want to use setBody() instead
|
||||
*
|
||||
* @param string $html
|
||||
*/
|
||||
public function setHTML($html) {
|
||||
$this->html = $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the plain text part of the mail
|
||||
*
|
||||
* You probably want to use setBody() instead
|
||||
*
|
||||
* @param string $text
|
||||
*/
|
||||
public function setText($text) {
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the To: recipients
|
||||
*
|
||||
* @see cleanAddress
|
||||
* @param string|string[] $address Multiple adresses separated by commas or as array
|
||||
*/
|
||||
public function to($address) {
|
||||
$this->setHeader('To', $address, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Cc: recipients
|
||||
*
|
||||
* @see cleanAddress
|
||||
* @param string|string[] $address Multiple adresses separated by commas or as array
|
||||
*/
|
||||
public function cc($address) {
|
||||
$this->setHeader('Cc', $address, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Bcc: recipients
|
||||
*
|
||||
* @see cleanAddress
|
||||
* @param string|string[] $address Multiple adresses separated by commas or as array
|
||||
*/
|
||||
public function bcc($address) {
|
||||
$this->setHeader('Bcc', $address, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the From: address
|
||||
*
|
||||
* This is set to $conf['mailfrom'] when not specified so you shouldn't need
|
||||
* to call this function
|
||||
*
|
||||
* @see cleanAddress
|
||||
* @param string $address from address
|
||||
*/
|
||||
public function from($address) {
|
||||
$this->setHeader('From', $address, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the mail's Subject: header
|
||||
*
|
||||
* @param string $subject the mail subject
|
||||
*/
|
||||
public function subject($subject) {
|
||||
$this->headers['Subject'] = $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a clean name which can be safely used in mail address
|
||||
* fields. That means the name will be enclosed in '"' if it includes
|
||||
* a '"' or a ','. Also a '"' will be escaped as '\"'.
|
||||
*
|
||||
* @param string $name the name to clean-up
|
||||
* @see cleanAddress
|
||||
*/
|
||||
public function getCleanName($name) {
|
||||
$name = trim($name, ' \t"');
|
||||
$name = str_replace('"', '\"', $name, $count);
|
||||
if ($count > 0 || strpos($name, ',') !== false) {
|
||||
$name = '"'.$name.'"';
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an email address header with correct encoding
|
||||
*
|
||||
* Unicode characters will be deaccented and encoded base64
|
||||
* for headers. Addresses may not contain Non-ASCII data!
|
||||
*
|
||||
* If @$addresses is a string then it will be split into multiple
|
||||
* addresses. Addresses must be separated by a comma. If the display
|
||||
* name includes a comma then it MUST be properly enclosed by '"' to
|
||||
* prevent spliting at the wrong point.
|
||||
*
|
||||
* Example:
|
||||
* cc("föö <foo@bar.com>, me@somewhere.com","TBcc");
|
||||
* to("foo, Dr." <foo@bar.com>, me@somewhere.com");
|
||||
*
|
||||
* @param string|string[] $addresses Multiple adresses separated by commas or as array
|
||||
* @return false|string the prepared header (can contain multiple lines)
|
||||
*/
|
||||
public function cleanAddress($addresses) {
|
||||
$headers = '';
|
||||
if(!is_array($addresses)){
|
||||
$count = preg_match_all('/\s*(?:("[^"]*"[^,]+),*)|([^,]+)\s*,*/', $addresses, $matches, PREG_SET_ORDER);
|
||||
$addresses = array();
|
||||
if ($count !== false && is_array($matches)) {
|
||||
foreach ($matches as $match) {
|
||||
array_push($addresses, rtrim($match[0], ','));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach($addresses as $part) {
|
||||
$part = preg_replace('/[\r\n\0]+/', ' ', $part); // remove attack vectors
|
||||
$part = trim($part);
|
||||
|
||||
// parse address
|
||||
if(preg_match('#(.*?)<(.*?)>#', $part, $matches)) {
|
||||
$text = trim($matches[1]);
|
||||
$addr = $matches[2];
|
||||
} else {
|
||||
$text = '';
|
||||
$addr = $part;
|
||||
}
|
||||
// skip empty ones
|
||||
if(empty($addr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// FIXME: is there a way to encode the localpart of a emailaddress?
|
||||
if(!\dokuwiki\Utf8\Clean::isASCII($addr)) {
|
||||
msg(hsc("E-Mail address <$addr> is not ASCII"), -1, __LINE__, __FILE__, MSG_ADMINS_ONLY);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!mail_isvalid($addr)) {
|
||||
msg(hsc("E-Mail address <$addr> is not valid"), -1, __LINE__, __FILE__, MSG_ADMINS_ONLY);
|
||||
continue;
|
||||
}
|
||||
|
||||
// text was given
|
||||
if(!empty($text) && !isWindows()) { // No named recipients for To: in Windows (see FS#652)
|
||||
// add address quotes
|
||||
$addr = "<$addr>";
|
||||
|
||||
if(defined('MAILHEADER_ASCIIONLY')) {
|
||||
$text = \dokuwiki\Utf8\Clean::deaccent($text);
|
||||
$text = \dokuwiki\Utf8\Clean::strip($text);
|
||||
}
|
||||
|
||||
if(strpos($text, ',') !== false || !\dokuwiki\Utf8\Clean::isASCII($text)) {
|
||||
$text = '=?UTF-8?B?'.base64_encode($text).'?=';
|
||||
}
|
||||
} else {
|
||||
$text = '';
|
||||
}
|
||||
|
||||
// add to header comma seperated
|
||||
if($headers != '') {
|
||||
$headers .= ', ';
|
||||
}
|
||||
$headers .= $text.' '.$addr;
|
||||
}
|
||||
|
||||
$headers = trim($headers);
|
||||
if(empty($headers)) return false;
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare the mime multiparts for all attachments
|
||||
*
|
||||
* Replaces placeholders in the HTML with the correct CIDs
|
||||
*
|
||||
* @return string mime multiparts
|
||||
*/
|
||||
protected function prepareAttachments() {
|
||||
$mime = '';
|
||||
$part = 1;
|
||||
// embedded attachments
|
||||
foreach($this->attach as $media) {
|
||||
$media['name'] = str_replace(':', '_', cleanID($media['name'], true));
|
||||
|
||||
// create content id
|
||||
$cid = 'part'.$part.'.'.$this->partid;
|
||||
|
||||
// replace wildcards
|
||||
if($media['embed']) {
|
||||
$this->html = str_replace('%%'.$media['embed'].'%%', 'cid:'.$cid, $this->html);
|
||||
}
|
||||
|
||||
$mime .= '--'.$this->boundary.MAILHEADER_EOL;
|
||||
$mime .= $this->wrappedHeaderLine('Content-Type', $media['mime'].'; id="'.$cid.'"');
|
||||
$mime .= $this->wrappedHeaderLine('Content-Transfer-Encoding', 'base64');
|
||||
$mime .= $this->wrappedHeaderLine('Content-ID',"<$cid>");
|
||||
if($media['embed']) {
|
||||
$mime .= $this->wrappedHeaderLine('Content-Disposition', 'inline; filename='.$media['name']);
|
||||
} else {
|
||||
$mime .= $this->wrappedHeaderLine('Content-Disposition', 'attachment; filename='.$media['name']);
|
||||
}
|
||||
$mime .= MAILHEADER_EOL; //end of headers
|
||||
$mime .= chunk_split(base64_encode($media['data']), 74, MAILHEADER_EOL);
|
||||
|
||||
$part++;
|
||||
}
|
||||
return $mime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the body and handles multi part mails
|
||||
*
|
||||
* Needs to be called before prepareHeaders!
|
||||
*
|
||||
* @return string the prepared mail body, false on errors
|
||||
*/
|
||||
protected function prepareBody() {
|
||||
|
||||
// no HTML mails allowed? remove HTML body
|
||||
if(!$this->allowhtml) {
|
||||
$this->html = '';
|
||||
}
|
||||
|
||||
// check for body
|
||||
if(!$this->text && !$this->html) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add general headers
|
||||
$this->headers['MIME-Version'] = '1.0';
|
||||
|
||||
$body = '';
|
||||
|
||||
if(!$this->html && !count($this->attach)) { // we can send a simple single part message
|
||||
$this->headers['Content-Type'] = 'text/plain; charset=UTF-8';
|
||||
$this->headers['Content-Transfer-Encoding'] = 'base64';
|
||||
$body .= chunk_split(base64_encode($this->text), 72, MAILHEADER_EOL);
|
||||
} else { // multi part it is
|
||||
$body .= "This is a multi-part message in MIME format.".MAILHEADER_EOL;
|
||||
|
||||
// prepare the attachments
|
||||
$attachments = $this->prepareAttachments();
|
||||
|
||||
// do we have alternative text content?
|
||||
if($this->text && $this->html) {
|
||||
$this->headers['Content-Type'] = 'multipart/alternative;'.MAILHEADER_EOL.
|
||||
' boundary="'.$this->boundary.'XX"';
|
||||
$body .= '--'.$this->boundary.'XX'.MAILHEADER_EOL;
|
||||
$body .= 'Content-Type: text/plain; charset=UTF-8'.MAILHEADER_EOL;
|
||||
$body .= 'Content-Transfer-Encoding: base64'.MAILHEADER_EOL;
|
||||
$body .= MAILHEADER_EOL;
|
||||
$body .= chunk_split(base64_encode($this->text), 72, MAILHEADER_EOL);
|
||||
$body .= '--'.$this->boundary.'XX'.MAILHEADER_EOL;
|
||||
$body .= 'Content-Type: multipart/related;'.MAILHEADER_EOL.
|
||||
' boundary="'.$this->boundary.'";'.MAILHEADER_EOL.
|
||||
' type="text/html"'.MAILHEADER_EOL;
|
||||
$body .= MAILHEADER_EOL;
|
||||
}
|
||||
|
||||
$body .= '--'.$this->boundary.MAILHEADER_EOL;
|
||||
$body .= 'Content-Type: text/html; charset=UTF-8'.MAILHEADER_EOL;
|
||||
$body .= 'Content-Transfer-Encoding: base64'.MAILHEADER_EOL;
|
||||
$body .= MAILHEADER_EOL;
|
||||
$body .= chunk_split(base64_encode($this->html), 72, MAILHEADER_EOL);
|
||||
$body .= MAILHEADER_EOL;
|
||||
$body .= $attachments;
|
||||
$body .= '--'.$this->boundary.'--'.MAILHEADER_EOL;
|
||||
|
||||
// close open multipart/alternative boundary
|
||||
if($this->text && $this->html) {
|
||||
$body .= '--'.$this->boundary.'XX--'.MAILHEADER_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup and encode the headers array
|
||||
*/
|
||||
protected function cleanHeaders() {
|
||||
global $conf;
|
||||
|
||||
// clean up addresses
|
||||
if(empty($this->headers['From'])) $this->from($conf['mailfrom']);
|
||||
$addrs = array('To', 'From', 'Cc', 'Bcc', 'Reply-To', 'Sender');
|
||||
foreach($addrs as $addr) {
|
||||
if(isset($this->headers[$addr])) {
|
||||
$this->headers[$addr] = $this->cleanAddress($this->headers[$addr]);
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($this->headers['Subject'])) {
|
||||
// add prefix to subject
|
||||
if(empty($conf['mailprefix'])) {
|
||||
if(\dokuwiki\Utf8\PhpString::strlen($conf['title']) < 20) {
|
||||
$prefix = '['.$conf['title'].']';
|
||||
} else {
|
||||
$prefix = '['.\dokuwiki\Utf8\PhpString::substr($conf['title'], 0, 20).'...]';
|
||||
}
|
||||
} else {
|
||||
$prefix = '['.$conf['mailprefix'].']';
|
||||
}
|
||||
$len = strlen($prefix);
|
||||
if(substr($this->headers['Subject'], 0, $len) != $prefix) {
|
||||
$this->headers['Subject'] = $prefix.' '.$this->headers['Subject'];
|
||||
}
|
||||
|
||||
// encode subject
|
||||
if(defined('MAILHEADER_ASCIIONLY')) {
|
||||
$this->headers['Subject'] = \dokuwiki\Utf8\Clean::deaccent($this->headers['Subject']);
|
||||
$this->headers['Subject'] = \dokuwiki\Utf8\Clean::strip($this->headers['Subject']);
|
||||
}
|
||||
if(!\dokuwiki\Utf8\Clean::isASCII($this->headers['Subject'])) {
|
||||
$this->headers['Subject'] = '=?UTF-8?B?'.base64_encode($this->headers['Subject']).'?=';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a complete, EOL terminated header line, wraps it if necessary
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $val
|
||||
* @return string line
|
||||
*/
|
||||
protected function wrappedHeaderLine($key, $val){
|
||||
return wordwrap("$key: $val", 78, MAILHEADER_EOL.' ').MAILHEADER_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a string from the headers array
|
||||
*
|
||||
* @returns string the headers
|
||||
*/
|
||||
protected function prepareHeaders() {
|
||||
$headers = '';
|
||||
foreach($this->headers as $key => $val) {
|
||||
if ($val === '' || $val === null) continue;
|
||||
$headers .= $this->wrappedHeaderLine($key, $val);
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* return a full email with all headers
|
||||
*
|
||||
* This is mainly intended for debugging and testing but could also be
|
||||
* used for MHT exports
|
||||
*
|
||||
* @return string the mail, false on errors
|
||||
*/
|
||||
public function dump() {
|
||||
$this->cleanHeaders();
|
||||
$body = $this->prepareBody();
|
||||
if($body === false) return false;
|
||||
$headers = $this->prepareHeaders();
|
||||
|
||||
return $headers.MAILHEADER_EOL.$body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare default token replacement strings
|
||||
*
|
||||
* Populates the '$replacements' property.
|
||||
* Should be called by the class constructor
|
||||
*/
|
||||
protected function prepareTokenReplacements() {
|
||||
global $INFO;
|
||||
global $conf;
|
||||
/* @var Input $INPUT */
|
||||
global $INPUT;
|
||||
global $lang;
|
||||
|
||||
$ip = clientIP();
|
||||
$cip = gethostsbyaddrs($ip);
|
||||
$name = isset($INFO) ? $INFO['userinfo']['name'] : '';
|
||||
$mail = isset($INFO) ? $INFO['userinfo']['mail'] : '';
|
||||
|
||||
$this->replacements['text'] = array(
|
||||
'DATE' => dformat(),
|
||||
'BROWSER' => $INPUT->server->str('HTTP_USER_AGENT'),
|
||||
'IPADDRESS' => $ip,
|
||||
'HOSTNAME' => $cip,
|
||||
'TITLE' => $conf['title'],
|
||||
'DOKUWIKIURL' => DOKU_URL,
|
||||
'USER' => $INPUT->server->str('REMOTE_USER'),
|
||||
'NAME' => $name,
|
||||
'MAIL' => $mail
|
||||
);
|
||||
$signature = str_replace(
|
||||
'@DOKUWIKIURL@',
|
||||
$this->replacements['text']['DOKUWIKIURL'],
|
||||
$lang['email_signature_text']
|
||||
);
|
||||
$this->replacements['text']['EMAILSIGNATURE'] = "\n-- \n" . $signature . "\n";
|
||||
|
||||
$this->replacements['html'] = array(
|
||||
'DATE' => '<i>' . hsc(dformat()) . '</i>',
|
||||
'BROWSER' => hsc($INPUT->server->str('HTTP_USER_AGENT')),
|
||||
'IPADDRESS' => '<code>' . hsc($ip) . '</code>',
|
||||
'HOSTNAME' => '<code>' . hsc($cip) . '</code>',
|
||||
'TITLE' => hsc($conf['title']),
|
||||
'DOKUWIKIURL' => '<a href="' . DOKU_URL . '">' . DOKU_URL . '</a>',
|
||||
'USER' => hsc($INPUT->server->str('REMOTE_USER')),
|
||||
'NAME' => hsc($name),
|
||||
'MAIL' => '<a href="mailto:"' . hsc($mail) . '">' .
|
||||
hsc($mail) . '</a>'
|
||||
);
|
||||
$signature = $lang['email_signature_text'];
|
||||
if(!empty($lang['email_signature_html'])) {
|
||||
$signature = $lang['email_signature_html'];
|
||||
}
|
||||
$signature = str_replace(
|
||||
array(
|
||||
'@DOKUWIKIURL@',
|
||||
"\n"
|
||||
),
|
||||
array(
|
||||
$this->replacements['html']['DOKUWIKIURL'],
|
||||
'<br />'
|
||||
),
|
||||
$signature
|
||||
);
|
||||
$this->replacements['html']['EMAILSIGNATURE'] = $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the mail
|
||||
*
|
||||
* Call this after all data was set
|
||||
*
|
||||
* @triggers MAIL_MESSAGE_SEND
|
||||
* @return bool true if the mail was successfully passed to the MTA
|
||||
*/
|
||||
public function send() {
|
||||
global $lang;
|
||||
$success = false;
|
||||
|
||||
// prepare hook data
|
||||
$data = array(
|
||||
// pass the whole mail class to plugin
|
||||
'mail' => $this,
|
||||
// pass references for backward compatibility
|
||||
'to' => &$this->headers['To'],
|
||||
'cc' => &$this->headers['Cc'],
|
||||
'bcc' => &$this->headers['Bcc'],
|
||||
'from' => &$this->headers['From'],
|
||||
'subject' => &$this->headers['Subject'],
|
||||
'body' => &$this->text,
|
||||
'params' => &$this->sendparam,
|
||||
'headers' => '', // plugins shouldn't use this
|
||||
// signal if we mailed successfully to AFTER event
|
||||
'success' => &$success,
|
||||
);
|
||||
|
||||
// do our thing if BEFORE hook approves
|
||||
$evt = new Event('MAIL_MESSAGE_SEND', $data);
|
||||
if($evt->advise_before(true)) {
|
||||
// clean up before using the headers
|
||||
$this->cleanHeaders();
|
||||
|
||||
// any recipients?
|
||||
if(trim($this->headers['To']) === '' &&
|
||||
trim($this->headers['Cc']) === '' &&
|
||||
trim($this->headers['Bcc']) === ''
|
||||
) return false;
|
||||
|
||||
// The To: header is special
|
||||
if(array_key_exists('To', $this->headers)) {
|
||||
$to = (string)$this->headers['To'];
|
||||
unset($this->headers['To']);
|
||||
} else {
|
||||
$to = '';
|
||||
}
|
||||
|
||||
// so is the subject
|
||||
if(array_key_exists('Subject', $this->headers)) {
|
||||
$subject = (string)$this->headers['Subject'];
|
||||
unset($this->headers['Subject']);
|
||||
} else {
|
||||
$subject = '';
|
||||
}
|
||||
|
||||
// make the body
|
||||
$body = $this->prepareBody();
|
||||
if($body === false) return false;
|
||||
|
||||
// cook the headers
|
||||
$headers = $this->prepareHeaders();
|
||||
// add any headers set by legacy plugins
|
||||
if(trim($data['headers'])) {
|
||||
$headers .= MAILHEADER_EOL.trim($data['headers']);
|
||||
}
|
||||
|
||||
if(!function_exists('mail')){
|
||||
$emsg = $lang['email_fail'] . $subject;
|
||||
error_log($emsg);
|
||||
msg(hsc($emsg), -1, __LINE__, __FILE__, MSG_MANAGERS_ONLY);
|
||||
$evt->advise_after();
|
||||
return false;
|
||||
}
|
||||
|
||||
// send the thing
|
||||
if($this->sendparam === null) {
|
||||
$success = @mail($to, $subject, $body, $headers);
|
||||
} else {
|
||||
$success = @mail($to, $subject, $body, $headers, $this->sendparam);
|
||||
}
|
||||
}
|
||||
// any AFTER actions?
|
||||
$evt->advise_after();
|
||||
return $success;
|
||||
}
|
||||
}
|
84
ap23/web/doku/inc/Manifest.php
Normal file
84
ap23/web/doku/inc/Manifest.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki;
|
||||
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
class Manifest
|
||||
{
|
||||
public function sendManifest()
|
||||
{
|
||||
$manifest = retrieveConfig('manifest', 'jsonToArray');
|
||||
|
||||
global $conf;
|
||||
|
||||
$manifest['scope'] = DOKU_REL;
|
||||
|
||||
if (empty($manifest['name'])) {
|
||||
$manifest['name'] = $conf['title'];
|
||||
}
|
||||
|
||||
if (empty($manifest['short_name'])) {
|
||||
$manifest['short_name'] = $conf['title'];
|
||||
}
|
||||
|
||||
if (empty($manifest['description'])) {
|
||||
$manifest['description'] = $conf['tagline'];
|
||||
}
|
||||
|
||||
if (empty($manifest['start_url'])) {
|
||||
$manifest['start_url'] = DOKU_REL;
|
||||
}
|
||||
|
||||
$styleUtil = new \dokuwiki\StyleUtils();
|
||||
$styleIni = $styleUtil->cssStyleini();
|
||||
$replacements = $styleIni['replacements'];
|
||||
|
||||
if (empty($manifest['background_color'])) {
|
||||
$manifest['background_color'] = $replacements['__background__'];
|
||||
}
|
||||
|
||||
if (empty($manifest['theme_color'])) {
|
||||
$manifest['theme_color'] = !empty($replacements['__theme_color__'])
|
||||
? $replacements['__theme_color__']
|
||||
: $replacements['__background_alt__'];
|
||||
}
|
||||
|
||||
if (empty($manifest['icons'])) {
|
||||
$manifest['icons'] = [];
|
||||
if (file_exists(mediaFN(':wiki:favicon.ico'))) {
|
||||
$url = ml(':wiki:favicon.ico', '', true, '', true);
|
||||
$manifest['icons'][] = [
|
||||
'src' => $url,
|
||||
'sizes' => '16x16',
|
||||
];
|
||||
}
|
||||
|
||||
$look = [
|
||||
':wiki:logo.svg',
|
||||
':logo.svg',
|
||||
':wiki:dokuwiki.svg'
|
||||
];
|
||||
|
||||
foreach ($look as $svgLogo) {
|
||||
|
||||
$svgLogoFN = mediaFN($svgLogo);
|
||||
|
||||
if (file_exists($svgLogoFN)) {
|
||||
$url = ml($svgLogo, '', true, '', true);
|
||||
$manifest['icons'][] = [
|
||||
'src' => $url,
|
||||
'sizes' => '17x17 512x512',
|
||||
'type' => 'image/svg+xml',
|
||||
];
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Event::createAndTrigger('MANIFEST_SEND', $manifest);
|
||||
|
||||
header('Content-Type: application/manifest+json');
|
||||
echo json_encode($manifest);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user