Compare commits

..

No commits in common. "master" and "v2.0.0" have entirely different histories.

35 changed files with 154 additions and 347 deletions

View file

@ -5,17 +5,10 @@ on: [push]
jobs:
build-test:
runs-on: ubuntu-latest
strategy:
matrix:
php-versions: ['7.4', '8.0', '8.4']
steps:
- uses: actions/checkout@v2
- name: Set up php${{ matrix.php-versons }}
uses: shivammathur/setup-php@master
- uses: actions/checkout@v1
- uses: php-actions/composer@v1
- uses: php-actions/phpunit@v1.0.0
with:
php-version: ${{ matrix.php-versions }}
- name: Install dependencies
run: composer i
- name: PHPUnit
run: ./vendor/phpunit/phpunit/phpunit -c tests/phpunit.xml
config: tests/phpunit.xml

View file

@ -1,41 +0,0 @@
name: Php Lint
on: [push, pull_request]
jobs:
php-linters:
runs-on: ubuntu-20.04
strategy:
matrix:
php-versions: ['7.4', '8.0', '8.4']
name: php${{ matrix.php-versions }} lint
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up php${{ matrix.php-versons }}
uses: shivammathur/setup-php@master
with:
php-version: ${{ matrix.php-versions }}
coverage: none
extensions: inotify
- name: Install dependencies
run: composer i
- name: Lint
run: composer run lint
php-cs-fixer:
name: php-cs check
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up php${{ matrix.php-versions }}
uses: shivammathur/setup-php@master
with:
php-version: 7.4
tools: composer:v1
coverage: none
extensions: inotify
- name: Install dependencies
run: composer i
- name: Run coding standards check
run: composer run cs:check

View file

@ -1,36 +0,0 @@
name: Php Static analysis
on: [push, pull_request]
jobs:
psalm:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up php
uses: shivammathur/setup-php@master
with:
php-version: 7.4
tools: composer:v1
coverage: none
extensions: redis
- name: Install dependencies
run: composer i
- name: Run coding standards check
run: composer run psalm
phpstan:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up php
uses: shivammathur/setup-php@master
with:
php-version: 7.4
tools: composer:v1
coverage: none
extensions: redis
- name: Install dependencies
run: composer i
- name: Run coding standards check
run: composer run psalm

1
.gitignore vendored
View file

@ -2,4 +2,3 @@ composer.lock
vendor
coverage.xml
.phpunit.result.cache
*.cache

View file

@ -1,15 +0,0 @@
<?php
$finder = PhpCsFixer\Finder::create()
->exclude('vendor')
->in(__DIR__)
;
return PhpCsFixer\Config::create()
->setRules([
'@PSR2' => true,
'array_syntax' => ['syntax' => 'short'],
'braces' => ['position_after_functions_and_oop_constructs' => 'same'],
'binary_operator_spaces' => ['align_double_arrow' => true, 'align_equals' => false],
])
->setIndent("\t")
->setFinder($finder)
;

View file

@ -9,14 +9,10 @@
}
],
"require": {
"php": ">=7.3 || >=8.0",
"php": ">=7.1",
"sabre/dav": "^4.0.0"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "^1.0",
"friendsofphp/php-cs-fixer": "^2",
"phpstan/phpstan": "^0.12",
"psalm/phar": "^4.3",
"phpunit/phpunit": "^8"
},
"autoload": {
@ -28,12 +24,5 @@
"psr-4": {
"SearchDAV\\Test\\": "tests/"
}
},
"scripts": {
"lint": "parallel-lint --exclude src --exclude vendor --exclude target --exclude build .",
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix",
"psalm": "psalm.phar",
"phpstan": "phpstan analyse --level 5 src"
}
}

View file

@ -1,15 +0,0 @@
<?xml version="1.0"?>
<psalm
errorLevel="3"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

View file

