How to automatically escape variables in a Zend Framework 2 view - security

A lot of times in a Zend Framework 2 view I'll be calling $this->escapeHtml() to make sure my data is safe. Is there a way to switch this behaviour from a blacklist to a whitelist?
PS: Read an article from Padraic Brady that suggests that automatic escaping is a bad idea. Additional thoughts?

You could write your own ViewModel class which escapes data when variables are assigned to it.

Thanks to Robs comment, I extended the ZF2 ViewModel as follows:
namespace Application\View\Model;
use Zend\View\Model\ViewModel;
use Zend\View\Helper\EscapeHtml;
class EscapeViewModel extends ViewModel
{
/**
* #var Zend\View\Helper\EscapeHtml
*/
protected $escaper = null;
/**
* Proxy to set auto-escape option
*
* #param bool $autoEscape
* #return ViewModel
*/
public function autoEscape($autoEscape = true)
{
$this->options['auto_escape'] = (bool) $autoEscape;
return $this;
}
/**
* Property overloading: get variable value;
* auto-escape if auto-escape option is set
*
* #param string $name
* #return mixed
*/
public function __get($name)
{
if (!$this->__isset($name)) {
return;
}
$variables = $this->getVariables();
if($this->getOption('auto_escape'))
return $this->getEscaper()->escape($variables[$name]);
return $variables[$name];
}
/**
* Get instance of Escaper
*
* #return Zend\View\Helper\EscapeHtml
*/
public function getEscaper()
{
if (null === $this->escaper) {
$this->escaper = new EscapeHtml;
}
return $this->escaper;
}
}
In a Controller it could be used like this:
public function fooAction()
{
return new EscapeViewModel(array(
'foo' => '<i>bar</i>'
));
//Turn off auto-escaping:
return new EscapeViewModel(array(
'foo' => '<i>bar</i>'
),['auto_escape' => false]);
}
Question:
I would appreciate it if soemebody would comment, if this is best practice or if there is a better and ecp. more efficient and resource-saving way?

Related

camelCase parameters in setter in PhpStorm

