Commit 1f959a16 authored by Tino Goratsch's avatar Tino Goratsch

Merge branch 'release/v4.10.0'

parents e2fd8135 26e1dbac
......@@ -6,12 +6,8 @@
#SetEnv ACP3_APPLICATION_MODE dev
<IfModule mod_rewrite.c>
RewriteEngine On
Options +FollowSymlinks
DirectoryIndex index.php
</IfModule>
Options +FollowSymlinks
DirectoryIndex index.php
# Uncomment the following lines of code if you want to use separate sitemaps for HTTP and HTTPS
#<IfModule mod_rewrite.c>
......@@ -19,11 +15,11 @@
#
# RewriteCond %{HTTPS} !=on
# RewriteCond %{REQUEST_FILENAME} !-f
# RewriteRule ^sitemap\.xml$ sitemap_http.xml [L]
# RewriteRule ^sitemap\.xml$ sitemap_http.xml [L,PT]
#
# RewriteCond %{HTTPS} =on
# RewriteCond %{REQUEST_FILENAME} !-f
# RewriteRule ^sitemap\.xml$ sitemap_https.xml [L]
# RewriteRule ^sitemap\.xml$ sitemap_https.xml [L,PT]
#</IfModule>
<IfModule mod_rewrite.c>
......
......@@ -4,7 +4,7 @@ dist: trusty
group: edge
php:
- 5.6
- 7.0
- 7.0.8
- 7.1
- nightly
- hhvm
......@@ -23,13 +23,14 @@ before_install:
- echo -e "machine github.com\n\tlogin ${ACP3_CI_USER_GH_TOKEN}" >> ~/.netrc
before_script:
- ./build/travis/before_script.sh ${ACP3_CI_USER_GH_TOKEN}
- ./build/travis/before_script.sh ${ACP3_CI_USER_GH_TOKEN} ${TRAVIS_PHP_VERSION}
script:
- ./build/travis/run_unit_tests.sh ${TRAVIS_PHP_VERSION}
after_success:
- ./build/travis/push_code_coverage.sh ${TRAVIS_PHP_VERSION}
- travis_retry vendor/bin/coveralls -v
# - ./build/travis/push_code_coverage.sh ${TRAVIS_PHP_VERSION}
before_deploy:
- ./build/travis/generate_artifact.sh ${TRAVIS_PHP_VERSION}
......
......@@ -14,7 +14,7 @@ interface BootstrapInterface extends HttpKernelInterface
/**
* Contains the current ACP3 version string
*/
const VERSION = '4.9.2';
const VERSION = '4.10.0';
/**
* Performs some startup checks
......
......@@ -49,7 +49,7 @@ class RequestFactory
}
/**
* @return \ACP3\Core\Http\Request
* @return \ACP3\Core\Http\RequestInterface
*/
protected function getRequest()
{
......
......@@ -12,6 +12,10 @@ class CountryList
* @var Translator
*/
private $translator;
/**
* @var null|array
*/
private $countries = null;
/**
* Country constructor.
......@@ -29,14 +33,52 @@ class CountryList
*/
public function worldCountries()
{
$path = ACP3_ROOT_DIR . 'vendor/umpirsky/country-list/data/' . $this->translator->getLocale() . '/country.json';
if ($this->countries === null) {
$this->cacheWorldCountries();
}
if (preg_match('/^[a-z]{2}_[A-Z]{2}/', $this->translator->getLocale()) && is_file($path)) {
$countries = file_get_contents($path);
return $this->countries;
}
return json_decode($countries, true);
private function cacheWorldCountries()
{
$basePath = ACP3_ROOT_DIR . 'vendor/giggsey/locale/data/';
$supportedLocales = include $basePath . '_list.php';
$this->countries = [];
if ($this->isSupportedLocale($supportedLocales)) {
$paths = [
$basePath . $this->getTransformedLocale() . '.php',
$basePath . $this->translator->getShortIsoCode() . '.php'
];
foreach ($paths as $path) {
if (is_file($path)) {
$this->countries = include $path;
break;
}
}
asort($this->countries, SORT_STRING);
}
}
return [];
/**
* @param array $supportedLocales
* @return bool
*/
private function isSupportedLocale(array $supportedLocales)
{
$localeAndRegion = $this->getTransformedLocale();
return array_key_exists($localeAndRegion, $supportedLocales)
|| array_key_exists($this->translator->getShortIsoCode(), $supportedLocales);
}
/**
* @return string
*/
private function getTransformedLocale()
{
return strtolower(str_replace('_', '-', $this->translator->getLocale()));
}
}
......@@ -9,6 +9,7 @@ namespace ACP3\Core\I18n;
use ACP3\Core\Cache;
use ACP3\Core\Environment\ApplicationPath;
use ACP3\Core\Modules\Vendor;
use Fisharebest\Localization\Locale;
/**
* Class Cache
......@@ -16,6 +17,8 @@ use ACP3\Core\Modules\Vendor;
*/
class DictionaryCache
{
use ExtractFromPathTrait;
/**
* @var Cache
*/
......@@ -32,9 +35,9 @@ class DictionaryCache
/**
* DictionaryCache constructor.
*
* @param \ACP3\Core\Cache $cache
* @param \ACP3\Core\Cache $cache
* @param \ACP3\Core\Environment\ApplicationPath $appPath
* @param \ACP3\Core\Modules\Vendor $vendors
* @param \ACP3\Core\Modules\Vendor $vendors
*/
public function __construct(
Cache $cache,
......@@ -78,14 +81,15 @@ class DictionaryCache
if ($languageFiles !== false) {
foreach ($languageFiles as $file) {
$xml = simplexml_load_file($file);
if (isset($data['info']['direction']) === false) {
$data['info']['direction'] = (string)$xml->info->direction;
$locale = Locale::create($this->getLanguagePackIsoCode($file));
$data['info']['direction'] = $locale->script()->direction();
}
$module = $this->getModuleFromPath($file);
// Iterate over all language keys
$xml = simplexml_load_file($file);
foreach ($xml->keys->item as $item) {
$data['keys'][strtolower($module . (string)$item['key'])] = trim((string)$item);
}
......@@ -96,18 +100,6 @@ class DictionaryCache
return $this->cache->save($language, $data);
}
/**
* @param string $path
*
* @return string
*/
protected function getModuleFromPath($path)
{
$pathArray = explode('/', $path);
return $pathArray[count($pathArray) - 4];
}
/**
* Gets the cache for all registered languages
*
......@@ -155,29 +147,19 @@ class DictionaryCache
*/
protected function registerLanguagePack($file)
{
$xml = simplexml_load_file($file);
$languageIso = $this->getLanguagePackIsoCode($file);
if (!empty($xml)) {
$languageIso = $this->getLanguagePackIsoCode($file);
try {
$locale = Locale::create($languageIso);
return [
$languageIso => [
'iso' => $languageIso,
'name' => (string)$xml->info->name
'name' => $locale->endonym()
]
];
} catch (\DomainException $e) {
return [];
}
return [];
}
/**
* @param string $file
*
* @return string
*/
protected function getLanguagePackIsoCode($file)
{
return substr($file, strrpos($file, '/') + 1, -4);
}
}
<?php
/**
* Copyright (c) 2017 by the ACP3 Developers.
* See the LICENCE file at the top-level module directory for licencing details.
*/
namespace ACP3\Core\I18n;
trait ExtractFromPathTrait
{
/**
* @param string $filePath
*
* @return string
*/
protected function getLanguagePackIsoCode($filePath)
{
return substr($filePath, strrpos($filePath, '/') + 1, -4);
}
/**
* @param string $filePath
*
* @return string
*/
protected function getModuleFromPath($filePath)
{
$pathArray = explode('/', $filePath);
return $pathArray[count($pathArray) - 4];
}
}
......@@ -71,6 +71,10 @@ class Mailer
* @var string
*/
private $template = '';
/**
* @var MailerMessage|null
*/
private $mailerMessage;
/**
* @var \PHPMailer
*/
......@@ -246,6 +250,8 @@ class Mailer
->setTemplate($data->getTemplate())
->setUrlWeb($data->getUrlWeb());
$this->mailerMessage = $data;
return $this;
}
......@@ -261,12 +267,9 @@ class Mailer
$this->phpMailer->Subject = $this->generateSubject();
if (is_array($this->from) === true) {
$this->phpMailer->setFrom($this->from['email'], $this->from['name']);
} else {
$this->phpMailer->setFrom($this->from);
}
$this->addReplyTo();
$this->addFrom();
$this->addSender();
$this->generateBody();
// Add attachments to the E-mail
......@@ -298,6 +301,33 @@ class Mailer
return "=?utf-8?b?" . base64_encode($this->decodeHtmlEntities($this->subject)) . "?=";
}
private function addReplyTo()
{
$replyTo = $this->mailerMessage->getReplyTo();
if (is_array($replyTo) === true) {
$this->phpMailer->addReplyTo($replyTo['email'], $replyTo['name']);
} elseif (!empty($replyTo)) {
$this->phpMailer->addReplyTo($replyTo);
}
}
private function addFrom()
{
if (is_array($this->from) === true) {
$this->phpMailer->setFrom($this->from['email'], $this->from['name']);
} else {
$this->phpMailer->setFrom($this->from);
}
}
private function addSender()
{
if (!empty($this->mailerMessage->getSender())) {
$this->phpMailer->Sender = $this->mailerMessage->getSender();
}
}
/**
* Generates the E-mail body
*
......
......@@ -33,6 +33,14 @@ class MailerMessage
* @var string|array
*/
private $from;
/**
* @var string|array
*/
private $replyTo;
/**
* @var string
*/
private $sender;
/**
* @var string|array
*/
......@@ -158,6 +166,42 @@ class MailerMessage
return $this;
}
/**
* @return array|string
*/
public function getReplyTo()
{
return $this->replyTo;
}
/**
* @param array|string $replyTo
* @return $this
*/
public function setReplyTo($replyTo)
{
$this->replyTo = $replyTo;
return $this;
}
/**
* @return string
*/
public function getSender()
{
return $this->sender;
}
/**
* @param string $sender
* @return $this
*/
public function setSender($sender)
{
$this->sender = $sender;
return $this;
}
/**
* @return array|string
*/
......
......@@ -123,7 +123,7 @@ class Modules
public function isInstalled($moduleName)
{
$info = $this->getModuleInfo($moduleName);
return !empty($info) && $info['installed'] === true;
return !empty($info) && $info['installed'] === true || $info['installable'] === false;
}
/**
......
......@@ -32,7 +32,7 @@ class CountryListTest extends \PHPUnit_Framework_TestCase
public function testValidLocale()
{
$this->translatorMock->expects($this->exactly(2))
$this->translatorMock->expects($this->exactly(6))
->method('getLocale')
->willReturn('en_US');
......@@ -44,7 +44,7 @@ class CountryListTest extends \PHPUnit_Framework_TestCase
public function testInvalidLocaleByPath()
{
$this->translatorMock->expects($this->exactly(2))
$this->translatorMock->expects($this->exactly(3))
->method('getLocale')
->willReturn('xx_ZZ');
......@@ -56,7 +56,7 @@ class CountryListTest extends \PHPUnit_Framework_TestCase
public function testInvalidLocaleByCharacters()
{
$this->translatorMock->expects($this->exactly(2))
$this->translatorMock->expects($this->exactly(3))
->method('getLocale')
->willReturn('2390');
......
<?php
/**
* Copyright (c) 2017 by the ACP3 Developers.
* See the LICENCE file at the top-level module directory for licencing details.
*/
/**
* Created by PhpStorm.
* User: tinog
* Date: 26.03.2017
* Time: 21:26
*/
namespace ACP3\Core\Test\View\Renderer\Smarty\Functions;
use ACP3\Core\Router\RouterInterface;
use ACP3\Core\Test\View\Renderer\Smarty\AbstractPluginTest;
use ACP3\Core\View\Renderer\Smarty\Functions\Uri;
class UriTest extends AbstractPluginTest
{
/**
* @var Uri
*/
protected $plugin;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $routerMock;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $smartyInternalTemplateMock;
protected function setUp()
{
$this->setUpMockObjects();
$this->plugin = new Uri($this->routerMock);
}
private function setUpMockObjects()
{
$this->routerMock = $this->getMockBuilder(RouterInterface::class)
->setMethods(['route'])
->getMock();
$this->smartyInternalTemplateMock = $this->getMockBuilder(\Smarty_Internal_Template::class)
->disableOriginalConstructor()
->getMock();
}
/**
* @return string
*/
protected function getExpectedExtensionName()
{
return 'uri';
}
public function testUriWithRouteOnly()
{
$params = [
'args' => 'foo/bar/baz'
];
$this->routerMock->expects($this->once())
->method('route')
->with('foo/bar/baz', false, null)
->willReturn('/foo/bar/baz/');
$this->assertEquals('/foo/bar/baz/', $this->plugin->process($params, $this->smartyInternalTemplateMock));
}
public function testUriWithForceHttp()
{
$params = [
'args' => 'foo/bar/baz',
'secure' => false,
];
$this->routerMock->expects($this->once())
->method('route')
->with('foo/bar/baz', false, false)
->willReturn('http://example.com/foo/bar/baz/');
$this->assertEquals('http://example.com/foo/bar/baz/', $this->plugin->process($params, $this->smartyInternalTemplateMock));
}
public function testUriWithForceHttps()
{
$params = [
'args' => 'foo/bar/baz',
'secure' => true
];
$this->routerMock->expects($this->once())
->method('route')
->with('foo/bar/baz', false, true)
->willReturn('https://example.com/foo/bar/baz/');
$this->assertEquals('https://example.com/foo/bar/baz/', $this->plugin->process($params, $this->smartyInternalTemplateMock));
}
}
......@@ -16,6 +16,10 @@ class UriSafeValidationRule extends AbstractValidationRule
return $this->isValid($data[$field], $field, $extra);
}
return (bool)preg_match('/^([a-z]{1}[a-z\d\-]*(\/[a-z\d\-]+)*)$/', $data);
if (is_scalar($data)) {
return (bool)preg_match('/^([a-z]{1}[a-z\d\-]*(\/[a-z\d\-]+)*)$/', $data);
}
return false;
}
}
......@@ -18,6 +18,8 @@
"acp3/composer-installer": "*",
"doctrine/dbal": "^2.5",
"doctrine/cache": "^1.6",
"fisharebest/localization": "^1.10",
"giggsey/locale": "^1.2",
"inlinestyle/inlinestyle": "^1.2",
"monolog/monolog": "^1.21",
"mrclay/minify": "^2.3",
......@@ -31,8 +33,7 @@
"symfony/http-kernel": "^3.1",
"symfony/yaml": "^3.1",
"symfony/event-dispatcher": "^3.1",
"friendsofsymfony/http-cache": "~1.0",
"umpirsky/country-list": "^2.0"
"friendsofsymfony/http-cache": "~1.0"
},
"autoload": {
"psr-4": {
......
<?xml version="1.0" encoding="UTF-8"?>
<language>
<info>
<name>Deutsch (Deutschland)</name>
<direction>ltr</direction>
</info>
<keys>
<item key="access_to_modules">Sie haben Zugriff auf folgende Module:</item>
<item key="acp">Administration</item>
......
<?xml version="1.0" encoding="UTF-8"?>
<language>
<info>
<name>English (United States)</name>
<direction>ltr</direction>
</info>
<keys>
<item key="access_to_modules">You have access to the following modules:</item>
<item key="acp">Administration</item>
......
......@@ -17,12 +17,12 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "*",
"acp3/core": "^4.9.2",
"acp3/setup": "^4.9.2",
"acp3/module-errors": "^4.9.2",
"acp3/module-permissions": "^4.9.2",
"acp3/module-system": "^4.9.2",
"acp3/module-users": "^4.9.2"
"acp3/core": "^4.10.0",
"acp3/setup": "^4.10.0",
"acp3/module-errors": "^4.10.0",
"acp3/module-permissions": "^4.10.0",
"acp3/module-system": "^4.10.0",
"acp3/module-users": "^4.10.0"
},
"autoload": {
"psr-4": {
......
<?xml version="1.0" encoding="UTF-8"?>
<language>
<info>
<name>Deutsch (Deutschland)</name>
<direction>ltr</direction>
</info>
<keys>
<item key="admin_index_create">Neuen Artikel erstellen</item>
<item key="admin_index_delete">Artikel löschen</item>
......
<?xml version="1.0" encoding="UTF-8"?>
<language>
<info>
<name>English (United States)</name>
<direction>ltr</direction>
</info>
<keys>
<item key="admin_index_create">Create new article</item>
<item key="admin_index_delete">Delete article</item>
......
......@@ -17,12 +17,12 @@
"prefer-stable": true,
"require": {
"acp3/composer-installer": "*",
"acp3/core": "^4.9.2",
"acp3/setup": "^4.9.2",
"acp3/module-errors": "^4.9.2",
"acp3/module-permissions": "^4.9.2",
"acp3/module-system": "^4.9.2",
"acp3/module-users": "^4.9.2"
"acp3/core": "^4.10.0",
"acp3/setup": "^4.10.0",
"acp3/module-errors": "^4.10.0",
"acp3/module-permissions": "^4.10.0",
"acp3/module-system": "^4.10.0",
"acp3/module-users": "^4.10.0"
},
"suggest": {
"acp3/module-seo": "Provides additional SEO capabilities"
......
......@@ -27,7 +27,7 @@ class OnDisplayCaptchaListener
* @param ACL $acl
* @param CaptchaExtensionInterface $captchaExtension
*/
public function __construct(ACL $acl, CaptchaExtensionInterface $captchaExtension)
public function __construct(ACL $acl, CaptchaExtensionInterface $captchaExtension = null)
{