[ticket/15538] Create macro and remove icons
PHPBB3-15538
|
@ -57,6 +57,7 @@ services:
|
||||||
template.twig.extensions.icon:
|
template.twig.extensions.icon:
|
||||||
class: phpbb\template\twig\extension\icon
|
class: phpbb\template\twig\extension\icon
|
||||||
arguments:
|
arguments:
|
||||||
|
- '@template.twig.environment'
|
||||||
- '@user'
|
- '@user'
|
||||||
tags:
|
tags:
|
||||||
- { name: twig.extension }
|
- { name: twig.extension }
|
||||||
|
|
|
@ -1,165 +1,120 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* This file is part of the phpBB Forum Software package.
|
* This file is part of the phpBB Forum Software package.
|
||||||
*
|
*
|
||||||
* @copyright (c) phpBB Limited <https://www.phpbb.com>
|
* @copyright (c) phpBB Limited <https://www.phpbb.com>
|
||||||
* @license GNU General Public License, version 2 (GPL-2.0)
|
* @license GNU General Public License, version 2 (GPL-2.0)
|
||||||
*
|
*
|
||||||
* For full copyright and license information, please see
|
* For full copyright and license information, please see
|
||||||
* the docs/CREDITS.txt file.
|
* the docs/CREDITS.txt file.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace phpbb\template\twig\extension;
|
namespace phpbb\template\twig\extension;
|
||||||
|
|
||||||
use phpbb\template\twig\environment;
|
use phpbb\template\twig\environment;
|
||||||
|
|
||||||
class icon extends \Twig_Extension
|
abstract class icon extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface
|
||||||
{
|
{
|
||||||
/** @var \phpbb\user */
|
protected $twig;
|
||||||
protected $user;
|
protected $user;
|
||||||
|
|
||||||
/**
|
public function __construct(environment $twig, \phpbb\user $user)
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param \phpbb\user $user User object
|
|
||||||
*/
|
|
||||||
public function __construct(\phpbb\user $user)
|
|
||||||
{
|
{
|
||||||
|
$this->twig = $twig;
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name of this extension.
|
* Get the name of this extension.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getName()
|
public function getName()
|
||||||
{
|
{
|
||||||
return 'icon';
|
return 'icon';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of global functions to add to the existing list.
|
* Returns a list of global variables to add to the existing list.
|
||||||
*
|
|
||||||
* @return array An array of global functions
|
|
||||||
*/
|
|
||||||
public function getFunctions()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
new \Twig_SimpleFunction('Icon', [$this, 'icon'], ['needs_environment' => true]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate icon HTML for use in the template, depending on the mode.
|
|
||||||
*
|
*
|
||||||
* @param environment $environment Twig environment object
|
* @return array An array of global variables
|
||||||
* @param string $type Icon type (font|png|svg)
|
|
||||||
* @param string $icon Icon name (eg. "bold")
|
|
||||||
* @param string $classes Additional classes (eg. "fa-fw")
|
|
||||||
* @param string $title Icon title
|
|
||||||
* @param bool $hidden Hide the icon title from view
|
|
||||||
* @param array $attributes Additional attributes for the icon, where the key is the attribute.
|
|
||||||
* {'data-ajax': 'mark_forums'} results in ' data-ajax="mark_forums"'
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public function icon(environment $environment, $type = '', $icon = '', $classes = '', $title = '', $hidden = false, array $attributes = [])
|
public function getGlobals()
|
||||||
{
|
{
|
||||||
switch ($type)
|
$macros = null;
|
||||||
{
|
|
||||||
case 'font':
|
|
||||||
$src = '';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'png':
|
|
||||||
$board_url = defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH;
|
|
||||||
$web_path = $board_url ? generate_board_url() . '/' : $environment->get_web_root_path();
|
|
||||||
|
|
||||||
$src = "{$web_path}styles/" . $this->user->style['style_path'] . "/template/icons/png/{$icon}.png";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'svg':
|
|
||||||
try
|
|
||||||
{
|
|
||||||
$file = $environment->load('icons/svg/' . $icon . '.svg');
|
|
||||||
}
|
|
||||||
catch (\Twig_Error $e)
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
$src = $this->filter_svg($file);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Not a supported icon type (font|png|svg), return an empty string
|
|
||||||
return '';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return $environment->render("icons/{$type}.html", [
|
$macros = $this->twig->loadTemplate('macros.html');
|
||||||
'ATTRIBUTES' => (string) $this->implode_attributes($attributes),
|
|
||||||
'CLASSES' => (string) $classes,
|
|
||||||
'ICON' => (string) $icon,
|
|
||||||
'SOURCE' => (string) $src,
|
|
||||||
'TITLE' => (string) $title,
|
|
||||||
'S_HIDDEN' => (bool) $hidden,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
catch (\Twig_Error $e)
|
catch (\Twig\Error\Error $e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'macro' => $macros,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFilters()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new \Twig\TwigFilter('Png_path', [$this, 'png_path'], ['needs_environment' => true]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFunctions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new \Twig\TwigFunction('Svg_clean', [$this, 'svg_clean'], ['needs_environment' => true]),
|
||||||
|
new \Twig\TwigFunction('Implode_attributes', [$this, 'implode_attributes']),
|
||||||
|
new \Twig\TwigFunction('Implode_classes', [$this, 'implode_classes']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function png_path(environment $environment, $icon)
|
||||||
|
{
|
||||||
|
$board_url = defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH;
|
||||||
|
$web_path = $board_url ? generate_board_url() . '/' : $environment->get_web_root_path();
|
||||||
|
$style_path = $this->user->style['style_path'];
|
||||||
|
|
||||||
|
return "{$web_path}styles/{$style_path}/template/icons/png/{$icon}.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function svg_clean(environment $environment, $icon)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$file = $environment->load('icons/svg/' . $icon . '.svg');
|
||||||
|
}
|
||||||
|
catch (\Twig\Error\Error $e)
|
||||||
{
|
{
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implode an associated array of attributes to a string for usage in a template.
|
|
||||||
*
|
|
||||||
* @param array $attributes Associated array of attributes
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function implode_attributes(array $attributes)
|
|
||||||
{
|
|
||||||
$attr_str = '';
|
|
||||||
|
|
||||||
foreach ($attributes as $attribute => $value)
|
|
||||||
{
|
|
||||||
$attr_str .= ' ' . $attribute . '="' . $value . '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $attr_str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter a SVG for usage in the template.
|
|
||||||
*
|
|
||||||
* @param \Twig_TemplateWrapper $file The svg file loaded from the environment
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function filter_svg(\Twig_TemplateWrapper $file)
|
|
||||||
{
|
|
||||||
/** @var \Twig_Source $src */
|
|
||||||
$src = $file->getSourceContext();
|
$src = $file->getSourceContext();
|
||||||
$svg = $src->getCode();
|
$svg = $src->getCode();
|
||||||
|
|
||||||
/** @var \DOMDocument $dom */
|
$doc = new \DOMDocument();
|
||||||
$dom = new \DOMDocument();
|
$doc->formatOutput = false;
|
||||||
$dom->preserveWhiteSpace = false;
|
$doc->preserveWhiteSpace = false;
|
||||||
|
$doc->strictErrorChecking = false;
|
||||||
|
|
||||||
/**
|
if (!$doc->loadXML($svg))
|
||||||
* Suppression is needed as DOMDocument does not like HTML5 and SVGs.
|
{
|
||||||
* Options parameter prevents $dom->saveHTML() from adding an <html> element.
|
return '';
|
||||||
*/
|
}
|
||||||
@$dom->loadHTML($svg, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
|
|
||||||
|
|
||||||
/** @var \DOMXPath $xpath */
|
foreach ($doc->childNodes as $child) {
|
||||||
$xpath = new \DOMXPath($dom);
|
if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
|
||||||
|
$child->parentNode->removeChild($child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$xpath = new \DOMXPath($doc);
|
||||||
|
|
||||||
/** @var \DOMNode $element */
|
|
||||||
foreach ($xpath->query('//svg | //title') as $element)
|
foreach ($xpath->query('//svg | //title') as $element)
|
||||||
{
|
{
|
||||||
if ($element->nodeName === 'svg')
|
if ($element->nodeName === 'svg')
|
||||||
|
@ -182,17 +137,95 @@ class icon extends \Twig_Extension
|
||||||
$element->parentNode->removeChild($element);
|
$element->parentNode->removeChild($element);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var \DOMElement $attribute */
|
$string = $doc->saveXML($doc->documentElement, LIBXML_NOEMPTYTAG);
|
||||||
foreach ($xpath->query('//@fill') as $attribute)
|
$string = preg_replace('/\s+/', ' ', $string);
|
||||||
{
|
|
||||||
if ($attribute->nodeName === 'fill' && $attribute->nodeValue === 'none')
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$attribute->parentNode->removeAttribute($attribute->nodeName);
|
return $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function implode_attributes(...$arguments)
|
||||||
|
{
|
||||||
|
$string = '';
|
||||||
|
$attributes = [];
|
||||||
|
|
||||||
|
foreach ($arguments as $argument)
|
||||||
|
{
|
||||||
|
if (is_string($argument))
|
||||||
|
{
|
||||||
|
$attributes[] = $argument;
|
||||||
|
}
|
||||||
|
else if (is_array($argument))
|
||||||
|
{
|
||||||
|
foreach ($argument as $key => $value)
|
||||||
|
{
|
||||||
|
if (is_integer($key) && is_string($value))
|
||||||
|
{
|
||||||
|
$attributes[] = $value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$attributes[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $dom->saveHTML();
|
foreach ($attributes as $attribute => $value)
|
||||||
|
{
|
||||||
|
if (is_string($attribute))
|
||||||
|
{
|
||||||
|
$string .= ' ' . $attribute . '="' . $value . '"';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$string .= ' ' . $attribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function implode_classes(...$arguments)
|
||||||
|
{
|
||||||
|
$classes = [];
|
||||||
|
|
||||||
|
foreach ($arguments as $argument)
|
||||||
|
{
|
||||||
|
if (is_string($argument))
|
||||||
|
{
|
||||||
|
$classes[] = $argument;
|
||||||
|
}
|
||||||
|
else if (is_array($argument))
|
||||||
|
{
|
||||||
|
foreach ($argument as $key => $value)
|
||||||
|
{
|
||||||
|
if (is_integer($key) && is_string($value))
|
||||||
|
{
|
||||||
|
$classes[] = $value;
|
||||||
|
}
|
||||||
|
else if (is_string($key))
|
||||||
|
{
|
||||||
|
if ($value)
|
||||||
|
{
|
||||||
|
$classes[] = $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (is_array($value))
|
||||||
|
{
|
||||||
|
foreach ($value as $class => $condition)
|
||||||
|
{
|
||||||
|
if ($condition)
|
||||||
|
{
|
||||||
|
$classes[] = $class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$string = implode(' ', array_unique($classes));
|
||||||
|
|
||||||
|
return $string ? ' ' . $string : $string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
33
phpBB/styles/all/template/macros.html
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{# Wrapper function #}
|
||||||
|
{% macro Icon(type, icon, classes = '', title = '', hidden = false, attributes = {}) %}
|
||||||
|
{% set type = type|capitalize %}
|
||||||
|
|
||||||
|
{% if type in ['Fa', 'Png', 'Svg'] %}
|
||||||
|
{{ attribute(_self, type, [icon, classes, title, hidden, attributes]) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endmacro Icon %}
|
||||||
|
|
||||||
|
{# FA icons #}
|
||||||
|
{% macro Fa(icon, classes = '', title = '', hidden = false, attributes = {}) %}
|
||||||
|
<i class="o-icon font fa-{{ icon ~ Implode_classes(classes) }}"{% if hidden %}{% if title %} title="{{ lang(title) }}"{% endif %} aria-hidden="true"{% endif %}{{ _self.attributes(attributes) }}></i>
|
||||||
|
{% if title %}<span{% if hidden %} class="sr-only"{% endif %}>{{ lang(title) }}</span>{% endif %}
|
||||||
|
{% endmacro Fa %}
|
||||||
|
|
||||||
|
{# PNG icons #}
|
||||||
|
{% macro Png(icon, classes = '', title = '', hidden = false, attributes = {}) %}
|
||||||
|
<img class="o-icon png png-{{ icon ~ Implode_classes(classes) }}" src="{{ icon|png_path }}" alt="{{ lang(title) }}"{{ _self.attributes(attributes) }}
|
||||||
|
{% endmacro Png %}
|
||||||
|
|
||||||
|
{# SVG icons #}
|
||||||
|
{% macro Svg(icon, classes = '', title = '', hidden = false, attributes = {}) %}
|
||||||
|
{% set title_id = title ? title|lower|replace({' ': '_'}) ~ '-' ~ random() %}
|
||||||
|
|
||||||
|
<svg class="o-icon svg svg-{{ icon ~ Implode_classes(classes) }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"{% if title %}{% if hidden %} aria-hidden="true"{% endif %} aria-labelledby="{{ title_id }}"{% endif %} role="img"{{ _self.attributes(attributes) }}>
|
||||||
|
{% if title %}
|
||||||
|
<title id="{{ title_id }}">{{ lang(title) }}</title>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{{ svg_clean(icon) }}
|
||||||
|
</svg>
|
||||||
|
{% endmacro Svg %}
|
|
@ -1,11 +0,0 @@
|
||||||
{% spaceless %}
|
|
||||||
<i
|
|
||||||
class="o-icon font fa-{{ ICON ~ (CLASSES ? ' ' ~ CLASSES) }}"
|
|
||||||
{% if S_HIDDEN %}
|
|
||||||
{% if TITLE %}title="{{ lang(TITLE) }}"{% endif %}
|
|
||||||
aria-hidden="true"
|
|
||||||
{% endif %}
|
|
||||||
{{ ATTRIBUTES }}
|
|
||||||
></i>
|
|
||||||
{% if TITLE %}<span{% if S_HIDDEN %} class="sr-only"{% endif %}>{{ lang(TITLE) }}</span>{% endif %}
|
|
||||||
{% endspaceless %}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{% spaceless %}
|
|
||||||
<img
|
|
||||||
class="o-icon png png-{{ ICON ~ (CLASSES ? ' ' ~ CLASSES) }}"
|
|
||||||
src="{{ SOURCE }}"
|
|
||||||
alt="{{ lang(TITLE) }}"
|
|
||||||
{{ ATTRIBUTES }}
|
|
||||||
/>
|
|
||||||
{% endspaceless %}
|
|
Before Width: | Height: | Size: 85 B |
Before Width: | Height: | Size: 189 B |
Before Width: | Height: | Size: 165 B |
Before Width: | Height: | Size: 195 B |
|
@ -1,22 +0,0 @@
|
||||||
{% spaceless %}
|
|
||||||
{% set TITLE_ID = TITLE ? TITLE|lower|replace({' ': '-'}) ~ '-' ~ random() %}
|
|
||||||
<svg
|
|
||||||
class="o-icon svg svg-{{ ICON ~ (CLASSES ? ' ' ~ CLASSES) }}"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
{% if TITLE %}
|
|
||||||
{% if S_HIDDEN %}aria-hidden="true"{% endif %}
|
|
||||||
aria-labelledby="{{ TITLE_ID }}"
|
|
||||||
{% endif %}
|
|
||||||
role="img"
|
|
||||||
{{ ATTRIBUTES }}
|
|
||||||
>
|
|
||||||
{% if TITLE %}
|
|
||||||
<title id="{{ TITLE_ID }}">
|
|
||||||
{{ lang(TITLE) }}
|
|
||||||
</title>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{{ SOURCE }}
|
|
||||||
</svg>
|
|
||||||
{% endspaceless %}
|
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path fill="red" d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg>
|
|
Before Width: | Height: | Size: 195 B |
|
@ -1 +0,0 @@
|
||||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H4V8l8 5 8-5v10zm-8-7L4 6h16l-8 5z"/></svg>
|
|
Before Width: | Height: | Size: 261 B |
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>My fake title!</title><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
|
|
Before Width: | Height: | Size: 317 B |
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M20.01 15.38c-1.23 0-2.42-.2-3.53-.56-.35-.12-.74-.03-1.01.24l-1.57 1.97c-2.83-1.35-5.48-3.9-6.89-6.83l1.95-1.66c.27-.28.35-.67.24-1.02-.37-1.11-.56-2.3-.56-3.53 0-.54-.45-.99-.99-.99H4.19C3.65 3 3 3.24 3 3.99 3 13.28 10.73 21 20.01 21c.71 0 .99-.63.99-1.18v-3.45c0-.54-.45-.99-.99-.99z"/></svg>
|
|
Before Width: | Height: | Size: 425 B |