Only Display a # of Characters in Twig - twig

I am very unfamiliar with twig. Here's what I have:
{% if wp.get_post_meta(post.ID, '_property_website').0 %}
<tr>
<th>{{ wp.__('Website', 'aviators') }}:</th>
<td>{{ wp.get_post_meta(post.ID, '_property_website').0 }}
</td>
</tr>
{% endif %}
I need to restrict this output to 35 characters without killing the link. It needs to still be active but only display 35 characters, plus ideally it would end with... to designate that the url is cut off but that's a bonus. Can anyone help?

I believe http://twig.sensiolabs.org/doc/filters/slice.html is what you are looking for
EDIT
Just found that Twig has an extension called text it includes the wordwrap filter that is exactly what you're looking for
Link: https://github.com/fabpot/Twig-extensions/blob/master/lib/Twig/Extensions/Extension/Text.php

You can make your own Twig Extension. It's very easy.
First you must create the file with the filter code:
<?php
//Acme/AcmeBundle/Twig/AnExtension.php
namespace Acme\AcmeBundle\Twig;
class AnExtension extends \Twig_Extension
{
public function getFilters()
{
return array(
new \Twig_SimpleFilter('cutText', array($this, 'cutTextFilter'))
);
}
public function cutTextFilter($text, $size = 50)
{
if (strlen($text) > $size)
{
return substr($text, 0, $size) . '...';
}
else
{
return $text;
}
}
public function getName()
{
return 'an_extension';
}
}
Then edit the services.yml file from this bundle, located at: /Acme/AcmeBundle/Resources/config/services.yml and add:
services:
acme.twig.an_extension:
class: Acme\AcmeBundle\Twig\AnExtension
tags:
- { name: twig.extension }
and now you can use the filter in your code:
<a href="http://{{ wp.get_post_meta(post.ID, '_property_website').0 }}">
{{ wp.get_post_meta(post.ID, '_property_website').0 | cutText(30) }}
</a>
More info: http://symfony.com/doc/current/cookbook/templating/twig_extension.html

Related

Get subcategories with twig on listing page

I'm trying to get the subcategories on a listing page in Shopware 6. However i can't seem to find the functionality to get an array with the subcategories with the template variables.
My goal is to loop over the children and make some kind of fastlinks in a CMS-element.
Is there a standard functionality build in shopware to get the children by id or name in TWIG?
I've tried to find anything relevant in
page.header.navigation.active
But the child data isn't available.
Thanks!
I don't think there is a build-in function to fetch this, but if you're doing it in a new CMS-Element you can take advantage of it by adding a new DataResolver for your new element and pass the subcategories to your CMS-element.
// myPlugin/src/DataResolver/SubcategoryListCmsElementResolver.php
<?php
namespace MyPlugin\DataResolver;
use Shopware\Core\Content\Category\CategoryDefinition;
use Shopware\Core\Content\Category\CategoryEntity;
use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
use Shopware\Core\Content\Cms\DataResolver\CriteriaCollection;
use Shopware\Core\Content\Cms\DataResolver\Element\AbstractCmsElementResolver;
use Shopware\Core\Content\Cms\DataResolver\Element\ElementDataCollection;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\ResolverContext;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
class SubcategoryListCmsElementResolver extends AbstractCmsElementResolver
{
public function getType(): string
{
return 'my-subcategory-list';
}
public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
{
/** #var CategoryEntity $categoryEntity */
$categoryEntity = $resolverContext->getEntity();
$criteria = new Criteria([$categoryEntity->getId()]);
$criteria->addAssociation('children');
$criteriaCollection = new CriteriaCollection();
$criteriaCollection->add('category_' . $slot->getUniqueIdentifier(), CategoryDefinition::class, $criteria);
return $criteriaCollection;
}
public function enrich(CmsSlotEntity $slot, ResolverContext $resolverContext, ElementDataCollection $result): void
{
/** #var CategoryEntity $categoryEntity */
$categoryEntity = $result->get('category_' . $slot->getUniqueIdentifier())?->getEntities()->first();
$slot->setData($categoryEntity->getChildren()?->sortByPosition()->filter(static function ($child) {
/** #var CategoryEntity $child */
return $child->getActive();
}));
}
}
services.xml
<service id="MyPlugin\DataResolver\SubcategoryListCmsElementResolver">
<tag name="shopware.cms.data_resolver"/>
</service>
You then provide a new template, e.g.
{% block element_my_subcategory_list %}
{% set subcategories = element.data.elements %}
{% set activeCategory = page.header.navigation.active %}
<ul>
{% for category in subcategories %}
<li>do something with your category</li>
{% endfor %}
</ul>
{% endblock %}
You can read more about data resolvers here in the docs: https://developer.shopware.com/docs/guides/plugins/plugins/content/cms/add-data-to-cms-elements#create-a-data-resolver
Not sure if I understood your problem correctly, but let's explain. If you need to fetch array inside array, you can create a loop inside a new loop something like that:
{% for item in category.item %}
{% for subcategory in category.subcategory %}
{{ subcategory.title}}
{% endfor %}
{% endfor %}

Shopware 6 - Find seo Url of variant on product detail page

I am trying to lookup the seoUrl of variants displayed on a product detail page - in the configurator.html.twig file. I have the option Id and have tried passing it as productId for the seoUrl function - but it doesn't return the correct seoUrl.
Searching for a solution, I found this question: Show all variations on the product detail page in Shopware 6 - which also lacks an answer.
But it hinted that you should add the data using a Subscriber - is that really necessary?
You may decorate the ProductDetailRoute, fetch the parent and with its children association and iterate them in the storefront template.
<service id="MyPlugin\Core\Content\Product\SalesChannel\Detail\ProductDetailRouteDecorator" public="true" decorates="Shopware\Core\Content\Product\SalesChannel\Detail\ProductDetailRoute">
<argument type="service" id="MyPlugin\Core\Content\Product\SalesChannel\Detail\ProductDetailRouteDecorator.inner"/>
<argument type="service" id="sales_channel.product.repository"/>
</service>
class ProductDetailRouteDecorator extends AbstractProductDetailRoute
{
private SalesChannelRepositoryInterface $productRepository;
private AbstractProductDetailRoute $decorated;
public function __construct(
AbstractProductDetailRoute $decorated,
SalesChannelRepositoryInterface $productRepository
) {
$this->decorated = $decorated;
$this->productRepository = $productRepository;
}
public function getDecorated(): AbstractProductDetailRoute
{
return $this->decorated;
}
public function load(string $productId, Request $request, SalesChannelContext $context, Criteria $criteria): ProductDetailRouteResponse
{
$response = $this->getDecorated()->load($productId, $request, $context, $criteria);
$product = $response->getProduct();
if (!$product->getParentId()) {
return $response;
}
$criteria = new Criteria([$product->getParentId()]);
$criteria->addAssociation('children');
$parent = $this->productRepository->search($criteria, $context)->first();
$product->setParent($parent);
return new ProductDetailRouteResponse($product, $response->getConfigurator());
}
}
{% if page.product.parent.children is defined %}
{% for child in page.product.parent.children %}
{{ seoUrl('frontend.detail.page', { productId: child.id }) }}
{% endfor %}
<br>
{% endif %}
Sample output:
http://localhost/Intelligent-Marble-Ultra-Beef/0491895660e94e32938022263595f861
http://localhost/Intelligent-Marble-Ultra-Beef/7c9f91d9051e40e0ba13d0e885e98d83
http://localhost/Intelligent-Marble-Ultra-Beef/f25c641abee446df82e1227cf200186c
Variation on this solution
A little more complex but with a lesser performance impact:
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('parentId', $product->getParentId()));
$ids = $this->productRepository->searchIds($criteria, $context)->getIds();
$product->addExtension('childrenIds', new ArrayStruct($ids));
{% if page.product.extensions.childrenIds is defined %}
{% for childId in page.product.extensions.childrenIds.all() %}
{{ seoUrl('frontend.detail.page', { productId: childId }) }}
<br>
{% endfor %}
{% endif %}
Yes, the best solution would be to fetch all variants based on the parentId in a subscriber.
This is necessary because you want to have more data then just the productId. You should also consider this information as necessary:
minPurchase
calculatedMaxPurchase
purchaseSteps
And optional:
packUnit
packUnitPlural
Without the necessary product information the end user will possibly encounter errors when adding a product with a invalid quantity to the cart.

Grav - Pass JSON data from Plugin to Twig

I want to retrieve a JSON from a webservice and incorporate it into a Twig template.
I went through the docs and I found that I could use this option.
I have followed the steps from the doc and I have prepared this plugin:
/var/www/html/grav/user/plugins/category# ls
category.php category.yaml twig
/var/www/html/grav/user/plugins/category# cat category.yaml
enabled: true
/var/www/html/grav/user/plugins/category# cat category.php
<?php
namespace Grav\Plugin;
use \Grav\Common\Plugin;
class CategoryPlugin extends Plugin
{
public static function getSubscribedEvents()
{
return [
'onTwigExtensions' => ['onTwigExtensions', 0]
];
}
public function onTwigExtensions()
{
require_once(__DIR__ . '/twig/CategoryTwigExtension.php');
$this->grav['twig']->twig->addExtension(new CategoryTwigExtension());
}
}
/var/www/html/grav/user/plugins/category# cat twig/CategoryTwigExtension.php
<?php
namespace Grav\Plugin;
class CategoryTwigExtension extends \Twig_Extension
{
public function getName()
{
return 'CategoryTwigExtension';
}
public function getFunctions()
{
return [
new \Twig_SimpleFunction('get_child_category', [$this, 'getChildCategoryFunction'])
];
}
public function getChildCategoryFunction()
{
$json = file_get_contents('http://localhost:8888/get_child_category/2/es_ES');
$obj = json_decode($json);
return $json;
}
}
I then incorporate the following function invocation in the Twig template:
{{ get_child_category() }}
But:
I can get $json string, but how can I pass the whole JSON data and retrieve individually the fields?
In my case if I use:
<span>{{ get_child_category() }}</span>
in Twig I get the following string:
[{"id": 11, "name": "Racoons"}, {"id": 10, "name": "Cats"}]
How would I access individual records in Twig, including iteration over the JSON array and individual field extraction (id, name) for each record?
Your function is returning an array. You need to iterate through it. Here is an example from the Grav docs.
<ul>
{% for cookie in cookies %}
<li>{{ cookie.flavor }}</li>
{% endfor %}
</ul>
A simple list of names from your example is a simple edit.
<ul>
{% for child in get_child_category() %}
<li>{{ child.name }}</li>
{% endfor %}
</ul>

Twig: while-workaround

I've already a solution, but just for JavaScript. Unfortunately while-loops do not exist in Twig.
My Twig-target in JavaScript:
var x = 10; // this is an unknown number
var result = x;
while (100 % result !== 0) {
result++;
}
console.log(result);
Any ideas how I do this in Twig?
What's my target: (not important if you already understood)
I want to get the first number after my unknown number, that satisfy the following condition:
100 divided by (the first number) equals a whole number as result.
EDIT: I have no access to PHP nor Twig-core.
You can make a Twig extension like:
namespace Acme\DemoBundle\Twig\Extension;
class NumberExtension extends \Twig_Extension
{
public function nextNumber($x)
{
$result = $x;
while (100 % $result !== 0) {
$result++;
}
return $result;
}
public function getFunctions()
{
return array(
'nextNumber' => new \Twig_Function_Method($this, 'nextNumber'),
);
}
/**
* Returns the name of the extension.
*
* #return string The extension name
*/
public function getName()
{
return 'demo_number';
}
}
And define it in the service.xml of the bundle:
<service id="twig.extension.acme.demo" class="Acme\DemoBundle\Twig\Extension\NumberExtension" >
<tag name="twig.extension" />
</service>
Then use it in the template:
{{ nextNumber(10) }}
UPDATE
A (not so great) approach but that possibly satisfy your needed is to do something like this:
{% set number = 10 %}
{% set max = number+10000 %} {# if you can define a limit #}
{% set result = -1 %}
{% for i in number..max %}
{% if 100 % i == 0 and result < 0 %} {# the exit condition #}
{% set result = i %}
{% endif %}
{% endfor %}
<h1>{{ result }}</h1>
Hope this help
In my case - I had to output an object with similar subobjects - including a templete with predefined values and setting up a normal if-condition worked.
See http://twig.sensiolabs.org/doc/tags/include.html for more infomration.

How can I minify HTML with Twig?

I'm using Twig and I'd like to be able to minify the HTML output. How do I do this? I tried {% spaceless %}, but that requires adding that to all my templates. Can I add minification within the Twig engine?
This may help you little.
use html-compress-twig you can compress html,css,js in one package.use composer to install composer require nochso/html-compress-twig and you have to add Extension with twig by using this code.
$app->extend('twig_theme', function($twig_theme, $ojt) {
$twig_theme->addExtension(new nochso\HtmlCompressTwig\Extension());
return $ojt_theme;});
finally go to your template file add this code.
{% htmlcompress %} ....your coding... {% endhtmlcompress %}
{{ htmlcompress('<ul> <li>') }}
{{ '<ul> <li>'|htmlcompress }}
For example you have the BaseController in your src/Controller directory.
You should create BaseController
Extends it from Controller
Override render method of the Controller class
And use this method in every controller
class BaseController extends Controller {
protected function render($view, array $parameters = array(), Response $response = null)
{
if ($this->container->has('templating')) {
$content = $this->container->get('templating')->render($view, $parameters);
} elseif ($this->container->has('twig')) {
$content = $this->container->get('twig')->render($view, $parameters);
} else {
throw new \LogicException('You can not use the "render" method if the Templating Component or the Twig Bundle are not available. Try running "composer require symfony/twig-bundle".');
}
if (null === $response) {
$response = new Response();
}
$content = preg_replace(array('/<!--(.*)-->/Uis',"/[[:blank:]]+/"),array('',' '),str_replace(array("\n","\r","\t"),'',$content));
$response->setContent($content);
return $response;
}
}
You also can extends BaseController in others controllers.
Use a Listener on kernel.response event to automatize the process:
In config/services.yaml:
services:
// ...
app.listener.compression:
class: App\Event\CompressionSubscriber
arguments:
tags:
- { name: kernel.event_subscriber }
In src/Event/CompressionSubscriber.php:
<?php
namespace App\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class CompressionSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
KernelEvents::RESPONSE => ['onKernelResponse', -256]
];
}
public function onKernelResponse($event)
{
if ($event->getRequestType() != HttpKernelInterface::MAIN_REQUEST) {
return;
}
$response = $event->getResponse();
$content = preg_replace(
['/<!--(.*)-->/Uis',"/[[:blank:]]+/"],
['',' '],
str_replace(["\n","\r","\t"], '', $response->getContent())
);
$response->setContent($content);
}
}
Based on this post
Use
{% spaceless %}
YOUR WHOLE PAGE GOES HERE HTML, TWIG, JS EVERYTHING...
{% endspaceless %}
It can be that your twig version does not recognize the tags, just update the latest version of twig.
This will minify the output html generated and page load will go up because it only loads the compiled version of html.
While you can still view the code in a readable situation.

Resources