Problem: I wrote a conditional VH (extending AbstractConditionViewHelper) and it works as usually, anyway I realized that in non-cached version it is evaluated only once. Initialy I thought that's my bug, but checked common <f:if> and the problem is identical :S
In general when I visit my page for the first time, condition is evaluated and valid result is given, but when I'll refresh the page VH isn't called anymore (checked by setting breakpoint inside the VH) and VH is always treated as FALSE. Only any change in view's code will cause that VH will be evaluated once, and again next refresh(es) won't call VH anymore.
typo3conf/ext/toolbox/Classes/ViewHelpers/IsFieldRequiredViewHelper.php:
<?php
namespace Vendor\Toolbox\ViewHelpers;
class IsFieldRequiredViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractConditionViewHelper {
/**
* #param string $fieldName Current field name
* #param string $requiredFields List of required names separated by commas
*
* #return string the rendered string
*/
public function render($fieldName, $requiredFields) {
$requiredArray = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $requiredFields, true);
return (in_array($fieldName, $requiredArray))
? $this->renderThenChild()
: $this->renderElseChild();
}
}
Usage:
{namespace toolbox=Vendor\Toolbox\ViewHelpers}
<toolbox:isFieldRequired fieldName="foo" requiredFields="foo, bar, baz">
<f:then>TRUE</f:then>
<f:else>FALSE</f:else>
</toolbox:isFieldRequired>
For the first hit I have TRUE but later only FALSE.
Any suggestions? Did I missed some important change in ViewHelpers API since 7.x- ?
Of course if extension is cached it will be not visible, as the first hit will be saved in cache with proper VH return.
The AbstractConditionViewHelper implements the TYPO3\CMS\Fluid\Core\ViewHelper\Facets\CompilableInterface interface. This means that it implements a compile method that actually returns PHP code that will be stored in the compiled Fluid views.
Have a look at this method in the source code:
public function compile($argumentsVariableName, $renderChildrenClosureVariableName, &$initializationPhpCode, \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $syntaxTreeNode, \TYPO3\CMS\Fluid\Core\Compiler\TemplateCompiler $templateCompiler)
{
foreach ($syntaxTreeNode->getChildNodes() as $childNode) {
if ($childNode instanceof ViewHelperNode
&& $childNode->getViewHelperClassName() === ThenViewHelper::class) {
$childNodesAsClosure = $templateCompiler->wrapChildNodesInClosure($childNode);
$initializationPhpCode .= sprintf('%s[\'__thenClosure\'] = %s;', $argumentsVariableName, $childNodesAsClosure) . LF;
}
if ($childNode instanceof ViewHelperNode
&& $childNode->getViewHelperClassName() === ElseViewHelper::class) {
$childNodesAsClosure = $templateCompiler->wrapChildNodesInClosure($childNode);
$initializationPhpCode .= sprintf('%s[\'__elseClosure\'] = %s;', $argumentsVariableName, $childNodesAsClosure) . LF;
}
}
return sprintf('%s::renderStatic(%s, %s, $renderingContext)',
get_class($this), $argumentsVariableName, $renderChildrenClosureVariableName);
}
Once compiled, the render() method will not be called anymore (it will on the first invocation, when the template is not yet compiled). Instead, the renderStatic() method will be called.
Solution: You can either
also override the renderStatic() method and implement your ViewHelper logic there (again)
not implement the render() method and simply overwrite the static evaluateCondition($arguments) method. This method is actually designed to be overwritten -- the default implementations of both render() and renderStatic() call this method:
This method decides if the condition is TRUE or FALSE. It can be overriden in extending viewhelpers to adjust functionality.
static protected function evaluateCondition($arguments = null)
{
$requiredArray = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $arguments['requiredFields'], true);
return (in_array($arguments['fieldName'], $requiredArray));
}
The quickest solution is to overwrite the class render and evaluateCondition like this:
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('yourArgument','array','',true);
}
public function render()
{
return self::evaluateCondition($this->arguments) ? $this->renderThenChild() : $this->renderElseChild();
}
/**
* #return bool
*/
protected static function evaluateCondition($arguments = null)
{
//do your stuff
return true;
}
Related
I currently have a custom form field with a bunch of optional parameters in the constructor. I want to change this and have the field use setter functions but I can't find any way to include my templated JavaScript except during construction
class CustomField extends FormField {
protected $myField;
public function __construct($name, $title = null, $myField = null)
{
parent::__construct($name, $title);
$this->setMyField($myField);
Requirements::javascriptTemplate('path/to/script.js', ['Field' => $this->myField]);
}
/**
* I can update the value of myField but the value is already baked into the JavaScript and wont be updated
*/
public function setMyField($value) {
$this->myField = $value;
return $this;
}
I found a solution but it does feel a little hacky. I added a RequireJs() function to the form field as such:
<?php
function RequireJs() {
Requirements::javascriptTemplate('path/to/script.js', ['Field' => $this->myField]);
}
The added $RequireJs to the top of my template file so it would be called when the template is being rendered.
What is the most straightforward way to get the current category in the view? I notice that there is a getTerm method in the Term class:
public function getEntity()
{
return $this->getTerm();
}
/**
* Returns the current Wordpress category
* This is just a wrapper for getCurrentCategory()
*
* #return \FishPig\WordPress\Model\Term
*/
public function getTerm()
{
if (!$this->hasTerm()) {
$this->setTerm($this->_registry->registry(Term::ENTITY));
}
return $this->_getData('term');
}
However if I try to utilize the method within a template (for example, the default post list wrapper.phtml template which utilizes the Term block in the layout) it throws an error:
<?php echo $this->getTerm() ?>
Recoverable Error: Object of class FishPig\WordPress\Model\Term could
not be converted to string in
I'm probably just missing something simple, any help would be greatly appreciated. Thanks!
$term = \Magento\Framework\App\ObjectManager::getInstance()
->get('Magento\Framework\Registry')
->registry('wordpress_term');
I have a regular php class outside of a controller, so it doesn't benefit from automatic injection of container. I need to access the response object from that class, and I guess I should get it from the container.
What's the proper way to access it ? Just pass it as argument so the outside class can use it ? Is there a better way ?
You need to use middleware for that because the response object is immutable so "changing" it will not update the response which will be used by slim.
$app->add(function($request, $response, $next) {
if($shouldRedirect === true) {
return $response->withRedirect('myurl'); // do not execute next middleware/route and redirect
}
return $next($request, $response); // execute next middleware/ the route
});
For more information about middleware have a look at this.
If you need to send a subrequest, Slim provides such functionality. Use it carefully though, as in some situations its result is not obvious.
<?php
class MySortOfOutsideClass
{
/**
* If you need to send a subrequest, you have to access application instance,
* so let's inject it here.
*/
public function __construct(\Slim\App $app)
{
$this->$app = $app;
}
/**
* Method that makes a subrequest, and returns the result of it.
*/
public function myMethod()
{
if ($subRequestIsRequired) {
return $this->app->subRequest('GET', '/hello');
}
}
}
I have created an Object to ID Data Transformer. It's part of a custom ObjectIdType that allows me to enter the ID of a document instead of using a 'document' form type. It's handy for MongoDB (when there could be 100 million documents to choose from).
The Data Transformer does a query upon the ID and returns an object. If it can't find an object then it returns null. The issue is - sometimes null is an acceptable value, and sometimes it isn't.
Even if I add a NotNull validator, I get the following error -
Catchable Fatal Error: Argument 1 passed to Character::setPlayer() must be an instance of Document\Player, null given
So it's calling the setter regardless of the validation failing. I fixed this by throwing a TransformationFailedException within the transformer - but this just seems like a bad idea. I shouldn't really be using a Data Transformer to validate.
The code of the transformer is below. What I'd like is to be able to put the validator in the correct place, and intercept the setter so it doesn't get called. Generally, this seems like a bit of a code smell, I would love to know how other people have solved this issue.
class ObjectToIdTransformer implements DataTransformerInterface
{
private $objectLocator;
private $objectName;
private $optional;
/**
* #param ObjectLocator $objectLocator
* #param $objectName
*/
public function __construct(ObjectLocator $objectLocator, $objectName, $optional = false)
{
$this->objectLocator = $objectLocator;
$this->objectName = $objectName;
$this->optional = $optional;
}
/**
* {#inheritdoc}
*/
public function transform($value)
{
if (null === $value) {
return null;
}
if (!$value instanceof BaseObject) {
throw new TransformationFailedException("transform() expects an instance of BaseObject.");
}
return $value->getId();
}
/**
* {#inheritdoc}
*/
public function reverseTransform($value)
{
if (null === $value) {
return null;
}
$repo = $this->objectLocator->getRepository($this->objectName);
$object = $repo->find($value);
if (!$this->optional && !$object) {
throw new TransformationFailedException("This is probably a bad place to validate data.");
}
return $object;
}
}
Actually, it's a PHP quirk that's very unintuitive — especially for those coming from other (logical, intuitive, sane) languages like Java. If you want to be able to pass a null argument to a typehinted parameter, you have to set its default value to null:
public function setPlayer(Player $player = null)
{
// ...
}
Yea, talk about some consistency here...
I'm trying to make a way to disable some view helpers that are inside "application/views/helpers"...
What I really want is to put some options on the application.ini to enable or disable some Helpers.
Example on application.ini:
helpers.Helper1=on
helpers.Helper2=off
Now the problem is that when a Helper is off, I want to rewrite some functions of this helper in order to return a different result on the view. In this way, I don't need to change anything in the view script.
I thought in having 2 different php files for each helper, in different locations. One with the real helper and another with the changed helper (to work when it is off on the application.ini).
The problem is that I don't know how to tell the view which one it shoul load...
Does anyone know how it could be done?
FINAL CODE
Ok, after many tries, I put it to work with the following code:
Bootstrap
protected function _initConfigureHelpers(){
$this->bootstrap('view');
$view = $this->getResource('view');
$view->addHelperPath("./../library/ConfigHelpers","Configurable_Helper");
$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
'ViewRenderer'
);
$viewRenderer->setView($view);
$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new Application_Plugin_ViewPlugins());
return $view;
}
Application_Plugin_ViewPlugins
class Application_Plugin_ViewPlugins extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request){
$front=Zend_Controller_Front::getInstance();
$bootstrap=$front->getParam('bootstrap');
$options=$bootstrap->getOption("helpers");
if (is_array($options)){
$view = $bootstrap->getResource('view');
foreach($options as $option => $value){
$helper=$view->getHelper($option);
if ($helper){
if ($value=="off")
$helper->__disable();
else if ($value!="on")
throw new Exception('The value of helpers.'.$option.' must be "on" or "off" on application.ini.');
} else {
throw new Exception("Inexistent Helper");
}
}
}
}
}
Modified helper example
require_once APPLICATION_HELPERS."CssCrush.php";
class Configurable_Helper_CssCrush extends Zend_View_Helper_CssCrush {
protected $__config_enabled = true;
public function __disable(){
$this->__config_enabled = false;
return $this;
}
public function __enable(){
$this->__config_enabled = true;
return $this;
}
public function cssCrush(){
if ($this->__config_enabled){
return parent::cssCrush();
} else{
return new Modified_CssCrush();
}
}
}
class Modified_CssCrush {
public static function file ( $file, $options = null ) {
return $file;
}
}
APPLICATION_HELPERS is defined on /public/index.php as "../application/views/helpers/".
Now, when I want to add a configurable helper, I put the original helper on "/application/views/helpers/" and then, create a modified version of it on "/library/ConfigHelpers" with the structure of the example above.
What I think you want is Dependency Injection which is coming in zf2, but not available in zf1.
With some tinkering though you can get what you need.
Configuring helpers in the bootstrap
(assumes default project structure)
View helpers paths config : application/configs/application.ini:
resources.view.helperPath.Zf_View_Helper_ = "Zf/View/Helper"
A simple configurable helper, (allows disable/enable but you can obviously add any methods you need (use this as base class for helpers that need the behaviour)
class Zf_View_Helper_Configurable extends Zend_View_Helper_Abstract
{
protected $isEnabled = true;
public function configurable()
{
return $this;
}
public function disable()
{
$this->isEnabled = false;
return $this;
}
public function enable()
{
$this->isEnabled = true;
return $this;
}
public function __toString()
{
if ($this->isEnabled) {
return 'Configurable is enabled';
} else {
return 'Configurable is disabled';
}
}
}
And configure the helpers in the bootstrap:
public function _initConfigureHelpers()
{
$this->bootstrap('view');
$view = $this->getResource('view');
$configurableHelper = $view->configurable();
$configurableHelper->disable();
}
You can add options in the .ini file and grab them in the bootstrap initConfigureHelpers() method.
If you want this behaviour from any default zf helper, do what #Ratzo said and extend those helpers and add the required behaviour and then configure them in your bootstrap.
Please take a look at the following link Zend_View link
Below is an important points you should consider from the Zend docs.
Note: Default Helper Path
The default helper path always points to the Zend Framework view
helpers, i.e., 'Zend/View/Helper/'. Even if you call setHelperPath()
to overwrite the existing paths, this path will be set to ensure the
default helpers work.
This means that you can't really turn off the helpers, unless you want to go about extending the Zend_View object and overwrite the setHelperPath method. This is not the way to go though.
Here is probably what you want to do. First though, here is my assumption.
Assumption : You want to write your own view helper that slightly alters what the current view helpers do by changing a few methods here or there.
Here is what you should do to accomplish that.
First, write your view helper. Make sure the last part of the class name is the same as the view helper you want to 'overwrite'. You don't have to, but this makes sure the original helper can't be used anymore.
class My_View_Helper_BaseUrl extends Zend_View_Helper_BaseUrl
{
private $_enabled = true;
public function setEnabled( $bool ){ $this->_enabled = (boolean) $bool; }
public function baseUrl(){
if( $this->_enabled ){
return 'testUrl'; //other code
}
else return parent::baseUrl();
}
Now that you have that, do the following
$view->setHelperPath('/path/to/my/helpers', 'My_View_Helper'); //1
echo $view->baseUrl(); //2
Excellent. Now you've effectively shadowed the original BaseUrl helper.
The above code will make it so that the view scans your directory
for any helpers before scanning the default zend directory. When it gets to line
2 the view will find YOUR baseUrl helper first and use THAT instead of the
original baseUrl helper. In the above example it should echo
'testurl' instead of the normal baseUrl behavior.
You can make a custom helper that extends the original helper, for example
class My_Helper_Url extends Zend_View_Helper_Url
{}
and rewrite the methods as you need.