Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Registered all namespaces in ModuleTemplateLoader class #9173

Merged
merged 5 commits into from Jun 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/PrestaShopBundle/Resources/config/services/services.yml
Expand Up @@ -325,6 +325,13 @@ services:

prestashop.twig.modules.loader:
class: 'PrestaShopBundle\Twig\Locator\ModuleTemplateLoader'
arguments: ['@=service("prestashop.module_kernel.repository").getActiveModulesPaths()']
arguments:
-
'PrestaShop': ''
'Product': '/Admin/Product'
'Twig': '/Admin/TwigTemplateForm'
'AdvancedParameters': '/Admin/Configure/AdvancedParameters'
'ShopParameters': '/Admin/Configure/ShopParameters'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not store this list elsewhere, like in a config or something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used just once, not sure it's necessary

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because this won't be re-used.

At start, I wanted to re-use the paths used in framework configuration, but thinking about it it's not a good idea. The 'array' look like the same but they have really different purposes.

If we have another place/service when we want to re-use it, of course I'm also in favor of extracting it into a parameter.

Note you can access to all registered Twig paths and namespaces using php bin/console debug:twig but not us, cause of weird call of Legacy Context in a Twig extension. This is a really minor issue we should try to fix asap.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we'll need to add new namespaces as we migrate new sections, won't we?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, so we will append new namespaces here.

But before I need to document which namespaces should be added as there is no real consistency actually: some templates use them, others don't, some namespace refers to a page, others to sections or even categories...

Once we will have consistency, I'm pretty sure we won't even need to configure an Array, but maybe loop into actual folders and declare namespaces dynamically ^^

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as it isn't done in runtime...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

array
nope, it's converted in PHP function in container cache: I don't think we can make the process faster 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes everything setting in yml files are automatically converted in string / array in the cache file =)

- '@=service("prestashop.module_kernel.repository").getActiveModulesPaths()'
tags:
- { name: twig.loader, priority: 1}
Expand Up @@ -23,117 +23,118 @@
* International Registered Trademark & Property of PrestaShop SA
*#}
<tbody
{% if activate_drag_and_drop and has_category_filter %}class="sortable"{% endif %}
last_sql="{{ last_sql_query|escape('html_attr') }}"
{% if activate_drag_and_drop and has_category_filter %}class="sortable"{% endif %}
last_sql="{{ last_sql_query|escape('html_attr') }}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe should be a data- attribute?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope! we dont change anything here: not the scope of this contribution.

HALTE AU GRABUGE !

>
{% for product in products %}
{% for product in products %}
{% block product_catalog_form_table_row %}
<tr data-uniturl="{{ product.unit_action_url|default('#') }}" data-product-id="{{ product.id_product }}">
<td class="checkbox-column form-group">
<div class="md-checkbox md-checkbox-inline">
<label>
<input type="checkbox" id="bulk_action_selected_products-{{ product.id_product }}" name="bulk_action_selected_products[]" value="{{ product.id_product }}">
<i class="md-checkbox-control"></i>
</label>
</div>
<div class="md-checkbox md-checkbox-inline">
<label>
<input type="checkbox" id="bulk_action_selected_products-{{ product.id_product }}" name="bulk_action_selected_products[]" value="{{ product.id_product }}">
<i class="md-checkbox-control"></i>
</label>
</div>
</td>
<td>
<label class="form-check-label" for="bulk_action_selected_products-{{ product.id_product }}">
{{ product.id_product }}
{{ product.id_product }}
</label>
</td>
<td>
<a href="{{ product.url|default('') }}#tab-step1">{{ product.image|raw }}</a>
<a href="{{ product.url|default('') }}#tab-step1">{{ product.image|raw }}</a>
</td>
<td>
<a href="{{ product.url|default('') }}#tab-step1">{{ product.name|default('N/A'|trans({}, 'Admin.Global')) }}</a>
<a href="{{ product.url|default('') }}#tab-step1">{{ product.name|default('N/A'|trans({}, 'Admin.Global')) }}</a>
</td>
<td>
{{ product.reference|default('') }}
{{ product.reference|default('') }}
</td>
<td>
{{ product.name_category|default('') }}
{{ product.name_category|default('') }}
</td>
<td class="text-center">
<a href="{{ product.url|default('') }}#tab-step2">{{ product.price|default('N/A'|trans({}, 'Admin.Global')) }}</a>
<a href="{{ product.url|default('') }}#tab-step2">{{ product.price|default('N/A'|trans({}, 'Admin.Global')) }}</a>
</td>

{% if 'PS_STOCK_MANAGEMENT'|configuration %}
<td class="product-sav-quantity text-center" data-product-quantity-value="{{ product.sav_quantity|default('') }}">
<a href="{{ product.url|default('') }}#tab-step3">
{% if product.sav_quantity is defined and product.sav_quantity > 0 %}
{{ product.sav_quantity }}
{% else %}
{{ product.sav_quantity|default('N/A'|trans({}, 'Admin.Global')) }}
{% endif %}
</a>
</td>
<td class="product-sav-quantity text-center" data-product-quantity-value="{{ product.sav_quantity|default('') }}">
<a href="{{ product.url|default('') }}#tab-step3">
{% if product.sav_quantity is defined and product.sav_quantity > 0 %}
{{ product.sav_quantity }}
{% else %}
{{ product.sav_quantity|default('N/A'|trans({}, 'Admin.Global')) }}
{% endif %}
</a>
</td>
{% else %}
<td></td>
{% endif %}
<td class="text-center">
{% if product.active|default(0) == 0 %}
<a href="#" onclick="unitProductAction(this, 'activate'); return false;">
<i class="material-icons action-disabled">clear</i>
</a>
{% else %}
<a href="#" onclick="unitProductAction(this, 'deactivate'); return false;">
<i class="material-icons action-enabled ">check</i>
</a>
{% endif %}
{% if product.active|default(0) == 0 %}
<a href="#" onclick="unitProductAction(this, 'activate'); return false;">
<i class="material-icons action-disabled">clear</i>
</a>
{% else %}
<a href="#" onclick="unitProductAction(this, 'deactivate'); return false;">
<i class="material-icons action-enabled ">check</i>
</a>
{% endif %}
</td>
{% if product.position is defined %}
<td {% if activate_drag_and_drop %}class="placeholder"{% endif %} style="cursor: pointer; cursor: hand;">
{% if activate_drag_and_drop %}
<big><big>⇅</big></big>
{% endif %}
<span class="position">{{ product.position }}</span>
<input type="hidden" name="mass_edit_action_sorted_products[]" value="{{ product.id_product }}" />
<input type="hidden" name="mass_edit_action_sorted_positions[]" value="{{ product.position }}" />
</td>
<td {% if activate_drag_and_drop %}class="placeholder"{% endif %} style="cursor: pointer; cursor: hand;">
{% if activate_drag_and_drop %}
<big><big>⇅</big></big>
{% endif %}
<span class="position">{{ product.position }}</span>
<input type="hidden" name="mass_edit_action_sorted_products[]" value="{{ product.id_product }}" />
<input type="hidden" name="mass_edit_action_sorted_positions[]" value="{{ product.position }}" />
</td>
{% endif %}
<td class="text-right">
<div class="btn-group-action">

{% set buttons_action = [
{
"href": product.preview_url|default('#'),
"target": "_blank",
"icon": "remove_red_eye",
"label": "Preview"|trans({}, 'Admin.Actions')
}
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

"href": product.preview_url|default('#'),
"target": "_blank",
"icon": "remove_red_eye",
"label": "Preview"|trans({}, 'Admin.Actions')
}
] %}

{% set buttons_action = buttons_action|merge([
{
"onclick": "unitProductAction(this, 'duplicate');",
"icon": "content_copy",
"label": "Duplicate"|trans({}, 'Admin.Actions')
}
{
"onclick": "unitProductAction(this, 'duplicate');",
"icon": "content_copy",
"label": "Duplicate"|trans({}, 'Admin.Actions')
}
]) %}

{% set buttons_action = buttons_action|merge([
{
"onclick": "unitProductAction(this, 'delete');",
"icon": "delete",
"label": "Delete"|trans({}, 'Admin.Actions')
}
{
"onclick": "unitProductAction(this, 'delete');",
"icon": "delete",
"label": "Delete"|trans({}, 'Admin.Actions')
}
]) %}

{% include '@Product/CatalogPage/Forms/form_edit_dropdown.html.twig' with {
'button_id': "product_list_id_" ~ product.id_product ~ "_menu",
'default_item': {
"href": product.url|default('#'),
"icon": "mode_edit"
},
'right': true,
'items': buttons_action
'button_id': "product_list_id_" ~ product.id_product ~ "_menu",
'default_item': {
"href": product.url|default('#'),
"icon": "mode_edit"
},
'right': true,
'items': buttons_action
} %}
</div>
</td>
</tr>
{% else %}
<tr><td colspan="11">
{% endblock %}
{% else %}
<tr><td colspan="11">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird indent

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was here previously, improving it now may make the contribution unreadable