I have under_score named properties in my class like transaction_id or web_order_item_id. And I want to customize the parameter name in my setters when I generate it.
For now it generate this:
/**
* #param int $original_transaction_id .camelCase()
*/
public function setOriginalTransactionId(int $original_transaction_id): void
{
$this->original_transaction_id = $original_transaction_id;
}
But I want this:
/**
* #param int $originalTransactionId .camelCase()
*/
public function setOriginalTransactionId(int $originalTransactionId): void
{
$this->original_transaction_id = $originalTransactionId;
}
I have tried to change this behavior in Settings/Editor/File and Code Templates/Code/Php Setter Method but I couldn`t found ability to do it.
There is a variable in the template:
${NAME}
But it returned value in this form $OriginalTransactionId instead $originalTransactionId
You can try using Apache Velocity StringUtils here, like
#set($Setter_param = ${StringUtils.removeAndHump(${PARAM_NAME})})
#set($Setter_param = $Setter_param.substring(0,1).toLowerCase() + $Setter_param.substring(1))
/**
* #param ${TYPE_HINT} $${Setter_param}
*/
public ${STATIC} function set${NAME}(#if (${SCALAR_TYPE_HINT})${SCALAR_TYPE_HINT} #end$${Setter_param})#if (${VOID_RETURN_TYPE}):void #end
{
#if (${STATIC} == "static")
self::$${FIELD_NAME} = $${Setter_param};
#else
$this->${FIELD_NAME} = $${Setter_param};
#end
}

Intervene template rendering

I have a controller method which I am using to "collect" variables to be assigned to template. I have overridden controller's render() method to merge "collected" and render parameters and assign them to template.
Example:
class Controller extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
private $jsVars = [];
protected function addJsVar($name, $value)
{
$this->jsVars[$name] = $value;
}
public function render($view, array $parameters = [], Response $response = null)
{
return parent::render($view, array_merge($parameters, ['jsVars' => $this->jsVars], $response);
}
public function indexAction()
{
// collect variables for template
$this->addJsVar('foo', 'bar');
return $this->render('#App/index.html.twig', ['foo2' => 'bar2']);
}
}
I just upgraded to Symfony 3.4 which complains that since Symfony4 I am not allowed to override render() method as it will be final.
How could I make it work seamlessly, i.e without defining a new method?
I know about Twig globals but these dont help me
I could use a service to collection variables and inject that service to Twig but that seems odd
Are there events I could listen, e.g TwigPreRender or smth?
You can render a controller from inside Twig like that:
{{ render(controller('App\\Controller\\YourController::yourAction', { 'args': 'hi' })) }}
Documentation here
Seems that there is no easy way.
Basically there are 2 options:
create your own template engine by extending current Symfony\Bundle\TwigBundle\TwigEngine
decorate current templating engine service templating.engine.mytwig
I chose the latter.
Few explanations:
I created service templating.engine.mytwig which decorates current engine templating.engine.twig. Class will get current ´TwigEngine` as input and I'll delegate most of the stuff to it
The class also needs to be twig extension by implementing \Twig_ExtensionInterface (or extending \Twig_Extension was sufficient for me). Also service needs to have tag twig.extension. Otherwise you'll end up having errors such as "Cannot find private service 'assetic' etc"
setParameter/getParameter are for collecting and returning parameters
Then I added shortcut methods to my Controller - setJsVar
Twig template requires also handling of those variables, preferably somewhere in the layout level. But that is not included here
One could you this solution to collect arbitrary template parameters, e.g if you want to assign from another method or whatever
It would be good idea to clear collected parameters after render
Was that all worth it? I dont know :) Cannot understand why Symfony team chose to make Controller::render final in the first place. But anyway here it is:
TwigEnging class:
namespace My\CommonBundle\Component\Templating\MyTwigEngine;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Bundle\TwigBundle\TwigEngine;
use Symfony\Component\HttpFoundation\Response;
class MyTwigEngine extends \Twig_Extension implements EngineInterface
{
/**
* #var TwigEngine $twig Original Twig Engine object
*/
private $twig;
/**
* #var array $parameters Collected parameters to be passed to template
*/
private $parameters = [];
/**
* MyTwigEngine constructor.
*
* #param TwigEngine $twig
*/
public function __construct(TwigEngine $twig)
{
$this->twig = $twig;
}
/**
* "Collects" parameter to be passed to template.
*
* #param string $key
* #param mixed $value
*
* #return static
*/
public function setParameter($key, $value)
{
$this->parameters[$key] = $value;
return $this;
}
/**
* Returns "collected" parameter
*
* #param string $key
* #return mixed
*/
public function getParameter($key, $default = null)
{
$val = $this->parameters[$key] ?? $default;
return $val;
}
/**
* #param string|\Symfony\Component\Templating\TemplateReferenceInterface $name
* #param array $parameters
*
* #return string
* #throws \Twig\Error\Error
*/
public function render($name, array $parameters = array())
{
return $this->twig->render($name, $this->getTemplateParameters($parameters));
}
/**
* #param string $view
* #param array $parameters
* #param Response|null $response
*
* #return Response
* #throws \Twig\Error\Error
*/
public function renderResponse($view, array $parameters = array(), Response $response = null)
{
return $this->twig->renderResponse($view, $this->getTemplateParameters($parameters), $response);
}
/**
* #param string|\Symfony\Component\Templating\TemplateReferenceInterface $name
*
* #return bool
*/
public function exists($name)
{
return $this->twig->exists($name);
}
/**
* #param string|\Symfony\Component\Templating\TemplateReferenceInterface $name
*
* #return bool
*/
public function supports($name)
{
return $this->twig->supports($name);
}
/**
* #param $name
* #param array $parameters
*
* #throws \Twig\Error\Error
*/
public function stream($name, array $parameters = array())
{
$this->twig->stream($name, $this->getTemplateParameters($parameters));
}
/**
* Returns template parameters, with merged jsVars, if there are any
* #param array $parameters
* #return array
*/
protected function getTemplateParameters(array $parameters = [])
{
$parameters = array_merge($this->parameters, $parameters);
return $parameters;
}
}
Decorator service (services.yml):
services:
templating.engine.mytwig:
decorates: templating.engine.twig
class: My\CommonBundle\Component\Templating\MyTwigEngine
# pass the old service as an argument
arguments: [ '#templating.engine.mytwig.inner' ]
# private, because you probably won't be needing to access "mytwig" directly
public: false
tags:
- { name: twig.extension }
Base controller alteration:
namespace My\CommonBundle\Controller;
use My\CommonBundle\Component\Templating\MyTwigEngine;
abstract class Controller extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
/**
* Allows to set javascript variable from action
*
* It also allows to pass arrays and objects - these are later json encoded
*
* #param string $name Variable name
* #param mixed $value - string|int|object|array
*
* #return static
*/
protected function setJsVar($name, $value)
{
/** #var MyTwigEngine $templating */
$templating = $this->getTemplating();
if (!$templating instanceof MyTwigEngine) {
throw new \RuntimeException(sprintf(
'Method %s is implemented only by %s', __METHOD__, MyTwigEngine::class
));
}
$jsvars = $templating->getParameter('jsVars', []);
$jsvars[$name] = $value;
$templating->setParameter('jsVars', $jsvars);
return $this;
}
/**
* Returns templating service
* #return null|object|\Twig\Environment
*/
private function getTemplating()
{
if ($this->container->has('templating')) {
$templating = $this->container->get('templating');
} elseif ($this->container->has('twig')) {
$templating = $this->container->get('twig');
} else {
$templating = null;
}
return $templating;
}
}

