avatar-privacy/includes/avatar-privacy/avatar-handlers/default-icons/generators/class-robohash.php

276 lines
7.5 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\Generators\Parts_Generator;
use Avatar_Privacy\Data_Storage\Site_Transients;
use Avatar_Privacy\Tools\Number_Generator;
use Avatar_Privacy\Tools\Template;
/**
* An icon generator based on the Robohash SVG library by Nimiq.
*
* The code is a new implementation based on the general idea (only color
* pre-selections have been reused).
*
* @link https://github.com/nimiq/robohash
*
* @since 2.3.0
* @since 2.4.0 Internal method render_svg removed.
*
* @author Peter Putzer <github@mundschenk.at>
*
* @phpstan-type PartType value-of<self::PARTS>
* @phpstan-type PartsTemplate array<PartType, array{}>
* @phpstan-type AllPossibleParts array<PartType, string[]>
* @phpstan-type RandomizedParts array<PartType, string>
* @phpstan-type AdditionalArguments array{ color: value-of<self::COLORS>, bg_color: value-of<self::BG_COLORS> }
*/
class Robohash extends Parts_Generator {
// Robot parts.
private const PART_BODY = 'body';
private const PART_FACE = 'face';
private const PART_EYES = 'eyes';
private const PART_MOUTH = 'mouth';
private const PART_ACCESSORY = 'accessory';
/**
* All Robot parts in their natural order.
*
* @since 2.7.0
*/
private const PARTS = [
self::PART_BODY,
self::PART_FACE,
self::PART_EYES,
self::PART_MOUTH,
self::PART_ACCESSORY,
];
const COLORS = [
'#ff9800', // orange-500.
'#E53935', // red-600.
'#FDD835', // yellow-600.
'#3f51b5', // indigo-500.
'#03a9f4', // light-blue-500.
'#9c27b0', // purple-500.
'#009688', // teal-500.
'#EC407A', // pink-400.
'#8bc34a', // light-green-500.
'#795548', // brown-500.
];
const BG_COLORS = [
/* Red */
'#FF8A80', // red-a100.
'#F48FB1', // pink-200.
'#ea80fc', // purple-a100.
/* Blue */
'#8c9eff', // indigo-a100.
'#80d8ff', // light-blue-a100.
'#CFD8DC', // blue-grey-100.
/* Green */
'#1DE9B6', // teal-a400.
'#00C853', // green-a-700.
/* Orange */
'#FF9E80', // deep-orange-a100.
'#FFE57F', // amber-a100.
];
/**
* The templating handler.
*
* @var Template
*/
private $template;
/**
* Creates a new instance.
*
* @since 2.4.0 Parameter $template added.
*
* @param Number_Generator $number_generator A pseudo-random number generator.
* @param Site_Transients $site_transients The transients handler.
* @param Template $template The templating handler.
*/
public function __construct( Number_Generator $number_generator, Site_Transients $site_transients, Template $template ) {
parent::__construct(
\dirname( \AVATAR_PRIVACY_PLUGIN_FILE ) . '/public/images/robohash',
self::PARTS,
$number_generator,
$site_transients
);
$this->template = $template;
}
/**
* 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 ) {
// Randomize colors.
return [
'color' => self::COLORS[ $this->number_generator->get( 0, \count( self::COLORS ) - 1 ) ],
'bg_color' => self::BG_COLORS[ $this->number_generator->get( 0, \count( self::BG_COLORS ) - 1 ) ],
];
}
/**
* 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
*/
protected function get_avatar( $size, array $parts, array $args ) {
// Add robot parts to arguments.
$args[ self::PART_BODY ] = $parts[ self::PART_BODY ];
$args[ self::PART_FACE ] = $parts[ self::PART_FACE ];
$args[ self::PART_EYES ] = $parts[ self::PART_EYES ];
$args[ self::PART_MOUTH ] = $parts[ self::PART_MOUTH ];
$args[ self::PART_ACCESSORY ] = $parts[ self::PART_ACCESSORY ];
return $this->template->get_partial( 'public/partials/robohash/svg.php', $args );
}
/**
* 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 PartsTemplate $parts
* @phpstan-return AllPossibleParts
*/
protected function read_parts_from_filesystem( array $parts ) {
// Get a recursive depth-first iterator over the part type directories.
$dir = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
$this->parts_dir,
\FilesystemIterator::KEY_AS_FILENAME |
\FilesystemIterator::CURRENT_AS_FILEINFO |
\FilesystemIterator::SKIP_DOTS
),
\RecursiveIteratorIterator::CHILD_FIRST
);
/**
* Iterate over the files in the parts directory.
*
* @var string $file
* @var \SplFileInfo $info
*/
foreach ( $dir as $file => $info ) {
if ( ! $info->isFile() ) {
continue;
}
/**
* Extract the part from the filename.
*
* @phpstan-var PartType $partname
*/
list( $partname, ) = \explode( '-', $file );
if ( isset( $parts[ $partname ] ) ) {
$parts[ $partname ][ $file ] = $this->prepare_svg_part(
(string) \file_get_contents( $info ) // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
);
}
}
return $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
*/
protected function sort_parts( array $parts ) {
foreach ( $parts as $key => $list ) {
\ksort( $list, \SORT_NATURAL );
$parts[ $key ] = \array_values( $list );
}
return $parts;
}
/**
* Prepares SVG elements for inclusion as robot parts.
*
* @param string $svg The part to include.
*
* @return string
*/
protected function prepare_svg_part( $svg ) {
$svg = \preg_replace(
[
'#<svg[^>]+>(.*)</svg>#',
'/#26a9e0/',
], [
'$1',
'currentColor',
],
$svg
);
return "<g transform=\"translate(0,20)\">{$svg}</g>";
}
}