diff options
33 files changed, 2718 insertions, 123 deletions
@@ -69,12 +69,8 @@ clean: rm -rf $(build_dir) rm -rf node_modules -# composer packages -composer: - composer install --prefer-dist - composer upgrade --prefer-dist -appstore: clean composer +appstore: clean mkdir -p $(sign_dir) rsync -a \ --exclude=/build \ diff --git a/composer.json b/composer.json deleted file mode 100644 index 8253a43..0000000 --- a/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "nextcloud/fulltextsearch", - "description": "Full text search framework for Nextcloud", - "minimum-stability": "stable", - "license": "agpl", - "config": { - "optimize-autoloader": true, - "classmap-authoritative": true, - "autoloader-suffix": "FullTextSearch" - }, - "authors": [ - { - "name": "Maxence Lange", - "email": "maxence@artificial-owl.com" - } - ], - "autoload": { - "psr-4": { - "OCA\\FullTextSearch\\": "lib/" - } - }, - "require": { - "artificial-owl/my-small-php-tools": "~23" - } -} diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 16163b4..0000000 --- a/composer.lock +++ /dev/null @@ -1,59 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "b52d6a5c9fb9be09368e44333c380806", - "packages": [ - { - "name": "artificial-owl/my-small-php-tools", - "version": "v23.0.2", - "source": { - "type": "git", - "url": "https://github.com/ArtificialOwl/my-small-php-tools.git", - "reference": "0246b20ebffabcb1e929c71d3b0f221086f72db1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ArtificialOwl/my-small-php-tools/zipball/0246b20ebffabcb1e929c71d3b0f221086f72db1", - "reference": "0246b20ebffabcb1e929c71d3b0f221086f72db1", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "ArtificialOwl\\MySmallPhpTools\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "AGPL-3.0-or-later" - ], - "authors": [ - { - "name": "Maxence Lange", - "email": "maxence@artificial-owl.com" - } - ], - "description": "My small PHP Tools", - "support": { - "issues": "https://github.com/ArtificialOwl/my-small-php-tools/issues", - "source": "https://github.com/ArtificialOwl/my-small-php-tools/tree/v23.0.2" - }, - "time": "2021-07-26T12:32:51+00:00" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": [], - "platform-dev": [], - "plugin-api-version": "2.0.0" -} diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index bcbb084..e54dd6a 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -47,12 +47,10 @@ use OCP\FullTextSearch\IFullTextSearchManager; use OCP\INavigationManager; use OCP\IServerContainer; use OCP\IURLGenerator; -use Symfony\Component\Routing\Exception\RouteNotFoundException; use Psr\Container\ContainerInterface; +use Symfony\Component\Routing\Exception\RouteNotFoundException; use Throwable; -require_once __DIR__ . '/../../vendor/autoload.php'; - class Application extends App implements IBootstrap { @@ -137,11 +135,11 @@ class Application extends App implements IBootstrap { $urlGen = OC::$server->get(IURLGenerator::class); return [ - 'id' => self::APP_ID, + 'id' => self::APP_ID, 'order' => 5, - 'href' => $urlGen->linkToRoute(self::APP_ID . '.Navigation.navigate'), - 'icon' => $urlGen->imagePath(self::APP_ID, 'fulltextsearch.svg'), - 'name' => 'Search' + 'href' => $urlGen->linkToRoute(self::APP_ID . '.Navigation.navigate'), + 'icon' => $urlGen->imagePath(self::APP_ID, 'fulltextsearch.svg'), + 'name' => 'Search' ]; } diff --git a/lib/Command/Index.php b/lib/Command/Index.php index 1508769..b8fece8 100644 --- a/lib/Command/Index.php +++ b/lib/Command/Index.php @@ -31,7 +31,7 @@ declare(strict_types=1); namespace OCA\FullTextSearch\Command; -use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools; +use OCA\FullTextSearch\Tools\Traits\TArrayTools; use Exception; use OC\Core\Command\InterruptedException; use OCA\FullTextSearch\ACommandBase; diff --git a/lib/Command/Live.php b/lib/Command/Live.php index 79684ca..1897de8 100644 --- a/lib/Command/Live.php +++ b/lib/Command/Live.php @@ -31,7 +31,7 @@ declare(strict_types=1); namespace OCA\FullTextSearch\Command; -use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools; +use OCA\FullTextSearch\Tools\Traits\TArrayTools; use Exception; use OC\Core\Command\InterruptedException; use OCA\FullTextSearch\ACommandBase; diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index 989a7bf..07947cc 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -31,13 +31,14 @@ declare(strict_types=1); namespace OCA\FullTextSearch\Controller; -use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\TNCDataResponse; use Exception; +use OC\AppFramework\Http; use OCA\FullTextSearch\AppInfo\Application; use OCA\FullTextSearch\Model\SearchRequest; use OCA\FullTextSearch\Service\ConfigService; use OCA\FullTextSearch\Service\MiscService; use OCA\FullTextSearch\Service\SearchService; +use OCA\FullTextSearch\Tools\Traits\TDeserialize; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\DataResponse; use OCP\IRequest; @@ -50,10 +51,7 @@ use OCP\IUserSession; * @package OCA\FullTextSearch\Controller */ class ApiController extends Controller { - - - use TNCDataResponse; - + use TDeserialize; /** @var IUserSession */ private $userSession; @@ -128,23 +126,25 @@ class ApiController extends Controller { $user = $this->userSession->getUser(); $result = $this->searchService->search($user->getUID(), $request); - return $this->success( - $result, + return new DataResponse( [ 'request' => $request, - 'version' => $this->configService->getAppValue('installed_version') + 'version' => $this->configService->getAppValue('installed_version'), + 'result' => $result, + 'status' => 1 ] ); } catch (Exception $e) { - return $this->fail( - $e, + return new DataResponse( [ 'request' => $request, - 'version' => $this->configService->getAppValue('installed_version') - ] + 'version' => $this->configService->getAppValue('installed_version'), + 'status' => -1, + 'exception' => get_class($e), + 'message' => $e->getMessage() + ], Http::STATUS_INTERNAL_SERVER_ERROR ); } } - } diff --git a/lib/Db/CoreQueryBuilder.php b/lib/Db/CoreQueryBuilder.php new file mode 100644 index 0000000..a7b7d8d --- /dev/null +++ b/lib/Db/CoreQueryBuilder.php @@ -0,0 +1,48 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2018 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Db; + + + +use OCA\FullTextSearch\Tools\Db\ExtendedQueryBuilder; + +/** + * Class CoreQueryBuilder + * + * @package OCA\Files_FullTextSearch\Db + */ +class CoreQueryBuilder extends ExtendedQueryBuilder { + + + +} + diff --git a/lib/Db/CoreRequestBuilder.php b/lib/Db/CoreRequestBuilder.php index 47018cc..54ffc76 100644 --- a/lib/Db/CoreRequestBuilder.php +++ b/lib/Db/CoreRequestBuilder.php @@ -85,6 +85,11 @@ class CoreRequestBuilder { } + public function getQueryBuilder(): CoreQueryBuilder { + return new CoreQueryBuilder(); + } + + /** * Limit the request to the Id * diff --git a/lib/Db/IndexesRequestBuilder.php b/lib/Db/IndexesRequestBuilder.php index 7634ec6..0fd87ee 100644 --- a/lib/Db/IndexesRequestBuilder.php +++ b/lib/Db/IndexesRequestBuilder.php @@ -31,8 +31,8 @@ declare(strict_types=1); namespace OCA\FullTextSearch\Db; -use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools; use OCA\FullTextSearch\Model\Index; +use OCA\FullTextSearch\Tools\Traits\TArrayTools; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -43,7 +43,6 @@ use OCP\DB\QueryBuilder\IQueryBuilder; */ class IndexesRequestBuilder extends CoreRequestBuilder { - use TArrayTools; @@ -53,7 +52,7 @@ class IndexesRequestBuilder extends CoreRequestBuilder { * @return IQueryBuilder */ protected function getIndexesInsertSql(): IQueryBuilder { - $qb = $this->dbConnection->getQueryBuilder(); + $qb = $this->getQueryBuilder(); $qb->insert(self::TABLE_INDEXES); return $qb; diff --git a/lib/Db/TickRequestBuilder.php b/lib/Db/TickRequestBuilder.php index 81cdd0d..a262bc1 100644 --- a/lib/Db/TickRequestBuilder.php +++ b/lib/Db/TickRequestBuilder.php @@ -31,7 +31,7 @@ declare(strict_types=1); namespace OCA\FullTextSearch\Db; -use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools; +use OCA\FullTextSearch\Tools\Traits\TArrayTools; use OCA\FullTextSearch\Model\Tick; use OCP\DB\QueryBuilder\IQueryBuilder; diff --git a/lib/Model/Index.php b/lib/Model/Index.php index 31d74ec..669456b 100644 --- a/lib/Model/Index.php +++ b/lib/Model/Index.php @@ -31,7 +31,7 @@ declare(strict_types=1); namespace OCA\FullTextSearch\Model; -use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools; +use OCA\FullTextSearch\Tools\Traits\TArrayTools; use JsonSerializable; use OCP\FullTextSearch\Model\IIndex; use OCP\IURLGenerator; diff --git a/lib/Model/IndexOptions.php b/lib/Model/IndexOptions.php index 6d54876..a4e1831 100644 --- a/lib/Model/IndexOptions.php +++ b/lib/Model/IndexOptions.php @@ -31,7 +31,7 @@ declare(strict_types=1); namespace OCA\FullTextSearch\Model; -use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools; +use OCA\FullTextSearch\Tools\Traits\TArrayTools; use JsonSerializable; use OCP\FullTextSearch\Model\IIndexOptions; diff --git a/lib/Model/Runner.php b/lib/Model/Runner.php index 0861861..96ef545 100644 --- a/lib/Model/Runner.php +++ b/lib/Model/Runner.php @@ -31,7 +31,7 @@ declare(strict_types=1); namespace OCA\FullTextSearch\Model; -use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools; +use OCA\FullTextSearch\Tools\Traits\TArrayTools; use Exception; use OCA\FullTextSearch\ACommandBase; use OCA\FullTextSearch\Exceptions\RunnerAlreadyUpException; diff --git a/lib/Model/SearchRequest.php b/lib/Model/SearchRequest.php index c56a5e2..991939e 100644 --- a/lib/Model/SearchRequest.php +++ b/lib/Model/SearchRequest.php @@ -31,7 +31,7 @@ declare(strict_types=1); namespace OCA\FullTextSearch\Model; -use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools; +use OCA\FullTextSearch\Tools\Traits\TArrayTools; use JsonSerializable; use OCP\FullTextSearch\Model\ISearchRequest; use OCP\FullTextSearch\Model\ISearchRequestSimpleQuery; diff --git a/lib/Model/Tick.php b/lib/Model/Tick.php index fada00a..baf888c 100644 --- a/lib/Model/Tick.php +++ b/lib/Model/Tick.php @@ -31,7 +31,7 @@ declare(strict_types=1); namespace OCA\FullTextSearch\Model; -use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools; +use OCA\FullTextSearch\Tools\Traits\TArrayTools; /** * Class Tick diff --git a/lib/Search/UnifiedSearchProvider.php b/lib/Search/UnifiedSearchProvider.php index 70cf72e..e6598a5 100644 --- a/lib/Search/UnifiedSearchProvider.php +++ b/lib/Search/UnifiedSearchProvider.php @@ -31,7 +31,7 @@ declare(strict_types=1); namespace OCA\FullTextSearch\Search; -use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools; +use OCA\FullTextSearch\Tools\Traits\TArrayTools; use Exception; use OCA\FullTextSearch\Model\SearchRequest; use OCA\FullTextSearch\Service\ConfigService; diff --git a/lib/Service/SearchService.php b/lib/Service/SearchService.php index 2dd8958..f41e738 100644 --- a/lib/Service/SearchService.php +++ b/lib/Service/SearchService.php @@ -31,7 +31,7 @@ declare(strict_types=1); namespace OCA\FullTextSearch\Service; -use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger; +use OCA\FullTextSearch\Tools\Traits\Nextcloud\nc22\TNC22Logger; use Exception; use OC; use OC\App\AppManager; @@ -44,6 +44,7 @@ use OCA\FullTextSearch\Exceptions\EmptySearchException; use OCA\FullTextSearch\Exceptions\ProviderDoesNotExistException; use OCA\FullTextSearch\Model\SearchRequest; use OCA\FullTextSearch\Model\SearchResult; +use OCA\FullTextSearch\Tools\Traits\TNCLogger; use OCP\FullTextSearch\IFullTextSearchPlatform; use OCP\FullTextSearch\IFullTextSearchProvider; use OCP\FullTextSearch\Model\IDocumentAccess; @@ -63,7 +64,7 @@ use OCP\IUserManager; class SearchService implements ISearchService { - use TNC22Logger; + use TNCLogger; /** @var string */ diff --git a/lib/Tools/Db/ExtendedQueryBuilder.php b/lib/Tools/Db/ExtendedQueryBuilder.php new file mode 100644 index 0000000..a019148 --- /dev/null +++ b/lib/Tools/Db/ExtendedQueryBuilder.php @@ -0,0 +1,1135 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Db; + +use DateInterval; +use DateTime; +use Doctrine\DBAL\Query\QueryBuilder as DBALQueryBuilder; +use Exception; +use OC; +use OC\DB\QueryBuilder\QueryBuilder; +use OC\SystemConfig; +use OCA\FullTextSearch\Tools\Exceptions\DateTimeException; +use OCA\FullTextSearch\Tools\Exceptions\InvalidItemException; +use OCA\FullTextSearch\Tools\Exceptions\RowNotFoundException; +use OCA\FullTextSearch\Tools\Traits\TArrayTools; +use OCP\DB\QueryBuilder\ICompositeExpression; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use Psr\Log\LoggerInterface; + +class ExtendedQueryBuilder extends QueryBuilder { + use TArrayTools; + + + /** @var string */ + private $defaultSelectAlias = ''; + + /** @var array */ + private $defaultValues = []; + + + public function __construct() { + parent::__construct( + OC::$server->get(IDBConnection::class), + OC::$server->get(SystemConfig::class), + OC::$server->get(LoggerInterface::class) + ); + } + + + /** + * @param string $alias + * + * @return self + */ + public function setDefaultSelectAlias(string $alias): self { + $this->defaultSelectAlias = $alias; + + return $this; + } + + /** + * @return string + */ + public function getDefaultSelectAlias(): string { + return $this->defaultSelectAlias; + } + + + /** + * @return array + */ + public function getDefaultValues(): array { + return $this->defaultValues; + } + + /** + * @param string $key + * @param string $value + * + * @return $this + */ + public function addDefaultValue(string $key, string $value): self { + $this->defaultValues[$key] = $value; + + return $this; + } + + /** + * @param int $size + * @param int $page + */ + public function paginate(int $size, int $page = 0): void { + if ($page < 0) { + $page = 0; + } + + $this->chunk($page * $size, $size); + } + + /** + * @param int $offset + * @param int $limit + */ + public function chunk(int $offset, int $limit): void { + if ($offset > -1) { + $this->setFirstResult($offset); + } + + if ($limit > 0) { + $this->setMaxResults($limit); + } + } + + + /** + * Limit the request to the Id + * + * @param int $id + */ + public function limitToId(int $id): void { + $this->limitInt('id', $id); + } + + /** + * @param array $ids + */ + public function limitToIds(array $ids): void { + $this->limitArray('id', $ids); + } + + /** + * @param string $id + */ + public function limitToIdString(string $id): void { + $this->limit('id', $id); + } + + /** + * @param string $userId + */ + public function limitToUserId(string $userId): void { + $this->limit('user_id', $userId); + } + + /** + * @param string $uniqueId + */ + public function limitToUniqueId(string $uniqueId): void { + $this->limit('unique_id', $uniqueId); + } + + /** + * @param string $memberId + */ + public function limitToMemberId(string $memberId): void { + $this->limit('member_id', $memberId); + } + + /** + * @param string $status + */ + public function limitToStatus(string $status): void { + $this->limit('status', $status, '', false); + } + + /** + * @param int $type + */ + public function limitToType(int $type): void { + $this->limitInt('type', $type); + } + + /** + * @param string $type + */ + public function limitToTypeString(string $type): void { + $this->limit('type', $type, '', false); + } + + /** + * @param string $token + */ + public function limitToToken(string $token): void { + $this->limit('token', $token); + } + + + /** + * Limit the request to the creation + * + * @param int $delay + * + * @return self + * @throws Exception + */ + public function limitToCreation(int $delay = 0): self { + $date = new DateTime('now'); + $date->sub(new DateInterval('PT' . $delay . 'M')); + + $this->limitToDBFieldDateTime('creation', $date, true); + + return $this; + } + + + /** + * @param string $field + * @param DateTime $date + * @param bool $orNull + */ + public function limitToDBFieldDateTime(string $field, DateTime $date, bool $orNull = false): void { + $expr = $this->expr(); + $pf = ($this->getType() === DBALQueryBuilder::SELECT) ? $this->getDefaultSelectAlias() + . '.' : ''; + $field = $pf . $field; + + $orX = $expr->orX(); + $orX->add( + $expr->lte($field, $this->createNamedParameter($date, IQueryBuilder::PARAM_DATE)) + ); + + if ($orNull === true) { + $orX->add($expr->isNull($field)); + } + + $this->andWhere($orX); + } + + + /** + * @param int $timestamp + * @param string $field + * + * @throws DateTimeException + */ + public function limitToSince(int $timestamp, string $field): void { + try { + $dTime = new DateTime(); + $dTime->setTimestamp($timestamp); + } catch (Exception $e) { + throw new DateTimeException($e->getMessage()); + } + + $expr = $this->expr(); + $pf = ($this->getType() === DBALQueryBuilder::SELECT) ? $this->getDefaultSelectAlias() . '.' : ''; + $field = $pf . $field; + + $orX = $expr->orX(); + $orX->add( + $expr->gte($field, $this->createNamedParameter($dTime, IQueryBuilder::PARAM_DATE)) + ); + + $this->andWhere($orX); + } + + + /** + * @param string $field + * @param string $value + */ + public function searchInDBField(string $field, string $value): void { + $expr = $this->expr(); + + $pf = ($this->getType() === DBALQueryBuilder::SELECT) ? $this->getDefaultSelectAlias() . '.' : ''; + $field = $pf . $field; + + $this->andWhere($expr->iLike($field, $this->createNamedParameter($value))); + } + + + /** + * @param string $field + * @param string $value + * @param string $alias + * @param bool $cs + */ + public function like(string $field, string $value, string $alias = '', bool $cs = true): void { + $this->andWhere($this->exprLike($field, $value, $alias, $cs)); + } + + + /** + * @param string $field + * @param string $value + * @param string $alias + * @param bool $cs + */ + public function limit(string $field, string $value, string $alias = '', bool $cs = true): void { + $this->andWhere($this->exprLimit($field, $value, $alias, $cs)); + } + + /** + * @param string $field + * @param int $value + * @param string $alias + */ + public function limitInt(string $field, int $value, string $alias = ''): void { + $this->andWhere($this->exprLimitInt($field, $value, $alias)); + } + + /** + * @param string $field + * @param bool $value + * @param string $alias + */ + public function limitBool(string $field, bool $value, string $alias = ''): void { + $this->andWhere($this->exprLimitBool($field, $value, $alias)); + } + + /** + * @param string $field + * @param bool $orNull + * @param string $alias + */ + public function limitEmpty(string $field, bool $orNull = false, string $alias = ''): void { + $this->andWhere($this->exprLimitEmpty($field, $orNull, $alias)); + } + + /** + * @param string $field + * @param bool $orEmpty + * @param string $alias + */ + public function limitNull(string $field, bool $orEmpty = false, string $alias = ''): void { + $this->andWhere($this->exprLimitNull($field, $orEmpty, $alias)); + } + + /** + * @param string $field + * @param array $value + * @param string $alias + * @param bool $cs + */ + public function limitArray(string $field, array $value, string $alias = '', bool $cs = true): void { + $this->andWhere($this->exprLimitArray($field, $value, $alias, $cs)); + } + + /** + * @param string $field + * @param array $value + * @param string $alias + */ + public function limitInArray(string $field, array $value, string $alias = ''): void { + $this->andWhere($this->exprLimitInArray($field, $value, $alias)); + } + + /** + * @param string $field + * @param int $flag + * @param string $alias + */ + public function limitBitwise(string $field, int $flag, string $alias = ''): void { + $this->andWhere($this->exprLimitBitwise($field, $flag, $alias)); + } + + /** + * @param string $field + * @param int $value + * @param bool $gte + * @param string $alias + */ + public function gt(string $field, int $value, bool $gte = false, string $alias = ''): void { + $this->andWhere($this->exprGt($field, $value, $gte, $alias)); + } + + /** + * @param string $field + * @param int $value + * @param bool $lte + * @param string $alias + */ + public function lt(string $field, int $value, bool $lte = false, string $alias = ''): void { + $this->andWhere($this->exprLt($field, $value, $lte, $alias)); + } + + + /** + * @param string $field + * @param string $value + * @param string $alias + * @param bool $cs + * + * @return string + */ + public function exprLike(string $field, string $value, string $alias = '', bool $cs = true): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + if ($cs) { + return $expr->like($field, $this->createNamedParameter($value)); + } else { + return $expr->iLike($field, $this->createNamedParameter($value)); + } + } + + + /** + * @param string $field + * @param string $value + * @param string $alias + * @param bool $cs + * + * @return string + */ + public function exprLimit(string $field, string $value, string $alias = '', bool $cs = true): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + if ($value === '') { + return $expr->emptyString($field); + } + if ($cs) { + return $expr->eq($field, $this->createNamedParameter($value)); + } else { + $func = $this->func(); + + return $expr->eq($func->lower($field), $func->lower($this->createNamedParameter($value))); + } + } + + + /** + * @param string $field + * @param int $value + * @param string $alias + * + * @return string + */ + public function exprLimitInt(string $field, int $value, string $alias = ''): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + + return $expr->eq($field, $this->createNamedParameter($value, IQueryBuilder::PARAM_INT)); + } + + + /** + * @param string $field + * @param bool $value + * @param string $alias + * + * @return string + */ + public function exprLimitBool(string $field, bool $value, string $alias = ''): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + + return $expr->eq($field, $this->createNamedParameter($value, IQueryBuilder::PARAM_BOOL)); + } + + /** + * @param string $field + * @param bool $orNull + * @param string $alias + * + * @return ICompositeExpression + */ + public function exprLimitEmpty( + string $field, + bool $orNull = false, + string $alias = '' + ): ICompositeExpression { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + $orX = $expr->orX(); + $orX->add($expr->emptyString($field)); + if ($orNull) { + $orX->add($expr->isNull($field)); + } + + return $orX; + } + + /** + * @param string $field + * @param bool $orEmpty + * @param string $alias + * + * @return ICompositeExpression + */ + public function exprLimitNull( + string $field, + bool $orEmpty = false, + string $alias = '' + ): ICompositeExpression { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + $orX = $expr->orX(); + $orX->add($expr->isNull($field)); + if ($orEmpty) { + $orX->add($expr->emptyString($field)); + } + + return $orX; + } + + + /** + * @param string $field + * @param array $values + * @param string $alias + * @param bool $cs + * + * @return ICompositeExpression + */ + public function exprLimitArray( + string $field, + array $values, + string $alias = '', + bool $cs = true + ): ICompositeExpression { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $andX = $this->expr()->andX(); + foreach ($values as $value) { + if (is_integer($value)) { + $andX->add($this->exprLimitInt($field, $value, $alias)); + } else { + $andX->add($this->exprLimit($field, $value, $alias, $cs)); + } + } + + return $andX; + } + + + /** + * @param string $field + * @param array $values + * @param string $alias + * + * @return string + */ + public function exprLimitInArray(string $field, array $values, string $alias = ''): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + + return $expr->in($field, $this->createNamedParameter($values, IQueryBuilder::PARAM_STR_ARRAY)); + } + + + /** + * @param string $field + * @param int $flag + * @param string $alias + * + * @return string + */ + public function exprLimitBitwise(string $field, int $flag, string $alias = ''): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + + return $expr->gt( + $expr->bitwiseAnd($field, $flag), + $this->createNamedParameter(0, IQueryBuilder::PARAM_INT) + ); + } + + + /** + * @param string $field + * @param int $value + * @param bool $lte + * @param string $alias + * + * @return string + */ + public function exprLt(string $field, int $value, bool $lte = false, string $alias = ''): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + + if ($lte) { + return $expr->lte($field, $this->createNamedParameter($value, IQueryBuilder::PARAM_INT)); + } else { + return $expr->lt($field, $this->createNamedParameter($value, IQueryBuilder::PARAM_INT)); + } + } + + /** + * @param string $field + * @param int $value + * @param bool $gte + * @param string $alias + * + * @return string + */ + public function exprGt(string $field, int $value, bool $gte = false, string $alias = ''): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + + if ($gte) { + return $expr->gte($field, $this->createNamedParameter($value, IQueryBuilder::PARAM_INT)); + } else { + return $expr->gt($field, $this->createNamedParameter($value, IQueryBuilder::PARAM_INT)); + } + } + + + /** + * @param string $field + * @param string $value + * @param string $alias + * @param bool $cs + */ + public function unlike(string $field, string $value, string $alias = '', bool $cs = true): void { + $this->andWhere($this->exprUnlike($field, $value, $alias, $cs)); + } + + + /** + * @param string $field + * @param string $value + * @param string $alias + * @param bool $cs + */ + public function filter(string $field, string $value, string $alias = '', bool $cs = true): void { + $this->andWhere($this->exprFilter($field, $value, $alias, $cs)); + } + + /** + * @param string $field + * @param int $value + * @param string $alias + */ + public function filterInt(string $field, int $value, string $alias = ''): void { + $this->andWhere($this->exprFilterInt($field, $value, $alias)); + } + + /** + * @param string $field + * @param bool $value + * @param string $alias + */ + public function filterBool(string $field, bool $value, string $alias = ''): void { + $this->andWhere($this->exprFilterBool($field, $value, $alias)); + } + + /** + * @param string $field + * @param bool $norNull + * @param string $alias + */ + public function filterEmpty(string $field, bool $norNull = false, string $alias = ''): void { + $this->andWhere($this->exprFilterEmpty($field, $norNull, $alias)); + } + + /** + * @param string $field + * @param bool $norEmpty + * @param string $alias + */ + public function filterNull(string $field, bool $norEmpty = false, string $alias = ''): void { + $this->andWhere($this->exprFilterNull($field, $norEmpty, $alias)); + } + + /** + * @param string $field + * @param array $value + * @param string $alias + * @param bool $cs + */ + public function filterArray(string $field, array $value, string $alias = '', bool $cs = true): void { + $this->andWhere($this->exprFilterArray($field, $value, $alias, $cs)); + } + + /** + * @param string $field + * @param array $value + * @param string $alias + */ + public function filterInArray(string $field, array $value, string $alias = ''): void { + $this->andWhere($this->exprFilterInArray($field, $value, $alias)); + } + + /** + * @param string $field + * @param int $flag + * @param string $alias + */ + public function filterBitwise(string $field, int $flag, string $alias = ''): void { + $this->andWhere($this->exprFilterBitwise($field, $flag, $alias)); + } + + + /** + * @param string $field + * @param string $value + * @param string $alias + * @param bool $cs + * + * @return string + */ + public function exprUnlike(string $field, string $value, string $alias = '', bool $cs = true): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + if ($cs) { + return $expr->notLike($field, $this->createNamedParameter($value)); + } else { + $func = $this->func(); + + return $expr->notLike($func->lower($field), $func->lower($this->createNamedParameter($value))); + } + } + + + /** + * @param string $field + * @param string $value + * @param string $alias + * @param bool $cs + * + * @return string + */ + public function exprFilter(string $field, string $value, string $alias = '', bool $cs = true): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + if ($value === '') { + return $expr->nonEmptyString($field); + } + if ($cs) { + return $expr->neq($field, $this->createNamedParameter($value)); + } else { + $func = $this->func(); + + return $expr->neq($func->lower($field), $func->lower($this->createNamedParameter($value))); + } + } + + + /** + * @param string $field + * @param int $value + * @param string $alias + * + * @return string + */ + public function exprFilterInt(string $field, int $value, string $alias = ''): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + + return $expr->neq($field, $this->createNamedParameter($value, IQueryBuilder::PARAM_INT)); + } + + + /** + * @param string $field + * @param bool $value + * @param string $alias + * + * @return string + */ + public function exprFilterBool(string $field, bool $value, string $alias = ''): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + + return $expr->neq($field, $this->createNamedParameter($value, IQueryBuilder::PARAM_BOOL)); + } + + /** + * @param string $field + * @param bool $norNull + * @param string $alias + * + * @return ICompositeExpression + */ + public function exprFilterEmpty( + string $field, + bool $norNull = false, + string $alias = '' + ): ICompositeExpression { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + $andX = $expr->andX(); + $andX->add($expr->nonEmptyString($field)); + if ($norNull) { + $andX->add($expr->isNotNull($field)); + } + + return $andX; + } + + /** + * @param string $field + * @param bool $norEmpty + * @param string $alias + * + * @return ICompositeExpression + */ + public function exprFilterNull( + string $field, + bool $norEmpty = false, + string $alias = '' + ): ICompositeExpression { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + $andX = $expr->andX(); + $andX->add($expr->isNotNull($field)); + if ($norEmpty) { + $andX->add($expr->nonEmptyString($field)); + } + + return $andX; + } + + + /** + * @param string $field + * @param array $values + * @param string $alias + * @param bool $cs + * + * @return ICompositeExpression + */ + public function exprFilterArray( + string $field, + array $values, + string $alias = '', + bool $cs = true + ): ICompositeExpression { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $orX = $this->expr()->orX(); + foreach ($values as $value) { + if (is_integer($value)) { + $orX->add($this->exprFilterInt($field, $value, $alias)); + } else { + $orX->add($this->exprFilter($field, $value, $alias, $cs)); + } + } + + return $orX; + } + + + /** + * @param string $field + * @param array $values + * @param string $alias + * + * @return string + */ + public function exprFilterInArray(string $field, array $values, string $alias = ''): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + + return $expr->notIn($field, $this->createNamedParameter($values, IQueryBuilder::PARAM_STR_ARRAY)); + } + + + /** + * @param string $field + * @param int $flag + * @param string $alias + * + * @return string + */ + public function exprFilterBitwise(string $field, int $flag, string $alias = ''): string { + if ($this->getType() === DBALQueryBuilder::SELECT) { + $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; + } + + $expr = $this->expr(); + + return $expr->eq( + $expr->bitwiseAnd($field, $flag), + $this->createNamedParameter(0, IQueryBuilder::PARAM_INT) + ); + } + + + /** + * @param string $object + * @param array $params + * + * @return IQueryRow + * @throws RowNotFoundException + * @throws InvalidItemException + */ + public function asItem(string $object, array $params = []): IQueryRow { + return $this->getRow([$this, 'parseSimpleSelectSql'], $object, $params); + } + + /** + * @param string $object + * @param array $params + * + * @return IQueryRow[] + */ + public function asItems(string $object, array $params = []): array { + return $this->getRows([$this, 'parseSimpleSelectSql'], $object, $params); + } + + + /** + * @param string $field + * @param array $params + * + * @return IQueryRow + * @throws InvalidItemException + * @throws RowNotFoundException + */ + public function asItemFromField(string $field, array $params = []): IQueryRow { + $param['modelFromField'] = $field; + + return $this->getRow([$this, 'parseSimpleSelectSql'], '', $params); + } + + /** + * @param string $field + * @param array $params + * + * @return IQueryRow[] + */ + public function asItemsFromField(string $field, array $params = []): array { + $param['modelFromField'] = $field; + + return $this->getRows([$this, 'parseSimpleSelectSql'], $field, $params); + } + + + /** + * @param array $data + * @param ExtendedQueryBuilder $qb + * @param string $object + * @param array $params + * + * @return IQueryRow + * @throws InvalidItemException + */ + private function parseSimpleSelectSql( + array $data, + ExtendedQueryBuilder $qb, + string $object, + array $params + ): IQueryRow { + $fromField = $this->get('modelFromField', $params); + if ($fromField !== '') { + $object = $fromField; + } + + $item = new $object(); + if (!($item instanceof IQueryRow)) { + throw new InvalidItemException(); + } + + if (!empty($params)) { + $data['_params'] = $params; + } + + foreach ($qb->getDefaultValues() as $k => $v) { + if ($this->get($k, $data) === '') { + $data[$k] = $v; + } + } + + $data = array_merge($qb->getDefaultValues(), $data); + + $item->importFromDatabase($data); + + return $item; + } + + + /** + * @param callable $method + * @param string $object + * @param array $params + * + * @return IQueryRow + * @throws RowNotFoundException + */ + public function getRow(callable $method, string $object = '', array $params = []): IQueryRow { + $cursor = $this->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + throw new RowNotFoundException(); + } + + return $method($data, $this, $object, $params); + } + + + /** + * @param callable $method + * @param string $object + * @param array $params + * + * @return IQueryRow[] + */ + public function getRows(callable $method, string $object = '', array $params = []): array { + $rows = []; + $cursor = $this->execute(); + while ($data = $cursor->fetch()) { + try { + $rows[] = $method($data, $this, $object, $params); + } catch (Exception $e) { + } + } + $cursor->closeCursor(); + + return $rows; + } + + + /** + * @param string $table + * @param array $fields + * @param string $alias + * + * @return $this + */ + public function generateSelect( + string $table, + array $fields, + string $alias = '' + ): self { + $selectFields = array_map( + function (string $item) use ($alias) { + if ($alias === '') { + return $item; + } + + return $alias . '.' . $item; + }, $fields + ); + + $this->select($selectFields) + ->from($table, $alias) + ->setDefaultSelectAlias($alias); + + return $this; + } + + + /** + * @param array $fields + * @param string $alias + * @param string $prefix + * @param array $default + * + * @return $this + */ + public function generateSelectAlias( + array $fields, + string $alias, + string $prefix, + array $default = [] + ): self { + $prefix = trim($prefix) . '_'; + foreach ($default as $k => $v) { + $this->addDefaultValue($prefix . $k, (string)$v); + } + + foreach ($fields as $field) { + $this->selectAlias($alias . '.' . $field, $prefix . $field); + } + + return $this; + } +} diff --git a/lib/Tools/Db/IQueryRow.php b/lib/Tools/Db/IQueryRow.php new file mode 100644 index 0000000..180537d --- /dev/null +++ b/lib/Tools/Db/IQueryRow.php @@ -0,0 +1,49 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Db; + +/** + * Interface IQueryRow + * + * @package OCA\FullTextSearch\Tools\Db + */ +interface IQueryRow { + + /** + * import data to feed the model. + * + * @param array $data + * + * @return IQueryRow + */ + public function importFromDatabase(array $data): self; +} diff --git a/lib/Tools/Exceptions/ArrayNotFoundException.php b/lib/Tools/Exceptions/ArrayNotFoundException.php new file mode 100644 index 0000000..aee3280 --- /dev/null +++ b/lib/Tools/Exceptions/ArrayNotFoundException.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Exceptions; + +use Exception; + +/** + * Class ArrayNotFoundException + * + * @package OCA\FullTextSearch\Tools\Exceptions + */ +class ArrayNotFoundException extends Exception { +} diff --git a/lib/Tools/Exceptions/DateTimeException.php b/lib/Tools/Exceptions/DateTimeException.php new file mode 100644 index 0000000..066b6bf --- /dev/null +++ b/lib/Tools/Exceptions/DateTimeException.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Exceptions; + +use Exception; + +/** + * Class DateTimeException + * + * @package OCA\FullTextSearch\Tools\Exceptions + */ +class DateTimeException extends Exception { +} diff --git a/lib/Tools/Exceptions/InvalidItemException.php b/lib/Tools/Exceptions/InvalidItemException.php new file mode 100644 index 0000000..ff4e89c --- /dev/null +++ b/lib/Tools/Exceptions/InvalidItemException.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Exceptions; + +use Exception; + +/** + * Class InvalidItemException + * + * @package OCA\FullTextSearch\Tools\Exceptions + */ +class InvalidItemException extends Exception { +} diff --git a/lib/Tools/Exceptions/ItemNotFoundException.php b/lib/Tools/Exceptions/ItemNotFoundException.php new file mode 100644 index 0000000..ad28b0a --- /dev/null +++ b/lib/Tools/Exceptions/ItemNotFoundException.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Exceptions; + +use Exception; + +/** + * Class ItemNotFoundException + * + * @package OCA\FullTextSearch\Tools\Exceptions + */ +class ItemNotFoundException extends Exception { +} diff --git a/lib/Tools/Exceptions/MalformedArrayException.php b/lib/Tools/Exceptions/MalformedArrayException.php new file mode 100644 index 0000000..a6a3304 --- /dev/null +++ b/lib/Tools/Exceptions/MalformedArrayException.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Exceptions; + +use Exception; + +/** + * Class MalformedArrayException + * + * @package OCA\FullTextSearch\Tools\Exceptions + */ +class MalformedArrayException extends Exception { +} diff --git a/lib/Tools/Exceptions/RowNotFoundException.php b/lib/Tools/Exceptions/RowNotFoundException.php new file mode 100644 index 0000000..b91dd01 --- /dev/null +++ b/lib/Tools/Exceptions/RowNotFoundException.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Exceptions; + +use Exception; + +/** + * Class RowNotFoundException + * + * @package OCA\FullTextSearch\Tools\Exceptions + */ +class RowNotFoundException extends Exception { +} diff --git a/lib/Tools/Exceptions/UnknownTypeException.php b/lib/Tools/Exceptions/UnknownTypeException.php new file mode 100644 index 0000000..cba7337 --- /dev/null +++ b/lib/Tools/Exceptions/UnknownTypeException.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Exceptions; + +use Exception; + +/** + * Class UnknownTypeException + * + * @package OCA\FullTextSearch\Tools\Exceptions + */ +class UnknownTypeException extends Exception { +} diff --git a/lib/Tools/IDeserializable.php b/lib/Tools/IDeserializable.php new file mode 100644 index 0000000..ab0fb4b --- /dev/null +++ b/lib/Tools/IDeserializable.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools; + +interface IDeserializable { + + /** + * @param array $data + * + * @return self + */ + public function import(array $data): self; +} diff --git a/lib/Tools/Traits/TArrayTools.php b/lib/Tools/Traits/TArrayTools.php new file mode 100644 index 0000000..cb9feae --- /dev/null +++ b/lib/Tools/Traits/TArrayTools.php @@ -0,0 +1,432 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Traits; + +use Exception; +use JsonSerializable; +use OCA\FullTextSearch\Tools\Exceptions\ArrayNotFoundException; +use OCA\FullTextSearch\Tools\Exceptions\ItemNotFoundException; +use OCA\FullTextSearch\Tools\Exceptions\MalformedArrayException; +use OCA\FullTextSearch\Tools\Exceptions\UnknownTypeException; + +trait TArrayTools { + public static $TYPE_NULL = 'Null'; + public static $TYPE_STRING = 'String'; + public static $TYPE_ARRAY = 'Array'; + public static $TYPE_BOOLEAN = 'Boolean'; + public static $TYPE_INTEGER = 'Integer'; + public static $TYPE_SERIALIZABLE = 'Serializable'; + + + /** + * @param string $k + * @param array $arr + * @param string $default + * + * @return string + */ + protected function get(string $k, array $arr, string $default = ''): string { + if (!array_key_exists($k, $arr)) { + $subs = explode('.', $k, 2); + if (sizeof($subs) > 1) { + if (!array_key_exists($subs[0], $arr)) { + return $default; + } + + $r = $arr[$subs[0]]; + if (!is_array($r)) { + return $default; + } + + return $this->get($subs[1], $r, $default); + } else { + return $default; + } + } + + if ($arr[$k] === null || !is_string($arr[$k]) && (!is_int($arr[$k]))) { + return $default; + } + + return (string)$arr[$k]; + } + + + /** + * @param string $k + * @param array $arr + * @param int $default + * + * @return int + */ + protected function getInt(string $k, array $arr, int $default = 0): int { + if (!array_key_exists($k, $arr)) { + $subs = explode('.', $k, 2); + if (sizeof($subs) > 1) { + if (!array_key_exists($subs[0], $arr)) { + return $default; + } + + $r = $arr[$subs[0]]; + if (!is_array($r)) { + return $default; + } + + return $this->getInt($subs[1], $r, $default); + } else { + return $default; + } + } + + if ($arr[$k] === null) { + return $default; + } + + return intval($arr[$k]); + } + + + /** + * @param string $k + * @param array $arr + * @param float $default + * + * @return float + */ + protected function getFloat(string $k, array $arr, float $default = 0): float { + if (!array_key_exists($k, $arr)) { + $subs = explode('.', $k, 2); + if (sizeof($subs) > 1) { + if (!array_key_exists($subs[0], $arr)) { + return $default; + } + + $r = $arr[$subs[0]]; + if (!is_array($r)) { + return $default; + } + + return $this->getFloat($subs[1], $r, $default); + } else { + return $default; + } + } + + if ($arr[$k] === null) { + return $default; + } + + return intval($arr[$k]); + } + + + /** + * @param string $k + * @param array $arr + * @param bool $default + * + * @return bool + */ + protected function getBool(string $k, array $arr, bool $default = false): bool { + if (!array_key_exists($k, $arr)) { + $subs = explode('.', $k, 2); + if (sizeof($subs) > 1) { + if (!array_key_exists($subs[0], $arr)) { + return $default; + } + + return $this->getBool($subs[1], $arr[$subs[0]], $default); + } else { + return $default; + } + } + + if ($arr[$k] === null) { + return $default; + } + + if (is_bool($arr[$k])) { + return $arr[$k]; + } + + $sk = (string)$arr[$k]; + if ($sk === '1' || strtolower($sk) === 'true') { + return true; + } + + if ($sk === '0' || strtolower($sk) === 'false') { + return false; + } + + return $default; + } + + + /** + * @param string $k + * @param array $arr + * @param JsonSerializable|null $default + * + * @return mixed + */ + protected function getObj(string $k, array $arr, ?JsonSerializable $default = null): ?JsonSerializable { + if (!array_key_exists($k, $arr)) { + $subs = explode('.', $k, 2); + if (sizeof($subs) > 1) { + if (!array_key_exists($subs[0], $arr)) { + return $default; + } + + return $this->getObj($subs[1], $arr[$subs[0]], $default); + } else { + return $default; + } + } + + return $arr[$k]; + } + + + /** + * @param string $k + * @param array $arr + * @param array $default + * + * @return array + */ + protected function getArray(string $k, array $arr, array $default = []): array { + if (!array_key_exists($k, $arr)) { + $subs = explode('.', $k, 2); + if (sizeof($subs) > 1) { + if (!array_key_exists($subs[0], $arr)) { + return $default; + } + + $r = $arr[$subs[0]]; + if (!is_array($r)) { + return $default; + } + + return $this->getArray($subs[1], $r, $default); + } else { + return $default; + } + } + + $r = $arr[$k]; + if (!is_array($r) && !is_string($r)) { + return $default; + } + + if (is_string($r)) { + $r = json_decode($r, true); + } + + if (!is_array($r)) { + return $default; + } + + return $r; + } + + + /** + * @param string $k + * @param array $arr + * + * @return bool + */ + public function validKey(string $k, array $arr): bool { + if (array_key_exists($k, $arr)) { + return true; + } + + $subs = explode('.', $k, 2); + if (sizeof($subs) > 1) { + if (!array_key_exists($subs[0], $arr)) { + return false; + } + + $r = $arr[$subs[0]]; + if (!is_array($r)) { + return false; + } + + return $this->validKey($subs[1], $r); + } + + return false; + } + + + /** + * @param string $k + * @param array $arr + * @param array $import + * @param array $default + * + * @return array + */ + protected function getList(string $k, array $arr, array $import, array $default = []): array { + $list = $this->getArray($k, $arr, $default); + + $r = []; + [$obj, $method] = $import; + foreach ($list as $item) { + try { + $o = new $obj(); + $o->$method($item); + + $r[] = $o; + } catch (Exception $e) { + } + } + + return $r; + } + + + /** + * @param string $k + * @param string $value + * @param array $list + * + * @return mixed + * @throws ArrayNotFoundException + */ + protected function extractArray(string $k, string $value, array $list) { + foreach ($list as $arr) { + if (!array_key_exists($k, $arr)) { + continue; + } + + if ($arr[$k] === $value) { + return $arr; + } + } + + throw new ArrayNotFoundException(); + } + + + /** + * @param string $key + * @param array $arr + * @param bool $root + * + * @return string + * @throws ItemNotFoundException + * @throws UnknownTypeException + */ + public function typeOf(string $key, array $arr, bool $root = true): string { + if (array_key_exists($key, $arr)) { + $item = $arr[$key]; + + if (is_null($item)) { + return self::$TYPE_NULL; + } + + if (is_string($item)) { + return self::$TYPE_STRING; + } + + if (is_array($item)) { + return self::$TYPE_ARRAY; + } + + if (is_bool($item)) { + return self::$TYPE_BOOLEAN; + } + + if (is_int($item)) { + return self::$TYPE_INTEGER; + } + + if ($item instanceof JsonSerializable) { + return self::$TYPE_SERIALIZABLE; + } + + throw new UnknownTypeException(); + } + + $subs = explode('.', $key, 2); + if (sizeof($subs) > 1) { + if (!array_key_exists($subs[0], $arr)) { + throw new ItemNotFoundException(); + } + + $r = $arr[$subs[0]]; + if (is_array($r)) { + return $this->typeOf($subs[1], $r); + } + } + + throw new ItemNotFoundException(); + } + + + /** + * @param array $keys + * @param array $arr + * + * @throws MalformedArrayException + */ + protected function mustContains(array $keys, array $arr) { + foreach ($keys as $key) { + if (!array_key_exists($key, $arr)) { + throw new MalformedArrayException( + 'source: ' . json_encode($arr) . ' - missing key: ' . $key + ); + } + } + } + + + /** + * @param array $arr + */ + protected function cleanArray(array &$arr) { + $arr = array_filter( + $arr, + function ($v) { + if (is_string($v)) { + return ($v !== ''); + } + if (is_array($v)) { + return !empty($v); + } + + return true; + } + ); + } +} diff --git a/lib/Tools/Traits/TDeserialize.php b/lib/Tools/Traits/TDeserialize.php new file mode 100644 index 0000000..0836477 --- /dev/null +++ b/lib/Tools/Traits/TDeserialize.php @@ -0,0 +1,114 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Traits; + +use JsonSerializable; +use OCA\FullTextSearch\Tools\Exceptions\InvalidItemException; +use OCA\FullTextSearch\Tools\IDeserializable; + +trait TDeserialize { + + + /** + * @param JsonSerializable $model + * + * @return array + */ + public function serialize(JsonSerializable $model): array { + return json_decode(json_encode($model), true); + } + + /** + * @param array $data + * + * @return array + */ + public function serializeArray(array $data): array { + return json_decode(json_encode($data), true); + } + + + /** + * @param array $data + * @param string $class + * + * @return IDeserializable + * @throws InvalidItemException + */ + public function deserialize(array $data, string $class): IDeserializable { + if ($class instanceof IDeserializable) { + throw new InvalidItemException(get_class($class) . ' does not implement IDeserializable'); + } + + /** @var IDeserializable $item */ + $item = new $class; + $item->import($data); + + return $item; + } + + + /** + * @param string $json + * @param string $class + * + * @return IDeserializable[] + * @throws InvalidItemException + */ + public function deserializeArray(string $json, string $class): array { + $arr = []; + $data = json_decode($json, true); + if (!is_array($data)) { + return $arr; + } + + foreach ($data as $entry) { + $arr[] = $this->deserialize($entry, $class); + } + + return $arr; + } + + + /** + * @param string $json + * @param string $class + * + * @return IDeserializable + * @throws InvalidItemException + */ + public function deserializeJson(string $json, string $class): IDeserializable { + $data = json_decode($json, true); + + return $this->deserialize($data, $class); + } +} diff --git a/lib/Tools/Traits/TNCLogger.php b/lib/Tools/Traits/TNCLogger.php new file mode 100644 index 0000000..4479e3f --- /dev/null +++ b/lib/Tools/Traits/TNCLogger.php @@ -0,0 +1,201 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Traits; + +use Exception; +use OC; +use OC\HintException; +use Psr\Log\LoggerInterface; +use Throwable; + +trait TNCLogger { + use TNCSetup; + + + public static $EMERGENCY = 4; + public static $ALERT = 3; + public static $CRITICAL = 3; + public static $ERROR = 3; + public static $WARNING = 2; + public static $NOTICE = 1; + public static $INFO = 1; + public static $DEBUG = 0; + + + /** + * @param Throwable $t + * @param array $serializable + */ + public function t(Throwable $t, array $serializable = []): void { + $this->throwable($t, self::$ERROR, $serializable); + } + + /** + * @param Throwable $t + * @param int $level + * @param array $serializable + */ + public function throwable(Throwable $t, int $level = 3, array $serializable = []): void { + $message = ''; + if (!empty($serializable)) { + $message = json_encode($serializable); + } + + $this->logger() + ->log( + $level, + $message, + [ + 'app' => $this->setup('app'), + 'exception' => $t + ] + ); + } + + + /** + * @param Exception $e + * @param array $serializable + */ + public function e(Exception $e, array $serializable = []): void { + $this->exception($e, self::$ERROR, $serializable); + } + + /** + * @param Exception $e + * @param int|array $level + * @param array $serializable + */ + public function exception(Exception $e, $level = 3, array $serializable = []): void { + if (is_array($level) && empty($serializable)) { + $serializable = $level; + $level = 3; + } + + $message = ''; + if (!empty($serializable)) { + $message = json_encode($serializable); + } + + if ($level === self::$DEBUG) { + $level = (int)$this->appConfig('debug_level'); + } + + $this->logger() + ->log( + $level, + $message, + [ + 'app' => $this->setup('app'), + 'exception' => $e + ] + ); + } + + + /** + * @param string $message + * @param bool $trace + * @param array $serializable + */ + public function emergency(string $message, bool $trace = false, array $serializable = []): void { + $this->log(self::$EMERGENCY, '[emergency] ' . $message, $trace, $serializable); + } + + /** + * @param string $message + * @param bool $trace + * @param array $serializable + */ + public function alert(string $message, bool $trace = false, array $serializable = []): void { + $this->log(self::$ALERT, '[alert] ' . $message, $trace, $serializable); + } + + /** + * @param string $message + * @param bool $trace + * @param array $serializable + */ + public function warning(string $message, bool $trace = false, array $serializable = []): void { + $this->log(self::$WARNING, '[warning] ' . $message, $trace, $serializable); + } + + /** + * @param string $message + * @param bool $trace + * @param array $serializable + */ + public function notice(string $message, bool $trace = false, array $serializable = []): void { + $this->log(self::$NOTICE, '[notice] ' . $message, $trace, $serializable); + } + + /** + * @param string $message + * @param array $serializable + */ + public function debug(string $message, array $serializable = []): void { + $message = '[debug] ' . $message; + $debugLevel = (int)$this->appConfig('debug_level'); + $this->log($debugLevel, $message, ($this->appConfig('debug_trace') === '1'), $serializable); + } + + + /** + * @param int $level + * @param string $message + * @param bool $trace + * @param array $serializable + */ + public function log(int $level, string $message, bool $trace = false, array $serializable = []): void { + $opts = ['app' => $this->setup('app')]; + if ($trace) { + $opts['exception'] = new HintException($message, json_encode($serializable)); + } elseif (!empty($serializable)) { + $message .= ' -- ' . json_encode($serializable); + } + + $this->logger() + ->log($level, $message, $opts); + } + + + /** + * @return LoggerInterface + */ + public function logger(): LoggerInterface { + if (isset($this->logger) && $this->logger instanceof LoggerInterface) { + return $this->logger; + } else { + return OC::$server->get(LoggerInterface::class); + } + } +} diff --git a/lib/Tools/Traits/TNCSetup.php b/lib/Tools/Traits/TNCSetup.php new file mode 100644 index 0000000..e8f4bb7 --- /dev/null +++ b/lib/Tools/Traits/TNCSetup.php @@ -0,0 +1,107 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Traits; + +use OC; +use OCP\IConfig; + +trait TNCSetup { + use TArrayTools; + + + /** @var array */ + private $_setup = []; + + + /** + * @param string $key + * @param string $value + * + * @param string $default + * + * @return string + */ + public function setup(string $key, string $value = '', string $default = ''): string { + if ($value !== '') { + $this->_setup[$key] = $value; + } + + return $this->get($key, $this->_setup, $default); + } + + /** + * @param string $key + * @param array $value + * @param array $default + * + * @return array + */ + public function setupArray(string $key, array $value = [], array $default = []): array { + if (!empty($value)) { + $this->_setup[$key] = $value; + } + + return $this->getArray($key, $this->_setup, $default); + } + + /** + * @param string $key + * @param int $value + * @param int $default + * + * @return int + */ + public function setupInt(string $key, int $value = -999, int $default = 0): int { + if ($value !== -999) { + $this->_setup[$key] = $value; + } + + return $this->getInt($key, $this->_setup, $default); + } + + /** + * @param string $key + * + * @return string + */ + public function appConfig(string $key): string { + $app = $this->setup('app'); + if ($app === '') { + return ''; + } + + /** @var IConfig $config */ + $config = OC::$server->get(IConfig::class); + + return $config->getAppValue($app, $key, ''); + } +} diff --git a/lib/Tools/Traits/TStringTools.php b/lib/Tools/Traits/TStringTools.php new file mode 100644 index 0000000..2a35a1d --- /dev/null +++ b/lib/Tools/Traits/TStringTools.php @@ -0,0 +1,258 @@ +<?php + +declare(strict_types=1); + + +/** + * FullTextSearch - Full text search framework for Nextcloud + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2022 + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\FullTextSearch\Tools\Traits; + +use DateTime; +use Exception; + +trait TStringTools { + use TArrayTools; + + + /** + * @param int $length + * + * @return string + */ + protected function token(int $length = 15): string { + $chars = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890'; + + $str = ''; + $max = strlen($chars); + for ($i = 0; $i < $length; $i++) { + try { + $str .= $chars[random_int(0, $max - 2)]; + } catch (Exception $e) { + } + } + + return $str; + } + + + /** + * Generate uuid: 2b5a7a87-8db1-445f-a17b-405790f91c80 + * + * @param int $length + * + * @return string + */ + protected function uuid(int $length = 0): string { + $uuid = sprintf( + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), + mt_rand(0, 0xffff), mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + + if ($length > 0) { + if ($length <= 16) { + $uuid = str_replace('-', '', $uuid); + } + + $uuid = substr($uuid, 0, $length); + } + + return $uuid; + } + + + /** + * @param string $line + * @param int $length + * + * @return string + */ + protected function cut(string $line, int $length): string { + if (strlen($line) < $length) { + return $line; + } + + return substr($line, 0, $length - 5) . ' (..)'; + } + + /** + * @param string $str1 + * @param string $str2 + * @param bool $cs case sensitive ? + * + * @return string + */ + protected function commonPart(string $str1, string $str2, bool $cs = true): string { + for ($i = 0; $i < strlen($str1) && $i < strlen($str2); $i++) { + $chr1 = $str1[$i]; + $chr2 = $str2[$i]; + + if (!$cs) { + $chr1 = strtolower($chr1); + $chr2 = strtolower($chr2); + } + + if ($chr1 !== $chr2) { + break; + } + } + + return substr($str1, 0, $i); + } + + + /** + * @param string $line + * @param array $params + * + * @return string + */ + protected function feedStringWithParams(string $line, array $params): string { + $ak = array_keys($params); + foreach ($ak as $k) { + $line = str_replace('{' . $k . '}', (string)$params[$k], $line); + } + + return $line; + } + + + /** + * @param int $words + * + * @return string + */ + public function generateRandomSentence(int $words = 5): string { + $sentence = []; + for ($i = 0; $i < $words; $i++) { + $sentence[] = $this->generateRandomWord(rand(2, 12)); + } + + return implode(' ', $sentence); + } + + + /** + * @param int $length + * + * @return string + */ + public function generateRandomWord(int $length = 8): string { + $c = ['b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v']; + $v = ['a', 'e', 'i', 'o', 'u', 'y']; + + $word = []; + for ($i = 0; $i <= ($length / 2); $i++) { + $word[] = $c[array_rand($c)]; + $word[] = $v[array_rand($v)]; + } + + return implode('', $word); + } + + + /** + * @param int $bytes + * + * @return string + */ + public function humanReadable(int $bytes): string { + if ($bytes == 0) { + return '0.00 B'; + } + + $s = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; + $e = floor(log($bytes, 1024)); + + return round($bytes / pow(1024, $e), 2) . ' ' . $s[$e]; + } + + + /** + * @param int $first + * @param int $second + * @param bool $short + * + * @return string + * @throws Exception + */ + public function getDateDiff( + int $first, + int $second = 0, + bool $short = false, + array $words = [] + ): string { + if ($second === 0) { + $first = time() - $first; + $second = time(); + } + + $f = new DateTime('@' . $first); + $s = new DateTime('@' . $second); + $duration = $second - $first; + if ($short) { + $minutes = $this->get('minutes', $words, 'M'); + $hours = $this->get('hours', $words, 'H'); + $days = $this->get('days', $words, 'D'); + + if ($duration < 60) { + return $f->diff($s)->format('<1' . $minutes); + } + if ($duration < 3600) { + return $f->diff($s)->format('%i' . $minutes); + } + if ($duration < 86400) { + return $f->diff($s)->format('%h' . $hours . ', %i' . $minutes); + } + + return $f->diff($s)->format('%a' . $days . ', %h' . $hours . ', %i' . $minutes); + } + + $seconds = $this->get('seconds', $words, 'seconds'); + $minutes = $this->get('minutes', $words, 'minutes'); + $hours = $this->get('hours', $words, 'hours'); + $days = $this->get('days', $words, 'days'); + if ($duration < 60) { + return $f->diff($s)->format('%s ' . $seconds); + } + + if ($duration < 3600) { + return $f->diff($s)->format('%i ' . $minutes . ' and %s ' . $seconds); + } + + if ($duration < 86400) { + return $f->diff($s)->format('%h ' . $hours . ', %i ' . $minutes . ' and %s ' . $seconds); + } + + return $f->diff($s)->format( + '%a ' . $days . + ', %h ' . $hours . + ', %i ' . $minutes . + ' and %s ' . $seconds + ); + } +} |