How to make a language switcher in main menu for Drupal8

Here is my problem on Drupal8. I have 3 languages in a website. In the future new languages can be installed.
I wanted to put a language switcher in the main menu.
My first try was to add a menu item with the UI. Like "Chinese". But it's impossible to state the language prefix doing like that. Also that means it won't update automatically if a language is added or removed from Drupal.
Second try :
$item = \Drupal\menu_link_content\Entity\MenuLinkContent::create([
'link' => ['uri' => 'internal:/zh-hans/'],
'title' => 'Chinese',
'menu_name' => 'main',
]);
$item->save();
It adds a new item to the main menu but it won't take the language prefix. Anyway this is not a dynamic solution cause you have to rebuild the menu when language will be added or removed. Also the order of the languages will be set once and for all... I prefer to have the current language at the top so that depends in which language you are at this moment.
Here is how I achieved language switcher links (for French) in a Drupal menu.
This code can be catered to your specific needs.
<my_module>.links.menu.yml:
<my_module>.switch:
class: Drupal\<my_module>\Plugin\Menu\MenuLanguageLink
menu_name: main
options:
attributes:
hreflang: 'fr'
Change "menu_name" to your Drupal menu.
Change "hreflang" to your language code.
src/Plugin/Menu/MenuLanguageLink.php:
namespace Drupal\<my_module>\Plugin\Menu;
use Drupal\Core\Menu\MenuLinkBase;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Routing\CurrentRouteMatch;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class MenuLanguageLink extends MenuLinkBase implements ContainerFactoryPluginInterface {
/** #var array Available languages */
protected $languages = [];
/** #var Drupal\Core\Language\Language */
protected $defaultLanguage;
/** #var Drupal\Core\Language\Language */
protected $currentLanguage;
/** #var Drupal\Core\Routing\CurrentRouteMatch */
protected $currentRouteMatch;
/**
* {#inheritdoc}
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
LanguageManagerInterface $language_manager,
CurrentRouteMatch $current_route_match
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->languages = $language_manager->getLanguages();
$this->defaultLanguage = $language_manager->getDefaultLanguage();
$this->currentLanguage = $language_manager->getCurrentLanguage();
$this->currentRouteMatch = $current_route_match;
}
/**
* Dependency injection.
*/
public static function create(
ContainerInterface $container,
array $configuration,
$plugin_id,
$plugin_definition
) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('language_manager'),
$container->get('current_route_match')
);
}
/**
* {#inheritdoc}
*/
public function updateLink(array $new_definition_values, $persist) {
return $this->pluginDefinition;
}
/**
* {#inheritdoc}
*/
public function getOptions() {
$options = parent::getOptions();
$langcode = $options['attributes']['hreflang'];
$langcode_default = $this->defaultLanguage->getId();
if ($langcode_default === $this->currentLanguage->getId()) {
if (isset($this->languages[$langcode])) {
$options += ['language' => $this->languages[$langcode]];
}
}
else {
$options += ['language' => $this->languages[$langcode_default]];
$options['attributes']['hreflang'] = $langcode_default;
}
return $options;
}
/**
* {#inheritdoc}
*/
public function getTitle() {
$langcode = $this->getOptions()['attributes']['hreflang'];
$langcode_default = $this->defaultLanguage->getId();
if ($langcode_default === $this->currentLanguage->getId()) {
return $this->languages[$langcode]->getName();
}
else {
return $this->languages[$langcode_default]->getName();
}
}
/**
* {#inheritdoc}
*/
public function getRouteName() {
return $this->currentRouteMatch->getRouteName();
}
/**
* {#inheritdoc}
*/
public function getRouteParameters() {
return $this->currentRouteMatch->getRawParameters()->all();
}
/**
* {#inheritdoc}
*/
public function getDescription() {
return $this->t('Switch language');
}
}
The trick is setting the 'language' option in getOptions().
Thanks to GregCube. I also created a module that adds Language links to Main menu, language links always point to Front page.
I created module 'helpers' and placed 3 files there:
listing of helpers.info.yml:
name: 'My Helpers'
type: module
description: 'My helpsers'
# core: 8.x
core_version_requirement: ^8.8 || ^9
#dependencies:
# - 'drupal:system (>= 8.8)'
version: '8.x-1.0'
datestamp: 1548970384
listing of helpers.links.menu.yml:
helpers.switch:
class: Drupal\helpers\Plugin\Menu\MenuLanguageLink
menu_name: main
options:
attributes:
hreflang: 'ru'
helpers.switch:
class: Drupal\helpers\Plugin\Menu\MenuLanguageLink
menu_name: main
options:
attributes:
hreflang: 'en'
listing of src/Plugin/Menu/MenuLanguageLink.php:
<?php
/*
Add menu links to a language Front page
options: hreflang
do not create a link if hreflang langcode does not exist
From https://stackoverflow.com/a/51005631/6851396
*/
namespace Drupal\helpers\Plugin\Menu;
use Drupal\Core\Menu\MenuLinkBase;
use Drupal\Core\Language\LanguageManagerInterface;
//use Drupal\Core\Routing\CurrentRouteMatch;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Url;
class MenuLanguageLink extends MenuLinkBase implements ContainerFactoryPluginInterface {
/** #var array Available languages */
protected $languages = [];
/**
* {#inheritdoc}
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
LanguageManagerInterface $language_manager
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->languages = $language_manager->getLanguages();
$this->url = \Drupal\Core\Url::fromRoute('<front>');
}
/**
* Dependency injection.
*/
public static function create(
ContainerInterface $container,
array $configuration,
$plugin_id,
$plugin_definition
) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('language_manager')
);
}
/**
* {#inheritdoc}
*/
public function updateLink(array $new_definition_values, $persist) {
return $this->pluginDefinition;
}
/**
* {#inheritdoc}
*/
public function getOptions() {
$options = parent::getOptions();
$langcode = $options['attributes']['hreflang'];
if (isset($this->languages[$langcode])) {
$options += ['language' => $this->languages[$langcode]];
}
return $options;
}
/**
* {#inheritdoc}
*/
public function getTitle() {
$langcode = $this->getOptions()['attributes']['hreflang'];
return $this->languages[$langcode]->getName();
}
/**
* {#inheritdoc}
*/
public function getRouteName() {
return $this->url->getRouteName();
}
/**
* {#inheritdoc}
*/
public function getRouteParameters() {
return $this->url->getRouteParameters();
}
/**
* {#inheritdoc}
*/
public function getDescription() {
$langcode = $this->getOptions()['attributes']['hreflang'];
return $this->languages[$langcode]->getName();
}
}
here how it works
language switcher in main menu, links to language front page always
You can follow Drupal's core language switcher code:
core/modules/language/src/Plugin/Block/LanguageBlock.php
First make sure to use PathMatcherInterface and LanguageManagerInterface
use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\Language\LanguageManagerInterface;
Then see get list of language links (lines 88 and 90):
$route_name = $this->pathMatcher->isFrontPage() ? '<front>' : '<current>';
$links = $this->languageManager->getLanguageSwitchLinks($type, Url::fromRoute($route_name));