@ -21,14 +21,13 @@
namespace SearchDAV\Backend;
use Sabre\DAV\INode;
use SearchDAV\Query\Query;
interface ISearchBackend {
/**
* Get the path of the search arbiter of this backend
*
* The search arbiter is the URI that the client will send its SEARCH requests to
* The search arbiter is the URI that the client will send it's SEARCH requests to
* Note that this is not required to be the same as the search scopes which determine what to search in
*
* The returned value should be a path relative the root of the dav server.
@ -38,10 +37,10 @@ interface ISearchBackend {
*
* @return string
*/
public function getArbiterPath(): string;
public function getArbiterPath();
/**
* Whether the search backend supports search requests on this scope
* Whether or not the search backend supports search requests on this scope
*
* The scope defines the resource that it being searched, such as a folder or address book.
*
@ -55,7 +54,7 @@ interface ISearchBackend {
* @param string|null $path the path of the search scope relative to the dav server, or null if the scope is outside the dav server
* @return bool
*/
public function isValidScope(string $href, $depth, ?string $path): bool;
public function isValidScope($href, $depth, $path);
/**
* List the available properties that can be used in search
@ -69,7 +68,7 @@ interface ISearchBackend {
* @param string|null $path the path of the search scope relative to the dav server, or null if the scope is outside the dav server
* @return SearchPropertyDefinition[]
*/
public function getPropertyDefinitionsForScope(string $href, ?string $path): array;
public function getPropertyDefinitionsForScope($href, $path);
/**
* Preform the search request
@ -81,14 +80,5 @@ interface ISearchBackend {
* @param Query $query
* @return SearchResult[]
*/
public function search(Query $query): array;
/**
* Called by the search plugin once the nodes to be returned have been found.
* This can be used to more efficiently load the requested properties for the results.
*
* @param INode[] $nodes
* @param string[] $requestProperties
*/
public function preloadPropertyFor(array $nodes, array $requestProperties): void;
public function search(Query $query);
}

View file

@ -26,7 +26,6 @@ class SearchPropertyDefinition {
const DATATYPE_STRING = self::XS . 'string';
const DATATYPE_INTEGER = self::XS . 'integer';
const DATATYPE_NONNEGATIVE_INTEGER = self::XS . 'nonNegativeInteger';
const DATATYPE_NON_NEGATIVE_INTEGER = self::XS . 'nonNegativeInteger';
const DATATYPE_DECIMAL = self::XS . 'decimal';
const DATATYPE_DATETIME = self::XS . 'dateTime';
const DATATYPE_BOOLEAN = self::XS . 'boolean';
@ -49,13 +48,13 @@ class SearchPropertyDefinition {
* SearchProperty constructor.
*
* @param string $name the name and namespace of the property in clark notation
* @param bool $searchable whether this property can be used as part of a search query
* @param bool $selectable whether this property can be returned as part of a search result
* @param bool $sortable whether this property can be used to sort the search result
* @param bool $searchable whether or not this property can be used as part of a search query
* @param bool $selectable whether or not this property can be returned as part of a search result
* @param bool $sortable whether or not this property can be used to sort the search result
* @param string $dataType the datatype of the property, one of the SearchProperty::DATATYPE_ constants or any XSD datatype in clark notation
* @param bool $caseSensitive whether comparisons on the property are case-sensitive, only applies to string properties
* @param bool $caseSensitive whether or not comparisons on the property are case sensitive, only applies to string propertries
*/
public function __construct(string $name, bool $selectable, bool $searchable, bool $sortable, string $dataType = self::DATATYPE_STRING, bool $caseSensitive = true) {
public function __construct($name, $searchable, $selectable, $sortable, $dataType = self::DATATYPE_STRING, $caseSensitive = true) {
$this->searchable = $searchable;
$this->selectable = $selectable;
$this->sortable = $sortable;

View file

@ -21,6 +21,7 @@
namespace SearchDAV\Backend;
use Sabre\DAV\INode;
class SearchResult {
@ -35,7 +36,7 @@ class SearchResult {
* @param INode $node
* @param string $href
*/
public function __construct(INode $node, string $href) {
public function __construct(INode $node, $href) {
$this->node = $node;
$this->href = $href;
}

View file

@ -54,7 +54,7 @@ class DiscoverHandler {
$this->queryParser = $queryParser;
}
public function handelDiscoverRequest($xml, RequestInterface $request, ResponseInterface $response): bool {
public function handelDiscoverRequest($xml, RequestInterface $request, ResponseInterface $response) {
if (!isset($xml['{DAV:}basicsearch'])) {
$response->setStatus(400);
$response->setBody('Unexpected xml content for query-schema-discovery, expected basicsearch');
@ -97,7 +97,12 @@ class DiscoverHandler {
foreach ($propertyDefinitions as $propertyDefinition) {
$key = $this->hashDefinition($propertyDefinition);
if (!isset($groups[$key])) {
$groups[$key] = new PropDesc($propertyDefinition);
$desc = new PropDesc();
$desc->dataType = $propertyDefinition->dataType;
$desc->sortable = $propertyDefinition->sortable;
$desc->selectable = $propertyDefinition->selectable;
$desc->searchable = $propertyDefinition->searchable;
$groups[$key] = $desc;
}
$groups[$key]->properties[] = $propertyDefinition->name;
}

View file

@ -37,7 +37,7 @@ class PathHelper {
$this->server = $server;
}
public function getPathFromUri(string $uri): ?string {
public function getPathFromUri($uri): ?string {
if (strpos($uri, '://') === false) {
return $uri;
}

View file

@ -30,31 +30,29 @@ use SearchDAV\XML\Literal;
use SearchDAV\XML\Operator;
use SearchDAV\XML\Order;
use SearchDAV\XML\Scope;
use function Sabre\Xml\Deserializer\keyValue;
use function Sabre\Xml\Deserializer\repeatingElements;
class QueryParser extends Service {
public $namespaceMap = [
'DAV:' => 'd',
'http://sabredav.org/ns' => 's',
'DAV:' => 'd',
'http://sabredav.org/ns' => 's',
'http://www.w3.org/2001/XMLSchema' => 'xs',
SearchPlugin::SEARCHDAV_NS => 'sd'
SearchPlugin::SEARCHDAV_NS => 'sd'
];
public function __construct() {
$this->elementMap = [
'{DAV:}literal' => Literal::class,
'{DAV:}searchrequest' => Element\KeyValue::class,
'{DAV:}literal' => Literal::class,
'{DAV:}searchrequest' => Element\KeyValue::class,
'{DAV:}query-schema-discovery' => Element\KeyValue::class,
'{DAV:}basicsearch' => BasicSearch::class,
'{DAV:}select' => function (Reader $reader) {
return keyValue($reader, '{DAV:}scope')['{DAV:}prop'];
'{DAV:}basicsearch' => BasicSearch::class,
'{DAV:}select' => function (Reader $reader) {
return \Sabre\Xml\Deserializer\keyValue($reader, '{DAV:}scope')['{DAV:}prop'];
},
'{DAV:}from' => function (Reader $reader) {
return repeatingElements($reader, '{DAV:}scope');
return \Sabre\Xml\Deserializer\repeatingElements($reader, '{DAV:}scope');
},
'{DAV:}orderby' => function (Reader $reader) {
return repeatingElements($reader, '{DAV:}order');
return \Sabre\Xml\Deserializer\repeatingElements($reader, '{DAV:}order');
},
'{DAV:}scope' => Scope::class,
'{DAV:}where' => function (Reader $reader) {
@ -63,21 +61,20 @@ class QueryParser extends Service {
}, $reader->parseGetElements());
return (isset($operators[0])) ? $operators[0] : null;
},
'{DAV:}prop' => Element\Elements::class,
'{DAV:}order' => Order::class,
'{DAV:}eq' => Operator::class,
'{DAV:}gt' => Operator::class,
'{DAV:}gte' => Operator::class,
'{DAV:}lt' => Operator::class,
'{DAV:}lte' => Operator::class,
'{DAV:}and' => Operator::class,
'{DAV:}or' => Operator::class,
'{DAV:}like' => Operator::class,
'{DAV:}contains' => Operator::class,
'{DAV:}not' => Operator::class,
'{DAV:}prop' => Element\Elements::class,
'{DAV:}order' => Order::class,
'{DAV:}eq' => Operator::class,
'{DAV:}gt' => Operator::class,
'{DAV:}gte' => Operator::class,
'{DAV:}lt' => Operator::class,
'{DAV:}lte' => Operator::class,
'{DAV:}and' => Operator::class,
'{DAV:}or' => Operator::class,
'{DAV:}like' => Operator::class,
'{DAV:}contains' => Operator::class,
'{DAV:}not' => Operator::class,
'{DAV:}is-collection' => Operator::class,
'{DAV:}is-defined' => Operator::class,
'{DAV:}limit' => Limit::class,
'{DAV:}limit' => Limit::class,
];
}
}

View file

@ -22,7 +22,6 @@
namespace SearchDAV\DAV;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\DAV\Server;
use Sabre\HTTP\ResponseInterface;
@ -55,10 +54,10 @@ class SearchHandler {
$this->server = $server;
}
public function handleSearchRequest($xml, ResponseInterface $response): bool {
public function handleSearchRequest($xml, ResponseInterface $response) {
if (!isset($xml['{DAV:}basicsearch'])) {
$response->setStatus(400);
$response->setBody('Unexpected xml content for search request, expected basicsearch');
$response->setBody('Unexpected xml content for searchrequest, expected basicsearch');
return false;
}
/** @var BasicSearch $query */
@ -85,10 +84,8 @@ class SearchHandler {
$response->setBody($e->getMessage());
return false;
}
$data = $this->server->generateMultiStatus(iterator_to_array($this->getPropertiesIteratorResults(
$results,
$query->select
)), false);
$data = $this->server->generateMultiStatus(iterator_to_array($this->getPropertiesIteratorResults($results,
$query->select)), false);
$response->setBody($data);
return false;
}
@ -129,7 +126,7 @@ class SearchHandler {
/**
* @param \SearchDAV\XML\Operator $operator
* @param SearchPropertyDefinition[] $allProps
* @param array $allProps
* @return Operator
* @throws BadRequest
*/
@ -166,20 +163,16 @@ class SearchHandler {
* If a depth of 1 is requested child elements will also be returned.
*
* @param SearchResult[] $results
* @param string[] $propertyNames
* @param array $propertyNames
* @param int $depth
* @return \Iterator<array>
* @return \Iterator
*/
private function getPropertiesIteratorResults(array $results, array $propertyNames = [], int $depth = 0): \Iterator {
private function getPropertiesIteratorResults($results, $propertyNames = [], $depth = 0): \Iterator {
$propFindType = $propertyNames ? PropFind::NORMAL : PropFind::ALLPROPS;
$this->searchBackend->preloadPropertyFor(array_map(function (SearchResult $result): INode {
return $result->node;
}, $results), $propertyNames);
foreach ($results as $result) {
$node = $result->node;
$propFind = new PropFind($result->href, $propertyNames, $depth, $propFindType);
$propFind = new PropFind($result->href, (array)$propertyNames, $depth, $propFindType);
$r = $this->server->getPropertiesByNode($propFind, $node);
if ($r) {
$result = $propFind->getResultForMultiStatus();

View file

@ -34,6 +34,9 @@ use SearchDAV\XML\SupportedQueryGrammar;
class SearchPlugin extends ServerPlugin {
const SEARCHDAV_NS = 'https://github.com/icewind1991/SearchDAV/ns';
/** @var Server */
private $server;
/** @var ISearchBackend */
private $searchBackend;
@ -54,7 +57,8 @@ class SearchPlugin extends ServerPlugin {
$this->queryParser = new QueryParser();
}
public function initialize(Server $server): void {
public function initialize(Server $server) {
$this->server = $server;
$this->pathHelper = new PathHelper($server);
$this->search = new SearchHandler($this->searchBackend, $this->pathHelper, $server);
$this->discover = new DiscoverHandler($this->searchBackend, $this->pathHelper, $this->queryParser);
@ -63,7 +67,7 @@ class SearchPlugin extends ServerPlugin {
$server->on('propFind', [$this, 'propFindHandler']);
}
public function propFindHandler(PropFind $propFind, INode $node): void {
public function propFindHandler(PropFind $propFind, INode $node) {
if ($propFind->getPath() === $this->searchBackend->getArbiterPath()) {
$propFind->handle('{DAV:}supported-query-grammar-set', new SupportedQueryGrammar());
}
@ -72,11 +76,11 @@ class SearchPlugin extends ServerPlugin {
/**
* SEARCH is allowed for users files
*
* @param string $path
* @return string[]
* @param string $uri
* @return array
*/
public function getHTTPMethods($path): array {
$path = $this->pathHelper->getPathFromUri($path);
public function getHTTPMethods($uri) {
$path = $this->pathHelper->getPathFromUri($uri);
if ($this->searchBackend->getArbiterPath() === $path) {
return ['SEARCH'];
} else {
@ -84,16 +88,16 @@ class SearchPlugin extends ServerPlugin {
}
}
public function optionHandler(RequestInterface $request, ResponseInterface $response): void {
public function optionHandler(RequestInterface $request, ResponseInterface $response) {
if ($request->getPath() === $this->searchBackend->getArbiterPath()) {
$response->addHeader('DASL', '<DAV:basicsearch>');
}
}
public function searchHandler(RequestInterface $request, ResponseInterface $response): bool {
$contentType = $request->getHeader('Content-Type') ?? '';
public function searchHandler(RequestInterface $request, ResponseInterface $response) {
$contentType = $request->getHeader('Content-Type');
// Currently, we only support xml search queries
// Currently we only support xml search queries
if ((strpos($contentType, 'text/xml') === false) && (strpos($contentType, 'application/xml') === false)) {
return true;
}

View file

@ -21,6 +21,7 @@
namespace SearchDAV\Query;
class Limit {
/**
* @var integer

View file

@ -21,6 +21,7 @@
namespace SearchDAV\Query;
class Literal {
/**
* @var string|boolean|\DateTime|integer

View file

@ -41,15 +41,14 @@ class Operator {
* The type of operation, one of the Operator::OPERATION_* constants
*/
public $type;
/**
* @var (Literal|\SearchDAV\Backend\SearchPropertyDefinition|Operator)[]
* @var (Literal|SearchPropDefinition|Operation)[]
*
* The list of arguments for the operation
*
* - SearchPropDefinition: property for comparison
* - Literal: literal value for comparison
* - Operator: nested operation for and/or/not operations
* - Operation: nested operation for and/or/not operations
*
* Which type and what number of argument an Operator takes depends on the operator type.
*/
@ -59,7 +58,7 @@ class Operator {
* Operator constructor.
*
* @param string $type
* @param (Literal|\SearchDAV\Backend\SearchPropertyDefinition|Operator)[] $arguments
* @param array $arguments
*/
public function __construct(string $type = '', array $arguments = []) {
$this->type = $type;

View file

@ -21,6 +21,7 @@
namespace SearchDAV\Query;
use SearchDAV\Backend\SearchPropertyDefinition;
class Order {

View file

@ -21,6 +21,7 @@
namespace SearchDAV\Query;
use SearchDAV\Backend\SearchPropertyDefinition;
class Query {
@ -37,7 +38,7 @@ class Query {
*/
public $from;
/**
* @var ?Operator
* @var Operator
*
* The search operator, either a comparison ('gt', 'eq', ...) or a boolean operator ('and', 'or', 'not')
*/
@ -48,7 +49,7 @@ class Query {
* The list of order operations that should be used to order the results.
*
* Each order operations consists of a property to sort on and a sort direction.
* If more than one order operations are specified, the comparisons for ordering should
* If more then one order operations are specified, the comparisons for ordering should
* be applied in the order that the order operations are defined in with the earlier comparisons being
* more significant.
*/

View file

@ -21,6 +21,7 @@
namespace SearchDAV\Query;
class Scope {
/**
* @var string
@ -51,7 +52,7 @@ class Scope {
* @param int|string $depth
* @param string|null $path
*/
public function __construct(string $href = '', $depth = 1, ?string $path = null) {
public function __construct(string $href = '', $depth = 1, string $path = null) {
$this->href = $href;
$this->depth = $depth;
$this->path = $path;

View file

@ -24,7 +24,6 @@ namespace SearchDAV\XML;
use Sabre\Xml\ParseException;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
use function Sabre\Xml\Deserializer\keyValue;
/**
* The object representation of a search query made by the client
@ -43,7 +42,7 @@ class BasicSearch implements XmlDeserializable {
*/
public $from;
/**
* @var ?Operator
* @var Operator
*
* The search operator, either a comparison ('gt', 'eq', ...) or a boolean operator ('and', 'or', 'not')
*/
@ -66,33 +65,26 @@ class BasicSearch implements XmlDeserializable {
*/
public $limit;
public function __construct(array $select, array $from, ?Operator $where, array $orderBy, Limit $limit) {
$this->select = $select;
$this->from = $from;
$this->where = $where;
$this->orderBy = $orderBy;
$this->limit = $limit;
}
/**
* @param Reader $reader
* @return BasicSearch
* @throws ParseException
*/
public static function xmlDeserialize(Reader $reader): BasicSearch {
$elements = keyValue($reader);
static function xmlDeserialize(Reader $reader): BasicSearch {
$search = new self();
$elements = \Sabre\Xml\Deserializer\keyValue($reader);
if (!isset($elements['{DAV:}from'])) {
throw new ParseException('Missing {DAV:}from when parsing {DAV:}basicsearch');
}
return new BasicSearch(
$elements['{DAV:}select'] ?? [],
$elements['{DAV:}from'],
$elements['{DAV:}where'] ?? null,
$elements['{DAV:}orderby'] ?? [],
$elements['{DAV:}limit'] ?? new Limit()
);
$search->select = isset($elements['{DAV:}select']) ? $elements['{DAV:}select'] : [];
$search->from = $elements['{DAV:}from'];
$search->where = isset($elements['{DAV:}where']) ? $elements['{DAV:}where'] : null;
$search->orderBy = isset($elements['{DAV:}orderby']) ? $elements['{DAV:}orderby'] : [];
$search->limit = isset($elements['{DAV:}limit']) ? $elements['{DAV:}limit'] : new Limit();
return $search;
}
}

View file

@ -21,6 +21,7 @@
namespace SearchDAV\XML;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;
@ -37,10 +38,10 @@ class BasicSearchSchema implements XmlSerializable {
$this->properties = $properties;
}
public function xmlSerialize(Writer $writer): void {
$childs = array_map(function (PropDesc $propDesc) {
function xmlSerialize(Writer $writer) {
$childs = array_map(function(PropDesc $propDesc) {
return [
'name' => '{DAV:}propdesc',
'name' => '{DAV:}propdesc',
'value' => $propDesc
];
}, $this->properties);

View file

@ -24,16 +24,15 @@ namespace SearchDAV\XML;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
use SearchDAV\DAV\SearchPlugin;
use function Sabre\Xml\Deserializer\keyValue;
/**
* The limit and offset of a search query
*/
class Limit extends \SearchDAV\Query\Limit implements XmlDeserializable {
public static function xmlDeserialize(Reader $reader): Limit {
static function xmlDeserialize(Reader $reader): Limit {
$limit = new self();
$elements = keyValue($reader);
$elements = \Sabre\Xml\Deserializer\keyValue($reader);
$namespace = SearchPlugin::SEARCHDAV_NS;
$limit->maxResults = isset($elements['{DAV:}nresults']) ? $elements['{DAV:}nresults'] : 0;

View file

@ -21,11 +21,12 @@
namespace SearchDAV\XML;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
class Literal extends \SearchDAV\Query\Literal implements XmlDeserializable {
public static function xmlDeserialize(Reader $reader): Literal {
static function xmlDeserialize(Reader $reader): Literal {
$literal = new self();
if ($reader->isEmptyElement) {

View file

@ -33,7 +33,7 @@ class Operator implements XmlDeserializable {
*/
public $type;
/**
* @var (Literal|string|Operator)[]
* @var (Literal|string|Operation)[]
*
* The list of arguments for the operation
*
@ -49,17 +49,17 @@ class Operator implements XmlDeserializable {
* Operator constructor.
*
* @param string $type
* @param (Literal|string|Operator)[] $arguments
* @param array $arguments
*/
public function __construct(string $type = '', array $arguments = []) {
$this->type = $type;
$this->arguments = $arguments;
}
public static function xmlDeserialize(Reader $reader): Operator {
static function xmlDeserialize(Reader $reader): Operator {
$operator = new self();
$operator->type = $reader->getClark() ?? '';
$operator->type = $reader->getClark();
if ($reader->isEmptyElement) {
$reader->next();
return $operator;

View file

@ -21,9 +21,9 @@
namespace SearchDAV\XML;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
use function Sabre\Xml\Deserializer\keyValue;
class Order implements XmlDeserializable {
/**
@ -50,10 +50,10 @@ class Order implements XmlDeserializable {
$this->order = $order;
}
public static function xmlDeserialize(Reader $reader): Order {
static function xmlDeserialize(Reader $reader): Order {
$order = new self();
$childs = keyValue($reader);
$childs = \Sabre\Xml\Deserializer\keyValue($reader);
$order->order = array_key_exists('{DAV:}descending', $childs) ? \SearchDAV\Query\Order::DESC : \SearchDAV\Query\Order::ASC;
$order->property = $childs['{DAV:}prop'][0];

View file

@ -21,9 +21,9 @@
namespace SearchDAV\XML;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;
use SearchDAV\Backend\SearchPropertyDefinition;
class PropDesc implements XmlSerializable {
/**
@ -47,14 +47,7 @@ class PropDesc implements XmlSerializable {
*/
public $sortable;
public function __construct(SearchPropertyDefinition $propertyDefinition) {
$this->dataType = $propertyDefinition->dataType;
$this->sortable = $propertyDefinition->sortable;
$this->selectable = $propertyDefinition->selectable;
$this->searchable = $propertyDefinition->searchable;
}
public function xmlSerialize(Writer $writer): void {
function xmlSerialize(Writer $writer) {
$data = [
'{DAV:}dataType' => [$this->dataType => null]
];
@ -69,7 +62,7 @@ class PropDesc implements XmlSerializable {
}
$writer->write(array_map(function ($propName) {
return [
'name' => '{DAV:}prop',
'name' => '{DAV:}prop',
'value' => $propName
];
}, $this->properties));

View file

@ -21,9 +21,9 @@
namespace SearchDAV\XML;
use Sabre\DAV\Xml\Element\Response;
use Sabre\Xml\Writer;
use function Sabre\HTTP\encodePath;
class QueryDiscoverResponse extends Response {
/**
@ -38,23 +38,21 @@ class QueryDiscoverResponse extends Response {
* @param BasicSearchSchema|null $schema
* @param null|int|string $httpStatus
*/
public function __construct($href, ?BasicSearchSchema $schema = null, $httpStatus = null) {
if ($httpStatus !== null) {
$httpStatus = (string)$httpStatus;
}
function __construct($href, BasicSearchSchema $schema = null, $httpStatus = null) {
parent::__construct($href, [], $httpStatus);
$this->schema = $schema;
}
public function xmlSerialize(Writer $writer): void {
function xmlSerialize(Writer $writer) {
if ($status = $this->getHTTPStatus()) {
$writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]);
}
$writer->writeElement('{DAV:}href', encodePath($this->getHref()));
$writer->writeElement('{DAV:}href', \Sabre\HTTP\encodePath($this->getHref()));
if ($this->schema) {
$writer->writeElement('{DAV:}query-schema', [
'{DAV:}basicsearchschema' => $this->schema
'{DAV:}basicsearchschema' => $this->schema
]);
}
}

View file

@ -23,13 +23,12 @@ namespace SearchDAV\XML;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
use function Sabre\Xml\Deserializer\keyValue;
class Scope extends \SearchDAV\Query\Scope implements XmlDeserializable {
public static function xmlDeserialize(Reader $reader): Scope {
static function xmlDeserialize(Reader $reader): Scope {
$scope = new self();
$values = keyValue($reader);
$values = \Sabre\Xml\Deserializer\keyValue($reader);
$scope->href = $values['{DAV:}href'];
$scope->depth = $values['{DAV:}depth'];

View file

@ -21,16 +21,19 @@
namespace SearchDAV\XML;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;
class SupportedQueryGrammar implements XmlSerializable {
const GRAMMAR_BASIC_SEARCH = '{DAV:}basicsearch';
const GRAMMAR_BASICSEARCH = '{DAV:}basicsearch';
public function xmlSerialize(Writer $writer): void {
public $grammar = self::GRAMMAR_BASICSEARCH;
function xmlSerialize(Writer $writer) {
$writer->startElement('{DAV:}supported-query-grammar');
$writer->startElement('{DAV:}grammar');
$writer->startElement(self::GRAMMAR_BASIC_SEARCH);
$writer->startElement($this->grammar);
$writer->endElement();
$writer->endElement();
$writer->endElement();

View file

@ -30,29 +30,26 @@ use SearchDAV\XML\BasicSearch;
use SearchDAV\Backend\SearchPropertyDefinition;
class DummyBackend implements ISearchBackend {
public function getArbiterPath(): string {
public function getArbiterPath() {
return '';
}
public function isValidScope(string $href, $depth, ?string $path): bool {
public function isValidScope($href, $depth, $path) {
return true;
}
public function getPropertyDefinitionsForScope($href, $path): array {
public function getPropertyDefinitionsForScope($href, $path) {
return [
new SearchPropertyDefinition('{DAV:}getcontentlength', true, true, true, SearchPropertyDefinition::DATATYPE_NON_NEGATIVE_INTEGER),
new SearchPropertyDefinition('{DAV:}getcontentlength', true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true),
new SearchPropertyDefinition('{DAV:}displayname', true, true, true),
new SearchPropertyDefinition('{http://ns.nextcloud.com:}fileid', false, true, true, SearchPropertyDefinition::DATATYPE_NON_NEGATIVE_INTEGER),
new SearchPropertyDefinition('{http://ns.nextcloud.com:}fileid', false, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
];
}
public function search(Query $query): array {
public function search(Query $query) {
return [
new SearchResult(new SimpleFile('foo.txt', 'foobar', 'text/plain'), '/bar/foo.txt')
];
}
public function preloadPropertyFor(array $nodes, array $requestProperties): void {
}
}

View file

@ -26,7 +26,7 @@ use Sabre\DAV\Server;
use SearchDAV\DAV\PathHelper;
class PathHelperTest extends TestCase {
public function uriProvider() {
public function uriProvider(){
return [
['/', '', ''],
['/index.php/', 'foo', 'foo'],

View file

@ -33,6 +33,7 @@ use SearchDAV\XML\Order;
use SearchDAV\XML\Scope;
use SearchDAV\XML\SupportedQueryGrammar;
class QueryParserTest extends TestCase {
public function testParseBasicQuery() {
$query = file_get_contents(__DIR__ . '/basicquery.xml');

View file

@ -180,22 +180,12 @@ class SearchPluginTest extends TestCase {
$this->searchBackend->expects($this->once())
->method('getPropertyDefinitionsForScope')
->willReturn([
new SearchPropertyDefinition(
'{DAV:}getcontentlength',
true,
true,
true,
SearchPropertyDefinition::DATATYPE_NON_NEGATIVE_INTEGER
),
new SearchPropertyDefinition('{DAV:}getcontentlength', true, true, true,
SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true),
new SearchPropertyDefinition('{DAV:}displayname', true, true, true),
new SearchPropertyDefinition(
'{http://ns.nextcloud.com:}fileid',
false,
true,
true,
SearchPropertyDefinition::DATATYPE_NON_NEGATIVE_INTEGER
),
new SearchPropertyDefinition('{http://ns.nextcloud.com:}fileid', false, true, true,
SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
]);
$plugin->searchHandler($request, $response);
@ -284,13 +274,8 @@ class SearchPluginTest extends TestCase {
->method('isValidScope')
->willReturn(true);
$lengthProp = new SearchPropertyDefinition(
'{DAV:}getcontentlength',
true,
true,
true,
SearchPropertyDefinition::DATATYPE_NON_NEGATIVE_INTEGER
);
$lengthProp = new SearchPropertyDefinition('{DAV:}getcontentlength', true, true, true,
SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER);
$orderBy = [
new \SearchDAV\Query\Order($lengthProp, \SearchDAV\Query\Order::ASC),
];
@ -362,13 +347,8 @@ class SearchPluginTest extends TestCase {
->method('getArbiterPath')
->willReturn('foo');
$lengthProp = new SearchPropertyDefinition(
'{DAV:}getcontentlength',
true,
true,
true,
SearchPropertyDefinition::DATATYPE_NON_NEGATIVE_INTEGER
);
$lengthProp = new SearchPropertyDefinition('{DAV:}getcontentlength', true, true, true,
SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER);
$plugin = new SearchPlugin($this->searchBackend);
$server = new Server();
$plugin->initialize($server);
@ -503,13 +483,8 @@ class SearchPluginTest extends TestCase {
$this->searchBackend->expects($this->once())
->method('getPropertyDefinitionsForScope')
->willReturn([
new SearchPropertyDefinition(
'{http://ns.nextcloud.com:}fileid',
false,
true,
true,
SearchPropertyDefinition::DATATYPE_NON_NEGATIVE_INTEGER
),
new SearchPropertyDefinition('{http://ns.nextcloud.com:}fileid', false, true, true,
SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
]);
$plugin->searchHandler($request, $response);
@ -543,20 +518,10 @@ class SearchPluginTest extends TestCase {
$this->searchBackend->expects($this->any())
->method('getPropertyDefinitionsForScope')
->willReturn([
new SearchPropertyDefinition(
'{http://ns.nextcloud.com:}fileid',
false,
true,
true,
SearchPropertyDefinition::DATATYPE_NON_NEGATIVE_INTEGER
),
new SearchPropertyDefinition(
'{DAV:}getcontentlength',
true,
true,
true,
SearchPropertyDefinition::DATATYPE_NON_NEGATIVE_INTEGER
),
new SearchPropertyDefinition('{http://ns.nextcloud.com:}fileid', false, true, true,
SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
new SearchPropertyDefinition('{DAV:}getcontentlength', true, true, true,
SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
]);
$plugin->searchHandler($request, $response);
@ -590,24 +555,15 @@ class SearchPluginTest extends TestCase {
$this->searchBackend->expects($this->any())
->method('getPropertyDefinitionsForScope')
->willReturn([
new SearchPropertyDefinition(
'{http://ns.nextcloud.com:}fileid',
false,
true,
true,
SearchPropertyDefinition::DATATYPE_NON_NEGATIVE_INTEGER
),
new SearchPropertyDefinition(
'{DAV:}getcontentlength',
true,
true,
true,
SearchPropertyDefinition::DATATYPE_NON_NEGATIVE_INTEGER
),
new SearchPropertyDefinition('{http://ns.nextcloud.com:}fileid', false, true, true,
SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
new SearchPropertyDefinition('{DAV:}getcontentlength', true, true, true,
SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
]);
$plugin->searchHandler($request, $response);
$this->assertEquals(400, $response->getStatus());
}
}