322 lines
9.1 KiB
PHP
322 lines
9.1 KiB
PHP
<?php
|
|
/**
|
|
* This file is part of Avatar Privacy.
|
|
*
|
|
* Copyright 2019-2023 Peter Putzer.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* ***
|
|
*
|
|
* @package mundschenk-at/avatar-privacy
|
|
* @license http://www.gnu.org/licenses/gpl-2.0.html
|
|
*/
|
|
|
|
namespace Avatar_Privacy\Avatar_Handlers\Default_Icons\Generators;
|
|
|
|
use Avatar_Privacy\Avatar_Handlers\Default_Icons\Generator;
|
|
use Avatar_Privacy\Data_Storage\Site_Transients;
|
|
use Avatar_Privacy\Exceptions\Part_Files_Not_Found_Exception;
|
|
use Avatar_Privacy\Tools\Number_Generator;
|
|
|
|
/**
|
|
* A base class for parts-based icon generators independent of the used image
|
|
* format. Part definitions are chached using WordPress' transient mechanism.
|
|
*
|
|
* The algorithm for building icons is conceptually based on Andreas Gohr's
|
|
* MonsterId library.
|
|
*
|
|
* @since 2.3.0
|
|
*
|
|
* @author Peter Putzer <github@mundschenk.at>
|
|
*
|
|
* @phpstan-type PartType string
|
|
* @phpstan-type PartsTemplate array<PartType, array{}>
|
|
* @phpstan-type AllPossibleParts array<PartType, string[]>
|
|
* @phpstan-type RandomizedParts array<PartType, string>
|
|
* @phpstan-type AdditionalArguments array<string, mixed>
|
|
*/
|
|
abstract class Parts_Generator implements Generator {
|
|
|
|
/**
|
|
* The path to the monster parts image files.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected string $parts_dir;
|
|
|
|
/**
|
|
* An array of part types.
|
|
*
|
|
* @var string[]
|
|
*/
|
|
protected array $part_types;
|
|
|
|
/**
|
|
* Lists of files, indexed by part types.
|
|
*
|
|
* @since 2.4.0 Property renamed to $all_parts, visibility changed to private.
|
|
*
|
|
* @var array {
|
|
* @type string[] $type An array of files.
|
|
* }
|
|
*
|
|
* @phpstan-var array<string, string[]>
|
|
*/
|
|
private array $all_parts;
|
|
|
|
/**
|
|
* The random number generator.
|
|
*
|
|
* @var Number_Generator
|
|
*/
|
|
protected Number_Generator $number_generator;
|
|
|
|
/**
|
|
* The site transients handler.
|
|
*
|
|
* @var Site_Transients
|
|
*/
|
|
private Site_Transients $site_transients;
|
|
|
|
/**
|
|
* Creates a new generator.
|
|
*
|
|
* @param string $parts_dir The directory containing our image parts.
|
|
* @param string[] $part_types The valid part types for this generator.
|
|
* @param Number_Generator $number_generator A pseudo-random number generator.
|
|
* @param Site_Transients $site_transients The site transients handler.
|
|
*/
|
|
public function __construct(
|
|
$parts_dir,
|
|
array $part_types,
|
|
Number_Generator $number_generator,
|
|
Site_Transients $site_transients
|
|
) {
|
|
$this->parts_dir = $parts_dir;
|
|
$this->part_types = $part_types;
|
|
$this->number_generator = $number_generator;
|
|
$this->site_transients = $site_transients;
|
|
}
|
|
|
|
/**
|
|
* Builds an icon based on the given seed returns the image data.
|
|
*
|
|
* @param string $seed The seed data (hash).
|
|
* @param int $size The size in pixels.
|
|
*
|
|
* @return string|false
|
|
*/
|
|
public function build( $seed, $size ) {
|
|
try {
|
|
// Set randomness from seed.
|
|
$this->number_generator->seed( $seed );
|
|
|
|
// Throw the dice for avatar parts.
|
|
$parts = $this->get_randomized_parts();
|
|
|
|
// Prepare any additional arguments needed.
|
|
$args = $this->get_additional_arguments( $seed, $size, $parts );
|
|
|
|
// Build the avatar image in its final size.
|
|
return $this->get_avatar( $size, $parts, $args );
|
|
} catch ( \Exception $e ) {
|
|
// Something went wrong but don't want to mess up blog layout.
|
|
return false;
|
|
} finally {
|
|
// Reset randomness to something unknonwn.
|
|
$this->number_generator->reset();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves the "randomized" parts for the avatar being built.
|
|
*
|
|
* @return array A simple array of files, indexed by part.
|
|
*
|
|
* @throws Part_Files_Not_Found_Exception The part files could not be found.
|
|
*
|
|
* @phpstan-return RandomizedParts
|
|
*/
|
|
protected function get_randomized_parts() {
|
|
return $this->randomize_parts( $this->get_parts() );
|
|
}
|
|
|
|
/**
|
|
* Prepares any additional arguments needed for rendering the avatar image.
|
|
*
|
|
* The arguments will be passed to `render_avatar()`.
|
|
*
|
|
* @param string $seed The seed data (hash).
|
|
* @param int $size The size in pixels.
|
|
* @param array $parts The (randomized) avatar parts.
|
|
*
|
|
* @return array
|
|
*
|
|
* @phpstan-param RandomizedParts $parts
|
|
* @phpstan-return AdditionalArguments
|
|
*/
|
|
protected function get_additional_arguments( $seed, $size, array $parts ) {
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Renders the avatar from its parts in the given size, using any of the
|
|
* optional additional arguments.
|
|
*
|
|
* @param int $size The target image size in pixels.
|
|
* @param array $parts The (randomized) avatar parts.
|
|
* @param array $args Any additional arguments defined by the subclass.
|
|
*
|
|
* @return string The image data (bytes).
|
|
*
|
|
* @phpstan-param RandomizedParts $parts
|
|
* @phpstan-param AdditionalArguments $args
|
|
*/
|
|
abstract protected function get_avatar( $size, array $parts, array $args );
|
|
|
|
/**
|
|
* Throws the dice for parts.
|
|
*
|
|
* @param array $parts An array of arrays containing all parts definitions.
|
|
*
|
|
* @return array A simple array of part definitions, indexed by type.
|
|
*
|
|
* @phpstan-param AllPossibleParts $parts
|
|
* @phpstan-return RandomizedParts
|
|
*/
|
|
protected function randomize_parts( array $parts ) {
|
|
$randomized_parts = [];
|
|
|
|
// Throw the dice for every part type.
|
|
foreach ( $parts as $type => $list ) {
|
|
$randomized_parts[ $type ] = $list[ $this->get_random_part_index( $type, \count( $list ) ) ];
|
|
}
|
|
|
|
return $randomized_parts;
|
|
}
|
|
|
|
/**
|
|
* Generates a random but valid part index based on the type and number of parts.
|
|
*
|
|
* @param string $type The part type.
|
|
* @param int $count The number of different parts of the type.
|
|
*
|
|
* @return int
|
|
*/
|
|
protected function get_random_part_index( $type, $count ) {
|
|
return $this->number_generator->get( 0, $count - 1 );
|
|
}
|
|
|
|
/**
|
|
* Retrieves the avatar parts image files.
|
|
*
|
|
* @return array {
|
|
* An array of file lists indexed by the part name.
|
|
*
|
|
* @type string[] $part An array of part definitions (the exact content
|
|
* is determined by the subclasses).
|
|
* }
|
|
*
|
|
* @throws Part_Files_Not_Found_Exception The part files could not be found.
|
|
*
|
|
* @phpstan-return AllPossibleParts
|
|
*/
|
|
protected function get_parts() {
|
|
if ( empty( $this->all_parts ) ) {
|
|
// Calculate transient key.
|
|
$basename = \basename( $this->parts_dir );
|
|
$key = "avatar_privacy_{$basename}_parts";
|
|
|
|
// Check existence of transient first.
|
|
$cached_parts = $this->site_transients->get( $key );
|
|
if ( \is_array( $cached_parts ) && ! empty( $cached_parts ) ) {
|
|
/**
|
|
* The cached parts look good, let's use those.
|
|
*
|
|
* @phpstan-var AllPossibleParts $cached_parts
|
|
*/
|
|
$this->all_parts = $cached_parts;
|
|
} else {
|
|
// Look at the actual filesystem.
|
|
$this->all_parts = $this->build_parts_array();
|
|
|
|
// Only store transient if we got a result.
|
|
if ( ! empty( $this->all_parts ) ) {
|
|
$this->site_transients->set( $key, $this->all_parts, \YEAR_IN_SECONDS );
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this->all_parts;
|
|
}
|
|
|
|
/**
|
|
* Builds a sorted array of parts.
|
|
*
|
|
* @return array {
|
|
* An array of part type definitions.
|
|
*
|
|
* @type string $type The part definition list, indexed by type.
|
|
* }
|
|
*
|
|
* @throws Part_Files_Not_Found_Exception The part files could not be found.
|
|
*
|
|
* @phpstan-return AllPossibleParts
|
|
*/
|
|
protected function build_parts_array() {
|
|
// Make sure the keys are in the correct order.
|
|
$empty_parts = \array_fill_keys( $this->part_types, [] );
|
|
|
|
// Read part definitions.
|
|
$parts = $this->read_parts_from_filesystem( $empty_parts );
|
|
|
|
// Raise an exception if there were no files found.
|
|
if ( $parts === $empty_parts ) {
|
|
throw new Part_Files_Not_Found_Exception( "Could not find parts images in {$this->parts_dir}" );
|
|
}
|
|
|
|
return $this->sort_parts( $parts );
|
|
}
|
|
|
|
/**
|
|
* Retrieves an array of SVG part type definitions.
|
|
*
|
|
* @param array $parts An array of empty arrays indexed by part type.
|
|
*
|
|
* @return array The same array, but now containing the part type definitions.
|
|
*
|
|
* @phpstan-param array<PartType, array{}> $parts
|
|
* @phpstan-return AllPossibleParts
|
|
*/
|
|
abstract protected function read_parts_from_filesystem( array $parts );
|
|
|
|
/**
|
|
* Sorts the parts array to be independent of filesystem sort order.
|
|
*
|
|
* @param array $parts {
|
|
* An array of part type definitions.
|
|
*
|
|
* @type string $type The part definition list, indexed by type.
|
|
* }
|
|
*
|
|
* @return array
|
|
*
|
|
* @phpstan-param AllPossibleParts $parts
|
|
* @phpstan-return AllPossibleParts
|
|
*/
|
|
abstract protected function sort_parts( array $parts );
|
|
}
|