Reworked API definition
This cleans up the API: * no more compatibility with obsolete wiki API * no more difference between wiki.* and dokuwiki.* calls -> core.* * use of optional parameters avoids double definitions * use Response objects for complex results * always use named primitives as input * major cleanup of docblock descriptions
This commit is contained in:
parent
dd7472d3f8
commit
6cce3332fb
|
@ -53,7 +53,7 @@ abstract class RemotePlugin extends Plugin
|
|||
}
|
||||
|
||||
// add to result
|
||||
$result[$method_name] = new ApiCall([$this, $method_name]);
|
||||
$result[$method_name] = new ApiCall([$this, $method_name], 'plugins');
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
|
|
@ -76,9 +76,9 @@ class Api
|
|||
{
|
||||
if (!$this->coreMethods) {
|
||||
if ($apiCore === null) {
|
||||
$this->coreMethods = (new ApiCore($this))->getRemoteInfo();
|
||||
$this->coreMethods = (new ApiCore($this))->getMethods();
|
||||
} else {
|
||||
$this->coreMethods = $apiCore->getRemoteInfo();
|
||||
$this->coreMethods = $apiCore->getMethods();
|
||||
}
|
||||
}
|
||||
return $this->coreMethods;
|
||||
|
|
|
@ -4,6 +4,11 @@ namespace dokuwiki\Remote;
|
|||
|
||||
|
||||
use dokuwiki\Remote\OpenApiDoc\DocBlockMethod;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionException;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
use RuntimeException;
|
||||
|
||||
class ApiCall
|
||||
{
|
||||
|
@ -13,6 +18,9 @@ class ApiCall
|
|||
/** @var bool Whether this call can be called without authentication */
|
||||
protected bool $isPublic = false;
|
||||
|
||||
/** @var string The category this call belongs to */
|
||||
protected string $category;
|
||||
|
||||
/** @var DocBlockMethod The meta data of this call as parsed from its doc block */
|
||||
protected $docs;
|
||||
|
||||
|
@ -20,14 +28,16 @@ class ApiCall
|
|||
* Make the given method available as an API call
|
||||
*
|
||||
* @param string|array $method Either [object,'method'] or 'function'
|
||||
* @param string $category The category this call belongs to
|
||||
*/
|
||||
public function __construct($method)
|
||||
public function __construct($method, $category = '')
|
||||
{
|
||||
if (!is_callable($method)) {
|
||||
throw new \InvalidArgumentException('Method is not callable');
|
||||
throw new InvalidArgumentException('Method is not callable');
|
||||
}
|
||||
|
||||
$this->method = $method;
|
||||
$this->category = $category;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,13 +68,13 @@ class ApiCall
|
|||
if ($this->docs === null) {
|
||||
try {
|
||||
if (is_array($this->method)) {
|
||||
$reflect = new \ReflectionMethod($this->method[0], $this->method[1]);
|
||||
$reflect = new ReflectionMethod($this->method[0], $this->method[1]);
|
||||
} else {
|
||||
$reflect = new \ReflectionFunction($this->method);
|
||||
$reflect = new ReflectionFunction($this->method);
|
||||
}
|
||||
$this->docs = new DocBlockMethod($reflect);
|
||||
} catch (\ReflectionException $e) {
|
||||
throw new \RuntimeException('Failed to parse API method documentation', 0, $e);
|
||||
} catch (ReflectionException $e) {
|
||||
throw new RuntimeException('Failed to parse API method documentation', 0, $e);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -123,6 +133,14 @@ class ApiCall
|
|||
return $this->getDocs()->getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCategory(): string
|
||||
{
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts named arguments to positional arguments
|
||||
*
|
||||
|
@ -134,7 +152,7 @@ class ApiCall
|
|||
{
|
||||
$args = [];
|
||||
|
||||
foreach (array_keys($this->docs->getParameters()) as $arg) {
|
||||
foreach (array_keys($this->getDocs()->getParameters()) as $arg) {
|
||||
if (isset($params[$arg])) {
|
||||
$args[] = $params[$arg];
|
||||
} else {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -100,10 +100,11 @@ class OpenAPIGenerator
|
|||
$this->typeToSchema($retType)
|
||||
);
|
||||
|
||||
return [
|
||||
$definition = [
|
||||
'operationId' => $method,
|
||||
'summary' => $call->getSummary(),
|
||||
'description' => $description,
|
||||
'tags' => [PhpString::ucwords($call->getCategory())],
|
||||
'requestBody' => [
|
||||
'required' => true,
|
||||
'content' => [
|
||||
|
@ -142,6 +143,22 @@ class OpenAPIGenerator
|
|||
],
|
||||
]
|
||||
];
|
||||
|
||||
if ($call->isPublic()) {
|
||||
$definition['security'] = [
|
||||
new \stdClass(),
|
||||
];
|
||||
$definition['description'] = 'This method is public and does not require authentication. ' .
|
||||
"\n\n" . $definition['description'];
|
||||
}
|
||||
|
||||
if ($call->getDocs()->getTag('deprecated')) {
|
||||
$definition['deprecated'] = true;
|
||||
$definition['description'] = '**This method is deprecated.** ' . $call->getDocs()->getTag('deprecated')[0] .
|
||||
"\n\n" . $definition['description'];
|
||||
}
|
||||
|
||||
return $definition;
|
||||
}
|
||||
|
||||
protected function getMethodArguments($args)
|
||||
|
@ -153,23 +170,35 @@ class OpenAPIGenerator
|
|||
}
|
||||
|
||||
$props = [];
|
||||
$reqs = [];
|
||||
$schema = [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
'required' => &$reqs,
|
||||
'properties' => &$props
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($args as $name => $info) {
|
||||
$example = $this->generateExample($name, $info['type']->getOpenApiType());
|
||||
|
||||
$description = $info['description'];
|
||||
if ($info['optional'] && isset($info['default'])) {
|
||||
$description .= ' [_default: `' . json_encode($info['default']) . '`_]';
|
||||
}
|
||||
|
||||
$props[$name] = array_merge(
|
||||
[
|
||||
'description' => $info['description'],
|
||||
'description' => $description,
|
||||
'examples' => [$example],
|
||||
'required' => !$info['optional'],
|
||||
],
|
||||
$this->typeToSchema($info['type'])
|
||||
);
|
||||
if (!$info['optional']) $reqs[] = $name;
|
||||
}
|
||||
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
|
@ -177,12 +206,15 @@ class OpenAPIGenerator
|
|||
{
|
||||
switch ($type) {
|
||||
case 'integer':
|
||||
if ($name === 'rev') return 0;
|
||||
if ($name === 'revision') return 0;
|
||||
if ($name === 'timestamp') return time() - 60 * 24 * 30 * 2;
|
||||
return 42;
|
||||
case 'boolean':
|
||||
return true;
|
||||
case 'string':
|
||||
if($name === 'page') return 'playground:playground';
|
||||
if($name === 'media') return 'wiki:dokuwiki-128.png';
|
||||
if ($name === 'page') return 'playground:playground';
|
||||
if ($name === 'media') return 'wiki:dokuwiki-128.png';
|
||||
return 'some-' . $name;
|
||||
case 'array':
|
||||
return ['some-' . $name, 'other-' . $name];
|
||||
|
@ -226,12 +258,12 @@ class OpenAPIGenerator
|
|||
];
|
||||
|
||||
// if a sub type is known, define the items
|
||||
if($schema['type'] === 'array' && $type->getSubType()) {
|
||||
if ($schema['type'] === 'array' && $type->getSubType()) {
|
||||
$schema['items'] = $this->typeToSchema($type->getSubType());
|
||||
}
|
||||
|
||||
// if this is an object, define the properties
|
||||
if($schema['type'] === 'object') {
|
||||
if ($schema['type'] === 'object') {
|
||||
try {
|
||||
$baseType = $type->getBaseType();
|
||||
$doc = new DocBlockClass(new \ReflectionClass($baseType));
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace dokuwiki\Remote\Response;
|
||||
|
||||
class Link extends ApiResponse
|
||||
{
|
||||
/** @var string The type of this link: `internal`, `external` or `interwiki` */
|
||||
public $type;
|
||||
/** @var string The wiki page this link points to, same as `href` for external links */
|
||||
public $page;
|
||||
/** @var string A hyperlink pointing to the linked target */
|
||||
public $href;
|
||||
|
||||
|
||||
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->type = $data['type'] ?? '';
|
||||
$this->page = $data['page'] ?? '';
|
||||
$this->href = $data['href'] ?? '';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace dokuwiki\Remote\Response;
|
||||
|
||||
/**
|
||||
* Represents a single media revision in the wiki.
|
||||
*/
|
||||
class Media extends ApiResponse
|
||||
{
|
||||
/** @var string The media ID */
|
||||
public $id;
|
||||
/** @var int The media revision aka last modified timestamp */
|
||||
public $revision;
|
||||
/** @var int The page size in bytes */
|
||||
public $size;
|
||||
/** @var int The current user's permissions for this file */
|
||||
public $perms;
|
||||
/** @var bool Wether this is an image file */
|
||||
public $isimage;
|
||||
/** @var string MD5 sum over the file's content (if available and requested) */
|
||||
public $hash;
|
||||
|
||||
/** @inheritdoc */
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->id = cleanID($data['id'] ?? '');
|
||||
if ($this->id === '') {
|
||||
throw new \InvalidArgumentException('Missing id');
|
||||
}
|
||||
if (!media_exists($this->id)) {
|
||||
throw new \InvalidArgumentException('Media does not exist');
|
||||
}
|
||||
|
||||
// FIXME this isn't really managing the difference between old and current revs correctly
|
||||
|
||||
$this->revision = (int)($data['rev'] ?? $data['mtime'] ?? @filemtime(mediaFN($this->id)));
|
||||
$this->size = (int)($data['size'] ?? @filesize(mediaFN($this->id)));
|
||||
$this->perms = $data['perm'] ?? auth_quickaclcheck($this->id);
|
||||
$this->isimage = (bool)($data['isimg'] ?? false);
|
||||
$this->hash = $data['hash'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the hash for this page
|
||||
*
|
||||
* This is a heavy operation and should only be called when needed.
|
||||
*/
|
||||
public function calculateHash()
|
||||
{
|
||||
if (!media_exists($this->id)) return;
|
||||
$this->hash = md5(io_readFile(mediaFN($this->id)));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace dokuwiki\Remote\Response;
|
||||
|
||||
class MediaRevision extends ApiResponse
|
||||
{
|
||||
/** @var string The media ID */
|
||||
public $id;
|
||||
/** @var int The revision (timestamp) of this change */
|
||||
public $revision;
|
||||
/** @var string The author of this change */
|
||||
public $author;
|
||||
/** @var string The IP address from where this change was made */
|
||||
public $ip;
|
||||
/** @var string The summary of this change */
|
||||
public $summary;
|
||||
/** @var string The type of this change */
|
||||
public $type;
|
||||
/** @var int The change in bytes */
|
||||
public $sizechange;
|
||||
|
||||
/** @inheritdoc */
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->id = $data['id'];
|
||||
$this->revision = (int)($data['revision'] ?? 0);
|
||||
$this->author = $data['author'] ?? '';
|
||||
$this->ip = $data['ip'] ?? '';
|
||||
$this->summary = $data['summary'] ?? '';
|
||||
$this->type = $data['type'] ?? '';
|
||||
$this->sizechange = (int)($data['sizechange'] ?? 0);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace dokuwiki\Remote\Response;
|
||||
|
||||
use dokuwiki\ChangeLog\PageChangeLog;
|
||||
|
||||
/**
|
||||
* Represents a single page revision in the wiki.
|
||||
*/
|
||||
|
@ -17,8 +19,10 @@ class Page extends ApiResponse
|
|||
public $title;
|
||||
/** @var int The current user's permissions for this page */
|
||||
public $perms;
|
||||
/** @var string MD5 sum over the page's content (only if requested) */
|
||||
/** @var string MD5 sum over the page's content (if available and requested) */
|
||||
public $hash;
|
||||
/** @var string The author of this page revision (if available and requested) */
|
||||
public $author;
|
||||
|
||||
/** @inheritdoc */
|
||||
public function __construct($data)
|
||||
|
@ -27,12 +31,18 @@ class Page extends ApiResponse
|
|||
if ($this->id === '') {
|
||||
throw new \InvalidArgumentException('Missing id');
|
||||
}
|
||||
if (!page_exists($this->id)) {
|
||||
throw new \InvalidArgumentException('Page does not exist');
|
||||
}
|
||||
|
||||
$this->revision = (int)($data['rev'] ?? $data['lastModified'] ?? @filemtime(wikiFN($this->id)));
|
||||
// FIXME this isn't really managing the difference between old and current revs correctly
|
||||
|
||||
$this->revision = (int)($data['rev'] ?? @filemtime(wikiFN($this->id)));
|
||||
$this->size = (int)($data['size'] ?? @filesize(wikiFN($this->id)));
|
||||
$this->title = $data['title'] ?? $this->retrieveTitle();
|
||||
$this->perms = $data['perm'] ?? auth_quickaclcheck($this->id);
|
||||
$this->hash = $data['hash'] ?? '';
|
||||
$this->author = $data['author'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,4 +65,26 @@ class Page extends ApiResponse
|
|||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the hash for this page
|
||||
*
|
||||
* This is a heavy operation and should only be called when needed.
|
||||
*/
|
||||
public function calculateHash()
|
||||
{
|
||||
if (!page_exists($this->id)) return;
|
||||
$this->hash = md5(io_readFile(wikiFN($this->id)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the author of this page
|
||||
*/
|
||||
public function retrieveAuthor()
|
||||
{
|
||||
if (!page_exists($this->id)) return;
|
||||
|
||||
$pagelog = new PageChangeLog($this->id, 1024);
|
||||
$info = $pagelog->getRevisionInfo($this->revision);
|
||||
$this->author = is_array($info) ? ($info['user'] ?: $info['ip']) : null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace dokuwiki\Remote\Response;
|
||||
|
||||
/**
|
||||
* Represents a page found by a search
|
||||
*/
|
||||
class PageHit extends Page
|
||||
{
|
||||
/** @var int The number of hits this result got */
|
||||
public $score;
|
||||
|
||||
/** @var string The HTML formatted snippet in which the search term was found (if available) */
|
||||
public $snippet;
|
||||
|
||||
/** @var string Not available for search results */
|
||||
public $hash;
|
||||
|
||||
/** @var string Not available for search results */
|
||||
public $author;
|
||||
|
||||
/** @inheritdoc */
|
||||
public function __construct($data)
|
||||
{
|
||||
parent::__construct($data);
|
||||
|
||||
$this->snippet = $data['snippet'] ?? '';
|
||||
$this->score = (int)($data['score'] ?? 0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace dokuwiki\Remote\Response;
|
||||
|
||||
class PageRevision extends ApiResponse
|
||||
{
|
||||
/** @var string The page ID */
|
||||
public $id;
|
||||
/** @var int The revision (timestamp) of this change */
|
||||
public $revision;
|
||||
/** @var string The author of this change */
|
||||
public $author;
|
||||
/** @var string The IP address from where this change was made */
|
||||
public $ip;
|
||||
/** @var string The summary of this change */
|
||||
public $summary;
|
||||
/** @var string The type of this change */
|
||||
public $type;
|
||||
/** @var int The change in bytes */
|
||||
public $sizechange;
|
||||
|
||||
/** @inheritdoc */
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->id = $data['id'];
|
||||
$this->revision = (int)($data['revision'] ?? 0);
|
||||
$this->author = $data['author'] ?? '';
|
||||
$this->ip = $data['ip'] ?? '';
|
||||
$this->summary = $data['summary'] ?? '';
|
||||
$this->type = $data['type'] ?? '';
|
||||
$this->sizechange = (int)($data['sizechange'] ?? 0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace dokuwiki\Remote\Response;
|
||||
|
||||
/**
|
||||
* Represents a user
|
||||
*/
|
||||
class User extends ApiResponse
|
||||
{
|
||||
/** @var string The login name of the user */
|
||||
public $login;
|
||||
/** @var string The full name of the user */
|
||||
public $name;
|
||||
/** @var string The email address of the user */
|
||||
public $mail;
|
||||
/** @var array The groups the user is in */
|
||||
public $groups;
|
||||
/** @var bool Whether the user is a super user */
|
||||
public bool $isAdmin;
|
||||
/** @var bool Whether the user is a manager */
|
||||
public bool $isManager;
|
||||
|
||||
/** @inheritdoc */
|
||||
public function __construct($data)
|
||||
{
|
||||
global $USERINFO;
|
||||
global $INPUT;
|
||||
$this->login = $INPUT->server->str('REMOTE_USER');
|
||||
$this->name = $USERINFO['name'];
|
||||
$this->mail = $USERINFO['mail'];
|
||||
$this->groups = $USERINFO['grps'];
|
||||
|
||||
$this->isAdmin = auth_isAdmin($this->login, $this->groups);
|
||||
$this->isManager = auth_isManager($this->login, $this->groups);
|
||||
}
|
||||
}
|
|
@ -154,8 +154,6 @@ function getVersionData()
|
|||
* If no version can be determined "snapshot? update version XX" is returned.
|
||||
* Where XX represents the update version number set in doku.php.
|
||||
*
|
||||
* For checking API compatibility, you should rather rely on dokuwiki.getXMLRPCAPIVersion
|
||||
*
|
||||
* @author Anika Henke <anika@selfthinker.org>
|
||||
* @return string The version string e.g. "Release 2023-04-04a"
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue