Frontend plugin which is included via typoScript does not work in TYPO3 8.7 anymore - typo3-8.x

in TYPO3 7.6 I defined a Frontend Plugin which is of type "PLUGIN_TYPE_CONTENT_ELEMENT".
ext_localconf.php:
ExtensionUtility::configurePlugin(
'MyVendor.' . $_EXTKEY,
'Maildownload',
['Maildownload' => 'show'],
[],
ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
);
the configurePlugin method creates a typoScript Setup, where the the "run()" method of Extbase's Bootstrap class is called:
...
tt_content.myext_maildownload.20 = USER
tt_content.myext_maildownload20.userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
...
This means Extbase processes the request by calling the action "show" in the controller "Maildownload" of my extension.
I Call my plugin via ajax. That means, I define a page with a separate typenum and include the plugin via typoScript:
setup.ts:
ajax_page = PAGE
ajax_page {
typeNum = 123
config.disableAllHeaderCode = 1
config.metaCharset = UTF-8
config.debug = 0
config.additionalHeaders = Content-type:application/json
config.xhtml_cleaning = 0
config.adminPanel = 0
10 < tt_content.myext_maildownload
}
When I call the page index.php?type=123 the action "show" in the controller "Maildownload" will be executed.
After upgrading to TYPO3 8.7 this behaviour has obviously changed.
The generated typoScript code of the "configurePlugin()" method is still the same. When I call page index.php?type=123 TYPO3 renders it like an ordinary content element and does not call the TYPO3\CMS\Extbase\Core\Bootstrap->run method anymore. So my showAction method will not be called.
I could define a dataProcessor class now and move the showAction into it.
But that is not a great solution, because I don't want to bypass the complete extbase renderering process.
Is there now a new way to include my plugin into the page so that it will be treated like an extbase plugin and not like a content element?

#andrei thank you for your reply, but it did not hit the point.
Meanwhile I found out that the cause for the problem was this breaking change in TYPO3 8.5: https://docs.typo3.org/typo3cms/extensions/core/Changelog/8.5/Breaking-78002-EnforceCHashArgumentForExtbaseActions.html?highlight=chash
so a possible solution is to set the requireCHashArgumentForActionArguments to false in the typoScript plugin settings:
plugin.tx_myextension {
features.requireCHashArgumentForActionArguments = 0
...
}

Related

Routing changes when upgrading easyadmin bundle symfony

I am in charge of upgrading easyadmin bundle on an app that was previously built using symfony v4.4.19. Initially we had: easycorp/easyadmin-bundle v2.3.12. Then, we decided to upgrade the easyadmin bundle to v3 because we faced some issues when enabling/disabling a boolean property from the list view.
When I was using the v2 :
php bin/console debug:router showed a route called easyadmin with a path /myworkshop/ .
I had no Dashboard controller nor NecklaceCrudController, I simply had a controlller called
AccessoriesController.php with several actions like deleteAction that is executed when the user
deletes an entity, editAction when the user edits an entity, SearchAction ...
In the deleteAction there is this line of code:
return $this->redirect($this->generateUrl('easyadmin', array('action' => 'list', 'entity'=> $this->entity['name'])));
so the url would become something like this
/myworkshop/?action=list&entity=necklace
To open the easy admin interface I have to click on a menu link whose link is :
->createItem('Visit my workshop', ['route' => 'easyadmin']);
When I open this interface /references, I get the list of the different entities in my app, if
I select one, I see the list view and I can edit one entity successfully but I cannot
enable/disable boolean properties from the list view as mentioned earlier.
We specified /myworkshop instead of /admin in app>config>routing.yml
# easy admin
easy_admin_bundle:
resource: "#myShop/Controller/AccessoriesController.php"
type: annotation
prefix: /myworkshop```
- A custom css was successfully employed in : app>config>config.yml
easy_admin:
design:
assets:
css:
- 'bundles/css/easyadmin.css
When I upgraded to v3:
php bin/console debug:router showed a route called myshop_admin_dashboard_index (which was
automatically generated) with a path /easyadmin.
Dashboard controller and NecklaceCrudController were created, The DashboardController only has configureCrud() and configureMenuItems() functions. The latter contains the links yield MenuItem::linkToCrud . Question 1 : In version 2 no menu links where created explicitely like here, so I was wondering how was the complete list of entities correctly showing up on my application interface?
In DashboardController there is no index () function nor a route nor a link just configureCrud() and configureMenuItems() functions.
I want to keep the AccessoriesController.php with his several actions but now, with the new route and path, it is completely being ignored. Question 2 : Is there something that I have to change in the generateUrl part? can someone give me an example of what this will become if I opt for adminUrlgenerator like I read in the documentation?
The routing.yml file remains the same however, my easyadmin interface appears only when visiting this link /easyadmin instead of /myworkshop . Question 3: I want to keep the /workshop url , what should I do in addition to keeping the routing.yml as it is now?
I wish we could change myshop_admin_dashboard_index to easyadmin and /easyadmin to my /myworkshop as it was in version 2, because there are many parts in AccessoriesController where I use $this->generateUrl('easyadmin',
Question 4: The css is no longer applicable any idea why? could be related to the AccessoriesController that is not currently being taken into account.
Well, you can add the index method to your dashboard controller with route annotation to change the route
class DashboardController extends AbstractDashboardController
{
/**
* #Route("/myworkshop", name="admin")
*/
public function index(): Response
{
return $this->render('dashboard/index.html.twig');
}
You can add any route to easyadmin menu like this
class DashboardController extends AbstractDashboardController
{
public function configureMenuItems(): iterable
{
yield MenuItem::linktoRoute('Some Route', 'fa fa-info', 'route_name_here');
#...
}
}
You can add any CSS/js file too
class DashboardController extends AbstractDashboardController
{
public function configureAssets(): Assets
{
return Assets::new()
->addCssFile('build/admin.css')
->addJsFile('build/admin.js')
;
}
}

Getting a list of tagged services in my controller

What i want is to add services to the service container that i want to use later in my controller or service.
So i created two services with my custom tag fbeen.admin
here they are:
services:
app.test:
class: AppBundle\Admin\TestAdmin
tags:
- { name: fbeen.admin }
fbeen.admin.test:
class: Fbeen\AdminBundle\Admin\TestAdmin
tags:
- { name: fbeen.admin }
Now i want to use all the services with the tag fbeen.admin in my controller but i dont know how.
I followed the How to work with service tags tutorial but i get stuck on this rule:
$definition->addMethodCall('addTransport', array(new Reference($id)));
On some way the addTransport method of the TransportChain class should be called but it seems that it isn't been called.
And even if it would be called then i still do not have a list of services with the fbeen.admin tag into my controller.
I am sure that i am missing something but who can explain me what it is?
p.s. I know that compilerPass runs at buildtime but for example sonata admin knows all admin classes and twig knows all twig extensions. How do they know?
Thank you for reading this :-)
Symfony 3.3
Container gets compiled once (in debug more often, but in production only once). What you manage with addMethodCall... is that once you request your service from container, which you are storing in $definition (that in this case is controller). Then container will call method addMethodCall('method'.. during initialising your service.
What it will look in container:
// This is pseudo content of compiled container
$service = new MyController();
// This is what compiler pass addMethodCall will add, now its your
// responsibility to implement method addAdmin to store admins in for
// example class variable. This is as well way which sonata is using
$service->addAdmin(new AppBundle\Admin\TestAdmin());
$service->addAdmin(new AppBundle\Admin\TestAdmin());
return $service; // So you get fully initialized service
Symfony 3.4+
What you can do is this:
// Your services.yaml
services:
App/MyController/WantToInjectSerivcesController:
arguments:
$admins: !tagged fbeen.admin
// Your controller
class WantToInjectSerivcesController {
public function __construct(iterable $admins) {
foreach ($admins as $admin) {
// you hot your services here
}
}
}
Bonus autotagging of your services. Lets say all your controllers implements interface AdminInterface.
// In your extension where you building container or your kernel build method
$container->registerForAutoconfiguration(AdminInterface::class)->addTag('fbeen.admin');
This will tag automatically all services which implement your interface with tag. So you don't need to set tag explicitly.
The thing to note here is this: The CompilerPass doesn't run the 'addTransport' (or whatever you may call it) in the compiler-pass itself - just says 'when the time is right - run $definition->addTransport(...) class, with this data'. The place to look for where that happens is in your cache directory (grep -R TransportChain var/cache/), where it sets up the $transportChain->addTransport(...).
When you come to use that service for the first time - only then is the data filled in as the class is being instantiated from the container.
This worked for me:
extend the TransportChain class with a getTransports method:
public function getTransports()
{
return $this->transports;
}
and use the TransportChain service in my controller:
use AppBundle\Mail\TransportChain;
$transportChain = $this->get(TransportChain::class);
$transports = $transportChain->getTransports();
// $transports is now an array with all the tagged services
Thank you Alister Bulman for pushing me forwards :-)

How to send external data to OnlyOffice plugin

I am developing onlyoffice plugin, which need to consume data (like reportid, session details that will be used to load data from server) from launching application.
structure of page come out like:
launching page (editor.aspx)
-- iframe 1 to load editor
-- -- ifram 2 to load plugin
Here i want to access data from editor.aspx into iframe 2 (javascript)
I tried using queryString like window.parent.location.search but it only can traverse till iframe 1, but not main aspx page. As i don't have control on what loads in iframe 1 it didn't work.
Also i tried with cookies and localStorage but none worked out.
Please guide..
launching page (editor.aspx) -- iframe 1 to load editor -- -- ifram 2
to load plugin. Here i want to access data from editor.aspx into iframe
2 (javascript)
There is no way to get direct access to the iframe with editor, the only way to work with it is using document server plugins
Actually there is a way... Having spent a LOT of time analysing what is going on... finally found a nice way to exchange configs between the TOP frame and PLUGIN frame with just a few lines of code leveraging the onlyoffice API - without any hacks :)
Editor Config object:
config: {
"width" : "100%",
"height" : "100%",
"type" : "desktop",
"documentType": "text",
"token" : "{{token}}",
"document" : {
"title" : "{{document.name}}",
"url" : "{{downloadUrl}}",
...
events: {
'onReady': <application ready callback>, // deprecated
...
'onInfo': function ( data ) {
if ( data && data.data && data.data.getConfig ) {
docEditor.serviceCommand ( 'getConfig', config.document );
}
}
}
}
var docEditor = new DocsAPI.DocEditor("placeholder", config);
onInfo event will receive the request from your plugin.
Need to check the event data has getConfig attribute.
If it does, send back the config to the plugin.
Within your plugin's index.html add an inline script tag with this content:
// config ref
var config;
// Get ready to receive the response from TOP
window.parent.Common.Gateway.on ( 'internalcommand', ( data ) => {
if ( data.command === 'getConfig' ) {
config = data.data;
}
} );
// Send custom config request to TOP
window.parent.Common.Gateway.sendInfo ( { getConfig: true } );
It subscribes to the internalcommand Gateway events which will be called by TOP, then kick in the communication process by calling the sendInfo command. Because the editor and your plugins (most likely) will be hosted on the same domain, you can access it via the window.parent ref.
This will download the config.document configuration object and store it in the plugins local config variable automatically - when you click on the plugin in the toolbar.

Meteor Package: Add Custom Options

I've created a Meteor smart package, and would like to add user generated custom options to the API.
However, I'm having issues due to Meteor's automatic load ordering.
SocialButtons.config({
facebook: false
});
This runs a config block that adds defaults.
SocialButtons.config = function (options) {
... add to options if valid ...
};
Which in turn grabs a set of defaults:
var defaults = {
facebook: true,
twitter: true
}
Which are mixed into the settings.
var settings = _.extend(defaults, options);
...(program starts, uses settings)...
The problem is that everything must run in the proper order.
Create SocialButtons object
Run the optional SocialButtons.config()
Create settings & run the program
How can I control the load order in Meteor without knowing where a user might place the optional configuration?
Step 2 will be in a different folder/file, but must run sandwiched between steps 1 & 3.
You can't really control load order right now so it's not guaranteed but placing files at /libs are loaded first but in your case it's doesn't really matter it might be something else here is a very simple package you can view the source on how I setup default options and allow to replace those easily https://github.com/voidale/meteor-bootstrap-alerts
Figured this out.
Put your package into a /lib directory.
Include a setup function that sets the settings when called, and loads the data
Return the data from the startup function
In this case:
SocialButtons.get = function () {
return initButtons();
}
function initButtons() { ... settings, startup, return final value ... }

ZF2 unit-testing authentication

I was learning about unit testing and I attempted to resolve the following issue:
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for zfcUserAuthentication
... using the only answer given at:
Simple ZF2 Unit Tests for a controller using ZfcUser
So my setUp function looks the same. Unfortunately, I get the error message:
Zend\Mvc\Exception\InvalidPluginException: Plugin of type Mock_ZfcUserAuthentication_868bf824 is invalid; must implement Zend\Mvc\Controller\Plugin\PluginInterface
It is caused at this part of the code (split up in my code in the same way):
$this -> controller->getPluginManager()
->setService('zfcUserAuthentication', $authMock); // Error refers to this line.
The $authMock object is apparently not implementing plugininterface, which I need to implement to pass into setService.
Is $authMock not meant to be passed there for it's use in unit testing? Should I be using a different (unit-testing oriented) setService method?
I need a way to handle logging into my application, or my unit testing is pointless.
Thanks for any advice.
=== Edit (11/02/2013) ===
I wanted to focus on this part for clarification, as I think this is the problem area:
// Getting mock of authentication object, which is used as a plugin.
$authMock = $this->getMock('ZfcUser\Controller\Plugin\ZfcUserAuthentication');
// Some expectations of the authentication service.
$authMock -> expects($this->any())
-> method('hasIdentity')
-> will($this->returnValue(true));
$authMock -> expects($this->any())
-> method('getIdentity')
-> will($this->returnValue($ZfcUserMock));
// At this point, PluginManager disallows mock being assigned as plugin because
// it will not implement plugin interface, as mentioned.
$this -> controller->getPluginManager()
->setService('zfcUserAuthentication', $authMock);
If the mock doesn't handle necessary implementations, how else am I to pretend to login?
You have a problem with name-spacing or your autoloader.
When you are creating your mock, the class definition of ZfcUser\Controller\Plugin\ZfcUserAuthentication is not being found. So PHPUnit creates a mock that only extends this class for your test. If the class was available then PHPUnit will use the actual class to extend when making its mock, which will then use the parent classes/interfaces.
You can see this logic here: https://github.com/sebastianbergmann/phpunit-mock-objects/blob/master/PHPUnit/Framework/MockObject/Generator.php
if (!class_exists($mockClassName['fullClassName'], $callAutoload) &&
!interface_exists($mockClassName['fullClassName'], $callAutoload)) {
$prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n";
if (!empty($mockClassName['namespaceName'])) {
$prologue = 'namespace ' . $mockClassName['namespaceName'] .
" {\n\n" . $prologue . "}\n\n" .
"namespace {\n\n";
$epilogue = "\n\n}";
}
$cloneTemplate = new Text_Template(
$templateDir . 'mocked_clone.tpl'
);
So if there is no class or interface, PHPUnit will actually create one itself so that the mock will meet the type hinting of original class name. However, any parent classes or interfaces will not be included because PHPUnit is not aware of them.
This would be due to not including the proper namespace in your test or having a problem in your autoloader. It is difficult to tell without actually seeing the entire test file.
Alternatively rather than mocking ZfcUser\Controller\Plugin\ZfcUserAuthentication, you could mock the Zend\Mvc\Controller\Plugin\PluginInterface in your test and pass that into the plugin manager. Though if you are type-hinting for the plugin in your code, your test still won't work.
//Mock the plugin interface for checking authorization
$authMock = $this->getMock('Zend\Mvc\Controller\Plugin\PluginInterface');
// Some expectations of the authentication service.
$authMock -> expects($this->any())
-> method('hasIdentity')
-> will($this->returnValue(true));
$authMock -> expects($this->any())
-> method('getIdentity')
-> will($this->returnValue($ZfcUserMock));
$this -> controller->getPluginManager()
->setService('zfcUserAuthentication', $authMock);
I just made an example for the FlashMessenger plugin. You should just use the ControllerPluginManager to override the ControllerPlugin. Make sure that your application bootstrap calls setApplicationConfig();
<?php
namespace SimpleTest\Controller;
use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
class SimpleControllerTest extends AbstractHttpControllerTestCase {
public function testControllerWillAddErrorMessageToFlashMessenger()
{
$flashMessengerMock = $this->getMockBuilder('\Zend\Mvc\Controller\Plugin\FlashMessenger', array('addErrorMessage'))->getMock();
$flashMessengerMock->expects($this->once())
->method('addErrorMessage')
->will($this->returnValue(array()));
$serviceManager = $this->getApplicationServiceLocator();
$serviceManager->setAllowOverride(true);
$serviceManager->get('ControllerPluginManager')->setService('flashMessenger', $flashMessengerMock);
$this->dispatch('/error/message');
}
}?>

Resources