Initial commit: apps.supersam24.ru

This commit is contained in:
root committed 2026-10-22 00:04:27 +03:00
commit e955e1da36
6566 files changed
+1271095

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
language: php
addons:
apt:
packages:
- language-pack-nl
php:
- 5.6
- 7.0
- 7.1
branches:
only:
- master
install:
- composer install
- wget https://scrutinizer-ci.com/ocular.phar -O "$HOME/ocular.phar"
script:
- vendor/bin/phpunit --coverage-clover cache/logs/clover.xml
after_success:
- php "$HOME/ocular.phar" code-coverage:upload --format=php-clover cache/logs/clover.xml
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) Arnold Daniels <arnold@jasny.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+122
View File
@@ -0,0 +1,122 @@
Jasny Twig Extensions
=======================
[![Build Status](https://travis-ci.org/jasny/twig-extensions.svg?branch=master)](https://travis-ci.org/jasny/twig-extensions)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/jasny/twig-extensions/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/jasny/twig-extensions/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/jasny/twig-extensions/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/jasny/twig-extensions/?branch=master)
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/20fdb2a4-7565-441b-8920-48a7c09113a1/mini.png)](https://insight.sensiolabs.com/projects/20fdb2a4-7565-441b-8920-48a7c09113a1)
[![Packagist Stable Version](https://img.shields.io/packagist/v/jasny/twig-extensions.svg)](https://packagist.org/packages/jasny/twig-extensions)
[![Packagist License](https://img.shields.io/packagist/l/jasny/twig-extensions.svg)](https://packagist.org/packages/jasny/twig-extensions)
A number of useful filters for Twig.
## Installation
Jasny's Twig Extensions can be easily installed using [composer](http://getcomposer.org/)
composer require jasny/twig-extensions
## Usage
```php
$twig = new Twig_Environment($loader, $options);
$twig->addExtension(new Jasny\Twig\DateExtension());
$twig->addExtension(new Jasny\Twig\PcreExtension());
$twig->addExtension(new Jasny\Twig\TextExtension());
$twig->addExtension(new Jasny\Twig\ArrayExtension());
```
To use in a symfony project [register the extensions as a service](http://symfony.com/doc/current/cookbook/templating/twig_extension.html#register-an-extension-as-a-service).
```yaml
services:
twig.extension.date:
class: Jasny\Twig\DateExtension
tags:
- { name: twig.extension }
twig.extension.pcre:
class: Jasny\Twig\PcreExtension
tags:
- { name: twig.extension }
twig.extension.text:
class: Jasny\Twig\TextExtension
tags:
- { name: twig.extension }
twig.extension.array:
class: Jasny\Twig\ArrayExtension
tags:
- { name: twig.extension }
```
## Date extension
Format a date base on the current locale. Requires the [intl extension](http://www.php.net/intl).
* localdate - Format the date value as a string based on the current locale
* localtime - Format the time value as a string based on the current locale
* localdatetime - Format the date/time value as a string based on the current locale
* age - Get the age (in years) based on a date
* duration - Get the duration string from seconds
```php
Locale::setDefault(LC_ALL, "en_US"); // vs "nl_NL"
```
```
{{"now"|localdate('long')}} <!-- July 12, 2013 --> <!-- 12 juli 2013 -->
{{"now"|localtime('short')}} <!-- 5:53 PM --> <!-- 17:53 -->
{{"2013-10-01 23:15:00"|localdatetime}} <!-- 10/01/2013 11:15 PM --> <!-- 01-10-2013 23:15 -->
{{"22-08-1981"|age}} <!-- 35 -->
{{ 3600|duration }} <!-- 1h -->
```
## PCRE
Exposes [PCRE](http://www.php.net/pcre) to Twig.
* preg_quote - Quote regular expression characters
* preg_match - Perform a regular expression match
* preg_get - Perform a regular expression match and returns the matched group
* preg_get_all - Perform a regular expression match and return the group for all matches
* preg_grep - Perform a regular expression match and return an array of entries that match the pattern
* preg_replace - Perform a regular expression search and replace
* preg_filter - Perform a regular expression search and replace, returning only matched subjects.
* preg_split - Split text into an array using a regular expression
```
{% if client.email|preg_match('/^.+@.+\.\w+$/) %}Email: {{ client.email }}{% endif %}
Website: {{ client.website|preg_replace('~^https?://~')
First name: {{ client.fullname|preg_get('/^\S+/') }}
<ul>
{% for item in items|preg_split('/\s+/')|grep_filter('/-test$/', 'invert') %}
<li>{{ item }}</li>
{% endfor %}
</ul>
```
## Text ##
Convert text to HTML + string functions
* paragraph - Add HTML paragraph and line breaks to text
* line - Get a single line of text
* less - Cut of text on a pagebreak
* truncate - Cut of text if it's to long
* linkify - Turn all URLs in clickable links (also supports Twitter @user and #subject)
## Array ##
Brings PHP's array functions to Twig
* sum - Calculate the sum of values in an array
* product - Calculate the product of values in an array
* values - Return all the values of an array
* as_array - Cast an object to an associated array
* html_attr - Turn an array into an HTML attribute string
+42
View File
@@ -0,0 +1,42 @@
{
"name": "jasny/twig-extensions",
"description": "A set of useful Twig filters",
"keywords": ["templating", "PCRE", "preg", "regex", "date", "time", "datetime", "text", "array"],
"license": "MIT",
"homepage": "http://github.com/jasny/twig-extensions#README",
"authors": [
{
"name": "Arnold Daniels",
"email": "arnold@jasny.net",
"homepage": "http://www.jasny.net"
}
],
"support": {
"issues": "https://github.com/jasny/twig-extensions/issues",
"source": "https://github.com/jasny/twig-extensions"
},
"require": {
"php": ">=7.0.0 | >=5.6.0",
"twig/twig": "^2.0 | ^1.12"
},
"suggest": {
"ext-intl": "Required for the use of the LocalDate Twig extension",
"ext-pcre": "Required for the use of the PCRE Twig extension"
},
"autoload": {
"psr-4": {
"Jasny\\Twig\\": "src/"
}
},
"require-dev": {
"ext-intl": "*",
"ext-pcre": "*",
"jasny/php-code-quality": "^2.1",
"phpunit/phpunit": "^5.0"
},
"autoload-dev": {
"classmap": [
"tests/support/"
]
}
}
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
colors="true"
bootstrap="vendor/autoload.php"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
>
<testsuites>
<testsuite>
<directory>tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-text" target="php://stdout"/>
</logging>
</phpunit>
+107
View File
@@ -0,0 +1,107 @@
<?php
namespace Jasny\Twig;
/**
* Expose PHP's array functions to Twig
*/
class ArrayExtension extends \Twig_Extension
{
/**
* Return extension name
*
* @return string
*/
public function getName()
{
return 'jasny/array';
}
/**
* Callback for Twig
* @ignore
*/
public function getFilters()
{
return [
new \Twig_SimpleFilter('sum', [$this, 'sum']),
new \Twig_SimpleFilter('product', [$this, 'product']),
new \Twig_SimpleFilter('values', [$this, 'values']),
new \Twig_SimpleFilter('as_array', [$this, 'asArray']),
new \Twig_SimpleFilter('html_attr', [$this, 'htmlAttributes']),
];
}
/**
* Calculate the sum of values in an array
*
* @param array $array
* @return int
*/
public function sum($array)
{
return isset($array) ? array_sum((array)$array) : null;
}
/**
* Calculate the product of values in an array
*
* @param array $array
* @return int
*/
public function product($array)
{
return isset($array) ? array_product((array)$array) : null;
}
/**
* Return all the values of an array or object
*
* @param array|object $array
* @return array
*/
public function values($array)
{
return isset($array) ? array_values((array)$array) : null;
}
/**
* Cast value to an array
*
* @param object|mixed $value
* @return array
*/
public function asArray($value)
{
return is_object($value) ? get_object_vars($value) : (array)$value;
}
/**
* Cast an array to an HTML attribute string
*
* @param mixed $array
* @return string
*/
public function htmlAttributes($array)
{
if (!isset($array)) {
return null;
}
$str = "";
foreach ($array as $key => $value) {
if (!isset($value) || $value === false) {
continue;
}
if ($value === true) {
$value = $key;
}
$str .= ' ' . $key . '="' . addcslashes($value, '"') . '"';
}
return trim($str);
}
}
+347
View File
@@ -0,0 +1,347 @@
<?php
namespace Jasny\Twig;
/**
* Format a date based on the current locale in Twig
*/
class DateExtension extends \Twig_Extension
{
/**
* Class constructor
*/
public function __construct()
{
if (!extension_loaded('intl')) {
throw new \Exception("The Date Twig extension requires the 'intl' PHP extension."); // @codeCoverageIgnore
}
}
/**
* Return extension name
*
* @return string
*/
public function getName()
{
return 'jasny/date';
}
/**
* Callback for Twig to get all the filters.
*
* @return \Twig_SimpleFilter[]
*/
public function getFilters()
{
return [
new \Twig_SimpleFilter('localdate', [$this, 'localDate']),
new \Twig_SimpleFilter('localtime', [$this, 'localTime']),
new \Twig_SimpleFilter('localdatetime', [$this, 'localDateTime']),
new \Twig_SimpleFilter('duration', [$this, 'duration']),
new \Twig_SimpleFilter('age', [$this, 'age']),
];
}
/**
* Turn a value into a DateTime object
*
* @param string|int|\DateTime $date
* @return \DateTime
*/
protected function valueToDateTime($date)
{
if (!$date instanceof \DateTime) {
$date = is_int($date) ? \DateTime::createFromFormat('U', $date) : new \DateTime((string)$date);
}
return $date;
}
/**
* Get configured intl date formatter.
*
* @param string|null $dateFormat
* @param string|null $timeFormat
* @param string $calendar
* @return \IntlDateFormatter
*/
protected function getDateFormatter($dateFormat, $timeFormat, $calendar)
{
$datetype = isset($dateFormat) ? $this->getFormat($dateFormat) : null;
$timetype = isset($timeFormat) ? $this->getFormat($timeFormat) : null;
$calendarConst = $calendar === 'traditional' ? \IntlDateFormatter::TRADITIONAL : \IntlDateFormatter::GREGORIAN;
$pattern = $this->getDateTimePattern(
isset($datetype) ? $datetype : $dateFormat,
isset($timetype) ? $timetype : $timeFormat,
$calendarConst
);
return new \IntlDateFormatter(\Locale::getDefault(), $datetype, $timetype, null, $calendarConst, $pattern);
}
/**
* Format the date/time value as a string based on the current locale
*
* @param string|false $format 'short', 'medium', 'long', 'full', 'none' or false
* @return int|null
*/
protected function getFormat($format)
{
if ($format === false) {
$format = 'none';
}
$types = [
'none' => \IntlDateFormatter::NONE,
'short' => \IntlDateFormatter::SHORT,
'medium' => \IntlDateFormatter::MEDIUM,
'long' => \IntlDateFormatter::LONG,
'full' => \IntlDateFormatter::FULL
];
return isset($types[$format]) ? $types[$format] : null;
}
/**
* Get the date/time pattern.
*
* @param int|string $datetype
* @param int|string $timetype
* @param int $calendar
* @return string
*/
protected function getDateTimePattern($datetype, $timetype, $calendar = \IntlDateFormatter::GREGORIAN)
{
if (is_int($datetype) && is_int($timetype)) {
return null;
}
return $this->getDatePattern(
isset($datetype) ? $datetype : \IntlDateFormatter::SHORT,
isset($timetype) ? $timetype : \IntlDateFormatter::SHORT,
$calendar
);
}
/**
* Get the formatter to create a date and/or time pattern
*
* @param int|string $datetype
* @param int|string $timetype
* @param int $calendar
* @return \IntlDateFormatter
*/
protected function getDatePatternFormatter($datetype, $timetype, $calendar = \IntlDateFormatter::GREGORIAN)
{
return \IntlDateFormatter::create(
\Locale::getDefault(),
is_int($datetype) ? $datetype : \IntlDateFormatter::NONE,
is_int($timetype) ? $timetype : \IntlDateFormatter::NONE,
\IntlTimeZone::getGMT(),
$calendar
);
}
/**
* Get the date and/or time pattern
* Default date pattern is short date pattern with 4 digit year.
*
* @param int|string $datetype
* @param int|string $timetype
* @param int $calendar
* @return string
*/
protected function getDatePattern($datetype, $timetype, $calendar = \IntlDateFormatter::GREGORIAN)
{
$createPattern =
(is_int($datetype) && $datetype !== \IntlDateFormatter::NONE) ||
(is_int($timetype) && $timetype !== \IntlDateFormatter::NONE);
$pattern = $createPattern ? $this->getDatePatternFormatter($datetype, $timetype, $calendar)->getPattern() : '';
return trim(
(is_string($datetype) ? $datetype . ' ' : '') .
preg_replace('/\byy?\b/', 'yyyy', $pattern) .
(is_string($timetype) ? ' ' . $timetype : '')
);
}
/**
* Format the date and/or time value as a string based on the current locale
*
* @param \DateTime|int|string $value
* @param string $dateFormat null, 'short', 'medium', 'long', 'full' or pattern
* @param string $timeFormat null, 'short', 'medium', 'long', 'full' or pattern
* @param string $calendar 'gregorian' or 'traditional'
* @return string
*/
protected function formatLocal($value, $dateFormat, $timeFormat, $calendar = 'gregorian')
{
if (!isset($value)) {
return null;
}
$date = $this->valueToDateTime($value);
$formatter = $this->getDateFormatter($dateFormat, $timeFormat, $calendar);
return $formatter->format($date->getTimestamp());
}
/**
* Format the date value as a string based on the current locale
*
* @param DateTime|int|string $date
* @param string $format null, 'short', 'medium', 'long', 'full' or pattern
* @param string $calendar 'gregorian' or 'traditional'
* @return string
*/
public function localDate($date, $format = null, $calendar = 'gregorian')
{
return $this->formatLocal($date, $format, false, $calendar);
}
/**
* Format the time value as a string based on the current locale
*
* @param DateTime|int|string $date
* @param string $format 'short', 'medium', 'long', 'full' or pattern
* @param string $calendar 'gregorian' or 'traditional'
* @return string
*/
public function localTime($date, $format = 'short', $calendar = 'gregorian')
{
return $this->formatLocal($date, false, $format, $calendar);
}
/**
* Format the date/time value as a string based on the current locale
*
* @param DateTime|int|string $date
* @param string $format date format, pattern or ['date'=>format, 'time'=>format)
* @param string $calendar 'gregorian' or 'traditional'
* @return string
*/
public function localDateTime($date, $format = null, $calendar = 'gregorian')
{
if (is_array($format) || $format instanceof \stdClass || !isset($format)) {
$formatDate = isset($format['date']) ? $format['date'] : null;
$formatTime = isset($format['time']) ? $format['time'] : 'short';
} else {
$formatDate = $format;
$formatTime = false;
}
return $this->formatLocal($date, $formatDate, $formatTime, $calendar);
}
/**
* Split duration into seconds, minutes, hours, days, weeks and years.
*
* @param int $seconds
* @return array
*/
protected function splitDuration($seconds, $max)
{
if ($max < 1 || $seconds < 60) {
return [$seconds];
}
$minutes = floor($seconds / 60);
$seconds = $seconds % 60;
if ($max < 2 || $minutes < 60) {
return [$seconds, $minutes];
}
$hours = floor($minutes / 60);
$minutes = $minutes % 60;
if ($max < 3 || $hours < 24) {
return [$seconds, $minutes, $hours];
}
$days = floor($hours / 24);
$hours = $hours % 24;
if ($max < 4 || $days < 7) {
return [$seconds, $minutes, $hours, $days];
}
$weeks = floor($days / 7);
$days = $days % 7;
if ($max < 5 || $weeks < 52) {
return [$seconds, $minutes, $hours, $days, $weeks];
}
$years = floor($weeks / 52);
$weeks = $weeks % 52;
return [$seconds, $minutes, $hours, $days, $weeks, $years];
}
/**
* Calculate duration from seconds.
* One year is seen as exactly 52 weeks.
*
* Use null to skip a unit.
*
* @param int $value Time in seconds
* @param array $units Time units (seconds, minutes, hours, days, weeks, years)
* @param string $separator
* @return string
*/
public function duration($value, $units = ['s', 'm', 'h', 'd', 'w', 'y'], $separator = ' ')
{
if (!isset($value)) {
return null;
}
list($seconds, $minutes, $hours, $days, $weeks, $years) =
$this->splitDuration($value, count($units) - 1) + array_fill(0, 6, null);
$duration = '';
if (isset($years) && isset($units[5])) {
$duration .= $separator . $years . $units[5];
}
if (isset($weeks) && isset($units[4])) {
$duration .= $separator . $weeks . $units[4];
}
if (isset($days) && isset($units[3])) {
$duration .= $separator . $days . $units[3];
}
if (isset($hours) && isset($units[2])) {
$duration .= $separator . $hours . $units[2];
}
if (isset($minutes) && isset($units[1])) {
$duration .= $separator . $minutes . $units[1];
}
if (isset($seconds) && isset($units[0])) {
$duration .= $separator . $seconds . $units[0];
}
return trim($duration, $separator);
}
/**
* Get the age (in years) based on a date.
*
* @param DateTime|string $value
* @return int
*/
public function age($value)
{
if (!isset($value)) {
return null;
}
$date = $this->valueToDateTime($value);
return $date->diff(new \DateTime())->format('%y');
}
}
+208
View File
@@ -0,0 +1,208 @@
<?php
namespace Jasny\Twig;
/**
* Expose the PCRE functions to Twig.
*
* @see http://php.net/manual/en/book.pcre.php
*/
class PcreExtension extends \Twig_Extension
{
/**
* Class constructor
*/
public function __construct()
{
if (!extension_loaded('pcre')) {
throw new \Exception("The Twig PCRE extension requires the PCRE extension."); // @codeCoverageIgnore
}
}
/**
* Return extension name
*
* @return string
*/
public function getName()
{
return 'jasny/pcre';
}
/**
* {@inheritdoc}
*/
public function getFilters()
{
return [
new \Twig_SimpleFilter('preg_quote', [$this, 'quote']),
new \Twig_SimpleFilter('preg_match', [$this, 'match']),
new \Twig_SimpleFilter('preg_get', [$this, 'get']),
new \Twig_SimpleFilter('preg_get_all', [$this, 'getAll']),
new \Twig_SimpleFilter('preg_grep', [$this, 'grep']),
new \Twig_SimpleFilter('preg_replace', [$this, 'replace']),
new \Twig_SimpleFilter('preg_filter', [$this, 'filter']),
new \Twig_SimpleFilter('preg_split', [$this, 'split']),
];
}
/**
* Check that the regex doesn't use the eval modifier
*
* @param string $pattern
* @throws \LogicException
*/
protected function assertNoEval($pattern)
{
$pos = strrpos($pattern, $pattern[0]);
$modifiers = substr($pattern, $pos + 1);
if (strpos($modifiers, 'e') !== false) {
throw new \Twig_Error_Runtime("Using the eval modifier for regular expressions is not allowed");
}
}
/**
* Quote regular expression characters.
*
* @param string $value
* @param string $delimiter
* @return string
*/
public function quote($value, $delimiter = '/')
{
if (!isset($value)) {
return null;
}
return preg_quote($value, $delimiter);
}
/**
* Perform a regular expression match.
*
* @param string $value
* @param string $pattern
* @return boolean
*/
public function match($value, $pattern)
{
if (!isset($value)) {
return null;
}
return preg_match($pattern, $value);
}
/**
* Perform a regular expression match and return a matched group.
*
* @param string $value
* @param string $pattern
* @return string
*/
public function get($value, $pattern, $group = 0)
{
if (!isset($value)) {
return null;
}
return preg_match($pattern, $value, $matches) && isset($matches[$group]) ? $matches[$group] : null;
}
/**
* Perform a regular expression match and return the group for all matches.
*
* @param string $value
* @param string $pattern
* @return array
*/
public function getAll($value, $pattern, $group = 0)
{
if (!isset($value)) {
return null;
}
return preg_match_all($pattern, $value, $matches, PREG_PATTERN_ORDER) && isset($matches[$group])
? $matches[$group] : [];
}
/**
* Perform a regular expression match and return an array of entries that match the pattern
*
* @param array $values
* @param string $pattern
* @param string $flags Optional 'invert' to return entries that do not match the given pattern.
* @return array
*/
public function grep($values, $pattern, $flags = '')
{
if (!isset($values)) {
return null;
}
if (is_string($flags)) {
$flags = $flags === 'invert' ? PREG_GREP_INVERT : 0;
}
return preg_grep($pattern, $values, $flags);
}
/**
* Perform a regular expression search and replace.
*
* @param string $value
* @param string $pattern
* @param string $replacement
* @param int $limit
* @return string
*/
public function replace($value, $pattern, $replacement = '', $limit = -1)
{
$this->assertNoEval($pattern);
if (!isset($value)) {
return null;
}
return preg_replace($pattern, $replacement, $value, $limit);
}
/**
* Perform a regular expression search and replace, returning only matched subjects.
*
* @param string $value
* @param string $pattern
* @param string $replacement
* @param int $limit
* @return string
*/
public function filter($value, $pattern, $replacement = '', $limit = -1)
{
$this->assertNoEval($pattern);
if (!isset($value)) {
return null;
}
return preg_filter($pattern, $replacement, $value, $limit);
}
/**
* Split text into an array using a regular expression.
*
* @param string $value
* @param string $pattern
* @return array
*/
public function split($value, $pattern)
{
if (!isset($value)) {
return null;
}
return preg_split($pattern, $value);
}
}
+216
View File
@@ -0,0 +1,216 @@
<?php
namespace Jasny\Twig;
/**
* Text functions for Twig.
*/
class TextExtension extends \Twig_Extension
{
/**
* Return extension name
*
* @return string
*/
public function getName()
{
return 'jasny/text';
}
/**
* {@inheritdoc}
*/
public function getFilters()
{
return [
new \Twig_SimpleFilter('paragraph', [$this, 'paragraph'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
new \Twig_SimpleFilter('line', [$this, 'line']),
new \Twig_SimpleFilter('less', [$this, 'less'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
new \Twig_SimpleFilter('truncate', [$this, 'truncate'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
new \Twig_SimpleFilter('linkify', [$this, 'linkify'], ['pre_escape' => 'html', 'is_safe' => ['html']])
];
}
/**
* Add paragraph and line breaks to text.
*
* @param string $value
* @return string
*/
public function paragraph($value)
{
if (!isset($value)) {
return null;
}
return '<p>' . preg_replace(['~\n(\s*)\n\s*~', '~(?<!</p>)\n\s*~'], ["</p>\n\$1<p>", "<br>\n"], trim($value)) .
'</p>';
}
/**
* Get a single line
*
* @param string $value
* @param int $line Line number (starts at 1)
* @return string
*/
public function line($value, $line = 1)
{
if (!isset($value)) {
return null;
}
$lines = explode("\n", $value);
return isset($lines[$line - 1]) ? $lines[$line - 1] : null;
}
/**
* Cut of text on a pagebreak.
*
* @param string $value
* @param string $replace
* @param string $break
* @return string
*/
public function less($value, $replace = '...', $break = '<!-- pagebreak -->')
{
if (!isset($value)) {
return null;
}
$pos = stripos($value, $break);
return $pos === false ? $value : substr($value, 0, $pos) . $replace;
}
/**
* Cut of text if it's to long.
*
* @param string $value
* @param int $length
* @param string $replace
* @return string
*/
public function truncate($value, $length, $replace = '...')
{
if (!isset($value)) {
return null;
}
return strlen($value) <= $length ? $value : substr($value, 0, $length - strlen($replace)) . $replace;
}
/**
* Linkify a HTTP(S) link.
*
* @param string $protocol 'http' or 'https'
* @param string $text
* @param array $links OUTPUT
* @param string $attr
* @param string $mode
* @return string
*/
protected function linkifyHttp($protocol, $text, array &$links, $attr, $mode)
{
$regexp = $mode != 'all'
? '~(?:(https?)://([^\s<>]+)|(?<!\w@)\b(www\.[^\s<>]+?\.[^\s<>]+))(?<![\.,:;\?!\'"\|])~i'
: '~(?:(https?)://([^\s<>]+)|(?<!\w@)\b([^\s<>@]+?\.[^\s<>]+)(?<![\.,:]))~i';
return preg_replace_callback($regexp, function ($match) use ($protocol, &$links, $attr) {
if ($match[1]) $protocol = $match[1];
$link = $match[2] ?: $match[3];
return '<' . array_push($links, '<a' . $attr . ' href="' . $protocol . '://' . $link . '">'
. rtrim($link, '/') . '</a>') . '>';
}, $text);
}
/**
* Linkify a mail link.
*
* @param string $text
* @param array $links OUTPUT
* @param string $attr
* @return string
*/
protected function linkifyMail($text, array &$links, $attr)
{
$regexp = '~([^\s<>]+?@[^\s<>]+?\.[^\s<>]+)(?<![\.,:;\?!\'"\|])~';
return preg_replace_callback($regexp, function ($match) use (&$links, $attr) {
return '<' . array_push($links, '<a' . $attr . ' href="mailto:' . $match[1] . '">' . $match[1] . '</a>')
. '>';
}, $text);
}
/**
* Linkify a link.
*
* @param string $protocol
* @param string $text
* @param array $links OUTPUT
* @param string $attr
* @param string $mode
* @return string
*/
protected function linkifyOther($protocol, $text, array &$links, $attr, $mode)
{
if (strpos($protocol, ':') === false) {
$protocol .= in_array($protocol, ['ftp', 'tftp', 'ssh', 'scp']) ? '://' : ':';
}
$regexp = $mode != 'all'
? '~' . preg_quote($protocol, '~') . '([^\s<>]+)(?<![\.,:;\?!\'"\|])~i'
: '~([^\s<>]+)(?<![\.,:])~i';
return preg_replace_callback($regexp, function ($match) use ($protocol, &$links, $attr) {
return '<' . array_push($links, '<a' . $attr . ' href="' . $protocol . $match[1] . '">' . $match[1]
. '</a>') . '>';
}, $text);
}
/**
* Turn all URLs in clickable links.
*
* @param string $value
* @param array $protocols 'http'/'https', 'mail' and also 'ftp', 'scp', 'tel', etc
* @param array $attributes HTML attributes for the link
* @param string $mode normal or all
* @return string
*/
public function linkify($value, $protocols = ['http', 'mail'], array $attributes = [], $mode = 'normal')
{
if (!isset($value)) {
return null;
}
// Link attributes
$attr = '';
foreach ($attributes as $key => $val) {
$attr .= ' ' . $key . '="' . htmlentities($val) . '"';
}
$links = [];
// Extract existing links and tags
$text = preg_replace_callback('~(<a .*?>.*?</a>|<.*?>)~i', function ($match) use (&$links) {
return '<' . array_push($links, $match[1]) . '>';
}, $value);
// Extract text links for each protocol
foreach ((array)$protocols as $protocol) {
switch ($protocol) {
case 'http':
case 'https': $text = $this->linkifyHttp($protocol, $text, $links, $attr, $mode); break;
case 'mail': $text = $this->linkifyMail($text, $links, $attr); break;
default: $text = $this->linkifyOther($protocol, $text, $links, $attr, $mode); break;
}
}
// Insert all link
return preg_replace_callback('/<(\d+)>/', function ($match) use (&$links) {
return $links[$match[1] - 1];
}, $text);
}
}
@@ -0,0 +1,89 @@
<?php
namespace Jasny\Twig;
use Jasny\Twig\ArrayExtension;
use Jasny\Twig\TestHelper;
/**
* @covers Jasny\Twig\ArrayExtension
*/
class ArrayExtensionTest extends \PHPUnit_Framework_TestCase
{
use TestHelper;
protected function getExtension()
{
return new ArrayExtension();
}
public function testSum()
{
$data = [1, 2, 3, 4];
$this->assertRender('10', '{{ data|sum }}', compact('data'));
}
public function testProduct()
{
$data = [1, 2, 3, 4];
$this->assertRender('24', '{{ data|product }}', compact('data'));
}
public function testValues()
{
$data = (object)['foo' => 1, 'bar' => 2, 'zoo' => 3];
$this->assertRender('1-2-3', '{{ data|values|join("-") }}', compact('data'));
}
public function testAsArrayWithObject()
{
$data = (object)['foo' => 1, 'bar' => 2, 'zoo' => 3];
$this->assertRender('foo-bar-zoo', '{{ data|as_array|keys|join("-") }}', compact('data'));
}
public function testAsArrayWithString()
{
$data = 'foo';
$this->assertRender('foo', '{{ data|as_array|join("-") }}', compact('data'));
}
public function testHtmlAttr()
{
$data = ['href' => 'foo.html', 'class' => 'big small', 'checked' => true, 'disabled' => false];
$this->assertRender(
'href="foo.html" class="big small" checked="checked"',
'{{ data|html_attr|raw }}',
compact('data')
);
}
public function filterProvider()
{
return [
['sum'],
['product'],
['values'],
['as_array'],
['html_attr']
];
}
/**
* @dataProvider filterProvider
*
* @param string $filter
*/
public function testWithNull($filter)
{
$this->assertRender('-', '{{ null|' . $filter . '("//")|default("-") }}');
}
}
+168
View File
@@ -0,0 +1,168 @@
<?php
namespace Jasny\Twig;
use Jasny\Twig\DateExtension;
use Jasny\Twig\TestHelper;
/**
* @covers Jasny\Twig\DateExtension
*/
class DateExtensionTest extends \PHPUnit_Framework_TestCase
{
use TestHelper;
public function setUp()
{
date_default_timezone_set('UTC');
\Locale::setDefault("en_EN");
}
protected function getExtension()
{
return new DateExtension();
}
public function localDateTimeProvider()
{
return [
['9/20/2015', '20-09-2015', "{{ '20-09-2015'|localdate }}"],
['September 20, 2015', '20 september 2015', "{{ '20-09-2015'|localdate('long') }}"],
['9/20/15', "20-09-15", "{{ '20-09-2015'|localdate('short') }}"],
['Sunday, September 20, 2015', "zondag 20 september 2015", "{{ '20-09-2015'|localdate('full') }}"],
['20|09|2015', "20|09|2015", "{{ '20-09-2015'|localdate('dd|MM|yyyy') }}"],
['11:14 PM', "23:14", "{{ '23:14:12'|localtime }}"],
['11:14:12 PM GMT', "23:14:12 GMT", "{{ '23:14:12'|localtime('long') }}"],
['11:14 PM', "23:14", "{{ '23:14:12'|localtime('short') }}"],
['11:14:12 PM GMT', "23:14:12 GMT", "{{ '23:14:12'|localtime('full') }}"],
['23|14|12', "23|14|12", "{{ '23:14:12'|localtime('HH|mm|ss') }}"],
// NOTE: a `replace` is used to remove the comma, which seems to be inconsistant accross environments.
['9/20/2015 11:14 PM', '20-09-2015 23:14', "{{ '20-09-2015 23:14:12'|localdatetime|replace({',': ''}) }}"],
['20|23', '20|23', "{{ '20-09-2015 23:14:12'|localdatetime('dd|HH') }}"],
[
'9/20/15 11:14:12 PM GMT',
'20-09-15 23:14:12 GMT',
"{{ '20-09-2015 23:14:12'|localdatetime({date: 'short', time: 'full'})|replace({',': ''}) }}"
],
[
'20150920 11:14:12 PM GMT',
'20150920 23:14:12 GMT',
"{{ '20-09-2015 23:14:12'|localdatetime({date: 'yyyyMMdd', time: 'full'}) }}"
]
];
}
/**
* @dataProvider localDateTimeProvider
*
* @param string $en
* @param string $nl
* @param string $template
*/
public function testLocalDateTimeEn($en, $nl, $template)
{
if (!\Locale::setDefault("en_EN")) {
return $this->markAsSkipped("Unable to set locale to 'en_EN'");
}
$this->assertRender($en, $template);
}
/**
* @dataProvider localDateTimeProvider
*
* @param string $en
* @param string $nl
* @param string $template
*/
public function testLocalDateTimeNL($en, $nl, $template)
{
if (!\Locale::setDefault("nl_NL")) {
return $this->markAsSkipped("Unable to set locale to 'nl_NL'");
}
$this->assertRender($nl, $template);
}
public function durationProvider()
{
return [
['31s', "{{ 31|duration }}"],
['17m 31s', "{{ 1051|duration }}"],
['3h 17m 31s', "{{ 11851|duration }}"],
['2d 3h 17m 31s', "{{ 184651|duration }}"],
['3w 2d 3h 17m 31s', "{{ 1999051|duration }}"],
['1y 3w 2d 3h 17m 31s', "{{ 33448651|duration }}"],
['17 minute(s)', "{{ 1051|duration([null, ' minute(s)', ' hour(s)', ' day(s)']) }}"],
['3 hour(s)', "{{ 11851|duration([null, null, ' hour(s)']) }}"],
['2 day(s)', "{{ 184651|duration([null, null, null, ' day(s)']) }}"],
['3 week(s)', "{{ 1999051|duration([null, null, null, null, ' week(s)']) }}"],
['1 year(s)', "{{ 33448651|duration([null, null, null, null, null, ' year(s)']) }}"],
['3u:17m', "{{ 11851|duration([null, 'm', 'u'], ':') }}"],
['3:17h', "{{ 11851|duration([null, '', ''], ':') }}h"],
];
}
/**
* @dataProvider durationProvider
*
* @param string $expect
* @param string $template
*/
public function testDuration($expect, $template)
{
$this->assertRender($expect, $template);
}
public function ageProvider()
{
$time = time() - (((32 * 365) + 100) * 24 * 3600);
$date = date('Y-m-d', $time);
return [
['32', "{{ $time|age }}"],
['32', "{{ '$date'|age }}"]
];
}
/**
* @dataProvider ageProvider
*
* @param string $expect
* @param string $template
*/
public function testAge($expect, $template)
{
$this->assertRender($expect, $template);
}
public function filterProvider()
{
return [
['localdate'],
['localtime'],
['localdatetime'],
['duration'],
['age']
];
}
/**
* @dataProvider filterProvider
*
* @param string $filter
*/
public function testWithNull($filter)
{
$this->assertRender('-', '{{ null|' . $filter . '("//")|default("-") }}');
}
}
+177
View File
@@ -0,0 +1,177 @@
<?php
namespace Jasny\Twig;
use Jasny\Twig\PcreExtension;
use Jasny\Twig\TestHelper;
/**
* @covers Jasny\Twig\PcreExtension
*/
class PcreExtensionTest extends \PHPUnit_Framework_TestCase
{
use TestHelper;
protected function getExtension()
{
return new PcreExtension();
}
public function testGetName()
{
$this->assertEquals('jasny/pcre', $this->getExtension()->getName());
}
public function testQuote()
{
$this->assertRender('foo\(\)', '{{ "foo()"|preg_quote }}');
}
public function testQuoteDelimiter()
{
$this->assertRender('foo\@bar', '{{ "foo@bar"|preg_quote("@") }}');
}
public function testPregMatch()
{
$this->assertRender('YES', '{% if "foo"|preg_match("/oo/") %}YES{% else %}NO{% endif %}');
}
public function testPregMatchNo()
{
$this->assertRender('NO', '{% if "fod"|preg_match("/oo/") %}YES{% else %}NO{% endif %}');
}
/**
* @expectedException \Twig_Error_Runtime
*/
public function testPregMatchError()
{
$this->render('{% if "fod"|preg_match("/o//o/") %}YES{% else %}NO{% endif %}');
}
public function testPregGet()
{
$this->assertRender('d', '{{ "food"|preg_get("/oo(.)/", 1) }}');
}
public function testPregGetDefault()
{
$this->assertRender('ood', '{{ "food"|preg_get("/oo(.)/") }}');
}
public function testPregGetAll()
{
$this->assertRender('d|t|m', '{{ "food woot should doom"|preg_get_all("/oo(.)/", 1)|join("|") }}');
}
public function testPregGetAllDefault()
{
$this->assertRender('ood|oot|oom', '{{ "food woot doom"|preg_get_all("/oo(.)/")|join("|") }}');
}
public function testPregGrep()
{
$this->assertRender(
'world|how|you',
'{{ ["hello", "sweet", "world", "how", "are", "you"]|preg_grep("/o./")|join("|") }}'
);
}
public function testPregGrepInvert()
{
$this->assertRender(
'hello|sweet|are',
'{{ ["hello", "sweet", "world", "how", "are", "you"]|preg_grep("/o./", "invert")|join("|") }}'
);
}
public function testReplace()
{
$this->assertRender(
'the quick brawen faxe jumped aveer the lazy dage',
'{{ "the quick brown fox jumped over the lazy dog"|preg_replace("/o(\\\\w)/", "a$1e") }}'
);
}
public function testReplaceLimit()
{
$this->assertRender(
'the quick brawen faxe jumped over the lazy dog',
'{{ "the quick brown fox jumped over the lazy dog"|preg_replace("/o(\\\\w)/", "a$1e", 2) }}'
);
}
public function testReplaceWithArray()
{
$this->assertRender(
'hello|sweet|wareld|hawe|are|yaue',
'{{ ["hello", "sweet", "world", "how", "are", "you"]|preg_replace("/o(.)/", "a$1e")|join("|") }}'
);
}
/**
* @expectedException Twig_Error_Runtime
*/
public function testReplaceAssertNoEval()
{
$this->render('{{ "foo"|preg_replace("/o/ei", "strtoupper") }}');
}
public function testFilter()
{
$this->assertRender(
'wareld|hawe|yaue',
'{{ ["hello", "sweet", "world", "how", "are", "you"]|preg_filter("/o(.)/", "a$1e")|join("|") }}'
);
}
/**
* @expectedException Twig_Error_Runtime
*/
public function testFilterAssertNoEval()
{
$this->render('{{ "foo"|preg_filter("/o/ei", "strtoupper") }}');
}
public function testSplit()
{
$this->assertRender(
'the quick br|n f| jumped |er the lazy d|',
'{{ "the quick brown fox jumped over the lazy dog"|preg_split("/o(\\\\w)/", "a$1e")|join("|") }}'
);
}
public function filterProvider()
{
return [
['preg_quote'],
['preg_match'],
['preg_get'],
['preg_get_all'],
['preg_grep'],
['preg_replace'],
['preg_filter'],
['preg_split']
];
}
/**
* @dataProvider filterProvider
*
* @param string $filter
*/
public function testWithNull($filter)
{
$this->assertRender('-', '{{ null|' . $filter . '("//")|default("-") }}');
}
}
+181
View File
@@ -0,0 +1,181 @@
<?php
namespace Jasny\Twig;
use Jasny\Twig\TextExtension;
use Jasny\Twig\TestHelper;
/**
* @covers Jasny\Twig\TextExtension
*/
class TextExtensionTest extends \PHPUnit_Framework_TestCase
{
use TestHelper;
protected function getExtension()
{
return new TextExtension();
}
public function testParagraph()
{
$this->assertRender("<p>foo<br>\nbar</p>\n<p>monkey</p>", "{{ 'foo\nbar\n\nmonkey'|paragraph() }}");
}
public function testLine()
{
$this->assertRender("foo", "{{ 'foo\nbar\nbaz'|line() }}");
}
public function testLineTwo()
{
$this->assertRender("bar", "{{ 'foo\nbar\nbaz'|line(2) }}");
}
public function testLineToHigh()
{
$this->assertRender("", "{{ 'foo\nbar\nbaz'|line(100) }}");
}
public function testLess()
{
$this->assertRender("foo...", "{{ 'foo<!-- pagebreak -->baz'|less() }}");
}
public function testLessCustom()
{
$this->assertRender("foo..", "{{ 'fooXbarXbaz'|less('..', 'X') }}");
}
public function testLessNoPageBreak()
{
$this->assertRender("foo bar", "{{ 'foo bar'|less }}");
}
public function testTruncate()
{
$this->assertRender("foo...", "{{ 'foo bar baz'|truncate(6) }}");
}
public function testTruncateCustom()
{
$this->assertRender("foo ..", "{{ 'foo bar baz'|truncate(6, '..') }}");
}
public function testTruncateToHigh()
{
$this->assertRender("foo bar baz", "{{ 'foo bar baz'|truncate(100) }}");
}
public function testLinkify()
{
$this->assertRender(
'<a href="http://www.example.com">www.example.com</a>, color.bar and '
. '<a href="mailto:john@example.com">john@example.com</a>',
'{{ "www.example.com, color.bar and john@example.com"|linkify }}'
);
}
public function testLinkifyAll()
{
$this->assertRender(
'<a href="http://www.example.com">www.example.com</a>, <a href="http://color.bar">color.bar</a> and '
. '<a href="mailto:john@example.com">john@example.com</a>',
'{{ "www.example.com, color.bar and john@example.com"|linkify(["http", "mail"], [], "all") }}'
);
}
public function testLinkifyHttps()
{
$this->assertRender(
'<a href="https://www.example.com">www.example.com</a>',
'{{ "www.example.com"|linkify("https") }}'
);
}
public function testLinkifyMail()
{
$this->assertRender(
'<a href="mailto:john@example.com">john@example.com</a> and '
. '<a href="mailto:jeff@example.com">jeff@example.com</a>',
'{{ "john@example.com and jeff@example.com"|linkify }}'
);
}
public function testLinkifyFtp()
{
$this->assertRender(
'<a href="ftp://www.example.com">www.example.com</a>',
'{{ "ftp://www.example.com"|linkify("ftp") }}'
);
}
public function testLinkifyFtpAll()
{
$this->assertRender(
'<a href="ftp://www.example.com">www.example.com</a>',
'{{ "www.example.com"|linkify("ftp", [], "all") }}'
);
}
public function testLinkifyOther()
{
$this->assertRender(
'<a href="foo:abc.def.hif">abc.def.hif</a>',
'{{ "foo:abc.def.hif"|linkify("foo") }}'
);
}
public function testLinkifyOtherAll()
{
$this->assertRender(
'<a href="foo:abc.def.hif">abc.def.hif</a>',
'{{ "abc.def.hif"|linkify("foo", [], "all") }}'
);
}
public function testLinkifyWithAttributes()
{
$this->assertRender(
'<a foo="bar" color="blue" href="http://www.example.com">www.example.com</a> and '
. '<a foo="bar" color="blue" href="mailto:john@example.com">john@example.com</a>',
'{{ "www.example.com and john@example.com"|linkify(["http", "mail"], {foo: "bar", color: "blue"}) }}'
);
}
public function testLinkifyWithExistingLink()
{
$this->assertRender(
'<a href="http://www.example.com">www.example.com</a> and '
. '<a href="http://www.example.net">www.example.net</a>',
'{{ "<a href=\\"http://www.example.com\\">www.example.com</a> and www.example.net"|linkify }}'
);
}
public function filterProvider()
{
return [
['paragraph'],
['line'],
['less'],
['truncate'],
['linkify']
];
}
/**
* @dataProvider filterProvider
*
* @param string $filter
*/
public function testWithNull($filter)
{
$this->assertRender('-', '{{ null|' . $filter . '("//")|default("-") }}');
}
}
@@ -0,0 +1,63 @@
<?php
namespace Jasny\Twig;
/**
* Helper methods for unit tests
*/
trait TestHelper
{
/**
* Get the tested extension
*
* @return \Twig_Extension
*/
abstract protected function getExtension();
/**
* Build the Twig environment for the template
*
* @param string $template
* @return \Twig_Environment
*/
protected function buildEnv($template)
{
$loader = new \Twig_Loader_Array([
'template' => $template,
]);
$twig = new \Twig_Environment($loader);
$twig->addExtension($this->getExtension());
return $twig;
}
/**
* Render template
*
* @param string $template
* @param array $data
* @return string
*/
protected function render($template, array $data = [])
{
$twig = $this->buildEnv($template);
$result = $twig->render('template', $data);
return $result;
}
/**
* Render template and assert equals
*
* @param string $expected
* @param string $template
* @param array $data
*/
protected function assertRender($expected, $template, array $data = [])
{
$result = $this->render($template, $data);
$this->assertEquals($expected, (string)$result);
}
}