To write a custom piwik plugin I'm following tutorial : http://piwik.org/blog/2014/09/create-widget-introducing-piwik-platform/
How can I access the request data that piwik receives within the plugin ?
Sample widget from above link :
class Widgets extends \Piwik\Plugin\Widgets
{
/**
* Here you can define the category the widget belongs to. You can reuse any existing widget category or define your own category.
* #var string
*/
protected $category = 'ExampleCompany';
/**
* Here you can add one or multiple widgets. You can add a widget by calling the method "addWidget()" and pass the name of the widget as well as a method name that should be called to render the widget. The method can be defined either directly here in this widget class or in the controller in case you want to reuse the same action for instance in the menu etc.
*/
protected function init()
{
$this->addWidget('Example Widget Name', $method = 'myExampleWidget');
$this->addWidget('Example Widget 2', $method = 'myExampleWidget', $params = array('myparam' => 'myvalue'));
}
/**
* This method renders a widget as defined in "init()". It's on you how to generate the content of the widget. As long as you return a string everything is fine. You can use for instance a "Piwik\View" to render a twig template. In such a case don't forget to create a twig template (eg. myViewTemplate.twig) in the "templates" directory of your plugin.
*
* #return string
*/
public function myExampleWidget()
{
$view = new View('#MyWidgetPlugin/myViewTemplate');
return $view->render();
}
}
How to access within the plugin the data piwik receives for each visitor request such as the request header fields ?
Access to a variable with name 'imageId':
$imageId = Common::getRequestVar('imageId');
About headers:
Piwik provides a list of headers methods through a ProxyHeaders class.
For now there are only two public static methods, that could be potentially interesting for you:
ProxyHeaders::getProxyClientHeaders, working with
'HTTP_CF_CONNECTING_IP',
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
and ProxyHeaders::getProxyHostHeaders for
'HTTP_X_FORWARDED_HOST'
Both these methods call another one, which is private:
/**
* Get headers present in the HTTP request
*
* #param array $recognizedHeaders
* #return array HTTP headers
*/
private static function getHeaders($recognizedHeaders)
{
$headers = array();
foreach ($recognizedHeaders as $header) {
if (isset($_SERVER[$header])) {
$headers[] = $header;
}
}
return $headers;
}
Because method getHeaders is private and it actually doesn't do what you want, probably the easiest way would be just to read headers directly from $_SERVER.
It will work this way: if you have a header with name "my-test-header" and value "123":
$_SERVER['HTTP_MY_TEST_HEADER'] // returns "123"
"Content-Type" => 'application/x-www-form-urlencoded'
$_SERVER['HTTP_CONTENT_TYPE'] // returns "application/x-www-form-urlencoded"
etc.
One note about web servers, whether it's Apache or Nginx or any other one, the configuration really matters here, especially for a HTTP_X_FORWARDED_FOR header.
Related
I have a Symfony\Component\HttpFoundation\Request object and from it I want to fetch all the url paramethers that provided. In other words when the user visits the http://example.org/soempage?param1=value1¶m2=value2¶m3=value3
I want to generate an array that has these values ['param1','param2','param3'] .
Also I have seen this one: How to get all post parameters in Symfony2?
And I tried the following based on above:
/**
* #var request Symfony\Component\HttpFoundation\Request
*/
$parametersToValidate=$request->request->all();
$parametersToValidate=array_keys($parametersToValidate);
And
/**
* #var request Symfony\Component\HttpFoundation\Request
*/
$parametersToValidate=$request->all();
$parametersToValidate=array_keys($parametersToValidate);
Without the desired result but instead I get this error message:
Attempted to call an undefined method named "all" of class "Symfony\Component\HttpFoundation\Request\
Edit 1
The request I use int into a static method that validates my input. The method is called via the controller and is implemented like that for reusability purposes.
public static function httpRequestShouldHaveSpecificParametersWhenGiven(Request $request,array $parametersThatHttpRequestShouldHave)
{
$parametersToValidate=$request->parameters->all();
if(empty($parametersToValidate)){
return;
}
$parameters=array_keys($parametersToValidate);
foreach($parameters as $param){
if(!in_array($parameters,$parametersThatHttpRequestShouldHave)){
throw new InvalidNumberOfParametersException(implode(',',$parametersToValidate),implode(',',$parametersThatHttpRequestShouldHave),implode(',',$diff));
}
}
}
Did you try:
$parametersToValidate = $request->query->all();
I've got a search form with some select boxes. I render it in the headnavi on every page using ebedded controllers. (http://symfony.com/doc/current/book/templating.html#embedding-controllers)
I want to use the form output to redirect to my list-view page like this:
/list-view/{city}/{category}?q=searchQuery
My form and the request is working well when I call the controller through a route, but unfortunately when I embed the controller, I'm stumblig over two problems. Like I've read here (Symfony 2 - Layout embed "no entity/class form" validation isn't working) my request isn't handeled by my form because of the sub-request. There is a solution in the answer, but its not very detailed.
The other problem, after fixing the first one, will be that I can't do a redirect from an embedded controller (Redirect from embedded controller).
Maybe anyone has an easier solution for having a form on every page that lets me do a redirect to its data?
Many thanks and greetings
Raphael
The answer of Symfony 2 - Layout embed "no entity/class form" validation isn't working is 100% correct, but we use contexts and isolate them, so an action which always uses the master request would break the rules. You have all requests (one master and zero or more subrequests) in the request_stack. Injecting Request $request into your controller action is the current request which is the subrequest with only max=3 (injecting the Request is deprecated now). Thus you have to use the 'correct' request.
Performing a redirection can be done in many ways, like return some JS script code to redirect (which is quite ugly imho). I would not use subrequests from twig because it's too late to start a redirection then, but make the subrequest in the action. I didn't test the code, but it should work. Controller::forward is your friend, since it duplicatest the current request for performing a subrequest.
Controller.php (just to see the implementation).
/**
* Forwards the request to another controller.
*
* #param string $controller The controller name (a string like BlogBundle:Post:index)
* #param array $path An array of path parameters
* #param array $query An array of query parameters
*
* #return Response A Response instance
*/
protected function forward($controller, array $path = array(), array $query = array())
{
$path['_controller'] = $controller;
$subRequest = $this->container->get('request_stack')->getCurrentRequest()->duplicate($query, null, $path);
return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
YourController.php
public function pageAction() {
$formResponse = $this->forward('...:...:form'); // e.g. formAction()
if($formResponse->isRedirection()) {
return $formResponse; // just the redirection, no content
}
$this->render('...:...:your.html.twig', [
'form_response' => $formResponse
]);
}
public function formAction() {
$requestStack = $this->get('request_stack');
/* #var $requestStack RequestStack */
$masterRequest = $requestStack->getCurrentRequest();
\assert(!\is_null($masterRequest));
$form = ...;
$form->handleRequest($masterRequest);
if($form->isValid()) {
return $this->redirect(...); // success
}
return $this->render('...:...:form.html.twig', [
'form' => $form->createView()
]);
}
your.html.twig
{{ form_response.content | raw }}
Here's the big picture: I am writing a symfony2 bundle for my web application. This application consists in a standard website with CRUD controllers. And on the other side it contains a rest api that also manages creation/edit/... on entities.
I started writing the Rest\UserController for the User entity. It contains all standard REST actions (GET, POST, PUT, DELETE). It is based on the very good tutorial by William Durand: http://williamdurand.fr/2012/08/02/rest-apis-with-symfony2-the-right-way/
Once this was created and functional I have created another UserController to handle the web side of the application. In this controller I have an action called editAction that renders a form in an HTML response. This form, when submitted sends a PUT request to the same controller's action putAction. My idea was to forward the request to Rest\UserController with action putAction. Here is the code for UserController::putAction:
/**
* This action forwards the request to the REST controller. It redirects
* to a user list upon success, or displays the message should an error
* occur.
* #Route("/{id}/put", name="igt_user_put")
* #Method("PUT")
*/
public function putAction (User $user)
{
$response = $this->forward('MyBundle:Rest\User:put', array('id'=>$user->getId()));
if ($response->getStatusCode() == Response::HTTP_NO_CONTENT) {
return new RedirectResponse($this->generateUrl('igt_user_list'));
}
return $response;
}
This works like a charm and it feels like it is the good way to do it. The problem occurred when I thought I'd do the same for user activation/deactivation. I'd have a lockAction in my UserController that would run a request through the "Rest\UserController::putAction` with synthetic data to change the enabled field.
But my problem is that there seems to be no way to set the POST vars in the forward call (only path and query). I even tried using $kernel->handle($request) with a synthetic request but it doesn't find my Rest controller's routes.
Am I missing something ?
I'm not sure of this will work or not but you could try it.
// Framework controller class
public function forward($controller, array $path = array(), array $query = array())
{
$path['_controller'] = $controller;
$subRequest = $this->container->get('request_stack')
->getCurrentRequest()->duplicate($query, null, $path);
return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
We can see that it duplicates the current request and then handles it.
// Request
/**
* Clones a request and overrides some of its parameters.
*
* #param array $query The GET parameters
* #param array $request The POST parameters
* #param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
* ...
*/
public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
{
So the duplicate method will take an array of post variables. So try something like:
public function forwardPost($controller,
array $path = array(),
array $query = array(),
array $post = array())
{
$path['_controller'] = $controller;
$subRequest = $this->container->get('request_stack')
->getCurrentRequest()->duplicate($query, $post, $path);
return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
Be curious to see if this will work. I always just set my REST up as a separate application and then use guzzle to interface to it. But forwarding would be faster.
I'm trying to clear a custom placeholder from a viewscript, let's say I have a controller plugin that creates a sidebar:
$bootstrap = Zend_Controller_Front::getInstance()->getParam('bootstrap');
$view = $bootstrap->getResource('view');
$partial = $view->render('partials/_sidebar.phtml');
$view->placeholder('sidebar')->append($partial);
My partial contains my submenu (rendered through Zend_Navigation view helper).
In order to render that sidebar, I have this in my layout:
<?= $this->placeholder('sidebar'); ?>
But what if in some pages I don't want to display my sidebar (login page for example) ? How can I handle these cases?
I thought I could reset/clear my placeholder using $this->placeholder('sidebar')->exchangeArray(array()); but it seems that I can't access my placeholder from a viewscript:
// in /application/modules/default/views/account/login.phtml
<?php $placeholder = $this->placeholder('sidebar');
Zend_Debug::dump($placeholder); ?>
// output:
object(Zend_View_Helper_Placeholder_Container)#217 (8) {
["_prefix":protected] => string(0) ""
["_postfix":protected] => string(0) ""
["_separator":protected] => string(0) ""
["_indent":protected] => string(0) ""
["_captureLock":protected] => bool(false)
["_captureType":protected] => NULL
["_captureKey":protected] => NULL
["storage":"ArrayObject":private] => array(0) {
}
}
Any idea how to do such a thing?
Thanks.
Edit:
My problem was very simple actually, since my plugin was registered and executed in the postDispatch() method, then my viewscript was executed before the plugin and the layout was executed after the plugin.
From now on, what are my options? I can't really declare my sidebar in the preDispatch method because there won't be any script directory set, and therefore I won't be able to determine which view script to execute at this step.
I could also use an action() helper, what do you think? A question has been already asked about it. I still feel like this is not the proper way to do it, and it sounds overkilling to me.
Also, another idea would be to move my plugin into a the preDispatch() method of my controller, but that would lead me to copy/paste on every controller my sidebar, or create a baseController, but I still don't like this idea, I feel like I'm doing it wrong.
Any idea?
You were pretty close actually. You just needed to add the name of your placeholder, sidebar in this case:
$this->placeholder('sidebar')->exchangeArray(array()); should work.
See http://framework.zend.com/manual/en/zend.view.helpers.html#zend.view.helpers.initial.placeholder
EDIT:
That's strange that the above did not work for you. I tested this out on my own ZF website and it worked. Possibly different scenario. I don't know.
Here is another alternative, albeit more involved.
//Get the placeholder object directly
$placeholderObj = $this->getHelper('placeholder')
//This may be why you were getting null in your example above. Since your were using the magic method in the View class to call the placeholder view helper method directly.
//For example you could call the placeholder like so:
$placeholderObj->placeholder('sidebar')->set("Some value");
//Now the alternative way to remove the placeholder value is as follows.
$registry = $placeholderObj->getRegistry()
$registry->deleteContainer('sidebar');
EDIT3
It should work as a Zend_Controller_Plugin using preDispatch hook. I think the only you need to make sure of is that you order that plugin after you setting your view script/helper paths and layout paths. This can be done in the same plugin or in another plugin.
Here is a code example based on something I use on my website. The relevant part is just the stuff in preDispatch where I set the view script paths, etc
class RT_Theme extends Zend_Controller_Plugin_Abstract
{
private $_sitename;
private $_assets;
private $_appPath;
private $_appLibrary;
/**
* Constructor
*
* #param string $sitename
* #param SM_Assets $assets
* #param string $appPath
* #param string $appLibrary
*/
public function __construct($sitename, $assets, $appPath, $appLibrary)
{
$this->_sitename = $sitename;
$this->_assets = $assets;
$this->_appPath = $appPath;
$this->_appLibrary = $appLibrary;
}
/**
* Sets up theme
*
* Sets up theme template directory and assets locations (js, css, images)
*
* #param Zend_Controller_Request_Abstract $request
*/
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$module = $request->getModuleName();
$controller = $request->getControllerName();
$action = $request->getActionName();
$layout = Zend_Layout::getMvcInstance();
$view = $layout->getView();
$view->sitename = $this->_sitename;
$view->headTitle()->setSeparator(' - ')->headTitle($view->sitename);
$layout->setLayoutPath($this->_appPath . "/html/$module/layout/");
$view->setScriptPath($this->_appPath . "/html/$module/view/");
$view->addScriptPath($this->_appPath . "/html/$module/view/_partials/");
$view->addScriptPath($this->_appLibrary . '/RT/View/Partials/');
$view->addHelperPath('RT/View/Helper', 'RT_View_Helper');
$view->addHelperPath($this->_appPath . '/html/view/helpers','My_View_Helper');
$viewRenderer =
Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
$viewRenderer->setView($view);
//Ignore this line. Not relevant to the question
$this->_assets->loadFor($controller, $action);
//Example: Now I can set the placeholder.
$view->placeholder('header_title')->set($view->sitename);
}
}
I am working on an Authentication Plugin using a Controller Plugin. I define my navigation config within the application.ini file, and then use that and the Database user records to dynamically load the ACL and apply it to Zend_Navigation. This bit works, as it successfully loads the menu and only displays the pages the user is allowed to see.
However, this doesn't stop the user from going to the page directly. What I want to do is identify when the user is going to a page they don't have access to within the Controller Plugin so I can redirect their request to the Authentication page.
I was thinking there must be a function to retrieve the current page from Zend_Navigation, but I can't find it... so maybe it doesn't exist.
Anyway, this is my full Controller Plugin. Anyone see a solution?
<?php
class Pog_Model_AuthPlugin extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $oRequest)
{
/**
* Load user
*/
$oAuth = Zend_Auth::getInstance();
$oDbUsers = new Pog_Model_DbTable_Users();
if (!$oAuth->hasIdentity())
{
$oUser = $oDbUsers->createRow();
$oUser->name = "guest";
$oUser->setReadOnly(true);
}
else
{
$oUser = $oAuth->getIdentity();
$oUser->setTable($oDbUsers);
}
/**
* Load ACL
*/
$oAcl = new Zend_Acl();
$oAcl->addRole($oUser->name);
/**
* Add current user privileges
*/
$oPrivileges = $oUser->getPrivileges();
foreach ($oPrivileges as $oPrivilege)
{
if (!$oAcl->has($oPrivilege->resource))
$oAcl->addResource($oPrivilege->resource);
$oAcl->allow($oUser->name, $oPrivilege->resource, $oPrivilege->privilege);
}
/**
* Load Navigation view helper
*/
$oViewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer');
$oNavigation = $oViewRenderer->view->navigation();
/**
* Add remaining Navigation resources
*/
foreach ($oNavigation->getPages() as $oPage)
{
if (!is_null($oPage->getResource()) && !$oAcl->has($oPage->getResource()))
$oAcl->addResource($oPage->getResource());
}
/**
* Set ACL and Role
*/
$oNavigation->setAcl($oAcl)->setRole($oUser->name);
/**
* Check if use is allowed to be here
*/
...MAGIC GOES HERE...
}
}
I think that you should be able to get current navigation page as follows:
/**
* Load Navigation view helper
*/
$oViewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer');
$oNavigation = $oViewRenderer->view->navigation();
/*#var $active array */
$active = $oNavigation->findActive($oNavigation->getContainer());
/*#var $activePage Zend_Navigation_Page_Mvc */
$activePage = $active['page'];
// example of getting page info
var_dump($activePage->getLabel(), $activePage->getController(), $activePage->getAction());
Hope this helps.
This is the solution I used, since I can't get Marcin's solution working in my setup for some reason.
I did some more thinking and thought of a nice simple solution to the problem. Rather than use the Navigation module to find the Active page, I find it myself. Since I am already iterating through the pages, it's a piece of cake to compare the Controller and Action - if these both match I have my Active page!
The new getPages() foreach loop looks like this:
$oCurrentPage = null;
foreach ($oNavigation->getPages() as $oPage)
{
/**
* Check for Current Page
*/
if ($oPage->getController() == $oRequest->getControllerName()
&& $oPage->getAction() == $oRequest->getActionName())
$oCurrentPage = $oPage;
/**
* Add Resource, if missing
*/
if (!is_null($oPage->getResource()) && !$oAcl->has($oPage->getResource()))
$oAcl->addResource($oPage->getResource());
}