{{ "There is no result for this search. Update your filters to view other products."|trans({}, 'Admin.Catalog.Notification') }}
</td></tr>
{% endfor %}
</tbody>
{% endfor %}
18 changes: 15 additions & 3 deletions src/PrestaShopBundle/Twig/Locator/ModuleTemplateLoader.php
Expand Up @@ -39,22 +39,34 @@ class ModuleTemplateLoader extends FilesystemLoader
private $rootPath;

/**
* @param array $namespaces A collection of path namespaces with namespace names.
* @param string|array $paths A path or an array of paths where to look for templates
* @param string|null $rootPath The root path common to all relative paths (null for getcwd())
* @param string|null $namespace A path namespace
*/
public function __construct($paths = array(), $rootPath = null, $namespace = 'PrestaShop')
public function __construct(array $namespaces, $paths = array(), $rootPath = null)
{
$this->rootPath = (null === $rootPath ? getcwd() : $rootPath).DIRECTORY_SEPARATOR;
if (false !== $realPath = realpath($rootPath)) {
$this->rootPath = $realPath.DIRECTORY_SEPARATOR;
}

if ($paths) {
$this->registerNamespacesFromConfig($paths, $namespaces);
}
}

/**
* Register namespaces in module and link them to the right paths.
* @param $paths
* @param array $namespaces
*/
private function registerNamespacesFromConfig($paths, array $namespaces)
{
foreach ($namespaces as $namespace => $namespacePath) {
$templatePaths = array();

foreach ($paths as $path) {
if (is_dir($dir = $path . '/views/'. $namespace)) {
if (is_dir($dir = $path . '/views/PrestaShop/' . $namespacePath)) {
$templatePaths[] = $dir;
}
}
Expand Down
@@ -0,0 +1 @@
module1
@@ -0,0 +1 @@
module2
@@ -0,0 +1 @@
List from module 3
@@ -0,0 +1 @@
module3
133 changes: 133 additions & 0 deletions tests/PrestaShopBundle/Twig/Locator/ModuleTemplateLoaderTest.php
@@ -0,0 +1,133 @@
<?php
/**
* 2007-2018 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2018 PrestaShop SA
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/

namespace Tests\PrestaShopBundle\Twig\Locator;

use PrestaShopBundle\Twig\Locator\ModuleTemplateLoader;
use PHPUnit\Framework\TestCase;

/**
* @group sf
*/
class ModuleTemplateLoaderTest extends TestCase
{
/**
* @var TemplateModuleLoader
*/
private $loader;

/**
* {@inheritdoc}
*/
protected function setUp()
{
$namespaces = [
'Product' => 'Admin/Product',
'PrestaShop' => '',
];

$paths = [
__DIR__.'/../Fixtures/module1',
__DIR__.'/../Fixtures/module2',
__DIR__.'/../Fixtures/module3',
];

$rootPath = null;

$this->loader = new ModuleTemplateLoader($namespaces, $paths, $rootPath);
}

/**
* {@inheritdoc}
*/
protected function tearDown()
{
$this->loader = null;
}

public function testGetPaths()
{
self::assertCount(
2,
$this->loader->getPaths('Product'),
'Two templates for the namespace "Product" should be found.'
);

self::assertCount(
3,
$this->loader->getPaths('PrestaShop'),
'One templates should be found.');
}

/**
* @dataProvider getSourceContextsProvider
* @param string $sourceContent The template file content.
* @param string $twigPathAsked The Twig path asked during Twig template rendering.
* @param string $successMessage In case of failure, describe what is expected.
*/
public function testGetSourceContext($sourceContent, $twigPathAsked, $successMessage)
{
self::assertEquals(
$sourceContent . PHP_EOL,
$this->loader->getSourceContext($twigPathAsked)->getCode(),
$successMessage
);
}

/**
* @return array
*/
public function getSourceContextsProvider()
{
return [
['module1', '@Product/test.html.twig', 'Module 1 wins as Module 3 is loaded after.'],
['module1', '@PrestaShop/Admin/Product/test.html.twig', 'PrestaShop is the main namespace.'],
['List from module 3', '@Product/ProductPage/Lists/list.html.twig', 'Module 3 templates are available.'],
['module2', '@PrestaShop/test.html.twig', 'Module 2 templates are availables.'],
];
}

public function testEmptyConstructor()
{
$loader = new ModuleTemplateLoader([]);

self::assertEquals(array(), $loader->getPaths());
}

/**
* @throws \Twig_Error_Loader
*/
public function testGetNamespaces()
{
$loader = new ModuleTemplateLoader([]);

self::assertEquals([], $loader->getNamespaces());

$loader->addPath(sys_get_temp_dir(), 'named');

self::assertEquals(['named'], $loader->getNamespaces());
}
}