diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1b4c03..79b1a6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,9 +73,3 @@ jobs: if: ${{ always() && steps.install.conclusion == 'success' }} run: | composer ci:test:php:rector - - - id: tests_unit - name: Unit Tests - if: ${{ always() && steps.install.conclusion == 'success' }} - run: | - composer ci:test:php:unit diff --git a/composer.json b/composer.json index effb418..ebb4cc9 100644 --- a/composer.json +++ b/composer.json @@ -59,7 +59,7 @@ "@composer require magicsunday/webtrees-module-base:^1.0", "### Copy base module to vendor directory", "mkdir -p webtrees-fan-chart/vendor/magicsunday", - "cp -r vendor/magicsunday/webtrees-module-base webtrees-fan-chart/vendor/magicsunday/webtrees-module-base", + "cp -r .build/vendor/magicsunday/webtrees-module-base webtrees-fan-chart/vendor/magicsunday/webtrees-module-base", "### Remove all not required files from archive", "rm -rf webtrees-fan-chart/.github", "rm -rf webtrees-fan-chart/resources/js/modules", @@ -94,7 +94,7 @@ "ci:test": [ "@ci:test:php:lint", "@ci:test:php:phpstan", - "@ci:test:php:rector", + "@ci:test:php:rector" ], "module:check": [ "@ci:test" diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..aab4991 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,2 @@ +parameters: + ignoreErrors: [] diff --git a/phpstan.neon b/phpstan.neon index ebe1801..7e77f2f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,7 +1,7 @@ includes: - %currentWorkingDirectory%/.build/vendor/phpstan/phpstan-strict-rules/rules.neon - %currentWorkingDirectory%/.build/vendor/phpstan/phpstan-deprecation-rules/rules.neon -# - %currentWorkingDirectory%/phpstan-baseline.neon + - %currentWorkingDirectory%/phpstan-baseline.neon parameters: # You can currently choose from 10 levels (0 is the loosest and 9 is the strictest). diff --git a/src/Facade/DataFacade.php b/src/Facade/DataFacade.php index d294dd1..2b075c6 100644 --- a/src/Facade/DataFacade.php +++ b/src/Facade/DataFacade.php @@ -18,9 +18,9 @@ use MagicSunday\Webtrees\FanChart\Configuration; use MagicSunday\Webtrees\FanChart\Model\Node; use MagicSunday\Webtrees\FanChart\Model\NodeData; -use MagicSunday\Webtrees\FanChart\Processor\DateProcessor; -use MagicSunday\Webtrees\FanChart\Processor\ImageProcessor; -use MagicSunday\Webtrees\FanChart\Processor\NameProcessor; +use MagicSunday\Webtrees\ModuleBase\Processor\DateProcessor; +use MagicSunday\Webtrees\ModuleBase\Processor\ImageProcessor; +use MagicSunday\Webtrees\ModuleBase\Processor\NameProcessor; /** * Facade class to hide complex logic to generate the structure required to display the tree. diff --git a/src/Module.php b/src/Module.php index ac68765..a4012b9 100644 --- a/src/Module.php +++ b/src/Module.php @@ -31,9 +31,6 @@ use MagicSunday\Webtrees\FanChart\Facade\DataFacade; use MagicSunday\Webtrees\FanChart\Traits\ModuleChartTrait; use MagicSunday\Webtrees\FanChart\Traits\ModuleCustomTrait; -use MagicSunday\Webtrees\ModuleBase\Processor\DateProcessor; -use MagicSunday\Webtrees\ModuleBase\Processor\ImageProcessor; -use MagicSunday\Webtrees\ModuleBase\Processor\NameProcessor; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; diff --git a/src/Processor/DateProcessor.php b/src/Processor/DateProcessor.php deleted file mode 100644 index d77e3e5..0000000 --- a/src/Processor/DateProcessor.php +++ /dev/null @@ -1,180 +0,0 @@ - - * @license https://opensource.org/licenses/GPL-3.0 GNU General Public License v3.0 - * @link https://github.com/magicsunday/webtrees-module-base/ - */ -class DateProcessor -{ - /** - * The individual. - * - * @var Individual - */ - private Individual $individual; - - /** - * The birthdate of the individual. - * - * @var Date - */ - private Date $birthDate; - - /** - * The death date of the individual. - * - * @var Date - */ - private Date $deathDate; - - /** - * Constructor. - * - * @param Individual $individual The individual to process - */ - public function __construct(Individual $individual) - { - $this->individual = $individual; - $this->birthDate = $this->individual->getBirthDate(); - $this->deathDate = $this->individual->getDeathDate(); - } - - /** - * Removes HTML tags and converts/decodes HTML entities to their corresponding characters. - * - * @param string $value The value to decode - * - * @return string - */ - private function decodeValue(string $value): string - { - return html_entity_decode(strip_tags($value), ENT_QUOTES, 'UTF-8'); - } - - /** - * Get the year of birth. - * - * @return int - */ - public function getBirthYear(): int - { - return $this->birthDate->minimumDate()->year(); - } - - /** - * Get the year of death. - * - * @return int - */ - public function getDeathYear(): int - { - return $this->deathDate->minimumDate()->year(); - } - - /** - * Returns the formatted birthdate without HTML tags. - * - * @return string - */ - public function getBirthDate(): string - { - return $this->decodeValue( - $this->birthDate->display() - ); - } - - /** - * Returns the formatted death date without HTML tags. - * - * @return string - */ - public function getDeathDate(): string - { - return $this->decodeValue( - $this->deathDate->display() - ); - } - - /** - * Create the timespan label. - * - * @return string - */ - public function getLifetimeDescription(): string - { - if ($this->birthDate->isOK() && $this->deathDate->isOK()) { - return $this->getBirthYear() . '-' . $this->getDeathYear(); - } - - if ($this->birthDate->isOK()) { - return I18N::translate('Born: %s', (string) $this->getBirthYear()); - } - - if ($this->deathDate->isOK()) { - return I18N::translate('Died: %s', (string) $this->getDeathYear()); - } - - if ($this->individual->isDead()) { - return I18N::translate('Deceased'); - } - - return ''; - } - - /** - * Returns the marriage date of the individual. - * - * @return string - */ - public function getMarriageDate(): string - { - /** @var Family|null $family */ - $family = $this->individual->spouseFamilies()->first(); - - if ($family !== null) { - return $this->decodeValue( - $family->getMarriageDate()->display() - ); - } - - return ''; - } - - /** - * Returns the marriage date of the parents. - * - * @return string - */ - public function getMarriageDateOfParents(): string - { - /** @var Family|null $family */ - $family = $this->individual->childFamilies()->first(); - - if ($family !== null) { - return $this->decodeValue( - $family->getMarriageDate()->display() - ); - } - - return ''; - } -} diff --git a/src/Processor/ImageProcessor.php b/src/Processor/ImageProcessor.php deleted file mode 100644 index 9d55f58..0000000 --- a/src/Processor/ImageProcessor.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @license https://opensource.org/licenses/GPL-3.0 GNU General Public License v3.0 - * @link https://github.com/magicsunday/webtrees-module-base/ - */ -class ImageProcessor -{ - /** - * The module. - * - * @var ModuleCustomInterface - */ - private ModuleCustomInterface $module; - - /** - * The individual. - * - * @var Individual - */ - private Individual $individual; - - /** - * Constructor. - * - * @param ModuleCustomInterface $module The module - * @param Individual $individual The individual to process - */ - public function __construct(ModuleCustomInterface $module, Individual $individual) - { - $this->module = $module; - $this->individual = $individual; - } - - /** - * Returns the URL of a person's highlight image. - * - * @param int $width The request maximum width of the image - * @param int $height The request maximum height of the image - * @param bool $returnSilhouettes Set to TRUE to return silhouette images if this is - * also enabled in the configuration - * - * @return string - */ - public function getHighlightImageUrl( - int $width = 250, - int $height = 250, - bool $returnSilhouettes = true - ): string { - if ( - $this->individual->canShow() - && ($this->individual->tree()->getPreference('SHOW_HIGHLIGHT_IMAGES') !== '') - ) { - $mediaFile = $this->individual->findHighlightedMediaFile(); - - if ($mediaFile instanceof MediaFile) { - return $mediaFile->imageUrl($width, $height, 'contain'); - } - - if ( - $returnSilhouettes - && ($this->individual->tree()->getPreference('USE_SILHOUETTE') !== '') - ) { - return $this->module->assetUrl( - sprintf( - 'images/silhouette-%s.svg', - $this->individual->sex() - ) - ); - } - } - - return ''; - } -} diff --git a/src/Processor/NameProcessor.php b/src/Processor/NameProcessor.php deleted file mode 100644 index 0f2f0d8..0000000 --- a/src/Processor/NameProcessor.php +++ /dev/null @@ -1,258 +0,0 @@ - - * @license https://opensource.org/licenses/GPL-3.0 GNU General Public License v3.0 - * @link https://github.com/magicsunday/webtrees-module-base/ - */ -class NameProcessor -{ - /** - * The full name identifier with name placeholders. - */ - private const FULL_NAME_WITH_PLACEHOLDERS = 'fullNN'; - - /** - * The full name identifier. - */ - private const FULL_NAME = 'full'; - - /** - * The XPath identifier to extract the starred name part. - */ - private const XPATH_PREFERRED_NAME = '//span[@class="NAME"]//span[@class="starredname"]/text()'; - - /** - * The individual. - * - * @var Individual - */ - private Individual $individual; - - /** - * The individual's primary name array. - * - * @var string[] - */ - private array $primaryName; - - /** - * The DOM xpath processor. - * - * @var DOMXPath - */ - private DOMXPath $xPath; - - /** - * Constructor. - * - * @param Individual $individual The individual to process - * @param Individual|null $spouse - * @param bool $useMarriedName TRUE to return the married name instead of the primary one - */ - public function __construct( - Individual $individual, - ?Individual $spouse = null, - bool $useMarriedName = false - ) { - $this->individual = $individual; - $this->primaryName = $this->extractPrimaryName($spouse, $useMarriedName); - - // The formatted name of the individual (containing HTML) is the input to the xpath processor - $this->xPath = $this->getDomXPathInstance($this->primaryName[self::FULL_NAME]); - } - - /** - * Returns the DOMXPath instance. - * - * @param string $input The input used as xpath base - * - * @return DOMXPath - */ - private function getDomXPathInstance(string $input): DOMXPath - { - $document = new DOMDocument(); - $document->loadHTML($this->convertToHtmlEntities($input)); - - return new DOMXPath($document); - } - - /** - * Extracts the primary name from the individual. - * - * @param Individual|null $spouse - * @param bool $useMarriedName TRUE to return the married name instead of the primary one - * - * @return array - */ - private function extractPrimaryName( - ?Individual $spouse = null, - bool $useMarriedName = false - ): array { - $individualNames = $this->individual->getAllNames(); - - if ($useMarriedName) { - foreach ($individualNames as $individualName) { - if ($spouse instanceof Individual) { - foreach ($spouse->getAllNames() as $spouseName) { - if ($individualName['type'] !== '_MARNM') { - continue; - } - - if ($individualName['surn'] !== $spouseName['surn']) { - continue; - } - - return $individualName; - } - } elseif ($individualName['type'] === '_MARNM') { - return $individualName; - } - } - } - - return $individualNames[$this->individual->getPrimaryName()]; - } - - /** - * Returns the UTF-8 chars converted to HTML entities. - * - * @param string $input The input to encode - * - * @return string - */ - private function convertToHtmlEntities(string $input): string - { - return mb_encode_numericentity($input, [0x80, 0xFFFFFFF, 0, 0xFFFFFFF], 'UTF-8'); - } - - /** - * Replace name placeholders. - * - * @param string $value - * - * @return string - */ - private function replacePlaceholders(string $value): string - { - return trim( - str_replace( - [ - Individual::NOMEN_NESCIO, - Individual::PRAENOMEN_NESCIO, - ], - '…', - $value - ) - ); - } - - /** - * Splits a name into an array, removing all name placeholders. - * - * @param string $name - * - * @return string[] - */ - private function splitAndCleanName(string $name): array - { - return array_values( - array_filter( - explode( - ' ', - $this->replacePlaceholders($name) - ) - ) - ); - } - - /** - * Returns the full name of the individual without formatting of the individual parts of the name. - * All placeholders were removed as we do not need them in this module. - * - * @return string - */ - public function getFullName(): string - { - // The name of the person without formatting of the individual parts of the name. - // Remove placeholders as we do not need them in this module - return $this->replacePlaceholders($this->primaryName[self::FULL_NAME_WITH_PLACEHOLDERS]); - } - - /** - * Returns all assigned first names of the individual. - * - * @return string[] - */ - public function getFirstNames(): array - { - return $this->splitAndCleanName($this->primaryName['givn']); - } - - /** - * Returns all assigned last names of the individual. - * - * @return string[] - */ - public function getLastNames(): array - { - return $this->splitAndCleanName($this->primaryName['surn']); - } - - /** - * Returns the preferred name of the individual. - * - * @return string - */ - public function getPreferredName(): string - { - $nodeList = $this->xPath->query(self::XPATH_PREFERRED_NAME); - - if (($nodeList !== false) && ($nodeList->length > 0)) { - $nodeItem = $nodeList->item(0); - - return ($nodeItem !== null) ? ($nodeItem->nodeValue ?? '') : ''; - } - - return ''; - } - - /** - * Returns the alternative name of the individual. - * - * @param Individual $individual - * - * @return string - */ - public function getAlternateName(Individual $individual): string - { - if ( - $individual->canShowName() - && ($individual->getPrimaryName() !== $individual->getSecondaryName()) - ) { - $allNames = $individual->getAllNames(); - $alternativeName = $allNames[$individual->getSecondaryName()][self::FULL_NAME_WITH_PLACEHOLDERS]; - - return $this->replacePlaceholders($alternativeName); - } - - return ''; - } -} diff --git a/test/MultiByteTest.php b/test/MultiByteTest.php deleted file mode 100644 index df8574c..0000000 --- a/test/MultiByteTest.php +++ /dev/null @@ -1,86 +0,0 @@ - - * @license https://opensource.org/licenses/GPL-3.0 GNU General Public License v3.0 - * @link https://github.com/magicsunday/webtrees-fan-chart/ - */ -class MultiByteTest extends TestCase -{ - /** - * @return string[][] - */ - public static function convertToHtmlEntitiesDataProvider(): array - { - // [ input, expected ] - return [ - // German umlauts - [ - '
abc äöü

äöü

', - '
abc äöü

äöü

', - ], - [ - '
abc äöü

äöü

', - '
abc äöü

äöü

', - ], - - // Euro sign - [ - '€ € €', - '€ € €', - ], - - // Korean - [ - '박성욱', - '박성욱', - ], - [ - '성욱', - '성욱', - ], - ]; - } - - /** - * Tests conversion of UTF-8 characters to HTML entities. - * - * @test - * - * @dataProvider convertToHtmlEntitiesDataProvider - * - * @param string $input - * @param string $expected - * - * @return void - */ - public function convertToHtmlEntities(string $input, string $expected): void - { - $nameProcessorMock = $this->createMock(NameProcessor::class); - - $reflection = new ReflectionClass(NameProcessor::class); - $method = $reflection->getMethod('convertToHtmlEntities'); - $method->setAccessible(true); - - $result = $method->invokeArgs($nameProcessorMock, [$input]); - - self::assertSame($expected, $result); - } -}