aboutsummaryrefslogtreecommitdiff
path: root/lib/classes/Icon.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/classes/Icon.php')
-rw-r--r--lib/classes/Icon.php395
1 files changed, 395 insertions, 0 deletions
diff --git a/lib/classes/Icon.php b/lib/classes/Icon.php
new file mode 100644
index 0000000..6c586a0
--- /dev/null
+++ b/lib/classes/Icon.php
@@ -0,0 +1,395 @@
+<?php
+/**
+ * Icon class is used to create icon objects which can be rendered as
+ * svg. Output will be html. Optionally, the icon can be rendered
+ * as a css background.
+ *
+ * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
+ * @copyright Stud.IP Core Group
+ * @license GPL2 or any later version
+ * @since 3.2
+ */
+class Icon
+{
+ const SVG = 1;
+ const CSS_BACKGROUND = 4;
+ const INPUT = 256;
+
+ const DEFAULT_SIZE = 16;
+ const DEFAULT_COLOR = 'blue';
+ const DEFAULT_ROLE = 'clickable';
+
+ const ROLE_INFO = 'info';
+ const ROLE_CLICKABLE = 'clickable';
+ const ROLE_ACCEPT = 'accept';
+ const ROLE_STATUS_GREEN = 'status-green';
+ const ROLE_INACTIVE = 'inactive';
+ const ROLE_NAVIGATION = 'navigation';
+ const ROLE_NEW = 'new';
+ const ROLE_ATTENTION = 'attention';
+ const ROLE_STATUS_RED = 'status-red';
+ const ROLE_INFO_ALT = 'info_alt';
+ const ROLE_SORT = 'sort';
+ const ROLE_STATUS_YELLOW = 'status-yellow';
+
+
+ protected $shape;
+ protected $role;
+ protected $attributes = [];
+
+
+ /**
+ * This is the magical Role to Color mapping.
+ */
+ private static $roles_to_colors = [
+ self::ROLE_INFO => 'black',
+ self::ROLE_CLICKABLE => 'blue',
+ self::ROLE_ACCEPT => 'green',
+ self::ROLE_STATUS_GREEN => 'green',
+ self::ROLE_INACTIVE => 'grey',
+ self::ROLE_NAVIGATION => 'blue',
+ self::ROLE_NEW => 'red',
+ self::ROLE_ATTENTION => 'red',
+ self::ROLE_STATUS_RED => 'red',
+ self::ROLE_INFO_ALT => 'white',
+ self::ROLE_SORT => 'blue',
+ self::ROLE_STATUS_YELLOW => 'yellow'
+ ];
+
+ // return the color associated to a role
+ private static function roleToColor($role)
+ {
+ if (!isset(self::$roles_to_colors[$role])) {
+ throw new \InvalidArgumentException('Unknown role: "' . $role . '"');
+ }
+ return self::$roles_to_colors[$role];
+ }
+
+ // return the roles! associated to a color
+ public static function colorToRoles($color)
+ {
+ static $colors_to_roles;
+
+ if (!$colors_to_roles) {
+ foreach (self::$roles_to_colors as $r => $c) {
+ $colors_to_roles[$c][] = $r;
+ }
+ }
+
+ if (!isset($colors_to_roles[$color])) {
+ throw new \InvalidArgumentException('Unknown color: "' . $color . '"');
+ }
+
+ return $colors_to_roles[$color];
+ }
+
+ /**
+ * Create a new Icon object.
+ *
+ * This is just a factory method. You could easily just call the
+ * constructor instead.
+ *
+ * @param String $shape Shape of the icon, may contain a mixed definition
+ * like 'seminar'
+ * @param String $role Role of the icon, defaults to Icon::DEFAULT_ROLE
+ * @param Array $attributes Additional attributes like 'title';
+ * only use semantic ones describing
+ * this icon regardless of its later
+ * rendering in a view
+ * @return Icon object
+ */
+ public static function create($shape, $role = Icon::DEFAULT_ROLE, $attributes = [])
+ {
+ // $role may be omitted
+ if (is_array($role)) {
+ $attributes = $role;
+ $role = Icon::DEFAULT_ROLE;
+ }
+
+ return new self($shape, $role, $attributes);
+ }
+
+ /**
+ * Constructor of the object.
+ *
+ * @param String $shape Shape of the icon, may contain a mixed definition
+ * like 'seminar'
+ * @param String $role Role of the icon, defaults to Icon::DEFAULT_ROLE
+ * @param Array $attributes Additional attributes like 'title';
+ * only use semantic ones describing
+ * this icon regardless of its later
+ * rendering in a view
+ */
+ public function __construct($shape, $role = Icon::DEFAULT_ROLE, array $attributes = [])
+ {
+
+ // only defined roles
+ if (!isset(self::$roles_to_colors[$role])) {
+ throw new \InvalidArgumentException('Creating an Icon without proper role: "' . $role . '"');
+ }
+
+ // only semantic attributes
+ if ($non_semantic = array_filter(array_keys($attributes), function ($attr) {
+ return !in_array($attr, ['title']);
+ })) {
+ // DEPRECATED
+ // TODO starting with the v3.6 the following line should
+ // be enabled to prevent non-semantic attributes in this position
+ # throw new \InvalidArgumentException('Creating an Icon with non-semantic attributes:' . json_encode($non_semantic));
+ }
+
+ $this->shape = $shape;
+ $this->role = $role;
+ $this->attributes = $attributes;
+ }
+
+ /**
+ * Returns the `shape` -- the string describing the shape of this instance.
+ * @return String the shape of this Icon
+ */
+ public function getShape()
+ {
+ return $this->shapeToPath($this->shape);
+ }
+
+ /**
+ * Returns the `role` -- the string describing the role of this instance.
+ * @return String the role of this Icon
+ */
+ public function getRole()
+ {
+ return $this->role;
+ }
+
+ /**
+ * Returns the semantic `attributes` of this instance, e.g. the title of this Icon
+ * @return Array the semantic attribiutes of the Icon
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * Returns whether this icon intends to signal attention.
+ *
+ * @todo This is currently just a heuristic based on the associated icon
+ * role. Although this is sufficient for the current requirements,
+ * it could probably in a better, more suitable way.
+ *
+ * @return bool
+ * @since Stud.IP 5.0
+ */
+ public function signalsAttention()
+ {
+ return $this->roleToColor($this->role) === 'red';
+ }
+
+ /**
+ * Function to be called whenever the object is converted to
+ * string. Internally the same as calling Icon::asImg
+ *
+ * @return String representation
+ */
+ public function __toString()
+ {
+ return $this->asImg();
+ }
+
+ /**
+ * Renders the icon inside an img html tag.
+ *
+ * @param int $size Optional; Defines the dimension in px of the rendered icon; FALSE prevents any
+ * width or height attributes
+ * @param Array $view_attributes Optional; Additional attributes to pass
+ * into the rendered output
+ * @return String containing the html representation for the icon.
+ */
+ public function asImg($size = null, $view_attributes = [])
+ {
+ if (is_array($size)) {
+ list($view_attributes, $size) = [$size, null];
+ }
+ return sprintf(
+ '<img %s>',
+ arrayToHtmlAttributes(
+ $this->prepareHTMLAttributes($size, $view_attributes)
+ )
+ );
+ }
+
+ /**
+ * Renders the icon inside an input html tag.
+ *
+ * @param int $size Optional; Defines the dimension in px of the rendered icon; FALSE prevents any
+ * width or height attributes
+ * @param Array $view_attributes Optional; Additional attributes to pass
+ * into the rendered output
+ * @return String containing the html representation for the icon.
+ */
+ public function asInput($size = null, $view_attributes = [])
+ {
+ if (is_array($size)) {
+ list($view_attributes, $size) = [$size, null];
+ }
+ return sprintf(
+ '<input type="image" %s>',
+ arrayToHtmlAttributes(
+ $this->prepareHTMLAttributes($size, $view_attributes)
+ )
+ );
+ }
+
+ /**
+ * Renders the icon as a set of css background rules.
+ *
+ * @param int $size Optional; Defines the size in px of the rendered icon
+ * @return String containing the html representation for css backgrounds
+ */
+ public function asCSS($size = null)
+ {
+ if (self::isStatic($this->shape)) {
+ return sprintf(
+ 'background-image:url(%1$s);background-size:%2$upx %2$upx;',
+ $this->shapeToPath($this->shape),
+ $this->get_size($size)
+ );
+ }
+
+ return sprintf(
+ 'background-image:url(%1$s);background-size:%2$upx %2$upx;',
+ $this->get_asset_svg(),
+ $this->get_size($size)
+ );
+ }
+
+ /**
+ * Returns a path to the SVG matching the icon.
+ *
+ * @return String containing the html representation for css backgrounds
+ */
+ public function asImagePath()
+ {
+ return $this->prepareHTMLAttributes(false, [])['src'];
+ }
+
+ /**
+ * Returns a new Icon with a changed shape
+ * @param mixed $shape New value of `shape`
+ * @return Icon A new Icon with a new `shape`
+ */
+ public function copyWithShape($shape)
+ {
+ $clone = clone $this;
+ $clone->shape = $shape;
+ return $clone;
+ }
+
+ /**
+ * Returns a new Icon with a changed role
+ * @param mixed $role New value of `role`
+ * @return Icon A new Icon with a new `role`
+ */
+ public function copyWithRole($role)
+ {
+ $clone = clone $this;
+ $clone->role = $role;
+ return $clone;
+ }
+
+ /**
+ * Returns a new Icon with new attributes
+ * @param mixed $attributes New value of `attributes`
+ * @return Icon A new Icon with a new `attributes`
+ */
+ public function copyWithAttributes($attributes)
+ {
+ $clone = clone $this;
+ $clone->attributes = $attributes;
+ return $clone;
+ }
+
+ /**
+ * Prepares the html attributes for use assembling HTML attributes
+ * from given shape, role, size, semantic and view attributes
+ *
+ * @param int $size Size of the icon
+ * @param array $attributes Additional attributes
+ * @return Array containing the merged attributes
+ */
+ private function prepareHTMLAttributes($size, $attributes)
+ {
+ $dimensions = [];
+ if ($size !== false) {
+ $size = $this->get_size($size);
+ $dimensions = ['width' => $size, 'height' => $size];
+ }
+
+ $result = array_merge($this->attributes, $attributes, $dimensions, [
+ 'src' => self::isStatic($this->shape) ? $this->shape : $this->get_asset_svg(),
+ ]);
+
+ if (!isset($result['alt']) && !isset($result['title'])) {
+ //Add an empty alt attribute to prevent screen readers from
+ //reading the URL of the icon:
+ $result['alt'] = '';
+ }
+
+ $classNames = 'icon-role-' . $this->role;
+
+ if (!self::isStatic($this->shape)) {
+ $classNames .= ' icon-shape-' . $this->shapeToPath($this->shape);
+ }
+
+ $result['class'] = isset($result['class']) ? $result['class'] . ' ' . $classNames : $classNames;
+
+ return $result;
+ }
+
+ /**
+ * Get the correct asset for an SVG icon.
+ *
+ * @return String containing the url of the corresponding asset
+ */
+ protected function get_asset_svg()
+ {
+ return Assets::url('images/icons/' . self::roleToColor($this->role) . '/' . $this->shapeToPath($this->shape) . '.svg');
+ }
+
+ /**
+ * Get the size of the icon. If a size was passed as a parameter and
+ * inside the attributes array during icon construction, the size from
+ * the attributes will be used.
+ *
+ * @param int $size size of the icon
+ * @return int Size of the icon in pixels
+ */
+ protected function get_size($size)
+ {
+ $size = $size ?: Icon::DEFAULT_SIZE;
+ if (isset($this->attributes['size'])) {
+ $parts = explode('@', $this->attributes['size'], 2);
+ $size = $parts[0];
+ $temp = $parts[1] ?? null;
+ unset($this->attributes['size']);
+ }
+ return (int)$size;
+ }
+
+ // an icon is static if it starts with 'http'
+ private static function isStatic($shape)
+ {
+ return mb_strpos($shape, 'http') === 0;
+ }
+
+ // transforms a shape w/ possible additions (`shape`) to a path `(addition/)?shape`
+ private function shapeToPath()
+ {
+ if (self::isStatic($this->shape)) {
+ return $this->shape;
+ }
+ $shape = array_reverse(explode('/', $this->shape))[0];
+ $shape = explode('+', $shape)[0];
+ return $shape;
+ }
+}