diff --git a/.github/workflows/phpcs.yml b/.github/workflows/phpcs.yml new file mode 100644 index 0000000..2aff4e8 --- /dev/null +++ b/.github/workflows/phpcs.yml @@ -0,0 +1,26 @@ +name: PHP_CodeSniffer + +on: [push, pull_request] + +jobs: + run: + runs-on: ${{ matrix.operating-system }} + + strategy: + matrix: + operating-system: [ubuntu-20.04] + php-version: ['7.1', '7.4'] + + name: Testing PHP ${{ matrix.php-version }} on ${{ matrix.operating-system }} + + steps: + - uses: shivammathur/setup-php@master + with: + php-version: ${{ matrix.php-version }} + extensions: json,intl,mbstring + + - uses: actions/checkout@v2 + + - run: composer validate + - run: composer install --no-progress + - run: vendor/bin/phpcs src/ --standard=PSR12 diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..50a8529 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,26 @@ +name: PHPStan + +on: [push, pull_request] + +jobs: + run: + runs-on: ${{ matrix.operating-system }} + + strategy: + matrix: + operating-system: [ubuntu-20.04] + php-version: ['7.1', '7.4'] + + name: Testing PHP ${{ matrix.php-version }} on ${{ matrix.operating-system }} + + steps: + - uses: shivammathur/setup-php@master + with: + php-version: ${{ matrix.php-version }} + extensions: json,intl,mbstring + + - uses: actions/checkout@v2 + + - run: composer validate + - run: composer install --no-progress + - run: vendor/bin/phpstan analyse -c phpstan.neon diff --git a/README.md b/README.md index d0efcd0..c02d352 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0) -[![PHPStan](https://img.shields.io/badge/PHPStan-level%205-brightgreen.svg?style=flat)](https://github.com/phpstan/phpstan) -[![PHP_CodeSniffer](https://img.shields.io/badge/PHP_CodeSniffer-PSR12-brightgreen.svg?style=flat)](https://github.com/squizlabs/PHP_CodeSniffer) +[![PHPStan](https://github.com/magicsunday/webtrees-descendants-chart/actions/workflows/phpstan.yml/badge.svg)](https://github.com/magicsunday/webtrees-descendants-chart/actions/workflows/phpstan.yml) +[![PHP_CodeSniffer](https://github.com/magicsunday/webtrees-descendants-chart/actions/workflows/phpcs.yml/badge.svg)](https://github.com/magicsunday/webtrees-descendants-chart/actions/workflows/phpcs.yml) + [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/magicsunday/webtrees-descendants-chart/badges/quality-score.png?b=main)](https://scrutinizer-ci.com/g/magicsunday/webtrees-descendants-chart/?branch=main) [![Build Status](https://scrutinizer-ci.com/g/magicsunday/webtrees-descendants-chart/badges/build.png?b=main)](https://scrutinizer-ci.com/g/magicsunday/webtrees-descendants-chart/build-status/main) [![Code Climate](https://codeclimate.com/github/magicsunday/webtrees-descendants-chart/badges/gpa.svg)](https://codeclimate.com/github/magicsunday/webtrees-descendants-chart) [![Issue Count](https://codeclimate.com/github/magicsunday/webtrees-descendants-chart/badges/issue_count.svg)](https://codeclimate.com/github/magicsunday/webtrees-descendants-chart) + # Descendants chart This module provides an SVG descendants chart for the [webtrees](https://www.webtrees.net) genealogy application. It is capable to display up to 25 descendants generations of an individual. diff --git a/composer.json b/composer.json index 5fe3573..37234fa 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "magicsunday/webtrees-module-installer-plugin": "^1.3" }, "require-dev": { - "phpstan/phpstan": "^0.12", - "squizlabs/php_codesniffer": "^3.5" + "phpstan/phpstan": "^1.0", + "squizlabs/php_codesniffer": "^3.6" } } diff --git a/phpstan.neon b/phpstan.neon index 4359811..888af7b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,8 +1,13 @@ parameters: - # you can currently choose from 9 levels (0 is the loosest and 8 is the strictest). - level: 5 + # You can currently choose from 10 levels (0 is the loosest and 9 is the strictest). + level: 8 paths: - src - # if you have tests and want to analyze this folder, uncomment the line below + # If you have tests and want to analyze this folder, uncomment the line below # - test + + fileExtensions: + - php + + checkMissingIterableValueType: false diff --git a/src/Module.php b/src/Module.php index c6a0378..c0f33de 100644 --- a/src/Module.php +++ b/src/Module.php @@ -9,17 +9,17 @@ namespace MagicSunday\Webtrees\DescendantsChart; use Aura\Router\RouterContainer; -use Exception; use Fig\Http\Message\RequestMethodInterface; use Fisharebest\Webtrees\Auth; -use Fisharebest\Webtrees\Exceptions\IndividualNotFoundException; +use Fisharebest\Webtrees\Contracts\UserInterface; use Fisharebest\Webtrees\Family; use Fisharebest\Webtrees\I18N; use Fisharebest\Webtrees\Individual; +use Fisharebest\Webtrees\Module\DescendancyChartModule; use Fisharebest\Webtrees\Module\ModuleCustomInterface; use Fisharebest\Webtrees\Module\ModuleThemeInterface; -use Fisharebest\Webtrees\Module\DescendancyChartModule; use Fisharebest\Webtrees\Registry; +use Fisharebest\Webtrees\Tree; use Fisharebest\Webtrees\View; use MagicSunday\Webtrees\DescendantsChart\Traits\IndividualTrait; use MagicSunday\Webtrees\DescendantsChart\Traits\ModuleChartTrait; @@ -94,7 +94,9 @@ public function boot(): void ->get(self::ROUTE_DEFAULT, self::ROUTE_DEFAULT_URL, $this) ->allows(RequestMethodInterface::METHOD_POST); - $this->theme = app(ModuleThemeInterface::class); + /** @var ModuleThemeInterface $theme */ + $theme = app(ModuleThemeInterface::class); + $this->theme = $theme; View::registerNamespace($this->name(), $this->resourcesFolder() . 'views/'); View::registerCustomView('::modules/charts/chart', $this->name() . '::modules/charts/chart'); @@ -136,20 +138,23 @@ public function resourcesFolder(): string * @param ServerRequestInterface $request * * @return ResponseInterface - * @throws Exception */ public function handle(ServerRequestInterface $request): ResponseInterface { - $tree = $request->getAttribute('tree'); - $user = $request->getAttribute('user'); - $xref = $request->getAttribute('xref'); + /** @var Tree $tree */ + $tree = $request->getAttribute('tree'); + assert($tree instanceof Tree); + + $xref = $request->getAttribute('xref'); + assert(is_string($xref)); + $individual = Registry::individualFactory()->make($xref, $tree); + $individual = Auth::checkIndividualAccess($individual, false, true); - $this->configuration = new Configuration($request); + /** @var UserInterface $user */ + $user = $request->getAttribute('user'); - if ($individual === null) { - throw new IndividualNotFoundException(); - } + $this->configuration = new Configuration($request); // Convert POST requests into GET requests for pretty URLs. // This also updates the name above the form, which wont get updated if only a POST request is used @@ -164,7 +169,6 @@ public function handle(ServerRequestInterface $request): ResponseInterface ])); } - Auth::checkIndividualAccess($individual, false, true); Auth::checkComponentAccess($this, 'chart', $tree, $user); $ajax = (bool) ($request->getQueryParams()['ajax'] ?? false); @@ -233,32 +237,6 @@ private function getChartParameters(): array ]; } - /** - * Update action. - * - * @param ServerRequestInterface $request The current HTTP request - * - * @return ResponseInterface - * - * @throws Exception - */ - public function getUpdateAction(ServerRequestInterface $request): ResponseInterface - { - $this->configuration = new Configuration($request); - - $tree = $request->getAttribute('tree'); - $user = $request->getAttribute('user'); - $xref = $request->getQueryParams()['xref']; - $individual = Registry::individualFactory()->make($xref, $tree); - - Auth::checkIndividualAccess($individual, false, true); - Auth::checkComponentAccess($this, 'chart', $tree, $user); - - return response( - $this->buildJsonTree($individual) - ); - } - /** * Recursively build the data array of the individual ancestors. * @@ -274,9 +252,7 @@ private function buildJsonTree(?Individual $individual, int $generation = 1): ar return []; } - $data = $this->getIndividualData($individual, $generation); - - /** @var null|Family $family */ + $data = $this->getIndividualData($individual, $generation); $families = $individual->spouseFamilies(); if (!$families->count()) { @@ -312,12 +288,15 @@ private function buildJsonTree(?Individual $individual, int $generation = 1): ar */ private function getAjaxRoute(Individual $individual, string $xref): string { - return $this->chartUrl($individual, [ - 'ajax' => true, - 'generations' => $this->configuration->getGenerations(), - 'layout' => $this->configuration->getLayout(), - 'xref' => $xref, - ]); + return $this->chartUrl( + $individual, + [ + 'ajax' => true, + 'generations' => $this->configuration->getGenerations(), + 'layout' => $this->configuration->getLayout(), + 'xref' => $xref, + ] + ); } /** @@ -330,20 +309,13 @@ private function getAjaxRoute(Individual $individual, string $xref): string */ private function getUpdateRoute(Individual $individual): string { - return $this->chartUrl($individual, [ - 'generations' => $this->configuration->getGenerations(), - 'layout' => $this->configuration->getLayout(), -// 'xref' => $individual->xref(), - ]); - -// return route('module', [ -// 'module' => $this->name(), -// 'action' => 'update', -// 'xref' => $individual->xref(), -// 'tree' => $individual->tree()->name(), -// 'generations' => $this->configuration->getGenerations(), -// 'layout' => $this->configuration->getLayout(), -// ]); + return $this->chartUrl( + $individual, + [ + 'generations' => $this->configuration->getGenerations(), + 'layout' => $this->configuration->getLayout(), + ] + ); } /** diff --git a/src/Traits/IndividualTrait.php b/src/Traits/IndividualTrait.php index a3982ba..2247a7c 100644 --- a/src/Traits/IndividualTrait.php +++ b/src/Traits/IndividualTrait.php @@ -65,7 +65,7 @@ trait IndividualTrait * @param Individual $individual The current individual * @param int $generation The generation the person belongs to * - * @return mixed[] + * @return array */ private function getIndividualData(Individual $individual, int $generation): array { @@ -112,7 +112,6 @@ private function getIndividualData(Individual $individual, int $generation): arr 'death' => $this->decodeValue($individual->getDeathDate()->display()), 'timespan' => $this->getLifetimeDescription($individual), 'color' => $this->getColor($individual), - 'colors' => [[], []], ]; } diff --git a/src/Traits/ModuleCustomTrait.php b/src/Traits/ModuleCustomTrait.php index b9f7b4d..8700446 100644 --- a/src/Traits/ModuleCustomTrait.php +++ b/src/Traits/ModuleCustomTrait.php @@ -52,29 +52,42 @@ public function customModuleLatestVersion(): string return $this->customModuleVersion(); } - return Registry::cache()->file()->remember($this->name() . '-latest-version', function () { - try { - $client = new Client([ - 'timeout' => 3, - ]); - - $response = $client->get($this->customModuleLatestVersionUrl()); - - if ($response->getStatusCode() === StatusCodeInterface::STATUS_OK) { - $json = json_decode($response->getBody()->getContents(), true); - $version = $json['tag_name']; - - // Does the response look like a version? - if (preg_match('/^\d+\.\d+\.\d+/', $json['tag_name'])) { - return $version; + return Registry::cache() + ->file() + ->remember( + $this->name() . '-latest-version', + function (): string { + try { + $client = new Client([ + 'timeout' => 3, + ]); + + $response = $client->get($this->customModuleLatestVersionUrl()); + + if ($response->getStatusCode() === StatusCodeInterface::STATUS_OK) { + $json = json_decode( + $response->getBody()->getContents(), + true + ); + + if (is_array($json)) { + /** @var string $version */ + $version = $json['tag_name'] ?? ''; + + // Does the response look like a version? + if (preg_match('/^\d+\.\d+\.\d+/', $version)) { + return $version; + } + } + } + } catch (RequestException $exception) { + // Can't connect to the server? } - } - } catch (RequestException $exception) { - // Can't connect to the server? - } - return $this->customModuleVersion(); - }, 86400); + return $this->customModuleVersion(); + }, + 86400 + ); } public function customModuleSupportUrl(): string