Adding a new global variable in twig

I'm trying to get data for a new field added in login page. What I've done:
Modify AccountController.php login function adding new parameter: $this->_app->login($user, $client, !empty($data['rememberme']))
In Userfrosting.php login function i've set it in application: $this->client = $client;
In setupTwigUserVariables funtion added twig global: $twig->addGlobal("client", $this->client);
The problem is that in a template, {{client.id}} returns nothing. Any help will be appreciated.
In UserFrosting 4, you should create a Twig extension in your Sprinkle's src/Twig/ directory, and add the variable to the return value for getGlobals.
Your situation is a little tricky, since I'm not sure how client can be a global variable but at the same time depend on $data['client_id'] - which appears to be a request parameter. For now, I'll assume that you're submitting this parameter with any requests that require the client variable.
<?php
/**
* Stack Overflow
*
* #link https://stackoverflow.com
*/
namespace UserFrosting\Sprinkle\Site\Twig;
use Interop\Container\ContainerInterface;
use UserFrosting\Sprinkle\Site\Database\Models\Client;
/**
* Extends Twig functionality for the Site sprinkle.
*
* #author Jose Luis
*/
class Extension extends \Twig_Extension
{
protected $services;
protected $config;
public function __construct(ContainerInterface $services)
{
$this->services = $services;
$this->config = $services->config;
}
public function getName()
{
return 'myproject';
}
public function getGlobals()
{
try {
$currentUser = $this->services->currentUser;
// Assumes the client_id is being submitted as a query string (url) parameter
$clientId = $this->services->request->getQueryParam('client_id');
$client = Client::where('client_id', clientId)->where('userid', $currentUser->id)->first();
} catch (\Exception $e) {
$client = null;
}
return [
'client' => $client
];
}
}
You will then need to register this extension in your Sprinkle's service provider class:
<?php
/**
* Stack Overflow
*
* #link https://stackoverflow.com
*/
namespace UserFrosting\Sprinkle\Site\ServicesProvider;
use UserFrosting\Sprinkle\Site\Twig\Extension as JoseExtension;
/**
* Services provider for the Site sprinkle.
*
* #author Jose Luis
*/
class ServicesProvider
{
/**
* Register extended user fields services.
*
* #param Container $container A DI container implementing ArrayAccess and container-interop.
*/
public function register($container)
{
/**
* Extends the 'view' service with Jose's Twig Extension.
*/
$container->extend('view', function ($view, $c) {
$twig = $view->getEnvironment();
$extension = new JoseExtension($c);
$twig->addExtension($extension);
return $view;
});
}
}
Yes, I know that there is a lot of boilerplate here. However once you set these up the first time, it is easy to add new variables/functions/filters to the Twig environment and new services to your Sprinkle in the future.

How can I set a default value in Doctrinemongodb document?

How can I set the default value in field.
In my document I need to set default value false for field emailnotify
In mogodb th default value should be zero.
Check my document
namespace xxx\xxxBundle\Document;
use FOS\UserBundle\Document\User as BaseUser;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document
*/
class User extends BaseUser
{
/**
* #MongoDB\Id(strategy="auto")
*/
protected $id;
/**
* #MongoDB\Boolean
*/
protected $emailnotify;
/**
* Sets the emailnotify.
*
* #param boolean $emailnotify
*
* #return User
*/
public function setEmailnotify($emailnotify)
{
$this->emailnotify = (Boolean) $emailnotify;
return $this;
}
/**
* #return boolean
*/
public function isEmailnotify()
{
return $this->emailnotify;
}
}
I have found that setting the default value in the constructor works
public function __construct() {
$this->emailnotify = false;
}
Of course just setting the class variable to false will work for most parts if you use Doctrine to fetch the Document again afterwards, but the property will not be persisted to MongoDB like with the above.

Resources