started underscores theme
This commit is contained in:
parent
1d21705ef1
commit
5aa4471032
|
@ -31,5 +31,10 @@
|
|||
"support": {
|
||||
"issues": "https://github.com/Automattic/_s/issues",
|
||||
"source": "https://github.com/Automattic/_s"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit076edb6aeb7c9f39fc30d8f0f581f9f9::getLoader();
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../gettext/languages/bin/export-plural-rules)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
include("phpvfscomposer://" . __DIR__ . '/..'.'/gettext/languages/bin/export-plural-rules');
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
include __DIR__ . '/..'.'/gettext/languages/bin/export-plural-rules';
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../php-parallel-lint/php-parallel-lint/parallel-lint)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
include("phpvfscomposer://" . __DIR__ . '/..'.'/php-parallel-lint/php-parallel-lint/parallel-lint');
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
include __DIR__ . '/..'.'/php-parallel-lint/php-parallel-lint/parallel-lint';
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../squizlabs/php_codesniffer/bin/phpcbf)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf');
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf';
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../squizlabs/php_codesniffer/bin/phpcs)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs');
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs';
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Support bash to support `source` with fallback on $0 if this does not run with bash
|
||||
# https://stackoverflow.com/a/35006505/6512
|
||||
selfArg="$BASH_SOURCE"
|
||||
if [ -z "$selfArg" ]; then
|
||||
selfArg="$0"
|
||||
fi
|
||||
|
||||
self=$(realpath $selfArg 2> /dev/null)
|
||||
if [ -z "$self" ]; then
|
||||
self="$selfArg"
|
||||
fi
|
||||
|
||||
dir=$(cd "${self%[/\\]*}" > /dev/null; cd '../wp-cli/wp-cli/bin' && pwd)
|
||||
|
||||
if [ -d /proc/cygdrive ]; then
|
||||
case $(which php) in
|
||||
$(readlink -n /proc/cygdrive)/*)
|
||||
# We are in Cygwin using Windows php, so the path must be translated
|
||||
dir=$(cygpath -m "$dir");
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
export COMPOSER_RUNTIME_BIN_DIR="$(cd "${self%[/\\]*}" > /dev/null; pwd)"
|
||||
|
||||
# If bash is sourcing this file, we have to source the target as well
|
||||
bashSource="$BASH_SOURCE"
|
||||
if [ -n "$bashSource" ]; then
|
||||
if [ "$bashSource" != "$0" ]; then
|
||||
source "${dir}/wp" "$@"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
"${dir}/wp" "$@"
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Support bash to support `source` with fallback on $0 if this does not run with bash
|
||||
# https://stackoverflow.com/a/35006505/6512
|
||||
selfArg="$BASH_SOURCE"
|
||||
if [ -z "$selfArg" ]; then
|
||||
selfArg="$0"
|
||||
fi
|
||||
|
||||
self=$(realpath $selfArg 2> /dev/null)
|
||||
if [ -z "$self" ]; then
|
||||
self="$selfArg"
|
||||
fi
|
||||
|
||||
dir=$(cd "${self%[/\\]*}" > /dev/null; cd '../wp-cli/wp-cli/bin' && pwd)
|
||||
|
||||
if [ -d /proc/cygdrive ]; then
|
||||
case $(which php) in
|
||||
$(readlink -n /proc/cygdrive)/*)
|
||||
# We are in Cygwin using Windows php, so the path must be translated
|
||||
dir=$(cygpath -m "$dir");
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
export COMPOSER_RUNTIME_BIN_DIR="$(cd "${self%[/\\]*}" > /dev/null; pwd)"
|
||||
|
||||
# If bash is sourcing this file, we have to source the target as well
|
||||
bashSource="$BASH_SOURCE"
|
||||
if [ -n "$bashSource" ]; then
|
||||
if [ "$bashSource" != "$0" ]; then
|
||||
source "${dir}/wp.bat" "$@"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
"${dir}/wp.bat" "$@"
|
|
@ -0,0 +1,572 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see https://www.php-fig.org/psr/psr-0/
|
||||
* @see https://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
/** @var ?string */
|
||||
private $vendorDir;
|
||||
|
||||
// PSR-4
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, array<string, int>>
|
||||
*/
|
||||
private $prefixLengthsPsr4 = array();
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, array<int, string>>
|
||||
*/
|
||||
private $prefixDirsPsr4 = array();
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, string>
|
||||
*/
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, array<string, string[]>>
|
||||
*/
|
||||
private $prefixesPsr0 = array();
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, string>
|
||||
*/
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
/** @var bool */
|
||||
private $useIncludePath = false;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @psalm-var array<string, string>
|
||||
*/
|
||||
private $classMap = array();
|
||||
|
||||
/** @var bool */
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
/**
|
||||
* @var bool[]
|
||||
* @psalm-var array<string, bool>
|
||||
*/
|
||||
private $missingClasses = array();
|
||||
|
||||
/** @var ?string */
|
||||
private $apcuPrefix;
|
||||
|
||||
/**
|
||||
* @var self[]
|
||||
*/
|
||||
private static $registeredLoaders = array();
|
||||
|
||||
/**
|
||||
* @param ?string $vendorDir
|
||||
*/
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @psalm-return array<string, array<int, string>>
|
||||
*/
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @psalm-return array<string, string>
|
||||
*/
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @psalm-return array<string, string>
|
||||
*/
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] Array of classname => path
|
||||
* @psalm-return array<string, string>
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $classMap Class to filename map
|
||||
* @psalm-param array<string, string> $classMap
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param string[]|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param string[]|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param string[]|string $paths The PSR-0 base directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param string[]|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
|
||||
if (null === $this->vendorDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($prepend) {
|
||||
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
||||
} else {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
self::$registeredLoaders[$this->vendorDir] = $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
|
||||
if (null !== $this->vendorDir) {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return true|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently registered loaders indexed by their corresponding vendor directories.
|
||||
*
|
||||
* @return self[]
|
||||
*/
|
||||
public static function getRegisteredLoaders()
|
||||
{
|
||||
return self::$registeredLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $ext
|
||||
* @return string|false
|
||||
*/
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*
|
||||
* @param string $file
|
||||
* @return void
|
||||
* @private
|
||||
*/
|
||||
function includeFile($file)
|
||||
{
|
||||
include $file;
|
||||
}
|
|
@ -0,0 +1,352 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* This class is copied in every Composer installed project and available to all
|
||||
*
|
||||
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
|
||||
*
|
||||
* To require its presence, you can require `composer-runtime-api ^2.0`
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private static $canGetVendors;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static $installedByVendor = array();
|
||||
|
||||
/**
|
||||
* Returns a list of all package names which are present, either by being installed, replaced or provided
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackages()
|
||||
{
|
||||
$packages = array();
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
$packages[] = array_keys($installed['versions']);
|
||||
}
|
||||
|
||||
if (1 === \count($packages)) {
|
||||
return $packages[0];
|
||||
}
|
||||
|
||||
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all package names with a specific type e.g. 'library'
|
||||
*
|
||||
* @param string $type
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackagesByType($type)
|
||||
{
|
||||
$packagesByType = array();
|
||||
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
foreach ($installed['versions'] as $name => $package) {
|
||||
if (isset($package['type']) && $package['type'] === $type) {
|
||||
$packagesByType[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $packagesByType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package is installed
|
||||
*
|
||||
* This also returns true if the package name is provided or replaced by another package
|
||||
*
|
||||
* @param string $packageName
|
||||
* @param bool $includeDevRequirements
|
||||
* @return bool
|
||||
*/
|
||||
public static function isInstalled($packageName, $includeDevRequirements = true)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (isset($installed['versions'][$packageName])) {
|
||||
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package satisfies a version constraint
|
||||
*
|
||||
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
|
||||
*
|
||||
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
|
||||
*
|
||||
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
|
||||
* @param string $packageName
|
||||
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
|
||||
* @return bool
|
||||
*/
|
||||
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||
{
|
||||
$constraint = $parser->parseConstraints($constraint);
|
||||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||
|
||||
return $provided->matches($constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a version constraint representing all the range(s) which are installed for a given package
|
||||
*
|
||||
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
|
||||
* whether a given version of a package is installed, and not just whether it exists
|
||||
*
|
||||
* @param string $packageName
|
||||
* @return string Version constraint usable with composer/semver
|
||||
*/
|
||||
public static function getVersionRanges($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ranges = array();
|
||||
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
||||
}
|
||||
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
||||
}
|
||||
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
||||
}
|
||||
|
||||
return implode(' || ', $ranges);
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getPrettyVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
|
||||
*/
|
||||
public static function getReference($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['reference'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['reference'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
|
||||
*/
|
||||
public static function getInstallPath($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
|
||||
*/
|
||||
public static function getRootPackage()
|
||||
{
|
||||
$installed = self::getInstalled();
|
||||
|
||||
return $installed[0]['root'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw installed.php data for custom implementations
|
||||
*
|
||||
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
|
||||
* @return array[]
|
||||
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
|
||||
*/
|
||||
public static function getRawData()
|
||||
{
|
||||
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
self::$installed = include __DIR__ . '/installed.php';
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
return self::$installed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw data of all installed.php which are currently loaded for custom implementations
|
||||
*
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
public static function getAllRawData()
|
||||
{
|
||||
return self::getInstalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets you reload the static array from another file
|
||||
*
|
||||
* This is only useful for complex integrations in which a project needs to use
|
||||
* this class but then also needs to execute another project's autoloader in process,
|
||||
* and wants to ensure both projects have access to their version of installed.php.
|
||||
*
|
||||
* A typical case would be PHPUnit, where it would need to make sure it reads all
|
||||
* the data it needs from this class, then call reload() with
|
||||
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
|
||||
* the project in which it runs can then also use this class safely, without
|
||||
* interference between PHPUnit's dependencies and the project's dependencies.
|
||||
*
|
||||
* @param array[] $data A vendor/composer/installed.php data set
|
||||
* @return void
|
||||
*
|
||||
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
|
||||
*/
|
||||
public static function reload($data)
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static function getInstalled()
|
||||
{
|
||||
if (null === self::$canGetVendors) {
|
||||
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
||||
}
|
||||
|
||||
$installed = array();
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
|
||||
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
||||
self::$installed = $installed[count($installed) - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
self::$installed = require __DIR__ . '/installed.php';
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
$installed[] = self::$installed;
|
||||
|
||||
return $installed;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
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.
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Application' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Application.php',
|
||||
'JakubOnderka\\PhpParallelLint\\ArrayIterator' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Settings.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Blame' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Error.php',
|
||||
'JakubOnderka\\PhpParallelLint\\CheckstyleOutput' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\ConsoleWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Contracts\\SyntaxErrorCallback' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Error' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Error.php',
|
||||
'JakubOnderka\\PhpParallelLint\\ErrorFormatter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Exception' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\FileWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\GitLabOutput' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\IWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\InvalidArgumentException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\JsonOutput' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Manager' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Manager.php',
|
||||
'JakubOnderka\\PhpParallelLint\\MultipleWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\NotExistsClassException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\NotExistsPathException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\NotImplementCallbackException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\NullWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Output' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\ParallelLint' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/ParallelLint.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Process\\GitBlameProcess' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Process\\LintProcess' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Process\\PhpExecutable' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Process\\PhpProcess' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Process\\Process' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/Process.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Process\\SkipLintProcess' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php',
|
||||
'JakubOnderka\\PhpParallelLint\\RecursiveDirectoryFilterIterator' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Manager.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Result' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Result.php',
|
||||
'JakubOnderka\\PhpParallelLint\\RunTimeException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Settings' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Settings.php',
|
||||
'JakubOnderka\\PhpParallelLint\\SyntaxError' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Error.php',
|
||||
'JakubOnderka\\PhpParallelLint\\TextOutput' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\TextOutputColored' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JsonSerializable' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/polyfill.php',
|
||||
'WP_CLI' => $vendorDir . '/wp-cli/wp-cli/php/class-wp-cli.php',
|
||||
'WP_CLI_Command' => $vendorDir . '/wp-cli/wp-cli/php/class-wp-cli-command.php',
|
||||
);
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
// autoload_files.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'3937806105cc8e221b8fa8db5b70d2f2' => $vendorDir . '/wp-cli/mustangostang-spyc/includes/functions.php',
|
||||
'be01b9b16925dcb22165c40b46681ac6' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/cli.php',
|
||||
'ffb465a494c3101218c4417180c2c9a2' => $vendorDir . '/wp-cli/i18n-command/i18n-command.php',
|
||||
);
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'cli' => array($vendorDir . '/wp-cli/php-cli-tools/lib'),
|
||||
'WP_CLI\\' => array($vendorDir . '/wp-cli/wp-cli/php'),
|
||||
'Requests' => array($vendorDir . '/rmccue/requests/library'),
|
||||
'Mustache' => array($vendorDir . '/mustache/mustache/src'),
|
||||
);
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'eftec\\bladeone\\' => array($vendorDir . '/eftec/bladeone/lib'),
|
||||
'WP_CLI\\I18n\\' => array($vendorDir . '/wp-cli/i18n-command/src'),
|
||||
'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
|
||||
'Peast\\test\\' => array($vendorDir . '/mck89/peast/test/Peast'),
|
||||
'Peast\\' => array($vendorDir . '/mck89/peast/lib/Peast'),
|
||||
'Mustangostang\\' => array($vendorDir . '/wp-cli/mustangostang-spyc/src'),
|
||||
'Gettext\\Languages\\' => array($vendorDir . '/gettext/languages/src'),
|
||||
'Gettext\\' => array($vendorDir . '/gettext/gettext/src'),
|
||||
'Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => array($vendorDir . '/dealerdirect/phpcodesniffer-composer-installer/src'),
|
||||
);
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit076edb6aeb7c9f39fc30d8f0f581f9f9
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit076edb6aeb7c9f39fc30d8f0f581f9f9', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit076edb6aeb7c9f39fc30d8f0f581f9f9', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit076edb6aeb7c9f39fc30d8f0f581f9f9::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
$includeFiles = \Composer\Autoload\ComposerStaticInit076edb6aeb7c9f39fc30d8f0f581f9f9::$files;
|
||||
foreach ($includeFiles as $fileIdentifier => $file) {
|
||||
composerRequire076edb6aeb7c9f39fc30d8f0f581f9f9($fileIdentifier, $file);
|
||||
}
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fileIdentifier
|
||||
* @param string $file
|
||||
* @return void
|
||||
*/
|
||||
function composerRequire076edb6aeb7c9f39fc30d8f0f581f9f9($fileIdentifier, $file)
|
||||
{
|
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
||||
|
||||
require $file;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit076edb6aeb7c9f39fc30d8f0f581f9f9
|
||||
{
|
||||
public static $files = array (
|
||||
'3937806105cc8e221b8fa8db5b70d2f2' => __DIR__ . '/..' . '/wp-cli/mustangostang-spyc/includes/functions.php',
|
||||
'be01b9b16925dcb22165c40b46681ac6' => __DIR__ . '/..' . '/wp-cli/php-cli-tools/lib/cli/cli.php',
|
||||
'ffb465a494c3101218c4417180c2c9a2' => __DIR__ . '/..' . '/wp-cli/i18n-command/i18n-command.php',
|
||||
);
|
||||
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'e' =>
|
||||
array (
|
||||
'eftec\\bladeone\\' => 15,
|
||||
),
|
||||
'W' =>
|
||||
array (
|
||||
'WP_CLI\\I18n\\' => 12,
|
||||
),
|
||||
'S' =>
|
||||
array (
|
||||
'Symfony\\Component\\Finder\\' => 25,
|
||||
),
|
||||
'P' =>
|
||||
array (
|
||||
'Peast\\test\\' => 11,
|
||||
'Peast\\' => 6,
|
||||
),
|
||||
'M' =>
|
||||
array (
|
||||
'Mustangostang\\' => 14,
|
||||
),
|
||||
'G' =>
|
||||
array (
|
||||
'Gettext\\Languages\\' => 18,
|
||||
'Gettext\\' => 8,
|
||||
),
|
||||
'D' =>
|
||||
array (
|
||||
'Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => 55,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'eftec\\bladeone\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/eftec/bladeone/lib',
|
||||
),
|
||||
'WP_CLI\\I18n\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/wp-cli/i18n-command/src',
|
||||
),
|
||||
'Symfony\\Component\\Finder\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/finder',
|
||||
),
|
||||
'Peast\\test\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/mck89/peast/test/Peast',
|
||||
),
|
||||
'Peast\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/mck89/peast/lib/Peast',
|
||||
),
|
||||
'Mustangostang\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/wp-cli/mustangostang-spyc/src',
|
||||
),
|
||||
'Gettext\\Languages\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/gettext/languages/src',
|
||||
),
|
||||
'Gettext\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/gettext/gettext/src',
|
||||
),
|
||||
'Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/dealerdirect/phpcodesniffer-composer-installer/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixesPsr0 = array (
|
||||
'c' =>
|
||||
array (
|
||||
'cli' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/wp-cli/php-cli-tools/lib',
|
||||
),
|
||||
),
|
||||
'W' =>
|
||||
array (
|
||||
'WP_CLI\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/wp-cli/wp-cli/php',
|
||||
),
|
||||
),
|
||||
'R' =>
|
||||
array (
|
||||
'Requests' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/rmccue/requests/library',
|
||||
),
|
||||
),
|
||||
'M' =>
|
||||
array (
|
||||
'Mustache' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/mustache/mustache/src',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Application' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Application.php',
|
||||
'JakubOnderka\\PhpParallelLint\\ArrayIterator' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Settings.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Blame' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Error.php',
|
||||
'JakubOnderka\\PhpParallelLint\\CheckstyleOutput' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\ConsoleWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Contracts\\SyntaxErrorCallback' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Error' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Error.php',
|
||||
'JakubOnderka\\PhpParallelLint\\ErrorFormatter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Exception' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\FileWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\GitLabOutput' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\IWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\InvalidArgumentException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\JsonOutput' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Manager' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Manager.php',
|
||||
'JakubOnderka\\PhpParallelLint\\MultipleWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\NotExistsClassException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\NotExistsPathException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\NotImplementCallbackException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\NullWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Output' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\ParallelLint' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/ParallelLint.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Process\\GitBlameProcess' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Process\\LintProcess' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Process\\PhpExecutable' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Process\\PhpProcess' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Process\\Process' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/Process.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Process\\SkipLintProcess' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php',
|
||||
'JakubOnderka\\PhpParallelLint\\RecursiveDirectoryFilterIterator' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Manager.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Result' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Result.php',
|
||||
'JakubOnderka\\PhpParallelLint\\RunTimeException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
|
||||
'JakubOnderka\\PhpParallelLint\\Settings' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Settings.php',
|
||||
'JakubOnderka\\PhpParallelLint\\SyntaxError' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Error.php',
|
||||
'JakubOnderka\\PhpParallelLint\\TextOutput' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JakubOnderka\\PhpParallelLint\\TextOutputColored' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
|
||||
'JsonSerializable' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/polyfill.php',
|
||||
'WP_CLI' => __DIR__ . '/..' . '/wp-cli/wp-cli/php/class-wp-cli.php',
|
||||
'WP_CLI_Command' => __DIR__ . '/..' . '/wp-cli/wp-cli/php/class-wp-cli-command.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit076edb6aeb7c9f39fc30d8f0f581f9f9::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit076edb6aeb7c9f39fc30d8f0f581f9f9::$prefixDirsPsr4;
|
||||
$loader->prefixesPsr0 = ComposerStaticInit076edb6aeb7c9f39fc30d8f0f581f9f9::$prefixesPsr0;
|
||||
$loader->classMap = ComposerStaticInit076edb6aeb7c9f39fc30d8f0f581f9f9::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,206 @@
|
|||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => 'automattic/underscores',
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => '1d21705ef199a337fbc9790702a658bcf2f90418',
|
||||
'type' => 'wordpress-theme',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev' => true,
|
||||
),
|
||||
'versions' => array(
|
||||
'automattic/underscores' => array(
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => '1d21705ef199a337fbc9790702a658bcf2f90418',
|
||||
'type' => 'wordpress-theme',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'dealerdirect/phpcodesniffer-composer-installer' => array(
|
||||
'pretty_version' => 'v0.7.2',
|
||||
'version' => '0.7.2.0',
|
||||
'reference' => '1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db',
|
||||
'type' => 'composer-plugin',
|
||||
'install_path' => __DIR__ . '/../dealerdirect/phpcodesniffer-composer-installer',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'eftec/bladeone' => array(
|
||||
'pretty_version' => '3.52',
|
||||
'version' => '3.52.0.0',
|
||||
'reference' => 'a19bf66917de0b29836983db87a455a4f6e32148',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../eftec/bladeone',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'gettext/gettext' => array(
|
||||
'pretty_version' => 'v4.8.7',
|
||||
'version' => '4.8.7.0',
|
||||
'reference' => '3f7bc5ef23302a9059e64934f3d59e454516bec0',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../gettext/gettext',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'gettext/languages' => array(
|
||||
'pretty_version' => '2.10.0',
|
||||
'version' => '2.10.0.0',
|
||||
'reference' => '4d61d67fe83a2ad85959fe6133d6d9ba7dddd1ab',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../gettext/languages',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'grogy/php-parallel-lint' => array(
|
||||
'dev_requirement' => true,
|
||||
'replaced' => array(
|
||||
0 => '*',
|
||||
),
|
||||
),
|
||||
'jakub-onderka/php-parallel-lint' => array(
|
||||
'dev_requirement' => true,
|
||||
'replaced' => array(
|
||||
0 => '*',
|
||||
),
|
||||
),
|
||||
'mck89/peast' => array(
|
||||
'pretty_version' => 'v1.15.0',
|
||||
'version' => '1.15.0.0',
|
||||
'reference' => '733cd8f62dcb8239094688063a92766bbfcbf523',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../mck89/peast',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'mustache/mustache' => array(
|
||||
'pretty_version' => 'v2.14.2',
|
||||
'version' => '2.14.2.0',
|
||||
'reference' => 'e62b7c3849d22ec55f3ec425507bf7968193a6cb',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../mustache/mustache',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'php-parallel-lint/php-parallel-lint' => array(
|
||||
'pretty_version' => 'v1.3.2',
|
||||
'version' => '1.3.2.0',
|
||||
'reference' => '6483c9832e71973ed29cf71bd6b3f4fde438a9de',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../php-parallel-lint/php-parallel-lint',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpcompatibility/php-compatibility' => array(
|
||||
'pretty_version' => '9.3.5',
|
||||
'version' => '9.3.5.0',
|
||||
'reference' => '9fb324479acf6f39452e0655d2429cc0d3914243',
|
||||
'type' => 'phpcodesniffer-standard',
|
||||
'install_path' => __DIR__ . '/../phpcompatibility/php-compatibility',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpcompatibility/phpcompatibility-paragonie' => array(
|
||||
'pretty_version' => '1.3.1',
|
||||
'version' => '1.3.1.0',
|
||||
'reference' => 'ddabec839cc003651f2ce695c938686d1086cf43',
|
||||
'type' => 'phpcodesniffer-standard',
|
||||
'install_path' => __DIR__ . '/../phpcompatibility/phpcompatibility-paragonie',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpcompatibility/phpcompatibility-wp' => array(
|
||||
'pretty_version' => '2.1.4',
|
||||
'version' => '2.1.4.0',
|
||||
'reference' => 'b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5',
|
||||
'type' => 'phpcodesniffer-standard',
|
||||
'install_path' => __DIR__ . '/../phpcompatibility/phpcompatibility-wp',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'rmccue/requests' => array(
|
||||
'pretty_version' => 'v1.8.1',
|
||||
'version' => '1.8.1.0',
|
||||
'reference' => '82e6936366eac3af4d836c18b9d8c31028fe4cd5',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../rmccue/requests',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'squizlabs/php_codesniffer' => array(
|
||||
'pretty_version' => '3.7.1',
|
||||
'version' => '3.7.1.0',
|
||||
'reference' => '1359e176e9307e906dc3d890bcc9603ff6d90619',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../squizlabs/php_codesniffer',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'symfony/finder' => array(
|
||||
'pretty_version' => 'v6.1.3',
|
||||
'version' => '6.1.3.0',
|
||||
'reference' => '39696bff2c2970b3779a5cac7bf9f0b88fc2b709',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/finder',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'wp-cli/i18n-command' => array(
|
||||
'pretty_version' => 'v2.4.0',
|
||||
'version' => '2.4.0.0',
|
||||
'reference' => '45bc2b47a4ed103b871cd2ec5b483ab55ad12d99',
|
||||
'type' => 'wp-cli-package',
|
||||
'install_path' => __DIR__ . '/../wp-cli/i18n-command',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'wp-cli/mustangostang-spyc' => array(
|
||||
'pretty_version' => '0.6.3',
|
||||
'version' => '0.6.3.0',
|
||||
'reference' => '6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../wp-cli/mustangostang-spyc',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'wp-cli/php-cli-tools' => array(
|
||||
'pretty_version' => 'v0.11.15',
|
||||
'version' => '0.11.15.0',
|
||||
'reference' => 'b6edd35988892ea1451392eb7a26d9dbe98c836d',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../wp-cli/php-cli-tools',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'wp-cli/wp-cli' => array(
|
||||
'pretty_version' => 'v2.7.1',
|
||||
'version' => '2.7.1.0',
|
||||
'reference' => '1ddc754f1c15e56fb2cdd1a4e82bd0ec6ca32a76',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../wp-cli/wp-cli',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'wp-coding-standards/wpcs' => array(
|
||||
'pretty_version' => '2.3.0',
|
||||
'version' => '2.3.0.0',
|
||||
'reference' => '7da1894633f168fe244afc6de00d141f27517b62',
|
||||
'type' => 'phpcodesniffer-standard',
|
||||
'install_path' => __DIR__ . '/../wp-coding-standards/wpcs',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'wptrt/wpthemereview' => array(
|
||||
'pretty_version' => '0.2.1',
|
||||
'version' => '0.2.1.0',
|
||||
'reference' => '462e59020dad9399ed2fe8e61f2a21b5e206e420',
|
||||
'type' => 'phpcodesniffer-standard',
|
||||
'install_path' => __DIR__ . '/../wptrt/wpthemereview',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
),
|
||||
);
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
// platform_check.php @generated by Composer
|
||||
|
||||
$issues = array();
|
||||
|
||||
if (!(PHP_VERSION_ID >= 50600)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.0". You are running ' . PHP_VERSION . '.';
|
||||
}
|
||||
|
||||
if ($issues) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
||||
} elseif (!headers_sent()) {
|
||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
34
vendor/dealerdirect/phpcodesniffer-composer-installer/.github_changelog_generator
vendored
Normal file
34
vendor/dealerdirect/phpcodesniffer-composer-installer/.github_changelog_generator
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
add-issues-wo-labels=true
|
||||
add-pr-wo-labels=true
|
||||
author=true
|
||||
breaking-labels=backwards-incompatible,Backwards incompatible,breaking
|
||||
breaking-prefix=### Breaking changes
|
||||
bug-labels=bug - confirmed
|
||||
bug-prefix=### Fixes
|
||||
compare-link=true
|
||||
date-format=%Y-%m-%d
|
||||
deprecated-labels=deprecated,Deprecated,Type: Deprecated
|
||||
deprecated-prefix=### Deprecates
|
||||
enhancement-labels=improvement,documentation,builds / deploys / releases,feature request
|
||||
enhancement-prefix=### Changes
|
||||
exclude-labels=bug - unconfirmed,can't reproduce / won't fix,invalid,triage
|
||||
filter-issues-by-milestone=true
|
||||
header=
|
||||
http-cache=true
|
||||
issues=true
|
||||
issue-prefix=### Closes
|
||||
merge-prefix=### Pull request(s) without label
|
||||
output=
|
||||
project=phpcodesniffer-composer-installer
|
||||
pulls=true
|
||||
removed-labels=removed,Removed,Type: Removed
|
||||
removed-prefix=### Removes
|
||||
security-labels=security,Security,Type: Security
|
||||
security-prefix=### Security
|
||||
summary-labels=Release summary,release-summary,Summary,summary
|
||||
unreleased=true
|
||||
unreleased-label=Unreleased
|
||||
unreleased-only=true
|
||||
user=Dealerdirect
|
||||
usernames-as-github-logins=true
|
||||
verbose=false
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"plugins": [
|
||||
"remark-preset-lint-recommended",
|
||||
["remark-lint-list-item-indent", "space"]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
line-length:
|
||||
level: warning
|
||||
max: 120
|
||||
truthy: {allowed-values: ["true", "false", "on"]}
|
|
@ -0,0 +1,129 @@
|
|||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
<potherca@gmail.com>.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016-2022 Dealerdirect B.V.
|
||||
|
||||
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.
|
|
@ -0,0 +1,285 @@
|
|||
# PHP_CodeSniffer Standards Composer Installer Plugin
|
||||
|
||||
![Project Stage][project-stage-shield]
|
||||
![Last Commit][last-updated-shield]
|
||||
![Awesome][awesome-shield]
|
||||
[![License][license-shield]](LICENSE.md)
|
||||
|
||||
[![Tests][ghactionstest-shield]][ghactions]
|
||||
[![Scrutinizer][scrutinizer-shield]][scrutinizer]
|
||||
[![Latest Version on Packagist][packagist-version-shield]][packagist-version]
|
||||
[![Packagist][packagist-shield]][packagist]
|
||||
|
||||
[![Contributor Covenant][code-of-conduct-shield]][code-of-conduct]
|
||||
|
||||
This composer installer plugin allows for easy installation of [PHP_CodeSniffer][codesniffer] coding standards (rulesets).
|
||||
|
||||
No more symbolic linking of directories, checking out repositories on specific locations or changing
|
||||
the `phpcs` configuration.
|
||||
|
||||
## Usage
|
||||
|
||||
Installation can be done with [Composer][composer], by requiring this package as a development dependency:
|
||||
|
||||
```bash
|
||||
composer require --dev dealerdirect/phpcodesniffer-composer-installer
|
||||
```
|
||||
|
||||
When using Composer 2.2 or higher, Composer will [ask for your permission](https://blog.packagist.com/composer-2-2/#more-secure-plugin-execution) to allow this plugin to execute code. For this plugin to be functional, permission needs to be granted.
|
||||
|
||||
When permission has been granted, the following snippet will automatically be added to your `composer.json` file by Composer:
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When using Composer < 2.2, you can add the permission flag ahead of the upgrade to Composer 2.2, by running:
|
||||
```bash
|
||||
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
|
||||
```
|
||||
|
||||
That's it.
|
||||
|
||||
### Compatibility
|
||||
|
||||
This plugin is compatible with:
|
||||
|
||||
- PHP **5.x**, **7.x**, and **8.x** (Support for PHP v8 is available since [`v0.7.0`][v0.7])
|
||||
- [Composer][composer] **1.x** and **2.x** (Support for Composer v2 is available since [`v0.7.0`][v0.7])
|
||||
- [PHP_CodeSniffer][codesniffer] **2.x** and **3.x** (Support for PHP_CodeSniffer v3 is available since [`v0.4.0`][v0.4])
|
||||
|
||||
|
||||
> **ℹ️ Please Note:** [Composer treats _minor_ releases below 1.0.0 as _major_ releases][composer-manual-caret]. So version `0.7.x` (or higher) of this plugin must be _explicitly_ set as version constraint when using Composer 2.x or PHP 8.0. In other words: using `^0.6` will **not** work with Composer 2.x or PHP 8.0.
|
||||
|
||||
### How it works
|
||||
|
||||
Basically, this plugin executes the following steps:
|
||||
|
||||
- This plugin searches for [`phpcodesniffer-standard` packages][] in all of your currently installed Composer packages.
|
||||
- Matching packages and the project itself are scanned for PHP_CodeSniffer rulesets.
|
||||
- The plugin will call PHP_CodeSniffer and configure the `installed_paths` option.
|
||||
|
||||
### Example project
|
||||
|
||||
The following is an example Composer project and has included
|
||||
multiple `phpcodesniffer-standard` packages.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "dealerdirect/example-project",
|
||||
"description": "Just an example project",
|
||||
"type": "project",
|
||||
"require": {},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "*",
|
||||
"object-calisthenics/phpcs-calisthenics-rules": "*",
|
||||
"phpcompatibility/php-compatibility": "*",
|
||||
"wp-coding-standards/wpcs": "*"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After running `composer install` PHP_CodeSniffer just works:
|
||||
|
||||
```bash
|
||||
$ ./vendor/bin/phpcs -i
|
||||
The installed coding standards are MySource, PEAR, PSR1, PSR2, PSR12, Squiz, Zend, ObjectCalisthenics,
|
||||
PHPCompatibility, WordPress, WordPress-Core, WordPress-Docs and WordPress-Extra
|
||||
```
|
||||
|
||||
### Calling the plugin directly
|
||||
|
||||
In some circumstances, it is desirable to call this plugin's functionality
|
||||
directly. For instance, during development or in [CI][definition-ci] environments.
|
||||
|
||||
As the plugin requires Composer to work, direct calls need to be wired through a
|
||||
project's `composer.json`.
|
||||
|
||||
This is done by adding a call to the `Plugin::run` function in the `script`
|
||||
section of the `composer.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"install-codestandards": [
|
||||
"Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The command can then be called using `composer run-script install-codestandards` or
|
||||
referenced from other script configurations, as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"install-codestandards": [
|
||||
"Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run"
|
||||
],
|
||||
"post-install-cmd": [
|
||||
"@install-codestandards"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For more details about Composer scripts, please refer to [the section on scripts
|
||||
in the Composer manual][composer-manual-scripts].
|
||||
|
||||
### Changing the Coding Standards search depth
|
||||
|
||||
By default, this plugin searches up for Coding Standards up to three directories
|
||||
deep. In most cases, this should be sufficient. However, this plugin allows
|
||||
you to customize the search depth setting if needed.
|
||||
|
||||
```json
|
||||
{
|
||||
"extra": {
|
||||
"phpcodesniffer-search-depth": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Caveats
|
||||
|
||||
When this plugin is installed globally, composer will load the _global_ plugin rather
|
||||
than the one from the local repository. Despite [this behavior being documented
|
||||
in the composer manual][using-composer-plugins], it could potentially confuse
|
||||
as another version of the plugin could be run and not the one specified by the project.
|
||||
|
||||
## Developing Coding Standards
|
||||
|
||||
Coding standard can be developed normally, as documented by [PHP_CodeSniffer][codesniffer], in the [Coding Standard Tutorial][tutorial].
|
||||
|
||||
Create a composer package of your coding standard by adding a `composer.json` file.
|
||||
|
||||
```json
|
||||
{
|
||||
"name" : "acme/phpcodesniffer-our-standards",
|
||||
"description" : "Package contains all coding standards of the Acme company",
|
||||
"require" : {
|
||||
"php" : ">=5.4.0",
|
||||
"squizlabs/php_codesniffer" : "^3.6"
|
||||
},
|
||||
"type" : "phpcodesniffer-standard"
|
||||
}
|
||||
```
|
||||
|
||||
Requirements:
|
||||
* The repository may contain one or more standards.
|
||||
* Each standard can have a separate directory no deeper than 3 levels from the repository root.
|
||||
* The package `type` must be `phpcodesniffer-standard`. Without this, the plugin will not trigger.
|
||||
|
||||
### Requiring the plugin from within your coding standard
|
||||
|
||||
If your coding standard itself depends on additional external PHPCS standards, this plugin can
|
||||
make life easier on your end-users by taking care of the installation of all standards - yours
|
||||
and your dependencies - for them.
|
||||
|
||||
This can help reduce the number of support questions about setting the `installed_paths`, as well
|
||||
as simplify your standard's installation instructions.
|
||||
|
||||
For this to work, make sure your external standard adds this plugin to the `composer.json` config
|
||||
via `require`, **not** `require-dev`.
|
||||
|
||||
> :warning: Your end-user may already `require-dev` this plugin and/or other external standards used
|
||||
> by your end-users may require this plugin as well.
|
||||
>
|
||||
> To prevent your end-users getting into "_dependency hell_", make sure to make the version requirement
|
||||
> for this plugin flexible.
|
||||
>
|
||||
> As, for now, this plugin is still regarded as "unstable" (version < 1.0), remember that Composer
|
||||
> treats unstable minors as majors and will not be able to resolve one config requiring this plugin
|
||||
> at version `^0.5`, while another requires it at version `^0.6`.
|
||||
> Either allow multiple minors or use `*` as the version requirement.
|
||||
>
|
||||
> Some examples of flexible requirements which can be used:
|
||||
> ```bash
|
||||
> composer require dealerdirect/phpcodesniffer-composer-installer:"*"
|
||||
> composer require dealerdirect/phpcodesniffer-composer-installer:"0.*"
|
||||
> composer require dealerdirect/phpcodesniffer-composer-installer:"^0.4.1 || ^0.5 || ^0.6 || ^0.7"
|
||||
> ```
|
||||
|
||||
## Changelog
|
||||
|
||||
This repository does not contain a `CHANGELOG.md` file, however, we do publish a changelog on each release
|
||||
using the [GitHub releases][changelog] functionality.
|
||||
|
||||
## Contributing
|
||||
|
||||
This is an active open-source project. We are always open to people who want to
|
||||
use the code or contribute to it.
|
||||
|
||||
We've set up a separate document for our [contribution guidelines][contributing-guidelines].
|
||||
|
||||
Thank you for being involved! :heart_eyes:
|
||||
|
||||
## Authors & contributors
|
||||
|
||||
The original idea and setup of this repository is by [Franck Nijhof][frenck], employee @ Dealerdirect.
|
||||
|
||||
For a full list of all author and/or contributors, check [the contributors page][contributors].
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2022 Dealerdirect B.V.
|
||||
|
||||
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.
|
||||
|
||||
[awesome-shield]: https://img.shields.io/badge/awesome%3F-yes-brightgreen.svg
|
||||
[changelog]: https://github.com/Dealerdirect/phpcodesniffer-composer-installer/releases
|
||||
[code-of-conduct-shield]: https://img.shields.io/badge/Contributor%20Covenant-v2.0-ff69b4.svg
|
||||
[code-of-conduct]: CODE_OF_CONDUCT.md
|
||||
[codesniffer]: https://github.com/squizlabs/PHP_CodeSniffer
|
||||
[composer-manual-scripts]: https://getcomposer.org/doc/articles/scripts.md
|
||||
[composer-manual-caret]: https://getcomposer.org/doc/articles/versions.md#caret-version-range-
|
||||
[composer]: https://getcomposer.org/
|
||||
[contributing-guidelines]: CONTRIBUTING.md
|
||||
[contributors]: https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors
|
||||
[definition-ci]: https://en.wikipedia.org/wiki/Continuous_integration
|
||||
[frenck]: https://github.com/frenck
|
||||
[last-updated-shield]: https://img.shields.io/github/last-commit/Dealerdirect/phpcodesniffer-composer-installer.svg
|
||||
[license-shield]: https://img.shields.io/github/license/dealerdirect/phpcodesniffer-composer-installer.svg
|
||||
[packagist-shield]: https://img.shields.io/packagist/dt/dealerdirect/phpcodesniffer-composer-installer.svg
|
||||
[packagist-version-shield]: https://img.shields.io/packagist/v/dealerdirect/phpcodesniffer-composer-installer.svg
|
||||
[packagist-version]: https://packagist.org/packages/dealerdirect/phpcodesniffer-composer-installer
|
||||
[packagist]: https://packagist.org/packages/dealerdirect/phpcodesniffer-composer-installer
|
||||
[`phpcodesniffer-standard` packages]: https://packagist.org/explore/?type=phpcodesniffer-standard
|
||||
[project-stage-shield]: https://img.shields.io/badge/Project%20Stage-Development-yellowgreen.svg
|
||||
[scrutinizer-shield]: https://img.shields.io/scrutinizer/g/dealerdirect/phpcodesniffer-composer-installer.svg
|
||||
[scrutinizer]: https://scrutinizer-ci.com/g/dealerdirect/phpcodesniffer-composer-installer/
|
||||
[ghactionstest-shield]: https://github.com/Dealerdirect/phpcodesniffer-composer-installer/actions/workflows/integrationtest.yml/badge.svg
|
||||
[ghactions]: https://github.com/Dealerdirect/phpcodesniffer-composer-installer/actions/workflows/integrationtest.yml
|
||||
[tutorial]: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Coding-Standard-Tutorial
|
||||
[using-composer-plugins]: https://getcomposer.org/doc/articles/plugins.md#using-plugins
|
||||
[v0.4]: https://github.com/Dealerdirect/phpcodesniffer-composer-installer/releases/tag/v0.4.0
|
||||
[v0.7]: https://github.com/Dealerdirect/phpcodesniffer-composer-installer/releases/tag/v0.7.0
|
49
vendor/dealerdirect/phpcodesniffer-composer-installer/bin/generate-changelog.sh
vendored
Normal file
49
vendor/dealerdirect/phpcodesniffer-composer-installer/bin/generate-changelog.sh
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit -o errtrace -o nounset -o pipefail
|
||||
|
||||
: ${GITHUB_CHANGELOG_GENERATOR:=github_changelog_generator}
|
||||
: ${GEM:=gem}
|
||||
|
||||
generate_changelog() {
|
||||
local -r sVersion="${1?One parameter required: <release-to-generate>}"
|
||||
|
||||
if ! command -v "${GITHUB_CHANGELOG_GENERATOR}" >/dev/null 2>&1;then
|
||||
echo "This script requires the '${GITHUB_CHANGELOG_GENERATOR}' Ruby Gem"
|
||||
|
||||
if ! command -v "${GEM}" >/dev/null 2>&1;then
|
||||
echo "Could not find the '${GEM}' command needed to install 'github_changelog_generator'!" >&2
|
||||
echo 'Aborting.'
|
||||
exit 67
|
||||
else
|
||||
echo "Installing '${GITHUB_CHANGELOG_GENERATOR}'..."
|
||||
gem install github_changelog_generator
|
||||
fi
|
||||
fi
|
||||
|
||||
local -r sChangelog="$(
|
||||
"${GITHUB_CHANGELOG_GENERATOR}" \
|
||||
--user Dealerdirect \
|
||||
--project phpcodesniffer-composer-installer \
|
||||
--token "$(cat ~/.github-token)" \
|
||||
--future-release "${sVersion}" \
|
||||
--enhancement-label '### Changes' \
|
||||
--bugs-label '### Fixes' \
|
||||
--issues-label '### Closes' \
|
||||
--usernames-as-github-logins \
|
||||
--bug-labels 'bug - confirmed' \
|
||||
--enhancement-labels 'improvement','documentation','builds / deploys / releases','feature request' \
|
||||
--exclude-labels 'bug - unconfirmed',"can't reproduce / won't fix",'invalid','triage' \
|
||||
--unreleased-only \
|
||||
--output '' 2>/dev/null
|
||||
)" || echo "There was a problem running '${GITHUB_CHANGELOG_GENERATOR}'"
|
||||
|
||||
echo "${sChangelog}" | sed -E 's/\[\\(#[0-9]+)\]\([^)]+\)/\1/' | head -n -3
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
|
||||
export -f generate_changelog
|
||||
else
|
||||
generate_changelog "${@}"
|
||||
exit $?
|
||||
fi
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"name": "dealerdirect/phpcodesniffer-composer-installer",
|
||||
"description": "PHP_CodeSniffer Standards Composer Installer Plugin",
|
||||
"type": "composer-plugin",
|
||||
"keywords": [
|
||||
"composer", "installer", "plugin",
|
||||
"phpcs", "phpcbf", "codesniffer", "phpcodesniffer", "php_codesniffer",
|
||||
"standard", "standards", "style guide", "stylecheck",
|
||||
"qa", "quality", "code quality", "tests"
|
||||
],
|
||||
"homepage": "http://www.dealerdirect.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Franck Nijhof",
|
||||
"email": "franck.nijhof@dealerdirect.com",
|
||||
"homepage": "http://www.frenck.nl",
|
||||
"role": "Developer / IT Manager"
|
||||
},
|
||||
{
|
||||
"name" : "Contributors",
|
||||
"homepage" : "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues",
|
||||
"source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"composer-plugin-api": "^1.0 || ^2.0",
|
||||
"squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "*",
|
||||
"phpcompatibility/php-compatibility": "^9.0",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.3.1"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
|
||||
},
|
||||
"scripts": {
|
||||
"install-codestandards": [
|
||||
"Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run"
|
||||
],
|
||||
"lint": [
|
||||
"@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,621 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Dealerdirect PHP_CodeSniffer Standards
|
||||
* Composer Installer Plugin package.
|
||||
*
|
||||
* @copyright 2016-2022 Dealerdirect B.V.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Dealerdirect\Composer\Plugin\Installers\PHPCodeSniffer;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\EventDispatcher\EventSubscriberInterface;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Package\RootPackageInterface;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Composer\Script\Event;
|
||||
use Composer\Script\ScriptEvents;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Process\Exception\LogicException;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
use Symfony\Component\Process\Exception\RuntimeException;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
|
||||
/**
|
||||
* PHP_CodeSniffer standard installation manager.
|
||||
*
|
||||
* @author Franck Nijhof <franck.nijhof@dealerdirect.com>
|
||||
*/
|
||||
class Plugin implements PluginInterface, EventSubscriberInterface
|
||||
{
|
||||
const KEY_MAX_DEPTH = 'phpcodesniffer-search-depth';
|
||||
|
||||
const MESSAGE_ERROR_WRONG_MAX_DEPTH =
|
||||
'The value of "%s" (in the composer.json "extra".section) must be an integer larger then %d, %s given.';
|
||||
|
||||
const MESSAGE_NOT_INSTALLED = 'PHPCodeSniffer is not installed';
|
||||
const MESSAGE_NOTHING_TO_INSTALL = 'Nothing to install or update';
|
||||
const MESSAGE_PLUGIN_UNINSTALLED = 'PHPCodeSniffer Composer Installer is uninstalled';
|
||||
const MESSAGE_RUNNING_INSTALLER = 'Running PHPCodeSniffer Composer Installer';
|
||||
|
||||
const PACKAGE_NAME = 'squizlabs/php_codesniffer';
|
||||
const PACKAGE_TYPE = 'phpcodesniffer-standard';
|
||||
|
||||
const PHPCS_CONFIG_REGEX = '`%s:[^\r\n]+`';
|
||||
const PHPCS_CONFIG_KEY = 'installed_paths';
|
||||
|
||||
const PLUGIN_NAME = 'dealerdirect/phpcodesniffer-composer-installer';
|
||||
|
||||
/**
|
||||
* @var Composer
|
||||
*/
|
||||
private $composer;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $cwd;
|
||||
|
||||
/**
|
||||
* @var Filesystem
|
||||
*/
|
||||
private $filesystem;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $installedPaths;
|
||||
|
||||
/**
|
||||
* @var IOInterface
|
||||
*/
|
||||
private $io;
|
||||
|
||||
/**
|
||||
* @var ProcessExecutor
|
||||
*/
|
||||
private $processExecutor;
|
||||
|
||||
/**
|
||||
* Triggers the plugin's main functionality.
|
||||
*
|
||||
* Makes it possible to run the plugin as a custom command.
|
||||
*
|
||||
* @param Event $event
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
* @throws LogicException
|
||||
* @throws ProcessFailedException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function run(Event $event)
|
||||
{
|
||||
$io = $event->getIO();
|
||||
$composer = $event->getComposer();
|
||||
|
||||
$instance = new static();
|
||||
|
||||
$instance->io = $io;
|
||||
$instance->composer = $composer;
|
||||
$instance->init();
|
||||
$instance->onDependenciesChangedEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
* @throws LogicException
|
||||
* @throws ProcessFailedException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the plugin so it's main functionality can be run.
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
* @throws LogicException
|
||||
* @throws ProcessFailedException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function init()
|
||||
{
|
||||
$this->cwd = getcwd();
|
||||
$this->installedPaths = array();
|
||||
|
||||
$this->processExecutor = new ProcessExecutor($this->io);
|
||||
$this->filesystem = new Filesystem($this->processExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return array(
|
||||
ScriptEvents::POST_INSTALL_CMD => array(
|
||||
array('onDependenciesChangedEvent', 0),
|
||||
),
|
||||
ScriptEvents::POST_UPDATE_CMD => array(
|
||||
array('onDependenciesChangedEvent', 0),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for post install and post update events.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws LogicException
|
||||
* @throws ProcessFailedException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function onDependenciesChangedEvent()
|
||||
{
|
||||
$io = $this->io;
|
||||
$isVerbose = $io->isVerbose();
|
||||
$exitCode = 0;
|
||||
|
||||
if ($isVerbose) {
|
||||
$io->write(sprintf('<info>%s</info>', self::MESSAGE_RUNNING_INSTALLER));
|
||||
}
|
||||
|
||||
if ($this->isPHPCodeSnifferInstalled() === true) {
|
||||
$this->loadInstalledPaths();
|
||||
$installPathCleaned = $this->cleanInstalledPaths();
|
||||
$installPathUpdated = $this->updateInstalledPaths();
|
||||
|
||||
if ($installPathCleaned === true || $installPathUpdated === true) {
|
||||
$exitCode = $this->saveInstalledPaths();
|
||||
} elseif ($isVerbose) {
|
||||
$io->write(sprintf('<info>%s</info>', self::MESSAGE_NOTHING_TO_INSTALL));
|
||||
}
|
||||
} else {
|
||||
$pluginPackage = $this
|
||||
->composer
|
||||
->getRepositoryManager()
|
||||
->getLocalRepository()
|
||||
->findPackages(self::PLUGIN_NAME)
|
||||
;
|
||||
|
||||
$isPluginUninstalled = count($pluginPackage) === 0;
|
||||
|
||||
if ($isPluginUninstalled) {
|
||||
if ($isVerbose) {
|
||||
$io->write(sprintf('<info>%s</info>', self::MESSAGE_PLUGIN_UNINSTALLED));
|
||||
}
|
||||
} else {
|
||||
$exitCode = 1;
|
||||
if ($isVerbose) {
|
||||
$io->write(sprintf('<error>%s</error>', self::MESSAGE_NOT_INSTALLED));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all paths from PHP_CodeSniffer into an array.
|
||||
*
|
||||
* @throws LogicException
|
||||
* @throws ProcessFailedException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function loadInstalledPaths()
|
||||
{
|
||||
if ($this->isPHPCodeSnifferInstalled() === true) {
|
||||
$this->processExecutor->execute(
|
||||
'phpcs --config-show',
|
||||
$output,
|
||||
$this->composer->getConfig()->get('bin-dir')
|
||||
);
|
||||
|
||||
$regex = sprintf(self::PHPCS_CONFIG_REGEX, self::PHPCS_CONFIG_KEY);
|
||||
if (preg_match($regex, $output, $match) === 1) {
|
||||
$phpcsInstalledPaths = str_replace(self::PHPCS_CONFIG_KEY . ': ', '', $match[0]);
|
||||
$phpcsInstalledPaths = trim($phpcsInstalledPaths);
|
||||
|
||||
if ($phpcsInstalledPaths !== '') {
|
||||
$this->installedPaths = explode(',', $phpcsInstalledPaths);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all coding standard paths back into PHP_CodeSniffer
|
||||
*
|
||||
* @throws LogicException
|
||||
* @throws ProcessFailedException
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return int Exit code. 0 for success, 1 or higher for failure.
|
||||
*/
|
||||
private function saveInstalledPaths()
|
||||
{
|
||||
// Check if we found installed paths to set.
|
||||
if (count($this->installedPaths) !== 0) {
|
||||
sort($this->installedPaths);
|
||||
$paths = implode(',', $this->installedPaths);
|
||||
$arguments = array('--config-set', self::PHPCS_CONFIG_KEY, $paths);
|
||||
$configMessage = sprintf(
|
||||
'PHP CodeSniffer Config <info>%s</info> <comment>set to</comment> <info>%s</info>',
|
||||
self::PHPCS_CONFIG_KEY,
|
||||
$paths
|
||||
);
|
||||
} else {
|
||||
// Delete the installed paths if none were found.
|
||||
$arguments = array('--config-delete', self::PHPCS_CONFIG_KEY);
|
||||
$configMessage = sprintf(
|
||||
'PHP CodeSniffer Config <info>%s</info> <comment>delete</comment>',
|
||||
self::PHPCS_CONFIG_KEY
|
||||
);
|
||||
}
|
||||
|
||||
// Prepare message in case of failure
|
||||
$failMessage = sprintf(
|
||||
'Failed to set PHP CodeSniffer <info>%s</info> Config',
|
||||
self::PHPCS_CONFIG_KEY
|
||||
);
|
||||
|
||||
// Determine the path to the main PHPCS file.
|
||||
$phpcsPath = $this->getPHPCodeSnifferInstallPath();
|
||||
if (file_exists($phpcsPath . '/bin/phpcs') === true) {
|
||||
// PHPCS 3.x.
|
||||
$phpcsExecutable = './bin/phpcs';
|
||||
} else {
|
||||
// PHPCS 2.x.
|
||||
$phpcsExecutable = './scripts/phpcs';
|
||||
}
|
||||
|
||||
// Okay, lets rock!
|
||||
$command = vsprintf(
|
||||
'%s %s %s',
|
||||
array(
|
||||
'php executable' => $this->getPhpExecCommand(),
|
||||
'phpcs executable' => $phpcsExecutable,
|
||||
'arguments' => implode(' ', $arguments),
|
||||
)
|
||||
);
|
||||
|
||||
$exitCode = $this->processExecutor->execute($command, $configResult, $phpcsPath);
|
||||
if ($exitCode === 0) {
|
||||
$exitCode = $this->verifySaveSuccess();
|
||||
}
|
||||
|
||||
if ($exitCode === 0) {
|
||||
$this->io->write($configMessage);
|
||||
} else {
|
||||
$this->io->write($failMessage);
|
||||
}
|
||||
|
||||
if ($this->io->isVerbose() && !empty($configResult)) {
|
||||
$this->io->write(sprintf('<info>%s</info>', $configResult));
|
||||
}
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the paths which were expected to be saved, have been.
|
||||
*
|
||||
* @return int Exit code. 0 for success, 1 for failure.
|
||||
*/
|
||||
private function verifySaveSuccess()
|
||||
{
|
||||
$exitCode = 1;
|
||||
$expectedPaths = $this->installedPaths;
|
||||
|
||||
// Request the currently set installed paths after the save.
|
||||
$this->loadInstalledPaths();
|
||||
|
||||
$registeredPaths = array_intersect($this->installedPaths, $expectedPaths);
|
||||
$registeredCount = count($registeredPaths);
|
||||
$expectedCount = count($expectedPaths);
|
||||
|
||||
if ($expectedCount === $registeredCount) {
|
||||
$exitCode = 0;
|
||||
}
|
||||
|
||||
if ($exitCode === 1 && $this->io->isVerbose()) {
|
||||
$verificationMessage = sprintf(
|
||||
"Paths to external standards found by the plugin: <info>%s</info>\n"
|
||||
. 'Actual paths registered with PHPCS: <info>%s</info>',
|
||||
implode(', ', $expectedPaths),
|
||||
implode(', ', $this->installedPaths)
|
||||
);
|
||||
$this->io->write($verificationMessage);
|
||||
}
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the current PHP version being used.
|
||||
*
|
||||
* Duplicate of the same in the EventDispatcher class in Composer itself.
|
||||
*/
|
||||
protected function getPhpExecCommand()
|
||||
{
|
||||
$finder = new PhpExecutableFinder();
|
||||
|
||||
$phpPath = $finder->find(false);
|
||||
|
||||
if ($phpPath === false) {
|
||||
throw new \RuntimeException('Failed to locate PHP binary to execute ' . $phpPath);
|
||||
}
|
||||
|
||||
$phpArgs = $finder->findArguments();
|
||||
$phpArgs = $phpArgs
|
||||
? ' ' . implode(' ', $phpArgs)
|
||||
: ''
|
||||
;
|
||||
|
||||
$command = ProcessExecutor::escape($phpPath) .
|
||||
$phpArgs .
|
||||
' -d allow_url_fopen=' . ProcessExecutor::escape(ini_get('allow_url_fopen')) .
|
||||
' -d disable_functions=' . ProcessExecutor::escape(ini_get('disable_functions')) .
|
||||
' -d memory_limit=' . ProcessExecutor::escape(ini_get('memory_limit'))
|
||||
;
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate trough all known paths and check if they are still valid.
|
||||
*
|
||||
* If path does not exists, is not an directory or isn't readable, the path
|
||||
* is removed from the list.
|
||||
*
|
||||
* @return bool True if changes where made, false otherwise
|
||||
*/
|
||||
private function cleanInstalledPaths()
|
||||
{
|
||||
$changes = false;
|
||||
foreach ($this->installedPaths as $key => $path) {
|
||||
// This might be a relative path as well
|
||||
$alternativePath = realpath($this->getPHPCodeSnifferInstallPath() . \DIRECTORY_SEPARATOR . $path);
|
||||
|
||||
if (
|
||||
(is_dir($path) === false || is_readable($path) === false) &&
|
||||
(
|
||||
$alternativePath === false ||
|
||||
is_dir($alternativePath) === false ||
|
||||
is_readable($alternativePath) === false
|
||||
)
|
||||
) {
|
||||
unset($this->installedPaths[$key]);
|
||||
$changes = true;
|
||||
}
|
||||
}
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all installed packages (including the root package) against
|
||||
* the installed paths from PHP_CodeSniffer and add the missing ones.
|
||||
*
|
||||
* @return bool True if changes where made, false otherwise
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function updateInstalledPaths()
|
||||
{
|
||||
$changes = false;
|
||||
|
||||
$searchPaths = array($this->cwd);
|
||||
$codingStandardPackages = $this->getPHPCodingStandardPackages();
|
||||
foreach ($codingStandardPackages as $package) {
|
||||
$installPath = $this->composer->getInstallationManager()->getInstallPath($package);
|
||||
if ($this->filesystem->isAbsolutePath($installPath) === false) {
|
||||
$installPath = $this->filesystem->normalizePath(
|
||||
$this->cwd . \DIRECTORY_SEPARATOR . $installPath
|
||||
);
|
||||
}
|
||||
$searchPaths[] = $installPath;
|
||||
}
|
||||
|
||||
$finder = new Finder();
|
||||
$finder->files()
|
||||
->depth('<= ' . $this->getMaxDepth())
|
||||
->depth('>= ' . $this->getMinDepth())
|
||||
->ignoreUnreadableDirs()
|
||||
->ignoreVCS(true)
|
||||
->in($searchPaths)
|
||||
->name('ruleset.xml');
|
||||
|
||||
// Process each found possible ruleset.
|
||||
foreach ($finder as $ruleset) {
|
||||
$standardsPath = $ruleset->getPath();
|
||||
|
||||
// Pick the directory above the directory containing the standard, unless this is the project root.
|
||||
if ($standardsPath !== $this->cwd) {
|
||||
$standardsPath = dirname($standardsPath);
|
||||
}
|
||||
|
||||
// Use relative paths for local project repositories.
|
||||
if ($this->isRunningGlobally() === false) {
|
||||
$standardsPath = $this->filesystem->findShortestPath(
|
||||
$this->getPHPCodeSnifferInstallPath(),
|
||||
$standardsPath,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// De-duplicate and add when directory is not configured.
|
||||
if (in_array($standardsPath, $this->installedPaths, true) === false) {
|
||||
$this->installedPaths[] = $standardsPath;
|
||||
$changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through Composers' local repository looking for valid Coding
|
||||
* Standard packages.
|
||||
*
|
||||
* If the package is the RootPackage (the one the plugin is installed into),
|
||||
* the package is ignored for now since it needs a different install path logic.
|
||||
*
|
||||
* @return array Composer packages containing coding standard(s)
|
||||
*/
|
||||
private function getPHPCodingStandardPackages()
|
||||
{
|
||||
$codingStandardPackages = array_filter(
|
||||
$this->composer->getRepositoryManager()->getLocalRepository()->getPackages(),
|
||||
function (PackageInterface $package) {
|
||||
if ($package instanceof AliasPackage) {
|
||||
return false;
|
||||
}
|
||||
return $package->getType() === Plugin::PACKAGE_TYPE;
|
||||
}
|
||||
);
|
||||
|
||||
if (
|
||||
! $this->composer->getPackage() instanceof RootPackageInterface
|
||||
&& $this->composer->getPackage()->getType() === self::PACKAGE_TYPE
|
||||
) {
|
||||
$codingStandardPackages[] = $this->composer->getPackage();
|
||||
}
|
||||
|
||||
return $codingStandardPackages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for the installed PHP_CodeSniffer Composer package
|
||||
*
|
||||
* @param null|string|\Composer\Semver\Constraint\ConstraintInterface $versionConstraint to match against
|
||||
*
|
||||
* @return PackageInterface|null
|
||||
*/
|
||||
private function getPHPCodeSnifferPackage($versionConstraint = null)
|
||||
{
|
||||
$packages = $this
|
||||
->composer
|
||||
->getRepositoryManager()
|
||||
->getLocalRepository()
|
||||
->findPackages(self::PACKAGE_NAME, $versionConstraint);
|
||||
|
||||
return array_shift($packages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the PHP_CodeSniffer package installation location
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getPHPCodeSnifferInstallPath()
|
||||
{
|
||||
return $this->composer->getInstallationManager()->getInstallPath($this->getPHPCodeSnifferPackage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple check if PHP_CodeSniffer is installed.
|
||||
*
|
||||
* @param null|string|\Composer\Semver\Constraint\ConstraintInterface $versionConstraint to match against
|
||||
*
|
||||
* @return bool Whether PHP_CodeSniffer is installed
|
||||
*/
|
||||
private function isPHPCodeSnifferInstalled($versionConstraint = null)
|
||||
{
|
||||
return ($this->getPHPCodeSnifferPackage($versionConstraint) !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if composer is running "global"
|
||||
* This check kinda dirty, but it is the "Composer Way"
|
||||
*
|
||||
* @return bool Whether Composer is running "globally"
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function isRunningGlobally()
|
||||
{
|
||||
return ($this->composer->getConfig()->get('home') === $this->cwd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the maximum search depth when searching for Coding Standards.
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function getMaxDepth()
|
||||
{
|
||||
$maxDepth = 3;
|
||||
|
||||
$extra = $this->composer->getPackage()->getExtra();
|
||||
|
||||
if (array_key_exists(self::KEY_MAX_DEPTH, $extra)) {
|
||||
$maxDepth = $extra[self::KEY_MAX_DEPTH];
|
||||
$minDepth = $this->getMinDepth();
|
||||
|
||||
if (
|
||||
(string) (int) $maxDepth !== (string) $maxDepth /* Must be an integer or cleanly castable to one */
|
||||
|| $maxDepth <= $minDepth /* Larger than the minimum */
|
||||
|| is_float($maxDepth) === true /* Within the boundaries of integer */
|
||||
) {
|
||||
$message = vsprintf(
|
||||
self::MESSAGE_ERROR_WRONG_MAX_DEPTH,
|
||||
array(
|
||||
'key' => self::KEY_MAX_DEPTH,
|
||||
'min' => $minDepth,
|
||||
'given' => var_export($maxDepth, true),
|
||||
)
|
||||
);
|
||||
|
||||
throw new \InvalidArgumentException($message);
|
||||
}
|
||||
}
|
||||
|
||||
return (int) $maxDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimal search depth for Coding Standard packages.
|
||||
*
|
||||
* Usually this is 0, unless PHP_CodeSniffer >= 3 is used.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function getMinDepth()
|
||||
{
|
||||
if ($this->isPHPCodeSnifferInstalled('>= 3.0.0') !== true) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->in(__DIR__ . '/lib');
|
||||
|
||||
return PhpCsFixer\Config::create()
|
||||
->setRiskyAllowed(true)
|
||||
->setRules([
|
||||
'@PSR2' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
])
|
||||
->setFinder($finder);
|
|
@ -0,0 +1,54 @@
|
|||
# BladeOneCache extension library (optional)
|
||||
|
||||
Requires: BladeOne
|
||||
|
||||
This library adds cache to the visual layer and business/logic layer.
|
||||
For using this library, the code requires to include and use the trait BladeOneCache
|
||||
|
||||
```php
|
||||
class MyBlade extends bladeone\BladeOne {
|
||||
use bladeone\BladeOneCache;
|
||||
}
|
||||
$blade=new MyBlade($views,$compiledFolder);
|
||||
```
|
||||
- Where MyBlade is a new class that extends the BladeOne class and use the cache features.
|
||||
|
||||
|
||||
|
||||
## New Tags (template file)
|
||||
|
||||
### cache
|
||||
|
||||
```html
|
||||
@ cache(1,86400).
|
||||
<!-- content here will be cached-->
|
||||
@ endcache()
|
||||
<!-- this content will not be cached-->
|
||||
@ cache(2,86400).
|
||||
<!-- content here will also be cached-->
|
||||
@ endcache()
|
||||
```
|
||||
|
||||
- @cache([id],[duration]) start a new cache block
|
||||
- The cacheid (optional) indicates the id of the cache. It shoulds be unique. If not id then its added automatically
|
||||
- The duration (optional) in seconds indicates the duration of the cache.
|
||||
- @endcache
|
||||
- End of the cache block. It shouldn't be stacked.
|
||||
|
||||
## New Business Logic / Controller function
|
||||
|
||||
### function cacheExpired
|
||||
```php
|
||||
if ($blade->cacheExpired('hellocache',1,5)) { // 'hellocache' = template, 1 = id cache, 5 = duration (seconds)
|
||||
// cache expired, so we should do some stuff (such as read from the database)
|
||||
}
|
||||
```
|
||||
|
||||
- function cacheExpired(cachefile,cacheid,duration) returns true if the cache expires (and it shoulds be calculated and rebuild), otherwise false
|
||||
- cachefile indicates the template to use.
|
||||
- cacheid indicates the id of the cache.
|
||||
- duration indicates the duration of the cache (in seconds)
|
||||
|
||||
> Note : if BLADEONE_MODE = 1 (**forced**) then the cache system is never used.
|
||||
|
||||
> Note : The cache system works per **template** and **cacheid**. I.e. its possible to cache a part of a template for a limited time, while caching the rest for a long while.
|
|
@ -0,0 +1,129 @@
|
|||
# BladeOneHtml extension library (optional)
|
||||
|
||||
Requires: BladeOne
|
||||
|
||||
For using this tag, the code requires to use the class BladeOneHtml
|
||||
|
||||
## New Tags
|
||||
|
||||
### Select/endselect
|
||||
|
||||
```html
|
||||
@select('id1')
|
||||
@item('0','--Select a country--',$countrySelected,'')
|
||||
@items($countries,'id','name',$countrySelected,'')
|
||||
@endselect()
|
||||
```
|
||||
|
||||
- `@select`/`@endselect` creates the **select** tag. The first value is the id and name of the tag.
|
||||
- `@item` allows to add one element **option** tag.
|
||||
- The first value is the id and the second is the visible text.
|
||||
- The third tag indicates the selected element. It could be a single value or an array of elements.
|
||||
- `@items` allows to add one list of elements **option** tag.
|
||||
- The first value is the list of values,
|
||||
- the second and third is the id and name.
|
||||
- And the fourth one is the selected value (optional)
|
||||
|
||||
![select](http://i.imgur.com/yaMavQB.jpg?1)
|
||||
|
||||
### Input
|
||||
|
||||
```html
|
||||
@input('iduser',$currentUser,'text'[,$extra])
|
||||
```
|
||||
|
||||
`@input` creates a **input** tag. The first value is the id/name, the second is the default value, the third is the type (by default is text for textbox)*[]:
|
||||
|
||||
### Form/endform
|
||||
```form
|
||||
@form(['action'],['post'][,$extra])
|
||||
... form goes here
|
||||
@endform
|
||||
```
|
||||
`@form` creates **form** html tag. The first value (optional) is the action, the second value (optional) is the method ('post','get')
|
||||
|
||||
### listboxes
|
||||
```html
|
||||
@listboxes('idlistbox',$countries,'id','name',$multipleSelect)
|
||||
```
|
||||
|
||||
- `@listboxes(id,$listvalues,$fieldid,$fieldvalue,·selected,[$extra])` Creates a list box
|
||||
- The `id` indicates the id of the object. It shoulds be unique.
|
||||
- `$listvalues` indicates the vlaues to show in the object.
|
||||
- `$fieldid` indicates the field used as key.
|
||||
- `$fieldvalue` indicates the field used to show.
|
||||
- `$selected` indicates the selected values.
|
||||
- `$extra` (optional) indicates the extra value (see note below).
|
||||
|
||||
### selectgroup
|
||||
|
||||
### radio/endradio
|
||||
|
||||
### checkbox/endcheckbox
|
||||
|
||||
Single checkbox:
|
||||
```html
|
||||
@checkbox('idsimple','777','SelectMe','777')
|
||||
```
|
||||
|
||||
Multiple checkboxes:
|
||||
```html
|
||||
@checkbox('id3')
|
||||
@item('0','--Select a country--')<br>
|
||||
@items($countries,'id','name',$countrySelected,'%s<br>')
|
||||
@endcheckbox()
|
||||
```
|
||||
|
||||
- `@checkbox(id,$value,$label,$valueSelected,[$extra])`
|
||||
- `id` indicates the id of the object. It shoulds be unique
|
||||
- `$value` indicates the value of the checkbox when its selected.
|
||||
- `$label` shows the label of the object.
|
||||
- `$valueselected` indicates the selected value. If $value is equals to $valueselected then the checkbox is checked
|
||||
- `$extra` (optional) indicates the extra value (see section note below).
|
||||
|
||||
> Note: It could be generates as a single value or as a list of checkboxes (see examples)
|
||||
|
||||
|
||||
### item/trio
|
||||
|
||||
### items/trios
|
||||
|
||||
### textarea
|
||||
|
||||
### hidden
|
||||
|
||||
### label
|
||||
```html
|
||||
@label('id','Select the value:')
|
||||
```
|
||||
- `@label(id,$label,[$extra])`
|
||||
- `id` indicates the id of the input object related with the label.
|
||||
- `$label` shows the label of the object.
|
||||
- `$extra` (optional) indicates the extra value (see section note below).
|
||||
|
||||
|
||||
### commandbutton
|
||||
|
||||
### link (new in 1.6)
|
||||
```html
|
||||
@link('http://www.google.com','Go to google')
|
||||
```
|
||||
|
||||
|
||||
|
||||
### NOTE: Extra Parameter
|
||||
|
||||
Additionally, you can add an (optional) last parameter with additional value (see the example of @select)
|
||||
|
||||
```html
|
||||
<!-- code using bootstrap -->
|
||||
<div class="form-group">
|
||||
<label for="sel1">Select list:</label>
|
||||
@select('id1')
|
||||
@item('0','--Select a country--',"",class='form-control'")
|
||||
@items($countries,'id','name',"",$countrySelected)
|
||||
@endselect()
|
||||
</select>
|
||||
</div>
|
||||
```
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# BladeOneLang extension library (optional)
|
||||
|
||||
Requires: BladeOne
|
||||
|
||||
This library adds cache to the visual layer and business/logic layer.
|
||||
For using this library, the code requires to include and use the trait BladeOneCache
|
||||
|
||||
Setting:
|
||||
```php
|
||||
class MyBlade extends bladeone\BladeOne {
|
||||
use bladeone\BladeOneLang;
|
||||
}
|
||||
$blade=new MyBlade($views,$compiledFolder);
|
||||
|
||||
$blade->missingLog='c:\temp\missingkey.txt'; // (optional) if a traduction is missing the it will be saved here.
|
||||
|
||||
$lang='jp'; // try es,jp or fr
|
||||
include './lang/'.$lang.'.php';
|
||||
```
|
||||
|
||||
Where /lang/es.php is simmilar to:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use eftec\bladeone\BladeOneLang;
|
||||
|
||||
BladeOneLang::$dictionary=array(
|
||||
'Hat'=>'Sombrero'
|
||||
,'Cat'=>'Gato'
|
||||
,'Cats'=>'Gatos'
|
||||
,'%s is a nice cat'=>'%s es un buen gato'
|
||||
);
|
||||
```
|
||||
Template file
|
||||
```php
|
||||
Hat in spanish is @_e('Hat')<br>
|
||||
There is one @_n('Cat','Cats',1)<br>
|
||||
@_ef('%s is a nice cat','Cheshire')<br>
|
||||
```
|
||||
Returns:
|
||||
Hat in spanish is Sombrero
|
||||
There is one Gato
|
||||
Cheshire es un buen gato.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- Where MyBlade is a new class that extends the bladeone class and use the Lang features.
|
||||
|
||||
|
||||
|
||||
## Template methods
|
||||
|
||||
### @_e('Word or phrase')
|
||||
|
||||
it tries to translate the word if its in the array defined by `BladeOneLang::$dictionary`.
|
||||
If there is not a entry with the word 'Hat' (case sensitive) then it returns 'Hat'. Also, if the log file is define, the it also saves an entry with the missing word.
|
||||
|
||||
For the previous example. `@_e('Hat')` returns Sombrero.
|
||||
|
||||
### @_ef('some phrase %s another words %s %i','word1','word2',20)
|
||||
|
||||
Its the same than `@_e`, however it parses the text (using `sprintf`).
|
||||
If the operation fails then, it returns the original expression without translation.
|
||||
|
||||
For the previous example.` @_ef('%s is a nice cat','Cheshire')` returns Cheshire es un buen gato.
|
||||
|
||||
### @_n('Singular','Plural',number)
|
||||
|
||||
If number is plural (more than 1) then it translates (if any) the second word, otherwise it translates the first word.
|
||||
If not number is used then it always translates the singular expression.
|
||||
|
||||
For the previous example.` @_n('Cat','Cats',100)` returns Cheshire es un buen gato.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
#BladeOneLogic extension library (optional)
|
||||
|
||||
Requires: BladeOne
|
||||
|
||||
For using this tag, the code requires to use the class BladeOneLogic
|
||||
|
||||
## Defintion of Blade Template
|
||||
For using this tag, the code requires to use the class BladeOneLogic that extends the class BladeOne.
|
||||
The code extends the class BladeOneHtml by creating a daisy chain.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Jorge Patricio Castro Castillo
|
||||
|
||||
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, other copyright notices 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.
|
|
@ -0,0 +1,485 @@
|
|||
![Logo](https://raw.githubusercontent.com/EFTEC/BladeOne/gh-pages/images/bladelogo.png)
|
||||
|
||||
# BladeOne Blade Template Engine
|
||||
BladeOne is a standalone version of Blade Template Engine that uses a single PHP file and can be ported and used in different projects. It allows you to use blade template outside Laravel.
|
||||
|
||||
Бладеоне-это отдельная версия ядра Blade-шаблонов, которая использует один PHP-файл и может быть портирована и использована в различных проектах. Он позволяет использовать шаблон Blade за пределами laravel.
|
||||
|
||||
|
||||
[![Packagist](https://img.shields.io/packagist/v/eftec/bladeone.svg)](https://packagist.org/packages/eftec/bladeone)
|
||||
[![Total Downloads](https://poser.pugx.org/eftec/bladeone/downloads)](https://packagist.org/packages/eftec/bladeone)
|
||||
[![Maintenance](https://img.shields.io/maintenance/yes/2021.svg)]()
|
||||
[![composer](https://img.shields.io/badge/composer-%3E1.6-blue.svg)]()
|
||||
[![php](https://img.shields.io/badge/php->5.6-green.svg)]()
|
||||
[![php](https://img.shields.io/badge/php-7.x-green.svg)]()
|
||||
[![php](https://img.shields.io/badge/php-8.x-green.svg)]()
|
||||
[![CocoaPods](https://img.shields.io/badge/docs-70%25-yellow.svg)]()
|
||||
|
||||
|
||||
NOTE: So far it's apparently the only one project that it's updated with the latest version of **Blade 7 (March 2020)**. It misses some commands [missing](#missing) but nothing more.
|
||||
|
||||
|
||||
Примечание: до сих пор это, видимо, только один проект, который обновляется с последней версией ** Blade 7 (2020 Марта) **. Он пропускает некоторые команды [отсутствует](#missing), но ничего больше.
|
||||
|
||||
## Comparison with Twig
|
||||
|
||||
> (spoiler) Twig is slower. 😊
|
||||
|
||||
| | First Time Time | First Time Memory | Overload First Time | Second Time | Second Time Memory |
|
||||
|----------|-----------------|-------------------|---------------------|-------------|--------------------|
|
||||
| BladeOne | 1962ms | 2024kb | 263 | 1917ms | 2024kb |
|
||||
| Twig | 3734ms | 2564kb | 123 | 3604ms | 2327kb |
|
||||
|
||||
What it was tested?. It was tested two features (that are the most used): It was tested with an array with
|
||||
1000 elements and tested many times.
|
||||
|
||||
[Comparison with Twig](https://github.com/EFTEC/BladeOne/wiki/Comparison-with-Twig)
|
||||
|
||||
|
||||
|
||||
## NOTE about questions, reports, doubts or suggesting:
|
||||
|
||||
✔ If you want to open an inquiry, do you have a doubt, or you find a bug, then you could open an [ISSUE](https://github.com/EFTEC/BladeOne/issues).
|
||||
Please, don't email me (or send me PM) directly for question or reports.
|
||||
Also, if you want to reopen a report, then you are open to do that.
|
||||
I will try to answer all and every one of the question (in my limited time).
|
||||
|
||||
## Some example
|
||||
| [ExampleTicketPHP](https://github.com/jorgecc/ExampleTicketPHP) | [Example cupcakes](https://github.com/EFTEC/example.cupcakes) | [Example Search](https://github.com/EFTEC/example-search) | [Example Editable Grid](https://github.com/EFTEC/example-php-editablegrid) |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| <img src="https://camo.githubusercontent.com/3c938f71f46a90eb85bb104f0f396fcba62b8f4a/68747470733a2f2f74686570726163746963616c6465762e73332e616d617a6f6e6177732e636f6d2f692f3436696b7061376661717677726533797537706a2e6a7067" alt="example php bladeone" width="200"/> | <img src="https://github.com/EFTEC/example.cupcakes/raw/master/docs/result.jpg" alt="example php bladeone cupcakes" width="200"/> | <img src="https://github.com/EFTEC/example-search/raw/master/img/search_bootstrap.jpg" alt="example php bladeone search" width="200"/> | <img src="https://github.com/EFTEC/example-php-editablegrid/raw/master/docs/final.jpg" alt="example php bladeone search" width="200"/> |
|
||||
|
||||
[https://www.southprojects.com](https://www.southprojects.com)
|
||||
|
||||
|
||||
## Manual
|
||||
|
||||
* [BladeOne Manual](https://github.com/EFTEC/BladeOne/wiki/BladeOne-Manual)
|
||||
* [Template tags (views)](https://github.com/EFTEC/BladeOne/wiki/Template-tags)
|
||||
* [Template variables](https://github.com/EFTEC/BladeOne/wiki/Template-variables)
|
||||
* [Template inheritance](https://github.com/EFTEC/BladeOne/wiki/Template-inheritance)
|
||||
* [Template component](https://github.com/EFTEC/BladeOne/wiki/Template-Component)
|
||||
* [Template stack](https://github.com/EFTEC/BladeOne/wiki/Template-stack)
|
||||
* [Template asset, relative, base, current and canonical links](https://github.com/EFTEC/BladeOne/wiki/Template-Asset,-Relative,-Base-and-Canonical-Links)
|
||||
* [Template calling methods](https://github.com/EFTEC/BladeOne/wiki/Template-calling-methods)
|
||||
* [Template logic](https://github.com/EFTEC/BladeOne/wiki/Template-logic)
|
||||
* [Template loop](https://github.com/EFTEC/BladeOne/wiki/Template-loop)
|
||||
* [Template Pipes (Filter)](https://github.com/EFTEC/BladeOne/wiki/Template-Pipes-(Filter))
|
||||
* [Methods of the class](https://github.com/EFTEC/BladeOne/wiki/Methods-of-the-class)
|
||||
* [Injecting logic before the view (composer)](https://github.com/EFTEC/BladeOne/wiki/Injecting-logic-before-the-view-(composer))
|
||||
* [Extending the class](https://github.com/EFTEC/BladeOne/wiki/Extending-the-class)
|
||||
* [Using BladeOne with YAF Yet Another Framework](https://github.com/EFTEC/BladeOne/wiki/Using--BladeOne-with-YAF)
|
||||
* [Differences between Blade and BladeOne](https://github.com/EFTEC/BladeOne/wiki/Differences-between-Blade-and-BladeOne)
|
||||
* [Comparision with Twig (May-2020)](https://github.com/EFTEC/BladeOne/wiki/Comparison-with-Twig)
|
||||
* [Changelog](https://github.com/EFTEC/BladeOne/wiki/Changelog)
|
||||
* [Changes between 2.x and 3.0 and TODO](https://github.com/EFTEC/BladeOne/wiki/Changes-between-2.x-and-3.0-and-TODO)
|
||||
* [Code Protection (Sourceguardian and similars)](https://github.com/EFTEC/BladeOne/wiki/Code-Protection-(Sourceguardian-and-similars))
|
||||
|
||||
## Why does it support PHP 5.x?
|
||||
|
||||
As for today (January 2021), PHP 5.x is still strong even when it is discontinued, but my main problem is the performance.
|
||||
|
||||
* PHP 7.x 60.2%
|
||||
* PHP 5.x 39.5%
|
||||
* PHP 8.x 00.1%
|
||||
|
||||
* PHP 7.0 brings some new features and definitions. One is the use of type-hinting. While it could be useful, but it affects the performance
|
||||
so there is not reason to use it for this library (we use PHPDOC and it doesn't affect the performance).
|
||||
* PHP 7.0 adds some new features such as is_countable(). However, it is slower than the method used here.
|
||||
* We could also use Null Coalescing Operator, but it is not slower or faster than a ternary operator.
|
||||
* PHP 8.0 also adds str_contains(), but it doesn't bring a sustancial performance but syntax sugar.
|
||||
|
||||
|
||||
## Laravel blade tutorial
|
||||
|
||||
You can find some tutorials and example on the folder [Examples](examples).
|
||||
|
||||
You could also check the wiki [Wiki](https://github.com/EFTEC/BladeOne/wiki)
|
||||
|
||||
## About this version
|
||||
By standard, The original Blade library is part of Laravel (Illuminate components) and to use this template library, you require install Laravel and Illuminate-view components.
|
||||
The syntax of Blade is pretty nice and bright. It's based in C# Razor (another template library for C#). It's starting to be considered a de-facto standard template system for many PHP (Smarty has been riding off the sunset since years ago) so, if we can use it without Laravel then its a big plus for many projects.
|
||||
In fact, in theory, it is even possible to use with Laravel.
|
||||
Exists different versions of Blade Template that runs without Laravel, but most requires 50 or more files, and those templates add a new level of complexity, so they are not removing Laravel but hiding:
|
||||
|
||||
- More files to manage.
|
||||
- Changes to the current project (if you want to integrate the template into an existent one)
|
||||
- Incompatibilities amongst other projects.
|
||||
- Slowness (if your server is not using op-cache)
|
||||
- Most of the code in the original Blade is used for future use, including the chance to use a different template engine.
|
||||
- Some Laravel legacy code.
|
||||
|
||||
This project uses a single file called BladeOne.php and a single class (called BladeOne).
|
||||
If you want to use it then include it, creates the folders and that's it!. Nothing more (not even namespaces)*[]: It is also possible to use Blade even with Laravel or any other framework. After all, BladeOne is native, so it's possible to integrate into almost any project.
|
||||
|
||||
## Why to use it instead of native PHP?
|
||||
|
||||
### Separation of concerns
|
||||
Let’s say that we have the next code
|
||||
|
||||
```php
|
||||
//some PHP code
|
||||
// some HTML code
|
||||
// more PHP code
|
||||
// more HTML code.
|
||||
```
|
||||
It leads to a mess of a code. For example, let’s say that we oversee changing the visual layout of the page. In this case, we should change all the code and we could even break part of the programming.
|
||||
Instead, using a template system works in the next way:
|
||||
```php
|
||||
// some php code
|
||||
ShowTemplate();
|
||||
```
|
||||
We are separating the visual layer from the code layer. As a plus, we could assign a non-php-programmer in charge to edit the template, and he/she doesn’t need to touch or know our php code.
|
||||
## Security
|
||||
Let’s say that we have the next exercise (it’s a dummy example)
|
||||
```php
|
||||
$name=@$_GET['name'];
|
||||
echo "my name is ".$name;
|
||||
```
|
||||
It could be separates as two files:
|
||||
```php // index.php
|
||||
$name=@$_GET['name'];
|
||||
include "template.php";
|
||||
```
|
||||
```php
|
||||
// template.php
|
||||
echo "my name is ".$name;
|
||||
```
|
||||
Even for this simple example, there is a risk of hacking. How? A user could sends malicious code by using the GET variable, such as html or even javascript. The second file should be written as follow:
|
||||
```php
|
||||
// template.php
|
||||
echo "my name is ".html_entities($name);
|
||||
```
|
||||
html_entities should be used in every single part of the visual layer (html) where the user could injects malicious code, and it’s a real tedious work. BladeOne does it automatically.
|
||||
```php
|
||||
// template.blade.php
|
||||
My name is {{$name}}
|
||||
```
|
||||
## Easy to use
|
||||
|
||||
BladeOne is focused on an easy syntax that it's fast to learn and to write, while it could keep the power of PHP.
|
||||
|
||||
Let's consider the next template:
|
||||
|
||||
```php // template.php
|
||||
<select>
|
||||
<? foreach($countries as $c) { ?>
|
||||
<option value=<? echo html_entities($c->value); ?> > <? echo html_entities($c->text); ?></option>
|
||||
<? } ?>
|
||||
</select>
|
||||
```
|
||||
With BladeOne, we could do the same with
|
||||
```php // template.blade.php
|
||||
<select>
|
||||
@foreach($countries as $c)
|
||||
<option value={{$c->value}} >{{echo html_entities($c->text)}}</option>
|
||||
@nextforeach
|
||||
</select>
|
||||
```
|
||||
And if we use thehtml extension we could even reduce to
|
||||
|
||||
```php // template.blade.php
|
||||
@select('id1')
|
||||
@items($countries,'value','text','','')
|
||||
@endselect()
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### Performance
|
||||
|
||||
This library works in two stages.
|
||||
|
||||
The first is when the template calls the first time. In this case, the template compiles and store in a folder.
|
||||
The second time the template calls then, it uses the compiled file. The compiled file consist mainly in native PHP, so **the performance is equals than native code.** since the compiled version IS PHP.
|
||||
|
||||
### Scalable
|
||||
|
||||
You could add and use your own function by adding a new method (or extending) to the BladeOne class. NOTE: The function should start with the name "compile"
|
||||
```php
|
||||
protected function compileMyFunction($expression)
|
||||
{
|
||||
return $this->phpTag . "echo 'YAY MY FUNCTION IS WORKING'; ?>";
|
||||
}
|
||||
```
|
||||
|
||||
Where the function could be used in a template as follow
|
||||
```php
|
||||
@myFunction('param','param2'...)
|
||||
```
|
||||
Alternatively, BladeOne allows to run arbitrary code from any class or method if its defined.
|
||||
```php
|
||||
{{SomeClass::SomeMethod('param','param2'...)}}
|
||||
```
|
||||
## Install (pick one of the next one)
|
||||
|
||||
1) Download the file manually then unzip (using WinRAR,7zip or any other program) https://github.com/EFTEC/BladeOne/archive/master.zip
|
||||
2) git clone https://github.com/EFTEC/BladeOne
|
||||
3) Composer. See [usage](#usage)
|
||||
4) wget https://github.com/EFTEC/BladeOne/archive/master.zip
|
||||
unzip master.zip
|
||||
|
||||
## Usage
|
||||
|
||||
If you use **composer**, then you could add the library using the next command (command line)
|
||||
|
||||
> composer require eftec/bladeone
|
||||
|
||||
If you don't use it, then you could download the library and include it manually.
|
||||
|
||||
### Implicit definition
|
||||
|
||||
```php
|
||||
use eftec\bladeone\BladeOne;
|
||||
|
||||
$views = __DIR__ . '/views';
|
||||
$cache = __DIR__ . '/cache';
|
||||
$blade = new BladeOne($views,$cache,BladeOne::MODE_DEBUG); // MODE_DEBUG allows to pinpoint troubles.
|
||||
echo $blade->run("hello",array("variable1"=>"value1")); // it calls /views/hello.blade.php
|
||||
```
|
||||
|
||||
Where `$views` is the folder where the views (templates not compiled) will be stored.
|
||||
`$cache` is the folder where the compiled files will be stored.
|
||||
|
||||
In this example, the BladeOne opens the template **hello**. So in the views folder it should exist a file called **hello.blade.php**
|
||||
|
||||
views/hello.blade.php:
|
||||
```html
|
||||
<h1>Title</h1>
|
||||
{{$variable1}}
|
||||
```
|
||||
|
||||
### Explicit
|
||||
|
||||
In this mode, it uses the folders ```__DIR__/views``` and ```__DIR__/compiles```, also it uses the mode as MODE_AUTO.
|
||||
|
||||
```php
|
||||
use eftec\bladeone\BladeOne;
|
||||
|
||||
$blade = new BladeOne(); // MODE_DEBUG allows to pinpoint troubles.
|
||||
echo $blade->run("hello",array("variable1"=>"value1")); // it calls /views/hello.blade.php
|
||||
```
|
||||
|
||||
### Fluent
|
||||
|
||||
```php
|
||||
use eftec\bladeone\BladeOne;
|
||||
|
||||
$blade = new BladeOne(); // MODE_DEBUG allows to pinpoint troubles.
|
||||
echo $blade->setView('hello') // it sets the view to render
|
||||
->share(array("variable1"=>"value1")) // it sets the variables to sends to the view
|
||||
->run(); // it calls /views/hello.blade.php
|
||||
```
|
||||
|
||||
## Filter (Pipes)
|
||||
|
||||
It is possible to modify the result by adding filters to the result.
|
||||
|
||||
Let's say we have the next value $name='Jack Sparrow'
|
||||
|
||||
```php
|
||||
$blade=new BladeOne();
|
||||
$blade->pipeEnable=true; // pipes are disable by default so it must be enable.
|
||||
echo $blade->run('template',['name'=>'Jack Sparrow']);
|
||||
```
|
||||
|
||||
Our view could look like:
|
||||
|
||||
```php
|
||||
{{$name}} or {!! $name !!} // Jack Sparrow
|
||||
```
|
||||
|
||||
What if we want to show the name in uppercase?.
|
||||
|
||||
We could do in our code $name=strtoupper('Jack Sparrow'). With Pipes, we could do the same as follow:
|
||||
|
||||
```php
|
||||
{{$name | strtoupper}} // JACK SPARROW
|
||||
```
|
||||
|
||||
We could also add arguments and chain methods.
|
||||
|
||||
```php
|
||||
{{$name | strtoupper | substr:0,5}} // JACK
|
||||
```
|
||||
|
||||
You can find more information on https://github.com/EFTEC/BladeOne/wiki/Template-Pipes-(Filter)
|
||||
|
||||
|
||||
|
||||
## Security (optional)
|
||||
|
||||
```php
|
||||
require "vendor/autoload.php";
|
||||
|
||||
Use eftec\bladeone;
|
||||
|
||||
$views = __DIR__ . '/views';
|
||||
$cache = __DIR__ . '/cache';
|
||||
$blade=new bladeone\BladeOne($views,$cache,BladeOne::MODE_AUTO);
|
||||
|
||||
$blade->setAuth('johndoe','admin'); // where johndoe is an user and admin is the role. The role is optional
|
||||
|
||||
echo $blade->run("hello",array("variable1"=>"value1"));
|
||||
```
|
||||
|
||||
If you log in using blade then you could use the tags @auth/@endauth/@guest/@endguest
|
||||
|
||||
|
||||
```html
|
||||
@auth
|
||||
// The user is authenticated...
|
||||
@endauth
|
||||
|
||||
@guest
|
||||
// The user is not authenticated...
|
||||
@endguest
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```html
|
||||
@auth('admin')
|
||||
// The user is authenticated...
|
||||
@endauth
|
||||
|
||||
@guest('admin')
|
||||
// The user is not authenticated...
|
||||
@endguest
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Extensions Libraries (optional)
|
||||
|
||||
[BladeOneCache Documentation](BladeOneCache.md)
|
||||
|
||||
[https://github.com/eftec/BladeOneHtml](https://github.com/eftec/BladeOneHtml)
|
||||
|
||||
|
||||
## Calling a static methods inside the template.
|
||||
|
||||
Since **3.34**, BladeOne allows to call a static method inside a class.
|
||||
|
||||
Let's say we have a class with namespace \namespace1\namespace2
|
||||
|
||||
```php
|
||||
namespace namespace1\namespace2 {
|
||||
class SomeClass {
|
||||
public static function Method($arg='') {
|
||||
return "hi world";
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Method 1 PHP Style
|
||||
|
||||
We could add a "use" in the template. Example:
|
||||
|
||||
Add the next line to the template
|
||||
```html
|
||||
@use(\namespace1\namespace2)
|
||||
```
|
||||
|
||||
and the next lines to the template (different methods)
|
||||
|
||||
```html
|
||||
{{SomeClass::Method()}}
|
||||
{!! SomeClass::Method() !!}
|
||||
@SomeClass::Method()
|
||||
```
|
||||
|
||||
> All those methods are executed at runtime
|
||||
|
||||
|
||||
### Method 2 Alias
|
||||
Or we could define alias for each classes.
|
||||
|
||||
php code:
|
||||
```php
|
||||
$blade = new BladeOne();
|
||||
// with the method addAliasClasses
|
||||
$blade->addAliasClasses('SomeClass', '\namespace1\namespace2\SomeClass');
|
||||
// with the setter setAliasClasses
|
||||
$blade->setAliasClasses(['SomeClass'=>'\namespace1\namespace2\SomeClass']);
|
||||
// or directly in the field
|
||||
$blade->aliasClasses=['SomeClass'=>'\namespace1\namespace2\SomeClass'];
|
||||
```
|
||||
|
||||
Template:
|
||||
```html
|
||||
{{SomeClass::Method()}}
|
||||
{!! SomeClass::Method() !!}
|
||||
@SomeClass::Method()
|
||||
```
|
||||
|
||||
> We won't need alias or use for global classes.
|
||||
|
||||
|
||||
|
||||
## Named argument (since 3.38)
|
||||
|
||||
BladeOne allows named arguments. This feature must be implemented per function.
|
||||
|
||||
Let's say the next problem:
|
||||
|
||||
It is the old library BladeOneHtml:
|
||||
|
||||
```
|
||||
@select('id1')
|
||||
@item('0','--Select a country--',"",class='form-control'")
|
||||
@items($countries,'id','name',"",$countrySelected)
|
||||
@endselect
|
||||
```
|
||||
|
||||
And it is the next library:
|
||||
|
||||
```html
|
||||
@select(id="aaa" value=$selection values=$countries alias=$country)
|
||||
@item(value='aaa' text='-- select a country--')
|
||||
@items( id="chkx" value=$country->id text=$country->name)
|
||||
@endselect
|
||||
```
|
||||
|
||||
The old method **select** only allows a limited number of arguments. And the order of the arguments is important.
|
||||
|
||||
The new method **select** allows to add different types of arguments
|
||||
|
||||
## BladeOneHtml
|
||||
|
||||
It is a new extension to BladeOne. It allows to create html components easily and with near-to-native performance.
|
||||
|
||||
It uses a new feature of BladeOne: named arguments
|
||||
|
||||
Example to create a select:
|
||||
|
||||
```html
|
||||
@select(id="aaa" value=$selection values=$countries alias=$country)
|
||||
@item(value='aaa' text='-- select a country--')
|
||||
@items( id="chkx" value=$country->id text=$country->name)
|
||||
@endselect
|
||||
```
|
||||
|
||||
[https://github.com/eftec/BladeOneHtml](https://github.com/eftec/BladeOneHtml)
|
||||
|
||||
You could download it or add it via Composer
|
||||
|
||||
> composer require eftec/bladeonehtml
|
||||
|
||||
|
||||
## Collaboration
|
||||
|
||||
You are welcome to use it, share it, ask for changes and whatever you want to. Just keeps the copyright notice in the file.
|
||||
|
||||
## Future
|
||||
* Blade locator/container
|
||||
|
||||
|
||||
|
||||
## License
|
||||
MIT License.
|
||||
BladeOne (c) 2016-2021 Jorge Patricio Castro Castillo
|
||||
Blade (c) 2012 Laravel Team (This code is based and inspired in the work of the team of Laravel, however BladeOne is mostly a original work)
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"name": "eftec/bladeone",
|
||||
"description": "The standalone version Blade Template Engine from Laravel in a single php file",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"blade",
|
||||
"template",
|
||||
"view",
|
||||
"php",
|
||||
"templating"
|
||||
],
|
||||
"homepage": "https://github.com/EFTEC/BladeOne",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jorge Patricio Castro Castillo",
|
||||
"email": "jcastro@eftec.cl"
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "5.6.1"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6",
|
||||
"ext-json": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "This extension is used if it's active",
|
||||
"eftec/bladeonehtml": "Extension to create forms"
|
||||
},
|
||||
"archive": {
|
||||
"exclude": [
|
||||
"/examples"
|
||||
]
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"eftec\\bladeone\\": "lib/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"eftec\\tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.7",
|
||||
"squizlabs/php_codesniffer": "^3.5.4",
|
||||
"friendsofphp/php-cs-fixer": "^2.16.1"
|
||||
},
|
||||
"scripts": {
|
||||
"sniff": [
|
||||
"phpcs --extensions=php ."
|
||||
],
|
||||
"fix": [
|
||||
"php-cs-fixer fix",
|
||||
"phpcbf --extensions=php ."
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,322 @@
|
|||
<?php /** @noinspection TypeUnsafeComparisonInspection */
|
||||
/** @noinspection PhpUnused */
|
||||
|
||||
/** @noinspection DuplicatedCode */
|
||||
|
||||
|
||||
namespace eftec\bladeone;
|
||||
|
||||
use function fclose;
|
||||
use function file_put_contents;
|
||||
use function filemtime;
|
||||
use function filesize;
|
||||
use function fopen;
|
||||
use function fwrite;
|
||||
use function is_array;
|
||||
use function is_object;
|
||||
use function ob_get_contents;
|
||||
use function print_r;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
use function time;
|
||||
|
||||
/**
|
||||
* trait BladeOneCache
|
||||
* Copyright (c) 2016 Jorge Patricio Castro Castillo MIT License. Don't delete this comment, its part of the license.
|
||||
* Extends the tags of the class BladeOne. Its optional
|
||||
* It adds the next tags to the template
|
||||
* <code>
|
||||
* @ cache([cacheid],[duration=86400]). The id is optional. The duration of the cache is in seconds
|
||||
* // content here
|
||||
* @ endcache()
|
||||
* </code>
|
||||
* It also adds a new function (optional) to the business or logic layer
|
||||
* <code>
|
||||
* if ($blade->cacheExpired('hellocache',1,5)) { //'helloonecache' =template, =1 id cache, 5=duration (seconds)
|
||||
* // cache expired, so we should do some stuff (such as read from the database)
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @package BladeOneCache
|
||||
* @version 3.42 2020-04-25
|
||||
* @link https://github.com/EFTEC/BladeOne
|
||||
* @author Jorge Patricio Castro Castillo <jcastro arroba eftec dot cl>
|
||||
*/
|
||||
trait BladeOneCache
|
||||
{
|
||||
protected $curCacheId = 0;
|
||||
protected $curCacheDuration = 0;
|
||||
protected $curCachePosition = 0;
|
||||
protected $cacheRunning = false;
|
||||
protected $cachePageRunning = false;
|
||||
protected $cacheLog;
|
||||
/**
|
||||
* @var array avoids to compare the file different times. It also avoids race conditions.
|
||||
*/
|
||||
private $cacheExpired = [];
|
||||
/**
|
||||
* @var string=['get','post','getpost','request',null][$i]
|
||||
*/
|
||||
private $cacheStrategy;
|
||||
/** @var array|null */
|
||||
private $cacheStrategyIndex;
|
||||
|
||||
/**
|
||||
* @return null|string $cacheStrategy=['get','post','getpost','request',null][$i]
|
||||
*/
|
||||
public function getCacheStrategy()
|
||||
{
|
||||
return $this->cacheStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* It sets the cache log. If not cache log then it does not generates a log file<br>
|
||||
* The cache log stores each time a template is creates or expired.<br>
|
||||
*
|
||||
* @param string $file
|
||||
*/
|
||||
public function setCacheLog($file)
|
||||
{
|
||||
$this->cacheLog=$file;
|
||||
}
|
||||
|
||||
public function writeCacheLog($txt, $nivel)
|
||||
{
|
||||
if (!$this->cacheLog) {
|
||||
return; // if there is not a file assigned then it skips saving.
|
||||
}
|
||||
$fz = @filesize($this->cacheLog);
|
||||
if (is_object($txt) || is_array($txt)) {
|
||||
$txt = print_r($txt, true);
|
||||
}
|
||||
// Rewrite file if more than 100000 bytes
|
||||
$mode=($fz > 100000) ? 'w':'a';
|
||||
$fp = fopen($this->cacheLog, $mode);
|
||||
if ($fp === false) {
|
||||
return;
|
||||
}
|
||||
switch ($nivel) {
|
||||
case 1:
|
||||
$txtNivel='expired';
|
||||
break;
|
||||
case 2:
|
||||
$txtNivel='new';
|
||||
break;
|
||||
default:
|
||||
$txtNivel='other';
|
||||
}
|
||||
$txtarg=json_encode($this->cacheUniqueGUID(false));
|
||||
fwrite($fp, date('c') . "\t$txt\t$txtNivel\t$txtarg\n");
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* It sets the strategy of the cache page.
|
||||
*
|
||||
* @param null|string $cacheStrategy =['get','post','getpost','request',null][$i]
|
||||
* @param array|null $index if null then it reads all indexes. If not, it reads a indexes.
|
||||
*/
|
||||
public function setCacheStrategy($cacheStrategy, $index = null)
|
||||
{
|
||||
$this->cacheStrategy = $cacheStrategy;
|
||||
$this->cacheStrategyIndex = $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* It obtains an unique GUID based in:<br>
|
||||
* <b>get</b>= parameters from the url<br>
|
||||
* <b>post</b>= parameters sends via post<br>
|
||||
* <b>getpost</b> = a mix between get and post<br>
|
||||
* <b>request</b> = get, post and cookies (including sessions)<br>
|
||||
* MD5 could generate colisions (2^64 = 18,446,744,073,709,551,616) but the end hash is the sum of the hash of
|
||||
* the page + this GUID.
|
||||
*
|
||||
* @param bool $serialize if true then it serializes using md5
|
||||
* @return string
|
||||
*/
|
||||
private function cacheUniqueGUID($serialize = true)
|
||||
{
|
||||
switch ($this->cacheStrategy) {
|
||||
case 'get':
|
||||
$r = $_GET;
|
||||
break;
|
||||
case 'post':
|
||||
$r = $_POST;
|
||||
break;
|
||||
case 'getpost':
|
||||
$arr = array_merge($_GET, $_POST);
|
||||
$r = $arr;
|
||||
break;
|
||||
case 'request':
|
||||
$r = $_REQUEST;
|
||||
break;
|
||||
default:
|
||||
$r = null;
|
||||
}
|
||||
if ($this->cacheStrategyIndex === null || !is_array($r)) {
|
||||
$r= serialize($r);
|
||||
} else {
|
||||
$copy=[];
|
||||
foreach ($r as $key => $item) {
|
||||
if (in_array($key, $this->cacheStrategyIndex, true)) {
|
||||
$copy[$key]=$item;
|
||||
}
|
||||
}
|
||||
$r=serialize($copy);
|
||||
}
|
||||
return $serialize===true ? md5($r): $r;
|
||||
}
|
||||
|
||||
public function compileCache($expression)
|
||||
{
|
||||
// get id of template
|
||||
// if the file exists then
|
||||
// compare date.
|
||||
// if the date is too old then re-save.
|
||||
// else
|
||||
// save for the first time.
|
||||
|
||||
return $this->phpTag . "echo \$this->cacheStart{$expression}; if(!\$this->cacheRunning) { ?>";
|
||||
}
|
||||
|
||||
public function compileEndCache($expression)
|
||||
{
|
||||
return $this->phpTag . "} // if cacheRunning\necho \$this->cacheEnd{$expression}; ?>";
|
||||
}
|
||||
|
||||
/**
|
||||
* It get the filename of the compiled file (cached). If cache is not enabled, then it
|
||||
* returns the regular file.
|
||||
*
|
||||
* @param string $view
|
||||
* @return string The full filename
|
||||
*/
|
||||
private function getCompiledFileCache($view)
|
||||
{
|
||||
$id = $this->cacheUniqueGUID();
|
||||
if ($id !== null) {
|
||||
return str_replace($this->compileExtension, '_cache' . $id
|
||||
. $this->compileExtension, $this->getCompiledFile($view));
|
||||
}
|
||||
return $this->getCompiledFile($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* run the blade engine. It returns the result of the code.
|
||||
*
|
||||
* @param string $view The name of the cache. Ex: "folder.folder.view" ("/folder/folder/view.blade")
|
||||
* @param array $variables An associative arrays with the values to display.
|
||||
* @param int $ttl time to live (in second).
|
||||
* @return string
|
||||
*/
|
||||
public function runCache($view, $variables = [], $ttl = 86400)
|
||||
{
|
||||
$this->cachePageRunning = true;
|
||||
$cacheStatus=$this->cachePageExpired($view, $ttl);
|
||||
if ($cacheStatus!==0) {
|
||||
$this->writeCacheLog($view, $cacheStatus);
|
||||
$this->cacheStart('_page_', $ttl);
|
||||
$content = $this->run($view, $variables); // if no cache, then it runs normally.
|
||||
$this->fileName = $view; // sometimes the filename is replaced (@include), so we restore it
|
||||
$this->cacheEnd($content); // and it stores as a cache paged.
|
||||
} else {
|
||||
$content = $this->getFile($this->getCompiledFileCache($view));
|
||||
}
|
||||
$this->cachePageRunning = false;
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the block cache expired (or doesn't exist), otherwise false.
|
||||
*
|
||||
* @param string $templateName name of the template to use (such hello for template hello.blade.php)
|
||||
* @param string $id (id of cache, optional, if not id then it adds automatically a number)
|
||||
* @param int $cacheDuration (duration of the cache in seconds)
|
||||
* @return int 0=cache exists, 1= cache expired, 2=not exists, string= the cache file (if any)
|
||||
*/
|
||||
public function cacheExpired($templateName, $id, $cacheDuration)
|
||||
{
|
||||
if ($this->getMode() & 1) {
|
||||
return 2; // forced mode, hence it always expires. (fast mode is ignored).
|
||||
}
|
||||
$compiledFile = $this->getCompiledFile($templateName) . '_cache' . $id;
|
||||
return $this->cacheExpiredInt($compiledFile, $cacheDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* It returns true if the whole page expired.
|
||||
*
|
||||
* @param string $templateName
|
||||
* @param int $cacheDuration is seconds.
|
||||
* @return int 0=cache exists, 1= cache expired, 2=not exists, string= the cache content (if any)
|
||||
*/
|
||||
public function cachePageExpired($templateName, $cacheDuration)
|
||||
{
|
||||
if ($this->getMode() & 1) {
|
||||
return 2; // forced mode, hence it always expires. (fast mode is ignored).
|
||||
}
|
||||
$compiledFile = $this->getCompiledFileCache($templateName);
|
||||
return $this->CacheExpiredInt($compiledFile, $cacheDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used by cacheExpired() and cachePageExpired()
|
||||
*
|
||||
* @param string $compiledFile
|
||||
* @param int $cacheDuration is seconds.
|
||||
* @return int|mixed 0=cache exists, 1= cache expired, 2=not exists, string= the cache content (if any)
|
||||
*/
|
||||
private function cacheExpiredInt($compiledFile, $cacheDuration)
|
||||
{
|
||||
if (isset($this->cacheExpired[$compiledFile])) {
|
||||
// if the information is already in the array then returns it.
|
||||
return $this->cacheExpired[$compiledFile];
|
||||
}
|
||||
$date = @filemtime($compiledFile);
|
||||
if ($date) {
|
||||
if ($date + $cacheDuration < time()) {
|
||||
$this->cacheExpired[$compiledFile] = 1;
|
||||
return 2; // time-out.
|
||||
}
|
||||
} else {
|
||||
$this->cacheExpired[$compiledFile] = 2;
|
||||
return 1; // no file
|
||||
}
|
||||
$this->cacheExpired[$compiledFile] = 0;
|
||||
return 0; // cache active.
|
||||
}
|
||||
|
||||
public function cacheStart($id = '', $cacheDuration = 86400)
|
||||
{
|
||||
$this->curCacheId = ($id == '') ? ($this->curCacheId + 1) : $id;
|
||||
$this->curCacheDuration = $cacheDuration;
|
||||
$this->curCachePosition = strlen(ob_get_contents());
|
||||
if ($this->cachePageRunning) {
|
||||
$compiledFile = $this->getCompiledFileCache($this->fileName);
|
||||
} else {
|
||||
$compiledFile = $this->getCompiledFile() . '_cache' . $this->curCacheId;
|
||||
}
|
||||
|
||||
if ($this->cacheExpired('', $id, $cacheDuration) !==0) {
|
||||
$this->cacheRunning = false;
|
||||
} else {
|
||||
$this->cacheRunning = true;
|
||||
$content = $this->getFile($compiledFile);
|
||||
echo $content;
|
||||
}
|
||||
}
|
||||
|
||||
public function cacheEnd($txt = null)
|
||||
{
|
||||
if (!$this->cacheRunning) {
|
||||
$txt = ($txt !== null) ? $txt : substr(ob_get_contents(), $this->curCachePosition);
|
||||
if ($this->cachePageRunning) {
|
||||
$compiledFile = $this->getCompiledFileCache($this->fileName);
|
||||
} else {
|
||||
$compiledFile = $this->getCompiledFile() . '_cache' . $this->curCacheId;
|
||||
}
|
||||
file_put_contents($compiledFile, $txt);
|
||||
}
|
||||
$this->cacheRunning = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace eftec\bladeone;
|
||||
|
||||
use Redis;
|
||||
|
||||
/**
|
||||
* trait BladeOneCacheRedis
|
||||
* Copyright (c) 2016 Jorge Patricio Castro Castillo MIT License. Don't delete this comment, its part of the license.
|
||||
* Extends the tags of the class BladeOne. Its optional
|
||||
* It adds the next tags to the template
|
||||
* <code>
|
||||
* @ cache([cacheid],[duration=86400]). The id is optional. The duration of the cache is in seconds
|
||||
* // content here
|
||||
* @ endcache()
|
||||
* </code>
|
||||
* It also adds a new function (optional) to the business or logic layer
|
||||
* <code>
|
||||
* if ($blade->cacheExpired('hellocache',1,5)) { //'helloonecache' =template, =1 id cache, 5=duration (seconds)
|
||||
* // cache expired, so we should do some stuff (such as read from the database)
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @package BladeOneCacheRedis
|
||||
* @version 0.1 2017-12-15 NOT YET IMPLEMENTED, ITS A WIP!!!!!!!!
|
||||
* @link https://github.com/EFTEC/BladeOne
|
||||
* @author Jorge Patricio Castro Castillo <jcastro arroba eftec dot cl>
|
||||
*/
|
||||
const CACHEREDIS_SCOPEURL = 1;
|
||||
|
||||
trait BladeOneCacheRedis
|
||||
{
|
||||
protected $curCacheId = 0;
|
||||
protected $curCacheDuration = "";
|
||||
protected $curCachePosition = 0;
|
||||
protected $cacheRunning = false;
|
||||
/** @var \Redis $redis */
|
||||
protected $redis;
|
||||
protected $redisIP = '127.0.0.1';
|
||||
protected $redisPort = 6379;
|
||||
protected $redisTimeOut = 2.5;
|
||||
protected $redisConnected = false;
|
||||
protected $redisNamespace = 'bladeonecache:';
|
||||
protected $redisBase = 0;
|
||||
private $cacheExpired = []; // avoids to compare the file different times.
|
||||
|
||||
//<editor-fold desc="compile">
|
||||
public function compileCache($expression)
|
||||
{
|
||||
// get id of template
|
||||
// if the file exists then
|
||||
// compare date.
|
||||
// if the date is too old then re-save.
|
||||
// else
|
||||
// save for the first time.
|
||||
|
||||
return $this->phpTag . "echo \$this->cacheStart{$expression}; if(!\$this->cacheRunning) { ?>";
|
||||
}
|
||||
|
||||
public function compileEndCache($expression)
|
||||
{
|
||||
return $this->phpTag . "} // if cacheRunning\necho \$this->cacheEnd{$expression}; ?>";
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
public function connect($redisIP = null, $redisPort = null, $redisTimeOut = null)
|
||||
{
|
||||
if ($this->redisConnected) {
|
||||
return true;
|
||||
}
|
||||
if (!\class_exists('Redis')) {
|
||||
return false; // it requires redis.
|
||||
}
|
||||
if ($redisIP !== null) {
|
||||
$this->redisIP = $redisIP;
|
||||
$this->redisPort = $redisPort;
|
||||
$this->redisTimeOut = $redisTimeOut;
|
||||
}
|
||||
$this->redis = new Redis();
|
||||
// 2.5 sec timeout.
|
||||
$this->redisConnected = $this->redis->connect($this->redisIP, $this->redisPort, $this->redisTimeOut);
|
||||
|
||||
return $this->redisConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the cache expired (or doesn't exist), otherwise false.
|
||||
*
|
||||
* @param string $templateName name of the template to use (such hello for template hello.blade.php)
|
||||
* @param string $id (id of cache, optional, if not id then it adds automatically a number)
|
||||
* @param int $scope scope of the cache.
|
||||
* @param int $cacheDuration (duration of the cache in seconds)
|
||||
* @return bool (return if the cache expired)
|
||||
*/
|
||||
public function cacheExpired($templateName, $id, $scope, $cacheDuration)
|
||||
{
|
||||
if ($this->getMode() & 1) {
|
||||
return true; // forced mode, hence it always expires. (fast mode is ignored).
|
||||
}
|
||||
$compiledFile = $this->getCompiledFile($templateName) . '_cache' . $id;
|
||||
if (isset($this->cacheExpired[$compiledFile])) {
|
||||
// if the information is already in the array then returns it.
|
||||
return $this->cacheExpired[$compiledFile];
|
||||
}
|
||||
$date = @\filemtime($compiledFile);
|
||||
if ($date) {
|
||||
if ($date + $cacheDuration < \time()) {
|
||||
$this->cacheExpired[$compiledFile] = true;
|
||||
return true; // time-out.
|
||||
}
|
||||
} else {
|
||||
$this->cacheExpired[$compiledFile] = true;
|
||||
return true; // no file
|
||||
}
|
||||
$this->cacheExpired[$compiledFile] = false;
|
||||
return false; // cache active.
|
||||
}
|
||||
|
||||
public function cacheStart($id = "", $cacheDuration = 86400)
|
||||
{
|
||||
$this->curCacheId = ($id == "") ? ($this->curCacheId + 1) : $id;
|
||||
$this->curCacheDuration = $cacheDuration;
|
||||
$this->curCachePosition = \strlen(\ob_get_contents());
|
||||
$compiledFile = $this->getCompiledFile() . '_cache' . $this->curCacheId;
|
||||
if ($this->cacheExpired('', $id, $cacheDuration)) {
|
||||
$this->cacheRunning = false;
|
||||
} else {
|
||||
$this->cacheRunning = true;
|
||||
$content = $this->getFile($compiledFile);
|
||||
echo $content;
|
||||
}
|
||||
// getFile($fileName)
|
||||
}
|
||||
|
||||
public function cacheEnd()
|
||||
{
|
||||
if (!$this->cacheRunning) {
|
||||
$txt = \substr(\ob_get_contents(), $this->curCachePosition);
|
||||
$compiledFile = $this->getCompiledFile() . '_cache' . $this->curCacheId;
|
||||
\file_put_contents($compiledFile, $txt);
|
||||
}
|
||||
$this->cacheRunning = false;
|
||||
}
|
||||
|
||||
private function keyByScope($scope)
|
||||
{
|
||||
$key = '';
|
||||
if ($scope && CACHEREDIS_SCOPEURL) {
|
||||
$key .= $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php /** @noinspection PhpUnused */
|
||||
|
||||
namespace eftec\bladeone;
|
||||
|
||||
/*
|
||||
* Its an example of a custom set of functions for bladeone.
|
||||
* in examples/TestCustom.php there is a working example
|
||||
*/
|
||||
|
||||
use function array_pop;
|
||||
|
||||
trait BladeOneCustom
|
||||
{
|
||||
private $customItem = []; // indicates the type of the current tag. such as select/selectgroup/etc.
|
||||
|
||||
//<editor-fold desc="compile function">
|
||||
/**
|
||||
* Usage @panel('title',true,true).....@endpanel()
|
||||
*
|
||||
* @param $expression
|
||||
* @return string
|
||||
*/
|
||||
protected function compilePanel($expression)
|
||||
{
|
||||
$this->customItem[] = 'Panel';
|
||||
return $this->phpTag . "echo \$this->panel{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileEndPanel()
|
||||
{
|
||||
$r = @array_pop($this->customItem);
|
||||
if ($r === null) {
|
||||
$this->showError('@endpanel', 'Missing @compilepanel or so many @compilepanel', true);
|
||||
}
|
||||
return ' </div></section><!-- end panel -->'; // we don't need to create a function for this.
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
|
||||
//<editor-fold desc="used function">
|
||||
protected function panel($title = '', $toggle = true, $dismiss = true)
|
||||
{
|
||||
return "<section class='panel'>
|
||||
<header class='panel-heading'>
|
||||
<div class='panel-actions'>
|
||||
" . (($toggle) ? "<a href='#' class='panel-action panel-action-toggle' data-panel-toggle></a>" : '') . '
|
||||
' . (($dismiss) ? "<a href='#' class='panel-action panel-action-dismiss' data-panel-dismiss></a>" : '') . "
|
||||
</div>
|
||||
|
||||
<h2 class='panel-title'>$title</h2>
|
||||
</header>
|
||||
<div class='panel-body'>";
|
||||
}
|
||||
//</editor-fold>
|
||||
}
|
|
@ -0,0 +1,551 @@
|
|||
<?php /** @noinspection HtmlUnknownTarget */
|
||||
/** @noinspection HtmlUnknownAttribute */
|
||||
/** @noinspection PhpFullyQualifiedNameUsageInspection */
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
|
||||
namespace eftec\bladeone;
|
||||
|
||||
/**
|
||||
* trait BladeOneHtml
|
||||
* Copyright (c) 2016 Jorge Patricio Castro Castillo MIT License. Don't delete this comment, its part of the license.
|
||||
* Extends the tags of the class BladeOne. Its optional
|
||||
* It adds the next tags
|
||||
* <code>
|
||||
* select:
|
||||
* @ select('idCountry','value',[,$extra])
|
||||
* @ item('0','--select a country'[,$extra])
|
||||
* @ items($countries,'id','name',$currentCountry[,$extra])
|
||||
* @ endselect()
|
||||
* input:
|
||||
* @ input('iduser',$currentUser,'text'[,$extra])
|
||||
* button:
|
||||
* @ commandbutton('idbutton','value','text'[,$extra])
|
||||
*
|
||||
* </code>
|
||||
* Note. The names of the tags are based in Java Server Faces (JSF)
|
||||
*
|
||||
* @package BladeOneHtml
|
||||
* @version 1.9.2 2020-05-28 (1)
|
||||
* @link https://github.com/EFTEC/BladeOne
|
||||
* @author Jorge Patricio Castro Castillo <jcastro arroba eftec dot cl>
|
||||
* @deprecated use https://github.com/eftec/BladeOneHtml
|
||||
*/
|
||||
trait BladeOneHtml
|
||||
{
|
||||
protected $htmlItem = []; // indicates the type of the current tag. such as select/selectgroup/etc.
|
||||
protected $htmlCurrentId = []; //indicates the id of the current tag.
|
||||
|
||||
//<editor-fold desc="compile function">
|
||||
protected function compileSelect($expression)
|
||||
{
|
||||
$this->htmlItem[] = 'select';
|
||||
return $this->phpTag . "echo \$this->select{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileListBoxes($expression)
|
||||
{
|
||||
return $this->phpTag . "echo \$this->listboxes{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileLink($expression)
|
||||
{
|
||||
return $this->phpTag . "echo \$this->link{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileSelectGroup($expression)
|
||||
{
|
||||
$this->htmlItem[] = 'selectgroup';
|
||||
$this->compilePush('');
|
||||
return $this->phpTag . "echo \$this->select{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileRadio($expression)
|
||||
{
|
||||
$this->htmlItem[] = 'radio';
|
||||
return $this->phpTag . "echo \$this->radio{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileCheckbox($expression)
|
||||
{
|
||||
$this->htmlItem[] = 'checkbox';
|
||||
return $this->phpTag . "echo \$this->checkbox{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileEndSelect()
|
||||
{
|
||||
$r = @\array_pop($this->htmlItem);
|
||||
if (\is_null($r)) {
|
||||
$this->showError("@endselect", "Missing @select or so many @endselect", true);
|
||||
}
|
||||
return $this->phpTag . "echo '</select>'; ?>";
|
||||
}
|
||||
|
||||
protected function compileEndRadio()
|
||||
{
|
||||
$r = @\array_pop($this->htmlItem);
|
||||
if (\is_null($r)) {
|
||||
return $this->showError("@EndRadio", "Missing @Radio or so many @EndRadio", true);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function compileEndCheckbox()
|
||||
{
|
||||
$r = @\array_pop($this->htmlItem);
|
||||
if (\is_null($r)) {
|
||||
return $this->showError("@EndCheckbox", "Missing @Checkbox or so many @EndCheckbox", true);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function compileItem($expression)
|
||||
{
|
||||
// we add a new attribute with the type of the current open tag
|
||||
$r = \end($this->htmlItem);
|
||||
$x = \trim($expression);
|
||||
$x = "('{$r}'," . \substr($x, 1);
|
||||
return $this->phpTag . "echo \$this->item{$x}; ?>";
|
||||
}
|
||||
|
||||
protected function compileItems($expression)
|
||||
{
|
||||
// we add a new attribute with the type of the current open tag
|
||||
$r = \end($this->htmlItem);
|
||||
$x = \trim($expression);
|
||||
$x = "('{$r}'," . \substr($x, 1);
|
||||
return $this->phpTag . "echo \$this->items{$x}; ?>";
|
||||
}
|
||||
|
||||
protected function compileTrio($expression)
|
||||
{
|
||||
// we add a new attribute with the type of the current open tag
|
||||
$r = \end($this->htmlItem);
|
||||
$x = \trim($expression);
|
||||
$x = "('{$r}'," . \substr($x, 1);
|
||||
return $this->phpTag . "echo \$this->trio{$x}; ?>";
|
||||
}
|
||||
|
||||
protected function compileTrios($expression)
|
||||
{
|
||||
// we add a new attribute with the type of the current open tag
|
||||
$r = \end($this->htmlItem);
|
||||
$x = \trim($expression);
|
||||
$x = "('{$r}'," . \substr($x, 1);
|
||||
return $this->phpTag . "echo \$this->trios{$x}; ?>";
|
||||
}
|
||||
|
||||
protected function compileInput($expression)
|
||||
{
|
||||
return $this->phpTag . "echo \$this->input{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileFile($expression)
|
||||
{
|
||||
return $this->phpTag . "echo \$this->file{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileImage($expression)
|
||||
{
|
||||
return $this->phpTag . "echo \$this->image{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileTextArea($expression)
|
||||
{
|
||||
return $this->phpTag . "echo \$this->textArea{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileHidden($expression)
|
||||
{
|
||||
return $this->phpTag . "echo \$this->hidden{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileLabel($expression)
|
||||
{
|
||||
return $this->phpTag . "// {$expression} \n echo \$this->label{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileCommandButton($expression)
|
||||
{
|
||||
return $this->phpTag . "echo \$this->commandButton{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileForm($expression)
|
||||
{
|
||||
return $this->phpTag . "echo \$this->form{$expression}; ?>";
|
||||
}
|
||||
|
||||
protected function compileEndForm()
|
||||
{
|
||||
return $this->phpTag . "echo '</form>'; ?>";
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
//<editor-fold desc="used function">
|
||||
public function select($name, $value, $extra = '')
|
||||
{
|
||||
if (\strpos($extra, 'readonly') === false) {
|
||||
return "<select id='" . static::e($name) . "' name='" . static::e($name) . "' {$this->convertArg($extra)}>\n";
|
||||
}
|
||||
|
||||
return "
|
||||
<input id='" . static::e($name) . "' name='" . static::e($name) . "' type='hidden' value='" . static::e($value) . "' />
|
||||
<select id='" . static::e($name) . "_disable' name='" . static::e($name) . "_disable' disabled {$this->convertArg($extra)}>\n";
|
||||
}
|
||||
|
||||
public function link($url, $label, $extra = '')
|
||||
{
|
||||
return "<a href='{$url}' {$this->convertArg($extra)}>{$label}</a>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an element in a array of arrays
|
||||
* If the element doesn't exist in the array then it returns false, otherwise returns true
|
||||
*
|
||||
* @param string $find
|
||||
* @param array $array array of primitives or objects
|
||||
* @param string $field field to search
|
||||
* @return bool
|
||||
*/
|
||||
private function listboxesFindArray($find, $array, $field)
|
||||
{
|
||||
if (\count($array) == 0) {
|
||||
return false;
|
||||
}
|
||||
if (!\is_array($array[0])) {
|
||||
return \in_array($find, $array);
|
||||
}
|
||||
|
||||
foreach ($array as $elem) {
|
||||
if ($elem[$field] == $find) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function listboxes($name, $allvalues, $fieldId, $fieldText, $selectedId, $extra = '')
|
||||
{
|
||||
$html = "";
|
||||
$html .= "<table>\n";
|
||||
$html .= " <tr>\n";
|
||||
$html .= " <td>\n";
|
||||
$html .= " <select id='{$name}_noselected' size='6' multiple='multiple' $extra>\n";
|
||||
if (\count($allvalues) == 0) {
|
||||
$allvalues = [];
|
||||
}
|
||||
$html2 = "";
|
||||
foreach ($allvalues as $v) {
|
||||
if (\is_object($v)) {
|
||||
$v = (array)$v;
|
||||
}
|
||||
if (!$this->listboxesFindArray($v[$fieldId], $selectedId, $fieldId)) {
|
||||
$html .= "<option value='" . $v[$fieldId] . "'>" . $v[$fieldText] . "</option>\n";
|
||||
} else {
|
||||
$html2 .= "<option value='" . $v[$fieldId] . "'>" . $v[$fieldText] . "</option>\n";
|
||||
}
|
||||
}
|
||||
$html .= " </select>\n";
|
||||
$html .= " </td>\n";
|
||||
$html .= " <td style='text-align:center;'>\n";
|
||||
$html .= " <input type='button' value='>' id='{$name}_add'/><br>\n";
|
||||
$html .= " <input type='button' value='>>' id='{$name}_addall'/><br>\n";
|
||||
$html .= " <input type='button' value='<' id='{$name}_delete'/><br>\n";
|
||||
$html .= " <input type='button' value='<<' id='{$name}_deleteall'/><br>\n";
|
||||
$html .= " </td>\n";
|
||||
$html .= " <td>\n";
|
||||
$html .= " <select id='{$name}' name='{$name}' size='6' multiple='multiple'>\n";
|
||||
$html .= $html2;
|
||||
$html .= " </select>\n";
|
||||
$html .= " </td>\n";
|
||||
$html .= " </tr>\n";
|
||||
$html .= "</table>\n";
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function selectGroup($name, $extra = '')
|
||||
{
|
||||
return $this->selectGroup($name, $extra);
|
||||
}
|
||||
|
||||
public function radio($id, $value = '', $text = '', $valueSelected = '', $extra = '')
|
||||
{
|
||||
$num = \func_num_args();
|
||||
if ($num > 2) {
|
||||
if ($value == $valueSelected) {
|
||||
if (\is_array($extra)) {
|
||||
$extra['checked'] = 'checked';
|
||||
} else {
|
||||
$extra .= ' checked="checked"';
|
||||
}
|
||||
}
|
||||
return $this->input($id, $value, 'radio', $extra) . ' ' . $text;
|
||||
}
|
||||
|
||||
$this->htmlCurrentId[] = $id;
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
* @param string $value
|
||||
* @param string $text
|
||||
* @param string|null $valueSelected
|
||||
* @param string|array $extra
|
||||
* @return string
|
||||
*/
|
||||
public function checkbox($id, $value = '', $text = '', $valueSelected = '', $extra = '')
|
||||
{
|
||||
$num = \func_num_args();
|
||||
if ($num > 2) {
|
||||
if ($value == $valueSelected) {
|
||||
if (\is_array($extra)) {
|
||||
$extra['checked'] = 'checked';
|
||||
} else {
|
||||
$extra .= ' checked="checked"';
|
||||
}
|
||||
}
|
||||
return $this->input($id, $value, 'checkbox', $extra) . ' ' . $text;
|
||||
}
|
||||
|
||||
$this->htmlCurrentId[] = $id;
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type type of the current open tag
|
||||
* @param array|string $valueId if is an array then the first value is used as value, the second is used as
|
||||
* extra
|
||||
* @param $valueText
|
||||
* @param array|string $selectedItem Item selected (optional)
|
||||
* @param string $wrapper Wrapper of the element. For example, <li>%s</li>
|
||||
* @param string $extra
|
||||
* @return string
|
||||
* @internal param string $fieldId Field of the id
|
||||
* @internal param string $fieldText Field of the value visible
|
||||
*/
|
||||
public function item($type, $valueId, $valueText, $selectedItem = '', $wrapper = '', $extra = '')
|
||||
{
|
||||
$id = @\end($this->htmlCurrentId);
|
||||
$wrapper = ($wrapper == '') ? '%s' : $wrapper;
|
||||
if (\is_array($selectedItem)) {
|
||||
$found = \in_array($valueId, $selectedItem);
|
||||
} else {
|
||||
$found = $valueId == $selectedItem;
|
||||
}
|
||||
|
||||
$valueHtml = (!\is_array($valueId)) ? "value='{$valueId}'" : "value='{$valueId[0]}' data='{$valueId[1]}'";
|
||||
switch ($type) {
|
||||
case 'select':
|
||||
$selected = ($found) ? 'selected' : '';
|
||||
return \sprintf($wrapper, "<option $valueHtml $selected " .
|
||||
$this->convertArg($extra) . ">{$valueText}</option>\n");
|
||||
break;
|
||||
case 'radio':
|
||||
$selected = ($found) ? 'checked' : '';
|
||||
return \sprintf($wrapper, "<input type='radio' id='" . static::e($id)
|
||||
. "' name='" . static::e($id) . "' $valueHtml $selected "
|
||||
. $this->convertArg($extra) . "> {$valueText}\n");
|
||||
break;
|
||||
case 'checkbox':
|
||||
$selected = ($found) ? 'checked' : '';
|
||||
return \sprintf($wrapper, "<input type='checkbox' id='" . static::e($id)
|
||||
. "' name='" . static::e($id) . "' $valueHtml $selected "
|
||||
. $this->convertArg($extra) . "> {$valueText}\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
return '???? type undefined: [$type] on @item<br>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type type of the current open tag
|
||||
* @param array $arrValues Array of objects/arrays to show.
|
||||
* @param string $fieldId Field of the id (for arrValues)
|
||||
* @param string $fieldText Field of the id of selectedItem
|
||||
* @param array|string $selectedItem Item selected (optional)
|
||||
* @param string $selectedFieldId field of the selected item.
|
||||
* @param string $wrapper Wrapper of the element. For example, <li>%s</li>
|
||||
* @param string $extra (optional) is used for add additional information for the html object (such
|
||||
* as class)
|
||||
* @return string
|
||||
* @version 1.1 2017
|
||||
*/
|
||||
public function items(
|
||||
$type,
|
||||
$arrValues,
|
||||
$fieldId,
|
||||
$fieldText,
|
||||
$selectedItem = '',
|
||||
$selectedFieldId = '',
|
||||
$wrapper = '',
|
||||
$extra = ''
|
||||
) {
|
||||
if (\count($arrValues) == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (\is_object(@$arrValues[0])) {
|
||||
$arrValues = (array)$arrValues;
|
||||
}
|
||||
if (\is_array($selectedItem)) {
|
||||
if (\is_object(@$selectedItem[0])) {
|
||||
$primitiveArray = [];
|
||||
foreach ($selectedItem as $v) {
|
||||
$primitiveArray[] = $v->{$selectedFieldId};
|
||||
}
|
||||
$selectedItem = $primitiveArray;
|
||||
}
|
||||
}
|
||||
$result = '';
|
||||
if (\is_object($selectedItem)) {
|
||||
$selectedItem = (array)$selectedItem;
|
||||
}
|
||||
foreach ($arrValues as $v) {
|
||||
if (\is_object($v)) {
|
||||
$v = (array)$v;
|
||||
}
|
||||
$result .= $this->item($type, $v[$fieldId], $v[$fieldText], $selectedItem, $wrapper, $extra);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type type of the current open tag
|
||||
* @param string $valueId value of the trio
|
||||
* @param string $valueText visible value of the trio.
|
||||
* @param string $value3 extra third value for select value or visual
|
||||
* @param array|string $selectedItem Item selected (optional)
|
||||
* @param string $wrapper Wrapper of the element. For example, <li>%s</li>
|
||||
* @param string $extra
|
||||
* @return string
|
||||
* @internal param string $fieldId Field of the id
|
||||
* @internal param string $fieldText Field of the value visible
|
||||
*/
|
||||
public function trio($type, $valueId, $valueText, $value3 = '', $selectedItem = '', $wrapper = '', $extra = '')
|
||||
{
|
||||
$id = @\end($this->htmlCurrentId);
|
||||
$wrapper = ($wrapper == '') ? '%s' : $wrapper;
|
||||
if (\is_array($selectedItem)) {
|
||||
$found = \in_array($valueId, $selectedItem);
|
||||
} else {
|
||||
$found = $valueId == $selectedItem;
|
||||
}
|
||||
switch ($type) {
|
||||
case 'selectgroup':
|
||||
$selected = ($found) ? 'selected' : '';
|
||||
return \sprintf($wrapper, "<option value='{$valueId}' $selected " .
|
||||
$this->convertArg($extra) . ">{$valueText}</option>\n");
|
||||
break;
|
||||
default:
|
||||
return '???? type undefined: [$type] on @item<br>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type type of the current open tag
|
||||
* @param array $arrValues Array of objects/arrays to show.
|
||||
* @param string $fieldId Field of the id
|
||||
* @param string $fieldText Field of the value visible
|
||||
* @param string $fieldThird
|
||||
* @param array|string $selectedItem Item selected (optional)
|
||||
* @param string $wrapper Wrapper of the element. For example, <li>%s</li>
|
||||
* @param string $extra (optional) is used for add additional information for the html object (such as
|
||||
* class)
|
||||
* @return string
|
||||
* @version 1.0
|
||||
*/
|
||||
public function trios(
|
||||
$type,
|
||||
$arrValues,
|
||||
$fieldId,
|
||||
$fieldText,
|
||||
$fieldThird,
|
||||
$selectedItem = '',
|
||||
$wrapper = '',
|
||||
$extra = ''
|
||||
) {
|
||||
if (\count($arrValues) === 0) {
|
||||
return "";
|
||||
}
|
||||
if (\is_object($arrValues[0])) {
|
||||
$arrValues = (array)$arrValues;
|
||||
}
|
||||
$result = '';
|
||||
$oldV3 = "";
|
||||
foreach ($arrValues as $v) {
|
||||
if (\is_object($v)) {
|
||||
$v = (array)$v;
|
||||
}
|
||||
$v3 = $v[$fieldThird];
|
||||
if ($type === 'selectgroup') {
|
||||
if ($v3 != $oldV3) {
|
||||
if ($oldV3 != "") {
|
||||
$result .= "</optgroup>";
|
||||
}
|
||||
$oldV3 = $v3;
|
||||
$result .= "<optgroup label='{$v3}'>";
|
||||
}
|
||||
}
|
||||
if ($result) {
|
||||
$result .= $this->trio($type, $v[$fieldId], $v[$fieldText], $v3, $selectedItem, $wrapper, $extra);
|
||||
}
|
||||
}
|
||||
if ($type === 'selectgroup' && $oldV3 != "") {
|
||||
$result .= "</optgroup>";
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
protected $paginationStructure=['selHtml'=>'<li class="selected" %3s><a href="%1s">%2s</a></li>'
|
||||
,'html'=>'<li %3s><a href="%1s">%2s</a></li>'
|
||||
,'maxItem'=>5
|
||||
,'url'=>''];
|
||||
public function pagination($id, $curPage, $maxPage, $baseUrl, $extra='')
|
||||
{
|
||||
$r="<ul $extra>";
|
||||
|
||||
$r.="</ul>";
|
||||
return $r;
|
||||
}
|
||||
|
||||
public function input($id, $value = '', $type = 'text', $extra = '')
|
||||
{
|
||||
return "<input id='" . static::e($id) . "' name='" . static::e($id) . "' type='" . $type . "' " . $this->convertArg($extra) . " value='" . static::e($value) . "' />\n";
|
||||
}
|
||||
|
||||
public function file($id, $fullfilepath = '', $file = '', $extra = '')
|
||||
{
|
||||
return "<a href='$fullfilepath'>$file</a>
|
||||
<input id='" . static::e($id) . "_file' name='" . static::e($id) . "_file' type='hidden' value='" . static::e($file) . "' />
|
||||
<input id='" . static::e($id) . "' name='" . static::e($id) . "' type='file' " . $this->convertArg($extra) . " value='" . static::e($fullfilepath) . "' />\n";
|
||||
}
|
||||
|
||||
public function textArea($id, $value = '', $extra = '')
|
||||
{
|
||||
$value = \str_replace('\n', "\n", $value);
|
||||
return "<textarea id='" . static::e($id) . "' name='" . static::e($id) . "' " . $this->convertArg($extra) . " >$value</textarea>\n";
|
||||
}
|
||||
|
||||
public function hidden($id, $value = '', $extra = '')
|
||||
{
|
||||
return $this->input($id, $value, 'hidden', $extra);
|
||||
}
|
||||
|
||||
public function label($id, $value = '', $extra = '')
|
||||
{
|
||||
return "<label for='{$id}' {$this->convertArg($extra)}>{$value}</label>";
|
||||
}
|
||||
|
||||
public function commandButton($id, $value = '', $text = 'Button', $type = 'submit', $extra = '')
|
||||
{
|
||||
return "<button type='{$type}' id='" . static::e($id) . "' name='" . static::e($id) . "' value='" . static::e($value) . "' {$this->convertArg($extra)}>{$text}</button>\n";
|
||||
}
|
||||
|
||||
public function form($action, $method = 'post', $extra = '')
|
||||
{
|
||||
return "<form action='{$action}' method='{$method}' {$this->convertArg($extra)}>";
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
<?php
|
||||
|
||||
namespace eftec\bladeone;
|
||||
|
||||
/**
|
||||
* trait BladeOneHtmlBootstrap
|
||||
* Copyright (c) 2016 Jorge Patricio Castro Castillo MIT License. Don't delete this comment, its part of the license.
|
||||
* Extends the tags of the class BladeOne. Its optional
|
||||
* It adds the next tags
|
||||
* <code>
|
||||
* select:
|
||||
* @ select('idCountry','value',[,$extra])
|
||||
* @ item('0','--select a country'[,$extra])
|
||||
* @ items($countries,'id','name',$currentCountry[,$extra])
|
||||
* @ endselect()
|
||||
* input:
|
||||
* @ input('iduser',$currentUser,'text'[,$extra])
|
||||
* button:
|
||||
* @ commandbutton('idbutton','value','text'[,$extra])
|
||||
*
|
||||
* </code>
|
||||
* Note. The names of the tags are based in Java Server Faces (JSF)
|
||||
*
|
||||
* @package BladeOneHtmlBootstrap
|
||||
* @version 1.9.1 2018-06-11 (1)
|
||||
* @link https://github.com/EFTEC/BladeOne
|
||||
* @author Jorge Patricio Castro Castillo <jcastro arroba eftec dot cl>
|
||||
* @deprecated use https://github.com/eftec/BladeOneHtml
|
||||
*/
|
||||
trait BladeOneHtmlBootstrap
|
||||
{
|
||||
use BladeOneHtml {
|
||||
BladeOneHtml::select as selectParent;
|
||||
BladeOneHtml::input as inputParent;
|
||||
BladeOneHtml::commandButton as commandButtonParent;
|
||||
BladeOneHtml::textArea as textAreaParent;
|
||||
BladeOneHtml::item as itemParent;
|
||||
BladeOneHtml::checkbox as checkboxParent;
|
||||
BladeOneHtml::compileEndCheckbox as compileEndCheckboxParent;
|
||||
BladeOneHtml::radio as radioParent;
|
||||
BladeOneHtml::compileEndRadio as compileEndRadioParent;
|
||||
}
|
||||
|
||||
//<editor-fold desc="Override methods">
|
||||
public function select($name, $value, $extra = '')
|
||||
{
|
||||
$extra = $this->addClass($extra, 'form-control');
|
||||
return $this->selectParent($name, $value, $extra);
|
||||
}
|
||||
|
||||
public function input($id, $value = '', $type = 'text', $extra = '')
|
||||
{
|
||||
$extra = $this->addClass($extra, 'form-control');
|
||||
return $this->inputParent($id, $value, $type, $extra);
|
||||
}
|
||||
|
||||
public function commandButton($id, $value = '', $text = 'Button', $type = 'submit', $extra = '')
|
||||
{
|
||||
$extra = $this->addClass($extra, 'btn');
|
||||
return $this->commandButtonParent($id, $value, $text, $type, $extra);
|
||||
}
|
||||
|
||||
public function textArea($id, $value = '', $extra = '')
|
||||
{
|
||||
$extra = $this->addClass($extra, 'form-control');
|
||||
return $this->textAreaParent($id, $value, $extra);
|
||||
}
|
||||
|
||||
|
||||
public function file($id, $fullfilepath = '', $file = '', $extra = '')
|
||||
{
|
||||
return "<input id='" . static::e($id) . "_file' name='" . static::e($id) . "_file' type='hidden' value='" . static::e($file) . "' />
|
||||
<div class='input-group'>
|
||||
<label class='input-group-btn'>
|
||||
<span class='btn btn-primary'>
|
||||
Browse…<a href='$fullfilepath' class='afile' ><i class='fa fa-paperclip'></i></a>
|
||||
<input type='file' style='display: none;' id='" . static::e($id) . "' name='" . static::e($id) . "' $extra />
|
||||
</span>
|
||||
</label>
|
||||
<input type='text' class='form-control' readonly></div>";
|
||||
// return "<a href='$fullfilepath'>$file</a>
|
||||
//<input id='".static::e($id)."_file' name='".static::e($id)."_file' type='hidden' value='".static::e($file)."' />
|
||||
// <input id='".static::e($id)."' name='".static::e($id)."' type='file' ".$this->convertArg($extra)." value='".static::e($fullfilepath)."' />\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id of the field
|
||||
* @param string $fullfilepath full file path of the image
|
||||
* @param string $file filename of the file
|
||||
* @param string $extra extra field of the input file
|
||||
* @return string html
|
||||
*/
|
||||
public function image($id, $fullfilepath = '', $file = '', $extra = '')
|
||||
{
|
||||
return "<input id='" . static::e($id) . "_file' name='" . static::e($id) . "_file' type='hidden' value='" . static::e($file) . "' />
|
||||
<img src='$fullfilepath' class='img-thumbnail' />
|
||||
<div class='input-group'>
|
||||
|
||||
<label class='input-group-btn'>
|
||||
<span class='btn btn-primary'>
|
||||
Browse…
|
||||
<input type='file' style='display: none;' id='" . static::e($id) . "' name='" . static::e($id) . "' $extra />
|
||||
</span>
|
||||
</label>
|
||||
<input type='text' class='form-control' readonly></div>";
|
||||
// return "<a href='$fullfilepath'>$file</a>
|
||||
//<input id='".static::e($id)."_file' name='".static::e($id)."_file' type='hidden' value='".static::e($file)."' />
|
||||
// <input id='".static::e($id)."' name='".static::e($id)."' type='file' ".$this->convertArg($extra)." value='".static::e($fullfilepath)."' />\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type type of the current open tag
|
||||
* @param array|string $valueId if is an array then the first value is used as value, the second is used as
|
||||
* extra
|
||||
* @param $valueText
|
||||
* @param array|string $selectedItem Item selected (optional)
|
||||
* @param string $wrapper Wrapper of the element. For example, <li>%s</li>
|
||||
* @param string $extra
|
||||
* @return string
|
||||
* @internal param string $fieldId Field of the id
|
||||
* @internal param string $fieldText Field of the value visible
|
||||
*/
|
||||
public function item($type, $valueId, $valueText, $selectedItem = '', $wrapper = '', $extra = '')
|
||||
{
|
||||
$id = @\end($this->htmlCurrentId);
|
||||
$wrapper = ($wrapper == '') ? '%s' : $wrapper;
|
||||
|
||||
if (\is_array($selectedItem)) {
|
||||
$found = \in_array($valueId, $selectedItem);
|
||||
} else {
|
||||
if (\is_null($selectedItem)) {
|
||||
// diferentiate null = '' != 0
|
||||
$found = $valueId === '' || $valueId === null;
|
||||
} else {
|
||||
$found = $selectedItem == $valueId;
|
||||
}
|
||||
}
|
||||
$valueHtml = (!\is_array($valueId)) ? "value='{$valueId}'" : "value='{$valueId[0]}' data='{$valueId[1]}'";
|
||||
switch ($type) {
|
||||
case 'select':
|
||||
$selected = ($found) ? 'selected' : '';
|
||||
return \sprintf($wrapper, "<option $valueHtml $selected " .
|
||||
$this->convertArg($extra) . ">{$valueText}</option>\n");
|
||||
break;
|
||||
case 'radio':
|
||||
$selected = ($found) ? 'checked' : '';
|
||||
return \sprintf($wrapper, "<label><input type='radio' id='" . static::e($id)
|
||||
. "' name='" . static::e($id) . "' $valueHtml $selected "
|
||||
. $this->convertArg($extra) . "> {$valueText}</label>\n");
|
||||
break;
|
||||
case 'checkbox':
|
||||
$selected = ($found) ? 'checked' : '';
|
||||
return \sprintf($wrapper, "<label><input type='checkbox' id='" . static::e($id)
|
||||
. "' name='" . static::e($id) . "' $valueHtml $selected "
|
||||
. $this->convertArg($extra) . "> {$valueText}</label>\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
return '???? type undefined: [$type] on @item<br>';
|
||||
}
|
||||
}
|
||||
|
||||
public function checkbox($id, $value = '', $text = '', $valueSelected = '', $extra = '')
|
||||
{
|
||||
$num = \func_num_args();
|
||||
if ($num > 2) {
|
||||
if ($value == $valueSelected) {
|
||||
if (\is_array($extra)) {
|
||||
$extra['checked'] = 'checked';
|
||||
} else {
|
||||
$extra .= ' checked="checked"';
|
||||
}
|
||||
}
|
||||
//return '<div class="checkbox"><label>'.$this->inputParent($id, $value, 'checkbox', $extra) . ' ' . $text.'</label></div>';
|
||||
return '<div><label>' . $this->inputParent($id, $value, 'checkbox', $extra) . ' ' . $text . '</label></div>';
|
||||
} else {
|
||||
\array_push($this->htmlCurrentId, $id);
|
||||
return '<div>';
|
||||
//return '<div class="checkbox">';
|
||||
}
|
||||
}
|
||||
|
||||
public function radio($id, $value = '', $text = '', $valueSelected = '', $extra = '')
|
||||
{
|
||||
$num = \func_num_args();
|
||||
if ($num > 2) {
|
||||
if ($value == $valueSelected) {
|
||||
if (\is_array($extra)) {
|
||||
$extra['checked'] = 'checked';
|
||||
} else {
|
||||
$extra .= ' checked="checked"';
|
||||
}
|
||||
}
|
||||
return '<div class="radio"><label>' . $this->inputParent($id, $value, 'radio', $extra) . ' ' . $text . '</label></div>';
|
||||
} else {
|
||||
\array_push($this->htmlCurrentId, $id);
|
||||
return '<div class="radio">';
|
||||
}
|
||||
}
|
||||
|
||||
public function compileEndCheckbox()
|
||||
{
|
||||
$r = $this->compileEndCheckboxParent();
|
||||
$r .= '</div>';
|
||||
return $r;
|
||||
}
|
||||
|
||||
public function compileEndRadio()
|
||||
{
|
||||
$r = $this->compileEndRadioParent();
|
||||
$r .= '</div>';
|
||||
return $r;
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
//<editor-fold desc="Misc members">
|
||||
|
||||
/**
|
||||
* It adds a class to a html tag parameter
|
||||
*
|
||||
* @example addClass('type="text" class="btn","btn-standard")
|
||||
* @param string|array $txt
|
||||
* @param string $newclass The class(es) to add, example "class1" or "class1 class"
|
||||
* @return string|array
|
||||
*/
|
||||
protected function addClass($txt, $newclass)
|
||||
{
|
||||
if (\is_array($txt)) {
|
||||
$txt = \array_change_key_case($txt);
|
||||
@$txt['class'] = ' ' . $newclass;
|
||||
return $txt;
|
||||
}
|
||||
$p0 = \stripos(' ' . $txt, ' class');
|
||||
if ($p0 === false) {
|
||||
// if the content of the tag doesn't contain a class then it adds one.
|
||||
return $txt . ' class="' . $newclass . '"';
|
||||
}
|
||||
// the class tag exists so we found the closes character ' or " and we add the class (or classes) inside it
|
||||
// may be it could duplicates the tag.
|
||||
$p1 = \strpos($txt, "'", $p0);
|
||||
$p2 = \strpos($txt, '"', $p0);
|
||||
$p1 = ($p1 === false) ? 99999 : $p1;
|
||||
$p2 = ($p2 === false) ? 99999 : $p2;
|
||||
|
||||
if ($p1 < $p2) {
|
||||
return \substr_replace($txt, $newclass . ' ', $p1 + 1, 0);
|
||||
} else {
|
||||
echo $p2 . "#";
|
||||
return \substr_replace($txt, $newclass . ' ', $p2 + 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected function separatesParam($txt)
|
||||
{
|
||||
$result = [];
|
||||
\preg_match_all("~\"[^\"]++\"|'[^']++'|\([^)]++\)|[^,]++~", $txt, $result);
|
||||
return $result;
|
||||
}
|
||||
//</editor-fold>
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
namespace eftec\bladeone;
|
||||
|
||||
/**
|
||||
* Trait BladeOneLang
|
||||
* It adds the next tags
|
||||
* <code>
|
||||
* select:
|
||||
* @ _e('hello')
|
||||
* @ _n('Product','Products',$n)
|
||||
* @ _ef('hello %s',$user)
|
||||
* </code>
|
||||
*
|
||||
* @package eftec\bladeone
|
||||
* @version 1.1 2019-08-09
|
||||
* @link https://github.com/EFTEC/BladeOne
|
||||
* @author Jorge Patricio Castro Castillo <jcastro arroba eftec dot cl>
|
||||
* @copyright 2017 Jorge Patricio Castro Castillo MIT License. Don't delete this comment, its part of the license.
|
||||
* @deprecated Note: It is not needing anymore (BladeOne already includes the same functionalities). It is keep for compatibility purpose.
|
||||
*/
|
||||
trait BladeOneLang
|
||||
{
|
||||
/** @var string The path to the missing translations log file. If empty then every missing key is not saved. */
|
||||
public $missingLog = '';
|
||||
|
||||
/** @var array Hold dictionary of translations */
|
||||
public static $dictionary = [];
|
||||
|
||||
/**
|
||||
* Tries to translate the word if its in the array defined by BladeOneLang::$dictionary
|
||||
* If the operation fails then, it returns the original expression without translation.
|
||||
*
|
||||
* @param $phrase
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function _e($phrase)
|
||||
{
|
||||
if ((!\array_key_exists($phrase, static::$dictionary))) {
|
||||
$this->missingTranslation($phrase);
|
||||
return $phrase;
|
||||
} else {
|
||||
return static::$dictionary[$phrase];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Its the same than @_e, however it parses the text (using sprintf).
|
||||
* If the operation fails then, it returns the original expression without translation.
|
||||
*
|
||||
* @param $phrase
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function _ef($phrase)
|
||||
{
|
||||
$argv = \func_get_args();
|
||||
$r = $this->_e($phrase);
|
||||
$argv[0] = $r; // replace the first argument with the translation.
|
||||
$result = @\call_user_func_array("sprintf", $argv);
|
||||
$result = ($result === false) ? $r : $result;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* if num is more than one then it returns the phrase in plural, otherwise the phrase in singular.
|
||||
* Note: the translation should be as follow: $msg['Person']='Person' $msg=['Person']['p']='People'
|
||||
*
|
||||
* @param string $phrase
|
||||
* @param string $phrases
|
||||
* @param int $num
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function _n($phrase, $phrases, $num = 0)
|
||||
{
|
||||
if ((!\array_key_exists($phrase, static::$dictionary))) {
|
||||
$this->missingTranslation($phrase);
|
||||
return ($num <= 1) ? $phrase : $phrases;
|
||||
} else {
|
||||
return ($num <= 1) ? $this->_e($phrase) : $this->_e($phrases);
|
||||
}
|
||||
}
|
||||
|
||||
//<editor-fold desc="compile">
|
||||
|
||||
/**
|
||||
* Used for @_e directive.
|
||||
*
|
||||
* @param $expression
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function compile_e($expression)
|
||||
{
|
||||
return $this->phpTag . "echo \$this->_e{$expression}; ?>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for @_ef directive.
|
||||
*
|
||||
* @param $expression
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function compile_ef($expression)
|
||||
{
|
||||
return $this->phpTag . "echo \$this->_ef{$expression}; ?>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for @_n directive.
|
||||
*
|
||||
* @param $expression
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function compile_n($expression)
|
||||
{
|
||||
return $this->phpTag . "echo \$this->_n{$expression}; ?>";
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
|
||||
/**
|
||||
* Log a missing translation into the file $this->missingLog.<br>
|
||||
* If the file is not defined, then it doesn't write the log.
|
||||
*
|
||||
* @param string $txt Message to write on.
|
||||
*/
|
||||
private function missingTranslation($txt)
|
||||
{
|
||||
if (!$this->missingLog) {
|
||||
return; // if there is not a file assigned then it skips saving.
|
||||
}
|
||||
|
||||
$fz = @\filesize($this->missingLog);
|
||||
$mode = 'a';
|
||||
|
||||
if (\is_object($txt) || \is_array($txt)) {
|
||||
$txt = \print_r($txt, true);
|
||||
}
|
||||
|
||||
// Rewrite file if more than 100000 bytes
|
||||
if ($fz > 100000) {
|
||||
$mode = 'w';
|
||||
}
|
||||
|
||||
$fp = \fopen($this->missingLog, 'w');
|
||||
\fwrite($fp, $txt . "\n");
|
||||
\fclose($fp);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
<ruleset name="BladeOne Standards">
|
||||
|
||||
<description>PHPCS standards for EFTEC/BladeOne</description>
|
||||
|
||||
<exclude-pattern>*/docs/*</exclude-pattern>
|
||||
<exclude-pattern>*/examples/*</exclude-pattern>
|
||||
<exclude-pattern>*/tests/*</exclude-pattern>
|
||||
<exclude-pattern>*/vendor/*</exclude-pattern>
|
||||
|
||||
<arg name="colors"/>
|
||||
|
||||
<rule ref="PSR2"/>
|
||||
|
||||
<rule ref="Generic.Files.LineLength.TooLong">
|
||||
<severity>0</severity>
|
||||
</rule>
|
||||
|
||||
<!-- Exclude Camel casing for _e,_ef,_n -->
|
||||
<rule ref="PSR1.Methods.CamelCapsMethodName">
|
||||
<exclude-pattern>/lib/BladeOneLang\.php</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
</ruleset>
|
|
@ -0,0 +1,40 @@
|
|||
#Templates
|
||||
##Template Inheritance
|
||||
|
||||
Example
|
||||
|
||||
_master.blade.php_ is the layout/masterpage template :
|
||||
```html
|
||||
<h1>Title</h1>
|
||||
@section('header')
|
||||
@show
|
||||
....
|
||||
@yield('footer')
|
||||
```
|
||||
_page.blade.php_ is the template that is using the layout page:
|
||||
```html
|
||||
@extends('master')
|
||||
@section('header')
|
||||
<head>....</head>
|
||||
@endsection
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### In the master page (layout)
|
||||
|Tag|Note|status|
|
||||
|---|---|---|
|
||||
|@section('sidebar')|Start a new section|0.2b ok|
|
||||
|@show|Indicates where the content of section will be displayed|0.2 ok|
|
||||
|@yield('title')|Show here the content of a section|0.2b ok|
|
||||
|
||||
#### Using the master page (using the layout)
|
||||
|Tag|Note|status|
|
||||
|---|---|---|
|
||||
|@extends('layouts.master')|Indicates the layout to use|0.2b ok|
|
||||
|@section('title', 'Page Title')|Sends a single text to a section|0.2b ok|
|
||||
|@section('sidebar')|Start a block of code to send to a section|0.2b ok|
|
||||
|@endsection|End a block of code|0.2b ok|
|
||||
|@parent|Show the original code of the section|REMOVED(*)|
|
||||
|
||||
Note :(*) This feature is in the original documentation but its not implemented neither its required. May be its an obsolete feature.
|
|
@ -0,0 +1,203 @@
|
|||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
Previous releases are documented in [github releases](https://github.com/oscarotero/Gettext/releases)
|
||||
|
||||
## [4.8.7] - 2022-08-02
|
||||
### Fixed
|
||||
- Suppress deprecation error on PHP 8.1 [#280]
|
||||
|
||||
## [4.8.6] - 2021-10-19
|
||||
### Fixed
|
||||
- Parse PO files with multiline disabled entries [#274]
|
||||
|
||||
## [4.8.5] - 2021-07-13
|
||||
### Fixed
|
||||
- Prevent adding the same translator comment to multiple functions [#271]
|
||||
|
||||
## [4.8.4] - 2021-03-10
|
||||
### Fixed
|
||||
- PHP 8 compatibilty [#266]
|
||||
|
||||
## [4.8.3] - 2020-11-18
|
||||
### Fixed
|
||||
- Blade extractor for Laravel8/Jetstream [#261]
|
||||
|
||||
## [4.8.2] - 2019-12-02
|
||||
### Fixed
|
||||
- UTF-8 handling for VueJs extractor [#242]
|
||||
|
||||
## [4.8.1] - 2019-11-15
|
||||
### Fixed
|
||||
- Php error when scanning for a single domain but other string found [#238]
|
||||
|
||||
## [4.8.0] - 2019-11-04
|
||||
### Changed
|
||||
- Many `private` properties and methods were changed to `protected` in order to improve the extensibility [#231]
|
||||
|
||||
### Fixed
|
||||
- PHP 7.4 support [#230]
|
||||
|
||||
## [4.7.0] - 2019-10-07
|
||||
### Added
|
||||
- Support for UnitID in Xliff [#221] [#224] [#225]
|
||||
- Support for scan multiple domains at the same time [#223]
|
||||
|
||||
### Fixed
|
||||
- New lines in windows [#218] [#226]
|
||||
|
||||
## [4.6.3] - 2019-07-15
|
||||
### Added
|
||||
- Some VueJs extraction improvements and additions [#205], [#213]
|
||||
|
||||
### Fixed
|
||||
- Multiline extractions in jsCode [#200]
|
||||
- Support for js template literals [#214]
|
||||
- Fixed tabs in PHP comments [#215]
|
||||
|
||||
## [4.6.2] - 2019-01-12
|
||||
### Added
|
||||
- New option `facade` in blade extractor to use a facade instead create a blade compiler [#197], [#198]
|
||||
|
||||
### Fixed
|
||||
- Added php-7.3 to travis
|
||||
- Added VueJS extractor method docblocks for IDEs [#191]
|
||||
|
||||
## [4.6.1] - 2018-08-27
|
||||
### Fixed
|
||||
- VueJS DOM parsing [#188]
|
||||
- Javascript parser was unable to extract some functions [#187]
|
||||
|
||||
## [4.6.0] - 2018-06-26
|
||||
### Added
|
||||
- New extractor for VueJs [#178]
|
||||
|
||||
### Fixed
|
||||
- Do not include empty translations containing the headers in the translator [#182]
|
||||
- Test enhancement [#177]
|
||||
|
||||
## [4.5.0] - 2018-04-23
|
||||
### Added
|
||||
- Support for disabled translations
|
||||
|
||||
### Fixed
|
||||
- Added php-7.2 to travis
|
||||
- Fixed po tests on bigendian [#159]
|
||||
- Improved comment estraction [#166]
|
||||
- Fixed incorrect docs to dn__ function [#170]
|
||||
- Ignored phpcs.xml file on export [#168]
|
||||
- Improved `@method` docs in `Translations` [#175]
|
||||
|
||||
## [4.4.4] - 2018-02-21
|
||||
### Fixed
|
||||
- Changed the comment extraction to be compatible with gettext behaviour: the comment must be placed in the line preceding the function [#161]
|
||||
|
||||
### Security
|
||||
- Validate eval input from plural forms [#156]
|
||||
|
||||
## [4.4.3] - 2017-08-09
|
||||
### Fixed
|
||||
- Handle `NULL` arguments on extract entries in php. For example `dn__(null, 'singular', 'plural')`.
|
||||
- Fixed the `PhpCode` and `JsCode` extractors that didn't extract `dn__` and `dngettext` entries [#155].
|
||||
- Fixed the `PhpCode` and `JsCode` extractors that didn't extract `dnpgettext` correctly.
|
||||
|
||||
## [4.4.2] - 2017-07-27
|
||||
### Fixed
|
||||
- Clone the translations in `Translations::mergeWith` to prevent that the translation is referenced in both places. [#152]
|
||||
- Fixed escaped quotes in the javascript extractor [#154]
|
||||
|
||||
## [4.4.1] - 2017-05-20
|
||||
### Fixed
|
||||
- Fixed a bug where the options was not passed correctly to the merging Translations object [#147]
|
||||
- Unified the plural behaviours between PHP gettext and Translator when the plural translation is unknown [#148]
|
||||
- Removed the deprecated function `create_function()` and use `eval()` instead
|
||||
|
||||
## [4.4.0] - 2017-05-10
|
||||
### Added
|
||||
- New option `noLocation` to po generator, to omit the references [#143]
|
||||
- New options `delimiter`, `enclosure` and `escape_char` to Csv and CsvDictionary extractors and generators [#145]
|
||||
- Added the missing `dn__()` function [#146]
|
||||
|
||||
### Fixed
|
||||
- Improved the code style including php_codesniffer in development
|
||||
|
||||
## [4.3.0] - 2017-03-04
|
||||
### Added
|
||||
- Added support for named placeholders (using `strtr`). For example:
|
||||
```php
|
||||
__('Hello :name', [':name' => 'World']);
|
||||
```
|
||||
- Added support for Twig v2
|
||||
- New function `BaseTranslator::includeFunctions()` to include the functions file without register any translator
|
||||
|
||||
### Fixed
|
||||
- Fixed a bug related with the javascript source extraction with single quotes
|
||||
|
||||
[#143]: https://github.com/oscarotero/Gettext/issues/143
|
||||
[#145]: https://github.com/oscarotero/Gettext/issues/145
|
||||
[#146]: https://github.com/oscarotero/Gettext/issues/146
|
||||
[#147]: https://github.com/oscarotero/Gettext/issues/147
|
||||
[#148]: https://github.com/oscarotero/Gettext/issues/148
|
||||
[#152]: https://github.com/oscarotero/Gettext/issues/152
|
||||
[#154]: https://github.com/oscarotero/Gettext/issues/154
|
||||
[#155]: https://github.com/oscarotero/Gettext/issues/155
|
||||
[#156]: https://github.com/oscarotero/Gettext/issues/156
|
||||
[#159]: https://github.com/oscarotero/Gettext/issues/159
|
||||
[#161]: https://github.com/oscarotero/Gettext/issues/161
|
||||
[#166]: https://github.com/oscarotero/Gettext/issues/166
|
||||
[#168]: https://github.com/oscarotero/Gettext/issues/168
|
||||
[#170]: https://github.com/oscarotero/Gettext/issues/170
|
||||
[#175]: https://github.com/oscarotero/Gettext/issues/175
|
||||
[#177]: https://github.com/oscarotero/Gettext/issues/177
|
||||
[#178]: https://github.com/oscarotero/Gettext/issues/178
|
||||
[#182]: https://github.com/oscarotero/Gettext/issues/182
|
||||
[#187]: https://github.com/oscarotero/Gettext/issues/187
|
||||
[#188]: https://github.com/oscarotero/Gettext/issues/188
|
||||
[#191]: https://github.com/oscarotero/Gettext/issues/191
|
||||
[#197]: https://github.com/oscarotero/Gettext/issues/197
|
||||
[#198]: https://github.com/oscarotero/Gettext/issues/198
|
||||
[#200]: https://github.com/oscarotero/Gettext/issues/200
|
||||
[#205]: https://github.com/oscarotero/Gettext/issues/205
|
||||
[#213]: https://github.com/oscarotero/Gettext/issues/213
|
||||
[#214]: https://github.com/oscarotero/Gettext/issues/214
|
||||
[#215]: https://github.com/oscarotero/Gettext/issues/215
|
||||
[#218]: https://github.com/oscarotero/Gettext/issues/218
|
||||
[#221]: https://github.com/oscarotero/Gettext/issues/221
|
||||
[#223]: https://github.com/oscarotero/Gettext/issues/223
|
||||
[#224]: https://github.com/oscarotero/Gettext/issues/224
|
||||
[#225]: https://github.com/oscarotero/Gettext/issues/225
|
||||
[#226]: https://github.com/oscarotero/Gettext/issues/226
|
||||
[#230]: https://github.com/oscarotero/Gettext/issues/230
|
||||
[#231]: https://github.com/oscarotero/Gettext/issues/231
|
||||
[#238]: https://github.com/oscarotero/Gettext/issues/238
|
||||
[#242]: https://github.com/oscarotero/Gettext/issues/242
|
||||
[#261]: https://github.com/oscarotero/Gettext/issues/261
|
||||
[#266]: https://github.com/oscarotero/Gettext/issues/266
|
||||
[#271]: https://github.com/oscarotero/Gettext/issues/271
|
||||
[#274]: https://github.com/oscarotero/Gettext/issues/274
|
||||
[#280]: https://github.com/oscarotero/Gettext/issues/280
|
||||
|
||||
[4.8.7]: https://github.com/oscarotero/Gettext/compare/v4.8.6...v4.8.7
|
||||
[4.8.6]: https://github.com/oscarotero/Gettext/compare/v4.8.5...v4.8.6
|
||||
[4.8.5]: https://github.com/oscarotero/Gettext/compare/v4.8.4...v4.8.5
|
||||
[4.8.4]: https://github.com/oscarotero/Gettext/compare/v4.8.3...v4.8.4
|
||||
[4.8.3]: https://github.com/oscarotero/Gettext/compare/v4.8.2...v4.8.3
|
||||
[4.8.2]: https://github.com/oscarotero/Gettext/compare/v4.8.1...v4.8.2
|
||||
[4.8.1]: https://github.com/oscarotero/Gettext/compare/v4.8.0...v4.8.1
|
||||
[4.8.0]: https://github.com/oscarotero/Gettext/compare/v4.7.0...v4.8.0
|
||||
[4.7.0]: https://github.com/oscarotero/Gettext/compare/v4.6.3...v4.7.0
|
||||
[4.6.3]: https://github.com/oscarotero/Gettext/compare/v4.6.2...v4.6.3
|
||||
[4.6.2]: https://github.com/oscarotero/Gettext/compare/v4.6.1...v4.6.2
|
||||
[4.6.1]: https://github.com/oscarotero/Gettext/compare/v4.6.0...v4.6.1
|
||||
[4.6.0]: https://github.com/oscarotero/Gettext/compare/v4.5.0...v4.6.0
|
||||
[4.5.0]: https://github.com/oscarotero/Gettext/compare/v4.4.4...v4.5.0
|
||||
[4.4.4]: https://github.com/oscarotero/Gettext/compare/v4.4.3...v4.4.4
|
||||
[4.4.3]: https://github.com/oscarotero/Gettext/compare/v4.4.2...v4.4.3
|
||||
[4.4.2]: https://github.com/oscarotero/Gettext/compare/v4.4.1...v4.4.2
|
||||
[4.4.1]: https://github.com/oscarotero/Gettext/compare/v4.4.0...v4.4.1
|
||||
[4.4.0]: https://github.com/oscarotero/Gettext/compare/v4.3.0...v4.4.0
|
||||
[4.3.0]: https://github.com/oscarotero/Gettext/releases/tag/v4.3.0
|
|
@ -0,0 +1,17 @@
|
|||
Contributing to Gettext
|
||||
=======================
|
||||
|
||||
Looking to contribute something to this library? Here's how you can help.
|
||||
|
||||
## Bugs
|
||||
|
||||
A bug is a demonstrable problem that is caused by the code in the repository. Good bug reports are extremely helpful – thank you!
|
||||
|
||||
Please try to be as detailed as possible in your report. Include specific information about the environment – version of PHP, version of gettext, etc, and steps required to reproduce the issue.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Good pull requests – patches, improvements, new features – are a fantastic help. New extractors or generator are welcome. Before create a pull request, please follow these instructions:
|
||||
|
||||
* The code must be PSR-2 compliant
|
||||
* Write some tests
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Oscar Otero Marzoa
|
||||
|
||||
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.
|
|
@ -0,0 +1,425 @@
|
|||
Gettext
|
||||
=======
|
||||
|
||||
[![Build Status](https://travis-ci.org/oscarotero/Gettext.png?branch=master)](https://travis-ci.org/oscarotero/Gettext)
|
||||
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/oscarotero/Gettext/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/oscarotero/Gettext/?branch=master)
|
||||
[![Latest Stable Version](https://poser.pugx.org/gettext/gettext/v/stable.svg)](https://packagist.org/packages/gettext/gettext)
|
||||
[![Total Downloads](https://poser.pugx.org/gettext/gettext/downloads.svg)](https://packagist.org/packages/gettext/gettext)
|
||||
[![Monthly Downloads](https://poser.pugx.org/gettext/gettext/d/monthly.png)](https://packagist.org/packages/gettext/gettext)
|
||||
[![License](https://poser.pugx.org/gettext/gettext/license.svg)](https://packagist.org/packages/gettext/gettext)
|
||||
|
||||
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/496dc2a6-43be-4046-a283-f8370239dd47/big.png)](https://insight.sensiolabs.com/projects/496dc2a6-43be-4046-a283-f8370239dd47)
|
||||
|
||||
Created by Oscar Otero <http://oscarotero.com> <oom@oscarotero.com> (MIT License)
|
||||
|
||||
Gettext is a PHP (>=5.4) library to import/export/edit gettext from PO, MO, PHP, JS files, etc.
|
||||
|
||||
## Installation
|
||||
|
||||
With composer (recomended):
|
||||
|
||||
```
|
||||
composer require gettext/gettext
|
||||
```
|
||||
|
||||
If you don't use composer in your project, you have to download and place this package in a directory of your project. You need to install also [gettext/languages](https://github.com/mlocati/cldr-to-gettext-plural-rules). Then, include the autoloaders of both projects in any place of your php code:
|
||||
|
||||
```php
|
||||
include_once "libs/gettext/src/autoloader.php";
|
||||
include_once "libs/cldr-to-gettext-plural-rules/src/autoloader.php";
|
||||
```
|
||||
|
||||
## Classes and functions
|
||||
|
||||
This package contains the following classes:
|
||||
|
||||
* `Gettext\Translation` - A translation definition
|
||||
* `Gettext\Translations` - A collection of translations
|
||||
* `Gettext\Extractors\*` - Import translations from various sources (po, mo, php, js, etc)
|
||||
* `Gettext\Generators\*` - Export translations to various formats (po, mo, php, json, etc)
|
||||
* `Gettext\Translator` - To use the translations in your php templates instead the [gettext extension](http://php.net/gettext)
|
||||
* `Gettext\GettextTranslator` - To use the [gettext extension](http://php.net/gettext)
|
||||
|
||||
## Usage example
|
||||
|
||||
```php
|
||||
use Gettext\Translations;
|
||||
|
||||
//import from a .po file:
|
||||
$translations = Translations::fromPoFile('locales/gl.po');
|
||||
|
||||
//edit some translations:
|
||||
$translation = $translations->find(null, 'apple');
|
||||
|
||||
if ($translation) {
|
||||
$translation->setTranslation('Mazá');
|
||||
}
|
||||
|
||||
//export to a php array:
|
||||
$translations->toPhpArrayFile('locales/gl.php');
|
||||
|
||||
//and to a .mo file
|
||||
$translations->toMoFile('Locale/gl/LC_MESSAGES/messages.mo');
|
||||
```
|
||||
|
||||
If you want use this translations in your php templates without using the gettext extension:
|
||||
|
||||
```php
|
||||
use Gettext\Translator;
|
||||
|
||||
//Create the translator instance
|
||||
$t = new Translator();
|
||||
|
||||
//Load your translations (exported as PhpArray):
|
||||
$t->loadTranslations('locales/gl.php');
|
||||
|
||||
//Use it:
|
||||
echo $t->gettext('apple'); // "Mazá"
|
||||
|
||||
//If you want use global functions:
|
||||
$t->register();
|
||||
|
||||
echo __('apple'); // "Mazá"
|
||||
```
|
||||
|
||||
To use this translations with the gettext extension:
|
||||
|
||||
```php
|
||||
use Gettext\GettextTranslator;
|
||||
|
||||
//Create the translator instance
|
||||
$t = new GettextTranslator();
|
||||
|
||||
//Set the language and load the domain
|
||||
$t->setLanguage('gl');
|
||||
$t->loadDomain('messages', 'Locale');
|
||||
|
||||
//Use it:
|
||||
echo $t->gettext('apple'); // "Mazá"
|
||||
|
||||
//Or use the gettext functions
|
||||
echo gettext('apple'); // "Mazá"
|
||||
|
||||
//If you want use the global functions
|
||||
$t->register();
|
||||
|
||||
echo __('apple'); // "Mazá"
|
||||
|
||||
//And use sprintf/strtr placeholders
|
||||
echo __('Hello %s', 'world'); //Hello world
|
||||
echo __('Hello {name}', ['{name}' => 'world']); //Hello world
|
||||
```
|
||||
|
||||
The benefits of using the functions provided by this library (`__()` instead `_()` or `gettext()`) are:
|
||||
|
||||
* You are using the same functions, no matter whether the translations are provided by gettext extension or any other method.
|
||||
* You can use variables easier because `sprintf` functionality is included. For example: `__('Hello %s', 'world')` instead `sprintf(_('Hello %s'), 'world')`.
|
||||
* You can also use named placeholders if the second argument is an array. For example: `__('Hello %name%', ['%name%' => 'world'])` instead of `strtr(_('Hello %name%'), ['%name%' => 'world'])`.
|
||||
|
||||
## Translation
|
||||
|
||||
The `Gettext\Translation` class stores all information about a translation: the original text, the translated text, source references, comments, etc.
|
||||
|
||||
```php
|
||||
// __construct($context, $original, $plural)
|
||||
$translation = new Gettext\Translation('comments', 'One comment', '%s comments');
|
||||
|
||||
$translation->setTranslation('Un comentario');
|
||||
$translation->setPluralTranslation('%s comentarios');
|
||||
|
||||
$translation->addReference('templates/comments/comment.php', 34);
|
||||
$translation->addComment('To display the amount of comments in a post');
|
||||
|
||||
echo $translation->getContext(); // comments
|
||||
echo $translation->getOriginal(); // One comment
|
||||
echo $translation->getTranslation(); // Un comentario
|
||||
|
||||
// etc...
|
||||
```
|
||||
|
||||
## Translations
|
||||
|
||||
The `Gettext\Translations` class stores a collection of translations:
|
||||
|
||||
```php
|
||||
$translations = new Gettext\Translations();
|
||||
|
||||
//You can add new translations using the array syntax
|
||||
$translations[] = new Gettext\Translation('comments', 'One comment', '%s comments');
|
||||
|
||||
//Or using the "insert" method
|
||||
$insertedTranslation = $translations->insert('comments', 'One comment', '%s comments');
|
||||
|
||||
//Find a specific translation
|
||||
$translation = $translations->find('comments', 'One comment');
|
||||
|
||||
//Edit headers, domain, etc
|
||||
$translations->setHeader('Last-Translator', 'Oscar Otero');
|
||||
$translations->setDomain('my-blog');
|
||||
```
|
||||
|
||||
## Extractors
|
||||
|
||||
The extrators allows to fetch gettext values from any source. For example, to scan a .po file:
|
||||
|
||||
```php
|
||||
$translations = new Gettext\Translations();
|
||||
|
||||
//From a file
|
||||
Gettext\Extractors\Po::fromFile('locales/en.po', $translations);
|
||||
|
||||
//From a string
|
||||
$string = file_get_contents('locales2/en.po');
|
||||
Gettext\Extractors\Po::fromString($string, $translations);
|
||||
```
|
||||
|
||||
The better way to use extractors is using the magic methods of `Gettext\Translations`:
|
||||
|
||||
```php
|
||||
//Create a Translations instance using a po file
|
||||
$translations = Gettext\Translations::fromPoFile('locales/en.po');
|
||||
|
||||
//Add more messages from other files
|
||||
$translations->addFromPoFile('locales2/en.po');
|
||||
```
|
||||
|
||||
The available extractors are the following:
|
||||
|
||||
Name | Description | Example
|
||||
---- | ----------- | --------
|
||||
**Blade** | Scans a Blade template (For laravel users). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/blade/input.php)
|
||||
**Csv** | Gets the messages from csv. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Csv.csv)
|
||||
**CsvDictionary** | Gets the messages from csv (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/CsvDictionary.csv)
|
||||
**Jed** | Gets the messages from a json compatible with [Jed](http://slexaxton.github.com/Jed/). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Jed.json)
|
||||
**JsCode** | Scans javascript code looking for all gettext functions (the same than PhpCode but for javascript). You can use [the javascript gettext-translator library](https://github.com/oscarotero/gettext-translator) | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/jscode/input.js)
|
||||
**Json** | Gets the messages from json compatible with [gettext-translator](https://github.com/oscarotero/gettext-translator). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Json.json)
|
||||
**JsonDictionary** | Gets the messages from a json (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/JsonDictionary.json)
|
||||
**Mo** | Gets the messages from MO. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Mo.mo)
|
||||
**PhpArray** | Gets the messages from a php file that returns an array. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/PhpArray.php)
|
||||
**PhpCode** | Scans php code looking for all gettext functions (see `translator_functions.php`). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/phpcode/input.php)
|
||||
**Po** | Gets the messages from PO. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Po.po)
|
||||
**Twig** | To scan a Twig template. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/twig/input.php)
|
||||
**Xliff** | Gets the messages from [xliff (2.0)](http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Xliff.xlf)
|
||||
**Yaml** | Gets the messages from yaml. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Yaml.yml)
|
||||
**YamlDictionary** | Gets the messages from a yaml (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/YamlDictionary.yml)
|
||||
**VueJs** | Gets the messages from a VueJs template. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/vuejs/input.vue)
|
||||
|
||||
## Generators
|
||||
|
||||
The generators export a `Gettext\Translations` instance to any format (po, mo, array, etc).
|
||||
|
||||
```php
|
||||
//Save to a file
|
||||
Gettext\Generators\Po::toFile($translations, 'locales/en.po');
|
||||
|
||||
//Return as a string
|
||||
$content = Gettext\Generators\Po::toString($translations);
|
||||
file_put_contents('locales/en.po', $content);
|
||||
```
|
||||
|
||||
Like extractors, the better way to use generators is using the magic methods of `Gettext\Translations`:
|
||||
|
||||
```php
|
||||
//Extract messages from a php code file
|
||||
$translations = Gettext\Translations::fromPhpCodeFile('templates/index.php');
|
||||
|
||||
//Export to a po file
|
||||
$translations->toPoFile('locales/en.po');
|
||||
|
||||
//Export to a po string
|
||||
$content = $translations->toPoString();
|
||||
file_put_contents('locales/en.po', $content);
|
||||
```
|
||||
|
||||
The available generators are the following:
|
||||
|
||||
Name | Description | Example
|
||||
---- | ----------- | --------
|
||||
**Csv** | Exports to csv. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Csv.csv)
|
||||
**CsvDictionary** | Exports to csv (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/CsvDictionary.csv)
|
||||
**Json** | Exports to json, compatible with [gettext-translator](https://github.com/oscarotero/gettext-translator). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Json.json)
|
||||
**JsonDictionary** | Exports to json (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/JsonDictionary.json)
|
||||
**Mo** | Exports to Mo. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Mo.mo)
|
||||
**PhpArray** | Exports to php code that returns an array. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/PhpArray.php)
|
||||
**Po** | Exports to Po. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Po.po)
|
||||
**Jed** | Exports to json format compatible with [Jed](http://slexaxton.github.com/Jed/). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Jed.json)
|
||||
**Xliff** | Exports to [xliff (2.0)](http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Xliff.xlf)
|
||||
**Yaml** | Exports to yaml. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Yaml.yml)
|
||||
**YamlDictionary** | Exports to yaml (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/YamlDictionary.yml)
|
||||
|
||||
## Translator
|
||||
|
||||
The class `Gettext\Translator` implements the gettext functions in php. Useful if you don't have the native gettext extension for php or want to avoid problems with it. You can load the translations from a php array file or using a `Gettext\Translations` instance:
|
||||
|
||||
```php
|
||||
use Gettext\Translator;
|
||||
|
||||
//Create a new instance of the translator
|
||||
$t = new Translator();
|
||||
|
||||
//Load the translations using any of the following ways:
|
||||
|
||||
// 1. from php files (generated by Gettext\Extractors\PhpArray)
|
||||
$t->loadTranslations('locales/gl.php');
|
||||
|
||||
// 2. using the array directly
|
||||
$array = include 'locales/gl.php';
|
||||
$t->loadTranslations($array);
|
||||
|
||||
// 3. using a Gettext\Translations instance (slower)
|
||||
$translations = Gettext\Translations::fromPoFile('locales/gl.po');
|
||||
$t->loadTranslations($translations);
|
||||
|
||||
//Now you can use it in your templates
|
||||
echo $t->gettext('apple');
|
||||
```
|
||||
|
||||
## GettextTranslator
|
||||
|
||||
The class `Gettext\GettextTranslator` uses the gettext extension. It's useful because combines the performance of using real gettext functions but with the same API than `Translator` class, so you can switch to one or other translator deppending of the environment without change code of your app.
|
||||
|
||||
```php
|
||||
use Gettext\GettextTranslator;
|
||||
|
||||
//Create a new instance
|
||||
$t = new GettextTranslator();
|
||||
|
||||
//It detects the environment variables to set the locale, but you can change it:
|
||||
$t->setLanguage('gl');
|
||||
|
||||
//Load the domains:
|
||||
$t->loadDomain('messages', 'project/Locale');
|
||||
//this means you have the file "project/Locale/gl/LC_MESSAGES/messages.po"
|
||||
|
||||
//Now you can use it in your templates
|
||||
echo $t->gettext('apple');
|
||||
```
|
||||
|
||||
## Global functions
|
||||
|
||||
To ease the use of translations in your php templates, you can use the provided functions:
|
||||
|
||||
```php
|
||||
//Register the translator to use the global functions
|
||||
$t->register();
|
||||
|
||||
echo __('apple'); // it's the same than $t->gettext('apple');
|
||||
```
|
||||
|
||||
You can scan the php files containing these functions and extract the values with the PhpCode extractor:
|
||||
|
||||
```html
|
||||
<!-- index.php -->
|
||||
<html>
|
||||
<body>
|
||||
<?= __('Hello world'); ?>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
## Merge translations
|
||||
|
||||
To work with different translations you may want merge them in an unique file. There are two ways to do this:
|
||||
|
||||
The simplest way is adding new translations:
|
||||
|
||||
```php
|
||||
use Gettext\Translations;
|
||||
|
||||
$translations = Translations::fromPoFile('my-file1.po');
|
||||
$translations->addFromPoFile('my-file2.po');
|
||||
```
|
||||
|
||||
A more advanced way is merge two `Translations` instances:
|
||||
|
||||
```php
|
||||
use Gettext\Translations;
|
||||
|
||||
//Create a new Translations instances with our translations.
|
||||
|
||||
$translations1 = Translations::fromPoFile('my-file1.po');
|
||||
$translations2 = Translations::fromPoFile('my-file2.po');
|
||||
|
||||
//Merge one inside other:
|
||||
$translations1->mergeWith($translations2);
|
||||
|
||||
//Now translations1 has all values
|
||||
```
|
||||
|
||||
The second argument of `mergeWith` defines how the merge will be done. Use the `Gettext\Merge` constants to configure the merging:
|
||||
|
||||
Constant | Description
|
||||
--------- | -----------
|
||||
`Merge::ADD` | Adds the translations from `$translations2` that are missing
|
||||
`Merge::REMOVE` | Removes the translations missing in `$translations2`
|
||||
`Merge::HEADERS_ADD` | Adds the headers from `$translations2` that are missing
|
||||
`Merge::HEADERS_REMOVE` | Removes the headers missing in `$translations2`
|
||||
`Merge::HEADERS_OVERRIDE` | Overrides the headers with the values of `$translations2`
|
||||
`Merge::LANGUAGE_OVERRIDE` | Set the language defined in `$translations2`
|
||||
`Merge::DOMAIN_OVERRIDE` | Set the domain defined in `$translations2`
|
||||
`Merge::TRANSLATION_OVERRIDE` | Override the translation and plural translations with the value of `$translation2`
|
||||
`Merge::COMMENTS_OURS` | Use only the comments of `$translation1`
|
||||
`Merge::COMMENTS_THEIRS` | Use only the comments of `$translation2`
|
||||
`Merge::EXTRACTED_COMMENTS_OURS` | Use only the extracted comments of `$translation1`
|
||||
`Merge::EXTRACTED_COMMENTS_THEIRS` | Use only the extracted comments of `$translation2`
|
||||
`Merge::FLAGS_OURS` | Use only the flags of `$translation1`
|
||||
`Merge::FLAGS_THEIRS` | Use only the flags of `$translation2`
|
||||
`Merge::REFERENCES_OURS` | Use only the references of `$translation1`
|
||||
`Merge::REFERENCES_THEIRS` | Use only the references of `$translation2`
|
||||
|
||||
Example:
|
||||
|
||||
```php
|
||||
use Gettext\Translations;
|
||||
use Gettext\Merge;
|
||||
|
||||
//Scan the php code to find the latest gettext translations
|
||||
$phpTranslations = Translations::fromPhpCodeFile('my-templates.php');
|
||||
|
||||
//Get the translations of the code that are stored in a po file
|
||||
$poTranslations = Translations::fromPoFile('locale.po');
|
||||
|
||||
//Merge the translations from the po file using the references from `$phpTranslations`:
|
||||
$translations->mergeWith($poTranslations, Merge::REFERENCES_OURS);
|
||||
|
||||
//Now save a po file with the result
|
||||
$translations->toPoFile('locale.po');
|
||||
```
|
||||
|
||||
Note, if the second argument is not defined, the default value is `Merge::DEFAULTS` that's equivalent to `Merge::ADD | Merge::HEADERS_ADD`.
|
||||
|
||||
## Use from CLI
|
||||
|
||||
There's a Robo task to use this library from the command line interface: https://github.com/oscarotero/GettextRobo
|
||||
|
||||
## Use in the browser
|
||||
|
||||
If you want to use your translations in the browser, there's a javascript translator: https://github.com/oscarotero/gettext-translator
|
||||
|
||||
## Third party packages
|
||||
|
||||
Twig integration:
|
||||
|
||||
* [jaimeperez/twig-configurable-i18n](https://packagist.org/packages/jaimeperez/twig-configurable-i18n)
|
||||
* [cemerson/translator-twig-extension](https://packagist.org/packages/cemerson/translator-twig-extension)
|
||||
|
||||
Framework integration:
|
||||
|
||||
* [Laravel 5](https://packagist.org/packages/eusonlito/laravel-gettext)
|
||||
* [CakePHP 3](https://packagist.org/packages/k1low/po)
|
||||
* [Symfony 2](https://packagist.org/packages/mablae/gettext-bundle)
|
||||
|
||||
[add your package](https://github.com/oscarotero/Gettext/issues/new)
|
||||
|
||||
## Contributors
|
||||
|
||||
Thanks to all [contributors](https://github.com/oscarotero/Gettext/graphs/contributors) specially to [@mlocati](https://github.com/mlocati).
|
||||
|
||||
## Donations
|
||||
|
||||
If this library is useful for you, consider to donate to the author.
|
||||
|
||||
[Buy me a beer :beer:](https://www.paypal.me/oscarotero)
|
||||
|
||||
Thanks in advance!
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"name": "gettext/gettext",
|
||||
"type": "library",
|
||||
"description": "PHP gettext manager",
|
||||
"keywords": ["js", "gettext", "i18n", "translation", "po", "mo"],
|
||||
"homepage": "https://github.com/oscarotero/Gettext",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oscar Otero",
|
||||
"email": "oom@oscarotero.com",
|
||||
"homepage": "http://oscarotero.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"email": "oom@oscarotero.com",
|
||||
"issues": "https://github.com/oscarotero/Gettext/issues"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0",
|
||||
"gettext/languages": "^2.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"illuminate/view": "^5.0.x-dev",
|
||||
"twig/twig": "^1.31|^2.0",
|
||||
"twig/extensions": "*",
|
||||
"symfony/yaml": "~2",
|
||||
"phpunit/phpunit": "^4.8|^5.7|^6.5",
|
||||
"squizlabs/php_codesniffer": "^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"illuminate/view": "Is necessary if you want to use the Blade extractor",
|
||||
"twig/twig": "Is necessary if you want to use the Twig extractor",
|
||||
"twig/extensions": "Is necessary if you want to use the Twig extractor",
|
||||
"symfony/yaml": "Is necessary if you want to use the Yaml extractor/generator"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Gettext\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Gettext\\Tests\\": "tests"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"phpunit",
|
||||
"phpcs"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext;
|
||||
|
||||
abstract class BaseTranslator implements TranslatorInterface
|
||||
{
|
||||
/** @var TranslatorInterface */
|
||||
public static $current;
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*/
|
||||
public function noop($original)
|
||||
{
|
||||
return $original;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$previous = static::$current;
|
||||
|
||||
static::$current = $this;
|
||||
|
||||
static::includeFunctions();
|
||||
|
||||
return $previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the gettext functions
|
||||
*/
|
||||
public static function includeFunctions()
|
||||
{
|
||||
include_once __DIR__.'/translator_functions.php';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\View\Compilers\BladeCompiler;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from blade.php files returning arrays.
|
||||
*/
|
||||
class Blade extends Extractor implements ExtractorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
if (empty($options['facade'])) {
|
||||
$cachePath = empty($options['cachePath']) ? sys_get_temp_dir() : $options['cachePath'];
|
||||
$bladeCompiler = new BladeCompiler(new Filesystem(), $cachePath);
|
||||
|
||||
if (method_exists($bladeCompiler, 'withoutComponentTags')) {
|
||||
$bladeCompiler->withoutComponentTags();
|
||||
}
|
||||
|
||||
$string = $bladeCompiler->compileString($string);
|
||||
} else {
|
||||
$string = $options['facade']::compileString($string);
|
||||
}
|
||||
|
||||
PhpCode::fromString($string, $translations, $options);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\HeadersExtractorTrait;
|
||||
use Gettext\Utils\CsvTrait;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from csv.
|
||||
*/
|
||||
class Csv extends Extractor implements ExtractorInterface
|
||||
{
|
||||
use HeadersExtractorTrait;
|
||||
use CsvTrait;
|
||||
|
||||
public static $options = [
|
||||
'delimiter' => ",",
|
||||
'enclosure' => '"',
|
||||
'escape_char' => "\\"
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
$handle = fopen('php://memory', 'w');
|
||||
|
||||
fputs($handle, $string);
|
||||
rewind($handle);
|
||||
|
||||
while ($row = static::fgetcsv($handle, $options)) {
|
||||
$context = array_shift($row);
|
||||
$original = array_shift($row);
|
||||
|
||||
if ($context === '' && $original === '') {
|
||||
static::extractHeaders(array_shift($row), $translations);
|
||||
continue;
|
||||
}
|
||||
|
||||
$translation = $translations->insert($context, $original);
|
||||
|
||||
if (!empty($row)) {
|
||||
$translation->setTranslation(array_shift($row));
|
||||
$translation->setPluralTranslations($row);
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\HeadersExtractorTrait;
|
||||
use Gettext\Utils\CsvTrait;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from csv.
|
||||
*/
|
||||
class CsvDictionary extends Extractor implements ExtractorInterface
|
||||
{
|
||||
use HeadersExtractorTrait;
|
||||
use CsvTrait;
|
||||
|
||||
public static $options = [
|
||||
'delimiter' => ",",
|
||||
'enclosure' => '"',
|
||||
'escape_char' => "\\"
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
$handle = fopen('php://memory', 'w');
|
||||
|
||||
fputs($handle, $string);
|
||||
rewind($handle);
|
||||
|
||||
while ($row = static::fgetcsv($handle, $options)) {
|
||||
list($original, $translation) = $row + ['', ''];
|
||||
|
||||
if ($original === '') {
|
||||
static::extractHeaders($translation, $translations);
|
||||
continue;
|
||||
}
|
||||
|
||||
$translations->insert(null, $original)->setTranslation($translation);
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use Gettext\Translations;
|
||||
|
||||
abstract class Extractor implements ExtractorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromFile($file, Translations $translations, array $options = [])
|
||||
{
|
||||
foreach (static::getFiles($file) as $file) {
|
||||
$options['file'] = $file;
|
||||
static::fromString(static::readFile($file), $translations, $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and returns all files.
|
||||
*
|
||||
* @param string|array $file The file/s
|
||||
*
|
||||
* @return array The file paths
|
||||
*/
|
||||
protected static function getFiles($file)
|
||||
{
|
||||
if (empty($file)) {
|
||||
throw new InvalidArgumentException('There is not any file defined');
|
||||
}
|
||||
|
||||
if (is_string($file)) {
|
||||
if (!is_file($file)) {
|
||||
throw new InvalidArgumentException("'$file' is not a valid file");
|
||||
}
|
||||
|
||||
if (!is_readable($file)) {
|
||||
throw new InvalidArgumentException("'$file' is not a readable file");
|
||||
}
|
||||
|
||||
return [$file];
|
||||
}
|
||||
|
||||
if (is_array($file)) {
|
||||
$files = [];
|
||||
|
||||
foreach ($file as $f) {
|
||||
$files = array_merge($files, static::getFiles($f));
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('The first argument must be string or array');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and returns the content of a file.
|
||||
*
|
||||
* @param string $file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function readFile($file)
|
||||
{
|
||||
$length = filesize($file);
|
||||
|
||||
if (!($fd = fopen($file, 'rb'))) {
|
||||
throw new Exception("Cannot read the file '$file', probably permissions");
|
||||
}
|
||||
|
||||
$content = $length ? fread($fd, $length) : '';
|
||||
fclose($fd);
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Gettext\Translations;
|
||||
|
||||
interface ExtractorInterface
|
||||
{
|
||||
/**
|
||||
* Extract the translations from a file.
|
||||
*
|
||||
* @param array|string $file A path of a file or files
|
||||
* @param Translations $translations The translations instance to append the new translations.
|
||||
* @param array $options
|
||||
*/
|
||||
public static function fromFile($file, Translations $translations, array $options = []);
|
||||
|
||||
/**
|
||||
* Parses a string and append the translations found in the Translations instance.
|
||||
*
|
||||
* @param string $string
|
||||
* @param Translations $translations
|
||||
* @param array $options
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = []);
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Gettext\Translations;
|
||||
|
||||
interface ExtractorMultiInterface
|
||||
{
|
||||
/**
|
||||
* Parses a string and append the translations found in the Translations instance.
|
||||
* Allows scanning for multiple domains at a time (each Translation has to have a different domain)
|
||||
*
|
||||
* @param string $string
|
||||
* @param Translations[] $translations
|
||||
* @param array $options
|
||||
*/
|
||||
public static function fromStringMultiple($string, array $translations, array $options = []);
|
||||
|
||||
/**
|
||||
* Parses a string and append the translations found in the Translations instance.
|
||||
* Allows scanning for multiple domains at a time (each Translation has to have a different domain)
|
||||
*
|
||||
* @param $file
|
||||
* @param Translations[] $translations
|
||||
* @param array $options
|
||||
*/
|
||||
public static function fromFileMultiple($file, array $translations, array $options = []);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Gettext\Translations;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from json files.
|
||||
*/
|
||||
class Jed extends Extractor implements ExtractorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
static::extract(json_decode($string, true), $translations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an array of translations and append to the Translations instance.
|
||||
*
|
||||
* @param array $content
|
||||
* @param Translations $translations
|
||||
*/
|
||||
public static function extract(array $content, Translations $translations)
|
||||
{
|
||||
$messages = current($content);
|
||||
$headers = isset($messages['']) ? $messages[''] : null;
|
||||
unset($messages['']);
|
||||
|
||||
if (!empty($headers['domain'])) {
|
||||
$translations->setDomain($headers['domain']);
|
||||
}
|
||||
|
||||
if (!empty($headers['lang'])) {
|
||||
$translations->setLanguage($headers['lang']);
|
||||
}
|
||||
|
||||
if (!empty($headers['plural-forms'])) {
|
||||
$translations->setHeader(Translations::HEADER_PLURAL, $headers['plural-forms']);
|
||||
}
|
||||
|
||||
$context_glue = '\u0004';
|
||||
|
||||
foreach ($messages as $key => $translation) {
|
||||
$key = explode($context_glue, $key);
|
||||
$context = isset($key[1]) ? array_shift($key) : '';
|
||||
|
||||
$translations->insert($context, array_shift($key))
|
||||
->setTranslation(array_shift($translation))
|
||||
->setPluralTranslations($translation);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Exception;
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\FunctionsScanner;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from javascript files.
|
||||
*/
|
||||
class JsCode extends Extractor implements ExtractorInterface, ExtractorMultiInterface
|
||||
{
|
||||
public static $options = [
|
||||
'constants' => [],
|
||||
|
||||
'functions' => [
|
||||
'gettext' => 'gettext',
|
||||
'__' => 'gettext',
|
||||
'ngettext' => 'ngettext',
|
||||
'n__' => 'ngettext',
|
||||
'pgettext' => 'pgettext',
|
||||
'p__' => 'pgettext',
|
||||
'dgettext' => 'dgettext',
|
||||
'd__' => 'dgettext',
|
||||
'dngettext' => 'dngettext',
|
||||
'dn__' => 'dngettext',
|
||||
'dpgettext' => 'dpgettext',
|
||||
'dp__' => 'dpgettext',
|
||||
'npgettext' => 'npgettext',
|
||||
'np__' => 'npgettext',
|
||||
'dnpgettext' => 'dnpgettext',
|
||||
'dnp__' => 'dnpgettext',
|
||||
'noop' => 'noop',
|
||||
'noop__' => 'noop',
|
||||
],
|
||||
];
|
||||
|
||||
protected static $functionsScannerClass = 'Gettext\Utils\JsFunctionsScanner';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
static::fromStringMultiple($string, [$translations], $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function fromStringMultiple($string, array $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
|
||||
/** @var FunctionsScanner $functions */
|
||||
$functions = new static::$functionsScannerClass($string);
|
||||
$functions->saveGettextFunctions($translations, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function fromFileMultiple($file, array $translations, array $options = [])
|
||||
{
|
||||
foreach (static::getFiles($file) as $file) {
|
||||
$options['file'] = $file;
|
||||
static::fromStringMultiple(static::readFile($file), $translations, $options);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\MultidimensionalArrayTrait;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from json.
|
||||
*/
|
||||
class Json extends Extractor implements ExtractorInterface
|
||||
{
|
||||
use MultidimensionalArrayTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
$messages = json_decode($string, true);
|
||||
|
||||
if (is_array($messages)) {
|
||||
static::fromArray($messages, $translations);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\DictionaryTrait;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from plain json.
|
||||
*/
|
||||
class JsonDictionary extends Extractor implements ExtractorInterface
|
||||
{
|
||||
use DictionaryTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
$messages = json_decode($string, true);
|
||||
|
||||
if (is_array($messages)) {
|
||||
static::fromArray($messages, $translations);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Exception;
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\StringReader;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from .mo files.
|
||||
*/
|
||||
class Mo extends Extractor implements ExtractorInterface
|
||||
{
|
||||
const MAGIC1 = -1794895138;
|
||||
const MAGIC2 = -569244523;
|
||||
const MAGIC3 = 2500072158;
|
||||
|
||||
protected static $stringReaderClass = 'Gettext\Utils\StringReader';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
/** @var StringReader $stream */
|
||||
$stream = new static::$stringReaderClass($string);
|
||||
$magic = static::readInt($stream, 'V');
|
||||
|
||||
if (($magic === static::MAGIC1) || ($magic === static::MAGIC3)) { //to make sure it works for 64-bit platforms
|
||||
$byteOrder = 'V'; //low endian
|
||||
} elseif ($magic === (static::MAGIC2 & 0xFFFFFFFF)) {
|
||||
$byteOrder = 'N'; //big endian
|
||||
} else {
|
||||
throw new Exception('Not MO file');
|
||||
}
|
||||
|
||||
static::readInt($stream, $byteOrder);
|
||||
|
||||
$total = static::readInt($stream, $byteOrder); //total string count
|
||||
$originals = static::readInt($stream, $byteOrder); //offset of original table
|
||||
$tran = static::readInt($stream, $byteOrder); //offset of translation table
|
||||
|
||||
$stream->seekto($originals);
|
||||
$table_originals = static::readIntArray($stream, $byteOrder, $total * 2);
|
||||
|
||||
$stream->seekto($tran);
|
||||
$table_translations = static::readIntArray($stream, $byteOrder, $total * 2);
|
||||
|
||||
for ($i = 0; $i < $total; ++$i) {
|
||||
$next = $i * 2;
|
||||
|
||||
$stream->seekto($table_originals[$next + 2]);
|
||||
$original = $stream->read($table_originals[$next + 1]);
|
||||
|
||||
$stream->seekto($table_translations[$next + 2]);
|
||||
$translated = $stream->read($table_translations[$next + 1]);
|
||||
|
||||
if ($original === '') {
|
||||
// Headers
|
||||
foreach (explode("\n", $translated) as $headerLine) {
|
||||
if ($headerLine === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$headerChunks = preg_split('/:\s*/', $headerLine, 2);
|
||||
$translations->setHeader($headerChunks[0], isset($headerChunks[1]) ? $headerChunks[1] : '');
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$chunks = explode("\x04", $original, 2);
|
||||
|
||||
if (isset($chunks[1])) {
|
||||
$context = $chunks[0];
|
||||
$original = $chunks[1];
|
||||
} else {
|
||||
$context = '';
|
||||
}
|
||||
|
||||
$chunks = explode("\x00", $original, 2);
|
||||
|
||||
if (isset($chunks[1])) {
|
||||
$original = $chunks[0];
|
||||
$plural = $chunks[1];
|
||||
} else {
|
||||
$plural = '';
|
||||
}
|
||||
|
||||
$translation = $translations->insert($context, $original, $plural);
|
||||
|
||||
if ($translated === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($plural === '') {
|
||||
$translation->setTranslation($translated);
|
||||
continue;
|
||||
}
|
||||
|
||||
$v = explode("\x00", $translated);
|
||||
$translation->setTranslation(array_shift($v));
|
||||
$translation->setPluralTranslations($v);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StringReader $stream
|
||||
* @param string $byteOrder
|
||||
*/
|
||||
protected static function readInt(StringReader $stream, $byteOrder)
|
||||
{
|
||||
if (($read = $stream->read(4)) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$read = unpack($byteOrder, $read);
|
||||
|
||||
return array_shift($read);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StringReader $stream
|
||||
* @param string $byteOrder
|
||||
* @param int $count
|
||||
*/
|
||||
protected static function readIntArray(StringReader $stream, $byteOrder, $count)
|
||||
{
|
||||
return unpack($byteOrder.$count, $stream->read(4 * $count));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\MultidimensionalArrayTrait;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from php files returning arrays.
|
||||
*/
|
||||
class PhpArray extends Extractor implements ExtractorInterface
|
||||
{
|
||||
use MultidimensionalArrayTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromFile($file, Translations $translations, array $options = [])
|
||||
{
|
||||
foreach (static::getFiles($file) as $file) {
|
||||
static::fromArray(include($file), $translations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
throw new BadMethodCallException('PhpArray::fromString() cannot be called. Use PhpArray::fromFile()');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Exception;
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\FunctionsScanner;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from php files returning arrays.
|
||||
*/
|
||||
class PhpCode extends Extractor implements ExtractorInterface, ExtractorMultiInterface
|
||||
{
|
||||
public static $options = [
|
||||
// - false: to not extract comments
|
||||
// - empty string: to extract all comments
|
||||
// - non-empty string: to extract comments that start with that string
|
||||
// - array with strings to extract comments format.
|
||||
'extractComments' => false,
|
||||
|
||||
'constants' => [],
|
||||
|
||||
'functions' => [
|
||||
'gettext' => 'gettext',
|
||||
'__' => 'gettext',
|
||||
'ngettext' => 'ngettext',
|
||||
'n__' => 'ngettext',
|
||||
'pgettext' => 'pgettext',
|
||||
'p__' => 'pgettext',
|
||||
'dgettext' => 'dgettext',
|
||||
'd__' => 'dgettext',
|
||||
'dngettext' => 'dngettext',
|
||||
'dn__' => 'dngettext',
|
||||
'dpgettext' => 'dpgettext',
|
||||
'dp__' => 'dpgettext',
|
||||
'npgettext' => 'npgettext',
|
||||
'np__' => 'npgettext',
|
||||
'dnpgettext' => 'dnpgettext',
|
||||
'dnp__' => 'dnpgettext',
|
||||
'noop' => 'noop',
|
||||
'noop__' => 'noop',
|
||||
],
|
||||
];
|
||||
|
||||
protected static $functionsScannerClass = 'Gettext\Utils\PhpFunctionsScanner';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
static::fromStringMultiple($string, [$translations], $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function fromStringMultiple($string, array $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
|
||||
/** @var FunctionsScanner $functions */
|
||||
$functions = new static::$functionsScannerClass($string);
|
||||
|
||||
if ($options['extractComments'] !== false) {
|
||||
$functions->enableCommentsExtraction($options['extractComments']);
|
||||
}
|
||||
|
||||
$functions->saveGettextFunctions($translations, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function fromFileMultiple($file, array $translations, array $options = [])
|
||||
{
|
||||
foreach (static::getFiles($file) as $file) {
|
||||
$options['file'] = $file;
|
||||
static::fromStringMultiple(static::readFile($file), $translations, $options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decodes a T_CONSTANT_ENCAPSED_STRING string.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function convertString($value)
|
||||
{
|
||||
if (strpos($value, '\\') === false) {
|
||||
return substr($value, 1, -1);
|
||||
}
|
||||
|
||||
if ($value[0] === "'") {
|
||||
return strtr(substr($value, 1, -1), ['\\\\' => '\\', '\\\'' => '\'']);
|
||||
}
|
||||
|
||||
$value = substr($value, 1, -1);
|
||||
|
||||
return preg_replace_callback(
|
||||
'/\\\(n|r|t|v|e|f|\$|"|\\\|x[0-9A-Fa-f]{1,2}|u{[0-9a-f]{1,6}}|[0-7]{1,3})/',
|
||||
function ($match) {
|
||||
switch ($match[1][0]) {
|
||||
case 'n':
|
||||
return "\n";
|
||||
case 'r':
|
||||
return "\r";
|
||||
case 't':
|
||||
return "\t";
|
||||
case 'v':
|
||||
return "\v";
|
||||
case 'e':
|
||||
return "\e";
|
||||
case 'f':
|
||||
return "\f";
|
||||
case '$':
|
||||
return '$';
|
||||
case '"':
|
||||
return '"';
|
||||
case '\\':
|
||||
return '\\';
|
||||
case 'x':
|
||||
return chr(hexdec(substr($match[1], 1)));
|
||||
case 'u':
|
||||
return static::unicodeChar(hexdec(substr($match[1], 1)));
|
||||
default:
|
||||
return chr(octdec($match[1]));
|
||||
}
|
||||
},
|
||||
$value
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $dec
|
||||
* @return string|null
|
||||
* @see http://php.net/manual/en/function.chr.php#118804
|
||||
*/
|
||||
protected static function unicodeChar($dec)
|
||||
{
|
||||
if ($dec < 0x80) {
|
||||
return chr($dec);
|
||||
}
|
||||
|
||||
if ($dec < 0x0800) {
|
||||
return chr(0xC0 + ($dec >> 6))
|
||||
. chr(0x80 + ($dec & 0x3f));
|
||||
}
|
||||
|
||||
if ($dec < 0x010000) {
|
||||
return chr(0xE0 + ($dec >> 12))
|
||||
. chr(0x80 + (($dec >> 6) & 0x3f))
|
||||
. chr(0x80 + ($dec & 0x3f));
|
||||
}
|
||||
|
||||
if ($dec < 0x200000) {
|
||||
return chr(0xF0 + ($dec >> 18))
|
||||
. chr(0x80 + (($dec >> 12) & 0x3f))
|
||||
. chr(0x80 + (($dec >> 6) & 0x3f))
|
||||
. chr(0x80 + ($dec & 0x3f));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Translation;
|
||||
use Gettext\Utils\HeadersExtractorTrait;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from php files returning arrays.
|
||||
*/
|
||||
class Po extends Extractor implements ExtractorInterface
|
||||
{
|
||||
use HeadersExtractorTrait;
|
||||
|
||||
/**
|
||||
* Parses a .po file and append the translations found in the Translations instance.
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
$lines = explode("\n", $string);
|
||||
$i = 0;
|
||||
|
||||
$translation = $translations->createNewTranslation('', '');
|
||||
|
||||
for ($n = count($lines); $i < $n; ++$i) {
|
||||
$line = trim($lines[$i]);
|
||||
$line = static::fixMultiLines($line, $lines, $i);
|
||||
|
||||
if ($line === '') {
|
||||
if ($translation->is('', '')) {
|
||||
static::extractHeaders($translation->getTranslation(), $translations);
|
||||
} elseif ($translation->hasOriginal()) {
|
||||
$translations[] = $translation;
|
||||
}
|
||||
|
||||
$translation = $translations->createNewTranslation('', '');
|
||||
continue;
|
||||
}
|
||||
|
||||
$splitLine = preg_split('/\s+/', $line, 2);
|
||||
$key = $splitLine[0];
|
||||
$data = isset($splitLine[1]) ? $splitLine[1] : '';
|
||||
|
||||
if ($key === '#~') {
|
||||
$translation->setDisabled(true);
|
||||
|
||||
$splitLine = preg_split('/\s+/', $data, 2);
|
||||
$key = $splitLine[0];
|
||||
$data = isset($splitLine[1]) ? $splitLine[1] : '';
|
||||
}
|
||||
|
||||
switch ($key) {
|
||||
case '#':
|
||||
$translation->addComment($data);
|
||||
$append = null;
|
||||
break;
|
||||
|
||||
case '#.':
|
||||
$translation->addExtractedComment($data);
|
||||
$append = null;
|
||||
break;
|
||||
|
||||
case '#,':
|
||||
foreach (array_map('trim', explode(',', trim($data))) as $value) {
|
||||
$translation->addFlag($value);
|
||||
}
|
||||
$append = null;
|
||||
break;
|
||||
|
||||
case '#:':
|
||||
foreach (preg_split('/\s+/', trim($data)) as $value) {
|
||||
if (preg_match('/^(.+)(:(\d*))?$/U', $value, $matches)) {
|
||||
$translation->addReference($matches[1], isset($matches[3]) ? $matches[3] : null);
|
||||
}
|
||||
}
|
||||
$append = null;
|
||||
break;
|
||||
|
||||
case 'msgctxt':
|
||||
$translation = $translation->getClone(static::convertString($data));
|
||||
$append = 'Context';
|
||||
break;
|
||||
|
||||
case 'msgid':
|
||||
$translation = $translation->getClone(null, static::convertString($data));
|
||||
$append = 'Original';
|
||||
break;
|
||||
|
||||
case 'msgid_plural':
|
||||
$translation->setPlural(static::convertString($data));
|
||||
$append = 'Plural';
|
||||
break;
|
||||
|
||||
case 'msgstr':
|
||||
case 'msgstr[0]':
|
||||
$translation->setTranslation(static::convertString($data));
|
||||
$append = 'Translation';
|
||||
break;
|
||||
|
||||
case 'msgstr[1]':
|
||||
$translation->setPluralTranslations([static::convertString($data)]);
|
||||
$append = 'PluralTranslation';
|
||||
break;
|
||||
|
||||
default:
|
||||
if (strpos($key, 'msgstr[') === 0) {
|
||||
$p = $translation->getPluralTranslations();
|
||||
$p[] = static::convertString($data);
|
||||
|
||||
$translation->setPluralTranslations($p);
|
||||
$append = 'PluralTranslation';
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($append)) {
|
||||
if ($append === 'Context') {
|
||||
$translation = $translation->getClone($translation->getContext()
|
||||
."\n"
|
||||
.static::convertString($data));
|
||||
break;
|
||||
}
|
||||
|
||||
if ($append === 'Original') {
|
||||
$translation = $translation->getClone(null, $translation->getOriginal()
|
||||
."\n"
|
||||
.static::convertString($data));
|
||||
break;
|
||||
}
|
||||
|
||||
if ($append === 'PluralTranslation') {
|
||||
$p = $translation->getPluralTranslations();
|
||||
$p[] = array_pop($p)."\n".static::convertString($data);
|
||||
$translation->setPluralTranslations($p);
|
||||
break;
|
||||
}
|
||||
|
||||
$getMethod = 'get'.$append;
|
||||
$setMethod = 'set'.$append;
|
||||
$translation->$setMethod($translation->$getMethod()."\n".static::convertString($data));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($translation->hasOriginal() && !in_array($translation, iterator_to_array($translations))) {
|
||||
$translations[] = $translation;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets one string from multiline strings.
|
||||
*
|
||||
* @param string $line
|
||||
* @param array $lines
|
||||
* @param int &$i
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function fixMultiLines($line, array $lines, &$i)
|
||||
{
|
||||
for ($j = $i, $t = count($lines); $j < $t; ++$j) {
|
||||
if (substr($line, -1, 1) == '"' && isset($lines[$j + 1])) {
|
||||
$nextLine = trim($lines[$j + 1]);
|
||||
if (substr($nextLine, 0, 1) == '"') {
|
||||
$line = substr($line, 0, -1).substr($nextLine, 1);
|
||||
continue;
|
||||
}
|
||||
if (substr($nextLine, 0, 4) == '#~ "') {
|
||||
$line = substr($line, 0, -1).substr($nextLine, 4);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$i = $j;
|
||||
break;
|
||||
}
|
||||
|
||||
return $line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string from its PO representation.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function convertString($value)
|
||||
{
|
||||
if (!$value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($value[0] === '"') {
|
||||
$value = substr($value, 1, -1);
|
||||
}
|
||||
|
||||
return strtr(
|
||||
$value,
|
||||
[
|
||||
'\\\\' => '\\',
|
||||
'\\a' => "\x07",
|
||||
'\\b' => "\x08",
|
||||
'\\t' => "\t",
|
||||
'\\n' => "\n",
|
||||
'\\v' => "\x0b",
|
||||
'\\f' => "\x0c",
|
||||
'\\r' => "\r",
|
||||
'\\"' => '"',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Twig_Loader_Array;
|
||||
use Twig_Environment;
|
||||
use Twig_Source;
|
||||
use Twig_Extensions_Extension_I18n;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from twig files returning arrays.
|
||||
*/
|
||||
class Twig extends Extractor implements ExtractorInterface
|
||||
{
|
||||
public static $options = [
|
||||
'extractComments' => 'notes:',
|
||||
'twig' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
|
||||
$twig = $options['twig'] ?: static::createTwig();
|
||||
|
||||
PhpCode::fromString($twig->compileSource(new Twig_Source($string, '')), $translations, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Twig instance.
|
||||
*
|
||||
* @return Twig_Environment
|
||||
*/
|
||||
protected static function createTwig()
|
||||
{
|
||||
$twig = new Twig_Environment(new Twig_Loader_Array(['' => '']));
|
||||
$twig->addExtension(new Twig_Extensions_Extension_I18n());
|
||||
|
||||
return static::$options['twig'] = $twig;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,423 @@
|
|||
<?php
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use DOMAttr;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMNode;
|
||||
use Exception;
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\FunctionsScanner;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from VueJS template files.
|
||||
*/
|
||||
class VueJs extends Extractor implements ExtractorInterface, ExtractorMultiInterface
|
||||
{
|
||||
public static $options = [
|
||||
'constants' => [],
|
||||
|
||||
'functions' => [
|
||||
'gettext' => 'gettext',
|
||||
'__' => 'gettext',
|
||||
'ngettext' => 'ngettext',
|
||||
'n__' => 'ngettext',
|
||||
'pgettext' => 'pgettext',
|
||||
'p__' => 'pgettext',
|
||||
'dgettext' => 'dgettext',
|
||||
'd__' => 'dgettext',
|
||||
'dngettext' => 'dngettext',
|
||||
'dn__' => 'dngettext',
|
||||
'dpgettext' => 'dpgettext',
|
||||
'dp__' => 'dpgettext',
|
||||
'npgettext' => 'npgettext',
|
||||
'np__' => 'npgettext',
|
||||
'dnpgettext' => 'dnpgettext',
|
||||
'dnp__' => 'dnpgettext',
|
||||
'noop' => 'noop',
|
||||
'noop__' => 'noop',
|
||||
],
|
||||
];
|
||||
|
||||
protected static $functionsScannerClass = 'Gettext\Utils\JsFunctionsScanner';
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function fromFileMultiple($file, array $translations, array $options = [])
|
||||
{
|
||||
foreach (static::getFiles($file) as $file) {
|
||||
$options['file'] = $file;
|
||||
static::fromStringMultiple(static::readFile($file), $translations, $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
static::fromStringMultiple($string, [$translations], $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function fromStringMultiple($string, array $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
$options += [
|
||||
// HTML attribute prefixes we parse as JS which could contain translations (are JS expressions)
|
||||
'attributePrefixes' => [
|
||||
':',
|
||||
'v-bind:',
|
||||
'v-on:',
|
||||
'v-text',
|
||||
],
|
||||
// HTML Tags to parse
|
||||
'tagNames' => [
|
||||
'translate',
|
||||
],
|
||||
// HTML tags to parse when attribute exists
|
||||
'tagAttributes' => [
|
||||
'v-translate',
|
||||
],
|
||||
// Comments
|
||||
'commentAttributes' => [
|
||||
'translate-comment',
|
||||
],
|
||||
'contextAttributes' => [
|
||||
'translate-context',
|
||||
],
|
||||
// Attribute with plural content
|
||||
'pluralAttributes' => [
|
||||
'translate-plural',
|
||||
],
|
||||
];
|
||||
|
||||
// Ok, this is the weirdest hack, but let me explain:
|
||||
// On Linux (Mac is fine), when converting HTML to DOM, new lines get trimmed after the first tag.
|
||||
// So if there are new lines between <template> and next element, they are lost
|
||||
// So we insert a "." which is a text node, and it will prevent that newlines are stripped between elements.
|
||||
// Same thing happens between template and script tag.
|
||||
$string = str_replace('<template>', '<template>.', $string);
|
||||
$string = str_replace('</template>', '</template>.', $string);
|
||||
|
||||
// Normalize newlines
|
||||
$string = str_replace(["\r\n", "\n\r", "\r"], "\n", $string);
|
||||
|
||||
// VueJS files are valid HTML files, we will operate with the DOM here
|
||||
$dom = static::convertHtmlToDom($string);
|
||||
|
||||
$script = static::extractScriptTag($string);
|
||||
|
||||
// Parse the script part as a regular JS code
|
||||
if ($script) {
|
||||
$scriptLineNumber = $dom->getElementsByTagName('script')->item(0)->getLineNo();
|
||||
static::getScriptTranslationsFromString(
|
||||
$script,
|
||||
$translations,
|
||||
$options,
|
||||
$scriptLineNumber - 1
|
||||
);
|
||||
}
|
||||
|
||||
// Template part is parsed separately, all variables will be extracted
|
||||
// and handled as a regular JS code
|
||||
$template = $dom->getElementsByTagName('template')->item(0);
|
||||
if ($template) {
|
||||
static::getTemplateTranslations(
|
||||
$template,
|
||||
$translations,
|
||||
$options,
|
||||
$template->getLineNo() - 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts script tag contents using regex instead of DOM operations.
|
||||
* If we parse using DOM, some contents may change, for example, tags within strings will be stripped
|
||||
*
|
||||
* @param $string
|
||||
* @return bool|string
|
||||
*/
|
||||
protected static function extractScriptTag($string)
|
||||
{
|
||||
if (preg_match('#<\s*?script\b[^>]*>(.*?)</script\b[^>]*>#s', $string, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $html
|
||||
* @return DOMDocument
|
||||
*/
|
||||
protected static function convertHtmlToDom($html)
|
||||
{
|
||||
$dom = new DOMDocument;
|
||||
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
// Prepend xml encoding so DOMDocument document handles UTF8 correctly.
|
||||
// Assuming that vue template files will not have any xml encoding tags, because duplicate tags may be ignored.
|
||||
$dom->loadHTML('<?xml encoding="utf-8"?>' . $html);
|
||||
|
||||
libxml_clear_errors();
|
||||
|
||||
return $dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract translations from script part
|
||||
*
|
||||
* @param string $scriptContents Only script tag contents, not the whole template
|
||||
* @param Translations|Translations[] $translations One or multiple domain Translation objects
|
||||
* @param array $options
|
||||
* @param int $lineOffset Number of lines the script is offset in the vue template file
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static function getScriptTranslationsFromString(
|
||||
$scriptContents,
|
||||
$translations,
|
||||
array $options = [],
|
||||
$lineOffset = 0
|
||||
) {
|
||||
/** @var FunctionsScanner $functions */
|
||||
$functions = new static::$functionsScannerClass($scriptContents);
|
||||
$options['lineOffset'] = $lineOffset;
|
||||
$functions->saveGettextFunctions($translations, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse template to extract all translations (element content and dynamic element attributes)
|
||||
*
|
||||
* @param DOMNode $dom
|
||||
* @param Translations|Translations[] $translations One or multiple domain Translation objects
|
||||
* @param array $options
|
||||
* @param int $lineOffset Line number where the template part starts in the vue file
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static function getTemplateTranslations(
|
||||
DOMNode $dom,
|
||||
$translations,
|
||||
array $options,
|
||||
$lineOffset = 0
|
||||
) {
|
||||
// Build a JS string from all template attribute expressions
|
||||
$fakeAttributeJs = static::getTemplateAttributeFakeJs($options, $dom);
|
||||
|
||||
// 1 line offset is necessary because parent template element was ignored when converting to DOM
|
||||
static::getScriptTranslationsFromString($fakeAttributeJs, $translations, $options, $lineOffset);
|
||||
|
||||
// Build a JS string from template element content expressions
|
||||
$fakeTemplateJs = static::getTemplateFakeJs($dom);
|
||||
static::getScriptTranslationsFromString($fakeTemplateJs, $translations, $options, $lineOffset);
|
||||
|
||||
static::getTagTranslations($options, $dom, $translations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @param DOMNode $dom
|
||||
* @param Translations|Translations[] $translations
|
||||
*/
|
||||
protected static function getTagTranslations(array $options, DOMNode $dom, $translations)
|
||||
{
|
||||
// Since tag scanning does not support domains, we always use the first translation given
|
||||
$translations = is_array($translations) ? reset($translations) : $translations;
|
||||
|
||||
$children = $dom->childNodes;
|
||||
for ($i = 0; $i < $children->length; $i++) {
|
||||
$node = $children->item($i);
|
||||
|
||||
if (!($node instanceof DOMElement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$translatable = false;
|
||||
|
||||
if (in_array($node->tagName, $options['tagNames'], true)) {
|
||||
$translatable = true;
|
||||
}
|
||||
|
||||
$attrList = $node->attributes;
|
||||
$context = null;
|
||||
$plural = "";
|
||||
$comment = null;
|
||||
|
||||
for ($j = 0; $j < $attrList->length; $j++) {
|
||||
/** @var DOMAttr $domAttr */
|
||||
$domAttr = $attrList->item($j);
|
||||
// Check if this is a dynamic vue attribute
|
||||
if (in_array($domAttr->name, $options['tagAttributes'])) {
|
||||
$translatable = true;
|
||||
}
|
||||
if (in_array($domAttr->name, $options['contextAttributes'])) {
|
||||
$context = $domAttr->value;
|
||||
}
|
||||
if (in_array($domAttr->name, $options['pluralAttributes'])) {
|
||||
$plural = $domAttr->value;
|
||||
}
|
||||
if (in_array($domAttr->name, $options['commentAttributes'])) {
|
||||
$comment = $domAttr->value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($translatable) {
|
||||
$translation = $translations->insert($context, trim($node->textContent), $plural);
|
||||
$translation->addReference($options['file'], $node->getLineNo());
|
||||
if ($comment) {
|
||||
$translation->addExtractedComment($comment);
|
||||
}
|
||||
}
|
||||
|
||||
if ($node->hasChildNodes()) {
|
||||
static::getTagTranslations($options, $node, $translations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract JS expressions from element attribute bindings (excluding text within elements)
|
||||
* For example: <span :title="__('extract this')"> skip element content </span>
|
||||
*
|
||||
* @param array $options
|
||||
* @param DOMNode $dom
|
||||
* @return string JS code
|
||||
*/
|
||||
protected static function getTemplateAttributeFakeJs(array $options, DOMNode $dom)
|
||||
{
|
||||
$expressionsByLine = static::getVueAttributeExpressions($options['attributePrefixes'], $dom);
|
||||
|
||||
if (empty($expressionsByLine)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$maxLines = max(array_keys($expressionsByLine));
|
||||
$fakeJs = '';
|
||||
|
||||
for ($line = 1; $line <= $maxLines; $line++) {
|
||||
if (isset($expressionsByLine[$line])) {
|
||||
$fakeJs .= implode("; ", $expressionsByLine[$line]);
|
||||
}
|
||||
$fakeJs .= "\n";
|
||||
}
|
||||
|
||||
return $fakeJs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop DOM element recursively and parse out all dynamic vue attributes which are basically JS expressions
|
||||
*
|
||||
* @param array $attributePrefixes List of attribute prefixes we parse as JS (may contain translations)
|
||||
* @param DOMNode $dom
|
||||
* @param array $expressionByLine [lineNumber => [jsExpression, ..], ..]
|
||||
* @return array [lineNumber => [jsExpression, ..], ..]
|
||||
*/
|
||||
protected static function getVueAttributeExpressions(
|
||||
array $attributePrefixes,
|
||||
DOMNode $dom,
|
||||
array &$expressionByLine = []
|
||||
) {
|
||||
$children = $dom->childNodes;
|
||||
|
||||
for ($i = 0; $i < $children->length; $i++) {
|
||||
$node = $children->item($i);
|
||||
|
||||
if (!($node instanceof DOMElement)) {
|
||||
continue;
|
||||
}
|
||||
$attrList = $node->attributes;
|
||||
|
||||
for ($j = 0; $j < $attrList->length; $j++) {
|
||||
/** @var DOMAttr $domAttr */
|
||||
$domAttr = $attrList->item($j);
|
||||
|
||||
// Check if this is a dynamic vue attribute
|
||||
if (static::isAttributeMatching($domAttr->name, $attributePrefixes)) {
|
||||
$line = $domAttr->getLineNo();
|
||||
$expressionByLine += [$line => []];
|
||||
$expressionByLine[$line][] = $domAttr->value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($node->hasChildNodes()) {
|
||||
$expressionByLine = static::getVueAttributeExpressions($attributePrefixes, $node, $expressionByLine);
|
||||
}
|
||||
}
|
||||
|
||||
return $expressionByLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this attribute name should be parsed for translations
|
||||
*
|
||||
* @param string $attributeName
|
||||
* @param string[] $attributePrefixes
|
||||
* @return bool
|
||||
*/
|
||||
protected static function isAttributeMatching($attributeName, $attributePrefixes)
|
||||
{
|
||||
foreach ($attributePrefixes as $prefix) {
|
||||
if (strpos($attributeName, $prefix) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract JS expressions from within template elements (excluding attributes)
|
||||
* For example: <span :title="skip attributes"> {{__("extract element content")}} </span>
|
||||
*
|
||||
* @param DOMNode $dom
|
||||
* @return string JS code
|
||||
*/
|
||||
protected static function getTemplateFakeJs(DOMNode $dom)
|
||||
{
|
||||
$fakeJs = '';
|
||||
$lines = explode("\n", $dom->textContent);
|
||||
|
||||
// Build a fake JS file from template by extracting JS expressions within each template line
|
||||
foreach ($lines as $line) {
|
||||
$expressionMatched = static::parseOneTemplateLine($line);
|
||||
|
||||
$fakeJs .= implode("; ", $expressionMatched) . "\n";
|
||||
}
|
||||
|
||||
return $fakeJs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match JS expressions in a template line
|
||||
*
|
||||
* @param string $line
|
||||
* @return string[]
|
||||
*/
|
||||
protected static function parseOneTemplateLine($line)
|
||||
{
|
||||
$line = trim($line);
|
||||
|
||||
if (!$line) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$regex = '#\{\{(.*?)\}\}#';
|
||||
|
||||
preg_match_all($regex, $line, $matches);
|
||||
|
||||
$matched = array_map(function ($v) {
|
||||
return trim($v, '\'"{}');
|
||||
}, $matches[1]);
|
||||
|
||||
return $matched;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Translation;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from xliff format.
|
||||
*/
|
||||
class Xliff extends Extractor implements ExtractorInterface
|
||||
{
|
||||
|
||||
public static $options = [
|
||||
'unitid_as_id' => false
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
|
||||
$xml = new SimpleXMLElement($string, null, false);
|
||||
|
||||
foreach ($xml->file as $file) {
|
||||
if (isset($file->notes)) {
|
||||
foreach ($file->notes->note as $note) {
|
||||
$translations->setHeader($note['id'], (string) $note);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($file->unit as $unit) {
|
||||
foreach ($unit->segment as $segment) {
|
||||
$targets = [];
|
||||
|
||||
foreach ($segment->target as $target) {
|
||||
$targets[] = (string) $target;
|
||||
}
|
||||
|
||||
$translation = $translations->createNewTranslation(null, (string) $segment->source);
|
||||
if (isset($unit['id'])) {
|
||||
$unitId = (string) $unit['id'];
|
||||
$translation->addComment("XLIFF_UNIT_ID: $unitId");
|
||||
if ($options['unitid_as_id']) {
|
||||
$translation->setId($unitId);
|
||||
}
|
||||
}
|
||||
$translation->setTranslation(array_shift($targets));
|
||||
$translation->setPluralTranslations($targets);
|
||||
|
||||
if (isset($unit->notes)) {
|
||||
foreach ($unit->notes->note as $note) {
|
||||
switch ($note['category']) {
|
||||
case 'context':
|
||||
$translation = $translation->getClone((string) $note);
|
||||
break;
|
||||
|
||||
case 'extracted-comment':
|
||||
$translation->addExtractedComment((string) $note);
|
||||
break;
|
||||
|
||||
case 'flag':
|
||||
$translation->addFlag((string) $note);
|
||||
break;
|
||||
|
||||
case 'reference':
|
||||
$ref = explode(':', (string) $note, 2);
|
||||
$translation->addReference($ref[0], isset($ref[1]) ? $ref[1] : null);
|
||||
break;
|
||||
|
||||
default:
|
||||
$translation->addComment((string) $note);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$translations[] = $translation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\MultidimensionalArrayTrait;
|
||||
use Symfony\Component\Yaml\Yaml as YamlParser;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from yaml.
|
||||
*/
|
||||
class Yaml extends Extractor implements ExtractorInterface
|
||||
{
|
||||
use MultidimensionalArrayTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
$messages = YamlParser::parse($string);
|
||||
|
||||
if (is_array($messages)) {
|
||||
static::fromArray($messages, $translations);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Extractors;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\DictionaryTrait;
|
||||
use Symfony\Component\Yaml\Yaml as YamlParser;
|
||||
|
||||
/**
|
||||
* Class to get gettext strings from yaml.
|
||||
*/
|
||||
class YamlDictionary extends Extractor implements ExtractorInterface
|
||||
{
|
||||
use DictionaryTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromString($string, Translations $translations, array $options = [])
|
||||
{
|
||||
$messages = YamlParser::parse($string);
|
||||
|
||||
if (is_array($messages)) {
|
||||
static::fromArray($messages, $translations);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Generators;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\HeadersGeneratorTrait;
|
||||
use Gettext\Utils\CsvTrait;
|
||||
|
||||
/**
|
||||
* Class to export translations to csv.
|
||||
*/
|
||||
class Csv extends Generator implements GeneratorInterface
|
||||
{
|
||||
use HeadersGeneratorTrait;
|
||||
use CsvTrait;
|
||||
|
||||
public static $options = [
|
||||
'includeHeaders' => false,
|
||||
'delimiter' => ",",
|
||||
'enclosure' => '"',
|
||||
'escape_char' => "\\"
|
||||
];
|
||||
|
||||
/**
|
||||
* {@parentDoc}.
|
||||
*/
|
||||
public static function toString(Translations $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
$handle = fopen('php://memory', 'w');
|
||||
|
||||
if ($options['includeHeaders']) {
|
||||
static::fputcsv($handle, ['', '', static::generateHeaders($translations)], $options);
|
||||
}
|
||||
|
||||
foreach ($translations as $translation) {
|
||||
if ($translation->isDisabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$line = [$translation->getContext(), $translation->getOriginal(), $translation->getTranslation()];
|
||||
|
||||
if ($translation->hasPluralTranslations(true)) {
|
||||
$line = array_merge($line, $translation->getPluralTranslations());
|
||||
}
|
||||
|
||||
static::fputcsv($handle, $line, $options);
|
||||
}
|
||||
|
||||
rewind($handle);
|
||||
$csv = stream_get_contents($handle);
|
||||
fclose($handle);
|
||||
|
||||
return $csv;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Generators;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\DictionaryTrait;
|
||||
use Gettext\Utils\CsvTrait;
|
||||
|
||||
class CsvDictionary extends Generator implements GeneratorInterface
|
||||
{
|
||||
use DictionaryTrait;
|
||||
use CsvTrait;
|
||||
|
||||
public static $options = [
|
||||
'includeHeaders' => false,
|
||||
'delimiter' => ",",
|
||||
'enclosure' => '"',
|
||||
'escape_char' => "\\"
|
||||
];
|
||||
|
||||
/**
|
||||
* {@parentDoc}.
|
||||
*/
|
||||
public static function toString(Translations $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
$handle = fopen('php://memory', 'w');
|
||||
|
||||
foreach (static::toArray($translations, $options['includeHeaders']) as $original => $translation) {
|
||||
static::fputcsv($handle, [$original, $translation], $options);
|
||||
}
|
||||
|
||||
rewind($handle);
|
||||
$csv = stream_get_contents($handle);
|
||||
fclose($handle);
|
||||
|
||||
return $csv;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Generators;
|
||||
|
||||
use Gettext\Translations;
|
||||
|
||||
abstract class Generator implements GeneratorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function toFile(Translations $translations, $file, array $options = [])
|
||||
{
|
||||
$content = static::toString($translations, $options);
|
||||
|
||||
if (file_put_contents($file, $content) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Generators;
|
||||
|
||||
use Gettext\Translations;
|
||||
|
||||
interface GeneratorInterface
|
||||
{
|
||||
/**
|
||||
* Saves the translations in a file.
|
||||
*
|
||||
* @param Translations $translations
|
||||
* @param string $file
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function toFile(Translations $translations, $file, array $options = []);
|
||||
|
||||
/**
|
||||
* Generates a string with the translations ready to save in a file.
|
||||
*
|
||||
* @param Translations $translations
|
||||
* @param array $options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function toString(Translations $translations, array $options = []);
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Generators;
|
||||
|
||||
use Gettext\Translations;
|
||||
|
||||
class Jed extends Generator implements GeneratorInterface
|
||||
{
|
||||
public static $options = [
|
||||
'json' => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@parentDoc}.
|
||||
*/
|
||||
public static function toString(Translations $translations, array $options = [])
|
||||
{
|
||||
$domain = $translations->getDomain() ?: 'messages';
|
||||
$options += static::$options;
|
||||
|
||||
return json_encode([
|
||||
$domain => [
|
||||
'' => [
|
||||
'domain' => $domain,
|
||||
'lang' => $translations->getLanguage() ?: 'en',
|
||||
'plural-forms' => $translations->getHeader('Plural-Forms') ?: 'nplurals=2; plural=(n != 1);',
|
||||
],
|
||||
] + static::buildMessages($translations),
|
||||
], $options['json']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array with all translations.
|
||||
*
|
||||
* @param Translations $translations
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function buildMessages(Translations $translations)
|
||||
{
|
||||
$pluralForm = $translations->getPluralForms();
|
||||
$pluralSize = is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
|
||||
$messages = [];
|
||||
$context_glue = '\u0004';
|
||||
|
||||
foreach ($translations as $translation) {
|
||||
if ($translation->isDisabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = ($translation->hasContext() ? $translation->getContext().$context_glue : '')
|
||||
.$translation->getOriginal();
|
||||
|
||||
if ($translation->hasPluralTranslations(true)) {
|
||||
$message = $translation->getPluralTranslations($pluralSize);
|
||||
array_unshift($message, $translation->getTranslation());
|
||||
} else {
|
||||
$message = [$translation->getTranslation()];
|
||||
}
|
||||
|
||||
$messages[$key] = $message;
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Generators;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\MultidimensionalArrayTrait;
|
||||
|
||||
class Json extends Generator implements GeneratorInterface
|
||||
{
|
||||
use MultidimensionalArrayTrait;
|
||||
|
||||
public static $options = [
|
||||
'json' => 0,
|
||||
'includeHeaders' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function toString(Translations $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
|
||||
return json_encode(static::toArray($translations, $options['includeHeaders'], true), $options['json']);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Generators;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\DictionaryTrait;
|
||||
|
||||
class JsonDictionary extends Generator implements GeneratorInterface
|
||||
{
|
||||
use DictionaryTrait;
|
||||
|
||||
public static $options = [
|
||||
'json' => 0,
|
||||
'includeHeaders' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@parentDoc}.
|
||||
*/
|
||||
public static function toString(Translations $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
|
||||
return json_encode(static::toArray($translations, $options['includeHeaders']), $options['json']);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Generators;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\HeadersGeneratorTrait;
|
||||
|
||||
class Mo extends Generator implements GeneratorInterface
|
||||
{
|
||||
use HeadersGeneratorTrait;
|
||||
|
||||
public static $options = [
|
||||
'includeHeaders' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@parentDoc}.
|
||||
*/
|
||||
public static function toString(Translations $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
$messages = [];
|
||||
|
||||
if ($options['includeHeaders']) {
|
||||
$messages[''] = static::generateHeaders($translations);
|
||||
}
|
||||
|
||||
foreach ($translations as $translation) {
|
||||
if (!$translation->hasTranslation() || $translation->isDisabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($translation->hasContext()) {
|
||||
$originalString = $translation->getContext()."\x04".$translation->getOriginal();
|
||||
} else {
|
||||
$originalString = $translation->getOriginal();
|
||||
}
|
||||
|
||||
$messages[$originalString] = $translation;
|
||||
}
|
||||
|
||||
ksort($messages);
|
||||
$numEntries = count($messages);
|
||||
$originalsTable = '';
|
||||
$translationsTable = '';
|
||||
$originalsIndex = [];
|
||||
$translationsIndex = [];
|
||||
$pluralForm = $translations->getPluralForms();
|
||||
$pluralSize = is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
|
||||
|
||||
foreach ($messages as $originalString => $translation) {
|
||||
if (is_string($translation)) {
|
||||
// Headers
|
||||
$translationString = $translation;
|
||||
} else {
|
||||
/* @var $translation \Gettext\Translation */
|
||||
if ($translation->hasPlural() && $translation->hasPluralTranslations(true)) {
|
||||
$originalString .= "\x00".$translation->getPlural();
|
||||
$translationString = $translation->getTranslation();
|
||||
$translationString .= "\x00".implode("\x00", $translation->getPluralTranslations($pluralSize));
|
||||
} else {
|
||||
$translationString = $translation->getTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
$originalsIndex[] = [
|
||||
'relativeOffset' => strlen($originalsTable),
|
||||
'length' => strlen($originalString)
|
||||
];
|
||||
$originalsTable .= $originalString."\x00";
|
||||
$translationsIndex[] = [
|
||||
'relativeOffset' => strlen($translationsTable),
|
||||
'length' => strlen($translationString)
|
||||
];
|
||||
$translationsTable .= $translationString."\x00";
|
||||
}
|
||||
|
||||
// Offset of table with the original strings index: right after the header (which is 7 words)
|
||||
$originalsIndexOffset = 7 * 4;
|
||||
|
||||
// Size of table with the original strings index
|
||||
$originalsIndexSize = $numEntries * (4 + 4);
|
||||
|
||||
// Offset of table with the translation strings index: right after the original strings index table
|
||||
$translationsIndexOffset = $originalsIndexOffset + $originalsIndexSize;
|
||||
|
||||
// Size of table with the translation strings index
|
||||
$translationsIndexSize = $numEntries * (4 + 4);
|
||||
|
||||
// Hashing table starts after the header and after the index table
|
||||
$originalsStringsOffset = $translationsIndexOffset + $translationsIndexSize;
|
||||
|
||||
// Translations start after the keys
|
||||
$translationsStringsOffset = $originalsStringsOffset + strlen($originalsTable);
|
||||
|
||||
// Let's generate the .mo file binary data
|
||||
$mo = '';
|
||||
|
||||
// Magic number
|
||||
$mo .= pack('L', 0x950412de);
|
||||
|
||||
// File format revision
|
||||
$mo .= pack('L', 0);
|
||||
|
||||
// Number of strings
|
||||
$mo .= pack('L', $numEntries);
|
||||
|
||||
// Offset of table with original strings
|
||||
$mo .= pack('L', $originalsIndexOffset);
|
||||
|
||||
// Offset of table with translation strings
|
||||
$mo .= pack('L', $translationsIndexOffset);
|
||||
|
||||
// Size of hashing table: we don't use it.
|
||||
$mo .= pack('L', 0);
|
||||
|
||||
// Offset of hashing table: it would start right after the translations index table
|
||||
$mo .= pack('L', $translationsIndexOffset + $translationsIndexSize);
|
||||
|
||||
// Write the lengths & offsets of the original strings
|
||||
foreach ($originalsIndex as $info) {
|
||||
$mo .= pack('L', $info['length']);
|
||||
$mo .= pack('L', $originalsStringsOffset + $info['relativeOffset']);
|
||||
}
|
||||
|
||||
// Write the lengths & offsets of the translated strings
|
||||
foreach ($translationsIndex as $info) {
|
||||
$mo .= pack('L', $info['length']);
|
||||
$mo .= pack('L', $translationsStringsOffset + $info['relativeOffset']);
|
||||
}
|
||||
|
||||
// Write original strings
|
||||
$mo .= $originalsTable;
|
||||
|
||||
// Write translation strings
|
||||
$mo .= $translationsTable;
|
||||
|
||||
return $mo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Generators;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\MultidimensionalArrayTrait;
|
||||
|
||||
class PhpArray extends Generator implements GeneratorInterface
|
||||
{
|
||||
use MultidimensionalArrayTrait;
|
||||
|
||||
public static $options = [
|
||||
'includeHeaders' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function toString(Translations $translations, array $options = [])
|
||||
{
|
||||
$array = static::generate($translations, $options);
|
||||
|
||||
return '<?php return '.var_export($array, true).';';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array with the translations.
|
||||
*
|
||||
* @param Translations $translations
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function generate(Translations $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
|
||||
return static::toArray($translations, $options['includeHeaders'], true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Generators;
|
||||
|
||||
use Gettext\Translations;
|
||||
|
||||
class Po extends Generator implements GeneratorInterface
|
||||
{
|
||||
public static $options = [
|
||||
'noLocation' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@parentDoc}.
|
||||
*/
|
||||
public static function toString(Translations $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
|
||||
$pluralForm = $translations->getPluralForms();
|
||||
$pluralSize = is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
|
||||
$lines = ['msgid ""', 'msgstr ""'];
|
||||
|
||||
foreach ($translations->getHeaders() as $name => $value) {
|
||||
$lines[] = sprintf('"%s: %s\\n"', $name, $value);
|
||||
}
|
||||
|
||||
$lines[] = '';
|
||||
|
||||
//Translations
|
||||
foreach ($translations as $translation) {
|
||||
if ($translation->hasComments()) {
|
||||
foreach ($translation->getComments() as $comment) {
|
||||
$lines[] = '# '.$comment;
|
||||
}
|
||||
}
|
||||
|
||||
if ($translation->hasExtractedComments()) {
|
||||
foreach ($translation->getExtractedComments() as $comment) {
|
||||
$lines[] = '#. '.$comment;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$options['noLocation'] && $translation->hasReferences()) {
|
||||
foreach ($translation->getReferences() as $reference) {
|
||||
$lines[] = '#: '.$reference[0].(!is_null($reference[1]) ? ':'.$reference[1] : null);
|
||||
}
|
||||
}
|
||||
|
||||
if ($translation->hasFlags()) {
|
||||
$lines[] = '#, '.implode(',', $translation->getFlags());
|
||||
}
|
||||
|
||||
$prefix = $translation->isDisabled() ? '#~ ' : '';
|
||||
|
||||
if ($translation->hasContext()) {
|
||||
$lines[] = $prefix.'msgctxt '.static::convertString($translation->getContext());
|
||||
}
|
||||
|
||||
static::addLines($lines, $prefix.'msgid', $translation->getOriginal());
|
||||
|
||||
if ($translation->hasPlural()) {
|
||||
static::addLines($lines, $prefix.'msgid_plural', $translation->getPlural());
|
||||
static::addLines($lines, $prefix.'msgstr[0]', $translation->getTranslation());
|
||||
|
||||
foreach ($translation->getPluralTranslations($pluralSize) as $k => $v) {
|
||||
static::addLines($lines, $prefix.'msgstr['.($k + 1).']', $v);
|
||||
}
|
||||
} else {
|
||||
static::addLines($lines, $prefix.'msgstr', $translation->getTranslation());
|
||||
}
|
||||
|
||||
$lines[] = '';
|
||||
}
|
||||
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes and adds double quotes to a string.
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function multilineQuote($string)
|
||||
{
|
||||
$lines = explode("\n", $string);
|
||||
$last = count($lines) - 1;
|
||||
|
||||
foreach ($lines as $k => $line) {
|
||||
if ($k === $last) {
|
||||
$lines[$k] = static::convertString($line);
|
||||
} else {
|
||||
$lines[$k] = static::convertString($line."\n");
|
||||
}
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more lines depending whether the string is multiline or not.
|
||||
*
|
||||
* @param array &$lines
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
*/
|
||||
protected static function addLines(array &$lines, $name, $value)
|
||||
{
|
||||
$newLines = static::multilineQuote($value);
|
||||
|
||||
if (count($newLines) === 1) {
|
||||
$lines[] = $name.' '.$newLines[0];
|
||||
} else {
|
||||
$lines[] = $name.' ""';
|
||||
|
||||
foreach ($newLines as $line) {
|
||||
$lines[] = $line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to its PO representation.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function convertString($value)
|
||||
{
|
||||
return '"'.strtr(
|
||||
$value,
|
||||
[
|
||||
"\x00" => '',
|
||||
'\\' => '\\\\',
|
||||
"\t" => '\t',
|
||||
"\r" => '\r',
|
||||
"\n" => '\n',
|
||||
'"' => '\\"',
|
||||
]
|
||||
).'"';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Generators;
|
||||
|
||||
use Gettext\Translation;
|
||||
use Gettext\Translations;
|
||||
use DOMDocument;
|
||||
|
||||
class Xliff extends Generator implements GeneratorInterface
|
||||
{
|
||||
const UNIT_ID_REGEXP = '/^XLIFF_UNIT_ID: (.*)$/';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function toString(Translations $translations, array $options = [])
|
||||
{
|
||||
$dom = new DOMDocument('1.0', 'utf-8');
|
||||
$dom->formatOutput = true;
|
||||
$xliff = $dom->appendChild($dom->createElement('xliff'));
|
||||
$xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0');
|
||||
$xliff->setAttribute('version', '2.0');
|
||||
$xliff->setAttribute('srcLang', $translations->getLanguage());
|
||||
$xliff->setAttribute('trgLang', $translations->getLanguage());
|
||||
$file = $xliff->appendChild($dom->createElement('file'));
|
||||
$file->setAttribute('id', $translations->getDomain().'.'.$translations->getLanguage());
|
||||
|
||||
//Save headers as notes
|
||||
$notes = $dom->createElement('notes');
|
||||
|
||||
foreach ($translations->getHeaders() as $name => $value) {
|
||||
$notes->appendChild(static::createTextNode($dom, 'note', $value))->setAttribute('id', $name);
|
||||
}
|
||||
|
||||
if ($notes->hasChildNodes()) {
|
||||
$file->appendChild($notes);
|
||||
}
|
||||
|
||||
foreach ($translations as $translation) {
|
||||
//Find an XLIFF unit ID, if one is available; otherwise generate
|
||||
$unitId = static::getUnitID($translation)?:md5($translation->getContext().$translation->getOriginal());
|
||||
|
||||
$unit = $dom->createElement('unit');
|
||||
$unit->setAttribute('id', $unitId);
|
||||
|
||||
//Save comments as notes
|
||||
$notes = $dom->createElement('notes');
|
||||
|
||||
$notes->appendChild(static::createTextNode($dom, 'note', $translation->getContext()))
|
||||
->setAttribute('category', 'context');
|
||||
|
||||
foreach ($translation->getComments() as $comment) {
|
||||
//Skip XLIFF unit ID comments.
|
||||
if (preg_match(static::UNIT_ID_REGEXP, $comment)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notes->appendChild(static::createTextNode($dom, 'note', $comment))
|
||||
->setAttribute('category', 'comment');
|
||||
}
|
||||
|
||||
foreach ($translation->getExtractedComments() as $comment) {
|
||||
$notes->appendChild(static::createTextNode($dom, 'note', $comment))
|
||||
->setAttribute('category', 'extracted-comment');
|
||||
}
|
||||
|
||||
foreach ($translation->getFlags() as $flag) {
|
||||
$notes->appendChild(static::createTextNode($dom, 'note', $flag))
|
||||
->setAttribute('category', 'flag');
|
||||
}
|
||||
|
||||
foreach ($translation->getReferences() as $reference) {
|
||||
$notes->appendChild(static::createTextNode($dom, 'note', $reference[0].':'.$reference[1]))
|
||||
->setAttribute('category', 'reference');
|
||||
}
|
||||
|
||||
$unit->appendChild($notes);
|
||||
|
||||
$segment = $unit->appendChild($dom->createElement('segment'));
|
||||
$segment->appendChild(static::createTextNode($dom, 'source', $translation->getOriginal()));
|
||||
$segment->appendChild(static::createTextNode($dom, 'target', $translation->getTranslation()));
|
||||
|
||||
foreach ($translation->getPluralTranslations() as $plural) {
|
||||
if ($plural !== '') {
|
||||
$segment->appendChild(static::createTextNode($dom, 'target', $plural));
|
||||
}
|
||||
}
|
||||
|
||||
$file->appendChild($unit);
|
||||
}
|
||||
|
||||
return $dom->saveXML();
|
||||
}
|
||||
|
||||
protected static function createTextNode(DOMDocument $dom, $name, $string)
|
||||
{
|
||||
$node = $dom->createElement($name);
|
||||
$text = (preg_match('/[&<>]/', $string) === 1)
|
||||
? $dom->createCDATASection($string)
|
||||
: $dom->createTextNode($string);
|
||||
$node->appendChild($text);
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translation's unit ID, if one is available.
|
||||
*
|
||||
* @param Translation $translation
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getUnitID(Translation $translation)
|
||||
{
|
||||
foreach ($translation->getComments() as $comment) {
|
||||
if (preg_match(static::UNIT_ID_REGEXP, $comment, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Generators;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\MultidimensionalArrayTrait;
|
||||
use Symfony\Component\Yaml\Yaml as YamlDumper;
|
||||
|
||||
class Yaml extends Generator implements GeneratorInterface
|
||||
{
|
||||
use MultidimensionalArrayTrait;
|
||||
|
||||
public static $options = [
|
||||
'includeHeaders' => false,
|
||||
'indent' => 2,
|
||||
'inline' => 4,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function toString(Translations $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
|
||||
return YamlDumper::dump(
|
||||
static::toArray($translations, $options['includeHeaders']),
|
||||
$options['inline'],
|
||||
$options['indent']
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Generators;
|
||||
|
||||
use Gettext\Translations;
|
||||
use Gettext\Utils\DictionaryTrait;
|
||||
use Symfony\Component\Yaml\Yaml as YamlDumper;
|
||||
|
||||
class YamlDictionary extends Generator implements GeneratorInterface
|
||||
{
|
||||
use DictionaryTrait;
|
||||
|
||||
public static $options = [
|
||||
'includeHeaders' => false,
|
||||
'indent' => 2,
|
||||
'inline' => 3,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function toString(Translations $translations, array $options = [])
|
||||
{
|
||||
$options += static::$options;
|
||||
|
||||
return YamlDumper::dump(
|
||||
static::toArray($translations, $options['includeHeaders']),
|
||||
$options['inline'],
|
||||
$options['indent']
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext;
|
||||
|
||||
class GettextTranslator extends BaseTranslator implements TranslatorInterface
|
||||
{
|
||||
/**
|
||||
* Constructor. Detects the current language using the environment variables.
|
||||
*
|
||||
* @param string $language
|
||||
*/
|
||||
public function __construct($language = null)
|
||||
{
|
||||
if (!function_exists('gettext')) {
|
||||
throw new \RuntimeException('This class require the gettext extension for PHP');
|
||||
}
|
||||
|
||||
//detects the language environment respecting the priority order
|
||||
//http://php.net/manual/en/function.gettext.php#114062
|
||||
if (empty($language)) {
|
||||
$language = getenv('LANGUAGE') ?: getenv('LC_ALL') ?: getenv('LC_MESSAGES') ?: getenv('LANG');
|
||||
}
|
||||
|
||||
if (!empty($language)) {
|
||||
$this->setLanguage($language);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the current locale.
|
||||
*
|
||||
* @param string $language
|
||||
* @param int|null $category
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setLanguage($language, $category = null)
|
||||
{
|
||||
if ($category === null) {
|
||||
$category = defined('LC_MESSAGES') ? LC_MESSAGES : LC_ALL;
|
||||
}
|
||||
|
||||
setlocale($category, $language);
|
||||
putenv('LANGUAGE='.$language);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a gettext domain.
|
||||
*
|
||||
* @param string $domain
|
||||
* @param string $path
|
||||
* @param bool $default
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function loadDomain($domain, $path = null, $default = true)
|
||||
{
|
||||
bindtextdomain($domain, $path);
|
||||
bind_textdomain_codeset($domain, 'UTF-8');
|
||||
|
||||
if ($default) {
|
||||
textdomain($domain);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function gettext($original)
|
||||
{
|
||||
return gettext($original);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function ngettext($original, $plural, $value)
|
||||
{
|
||||
return ngettext($original, $plural, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dngettext($domain, $original, $plural, $value)
|
||||
{
|
||||
return dngettext($domain, $original, $plural, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function npgettext($context, $original, $plural, $value)
|
||||
{
|
||||
$message = $context."\x04".$original;
|
||||
$translation = ngettext($message, $plural, $value);
|
||||
|
||||
return ($translation === $message) ? $original : $translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function pgettext($context, $original)
|
||||
{
|
||||
$message = $context."\x04".$original;
|
||||
$translation = gettext($message);
|
||||
|
||||
return ($translation === $message) ? $original : $translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dgettext($domain, $original)
|
||||
{
|
||||
return dgettext($domain, $original);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dpgettext($domain, $context, $original)
|
||||
{
|
||||
$message = $context."\x04".$original;
|
||||
$translation = dgettext($domain, $message);
|
||||
|
||||
return ($translation === $message) ? $original : $translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dnpgettext($domain, $context, $original, $plural, $value)
|
||||
{
|
||||
$message = $context."\x04".$original;
|
||||
$translation = dngettext($domain, $message, $plural, $value);
|
||||
|
||||
return ($translation === $message) ? $original : $translation;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext;
|
||||
|
||||
/**
|
||||
* Static class with merge contants.
|
||||
*/
|
||||
final class Merge
|
||||
{
|
||||
const ADD = 1;
|
||||
const REMOVE = 2;
|
||||
|
||||
const HEADERS_ADD = 4;
|
||||
const HEADERS_REMOVE = 8;
|
||||
const HEADERS_OVERRIDE = 16;
|
||||
|
||||
const LANGUAGE_OVERRIDE = 32;
|
||||
const DOMAIN_OVERRIDE = 64;
|
||||
const TRANSLATION_OVERRIDE = 128;
|
||||
|
||||
const COMMENTS_OURS = 256;
|
||||
const COMMENTS_THEIRS = 512;
|
||||
|
||||
const EXTRACTED_COMMENTS_OURS = 1024;
|
||||
const EXTRACTED_COMMENTS_THEIRS = 2048;
|
||||
|
||||
const FLAGS_OURS = 4096;
|
||||
const FLAGS_THEIRS = 8192;
|
||||
|
||||
const REFERENCES_OURS = 16384;
|
||||
const REFERENCES_THEIRS = 32768;
|
||||
|
||||
const DEFAULTS = 5; //1 + 4
|
||||
|
||||
/**
|
||||
* Merge the flags of two translations.
|
||||
*
|
||||
* @param Translation $from
|
||||
* @param Translation $to
|
||||
* @param int $options
|
||||
*/
|
||||
public static function mergeFlags(Translation $from, Translation $to, $options = self::DEFAULTS)
|
||||
{
|
||||
if ($options & self::FLAGS_THEIRS) {
|
||||
$to->deleteFlags();
|
||||
}
|
||||
|
||||
if (!($options & self::FLAGS_OURS)) {
|
||||
foreach ($from->getFlags() as $flag) {
|
||||
$to->addFlag($flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the extracted comments of two translations.
|
||||
*
|
||||
* @param Translation $from
|
||||
* @param Translation $to
|
||||
* @param int $options
|
||||
*/
|
||||
public static function mergeExtractedComments(Translation $from, Translation $to, $options = self::DEFAULTS)
|
||||
{
|
||||
if ($options & self::EXTRACTED_COMMENTS_THEIRS) {
|
||||
$to->deleteExtractedComments();
|
||||
}
|
||||
|
||||
if (!($options & self::EXTRACTED_COMMENTS_OURS)) {
|
||||
foreach ($from->getExtractedComments() as $comment) {
|
||||
$to->addExtractedComment($comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the comments of two translations.
|
||||
*
|
||||
* @param Translation $from
|
||||
* @param Translation $to
|
||||
* @param int $options
|
||||
*/
|
||||
public static function mergeComments(Translation $from, Translation $to, $options = self::DEFAULTS)
|
||||
{
|
||||
if ($options & self::COMMENTS_THEIRS) {
|
||||
$to->deleteComments();
|
||||
}
|
||||
|
||||
if (!($options & self::COMMENTS_OURS)) {
|
||||
foreach ($from->getComments() as $comment) {
|
||||
$to->addComment($comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the references of two translations.
|
||||
*
|
||||
* @param Translation $from
|
||||
* @param Translation $to
|
||||
* @param int $options
|
||||
*/
|
||||
public static function mergeReferences(Translation $from, Translation $to, $options = self::DEFAULTS)
|
||||
{
|
||||
if ($options & self::REFERENCES_THEIRS) {
|
||||
$to->deleteReferences();
|
||||
}
|
||||
|
||||
if (!($options & self::REFERENCES_OURS)) {
|
||||
foreach ($from->getReferences() as $reference) {
|
||||
$to->addReference($reference[0], $reference[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the translations of two translations.
|
||||
*
|
||||
* @param Translation $from
|
||||
* @param Translation $to
|
||||
* @param int $options
|
||||
*/
|
||||
public static function mergeTranslation(Translation $from, Translation $to, $options = self::DEFAULTS)
|
||||
{
|
||||
$override = (boolean) ($options & self::TRANSLATION_OVERRIDE);
|
||||
|
||||
if (!$to->hasTranslation() || ($from->hasTranslation() && $override)) {
|
||||
$to->setTranslation($from->getTranslation());
|
||||
}
|
||||
|
||||
if (!$to->hasPlural() || ($from->hasPlural() && $override)) {
|
||||
$to->setPlural($from->getPlural());
|
||||
}
|
||||
|
||||
if (!$to->hasPluralTranslations() || ($from->hasPluralTranslations() && $override)) {
|
||||
$to->setPluralTranslations($from->getPluralTranslations());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the translations of two translations.
|
||||
*
|
||||
* @param Translations $from
|
||||
* @param Translations $to
|
||||
* @param int $options
|
||||
*/
|
||||
public static function mergeTranslations(Translations $from, Translations $to, $options = self::DEFAULTS)
|
||||
{
|
||||
if ($options & self::REMOVE) {
|
||||
$filtered = [];
|
||||
|
||||
foreach ($to as $entry) {
|
||||
if ($from->find($entry)) {
|
||||
$filtered[$entry->getId()] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
$to->exchangeArray($filtered);
|
||||
}
|
||||
|
||||
foreach ($from as $entry) {
|
||||
if (($existing = $to->find($entry))) {
|
||||
$existing->mergeWith($entry, $options);
|
||||
} elseif ($options & self::ADD) {
|
||||
$to[] = $entry->getClone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the headers of two translations.
|
||||
*
|
||||
* @param Translations $from
|
||||
* @param Translations $to
|
||||
* @param int $options
|
||||
*/
|
||||
public static function mergeHeaders(Translations $from, Translations $to, $options = self::DEFAULTS)
|
||||
{
|
||||
if ($options & self::HEADERS_REMOVE) {
|
||||
foreach (array_keys($to->getHeaders()) as $name) {
|
||||
if ($from->getHeader($name) === null) {
|
||||
$to->deleteHeader($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($from->getHeaders() as $name => $value) {
|
||||
$current = $to->getHeader($name);
|
||||
|
||||
if (empty($current)) {
|
||||
if ($options & self::HEADERS_ADD) {
|
||||
$to->setHeader($name, $value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($name) {
|
||||
case Translations::HEADER_LANGUAGE:
|
||||
case Translations::HEADER_PLURAL:
|
||||
if ($options & self::LANGUAGE_OVERRIDE) {
|
||||
$to->setHeader($name, $value);
|
||||
}
|
||||
break;
|
||||
|
||||
case Translations::HEADER_DOMAIN:
|
||||
if ($options & self::DOMAIN_OVERRIDE) {
|
||||
$to->setHeader($name, $value);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if ($options & self::HEADERS_OVERRIDE) {
|
||||
$to->setHeader($name, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,537 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext;
|
||||
|
||||
/**
|
||||
* Class to manage a translation string.
|
||||
*/
|
||||
class Translation
|
||||
{
|
||||
protected $id;
|
||||
protected $context;
|
||||
protected $original;
|
||||
protected $translation = '';
|
||||
protected $plural;
|
||||
protected $pluralTranslation = [];
|
||||
protected $references = [];
|
||||
protected $comments = [];
|
||||
protected $extractedComments = [];
|
||||
protected $flags = [];
|
||||
protected $disabled = false;
|
||||
|
||||
/**
|
||||
* Generates the id of a translation (context + glue + original).
|
||||
*
|
||||
* @param string $context
|
||||
* @param string $original
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generateId($context, $original)
|
||||
{
|
||||
return "{$context}\004{$original}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of a Translation object.
|
||||
*
|
||||
* This is a factory method that will work even when Translation is extended.
|
||||
*
|
||||
* @param string $context The context of the translation
|
||||
* @param string $original The original string
|
||||
* @param string $plural The original plural string
|
||||
* @return static New Translation instance
|
||||
*/
|
||||
public static function create($context, $original, $plural = '')
|
||||
{
|
||||
return new static($context, $original, $plural);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param string $context The context of the translation
|
||||
* @param string $original The original string
|
||||
* @param string $plural The original plural string
|
||||
*/
|
||||
public function __construct($context, $original, $plural = '')
|
||||
{
|
||||
$this->context = (string) $context;
|
||||
$this->original = (string) $original;
|
||||
|
||||
$this->setPlural($plural);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones this translation.
|
||||
*
|
||||
* @param null|string $context Optional new context
|
||||
* @param null|string $original Optional new original
|
||||
*
|
||||
* @return Translation
|
||||
*/
|
||||
public function getClone($context = null, $original = null)
|
||||
{
|
||||
$new = clone $this;
|
||||
|
||||
if ($context !== null) {
|
||||
$new->context = (string) $context;
|
||||
}
|
||||
|
||||
if ($original !== null) {
|
||||
$new->original = (string) $original;
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the id of this translation.
|
||||
* @warning The use of this function to set a custom ID will prevent
|
||||
* Translations::find from matching this translation.
|
||||
*
|
||||
* @param string $id
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the id of this translation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
if ($this->id === null) {
|
||||
return static::generateId($this->context, $this->original);
|
||||
}
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the translation matches with the arguments.
|
||||
*
|
||||
* @param string $context
|
||||
* @param string $original
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is($context, $original = '')
|
||||
{
|
||||
return (($this->context === $context) && ($this->original === $original)) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the translation
|
||||
*
|
||||
* @param bool $disabled
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDisabled($disabled)
|
||||
{
|
||||
$this->disabled = (bool) $disabled;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the translation is disabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDisabled()
|
||||
{
|
||||
return $this->disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the original string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOriginal()
|
||||
{
|
||||
return $this->original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the original string is empty or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOriginal()
|
||||
{
|
||||
return ($this->original !== '') ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the translation string.
|
||||
*
|
||||
* @param string $translation
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setTranslation($translation)
|
||||
{
|
||||
$this->translation = (string) $translation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translation string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTranslation()
|
||||
{
|
||||
return $this->translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the translation string is empty or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTranslation()
|
||||
{
|
||||
return ($this->translation !== '') ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the plural translation string.
|
||||
*
|
||||
* @param string $plural
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPlural($plural)
|
||||
{
|
||||
$this->plural = (string) $plural;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plural translation string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPlural()
|
||||
{
|
||||
return $this->plural;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the plural translation string is empty or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPlural()
|
||||
{
|
||||
return ($this->plural !== '') ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new plural translation.
|
||||
*
|
||||
* @param array $plural
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPluralTranslations(array $plural)
|
||||
{
|
||||
$this->pluralTranslation = $plural;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all plural translations.
|
||||
*
|
||||
* @param int $size
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPluralTranslations($size = null)
|
||||
{
|
||||
if ($size === null) {
|
||||
return $this->pluralTranslation;
|
||||
}
|
||||
|
||||
$current = count($this->pluralTranslation);
|
||||
|
||||
if ($size > $current) {
|
||||
return $this->pluralTranslation + array_fill(0, $size, '');
|
||||
}
|
||||
|
||||
if ($size < $current) {
|
||||
return array_slice($this->pluralTranslation, 0, $size);
|
||||
}
|
||||
|
||||
return $this->pluralTranslation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any plural translation.
|
||||
*
|
||||
* @param bool $checkContent
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPluralTranslations($checkContent = false)
|
||||
{
|
||||
if ($checkContent) {
|
||||
return implode('', $this->pluralTranslation) !== '';
|
||||
}
|
||||
|
||||
return !empty($this->pluralTranslation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all plural translations.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function deletePluralTranslation()
|
||||
{
|
||||
$this->pluralTranslation = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the context of this translation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContext()
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the context is empty or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasContext()
|
||||
{
|
||||
return (isset($this->context) && ($this->context !== '')) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new reference for this translation.
|
||||
*
|
||||
* @param string $filename The file path where the translation has been found
|
||||
* @param null|int $line The line number where the translation has been found
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function addReference($filename, $line = null)
|
||||
{
|
||||
$key = "{$filename}:{$line}";
|
||||
$this->references[$key] = [$filename, $line];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the translation has any reference.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasReferences()
|
||||
{
|
||||
return !empty($this->references);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all references for this translation.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getReferences()
|
||||
{
|
||||
return array_values($this->references);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all references.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function deleteReferences()
|
||||
{
|
||||
$this->references = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new comment for this translation.
|
||||
*
|
||||
* @param string $comment
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function addComment($comment)
|
||||
{
|
||||
if (!in_array($comment, $this->comments, true)) {
|
||||
$this->comments[] = $comment;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the translation has any comment.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasComments()
|
||||
{
|
||||
return isset($this->comments[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all comments for this translation.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getComments()
|
||||
{
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all comments.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function deleteComments()
|
||||
{
|
||||
$this->comments = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new extracted comment for this translation.
|
||||
*
|
||||
* @param string $comment
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function addExtractedComment($comment)
|
||||
{
|
||||
if (!in_array($comment, $this->extractedComments, true)) {
|
||||
$this->extractedComments[] = $comment;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the translation has any extracted comment.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExtractedComments()
|
||||
{
|
||||
return isset($this->extractedComments[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all extracted comments for this translation.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getExtractedComments()
|
||||
{
|
||||
return $this->extractedComments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all extracted comments.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function deleteExtractedComments()
|
||||
{
|
||||
$this->extractedComments = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new flag for this translation.
|
||||
*
|
||||
* @param string $flag
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function addFlag($flag)
|
||||
{
|
||||
if (!in_array($flag, $this->flags, true)) {
|
||||
$this->flags[] = $flag;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the translation has any flag.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFlags()
|
||||
{
|
||||
return isset($this->flags[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all extracted flags for this translation.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFlags()
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all flags.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function deleteFlags()
|
||||
{
|
||||
$this->flags = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges this translation with other translation.
|
||||
*
|
||||
* @param Translation $translation The translation to merge with
|
||||
* @param int $options
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function mergeWith(Translation $translation, $options = Merge::DEFAULTS)
|
||||
{
|
||||
Merge::mergeTranslation($translation, $this, $options);
|
||||
Merge::mergeReferences($translation, $this, $options);
|
||||
Merge::mergeComments($translation, $this, $options);
|
||||
Merge::mergeExtractedComments($translation, $this, $options);
|
||||
Merge::mergeFlags($translation, $this, $options);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,499 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext;
|
||||
|
||||
use Gettext\Languages\Language;
|
||||
use BadMethodCallException;
|
||||
use InvalidArgumentException;
|
||||
use ArrayObject;
|
||||
|
||||
/**
|
||||
* Class to manage a collection of translations.
|
||||
*
|
||||
* @method static $this fromBladeFile(string $filename, array $options = [])
|
||||
* @method static $this fromBladeString(string $string, array $options = [])
|
||||
* @method $this addFromBladeFile(string $filename, array $options = [])
|
||||
* @method $this addFromBladeString(string $string, array $options = [])
|
||||
* @method static $this fromCsvFile(string $filename, array $options = [])
|
||||
* @method static $this fromCsvString(string $string, array $options = [])
|
||||
* @method $this addFromCsvFile(string $filename, array $options = [])
|
||||
* @method $this addFromCsvString(string $string, array $options = [])
|
||||
* @method bool toCsvFile(string $filename, array $options = [])
|
||||
* @method string toCsvString(array $options = [])
|
||||
* @method static $this fromCsvDictionaryFile(string $filename, array $options = [])
|
||||
* @method static $this fromCsvDictionaryString(string $string, array $options = [])
|
||||
* @method $this addFromCsvDictionaryFile(string $filename, array $options = [])
|
||||
* @method $this addFromCsvDictionaryString(string $string, array $options = [])
|
||||
* @method bool toCsvDictionaryFile(string $filename, array $options = [])
|
||||
* @method string toCsvDictionaryString(array $options = [])
|
||||
* @method static $this fromJedFile(string $filename, array $options = [])
|
||||
* @method static $this fromJedString(string $string, array $options = [])
|
||||
* @method $this addFromJedFile(string $filename, array $options = [])
|
||||
* @method $this addFromJedString(string $string, array $options = [])
|
||||
* @method bool toJedFile(string $filename, array $options = [])
|
||||
* @method string toJedString(array $options = [])
|
||||
* @method static $this fromJsCodeFile(string $filename, array $options = [])
|
||||
* @method static $this fromJsCodeString(string $string, array $options = [])
|
||||
* @method $this addFromJsCodeFile(string $filename, array $options = [])
|
||||
* @method $this addFromJsCodeString(string $string, array $options = [])
|
||||
* @method static $this fromJsonFile(string $filename, array $options = [])
|
||||
* @method static $this fromJsonString(string $string, array $options = [])
|
||||
* @method $this addFromJsonFile(string $filename, array $options = [])
|
||||
* @method $this addFromJsonString(string $string, array $options = [])
|
||||
* @method bool toJsonFile(string $filename, array $options = [])
|
||||
* @method string toJsonString(array $options = [])
|
||||
* @method static $this fromJsonDictionaryFile(string $filename, array $options = [])
|
||||
* @method static $this fromJsonDictionaryString(string $string, array $options = [])
|
||||
* @method $this addFromJsonDictionaryFile(string $filename, array $options = [])
|
||||
* @method $this addFromJsonDictionaryString(string $string, array $options = [])
|
||||
* @method bool toJsonDictionaryFile(string $filename, array $options = [])
|
||||
* @method string toJsonDictionaryString(array $options = [])
|
||||
* @method static $this fromMoFile(string $filename, array $options = [])
|
||||
* @method static $this fromMoString(string $string, array $options = [])
|
||||
* @method $this addFromMoFile(string $filename, array $options = [])
|
||||
* @method $this addFromMoString(string $string, array $options = [])
|
||||
* @method bool toMoFile(string $filename, array $options = [])
|
||||
* @method string toMoString(array $options = [])
|
||||
* @method static $this fromPhpArrayFile(string $filename, array $options = [])
|
||||
* @method static $this fromPhpArrayString(string $string, array $options = [])
|
||||
* @method $this addFromPhpArrayFile(string $filename, array $options = [])
|
||||
* @method $this addFromPhpArrayString(string $string, array $options = [])
|
||||
* @method bool toPhpArrayFile(string $filename, array $options = [])
|
||||
* @method string toPhpArrayString(array $options = [])
|
||||
* @method static $this fromPhpCodeFile(string $filename, array $options = [])
|
||||
* @method static $this fromPhpCodeString(string $string, array $options = [])
|
||||
* @method $this addFromPhpCodeFile(string $filename, array $options = [])
|
||||
* @method $this addFromPhpCodeString(string $string, array $options = [])
|
||||
* @method static $this fromPoFile(string $filename, array $options = [])
|
||||
* @method static $this fromPoString(string $string, array $options = [])
|
||||
* @method $this addFromPoFile(string $filename, array $options = [])
|
||||
* @method $this addFromPoString(string $string, array $options = [])
|
||||
* @method bool toPoFile(string $filename, array $options = [])
|
||||
* @method string toPoString(array $options = [])
|
||||
* @method static $this fromTwigFile(string $filename, array $options = [])
|
||||
* @method static $this fromTwigString(string $string, array $options = [])
|
||||
* @method $this addFromTwigFile(string $filename, array $options = [])
|
||||
* @method $this addFromTwigString(string $string, array $options = [])
|
||||
* @method static $this fromVueJsFile(string $filename, array $options = [])
|
||||
* @method static $this fromVueJsString(string $filename, array $options = [])
|
||||
* @method $this addFromVueJsFile(string $filename, array $options = [])
|
||||
* @method $this addFromVueJsString(string $filename, array $options = [])
|
||||
* @method static $this fromXliffFile(string $filename, array $options = [])
|
||||
* @method static $this fromXliffString(string $string, array $options = [])
|
||||
* @method $this addFromXliffFile(string $filename, array $options = [])
|
||||
* @method $this addFromXliffString(string $string, array $options = [])
|
||||
* @method bool toXliffFile(string $filename, array $options = [])
|
||||
* @method string toXliffString(array $options = [])
|
||||
* @method static $this fromYamlFile(string $filename, array $options = [])
|
||||
* @method static $this fromYamlString(string $string, array $options = [])
|
||||
* @method $this addFromYamlFile(string $filename, array $options = [])
|
||||
* @method $this addFromYamlString(string $string, array $options = [])
|
||||
* @method bool toYamlFile(string $filename, array $options = [])
|
||||
* @method string toYamlString(array $options = [])
|
||||
* @method static $this fromYamlDictionaryFile(string $filename, array $options = [])
|
||||
* @method static $this fromYamlDictionaryString(string $string, array $options = [])
|
||||
* @method $this addFromYamlDictionaryFile(string $filename, array $options = [])
|
||||
* @method $this addFromYamlDictionaryString(string $string, array $options = [])
|
||||
* @method bool toYamlDictionaryFile(string $filename, array $options = [])
|
||||
* @method string toYamlDictionaryString(array $options = [])
|
||||
*/
|
||||
class Translations extends ArrayObject
|
||||
{
|
||||
const HEADER_LANGUAGE = 'Language';
|
||||
const HEADER_PLURAL = 'Plural-Forms';
|
||||
const HEADER_DOMAIN = 'X-Domain';
|
||||
|
||||
public static $options = [
|
||||
'defaultHeaders' => [
|
||||
'Project-Id-Version' => '',
|
||||
'Report-Msgid-Bugs-To' => '',
|
||||
'Last-Translator' => '',
|
||||
'Language-Team' => '',
|
||||
'MIME-Version' => '1.0',
|
||||
'Content-Type' => 'text/plain; charset=UTF-8',
|
||||
'Content-Transfer-Encoding' => '8bit',
|
||||
],
|
||||
'headersSorting' => false,
|
||||
'defaultDateHeaders' => [
|
||||
'POT-Creation-Date',
|
||||
'PO-Revision-Date',
|
||||
],
|
||||
];
|
||||
|
||||
protected $headers;
|
||||
|
||||
protected $translationClass;
|
||||
|
||||
/**
|
||||
* @see ArrayObject::__construct()
|
||||
*/
|
||||
public function __construct(
|
||||
$input = [],
|
||||
$flags = 0,
|
||||
$iterator_class = 'ArrayIterator',
|
||||
$translationClass = 'Gettext\Translation'
|
||||
) {
|
||||
$this->headers = static::$options['defaultHeaders'];
|
||||
|
||||
foreach (static::$options['defaultDateHeaders'] as $header) {
|
||||
$this->headers[$header] = date('c');
|
||||
}
|
||||
|
||||
$this->headers[self::HEADER_LANGUAGE] = '';
|
||||
|
||||
$this->translationClass = $translationClass;
|
||||
|
||||
parent::__construct($input, $flags, $iterator_class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to create new instances using extractors
|
||||
* For example: Translations::fromMoFile($filename, $options);.
|
||||
*
|
||||
* @return Translations
|
||||
*/
|
||||
public static function __callStatic($name, $arguments)
|
||||
{
|
||||
if (!preg_match('/^from(\w+)(File|String)$/i', $name, $matches)) {
|
||||
throw new BadMethodCallException("The method $name does not exists");
|
||||
}
|
||||
|
||||
return call_user_func_array([new static(), 'add'.ucfirst($name)], $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to import/export the translations to a specific format
|
||||
* For example: $translations->toMoFile($filename, $options);
|
||||
* For example: $translations->addFromMoFile($filename, $options);.
|
||||
*
|
||||
* @return self|bool
|
||||
*/
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
if (!preg_match('/^(addFrom|to)(\w+)(File|String)$/i', $name, $matches)) {
|
||||
throw new BadMethodCallException("The method $name does not exists");
|
||||
}
|
||||
|
||||
if ($matches[1] === 'addFrom') {
|
||||
$extractor = 'Gettext\\Extractors\\'.$matches[2].'::from'.$matches[3];
|
||||
$source = array_shift($arguments);
|
||||
$options = array_shift($arguments) ?: [];
|
||||
|
||||
call_user_func($extractor, $source, $this, $options);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$generator = 'Gettext\\Generators\\'.$matches[2].'::to'.$matches[3];
|
||||
|
||||
array_unshift($arguments, $this);
|
||||
|
||||
return call_user_func_array($generator, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to clone each translation on clone the translations object.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$array = [];
|
||||
|
||||
foreach ($this as $key => $translation) {
|
||||
$array[$key] = clone $translation;
|
||||
}
|
||||
|
||||
$this->exchangeArray($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Control the new translations added.
|
||||
*
|
||||
* @param mixed $index
|
||||
* @param Translation $value
|
||||
*
|
||||
* @throws InvalidArgumentException If the value is not an instance of Gettext\Translation
|
||||
*
|
||||
* @return Translation
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($index, $value)
|
||||
{
|
||||
if (!($value instanceof Translation)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Only instances of Gettext\\Translation must be added to a Gettext\\Translations'
|
||||
);
|
||||
}
|
||||
|
||||
$id = $value->getId();
|
||||
|
||||
if ($this->offsetExists($id)) {
|
||||
$this[$id]->mergeWith($value);
|
||||
|
||||
return $this[$id];
|
||||
}
|
||||
|
||||
parent::offsetSet($id, $value);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the plural definition.
|
||||
*
|
||||
* @param int $count
|
||||
* @param string $rule
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPluralForms($count, $rule)
|
||||
{
|
||||
if (preg_match('/[a-z]/i', str_replace('n', '', $rule))) {
|
||||
throw new \InvalidArgumentException('Invalid Plural form: ' . $rule);
|
||||
}
|
||||
$this->setHeader(self::HEADER_PLURAL, "nplurals={$count}; plural={$rule};");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parsed plural definition.
|
||||
*
|
||||
* @param null|array [count, rule]
|
||||
*/
|
||||
public function getPluralForms()
|
||||
{
|
||||
$header = $this->getHeader(self::HEADER_PLURAL);
|
||||
|
||||
if (!empty($header)
|
||||
&& preg_match('/^nplurals\s*=\s*(\d+)\s*;\s*plural\s*=\s*([^;]+)\s*;$/', $header, $matches)
|
||||
) {
|
||||
return [intval($matches[1]), $matches[2]];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new header.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setHeader($name, $value)
|
||||
{
|
||||
$name = trim($name);
|
||||
$this->headers[$name] = trim($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a header value.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getHeader($name)
|
||||
{
|
||||
return isset($this->headers[$name]) ? $this->headers[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all header for this translations (in alphabetic order).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getHeaders()
|
||||
{
|
||||
if (static::$options['headersSorting']) {
|
||||
ksort($this->headers);
|
||||
}
|
||||
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all headers.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function deleteHeaders()
|
||||
{
|
||||
$this->headers = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes one header.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function deleteHeader($name)
|
||||
{
|
||||
unset($this->headers[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the language value.
|
||||
*
|
||||
* @return string $language
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->getHeader(self::HEADER_LANGUAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the language and the plural forms.
|
||||
*
|
||||
* @param string $language
|
||||
*
|
||||
* @throws InvalidArgumentException if the language hasn't been recognized
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setLanguage($language)
|
||||
{
|
||||
$this->setHeader(self::HEADER_LANGUAGE, trim($language));
|
||||
|
||||
if (($info = Language::getById($language))) {
|
||||
return $this->setPluralForms(count($info->categories), $info->formula);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(sprintf('The language "%s" is not valid', $language));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the language is empty or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasLanguage()
|
||||
{
|
||||
$language = $this->getLanguage();
|
||||
|
||||
return (is_string($language) && ($language !== '')) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new domain for this translations.
|
||||
*
|
||||
* @param string $domain
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDomain($domain)
|
||||
{
|
||||
$this->setHeader(self::HEADER_DOMAIN, trim($domain));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the domain.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDomain()
|
||||
{
|
||||
return $this->getHeader(self::HEADER_DOMAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the domain is empty or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDomain()
|
||||
{
|
||||
$domain = $this->getDomain();
|
||||
|
||||
return (is_string($domain) && ($domain !== '')) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a specific translation.
|
||||
*
|
||||
* @param string|Translation $context The context of the translation or a translation instance
|
||||
* @param string $original The original string
|
||||
* @warning Translations with custom identifiers (e.g. XLIFF unit IDs) cannot be found using this function.
|
||||
*
|
||||
* @return Translation|false
|
||||
*/
|
||||
public function find($context, $original = '')
|
||||
{
|
||||
if ($context instanceof Translation) {
|
||||
$id = $context->getId();
|
||||
} else {
|
||||
$id = Translation::generateId($context, $original);
|
||||
}
|
||||
|
||||
return $this->offsetExists($id) ? $this[$id] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all elements translated
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function countTranslated()
|
||||
{
|
||||
$c = 0;
|
||||
foreach ($this as $v) {
|
||||
if ($v->hasTranslation()) {
|
||||
$c++;
|
||||
}
|
||||
}
|
||||
return $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and insert/merges a new translation.
|
||||
*
|
||||
* @param string $context The translation context
|
||||
* @param string $original The translation original string
|
||||
* @param string $plural The translation original plural string
|
||||
*
|
||||
* @return Translation The translation created
|
||||
*/
|
||||
public function insert($context, $original, $plural = '')
|
||||
{
|
||||
return $this->offsetSet(null, $this->createNewTranslation($context, $original, $plural));
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges this translations with other translations.
|
||||
*
|
||||
* @param Translations $translations The translations instance to merge with
|
||||
* @param int $options
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function mergeWith(Translations $translations, $options = Merge::DEFAULTS)
|
||||
{
|
||||
Merge::mergeHeaders($translations, $this, $options);
|
||||
Merge::mergeTranslations($translations, $this, $options);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of a Translation object.
|
||||
*
|
||||
* @param string $context The context of the translation
|
||||
* @param string $original The original string
|
||||
* @param string $plural The original plural string
|
||||
* @return Translation New Translation instance
|
||||
*/
|
||||
public function createNewTranslation($context, $original, $plural = '')
|
||||
{
|
||||
$class = $this->translationClass;
|
||||
return $class::create($context, $original, $plural);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,270 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext;
|
||||
|
||||
use Gettext\Generators\PhpArray;
|
||||
|
||||
class Translator extends BaseTranslator implements TranslatorInterface
|
||||
{
|
||||
protected $domain;
|
||||
protected $dictionary = [];
|
||||
protected $plurals = [];
|
||||
|
||||
/**
|
||||
* Loads translation from a Translations instance, a file on an array.
|
||||
*
|
||||
* @param Translations|string|array $translations
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function loadTranslations($translations)
|
||||
{
|
||||
if (is_object($translations) && $translations instanceof Translations) {
|
||||
$translations = PhpArray::generate($translations, ['includeHeaders' => false]);
|
||||
} elseif (is_string($translations) && is_file($translations)) {
|
||||
$translations = include $translations;
|
||||
} elseif (!is_array($translations)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid Translator: only arrays, files or instance of Translations are allowed'
|
||||
);
|
||||
}
|
||||
|
||||
$this->addTranslations($translations);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default domain.
|
||||
*
|
||||
* @param string $domain
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function defaultDomain($domain)
|
||||
{
|
||||
$this->domain = $domain;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function gettext($original)
|
||||
{
|
||||
return $this->dpgettext($this->domain, null, $original);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function ngettext($original, $plural, $value)
|
||||
{
|
||||
return $this->dnpgettext($this->domain, null, $original, $plural, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dngettext($domain, $original, $plural, $value)
|
||||
{
|
||||
return $this->dnpgettext($domain, null, $original, $plural, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function npgettext($context, $original, $plural, $value)
|
||||
{
|
||||
return $this->dnpgettext($this->domain, $context, $original, $plural, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function pgettext($context, $original)
|
||||
{
|
||||
return $this->dpgettext($this->domain, $context, $original);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dgettext($domain, $original)
|
||||
{
|
||||
return $this->dpgettext($domain, null, $original);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dpgettext($domain, $context, $original)
|
||||
{
|
||||
$translation = $this->getTranslation($domain, $context, $original);
|
||||
|
||||
if (isset($translation[0]) && $translation[0] !== '') {
|
||||
return $translation[0];
|
||||
}
|
||||
|
||||
return $original;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TranslatorInterface
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dnpgettext($domain, $context, $original, $plural, $value)
|
||||
{
|
||||
$translation = $this->getTranslation($domain, $context, $original);
|
||||
$key = $this->getPluralIndex($domain, $value, $translation === false);
|
||||
|
||||
if (isset($translation[$key]) && $translation[$key] !== '') {
|
||||
return $translation[$key];
|
||||
}
|
||||
|
||||
return ($key === 0) ? $original : $plural;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new translations to the dictionary.
|
||||
*
|
||||
* @param array $translations
|
||||
*/
|
||||
protected function addTranslations(array $translations)
|
||||
{
|
||||
$domain = isset($translations['domain']) ? $translations['domain'] : '';
|
||||
|
||||
//Set the first domain loaded as default domain
|
||||
if ($this->domain === null) {
|
||||
$this->domain = $domain;
|
||||
}
|
||||
|
||||
if (isset($this->dictionary[$domain])) {
|
||||
$this->dictionary[$domain] = array_replace_recursive($this->dictionary[$domain], $translations['messages']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($translations['plural-forms'])) {
|
||||
list($count, $code) = array_map('trim', explode(';', $translations['plural-forms'], 2));
|
||||
|
||||
// extract just the expression turn 'n' into a php variable '$n'.
|
||||
// Slap on a return keyword and semicolon at the end.
|
||||
$this->plurals[$domain] = [
|
||||
'count' => (int) str_replace('nplurals=', '', $count),
|
||||
'code' => str_replace('plural=', 'return ', str_replace('n', '$n', $code)).';',
|
||||
];
|
||||
}
|
||||
|
||||
$this->dictionary[$domain] = $translations['messages'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Search and returns a translation.
|
||||
*
|
||||
* @param string $domain
|
||||
* @param string $context
|
||||
* @param string $original
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
protected function getTranslation($domain, $context, $original)
|
||||
{
|
||||
return isset($this->dictionary[$domain][$context][$original])
|
||||
? $this->dictionary[$domain][$context][$original]
|
||||
: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the plural decision code given the number to decide which
|
||||
* plural version to take.
|
||||
*
|
||||
* @param string $domain
|
||||
* @param string $n
|
||||
* @param bool $fallback set to true to get fallback plural index
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getPluralIndex($domain, $n, $fallback)
|
||||
{
|
||||
//Not loaded domain or translation, use a fallback
|
||||
if (!isset($this->plurals[$domain]) || $fallback === true) {
|
||||
return $n == 1 ? 0 : 1;
|
||||
}
|
||||
|
||||
if (!isset($this->plurals[$domain]['function'])) {
|
||||
$code = static::fixTerseIfs($this->plurals[$domain]['code']);
|
||||
$this->plurals[$domain]['function'] = eval("return function (\$n) { $code };");
|
||||
}
|
||||
|
||||
if ($this->plurals[$domain]['count'] <= 2) {
|
||||
return call_user_func($this->plurals[$domain]['function'], $n) ? 1 : 0;
|
||||
}
|
||||
|
||||
return call_user_func($this->plurals[$domain]['function'], $n);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will recursively wrap failure states in brackets if they contain a nested terse if.
|
||||
*
|
||||
* This because PHP can not handle nested terse if's unless they are wrapped in brackets.
|
||||
*
|
||||
* This code probably only works for the gettext plural decision codes.
|
||||
*
|
||||
* return ($n==1 ? 0 : $n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2);
|
||||
* becomes
|
||||
* return ($n==1 ? 0 : ($n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2));
|
||||
*
|
||||
* @param string $code the terse if string
|
||||
* @param bool $inner If inner is true we wrap it in brackets
|
||||
*
|
||||
* @return string A formatted terse If that PHP can work with.
|
||||
*/
|
||||
private static function fixTerseIfs($code, $inner = false)
|
||||
{
|
||||
/*
|
||||
* (?P<expression>[^?]+) Capture everything up to ? as 'expression'
|
||||
* \? ?
|
||||
* (?P<success>[^:]+) Capture everything up to : as 'success'
|
||||
* : :
|
||||
* (?P<failure>[^;]+) Capture everything up to ; as 'failure'
|
||||
*/
|
||||
preg_match('/(?P<expression>[^?]+)\?(?P<success>[^:]+):(?P<failure>[^;]+)/', $code, $matches);
|
||||
|
||||
// If no match was found then no terse if was present
|
||||
if (!isset($matches[0])) {
|
||||
return $code;
|
||||
}
|
||||
|
||||
$expression = $matches['expression'];
|
||||
$success = $matches['success'];
|
||||
$failure = $matches['failure'];
|
||||
|
||||
// Go look for another terse if in the failure state.
|
||||
$failure = static::fixTerseIfs($failure, true);
|
||||
$code = $expression.' ? '.$success.' : '.$failure;
|
||||
|
||||
if ($inner) {
|
||||
return "($code)";
|
||||
}
|
||||
|
||||
// note the semicolon. We need that for executing the code.
|
||||
return "$code;";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext;
|
||||
|
||||
/**
|
||||
* Interface used by all translators.
|
||||
*/
|
||||
interface TranslatorInterface
|
||||
{
|
||||
/**
|
||||
* Register this translator as global, to use with the gettext functions __(), p__(), etc.
|
||||
* Returns the previous translator if exists.
|
||||
*
|
||||
* @return TranslatorInterface|null
|
||||
*/
|
||||
public function register();
|
||||
|
||||
/**
|
||||
* Noop, marks the string for translation but returns it unchanged.
|
||||
*
|
||||
* @param string $original
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function noop($original);
|
||||
|
||||
/**
|
||||
* Gets a translation using the original string.
|
||||
*
|
||||
* @param string $original
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function gettext($original);
|
||||
|
||||
/**
|
||||
* Gets a translation checking the plural form.
|
||||
*
|
||||
* @param string $original
|
||||
* @param string $plural
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function ngettext($original, $plural, $value);
|
||||
|
||||
/**
|
||||
* Gets a translation checking the domain and the plural form.
|
||||
*
|
||||
* @param string $domain
|
||||
* @param string $original
|
||||
* @param string $plural
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function dngettext($domain, $original, $plural, $value);
|
||||
|
||||
/**
|
||||
* Gets a translation checking the context and the plural form.
|
||||
*
|
||||
* @param string $context
|
||||
* @param string $original
|
||||
* @param string $plural
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function npgettext($context, $original, $plural, $value);
|
||||
|
||||
/**
|
||||
* Gets a translation checking the context.
|
||||
*
|
||||
* @param string $context
|
||||
* @param string $original
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function pgettext($context, $original);
|
||||
|
||||
/**
|
||||
* Gets a translation checking the domain.
|
||||
*
|
||||
* @param string $domain
|
||||
* @param string $original
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function dgettext($domain, $original);
|
||||
|
||||
/**
|
||||
* Gets a translation checking the domain and context.
|
||||
*
|
||||
* @param string $domain
|
||||
* @param string $context
|
||||
* @param string $original
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function dpgettext($domain, $context, $original);
|
||||
|
||||
/**
|
||||
* Gets a translation checking the domain, the context and the plural form.
|
||||
*
|
||||
* @param string $domain
|
||||
* @param string $context
|
||||
* @param string $original
|
||||
* @param string $plural
|
||||
* @param string $value
|
||||
*/
|
||||
public function dnpgettext($domain, $context, $original, $plural, $value);
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Utils;
|
||||
|
||||
/*
|
||||
* Trait to provide the functionality of read/write csv.
|
||||
*/
|
||||
trait CsvTrait
|
||||
{
|
||||
protected static $csvEscapeChar;
|
||||
|
||||
/**
|
||||
* Check whether support the escape_char argument to fgetcsv/fputcsv or not
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function supportsCsvEscapeChar()
|
||||
{
|
||||
if (static::$csvEscapeChar === null) {
|
||||
static::$csvEscapeChar = version_compare(PHP_VERSION, '5.5.4') >= 0;
|
||||
}
|
||||
|
||||
return static::$csvEscapeChar;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $handle
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function fgetcsv($handle, $options)
|
||||
{
|
||||
if (static::supportsCsvEscapeChar()) {
|
||||
return fgetcsv($handle, 0, $options['delimiter'], $options['enclosure'], $options['escape_char']);
|
||||
}
|
||||
|
||||
return fgetcsv($handle, 0, $options['delimiter'], $options['enclosure']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $handle
|
||||
* @param array $fields
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
protected static function fputcsv($handle, $fields, $options)
|
||||
{
|
||||
if (static::supportsCsvEscapeChar()) {
|
||||
return fputcsv($handle, $fields, $options['delimiter'], $options['enclosure'], $options['escape_char']);
|
||||
}
|
||||
|
||||
return fputcsv($handle, $fields, $options['delimiter'], $options['enclosure']);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Utils;
|
||||
|
||||
use Gettext\Translations;
|
||||
|
||||
/**
|
||||
* Trait used by all generators that exports the translations to plain dictionary (original => singular-translation).
|
||||
*/
|
||||
trait DictionaryTrait
|
||||
{
|
||||
use HeadersGeneratorTrait;
|
||||
use HeadersExtractorTrait;
|
||||
|
||||
/**
|
||||
* Returns a plain dictionary with the format [original => translation].
|
||||
*
|
||||
* @param Translations $translations
|
||||
* @param bool $includeHeaders
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function toArray(Translations $translations, $includeHeaders)
|
||||
{
|
||||
$messages = [];
|
||||
|
||||
if ($includeHeaders) {
|
||||
$messages[''] = static::generateHeaders($translations);
|
||||
}
|
||||
|
||||
foreach ($translations as $translation) {
|
||||
if ($translation->isDisabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$messages[$translation->getOriginal()] = $translation->getTranslation();
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the entries from a dictionary.
|
||||
*
|
||||
* @param array $messages
|
||||
* @param Translations $translations
|
||||
*/
|
||||
protected static function fromArray(array $messages, Translations $translations)
|
||||
{
|
||||
foreach ($messages as $original => $translation) {
|
||||
if ($original === '') {
|
||||
static::extractHeaders($translation, $translations);
|
||||
continue;
|
||||
}
|
||||
|
||||
$translations->insert(null, $original)->setTranslation($translation);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Utils;
|
||||
|
||||
use Exception;
|
||||
use Gettext\Translations;
|
||||
|
||||
abstract class FunctionsScanner
|
||||
{
|
||||
/**
|
||||
* Scan and returns the functions and the arguments.
|
||||
*
|
||||
* @param array $constants Constants used in the code to replace
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getFunctions(array $constants = []);
|
||||
|
||||
/**
|
||||
* Search for specific functions and create translations.
|
||||
*
|
||||
* You can pass multiple translation with different domains and value found will be sorted respectively.
|
||||
*
|
||||
* @param Translations|Translations[] $translations Multiple domain translations instances where to save the values
|
||||
* @param array $options The extractor options
|
||||
* @throws Exception
|
||||
*/
|
||||
public function saveGettextFunctions($translations, array $options)
|
||||
{
|
||||
$translations = is_array($translations) ? $translations : [$translations];
|
||||
|
||||
/** @var Translations[] $translationByDomain [domain => translations, ..] */
|
||||
$translationByDomain = array_reduce($translations, function ($carry, Translations $translations) {
|
||||
$carry[$translations->getDomain()] = $translations;
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
$functions = $options['functions'];
|
||||
$file = $options['file'];
|
||||
|
||||
/**
|
||||
* List of source code comments already associated with a function.
|
||||
*
|
||||
* Prevents associating the same comment to multiple functions.
|
||||
*
|
||||
* @var ParsedComment[] $commentsCache
|
||||
*/
|
||||
$commentsCache = [];
|
||||
|
||||
foreach ($this->getFunctions($options['constants']) as $function) {
|
||||
list($name, $line, $args) = $function;
|
||||
|
||||
if (isset($options['lineOffset'])) {
|
||||
$line += $options['lineOffset'];
|
||||
}
|
||||
|
||||
if (!isset($functions[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$deconstructed = $this->deconstructArgs($functions[$name], $args);
|
||||
|
||||
if (!$deconstructed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list($domain, $context, $original, $plural) = $deconstructed;
|
||||
|
||||
if ((string)$original === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isDefaultDomain = $domain === null;
|
||||
|
||||
$domainTranslations = isset($translationByDomain[$domain]) ? $translationByDomain[$domain] : false;
|
||||
|
||||
if (!empty($options['domainOnly']) && $isDefaultDomain) {
|
||||
// If we want to find translations for a specific domain, skip default domain messages
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$domainTranslations) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$translation = $domainTranslations->insert($context, $original, $plural);
|
||||
$translation->addReference($file, $line);
|
||||
|
||||
if (isset($function[3])) {
|
||||
/* @var ParsedComment $extractedComment */
|
||||
foreach ($function[3] as $extractedComment) {
|
||||
if (in_array($extractedComment, $commentsCache, true)) {
|
||||
continue;
|
||||
}
|
||||
$translation->addExtractedComment($extractedComment->getComment());
|
||||
$commentsCache[] = $extractedComment;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deconstruct arguments to translation values
|
||||
*
|
||||
* @param $function
|
||||
* @param $args
|
||||
* @return array|null
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function deconstructArgs($function, $args)
|
||||
{
|
||||
$domain = null;
|
||||
$context = null;
|
||||
$original = null;
|
||||
$plural = null;
|
||||
|
||||
switch ($function) {
|
||||
case 'noop':
|
||||
case 'gettext':
|
||||
if (!isset($args[0])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$original = $args[0];
|
||||
break;
|
||||
case 'ngettext':
|
||||
if (!isset($args[1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
list($original, $plural) = $args;
|
||||
break;
|
||||
case 'pgettext':
|
||||
if (!isset($args[1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
list($context, $original) = $args;
|
||||
break;
|
||||
case 'dgettext':
|
||||
if (!isset($args[1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
list($domain, $original) = $args;
|
||||
break;
|
||||
case 'dpgettext':
|
||||
if (!isset($args[2])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
list($domain, $context, $original) = $args;
|
||||
break;
|
||||
case 'npgettext':
|
||||
if (!isset($args[2])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
list($context, $original, $plural) = $args;
|
||||
break;
|
||||
case 'dnpgettext':
|
||||
if (!isset($args[3])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
list($domain, $context, $original, $plural) = $args;
|
||||
break;
|
||||
case 'dngettext':
|
||||
if (!isset($args[2])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
list($domain, $original, $plural) = $args;
|
||||
break;
|
||||
default:
|
||||
throw new Exception(sprintf('Not valid function %s', $function));
|
||||
}
|
||||
|
||||
return [$domain, $context, $original, $plural];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Utils;
|
||||
|
||||
use Gettext\Translations;
|
||||
|
||||
/**
|
||||
* Trait to provide the functionality of extracting headers.
|
||||
*/
|
||||
trait HeadersExtractorTrait
|
||||
{
|
||||
/**
|
||||
* Add the headers found to the translations instance.
|
||||
*
|
||||
* @param string $headers
|
||||
* @param Translations $translations
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function extractHeaders($headers, Translations $translations)
|
||||
{
|
||||
$headers = explode("\n", $headers);
|
||||
$currentHeader = null;
|
||||
|
||||
foreach ($headers as $line) {
|
||||
$line = static::convertString($line);
|
||||
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (static::isHeaderDefinition($line)) {
|
||||
$header = array_map('trim', explode(':', $line, 2));
|
||||
$currentHeader = $header[0];
|
||||
$translations->setHeader($currentHeader, $header[1]);
|
||||
} else {
|
||||
$entry = $translations->getHeader($currentHeader);
|
||||
$translations->setHeader($currentHeader, $entry.$line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if it is a header definition line. Useful for distguishing between header definitions
|
||||
* and possible continuations of a header entry.
|
||||
*
|
||||
* @param string $line Line to parse
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function isHeaderDefinition($line)
|
||||
{
|
||||
return (bool) preg_match('/^[\w-]+:/', $line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a string.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function convertString($value)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Utils;
|
||||
|
||||
use Gettext\Translations;
|
||||
|
||||
/**
|
||||
* Trait to provide the functionality of extracting headers.
|
||||
*/
|
||||
trait HeadersGeneratorTrait
|
||||
{
|
||||
/**
|
||||
* Returns the headers as a string.
|
||||
*
|
||||
* @param Translations $translations
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function generateHeaders(Translations $translations)
|
||||
{
|
||||
$headers = '';
|
||||
|
||||
foreach ($translations->getHeaders() as $name => $value) {
|
||||
$headers .= sprintf("%s: %s\n", $name, $value);
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Utils;
|
||||
|
||||
class JsFunctionsScanner extends FunctionsScanner
|
||||
{
|
||||
protected $code;
|
||||
protected $status = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $code The php code to scan
|
||||
*/
|
||||
public function __construct($code)
|
||||
{
|
||||
// Normalize newline characters
|
||||
$this->code = str_replace(["\r\n", "\n\r", "\r"], "\n", $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions(array $constants = [])
|
||||
{
|
||||
$length = strlen($this->code);
|
||||
$line = 1;
|
||||
$buffer = '';
|
||||
$functions = [];
|
||||
$bufferFunctions = [];
|
||||
$char = null;
|
||||
|
||||
for ($pos = 0; $pos < $length; ++$pos) {
|
||||
$prev = $char;
|
||||
$char = $this->code[$pos];
|
||||
$next = isset($this->code[$pos + 1]) ? $this->code[$pos + 1] : null;
|
||||
|
||||
switch ($char) {
|
||||
case '\\':
|
||||
switch ($this->status()) {
|
||||
case 'simple-quote':
|
||||
if ($next !== "'") {
|
||||
break 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'double-quote':
|
||||
if ($next !== '"') {
|
||||
break 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'back-tick':
|
||||
if ($next !== '`') {
|
||||
break 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$prev = $char;
|
||||
$char = $next;
|
||||
$pos++;
|
||||
$next = isset($this->code[$pos]) ? $this->code[$pos] : null;
|
||||
break;
|
||||
|
||||
case "\n":
|
||||
++$line;
|
||||
|
||||
if ($this->status('line-comment')) {
|
||||
$this->upStatus();
|
||||
}
|
||||
break;
|
||||
|
||||
case '/':
|
||||
switch ($this->status()) {
|
||||
case 'simple-quote':
|
||||
case 'double-quote':
|
||||
case 'back-tick':
|
||||
case 'line-comment':
|
||||
break;
|
||||
|
||||
case 'block-comment':
|
||||
if ($prev === '*') {
|
||||
$this->upStatus();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if ($next === '/') {
|
||||
$this->downStatus('line-comment');
|
||||
} elseif ($next === '*') {
|
||||
$this->downStatus('block-comment');
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case "'":
|
||||
switch ($this->status()) {
|
||||
case 'simple-quote':
|
||||
$this->upStatus();
|
||||
break;
|
||||
|
||||
case 'line-comment':
|
||||
case 'block-comment':
|
||||
case 'double-quote':
|
||||
case 'back-tick':
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->downStatus('simple-quote');
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case '"':
|
||||
switch ($this->status()) {
|
||||
case 'double-quote':
|
||||
$this->upStatus();
|
||||
break;
|
||||
|
||||
case 'line-comment':
|
||||
case 'block-comment':
|
||||
case 'simple-quote':
|
||||
case 'back-tick':
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->downStatus('double-quote');
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case '`':
|
||||
switch ($this->status()) {
|
||||
case 'back-tick':
|
||||
$this->upStatus();
|
||||
break;
|
||||
|
||||
case 'line-comment':
|
||||
case 'block-comment':
|
||||
case 'simple-quote':
|
||||
case 'double-quote':
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->downStatus('back-tick');
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case '(':
|
||||
switch ($this->status()) {
|
||||
case 'simple-quote':
|
||||
case 'double-quote':
|
||||
case 'back-tick':
|
||||
case 'line-comment':
|
||||
case 'block-comment':
|
||||
break;
|
||||
|
||||
default:
|
||||
if ($buffer && preg_match('/(\w+)$/', $buffer, $matches)) {
|
||||
$this->downStatus('function');
|
||||
array_unshift($bufferFunctions, [$matches[1], $line, []]);
|
||||
$buffer = '';
|
||||
continue 3;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ')':
|
||||
switch ($this->status()) {
|
||||
case 'function':
|
||||
if (($argument = static::prepareArgument($buffer))) {
|
||||
$bufferFunctions[0][2][] = $argument;
|
||||
}
|
||||
|
||||
if (!empty($bufferFunctions)) {
|
||||
$functions[] = array_shift($bufferFunctions);
|
||||
}
|
||||
|
||||
$this->upStatus();
|
||||
$buffer = '';
|
||||
continue 3;
|
||||
}
|
||||
break;
|
||||
|
||||
case ',':
|
||||
switch ($this->status()) {
|
||||
case 'function':
|
||||
if (($argument = static::prepareArgument($buffer))) {
|
||||
$bufferFunctions[0][2][] = $argument;
|
||||
}
|
||||
|
||||
$buffer = '';
|
||||
continue 3;
|
||||
}
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
case '\t':
|
||||
switch ($this->status()) {
|
||||
case 'double-quote':
|
||||
case 'simple-quote':
|
||||
case 'back-tick':
|
||||
break;
|
||||
|
||||
default:
|
||||
$buffer = '';
|
||||
continue 3;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($this->status()) {
|
||||
case 'line-comment':
|
||||
case 'block-comment':
|
||||
break;
|
||||
|
||||
default:
|
||||
$buffer .= $char;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $functions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current context of the scan.
|
||||
*
|
||||
* @param null|string $match To check whether the current status is this value
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
protected function status($match = null)
|
||||
{
|
||||
$status = isset($this->status[0]) ? $this->status[0] : null;
|
||||
|
||||
if ($match !== null) {
|
||||
return $status === $match;
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new status to the stack.
|
||||
*
|
||||
* @param string $status
|
||||
*/
|
||||
protected function downStatus($status)
|
||||
{
|
||||
array_unshift($this->status, $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes and return the current status.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function upStatus()
|
||||
{
|
||||
return array_shift($this->status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the arguments found in functions.
|
||||
*
|
||||
* @param string $argument
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function prepareArgument($argument)
|
||||
{
|
||||
if ($argument && in_array($argument[0], ['"', "'", '`'], true)) {
|
||||
return static::convertString(substr($argument, 1, -1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a string with an argument.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function convertString($value)
|
||||
{
|
||||
if (strpos($value, '\\') === false) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return preg_replace_callback(
|
||||
'/\\\(n|r|t|v|e|f|"|\\\)/',
|
||||
function ($match) {
|
||||
switch ($match[1][0]) {
|
||||
case 'n':
|
||||
return "\n";
|
||||
case 'r':
|
||||
return "\r";
|
||||
case 't':
|
||||
return "\t";
|
||||
case 'v':
|
||||
return "\v";
|
||||
case 'e':
|
||||
return "\e";
|
||||
case 'f':
|
||||
return "\f";
|
||||
case '"':
|
||||
return '"';
|
||||
case '\\':
|
||||
return '\\';
|
||||
}
|
||||
},
|
||||
$value
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Utils;
|
||||
|
||||
use Gettext\Translations;
|
||||
|
||||
/**
|
||||
* Trait used by all generators that exports the translations to multidimensional arrays
|
||||
* (context => [original => [translation, plural1, pluraln...]]).
|
||||
*/
|
||||
trait MultidimensionalArrayTrait
|
||||
{
|
||||
use HeadersGeneratorTrait;
|
||||
use HeadersExtractorTrait;
|
||||
|
||||
/**
|
||||
* Returns a multidimensional array.
|
||||
*
|
||||
* @param Translations $translations
|
||||
* @param bool $includeHeaders
|
||||
* @param bool $forceArray
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function toArray(Translations $translations, $includeHeaders, $forceArray = false)
|
||||
{
|
||||
$pluralForm = $translations->getPluralForms();
|
||||
$pluralSize = is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
|
||||
$messages = [];
|
||||
|
||||
if ($includeHeaders) {
|
||||
$messages[''] = [
|
||||
'' => [static::generateHeaders($translations)],
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($translations as $translation) {
|
||||
if ($translation->isDisabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$context = $translation->getContext();
|
||||
$original = $translation->getOriginal();
|
||||
|
||||
if (!isset($messages[$context])) {
|
||||
$messages[$context] = [];
|
||||
}
|
||||
|
||||
if ($translation->hasPluralTranslations(true)) {
|
||||
$messages[$context][$original] = $translation->getPluralTranslations($pluralSize);
|
||||
array_unshift($messages[$context][$original], $translation->getTranslation());
|
||||
} elseif ($forceArray) {
|
||||
$messages[$context][$original] = [$translation->getTranslation()];
|
||||
} else {
|
||||
$messages[$context][$original] = $translation->getTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'domain' => $translations->getDomain(),
|
||||
'plural-forms' => $translations->getHeader('Plural-Forms'),
|
||||
'messages' => $messages,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the entries from a multidimensional array.
|
||||
*
|
||||
* @param array $messages
|
||||
* @param Translations $translations
|
||||
*/
|
||||
protected static function fromArray(array $messages, Translations $translations)
|
||||
{
|
||||
if (!empty($messages['domain'])) {
|
||||
$translations->setDomain($messages['domain']);
|
||||
}
|
||||
|
||||
if (!empty($messages['plural-forms'])) {
|
||||
$translations->setHeader(Translations::HEADER_PLURAL, $messages['plural-forms']);
|
||||
}
|
||||
|
||||
foreach ($messages['messages'] as $context => $contextTranslations) {
|
||||
foreach ($contextTranslations as $original => $value) {
|
||||
if ($context === '' && $original === '') {
|
||||
static::extractHeaders(is_array($value) ? array_shift($value) : $value, $translations);
|
||||
continue;
|
||||
}
|
||||
|
||||
$translation = $translations->insert($context, $original);
|
||||
|
||||
if (is_array($value)) {
|
||||
$translation->setTranslation(array_shift($value));
|
||||
$translation->setPluralTranslations($value);
|
||||
} else {
|
||||
$translation->setTranslation($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
namespace Gettext\Utils;
|
||||
|
||||
/**
|
||||
* Comment parsed by PhpFunctionsScanner.
|
||||
*/
|
||||
class ParsedComment
|
||||
{
|
||||
/**
|
||||
* The comment itself.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $comment;
|
||||
|
||||
/**
|
||||
* The line where the comment starts.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $firstLine;
|
||||
|
||||
/**
|
||||
* The line where the comment ends.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $lastLine;
|
||||
|
||||
/**
|
||||
* Initializes the instance.
|
||||
*
|
||||
* @param string $comment The comment itself.
|
||||
* @param int $firstLine The line where the comment starts.
|
||||
* @param int $lastLine The line where the comment ends.
|
||||
*/
|
||||
public function __construct($comment, $firstLine, $lastLine)
|
||||
{
|
||||
$this->comment = $comment;
|
||||
$this->firstLine = $firstLine;
|
||||
$this->lastLine = $lastLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new object from raw comment data.
|
||||
*
|
||||
* @param string $value The PHP comment string.
|
||||
* @param int $line The line where the comment starts.
|
||||
*
|
||||
* @return static The parsed comment.
|
||||
*/
|
||||
public static function create($value, $line)
|
||||
{
|
||||
$lastLine = $line + substr_count($value, "\n");
|
||||
|
||||
$lines = array_map(function ($line) {
|
||||
if ('' === trim($line)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$line = ltrim($line, "#*/ \t");
|
||||
$line = rtrim($line, "#*/ \t");
|
||||
|
||||
return trim($line);
|
||||
}, explode("\n", $value));
|
||||
|
||||
// Remove empty lines.
|
||||
$lines = array_filter($lines);
|
||||
$value = implode(' ', $lines);
|
||||
|
||||
return new static($value, $line, $lastLine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the line where the comment starts.
|
||||
*
|
||||
* @return int Line number.
|
||||
*/
|
||||
public function getFirstLine()
|
||||
{
|
||||
return $this->firstLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the line where the comment ends.
|
||||
*
|
||||
* @return int Line number.
|
||||
*/
|
||||
public function getLastLine()
|
||||
{
|
||||
return $this->lastLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the actual comment string.
|
||||
*
|
||||
* @return string The comment.
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this comment is related with a given function.
|
||||
*
|
||||
* @param ParsedFunction $function The function to check.
|
||||
* @return bool Whether the comment is related or not.
|
||||
*/
|
||||
public function isRelatedWith(ParsedFunction $function)
|
||||
{
|
||||
return $this->getLastLine() === $function->getLine() || $this->getLastLine() === $function->getLine() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the comment matches the required prefixes.
|
||||
*
|
||||
* @param array $prefixes An array of prefixes to check.
|
||||
* @return bool Whether the comment matches the prefixes or not.
|
||||
*/
|
||||
public function checkPrefixes(array $prefixes)
|
||||
{
|
||||
if ('' === $this->comment) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($prefixes)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($prefixes as $prefix) {
|
||||
if (strpos($this->comment, $prefix) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue