Using Zend view helper from admin module with default module layout - zend-framework

I have a view helper being called in my layout that works fine in the default module, but I get an exception when I am in another module.
I have already changed my app.ini to use the default layout across all modules by setting:
resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts/"
And searching here and google provided me with another app.ini setting to add the view helper path for all modules:
resources.view.helperPath.Zend_View_Helper = APPLICATION_PATH "/views/helpers"
However instead of fixing the problem, that additional setting causes the Zend Exception to become a WSOD.
Without that second app.ini setting, I see the layout and get this exception:
Plugin by name 'AutoScript' was not found in the registry; used paths: Admin_View_Helper_: /Applications/XAMPP/xamppfiles/htdocs/dad/application/modules/admin/views/helpers/ Zend_View_Helper_: Zend/View/Helper/:./views/helpers/
With the helperPath.Zend_View_Helper ini setting I get a WSOD with the following:
Fatal error: Uncaught exception 'Zend_Loader_PluginLoader_Exception' with message 'Plugin by name 'AutoScript' was not found in the registry; used paths: Zend_View_Helper_: Zend/View/Helper/:./views/helpers/'
It appears the plugin loader is looking in public/views/helpers/ for the AutoScript.php file even though it should be using the APPLICATION_PATH value as a prefix.
My layout invocation looks like this
<?php $this->AutoScript(); ?>
My AutoScript.php file's class is defined in application/views/helpers/
class Zend_View_Helper_AutoScript extends Zend_View_Helper_Abstract {
public function AutoScript() {...}
}
My current fix is to copy the AutoScript.php file from application/views/helpers into modules/admin/views/helpers which fixes the issue, but duplicates a file. What am I missing? Do I have to add this view helper path programmatically by creating an _initView function in my bootstrap?

Typically, you would name your custom view helper with your own prefix, not the Zend_ prefix. Beyond that, there are several choices for where to put and name your view helpers.
If this view-helper is really a single-application helper, then I find it natural for it to reside somewhere in the application folder. Within that possibility space, I would ask if the view-helper will be used in a single module or across multiple modules.
If the view-helper is intended for use in a single module, then I depend upon the build-in resource-autoloader mappings and place my view-helper class Mymodule_View_Helper_Myhelper in the file application/modules/mymodule/views/helpers/Myhelper.php.
If the view-helper is intended for use across multiple modules , I might pull it up a little higher than the modules folder, say Application_View_Helper_Myhelper (assuming an appnamespace of Application) stored in
application/views/helpers/Myhelper.php. In this case, we need to tell the view that there are helpers with prefix Application_View_Helper_ in that directory. This can be done at bootstrap:
protected function _initViewHelperPaths()
{
$this->bootstrap('view');
$view = $this->getResource('view');
$view->addHelperPath(APPLICATION_PATH . '/views/helpers', 'Application_View_Helper_');
}
Sometimes, you need a view-helper in one module that exists in another module and you cannot - in practical terms - move the original one around. In that case, one workaround is to define an empty shell of a view-helper in your consuming module extending your unmovable view-helper. In file application/mymodule/views/helpers/MyHelper.php:
class Mymodule_View_Helper_Myhelper extends Othermodule_View_Helper_Myhelper
{
}
This way, the code for the helper implementation is not duplicated. The module-specific resource-autoloader mappings will allow all those classes to be found when they invoked as view-helpers from a view-script.
Finally, if this view helper is to be used in multiple projects, then there is an argument for placing it outside of application scope, out in in the library folder. So perhaps a class MyLibrary_View_Helper_Myhelper stored in the file library/MyLibrary/View/Helper/Myhelper.php. As before, you would need to inform the view - probably at bootstrap or in a front-controller plugin - of the prefix/path mapping:
$view->addHelperPath(APPLICATON_PATH . '/../library/MyLibrary/View/Helper', 'MyLibrary_View_Helper_');
Note that in all cases above, the invocation of the view-helper functionality itself - say, inside a view-script - is:
<?php echo $this->myhelper($params) ?>
Note, in particular, the casing difference between the classname and the invocation.

Related

Fluid standalone view in BE context

Given you are in a BE or CLI context (e.g. sending emails via extbase command controller task), the following worked up to 7 LTS for rendering a fluid standalone view:
$view = $this->objectManager->get(StandaloneView::class);
$view->setTemplatePathAndFilename('/Absolute/Path/To/Template.html');
$view->setFormat('html');
$view->getRequest()->setControllerExtensionName('Myextensionname');
return trim($view->render());
However, in 8 LTS, this throws the following exception:
Tried resolving a template file for controller action "Standard->index" in format ".html", but none of the paths contained the expected template fileā€¦ No paths configured.
As suggested in the wiki page at https://wiki.typo3.org/How_to_use_the_Fluid_Standalone_view_to_render_template_based_emails#Usage_in_TYPO3_8.7, I tried setting layout and partial root paths for the view:
$view->setLayoutRootPaths(['EXT:Myextensionname/Resources/Private/Layouts/']);
$view->setPartialRootPaths(['EXT:Myextensionname/Resources/Private/Partials/']);
However, this won't do the trick.
Digging a bit deeper, I could imagine that one would have to set the controller and action name, e.g. by setting the controller context, but that does not seem to be a solid solution as multiple other class instances are tied to it.
What is the correct way to render fluid standalone views in 8 LTS?
Here an example from our current webproject where we want to show a simple note in backend context based on a FLUID HTML in TYPO3 8.7
protected function renderMarkup(): string
{
$standaloneView = GeneralUtility::makeInstance(StandaloneView::class);
$standaloneView->getRequest()->setControllerExtensionName('in2template');
$templatePathAndFile = 'EXT:in2template/Resources/Private/Templates/Tca/ToolbarNoteEmptyFields.html';
$standaloneView->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templatePathAndFile));
$standaloneView->assign('toolbar', 'toolbarstuff');
return $standaloneView->render();
}
StandaloneView likes to receive all template paths (partial, template and layout root paths) so if you don't already provide all of them, you should do so. The reason being that the naming "Standalone" refers to the view being tied neither to a specific MVC action nor a specific extension context.
That said, if you use 8.7.5 there's a chance you are hit by a regression that is going to be solved by https://review.typo3.org/#/c/53917/ so it might be worth checking that out before you do a major refactoring. Technically the StandaloneView can be operated like a TemplateView with extension context, it's just not officially supported behavior and TYPO3 may not consistently apply all of the context you expect.
In my 8.7 extension I use the following code to get the StandaloneView:
$extbaseFrameworkConfiguration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
/** #var StandaloneView $emailView */
$emailView = $this->objectManager->get(StandaloneView::class);
$emailView->getRequest()->setControllerExtensionName($controllerExtensionName);
$emailView->getRequest()->setPluginName($pluginName);
$emailView->getRequest()->setControllerName($controllerName);
$emailView->setTemplateRootPaths($extbaseFrameworkConfiguration['view']['templateRootPaths']);
$emailView->setLayoutRootPaths($extbaseFrameworkConfiguration['view']['layoutRootPaths']);
$emailView->setPartialRootPaths($extbaseFrameworkConfiguration['view']['partialRootPaths']);
$emailView->setTemplate('Email/' . ucfirst($templateName));
$emailView->assignMultiple($variables);
$emailBody = $emailView->render();
In my function I gave the $controllerExtensionName, $pluginName and $controllerName as parameter with default values, so that other controllers/plugins can use this function, too.

Smarty seems to ignore addTemplateDir

I'm working in a Zend Framework based application (Shopware).
I add a template dir in my controller like this:
class Shopware_Controllers_Backend_Pricify extends Shopware_Controllers_Backend_ExtJs
{
public function init()
{
$this->View()->addTemplateDir(dirname(__FILE__) . "/../../Views/backend/");
parent::init();
}
}
But somehow, smarty always looks in the (not existing) part of the controller action:
Unable to load template snippet 'backend/mycontroller/model/main.js' in 'snippet:string:{include file="backend/pricify/model/main.js"} in Smarty/sysplugins/smarty_internal_templatebase.php on line 128
The Controller works over loading via ext js, but I do not see that this is a problem. When I var_dump template directories, the correct dir is included. I debugged the code far into smarty, but never found the part, where the directories are checked.
I'm aware, that this may be a problem within the software stack, but since I do not know where to search, I ask here. If I need to post additional data, please tell me.
I found, that the problem was that shopware extends CamelCase to camel_case folders.

Zend_Layout for modules

How can I make every module have it's own layouts directory?
I.e. when I don't have any modules my layout entry in config file looks like this:
resources.layout.layoutPath = APPLICATION_PATH "/layouts"
I try entering i.e.
; Layout directory for admin module
admin.resources.layout.layoutPath = APPLICATION_PATH "/modules/admin/layouts"
Where admin is module name; but it doesn't work. For some strange reason ZF looks for module layouts in /module/admin/views/scripts directory.
I also have a separate module.ini config file for every module as per this tutorial, alas layout path there gets ignored as well. Also I've been trying to follow this modules layout tutorial but it didn't work, I guess due to differences in ZF versions (tutorial is rather old). So I don't know what else to do
Using plugin from the tutorial you are talked about:
class My_Controller_Plugin_RequestedModuleLayoutLoader extends Zend_Controller_Plugin_Abstract {
public function preDispatch(Zend_Controller_Request_Abstract $request) {
$config = Zend_Controller_Front::getInstance()->getParam('bootstrap')->getOptions();
$moduleName = $request->getModuleName();
if (isset($config[$moduleName]['resources']['layout'])) {
Zend_Layout::startMvc($config[$moduleName]['resources']['layout']);
}
}
}
application.ini
resources.frontController.plugins.layoutloader = My_Controller_Plugin_RequestedModuleLayoutLoader
module.ini:
resources.layout.layout = "Admin"
resources.layout.layoutPath = APPLICATION_PATH "/modules/admin/layouts/scripts"
Working fine.
A slightly alternate method to Ololo recommendation (which is a great way to do it)..
class YourApp_Controller_Plugin_Modulelayout extends Zend_Controller_Plugin_Abstract
{
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
$module = $request->getModuleName();
if ($module != 'default')
{
if (file_exists(APPLICATION_PATH . '/layouts/' . $module . '.html')) {
Zend_Layout::getMvcInstance()->setLayout($module);
}
}
}
}
Place this controller plugin in /library/YourApp/Controller/Plugin/Modulelayout.php
Then save your module layouts as the module name in your layouts folder (E.g., /layout/admin.phtml). If it does not find a layout for that module, it will default back to layout.phtml or whatever you originally set it to.
Have a look at this gist - https://gist.github.com/891384
This uses a combination of
Action helper to inspect the requested module and given a matching configuration, change the layout's layout and layoutPath properties in the preDispatch hook
Application resource plugin to capture module layout options, inject them into the above helper and add it to the helper broker
Happened to me too I got around it by using this line in my controller (I created a init function)
Zend_Layout::startMvc(array('layoutPath' => APPLICATION_PATH . '/modules/admin/layouts'));
As of Zend Framework 1.12 (Haven't tested it on previous releases):
Create your modules
Initialize the layout in your prefered fashion. For example in application.ini as zend tools does it:
resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"
Create a layout inside each module with the default layout name inside the modules path to "views/scripts/" for example "application/modules/default/views/scripts/layout.phtml"
Don't forget to create one for the default module as it will be your fallback layout!
DO NOT create the default layout inside /application/layouts/scripts or this won't work
You are ready to run!
When Zend_Layout doesn't find the default layut it will look into the modules folders for it.
If you need some extra tweaking you may create a plugin and assign it to the layout object itself. For example, inside application.ini:
resources.layout.pluginClass = "MyLibrary_Controller_Plugin_Layout"
...or in the Bootstrap:
Zend_Layout::getMvcInstance()->setPluginClass("MyLibrary_Controller_Plugin_Layout");
Cheers!

Zend Framework unknown module is interpreted as default module

i wanted to support multilingual structure for my work i used the following lines
$controller=Zend_Controller_Front::getInstance();
$router=$controller->getRouter();
$languageRouter=new Zend_Controller_Router_Route(":lang/:module/:controller/:action", array("lang"=>"en","module"=>"default","controller"=>"index","action"=>"index"),
array("lang"=>"[a-zA-Z]{2}"));
$router->addRoute("default",$languageRouter);
it works fine http://localhost/zend/public/en set the lang param to en and call default module
but the problem is that when i use url like this http://localhost/zend/public/en/anything
where anything isn't module it still show the default module how to prevent that???
after the answer of takeshin i added this function to the bootstarp file and now it works as i want it
protected function _initRoutes()
{
$routeLang=new Zend_Controller_Router_Route(':lang',array('lang'=>'en'),array('lang'=>'[a-z]{2}'));
$front = Zend_Controller_Front::getInstance() /*$this->getResource('frontcontroller')*/;
$router = $front->getRouter();
$routeDefault=new Zend_Controller_Router_Route_Module(array(),$front->getDispatcher(),$front->getRequest());
$routeLangDefault=$routeLang->chain($routeDefault);
$router->addRoute('default',$routeLangDefault);
$router->addRoute('lang',$routeLang);
}
It looks like you have overwritten default module defined in Zend Application by your custom one.
You should chain the routes instead.
The settings you are using means module will 'default' to default , if you didn't it would throw a route not found error - which should throw to appropriate error controller
I'm not sure if I unterstood this correctly, but it looks like it works fine, as it should. If you try to call non existing module, Zend Framework automatically "redirects" to the default module.

Zend Action helper

I am learning how to use Zend framework and realise that the action helper is something that would be useful.
I have set up a default installation of Zend on my machine, but I dont know where the helper file needs to go, what I need to put in the bootstrap file and how I use it. Can anyone point me in the right direction please - the ZF user guide is not to clear to me.
Thanks
John
Two thoughts for where to place your custom action-helpers:
In a separate, custom library
In the folder application/controllers/helpers
These ideas are not exclusive. Functionality that is general enough to work in multiple projects should probably be pulled into a separate library. But for functionality that is application-specific, there is an argument that it could be somewhere in the application folder.
#Jurian has already described the "separate-library" approach. For app-specific helpers, you can do as follows:
For a helper called myHelper, create a class Application_Controller_Helper_MyHelper in the file application/controllers/helpers/MyHelper.php. In Bootstrap, you have something like:
protected function _initAutoload()
{
$autoloader = new Zend_Application_Module_Autoloader(array(
'namespace' => 'Application',
'basePath' => APPLICATION_PATH,
));
Zend_Controller_Action_HelperBroker::addPath(
APPLICATION_PATH . '/controllers/helpers',
'Application_Controller_Helper_');
return $autoloader;
}
Then your helper can be invoked in a controller by using:
$this->_helper->myHelper;
As you can see, this presumes you are using appNamespace 'Application'. If not, you can (must!) modify your class names to accommodate your circumstance.
Cheers!
You can place action helpers in your own library. Besides library/Zend where all the Zend stuff is around, you can create a library/MyLibrary folder (MyLibrary is arbitrary chosen) and put the action helpers there.
A good place is the library/MyLibrary/Controller/Action/Helper folder you need to create and place your action helper there (i.e. Navigation.php). In this file, create the class MyLibrary_Controller_Action_Helper_Navigation.
The next step is to add the action helper to the HelperBroker of the Zend Framework during bootstrap. Therefore, create a new method in your Bootstrap.php file and add this function:
protected function _initActionHelpers ()
{
Zend_Controller_Action_HelperBroker::addHelper(
new MyLibrary_Controller_Action_Helper_Navigation()
);
}
One last remark is you need to configure the use of this library by adding this rule to your application.ini:
autoLoaderNameSpaces[] = "MyLibrary_"
You can do this through your application.ini file like so
resources.view[] =
resources.view.helperPath.Default_View_Helper_ = APPLICATION_PATH "/views/helpers/"
Then in your views/helpers path you can create a file like Time.php. This file would contain the following code:
<?php
class Default_View_Helper_Time extends Zend_View_Helper_Abstract
{
public function time()
{
$date = new Zend_Date();
return $date->get(Zend_Date::TIME_MEDIUM);
}
}
?>
To use this in your view scripts you would use
<?=$this->time()?>
Which would display the current time using your new View_Helper
You can avoid having to register your action helper namespace and path within the Bootstrap.php by declaring them in the application.ini instead like so:
resources.frontController.actionHelperPaths.My_Controller_Action_Helper = APPLICATION_PATH "/controllers/helpers"
Simply replace My_Controller_Action_Helper with your desired namespace, and modify the helpers directory path accordingly.
The helper can be initialized the same way:
$this->_helper->myHelper;
As mentioned by the docs, registering the prefix or path of the helpers is usually preferred because helpers would not be initialized until they are called like in the snippet above.
Of course, instantiating and passing helpers to the broker is a bit
time and resource intensive, so two methods exists to automate things
slightly: addPrefix() and addPath().
http://framework.zend.com/manual/1.12/en/zend.loader.pluginloader.html
Adding the config entry to the application.ini follows the same suggested pattern.