diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 11f7333cf..d132734ca 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -24,7 +24,7 @@ jobs: fail-fast: false matrix: php-versions: ['8.0'] - server-versions: ['master'] + server-versions: ['stable26'] name: SQLite @@ -56,7 +56,7 @@ jobs: fail-fast: false matrix: php-versions: ['8.1'] - server-versions: ['master'] + server-versions: ['stable26'] name: MySQL @@ -99,7 +99,7 @@ jobs: fail-fast: false matrix: php-versions: ['8.2'] - server-versions: ['master'] + server-versions: ['stable26'] name: PostgreSQL diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 8fa4c8839..7bd905a6e 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -1,37 +1,37 @@ -name: Static analysis - -on: - pull_request: - push: - branches: - - master - - next - - stable* - -jobs: - psalm-master: - runs-on: ubuntu-latest - strategy: - matrix: - ocp-version: ['master'] - php-versions: ['8.0', '8.1', '8.2'] - - name: Psalm - - steps: - - uses: actions/checkout@v3 - - - name: Setup composer and PHP - uses: ./.github/actions/setup-composer - with: - php-version: ${{ matrix.php-versions }} - php-tools: composer, psalm - - - name: Install Nextcloud API - run: composer require --dev nextcloud/ocp:dev-${{ matrix.ocp-version }} - - - name: Install symfony/console - run: composer require symfony/console - - - name: Run coding standards check - run: composer run psalm +name: Static analysis + +on: + pull_request: + push: + branches: + - master + - next + - stable* + +jobs: + psalm-master: + runs-on: ubuntu-latest + strategy: + matrix: + ocp-version: ['stable26'] + php-versions: ['8.0', '8.1', '8.2'] + + name: Psalm + + steps: + - uses: actions/checkout@v3 + + - name: Setup composer and PHP + uses: ./.github/actions/setup-composer + with: + php-version: ${{ matrix.php-versions }} + php-tools: composer, psalm + + - name: Install Nextcloud API + run: composer require --dev nextcloud/ocp:dev-${{ matrix.ocp-version }} + + - name: Install symfony/console + run: composer require symfony/console + + - name: Run coding standards check + run: composer run psalm diff --git a/appinfo/info.xml b/appinfo/info.xml index b672411b5..f332319fe 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -23,7 +23,7 @@ https://raw.githubusercontent.com/nextcloud/polls/master/screenshots/edit-poll.png - + @@ -79,4 +79,4 @@ 77 - \ No newline at end of file + diff --git a/composer.json b/composer.json index 9f84556df..1a53fbfab 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,10 @@ "autoloader-suffix": "Polls", "platform": { "php": "8.0" - } + }, + "allow-plugins": { + "bamarni/composer-bin-plugin": true + } }, "autoload": { "psr-4": { @@ -32,11 +35,11 @@ } }, "require-dev": { + "doctrine/dbal": "^3.6", "league/factory-muffin": "^3.0", "league/factory-muffin-faker": "^2.0", "nextcloud/coding-standard": "^1.0", - "nextcloud/ocp": "dev-stable27", - "doctrine/dbal": "^3.6" + "nextcloud/ocp": "dev-stable26" }, "scripts": { "cs:check": "php-cs-fixer fix --dry-run --diff", diff --git a/composer.lock b/composer.lock index 4e2aafdbe..11ab0b155 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e0df7b7aa1b1a99be92fcddebc226176", + "content-hash": "fe7685b3d6c9b219254d39619780c806", "packages": [ { "name": "dflydev/dot-access-data", @@ -1259,29 +1259,28 @@ }, { "name": "nextcloud/ocp", - "version": "dev-stable27", + "version": "dev-stable26", "source": { "type": "git", "url": "https://github.com/nextcloud-deps/ocp.git", - "reference": "3beb4e9456fd71c91c299ee416e142ad89901deb" + "reference": "692e8fb9d10e591600048723c6ed5f7d33fa4275" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/3beb4e9456fd71c91c299ee416e142ad89901deb", - "reference": "3beb4e9456fd71c91c299ee416e142ad89901deb", + "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/692e8fb9d10e591600048723c6ed5f7d33fa4275", + "reference": "692e8fb9d10e591600048723c6ed5f7d33fa4275", "shasum": "" }, "require": { "php": "^7.4 || ~8.0 || ~8.1", - "psr/clock": "^1.0", - "psr/container": "^2.0.2", + "psr/container": "^1.1.1", "psr/event-dispatcher": "^1.0", - "psr/log": "^1.1.4" + "psr/log": "^1.1" }, "type": "library", "extra": { "branch-alias": { - "dev-stable27": "27.0.0-dev" + "dev-master": "26.0.0-dev" } }, "notification-url": "https://packagist.org/downloads/", @@ -1297,9 +1296,9 @@ "description": "Composer package containing Nextcloud's public API (classes, interfaces)", "support": { "issues": "https://github.com/nextcloud-deps/ocp/issues", - "source": "https://github.com/nextcloud-deps/ocp/tree/stable27" + "source": "https://github.com/nextcloud-deps/ocp/tree/stable26" }, - "time": "2024-03-05T00:31:26+00:00" + "time": "2024-01-03T00:33:21+00:00" }, { "name": "php-cs-fixer/shim", @@ -1402,77 +1401,24 @@ }, "time": "2021-02-03T23:26:27+00:00" }, - { - "name": "psr/clock", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/clock.git", - "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", - "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Clock\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for reading the clock.", - "homepage": "https://github.com/php-fig/clock", - "keywords": [ - "clock", - "now", - "psr", - "psr-20", - "time" - ], - "support": { - "issues": "https://github.com/php-fig/clock/issues", - "source": "https://github.com/php-fig/clock/tree/1.0.0" - }, - "time": "2022-11-25T14:36:26+00:00" - }, { "name": "psr/container", - "version": "2.0.2", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { "php": ">=7.4.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -1499,9 +1445,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "source": "https://github.com/php-fig/container/tree/1.1.2" }, - "time": "2021-11-05T16:47:00+00:00" + "time": "2021-11-05T16:50:12+00:00" }, { "name": "psr/log", @@ -1566,5 +1512,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/lib/Controller/AdminController.php b/lib/Controller/AdminController.php index c3b1574e3..7a4667f26 100644 --- a/lib/Controller/AdminController.php +++ b/lib/Controller/AdminController.php @@ -28,7 +28,6 @@ use OCA\Polls\AppConstants; use OCA\Polls\Db\UserMapper; use OCA\Polls\Service\PollService; -use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\TemplateResponse; use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent; @@ -52,7 +51,9 @@ public function __construct( parent::__construct($appName, $request); } - #[NoCSRFRequired] + /** + * @NoCSRFRequired + */ public function index(): TemplateResponse { Util::addScript(AppConstants::APP_ID, 'polls-main'); $this->eventDispatcher->dispatchTyped(new LoadAdditionalScriptsEvent()); diff --git a/lib/Controller/BaseApiController.php b/lib/Controller/BaseApiController.php index 508de30c8..9eb452bb5 100644 --- a/lib/Controller/BaseApiController.php +++ b/lib/Controller/BaseApiController.php @@ -50,8 +50,8 @@ public function __construct( /** * response + * @NoAdminRequired */ - #[NoAdminRequired] protected function response(Closure $callback): JSONResponse { try { return new JSONResponse($callback(), Http::STATUS_OK); @@ -62,8 +62,8 @@ protected function response(Closure $callback): JSONResponse { /** * response + * @NoAdminRequired */ - #[NoAdminRequired] protected function responseLong(Closure $callback): JSONResponse { try { return new JSONResponse($callback(), Http::STATUS_OK); @@ -74,8 +74,8 @@ protected function responseLong(Closure $callback): JSONResponse { /** * responseCreate + * @NoAdminRequired */ - #[NoAdminRequired] protected function responseCreate(Closure $callback): JSONResponse { try { return new JSONResponse($callback(), Http::STATUS_CREATED); diff --git a/lib/Controller/BaseController.php b/lib/Controller/BaseController.php index b8ee9be63..9fda8fb21 100644 --- a/lib/Controller/BaseController.php +++ b/lib/Controller/BaseController.php @@ -48,8 +48,8 @@ public function __construct( /** * response + * @NoAdminRequired */ - #[NoAdminRequired] protected function response(Closure $callback): JSONResponse { try { return new JSONResponse($callback(), Http::STATUS_OK); @@ -60,8 +60,8 @@ protected function response(Closure $callback): JSONResponse { /** * response + * @NoAdminRequired */ - #[NoAdminRequired] protected function responseLong(Closure $callback): JSONResponse { try { return new JSONResponse($callback(), Http::STATUS_OK); @@ -72,8 +72,8 @@ protected function responseLong(Closure $callback): JSONResponse { /** * responseCreate + * @NoAdminRequired */ - #[NoAdminRequired] protected function responseCreate(Closure $callback): JSONResponse { try { return new JSONResponse($callback(), Http::STATUS_CREATED); @@ -84,8 +84,8 @@ protected function responseCreate(Closure $callback): JSONResponse { /** * responseDeleteTolerant + * @NoAdminRequired */ - #[NoAdminRequired] protected function responseDeleteTolerant(Closure $callback): JSONResponse { try { return new JSONResponse($callback(), Http::STATUS_OK); diff --git a/lib/Controller/BasePublicController.php b/lib/Controller/BasePublicController.php index 6a4ab4098..ac038488e 100644 --- a/lib/Controller/BasePublicController.php +++ b/lib/Controller/BasePublicController.php @@ -32,7 +32,6 @@ use OCA\Polls\Model\Acl; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; use OCP\ISession; @@ -52,8 +51,8 @@ public function __construct( /** * response + * @NoAdminRequired */ - #[NoAdminRequired] protected function response(Closure $callback, string $token): JSONResponse { $this->updateSessionToken($token); @@ -66,8 +65,8 @@ protected function response(Closure $callback, string $token): JSONResponse { /** * response + * @NoAdminRequired */ - #[NoAdminRequired] protected function responseLong(Closure $callback, string $token): JSONResponse { $this->updateSessionToken($token); @@ -79,8 +78,8 @@ protected function responseLong(Closure $callback, string $token): JSONResponse } /** * responseCreate + * @NoAdminRequired */ - #[NoAdminRequired] protected function responseCreate(Closure $callback, string $token): JSONResponse { $this->updateSessionToken($token); diff --git a/lib/Controller/CommentApiController.php b/lib/Controller/CommentApiController.php index ee1f72b72..f76074983 100644 --- a/lib/Controller/CommentApiController.php +++ b/lib/Controller/CommentApiController.php @@ -26,9 +26,6 @@ namespace OCA\Polls\Controller; use OCA\Polls\Service\CommentService; -use OCP\AppFramework\Http\Attribute\CORS; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; -use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -46,10 +43,10 @@ public function __construct( /** * Read all comments of a poll based on the poll id and return list as array + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function list(int $pollId): JSONResponse { return $this->response(fn () => [ 'comments' => $this->commentService->list($pollId) @@ -58,10 +55,10 @@ public function list(int $pollId): JSONResponse { /** * Add comment + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function add(int $pollId, string $comment): JSONResponse { return $this->response(fn () => [ 'comment' => $this->commentService->add($comment, $pollId) @@ -70,10 +67,10 @@ public function add(int $pollId, string $comment): JSONResponse { /** * Delete comment + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function delete(int $commentId): JSONResponse { $comment = $this->commentService->get($commentId); @@ -83,10 +80,10 @@ public function delete(int $commentId): JSONResponse { /** * Restore comment + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function restore(int $commentId): JSONResponse { $comment = $this->commentService->get($commentId); diff --git a/lib/Controller/CommentController.php b/lib/Controller/CommentController.php index fdded403e..ad1a194b3 100644 --- a/lib/Controller/CommentController.php +++ b/lib/Controller/CommentController.php @@ -26,7 +26,6 @@ namespace OCA\Polls\Controller; use OCA\Polls\Service\CommentService; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -44,8 +43,8 @@ public function __construct( /** * Write a new comment to the db and returns the new comment as array + * @NoAdminRequired */ - #[NoAdminRequired] public function list(int $pollId): JSONResponse { return $this->response(fn () => [ 'comments' => $this->commentService->list($pollId) @@ -54,8 +53,8 @@ public function list(int $pollId): JSONResponse { /** * Write a new comment to the db and returns the new comment as array + * @NoAdminRequired */ - #[NoAdminRequired] public function add(int $pollId, string $message): JSONResponse { return $this->response(fn () => [ 'comment' => $this->commentService->add($message, $pollId) @@ -64,8 +63,8 @@ public function add(int $pollId, string $message): JSONResponse { /** * Delete Comment + * @NoAdminRequired */ - #[NoAdminRequired] public function delete(int $commentId): JSONResponse { $comment = $this->commentService->get($commentId); @@ -76,8 +75,8 @@ public function delete(int $commentId): JSONResponse { /** * Restore deleted Comment + * @NoAdminRequired */ - #[NoAdminRequired] public function restore(int $commentId): JSONResponse { $comment = $this->commentService->get($commentId); diff --git a/lib/Controller/OptionApiController.php b/lib/Controller/OptionApiController.php index 44b6eab81..181f4d2cb 100644 --- a/lib/Controller/OptionApiController.php +++ b/lib/Controller/OptionApiController.php @@ -26,9 +26,6 @@ namespace OCA\Polls\Controller; use OCA\Polls\Service\OptionService; -use OCP\AppFramework\Http\Attribute\CORS; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; -use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -46,20 +43,20 @@ public function __construct( /** * Get all options of given poll + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function list(int $pollId): JSONResponse { return $this->response(fn () => ['options' => $this->optionService->list($pollId)]); } /** * Add a new option + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function add(int $pollId, int $timestamp = 0, string $pollOptionText = '', int $duration = 0): JSONResponse { return $this->responseCreate(fn () => ['option' => $this->optionService->add($pollId, $timestamp, $pollOptionText, $duration)]); } @@ -67,50 +64,50 @@ public function add(int $pollId, int $timestamp = 0, string $pollOptionText = '' /** * Update option + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function update(int $optionId, int $timestamp = 0, string $pollOptionText = '', int $duration = 0): JSONResponse { return $this->response(fn () => ['option' => $this->optionService->update($optionId, $timestamp, $pollOptionText, $duration)]); } /** * Delete option + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function delete(int $optionId): JSONResponse { return $this->response(fn () => ['option' => $this->optionService->delete($optionId)]); } /** * Restore option + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function restore(int $optionId): JSONResponse { return $this->response(fn () => ['option' => $this->optionService->delete($optionId, true)]); } /** * Switch option confirmation + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function confirm(int $optionId): JSONResponse { return $this->response(fn () => ['option' => $this->optionService->confirm($optionId)]); } /** * Set order position for option + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function setOrder(int $optionId, int $order): JSONResponse { return $this->response(fn () => ['option' => $this->optionService->setOrder($optionId, $order)]); } diff --git a/lib/Controller/OptionController.php b/lib/Controller/OptionController.php index f4be9a655..604e4c08d 100644 --- a/lib/Controller/OptionController.php +++ b/lib/Controller/OptionController.php @@ -27,7 +27,6 @@ use OCA\Polls\Service\CalendarService; use OCA\Polls\Service\OptionService; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -46,8 +45,8 @@ public function __construct( /** * Get all options of given poll + * @NoAdminRequired */ - #[NoAdminRequired] public function list(int $pollId): JSONResponse { return $this->response(function () use ($pollId) { return ['options' => $this->optionService->list($pollId)]; @@ -56,80 +55,80 @@ public function list(int $pollId): JSONResponse { /** * Add a new option + * @NoAdminRequired */ - #[NoAdminRequired] public function add(int $pollId, int $timestamp = 0, string $text = '', int $duration = 0): JSONResponse { return $this->responseCreate(fn () => ['option' => $this->optionService->add($pollId, $timestamp, $text, $duration)]); } /** * Add mulitple new option + * @NoAdminRequired */ - #[NoAdminRequired] public function addBulk(int $pollId, string $text = ''): JSONResponse { return $this->responseCreate(fn () => ['options' => $this->optionService->addBulk($pollId, $text)]); } /** * Update option + * @NoAdminRequired */ - #[NoAdminRequired] public function update(int $optionId, int $timestamp, string $text, int $duration): JSONResponse { return $this->response(fn () => ['option' => $this->optionService->update($optionId, $timestamp, $text, $duration)]); } /** * Delete option + * @NoAdminRequired */ - #[NoAdminRequired] public function delete(int $optionId): JSONResponse { return $this->response(fn () => ['option' => $this->optionService->delete($optionId)]); } /** * Restore option + * @NoAdminRequired */ - #[NoAdminRequired] public function restore(int $optionId): JSONResponse { return $this->response(fn () => ['option' => $this->optionService->delete($optionId, true)]); } /** * Switch option confirmation + * @NoAdminRequired */ - #[NoAdminRequired] public function confirm(int $optionId): JSONResponse { return $this->response(fn () => ['option' => $this->optionService->confirm($optionId)]); } /** * Reorder options + * @NoAdminRequired */ - #[NoAdminRequired] public function reorder(int $pollId, array $options): JSONResponse { return $this->response(fn () => ['options' => $this->optionService->reorder($pollId, $options)]); } /** * Reorder options + * @NoAdminRequired */ - #[NoAdminRequired] public function sequence(int $optionId, int $step, string $unit, int $amount): JSONResponse { return $this->response(fn () => ['options' => $this->optionService->sequence($optionId, $step, $unit, $amount)]); } /** * Reorder options + * @NoAdminRequired */ - #[NoAdminRequired] public function shift(int $pollId, int $step, string $unit): JSONResponse { return $this->response(fn () => ['options' => $this->optionService->shift($pollId, $step, $unit)]); } /** * findCalendarEvents + * @NoAdminRequired */ - #[NoAdminRequired] public function findCalendarEvents(int $optionId, string $tz): JSONResponse { return $this->response(fn () => ['events' => $this->calendarService->getEvents($optionId)]); } diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index b994a006c..87cdbfe72 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -29,8 +29,6 @@ use OCA\Polls\AppConstants; use OCA\Polls\Service\NotificationService; use OCP\AppFramework\Controller; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; -use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\TemplateResponse; use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent; use OCP\EventDispatcher\IEventDispatcher; @@ -52,17 +50,20 @@ public function __construct( parent::__construct($appName, $request); } - - #[NoAdminRequired] - #[NoCSRFRequired] + /** + * @NoAdminRequired + * @NoCSRFRequired + */ public function index(): TemplateResponse { Util::addScript(AppConstants::APP_ID, 'polls-main'); $this->eventDispatcher->dispatchTyped(new LoadAdditionalScriptsEvent()); return new TemplateResponse(AppConstants::APP_ID, 'main'); } - #[NoAdminRequired] - #[NoCSRFRequired] + /** + * @NoAdminRequired + * @NoCSRFRequired + */ public function vote(int $id): TemplateResponse { $this->notificationService->removeNotification($id); Util::addScript(AppConstants::APP_ID, 'polls-main'); diff --git a/lib/Controller/PollApiController.php b/lib/Controller/PollApiController.php index 425c0d9d6..9be990cbf 100644 --- a/lib/Controller/PollApiController.php +++ b/lib/Controller/PollApiController.php @@ -31,9 +31,6 @@ use OCA\Polls\Service\PollService; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Http; -use OCP\AppFramework\Http\Attribute\CORS; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; -use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -52,10 +49,10 @@ public function __construct( /** * Get list of polls + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function list(): JSONResponse { try { return new JSONResponse([AppConstants::APP_ID => $this->pollService->list()], Http::STATUS_OK); @@ -68,10 +65,10 @@ public function list(): JSONResponse { /** * get poll configuration + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function get(int $pollId): JSONResponse { try { return new JSONResponse(['poll' => $this->pollService->get($pollId)], Http::STATUS_OK); @@ -84,10 +81,10 @@ public function get(int $pollId): JSONResponse { /** * Add poll + * @NoAdminRequired + * @NoCSRFRequired + * @CORS */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function add(string $type, string $title): JSONResponse { try { return new JSONResponse(['poll' => $this->pollService->add($type, $title)], Http::STATUS_CREATED); @@ -98,10 +95,10 @@ public function add(string $type, string $title): JSONResponse { /** * Update poll configuration + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function update(int $pollId, array $poll): JSONResponse { try { $this->acl->setPollId($pollId, Acl::PERMISSION_POLL_EDIT); @@ -119,10 +116,10 @@ public function update(int $pollId, array $poll): JSONResponse { /** * Switch deleted status (move to deleted polls) + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function toggleArchive(int $pollId): JSONResponse { try { return new JSONResponse(['poll' => $this->pollService->toggleArchive($pollId)], Http::STATUS_OK); @@ -135,10 +132,10 @@ public function toggleArchive(int $pollId): JSONResponse { /** * Close poll + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function close(int $pollId): JSONResponse { try { return new JSONResponse(['poll' => $this->pollService->close($pollId)], Http::STATUS_OK); @@ -151,10 +148,10 @@ public function close(int $pollId): JSONResponse { /** * Reopen poll + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function reopen(int $pollId): JSONResponse { try { return new JSONResponse(['poll' => $this->pollService->reopen($pollId)], Http::STATUS_OK); @@ -167,10 +164,10 @@ public function reopen(int $pollId): JSONResponse { /** * Delete poll + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function delete(int $pollId): JSONResponse { try { return new JSONResponse(['poll' => $this->pollService->delete($pollId)], Http::STATUS_OK); @@ -183,10 +180,10 @@ public function delete(int $pollId): JSONResponse { /** * Clone poll + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function clone(int $pollId): JSONResponse { try { return new JSONResponse(['poll' => $this->pollService->clone($pollId)], Http::STATUS_CREATED); @@ -199,11 +196,11 @@ public function clone(int $pollId): JSONResponse { /** * Transfer all polls from one user to another (change owner of poll) + * @CORS + * @NoCSRFRequired * @param string $sourceUser User to transfer polls from * @param string $destinationUser User to transfer polls to */ - #[CORS] - #[NoCSRFRequired] public function transferPolls(string $sourceUser, string $destinationUser): JSONResponse { try { return new JSONResponse(['transferred' => $this->pollService->transferPolls($sourceUser, $destinationUser)], Http::STATUS_CREATED); @@ -214,11 +211,11 @@ public function transferPolls(string $sourceUser, string $destinationUser): JSON /** * Transfer singe poll to another user (change owner of poll) + * @CORS + * @NoCSRFRequired * @param int $pollId Poll to transfer * @param string $destinationUser User to transfer the poll to */ - #[CORS] - #[NoCSRFRequired] public function transferPoll(int $pollId, string $destinationUser): JSONResponse { try { return new JSONResponse(['transferred' => $this->pollService->transferPoll($pollId, $destinationUser)], Http::STATUS_CREATED); @@ -229,10 +226,10 @@ public function transferPoll(int $pollId, string $destinationUser): JSONResponse /** * Collect email addresses from particitipants + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function getParticipantsEmailAddresses(int $pollId): JSONResponse { try { return new JSONResponse($this->pollService->getParticipantsEmailAddresses($pollId), Http::STATUS_OK); @@ -245,10 +242,10 @@ public function getParticipantsEmailAddresses(int $pollId): JSONResponse { /** * Get valid values for configuration options + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function enum(): JSONResponse { return new JSONResponse($this->pollService->getValidEnum(), Http::STATUS_OK); } diff --git a/lib/Controller/PollController.php b/lib/Controller/PollController.php index c0b3deff2..49a6de067 100644 --- a/lib/Controller/PollController.php +++ b/lib/Controller/PollController.php @@ -31,9 +31,9 @@ use OCA\Polls\Service\MailService; use OCA\Polls\Service\OptionService; use OCA\Polls\Service\PollService; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; +use OCP\Server; /** * @psalm-api @@ -52,11 +52,11 @@ public function __construct( /** * Get list of polls + * @NoAdminRequired */ - #[NoAdminRequired] public function list(): JSONResponse { return $this->response(function () { - $appSettings = new AppSettings; + $appSettings = Server::get(AppSettings::class); return [ 'list' => $this->pollService->list(), 'pollCreationAllowed' => $appSettings->getPollCreationAllowed(), @@ -67,8 +67,8 @@ public function list(): JSONResponse { /** * get complete poll + * @NoAdminRequired */ - #[NoAdminRequired] public function get(int $pollId): JSONResponse { $poll = $this->pollService->get($pollId); $this->acl->setPollId($pollId); @@ -80,16 +80,16 @@ public function get(int $pollId): JSONResponse { /** * Add poll + * @NoAdminRequired */ - #[NoAdminRequired] public function add(string $type, string $title): JSONResponse { return $this->responseCreate(fn () => $this->pollService->add($type, $title)); } /** * Update poll configuration + * @NoAdminRequired */ - #[NoAdminRequired] public function update(int $pollId, array $poll): JSONResponse { $this->acl->setPollId($pollId, Acl::PERMISSION_POLL_EDIT); return $this->response(fn () => [ @@ -100,8 +100,8 @@ public function update(int $pollId, array $poll): JSONResponse { /** * Send confirmation mails + * @NoAdminRequired */ - #[NoAdminRequired] public function sendConfirmation(int $pollId): JSONResponse { $this->acl->setPollId($pollId, Acl::PERMISSION_POLL_EDIT); return $this->response(fn () => [ @@ -111,16 +111,16 @@ public function sendConfirmation(int $pollId): JSONResponse { /** * Switch deleted status (move to deleted polls) + * @NoAdminRequired */ - #[NoAdminRequired] public function toggleArchive(int $pollId): JSONResponse { return $this->response(fn () => $this->pollService->toggleArchive($pollId)); } /** * Delete poll + * @NoAdminRequired */ - #[NoAdminRequired] public function delete(int $pollId): JSONResponse { return $this->responseDeleteTolerant(fn () => $this->pollService->delete($pollId)); @@ -128,8 +128,8 @@ public function delete(int $pollId): JSONResponse { /** * Close poll + * @NoAdminRequired */ - #[NoAdminRequired] public function close(int $pollId): JSONResponse { return $this->response(fn () => [ 'poll' => $this->pollService->close($pollId), @@ -139,8 +139,8 @@ public function close(int $pollId): JSONResponse { /** * Reopen poll + * @NoAdminRequired */ - #[NoAdminRequired] public function reopen(int $pollId): JSONResponse { return $this->response(fn () => [ 'poll' => $this->pollService->reopen($pollId), @@ -150,8 +150,8 @@ public function reopen(int $pollId): JSONResponse { /** * Clone poll + * @NoAdminRequired */ - #[NoAdminRequired] public function clone(int $pollId): JSONResponse { return $this->response(fn () => $this->clonePoll($pollId)); } @@ -171,8 +171,8 @@ public function transferPolls(string $sourceUser, string $targetUser): JSONRespo /** * Collect email addresses from particitipants + * @NoAdminRequired */ - #[NoAdminRequired] public function getParticipantsEmailAddresses(int $pollId): JSONResponse { return $this->response(fn () => $this->pollService->getParticipantsEmailAddresses($pollId)); } diff --git a/lib/Controller/PreferencesController.php b/lib/Controller/PreferencesController.php index 350d0a515..e72ae1825 100644 --- a/lib/Controller/PreferencesController.php +++ b/lib/Controller/PreferencesController.php @@ -29,8 +29,6 @@ use OCA\Polls\Service\CalendarService; use OCA\Polls\Service\PreferencesService; use OCP\AppFramework\Http; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; -use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -50,17 +48,17 @@ public function __construct( /** * Read all preferences + * @NoAdminRequired + * @NoCSRFRequired */ - #[NoAdminRequired] - #[NoCSRFRequired] public function get(): JSONResponse { return $this->response(fn () => $this->preferencesService->get()); } /** * Write preferences + * @NoAdminRequired */ - #[NoAdminRequired] public function write(array $preferences): JSONResponse { if (!$this->userMapper->getCurrentUser()->getIsLoggedIn()) { return new JSONResponse([], Http::STATUS_OK); @@ -70,9 +68,9 @@ public function write(array $preferences): JSONResponse { /** * Read all preferences + * @NoAdminRequired + * @NoCSRFRequired */ - #[NoAdminRequired] - #[NoCSRFRequired] public function getCalendars(): JSONResponse { return new JSONResponse(['calendars' => $this->calendarService->getCalendars()], Http::STATUS_OK); } diff --git a/lib/Controller/PublicController.php b/lib/Controller/PublicController.php index b96a27e76..110b58685 100644 --- a/lib/Controller/PublicController.php +++ b/lib/Controller/PublicController.php @@ -35,8 +35,6 @@ use OCA\Polls\Service\SystemService; use OCA\Polls\Service\VoteService; use OCA\Polls\Service\WatchService; -use OCP\AppFramework\Http\Attribute\NoCSRFRequired; -use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\Template\PublicTemplateResponse; use OCP\AppFramework\Http\TemplateResponse; @@ -47,6 +45,10 @@ use OCP\Util; /** + * Always use parent's classe response* methods to make sure, the token gets set correctly. + * Requesting the token inside the controller is not possible, because the token is submitted + * as a paramter and not known while contruction time + * i.e. ACL requests are not valid before calling the response* method * @psalm-api */ class PublicController extends BasePublicController { @@ -70,10 +72,10 @@ public function __construct( } /** + * @NoCSRFRequired + * @PublicPage * @return TemplateResponse|PublicTemplateResponse */ - #[PublicPage] - #[NoCSRFRequired] public function votePage(string $token) { Util::addScript(AppConstants::APP_ID, 'polls-main'); if ($this->userSession->isLoggedIn()) { @@ -87,22 +89,23 @@ public function votePage(string $token) { /** * get complete poll via token + * @PublicPage */ - #[PublicPage] public function getPoll(string $token): JSONResponse { - $this->acl->request(Acl::PERMISSION_POLL_VIEW); - - // load poll through acl - return $this->response(fn () => [ - 'acl' => $this->acl, - 'poll' => $this->acl->getPoll(), - ], $token); + return $this->response(function () { + $this->acl->request(Acl::PERMISSION_POLL_VIEW); + // load poll through acl + return [ + 'acl' => $this->acl, + 'poll' => $this->acl->getPoll(), + ]; + }, $token); } /** * Watch poll for updates + * @PublicPage */ - #[PublicPage] public function watchPoll(string $token, ?int $offset): JSONResponse { return $this->responseLong(fn () => [ 'updates' => $this->watchService->watchUpdates(offset: $offset) @@ -111,8 +114,8 @@ public function watchPoll(string $token, ?int $offset): JSONResponse { /** * Get share + * @PublicPage */ - #[PublicPage] public function getShare(string $token): JSONResponse { return $this->response(fn () => [ 'share' => $this->shareService->request($token) @@ -121,8 +124,8 @@ public function getShare(string $token): JSONResponse { /** * Get votes + * @PublicPage */ - #[PublicPage] public function getVotes(string $token): JSONResponse { return $this->response(fn () => [ 'votes' => $this->voteService->list() @@ -131,8 +134,8 @@ public function getVotes(string $token): JSONResponse { /** * Delete current user's votes + * @PublicPage */ - #[PublicPage] public function deleteUser(string $token): JSONResponse { return $this->response(fn () => [ 'deleted' => $this->voteService->delete() @@ -141,8 +144,8 @@ public function deleteUser(string $token): JSONResponse { /** * Delete current user's orphaned votes + * @PublicPage */ - #[PublicPage] public function deleteOrphanedVotes(string $token): JSONResponse { return $this->response(fn () => [ 'deleted' => $this->voteService->delete(deleteOnlyOrphaned: true) @@ -151,8 +154,8 @@ public function deleteOrphanedVotes(string $token): JSONResponse { /** * Get options + * @PublicPage */ - #[PublicPage] public function getOptions(string $token): JSONResponse { return $this->response(fn () => [ 'options' => $this->optionService->list() @@ -161,8 +164,8 @@ public function getOptions(string $token): JSONResponse { /** * Add options + * @PublicPage */ - #[PublicPage] public function addOption(string $token, int $timestamp = 0, string $text = '', int $duration = 0): JSONResponse { return $this->responseCreate(fn () => [ 'option' => $this->optionService->add( @@ -175,8 +178,8 @@ public function addOption(string $token, int $timestamp = 0, string $text = '', /** * Delete option + * @PublicPage */ - #[PublicPage] public function deleteOption(string $token, int $optionId): JSONResponse { return $this->response(fn () => [ 'option' => $this->optionService->delete($optionId) @@ -185,8 +188,8 @@ public function deleteOption(string $token, int $optionId): JSONResponse { /** * Restore option + * @PublicPage */ - #[PublicPage] public function restoreOption(string $token, int $optionId): JSONResponse { return $this->response(fn () => [ 'option' => $this->optionService->delete($optionId, true) @@ -195,8 +198,8 @@ public function restoreOption(string $token, int $optionId): JSONResponse { /** * Set Vote + * @PublicPage */ - #[PublicPage] public function setVote(int $optionId, string $setTo, string $token): JSONResponse { return $this->response(fn () => [ 'vote' => $this->voteService->set($optionId, $setTo) @@ -205,8 +208,8 @@ public function setVote(int $optionId, string $setTo, string $token): JSONRespon /** * Get Comments + * @PublicPage */ - #[PublicPage] public function getComments(string $token): JSONResponse { return $this->response(fn () => [ 'comments' => $this->commentService->list() @@ -215,8 +218,8 @@ public function getComments(string $token): JSONResponse { /** * Write a new comment to the db and returns the new comment as array + * @PublicPage */ - #[PublicPage] public function addComment(string $token, string $message): JSONResponse { return $this->response(fn () => [ 'comment' => $this->commentService->add($message) @@ -225,8 +228,8 @@ public function addComment(string $token, string $message): JSONResponse { /** * Delete Comment + * @PublicPage */ - #[PublicPage] public function deleteComment(int $commentId, string $token): JSONResponse { $comment = $this->commentService->get($commentId); return $this->response(fn () => [ @@ -236,8 +239,8 @@ public function deleteComment(int $commentId, string $token): JSONResponse { /** * Restore deleted Comment + * @PublicPage */ - #[PublicPage] public function restoreComment(int $commentId, string $token): JSONResponse { $comment = $this->commentService->get($commentId); @@ -248,8 +251,8 @@ public function restoreComment(int $commentId, string $token): JSONResponse { /** * Get subscription status + * @PublicPage */ - #[PublicPage] public function getSubscription(string $token): JSONResponse { return $this->response(fn () => [ 'subscribed' => $this->subscriptionService->get() @@ -258,8 +261,8 @@ public function getSubscription(string $token): JSONResponse { /** * subscribe + * @PublicPage */ - #[PublicPage] public function subscribe(string $token): JSONResponse { return $this->response(fn () => [ 'subscribed' => $this->subscriptionService->set(true) @@ -268,8 +271,8 @@ public function subscribe(string $token): JSONResponse { /** * Unsubscribe + * @PublicPage */ - #[PublicPage] public function unsubscribe(string $token): JSONResponse { return $this->response(fn () => [ 'subscribed' => $this->subscriptionService->set(false) @@ -279,8 +282,8 @@ public function unsubscribe(string $token): JSONResponse { /** * Validate it the user name is reserved * return false, if this username already exists as a user or as a participant of the poll + * @PublicPage */ - #[PublicPage] public function validatePublicDisplayName(string $displayName, string $token): JSONResponse { return $this->response(fn () => [ 'name' => $this->systemService->validatePublicUsername($displayName, token: $token) @@ -289,8 +292,8 @@ public function validatePublicDisplayName(string $displayName, string $token): J /** * Validate email address (simple validation) + * @PublicPage */ - #[PublicPage] public function validateEmailAddress(string $emailAddress, string $token = ''): JSONResponse { return $this->response(fn () => [ 'result' => MailService::validateEmailAddress($emailAddress), 'emailAddress' => $emailAddress @@ -299,8 +302,8 @@ public function validateEmailAddress(string $emailAddress, string $token = ''): /** * Change displayName + * @PublicPage */ - #[PublicPage] public function setDisplayName(string $token, string $displayName): JSONResponse { return $this->response(fn () => [ 'share' => $this->shareService->setDisplayname($displayName, $token) @@ -310,8 +313,8 @@ public function setDisplayName(string $token, string $displayName): JSONResponse /** * Set EmailAddress + * @PublicPage */ - #[PublicPage] public function setEmailAddress(string $token, string $emailAddress = ''): JSONResponse { return $this->response(fn () => [ 'share' => $this->shareService->setEmailAddress($this->shareService->get($token), $emailAddress) @@ -320,8 +323,8 @@ public function setEmailAddress(string $token, string $emailAddress = ''): JSONR /** * Set EmailAddress + * @PublicPage */ - #[PublicPage] public function deleteEmailAddress(string $token): JSONResponse { return $this->response(fn () => [ 'share' => $this->shareService->deleteEmailAddress($this->shareService->get($token)) @@ -331,8 +334,8 @@ public function deleteEmailAddress(string $token): JSONResponse { /** * Create a personal share from a public share * or update an email share with the username + * @PublicPage */ - #[PublicPage] public function register(string $token, string $displayName, string $emailAddress = '', string $timeZone = ''): JSONResponse { return $this->responseCreate(fn () => [ 'share' => $this->shareService->register($token, $displayName, $emailAddress, $timeZone), @@ -342,8 +345,8 @@ public function register(string $token, string $displayName, string $emailAddres /** * Sent invitation mails for a share * Additionally send notification via notifications + * @PublicPage */ - #[PublicPage] public function resendInvitation(string $token): JSONResponse { $share = $this->shareService->get($token); return $this->response(fn () => [ diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index a921fcdd5..594f5ac31 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -26,8 +26,6 @@ namespace OCA\Polls\Controller; use OCA\Polls\Service\SettingsService; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; -use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -45,9 +43,9 @@ public function __construct( /** * Read app settings + * @PublicPage + * @NoAdminRequired */ - #[NoAdminRequired] - #[PublicPage] public function getAppSettings(): JSONResponse { return $this->response(fn () => ['appSettings' => $this->settingsService->getAppSettings()]); } diff --git a/lib/Controller/ShareApiController.php b/lib/Controller/ShareApiController.php index f6324d6ea..1e79ed90d 100644 --- a/lib/Controller/ShareApiController.php +++ b/lib/Controller/ShareApiController.php @@ -27,9 +27,6 @@ use OCA\Polls\Service\MailService; use OCA\Polls\Service\ShareService; -use OCP\AppFramework\Http\Attribute\CORS; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; -use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -48,80 +45,80 @@ public function __construct( /** * Read all shares of a poll based on the poll id and return list as array + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function list(int $pollId): JSONResponse { return $this->response(fn () => ['shares' => $this->shareService->list($pollId)]); } /** * Get share by token + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function get(string $token): JSONResponse { return $this->response(fn () => ['share' => $this->shareService->get($token)]); } /** * Add share + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function add(int $pollId, string $type, string $userId = ''): JSONResponse { return $this->responseCreate(fn () => ['share' => $this->shareService->add($pollId, $type, $userId)]); } /** * Delete share + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function delete(string $token): JSONResponse { return $this->response(fn () => ['share' => $this->shareService->delete(token: $token)]); } /** * Delete share + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function restore(string $token): JSONResponse { return $this->response(fn () => ['share' => $this->shareService->delete(token: $token, restore: true)]); } /** * Lock share + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function lock(string $token): JSONResponse { return $this->response(fn () => ['share' => $this->shareService->lock(token: $token)]); } /** * Unlock share + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function unlock(string $token): JSONResponse { return $this->response(fn () => ['share' => $this->shareService->lock(token: $token, unlock: true)]); } /** * Sent invitation mails for a share + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function sendInvitation(string $token): JSONResponse { $share = $this->shareService->get($token); return $this->response(fn () => [ diff --git a/lib/Controller/ShareController.php b/lib/Controller/ShareController.php index 6870ff1e5..6a1848dd1 100644 --- a/lib/Controller/ShareController.php +++ b/lib/Controller/ShareController.php @@ -27,7 +27,6 @@ use OCA\Polls\Db\Share; use OCA\Polls\Service\ShareService; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -45,32 +44,34 @@ public function __construct( /** * List shares + * @NoAdminRequired + * + * @return JSONResponse */ - #[NoAdminRequired] public function list(int $pollId): JSONResponse { return $this->response(fn () => ['shares' => $this->shareService->list($pollId)]); } /** * Add share + * @NoAdminRequired */ - #[NoAdminRequired] public function add(int $pollId, string $type, string $userId = '', string $displayName = '', string $emailAddress = ''): JSONResponse { return $this->responseCreate(fn () => ['share' => $this->shareService->add($pollId, $type, $userId, $displayName, $emailAddress)]); } /** * Change the contraints for email addresses in public polls + * @NoAdminRequired */ - #[NoAdminRequired] public function setPublicPollEmail(string $token, string $value): JSONResponse { return $this->response(fn () => ['share' => $this->shareService->setPublicPollEmail($token, $value)]); } /** * Change Label of a public share + * @NoAdminRequired */ - #[NoAdminRequired] public function setLabel(string $token, string $label = ''): JSONResponse { return $this->response(fn () => [ 'share' => $this->shareService->setLabel($label, $token) @@ -79,24 +80,24 @@ public function setLabel(string $token, string $label = ''): JSONResponse { /** * Convert poll admin to user + * @NoAdminRequired */ - #[NoAdminRequired] public function adminToUser(string $token): JSONResponse { return $this->responseCreate(fn () => ['share' => $this->shareService->setType($token, Share::TYPE_USER)]); } /** * Convert user to poll admin + * @NoAdminRequired */ - #[NoAdminRequired] public function userToAdmin(string $token): JSONResponse { return $this->responseCreate(fn () => ['share' => $this->shareService->setType($token, Share::TYPE_ADMIN)]); } /** * Set email address + * @NoAdminRequired */ - #[NoAdminRequired] public function setEmailAddress(string $token, string $emailAddress = ''): JSONResponse { return $this->response(fn () => [ 'share' => $this->shareService->setEmailAddress($this->shareService->get($token), @@ -106,32 +107,32 @@ public function setEmailAddress(string $token, string $emailAddress = ''): JSONR /** * Delete share + * @NoAdminRequired */ - #[NoAdminRequired] public function delete(string $token): JSONResponse { return $this->response(fn () => ['share' => $this->shareService->delete(token: $token)]); } /** * Delete share + * @NoAdminRequired */ - #[NoAdminRequired] public function restore(string $token): JSONResponse { return $this->response(fn () => ['share' => $this->shareService->delete(token: $token, restore: true)]); } /** * Delete or restore share + * @NoAdminRequired */ - #[NoAdminRequired] public function lock(string $token): JSONResponse { return $this->response(fn () => ['share' => $this->shareService->lock(token: $token)]); } /** * Lock or unlock share + * @NoAdminRequired */ - #[NoAdminRequired] public function unlock(string $token): JSONResponse { return $this->response(fn () => ['share' => $this->shareService->lock(token: $token, unlock: true)]); } @@ -139,8 +140,8 @@ public function unlock(string $token): JSONResponse { /** * Send invitation mails for a share * Additionally send notification via notifications + * @NoAdminRequired */ - #[NoAdminRequired] public function sendInvitation(string $token): JSONResponse { $share = $this->shareService->get($token); return $this->response(fn () => [ @@ -152,8 +153,8 @@ public function sendInvitation(string $token): JSONResponse { /** * Send all invitation mails for a share and resolve groups * Additionally send notification via notifications + * @NoAdminRequired */ - #[NoAdminRequired] public function sendAllInvitations(int $pollId): JSONResponse { return $this->response(fn () => [ 'poll' => $pollId, @@ -163,8 +164,8 @@ public function sendAllInvitations(int $pollId): JSONResponse { /** * resolve contact group to individual shares + * @NoAdminRequired */ - #[NoAdminRequired] public function resolveGroup(string $token): JSONResponse { return $this->response(fn () => [ 'shares' => $this->shareService->resolveGroup($token) diff --git a/lib/Controller/SubscriptionApiController.php b/lib/Controller/SubscriptionApiController.php index 2fcb66c34..3c88742c6 100644 --- a/lib/Controller/SubscriptionApiController.php +++ b/lib/Controller/SubscriptionApiController.php @@ -28,9 +28,6 @@ use OCA\Polls\Exceptions\Exception; use OCA\Polls\Service\SubscriptionService; use OCP\AppFramework\Http; -use OCP\AppFramework\Http\Attribute\CORS; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; -use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -48,10 +45,10 @@ public function __construct( /** * Get subscription status + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function get(int $pollId): JSONResponse { try { return new JSONResponse([ @@ -65,10 +62,10 @@ public function get(int $pollId): JSONResponse { /** * Subscribe to poll + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function subscribe(int $pollId): JSONResponse { try { $this->subscriptionService->set(true, $pollId); @@ -83,10 +80,10 @@ public function subscribe(int $pollId): JSONResponse { /** * Unsubscribe from poll + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function unsubscribe(int $pollId): JSONResponse { try { $this->subscriptionService->set(false, $pollId); diff --git a/lib/Controller/SubscriptionController.php b/lib/Controller/SubscriptionController.php index c66157ed4..fd60b5fd1 100644 --- a/lib/Controller/SubscriptionController.php +++ b/lib/Controller/SubscriptionController.php @@ -26,7 +26,6 @@ namespace OCA\Polls\Controller; use OCA\Polls\Service\SubscriptionService; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -44,8 +43,8 @@ public function __construct( /** * Get subscription status + * @NoAdminRequired */ - #[NoAdminRequired] public function get(int $pollId): JSONResponse { return $this->response(fn () => [ 'subscribed' => $this->subscriptionService->get($pollId) @@ -54,8 +53,8 @@ public function get(int $pollId): JSONResponse { /** * subscribe + * @NoAdminRequired */ - #[NoAdminRequired] public function subscribe(int $pollId): JSONResponse { return $this->response(fn () => [ 'subscribed' => $this->subscriptionService->set(true, $pollId) @@ -64,8 +63,8 @@ public function subscribe(int $pollId): JSONResponse { /** * Unsubscribe + * @NoAdminRequired */ - #[NoAdminRequired] public function unsubscribe(int $pollId): JSONResponse { return $this->response(fn () => [ 'subscribed' => $this->subscriptionService->set(false, $pollId) diff --git a/lib/Controller/SystemController.php b/lib/Controller/SystemController.php index c4ae75fa1..ae0354ad7 100644 --- a/lib/Controller/SystemController.php +++ b/lib/Controller/SystemController.php @@ -27,7 +27,6 @@ use OCA\Polls\Service\SystemService; use OCP\AppFramework\Http; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -45,8 +44,8 @@ public function __construct( /** * Get a combined list of NC users, groups and contacts + * @NoAdminRequired */ - #[NoAdminRequired] public function userSearch(string $query = ''): JSONResponse { return new JSONResponse(['siteusers' => $this->systemService->getSiteUsersAndGroups( $query)], Http::STATUS_OK); diff --git a/lib/Controller/VoteApiController.php b/lib/Controller/VoteApiController.php index 36dcc7ecd..0236d2271 100644 --- a/lib/Controller/VoteApiController.php +++ b/lib/Controller/VoteApiController.php @@ -29,9 +29,6 @@ use OCA\Polls\Service\VoteService; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Http; -use OCP\AppFramework\Http\Attribute\CORS; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; -use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -49,10 +46,10 @@ public function __construct( /** * Read all votes of a poll based on the poll id and return list as array + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function list(int $pollId): JSONResponse { try { return new JSONResponse(['votes' => $this->voteService->list($pollId)], Http::STATUS_OK); @@ -65,10 +62,10 @@ public function list(int $pollId): JSONResponse { /** * Set vote answer + * @NoAdminRequired + * @CORS + * @NoCSRFRequired */ - #[CORS] - #[NoAdminRequired] - #[NoCSRFRequired] public function set(int $optionId, string $answer): JSONResponse { try { return new JSONResponse(['vote' => $this->voteService->set($optionId, $answer)], Http::STATUS_OK); diff --git a/lib/Controller/VoteController.php b/lib/Controller/VoteController.php index e908fe07e..315b2b8ab 100644 --- a/lib/Controller/VoteController.php +++ b/lib/Controller/VoteController.php @@ -26,8 +26,6 @@ namespace OCA\Polls\Controller; use OCA\Polls\Service\VoteService; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; -use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -45,33 +43,33 @@ public function __construct( /** * list votes per poll + * @NoAdminRequired + * @NoCSRFRequired */ - #[NoAdminRequired] - #[NoCSRFRequired] public function list(int $pollId): JSONResponse { return $this->response(fn () => ['votes' => $this->voteService->list($pollId)]); } /** * set vote answer + * @NoAdminRequired + * @NoCSRFRequired */ - #[NoAdminRequired] - #[NoCSRFRequired] public function set(int $optionId, string $setTo): JSONResponse { return $this->response(fn () => ['vote' => $this->voteService->set($optionId, $setTo)]); } /** * Remove user from poll + * @NoAdminRequired */ - #[NoAdminRequired] public function delete(int $pollId, string $userId = ''): JSONResponse { return $this->response(fn () => ['deleted' => $this->voteService->delete($pollId, $userId)]); } /** * Relete orphaned votes + * @NoAdminRequired */ - #[NoAdminRequired] public function deleteOrphaned(int $pollId, string $userId = ''): JSONResponse { return $this->response(fn () => ['deleted' => $this->voteService->delete($pollId, $userId, true)]); } diff --git a/lib/Controller/WatchController.php b/lib/Controller/WatchController.php index 725cfb520..566fdb626 100644 --- a/lib/Controller/WatchController.php +++ b/lib/Controller/WatchController.php @@ -26,8 +26,6 @@ namespace OCA\Polls\Controller; use OCA\Polls\Service\WatchService; -use OCP\AppFramework\Http\Attribute\NoAdminRequired; -use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\IRequest; @@ -45,9 +43,9 @@ public function __construct( /** * Watch poll for updates + * @NoAdminRequired + * @NoCSRFRequired */ - #[NoAdminRequired] - #[NoCSRFRequired] public function watchPoll(int $pollId, ?int $offset): JSONResponse { return $this->responseLong(fn () => ['updates' => $this->watchService->watchUpdates($pollId, $offset)]); } diff --git a/lib/Cron/JanitorCron.php b/lib/Cron/JanitorCron.php index e2c4fcd81..b922a9569 100644 --- a/lib/Cron/JanitorCron.php +++ b/lib/Cron/JanitorCron.php @@ -34,6 +34,7 @@ use OCA\Polls\Model\Settings\AppSettings; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\TimedJob; +use OCP\Server; /** * @psalm-api @@ -52,10 +53,7 @@ public function __construct( ) { parent::__construct($time); parent::setInterval(86400); // run once a day - $this->logMapper = $logMapper; - $this->pollMapper = $pollMapper; - $this->watchMapper = $watchMapper; - $this->appSettings = new AppSettings; + $this->appSettings = Server::get(AppSettings::class); } /** diff --git a/lib/Db/Poll.php b/lib/Db/Poll.php index 62442d162..fb9f56277 100644 --- a/lib/Db/Poll.php +++ b/lib/Db/Poll.php @@ -135,6 +135,9 @@ class Poll extends EntityWithUser implements JsonSerializable { protected bool $hasOrphanedVotes = false; protected int $maxDate = 0; protected int $minDate = 0; + protected int $currentUserVotes = 0; + protected string $userRole = "none"; + protected ?int $isCurrentUserLocked = 0; public function __construct() { $this->addType('created', 'int'); @@ -152,6 +155,7 @@ public function __construct() { $this->addType('lastInteraction', 'int'); $this->addType('maxDate', 'int'); $this->addType('minDate', 'int'); + $this->addType('currentUserVotes', 'int'); $this->urlGenerator = Container::queryClass(IURLGenerator::class); $this->userMapper = Container::queryClass(UserMapper::class); $this->voteMapper = Container::queryClass(VoteMapper::class); @@ -165,6 +169,7 @@ public function __construct() { public function jsonSerialize(): array { return [ 'id' => $this->getId(), + 'type' => $this->getType(), 'title' => $this->getTitle(), 'description' => $this->getDescription(), 'descriptionSafe' => $this->getDescriptionSafe(), @@ -182,13 +187,14 @@ public function jsonSerialize(): array { 'optionLimit' => $this->getOptionLimit(), 'proposalsExpire' => $this->getProposalsExpire(), 'showResults' => $this->getShowResults() === 'expired' ? Poll::SHOW_RESULTS_CLOSED : $this->getShowResults(), - 'type' => $this->getType(), 'useNo' => $this->getUseNo(), 'voteLimit' => $this->getVoteLimit(), 'lastInteraction' => $this->getLastInteraction(), 'summary' => [ - 'orphanedVotes' => count($this->voteMapper->findOrphanedByPollandUser($this->id, $this->userMapper->getCurrentUserCached()->getId())), - 'yesByCurrentUser' => count($this->voteMapper->getYesVotesByParticipant($this->getPollId(), $this->userMapper->getCurrentUserCached()->getId())), + 'orphanedVotes' => $this->getCurrentUserOrphanedVotes(), + 'yesByCurrentUser' => $this->getCurrentUserYesVotes(), + 'countVotes' => $this->getCurrentUserCountVotes(), + 'userRole' => $this->getUserRole(), ], ]; } @@ -227,6 +233,13 @@ public function getExpired(): bool { ); } + public function getUserRole(): string { + if ($this->userMapper->getCurrentUser()->getId() === $this->getOwner()) { + return 'owner'; + } + return $this->userRole; + } + public function getVoteUrl(): string { return $this->urlGenerator->linkToRouteAbsolute( AppConstants::APP_ID . '.page.vote', @@ -329,13 +342,28 @@ public function getRelevantThresholdNet(): int { ); } + public function getCurrentUserCountVotes(): int { + return $this->currentUserVotes; + } + + public function getIsCurrentUserLocked(): bool { + return (bool) $this->isCurrentUserLocked; + } + /** * @psalm-return int<0, max> */ - public function getOrphanedVotes(): int { + public function getCurrentUserOrphanedVotes(): int { return count($this->voteMapper->findOrphanedByPollandUser($this->id, $this->userMapper->getCurrentUserCached()->getId())); } + /** + * @psalm-return int<0, max> + */ + public function getCurrentUserYesVotes(): int { + return count($this->voteMapper->getYesVotesByParticipant($this->getPollId(), $this->userMapper->getCurrentUserCached()->getId())); + } + public function getDeadline(): int { // if expiration is set return expiration date if ($this->getExpire()) { diff --git a/lib/Db/PollMapper.php b/lib/Db/PollMapper.php index 93718d24c..12726301f 100644 --- a/lib/Db/PollMapper.php +++ b/lib/Db/PollMapper.php @@ -40,7 +40,10 @@ class PollMapper extends QBMapper { /** * @psalm-suppress PossiblyUnusedMethod */ - public function __construct(IDBConnection $db) { + public function __construct( + IDBConnection $db, + private UserMapper $userMapper, + ) { parent::__construct($db, Poll::TABLE, Poll::class); } @@ -167,6 +170,7 @@ public function deleteByUserId(string $userId): void { * Build the enhanced query with joined tables */ protected function buildQuery(): IQueryBuilder { + $currentUserId = $this->userMapper->getCurrentUser()->getId(); $qb = $this->db->getQueryBuilder(); $qb->select(self::TABLE . '.*') @@ -174,10 +178,33 @@ protected function buildQuery(): IQueryBuilder { // ->groupBy(self::TABLE . '.id') ->from($this->getTableName(), self::TABLE); $this->joinOptionsForMaxDate($qb, self::TABLE); + $this->joinCurrentUserVotes($qb, self::TABLE, $currentUserId); + $this->joinUserRole($qb, self::TABLE, $currentUserId); $qb->groupBy(self::TABLE . '.id'); return $qb; } + /** + * Joins options to evaluate min and max option date for date polls + * if text poll or no options are set, + * the min value is the current time, + * the max value is null + */ + protected function joinUserRole(IQueryBuilder &$qb, string $fromAlias, string $currentUserId): void { + $joinAlias = 'shares'; + $qb->addSelect($qb->createFunction('coalesce(' . $joinAlias . '.type, "") AS user_role')); + + $qb->leftJoin( + $fromAlias, + Share::TABLE, + $joinAlias, + $qb->expr()->andX( + $qb->expr()->eq($fromAlias . '.id', $joinAlias . '.poll_id'), + $qb->expr()->eq($joinAlias . '.user_id', $qb->createNamedParameter($currentUserId, IQueryBuilder::PARAM_STR)), + ) + ); + } + /** * Joins options to evaluate min and max option date for date polls * if text poll or no options are set, @@ -188,8 +215,6 @@ protected function joinOptionsForMaxDate(IQueryBuilder &$qb, string $fromAlias): $joinAlias = 'options'; $saveMin = (string) time(); - // force value into a MIN function to avoid grouping errors - // $qb->selectAlias($qb->func()->max($joinAlias . '.timestamp'), 'max_date'); $qb->addSelect($qb->createFunction('coalesce(MAX(' . $joinAlias . '.timestamp), 0) AS max_date')) ->addSelect($qb->createFunction('coalesce(MIN(' . $joinAlias . '.timestamp), ' . $saveMin . ') AS min_date')); @@ -201,4 +226,26 @@ protected function joinOptionsForMaxDate(IQueryBuilder &$qb, string $fromAlias): ); } + /** + * Joins options to evaluate min and max option date for date polls + * if text poll or no options are set, + * the min value is the current time, + * the max value is null + */ + protected function joinCurrentUserVotes(IQueryBuilder &$qb, string $fromAlias, $currentUserId): void { + $joinAlias = 'user_vote'; + // force value into a MIN function to avoid grouping errors + $qb->selectAlias($qb->func()->count($joinAlias . '.vote_answer'), 'current_user_votes'); + + $qb->leftJoin( + $fromAlias, + Vote::TABLE, + $joinAlias, + $qb->expr()->andX( + $qb->expr()->eq($joinAlias . '.poll_id', $fromAlias . '.id'), + $qb->expr()->eq($joinAlias . '.user_id', $qb->createNamedParameter($currentUserId, IQueryBuilder::PARAM_STR)), + ) + ); + } + } diff --git a/lib/Db/Share.php b/lib/Db/Share.php index 868f7b42c..6c077681b 100644 --- a/lib/Db/Share.php +++ b/lib/Db/Share.php @@ -27,9 +27,9 @@ use JsonSerializable; use OCA\Polls\AppConstants; -use OCA\Polls\Helper\Container; use OCA\Polls\Model\Settings\AppSettings; use OCP\IURLGenerator; +use OCP\Server; /** * @method int getId() @@ -145,8 +145,8 @@ public function __construct() { $this->addType('locked', 'int'); $this->addType('reminderSent', 'int'); $this->addType('deleted', 'int'); - $this->urlGenerator = Container::queryClass(IURLGenerator::class); - $this->appSettings = new AppSettings; + $this->urlGenerator = Server::get(IURLGenerator::class); + $this->appSettings = Server::get(AppSettings::class); } /** diff --git a/lib/Db/TableManager.php b/lib/Db/TableManager.php index 730ff3172..56c88b808 100644 --- a/lib/Db/TableManager.php +++ b/lib/Db/TableManager.php @@ -179,14 +179,14 @@ public function createTable(string $tableName, array $columns): array { foreach ($columns as $columnName => $columnDefinition) { if ($table->hasColumn($columnName)) { $column = $table->getColumn($columnName); - if (Type::lookupName($column->getType()) !== $columnDefinition['type']) { - $messages[] = 'Migrated type of ' . $table->getName() . '[\'' . $columnName . '\'] from ' . Type::lookupName($column->getType()) . ' to ' . $columnDefinition['type']; + if ($column->getType()->getName() !== $columnDefinition['type']) { + $messages[] = 'Migrated type of ' . $table->getName() . '[\'' . $columnName . '\'] from ' . $column->getType()->getName() . ' to ' . $columnDefinition['type']; $column->setType(Type::getType($columnDefinition['type'])); } $column->setOptions($columnDefinition['options']); // force change to current options definition - $table->modifyColumn($columnName, $columnDefinition['options']); + $table->changeColumn($columnName, $columnDefinition['options']); } else { $table->addColumn($columnName, $columnDefinition['type'], $columnDefinition['options']); $messages[] = 'Added ' . $table->getName() . ', ' . $columnName . ' (' . $columnDefinition['type'] . ')'; diff --git a/lib/Db/UserMapper.php b/lib/Db/UserMapper.php index 628aedd23..6a2e03ec9 100644 --- a/lib/Db/UserMapper.php +++ b/lib/Db/UserMapper.php @@ -127,13 +127,16 @@ public function getParticipant(string $userId, ?int $pollId = null): UserBase { // just catch and continue if not found and try to find user by share; } - try { - $share = $this->getShareByPollAndUser($userId, $pollId); - return $this->getUserFromShare($share); - } catch (ShareNotFoundException $e) { - // User seems to be probaly deleted, use fake share - return new Ghost($userId); + if ($pollId !== null) { + try { + $share = $this->getShareByPollAndUser($userId, $pollId); + return $this->getUserFromShare($share); + } catch (ShareNotFoundException $e) { + // User seems to be probably deleted, use fake share + return new Ghost($userId); + } } + return new Ghost($userId); } /** @@ -175,7 +178,7 @@ public function getUserFromUserBase(string $userId, ?int $pollId = null): User { if ($user instanceof IUser) { try { // check if we find a share, where the user got admin rights for the particular poll - if ($this->getShareByPollAndUser($userId, $pollId)->getType() === Share::TYPE_ADMIN) { + if (($pollId !== null) && $this->getShareByPollAndUser($userId, $pollId)->getType() === Share::TYPE_ADMIN) { return new Admin($userId); } } catch (Exception $e) { @@ -215,15 +218,15 @@ public function getUserObject(string $type, string $id, string $displayName = '' private function getShareByToken(string $token): Share { $qb = $this->db->getQueryBuilder(); - + $qb->select('*') ->from($this->getTableName()) ->where($qb->expr()->eq('token', $qb->createNamedParameter($token, IQueryBuilder::PARAM_STR))); - + return $this->findEntity($qb); } - private function getShareByPollAndUser(string $userId, ?int $pollId = null): Share { + private function getShareByPollAndUser(string $userId, int $pollId): Share { $qb = $this->db->getQueryBuilder(); $qb->select('*') diff --git a/lib/Db/VoteMapper.php b/lib/Db/VoteMapper.php index f614aa60e..85119df66 100644 --- a/lib/Db/VoteMapper.php +++ b/lib/Db/VoteMapper.php @@ -126,19 +126,6 @@ public function findParticipantsByPoll(int $pollId): array { return $this->findEntities($qb); } - - /** - * @throws \OCP\AppFramework\Db\DoesNotExistException if not found - * @return Vote[] - * @psalm-return array - */ - public function findParticipantsVotes(int $pollId, string $userId): array { - $qb = $this->buildQuery(); - $qb->andWhere($qb->expr()->eq(self::TABLE . '.poll_id', $qb->createNamedParameter($pollId, IQueryBuilder::PARAM_INT))) - ->andWhere($qb->expr()->eq(self::TABLE . '.user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))); - return $this->findEntities($qb); - } - public function deleteByPollAndUserId(int $pollId, string $userId): void { $qb = $this->db->getQueryBuilder(); $qb->delete($this->getTableName()) diff --git a/lib/Helper/Container.php b/lib/Helper/Container.php index 0a1d37a29..0faa8b2da 100644 --- a/lib/Helper/Container.php +++ b/lib/Helper/Container.php @@ -31,34 +31,27 @@ use OCA\Polls\Db\Share; use OCA\Polls\Db\ShareMapper; use OCP\App\IAppManager; -use OCP\AppFramework\App; use OCP\IL10N; use OCP\L10N\IFactory; -use Psr\Container\ContainerInterface; +use OCP\Server; abstract class Container { - public static function getContainer(): ContainerInterface { - $app = new App(AppConstants::APP_ID); - return $app->getContainer(); - } - public static function queryClass(string $class): mixed { - return self::getContainer()->get($class); + return Server::get($class); } public static function queryPoll(int $pollId): Poll { - return self::queryClass(PollMapper::class)->find($pollId); + return Server::get(PollMapper::class)->find($pollId); } public static function findShare(int $pollId, string $userId): Share { - return self::queryClass(ShareMapper::class) - ->findByPollAndUser($pollId, $userId); + return Server::get(ShareMapper::class)->findByPollAndUser($pollId, $userId); } public static function getL10N(?string $lang = null): IL10N { - return self::queryClass(IFactory::class)->get(AppConstants::APP_ID, $lang); + return Server::get(IFactory::class)->get(AppConstants::APP_ID, $lang); } public static function isAppEnabled(string $app): bool { - return self::queryClass(IAppManager::class)->isEnabledForUser($app); + return Server::get(IAppManager::class)->isEnabledForUser($app); } } diff --git a/lib/Middleware/RequestAttributesMiddleware.php b/lib/Middleware/RequestAttributesMiddleware.php index 29f732c6a..845aa17e9 100644 --- a/lib/Middleware/RequestAttributesMiddleware.php +++ b/lib/Middleware/RequestAttributesMiddleware.php @@ -3,7 +3,6 @@ namespace OCA\Polls\Middleware; use OCA\Polls\AppConstants; -use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Middleware; use OCP\IRequest; @@ -24,7 +23,7 @@ public function __construct( ) { } - public function beforeController(Controller $controller, string $methodName): void { + public function beforeController($controller, $methodName): void { $reflectionMethod = new ReflectionMethod($controller, $methodName); $clientId = $this->request->getHeader(self::CLIENT_ID_KEY); $clientTimeZone = $this->request->getHeader(self::TIME_ZONE_KEY); diff --git a/lib/Migration/TableSchema.php b/lib/Migration/TableSchema.php index 0bd43720e..c9d9cab5d 100644 --- a/lib/Migration/TableSchema.php +++ b/lib/Migration/TableSchema.php @@ -283,13 +283,13 @@ public static function createOrUpdateSchema(ISchemaWrapper &$schema): array { if ($table->hasColumn($columnName)) { $column = $table->getColumn($columnName); $column->setOptions($columnDefinition['options']); - if (Type::lookupName($column->getType()) !== $columnDefinition['type']) { + if ($column->getType()->getName() !== $columnDefinition['type']) { $messages[] = 'Migrating type of ' . $tableName . ', ' . $columnName . ' to ' . $columnDefinition['type']; $column->setType(Type::getType($columnDefinition['type'])); } // force change to current options definition - $table->modifyColumn($columnName, $columnDefinition['options']); + $table->changeColumn($columnName, $columnDefinition['options']); } else { $table->addColumn($columnName, $columnDefinition['type'], $columnDefinition['options']); } diff --git a/lib/Migration/Version060100Date20240209073304.php b/lib/Migration/Version060100Date20240209073304.php index b904caa01..7e1bbab2f 100644 --- a/lib/Migration/Version060100Date20240209073304.php +++ b/lib/Migration/Version060100Date20240209073304.php @@ -98,14 +98,14 @@ public function createTable(string $tableName, array $columns): array { foreach ($columns as $columnName => $columnDefinition) { if ($table->hasColumn($columnName)) { $column = $table->getColumn($columnName); - if (Type::lookupName($column->getType()) !== $columnDefinition['type']) { - $messages[] = 'Migrated type of ' . $table->getName() . '[\'' . $columnName . '\'] from ' . Type::lookupName($column->getType()) . ' to ' . $columnDefinition['type']; + if ($column->getType()->getName() !== $columnDefinition['type']) { + $messages[] = 'Migrated type of ' . $table->getName() . '[\'' . $columnName . '\'] from ' . $column->getType()->getName() . ' to ' . $columnDefinition['type']; $column->setType(Type::getType($columnDefinition['type'])); } $column->setOptions($columnDefinition['options']); // force change to current options definition - $table->modifyColumn($columnName, $columnDefinition['options']); + $table->changeColumn($columnName, $columnDefinition['options']); } else { $table->addColumn($columnName, $columnDefinition['type'], $columnDefinition['options']); $messages[] = 'Added ' . $table->getName() . ', ' . $columnName . ' (' . $columnDefinition['type'] . ')'; diff --git a/lib/Model/Acl.php b/lib/Model/Acl.php index 00148e954..e4ea7087e 100644 --- a/lib/Model/Acl.php +++ b/lib/Model/Acl.php @@ -33,7 +33,6 @@ use OCA\Polls\Db\Share; use OCA\Polls\Db\ShareMapper; use OCA\Polls\Db\UserMapper; -use OCA\Polls\Db\VoteMapper; use OCA\Polls\Exceptions\ForbiddenException; use OCA\Polls\Exceptions\InvalidPollIdException; use OCA\Polls\Exceptions\NotFoundException; @@ -70,6 +69,8 @@ class Acl implements JsonSerializable { public const PERMISSION_ALL_ACCESS = 'allAccess'; private ?int $pollId = null; private ?UserBase $currentUser = null; + // Cache whether the current poll has shares + private bool $noShare = false; /** @@ -82,14 +83,10 @@ public function __construct( private ISession $session, private ShareMapper $shareMapper, private UserMapper $userMapper, - private VoteMapper $voteMapper, private ?Poll $poll = null, private ?Share $share = null, ) { $this->pollId = null; - $this->poll = $poll; - $this->share = $share; - $this->appSettings = new AppSettings; } /** @@ -148,13 +145,26 @@ public function setPollId(?int $pollId = null, string $permission = self::PERMIS } else { $this->pollId = $pollId; } - + $this->loadPoll(); $this->request($permission); return $this; } + /** + * Set poll id and load poll + * @return $this + */ + public function setPoll(Poll $poll, string $permission = self::PERMISSION_POLL_VIEW): static { + $this->pollId = $poll->getId(); + $this->poll = $poll; + $this->noShare = false; + $this->request($permission); + + return $this; + } + public function getPoll(): ?Poll { if ($this->getToken()) { // first verify working share @@ -173,7 +183,7 @@ public function getShare(): ?Share { return $this->share; } - + /** * load poll * @throws NotFoundException Thrown if poll not found @@ -187,6 +197,7 @@ private function loadPoll(): void { try { // otherwise load poll from db $this->poll = $this->pollMapper->find((int) $this->pollId); + $this->noShare = false; } catch (DoesNotExistException $e) { throw new NotFoundException('Error loading poll with id ' . $this->pollId); } @@ -199,21 +210,31 @@ private function loadPoll(): void { * and the pollId will get set to the share's pollId */ private function loadShare(): void { + if ($this->noShare) { + throw new ShareNotFoundException('No token was set for ACL'); + } + // no token in session, try to find a user, who matches if (!$this->getToken()) { if ($this->getCurrentUser()->getIsLoggedIn()) { // search for logged in user's share, load it and return - $this->share = $this->shareMapper->findByPollAndUser($this->getPollId(), $this->getUserId()); + try { + $this->share = $this->shareMapper->findByPollAndUser($this->getPollId(), $this->getUserId()); + } catch (\Throwable $ex) { + $this->noShare = true; + throw $ex; + } // store share in session for further validations // $this->session->set(AppConstants::SESSION_KEY_SHARE_TOKEN, $this->share->getToken()); return; } else { $this->share = new Share(); + $this->noShare = true; // must fail, if no token is present and not logged in throw new ShareNotFoundException('No token was set for ACL'); } } - + // if share is already cached, verify against session token if ($this->share?->getToken() === $this->getToken()) { return; @@ -224,7 +245,7 @@ private function loadShare(): void { // ensure, poll and currentUser get reset $this->poll = null; $this->currentUser = null; - + // set the poll id based on the share $this->pollId = $this->share->getPollId(); } @@ -342,9 +363,7 @@ private function getIsInvolved(): bool { * Returns true, if the current user is already a particitipant of the current poll. */ private function getIsParticipant(): bool { - return count( - $this->voteMapper->findParticipantsVotes($this->getPollId(), $this->getUserId()) - ) > 0; + return $this->getPoll()->getCurrentUserCountVotes() > 0; } /** @@ -502,7 +521,7 @@ private function getAllowAddOptions(): bool { * @return bool|null */ private function getAllowDeleteOption(?string $optionOwner, ?int $pollId) { - + if (!$pollId) { $this->logger->warning('Poll id missing'); return false; @@ -519,7 +538,7 @@ private function getAllowDeleteOption(?string $optionOwner, ?int $pollId) { $this->logger->warning('Option owner missing'); return false; } - + if ($this->matchUser($optionOwner)) { return true; diff --git a/lib/Model/Settings/AppSettings.php b/lib/Model/Settings/AppSettings.php index d0451c763..6fce8d364 100644 --- a/lib/Model/Settings/AppSettings.php +++ b/lib/Model/Settings/AppSettings.php @@ -27,7 +27,6 @@ use JsonSerializable; use OCA\Polls\AppConstants; -use OCA\Polls\Helper\Container; use OCA\Polls\Model\Group\Group; use OCP\IConfig; use OCP\IGroupManager; @@ -64,16 +63,15 @@ class AppSettings implements JsonSerializable { public const SETTING_UPDATE_TYPE_PERIODIC_POLLING = 'periodicPolling'; public const SETTING_UPDATE_TYPE_DEFAULT = self::SETTING_UPDATE_TYPE_NO_POLLING; - private IConfig $config; - private IGroupManager $groupManager; - private IUserSession $session; private string $userId = ''; + + public function __construct( + private IConfig $config, + private IGroupManager $groupManager, + private IUserSession $session, - public function __construct() { - $this->config = Container::queryClass(IConfig::class); - $this->session = Container::queryClass(IUserSession::class); - $this->userId = Container::queryClass(IUserSession::class)->getUser()?->getUId() ?? ''; - $this->groupManager = Container::queryClass(IGroupManager::class); + ) { + $this->userId = $this->session->getUser()?->getUId() ?? ''; } // Getters diff --git a/lib/Model/UserBase.php b/lib/Model/UserBase.php index 875014ad8..a757d2805 100644 --- a/lib/Model/UserBase.php +++ b/lib/Model/UserBase.php @@ -43,6 +43,7 @@ use OCP\IGroupManager; use OCP\IL10N; use OCP\IUserSession; +use OCP\Server; use OCP\Share\IShare; class UserBase implements \JsonSerializable { @@ -89,11 +90,11 @@ public function __construct( ) { $this->icon = 'icon-share'; $this->l10n = Container::getL10N(); - $this->groupManager = Container::queryClass(IGroupManager::class); - $this->timeZone = Container::queryClass(IDateTimeZone::class); - $this->userMapper = Container::queryClass(UserMapper::class); - $this->userSession = Container::queryClass(IUserSession::class); - $this->appSettings = Container::queryClass(AppSettings::class); + $this->groupManager = Server::get(IGroupManager::class); + $this->timeZone = Server::get(IDateTimeZone::class); + $this->userMapper = Server::get(UserMapper::class); + $this->userSession = Server::get(IUserSession::class); + $this->appSettings = Server::get(AppSettings::class); } public function getId(): string { diff --git a/lib/Service/PollService.php b/lib/Service/PollService.php index c8027c804..22e6d5ffa 100644 --- a/lib/Service/PollService.php +++ b/lib/Service/PollService.php @@ -81,7 +81,7 @@ public function list(): array { $this->preferences = $this->preferencesService->get(); foreach ($polls as $poll) { try { - $this->acl->setPollId($poll->getId()); + $this->acl->setPoll($poll); $relevantThreshold = $poll->getRelevantThresholdNet() + $this->preferences->getRelevantOffsetTimestamp(); // mix poll settings, currentUser attributes, permissions and relevantThreshold into one array diff --git a/lib/Service/SettingsService.php b/lib/Service/SettingsService.php index f27cc2558..2df736761 100644 --- a/lib/Service/SettingsService.php +++ b/lib/Service/SettingsService.php @@ -28,13 +28,11 @@ use OCA\Polls\Model\Settings\AppSettings; class SettingsService { - private AppSettings $appSettings; - + /** * @psalm-suppress PossiblyUnusedMethod */ - public function __construct() { - $this->appSettings = new AppSettings; + public function __construct(private AppSettings $appSettings) { } /** diff --git a/lib/Service/WatchService.php b/lib/Service/WatchService.php index a60af2cdb..c4518efdd 100644 --- a/lib/Service/WatchService.php +++ b/lib/Service/WatchService.php @@ -36,9 +36,7 @@ use OCP\ISession; class WatchService { - private AppSettings $appSettings; - private Watch $watch; - + /** * @psalm-suppress PossiblyUnusedMethod */ @@ -46,9 +44,9 @@ public function __construct( private ISession $session, private WatchMapper $watchMapper, private Acl $acl, + private AppSettings $appSettings, + private Watch $watch, ) { - $this->appSettings = new AppSettings; - $this->watch = new Watch; } /** diff --git a/src/js/store/modules/polls.js b/src/js/store/modules/polls.js index 515cdbda6..951805fac 100644 --- a/src/js/store/modules/polls.js +++ b/src/js/store/modules/polls.js @@ -32,6 +32,7 @@ const state = { isPollCreationAllowed: false, isComboAllowed: false, currentCategoryId: 'all', + pollsLoading: false, sort: { by: 'created', reverse: true, @@ -141,6 +142,10 @@ const mutations = { Object.assign(state, payload) }, + setLoading(state, loading) { + state.pollsLoading = loading ?? true + }, + setFilter(state, payload) { state.currentCategoryId = payload.currentCategoryId }, @@ -195,6 +200,7 @@ const actions = { async list(context) { try { + context.commit('setLoading') const response = await PollsAPI.getPolls() context.commit('set', { list: response.data.list }) context.commit('setPollCreationAllowed', { pollCreationAllowed: response.data.pollCreationAllowed }) @@ -203,6 +209,8 @@ const actions = { if (e?.code === 'ERR_CANCELED') return console.error('Error loading polls', { error: e.response }) throw e + } finally { + context.commit('setLoading', false) } }, } diff --git a/src/js/views/PollList.vue b/src/js/views/PollList.vue index c19a96706..38f30ed74 100644 --- a/src/js/views/PollList.vue +++ b/src/js/views/PollList.vue @@ -30,7 +30,11 @@
- + + + -
-