How to append a GET parameter to all urls using Phalcon - phalcon

Given a page retrieved at for example:
http://myapp.dev/path/subfolder?param=abc
Whenever the additional GET parameter called param is present it should be added automatically to all subsequent links in my navigation as constructed in the .volt template. For example:
Go to subfolder 2
I.e. based on this .volt link the the goal is to generate:
Go to subfolder 2

If you want to append Query string parameters only for given links you can go with Luke's solution.
However I think you want to achieve something a bit different and it involves custom logic. For this to happen we should create a custom Volt function.
Custom function definition:
public static function urlFor($params, $queryStringParams = [])
{
$di = \Phalcon\DI::getDefault();
if ($di->getRequest()->has('param')) {
$queryStringParams['param'] = $di->getRequest()->get('param');
}
return $di->getUrl()->get($params, $queryStringParams);
}
The above function acts the same as url() function in Phalcon, it just allows us to write a bit of custom logic before passing the parameters to url().
In your case we check if URL contains desired query param and we add it to every URL generated on the current request. In my case the above function is in Helper file so I can use it anywhere I need to.
This is our View service definition:
$di->set('view', function() use ($di) {
$view = new \Phalcon\Mvc\View();
...
$view->registerEngines([
'.phtml' => function($view, $di) {
$volt = new \Phalcon\Mvc\View\Engine\Volt($view, $di);
$options = [
'compiledPath' => $di->getConfig()->site->path->cache . 'volt/frontend/',
'compiledExtension' => '.php',
'compileAlways' => $di->getConfig()->debug,
];
$volt->setOptions($options);
...
// IMPORTANT PART: Overwriting default url() function in Volt
$compiler = $volt->getCompiler();
$compiler->addFunction('url', function($resolvedArgs, $exprArgs){
return 'Helpers\Common::urlFor(' . $resolvedArgs . ')';
});
return $volt;
}
]);
return $view;
});
Please note the IMPORTANT PART comment in the above code block.
Let us finish with example:
User is on this address:
http://myapp.dev/path/subfolder?param=abc
But somewhere in your code you want to generate a link to News page:
News
Our code will catch the param in the URL and will generate the following address:
http://myapp.dev/news/list?param=abc

Related

How to define route in phalcon

I have controller named GlossaryController with 2 actions indexAction and anotherAction in view i have a directory glossary and index.volt file
i want to define a route with parameters for example
http://localhost/project/glossary/another/params it should redirect me to indexAction with parameters
In your routes.php file in app/config folder add this line:
$router->add("/glossary/another/{param1}/?{param2}", array(
"controller" => "Glossary",
"action" => "another",
));
And your anotherAction method will be like:
public function anotherAction($param1, $param2 = 0) {
//some code
}
This way first param must be sent, second one is optional, you can add this way as much params as you like.
See official docs for various ways of routing:
https://olddocs.phalconphp.com/en/3.0.3/reference/routing.html

why using viewSimple renderer in Phalcon corrupts main renderer

First I declare simple view like this:
$di->set('viewSimple', function() {
$view = new \Phalcon\Mvc\View\Simple();
$view->setViewsDir('../app/views/');
$view->registerEngines(array(
".volt" => 'volt'
));
return $view;
});
then to generate html email, I use it as following:
public function renderHTMLEmail($template_name, $template_params) {
$content = $this->viewSimple->render("emails/$template_name", $template_params);
return $this->viewSimple->render("emails/master", array( 'content' => $content) );
}
My emails are being generated just fine, but whenever I call my renderHTMLEmail function actual page rendering is somehow corrupted and page appears totally blank (I have to use redirect as workaround). This is a mystery to me as main view renderer is completely different object. Can I prevent this?
Or does anybody have recommended method of generating arbitrary pieces of html outside of main view rendering process which would not interfere with it? There is couple of similar questions on SO, but none of solutions work. They either don't generate my email or they corrupt main view.
Found a solution, when registering the "\Phalcon\Mvc\View\Simple" component into the DI container make sure to use a new volt instance otherwise it will join up with the application's \Phalcon\Mvc\View volt instance somehow. This is what lot (https://stackoverflow.com/users/1672165/lot) was suggesting in the comments.
Code sample:
public function getTemplate($name, $params)
{
$parameters = array_merge(array(
'publicUrl' => $this->getDI()->getApp()->url->host . $this->getDI()->getApp()->url->baseUri
), $params);
$app = $this->getDI()->getApp();
$this->getDI()->set('simpleView', function() use ($app) {
$view = new \Phalcon\Mvc\View\Simple();
$view->setViewsDir(APP_DIR . $app->application->viewsDir);
$view->registerEngines(array(
".volt" => function ($view, $di) {
$volt = new \Phalcon\Mvc\View\Engine\Volt($view, $di);
$volt->setOptions(array(
'compiledPath' => APP_DIR . '/cache/volt/',
'compiledSeparator' => '_',
'compiledExtension' => '.compiled'
));
$compiler = $volt->getCompiler();
$compiler->addFunction('is_a', 'is_a');
return $volt;
}
));
return $view;
});
/* #var $simpleView \Phalcon\Mvc\View\Simple */
$simpleView = $this->getDI()->get('simpleView');
foreach ($parameters as $key => $value) {
$simpleView->{$key} = $value;
}
$html = $simpleView->render('emails/' . $name, $parameters);
return $html;
}
Sample was pulled straight from our working app so may need some re-working but should help solve the original issue.
This works for me using \View as the application DI registered view component which renders controller action volt views and the above \View\Simple with fresh volt registration successfully emails and allows the original view with volt to carry on.
This was asked a while ago however i've not seen a working solution anywhere else.
For completeness I've registered a github issue with the findings: https://github.com/phalcon/cphalcon/issues/3096
I recently had to deal with a similar problem, I'm not certain where you're falling short, but one important thing was to use a separate view from the one used by the application – you're already doing this. See if the following works.
protected function renderView($view, $template, array $data = null)
{
return $view
->reset()
->pick('emails/' . $template)
->setVars($data)
->start()
->render(null, null)
->finish()
->getContent();
}
public function renderHTMLEmail($template_name, $template_params)
{
$content = $this->render($template_name, $template_params);
return $this->render('master', array('content' => $content));
}

Dynamically Adding / Removing Route in Durandal Router when application is loaded

I need help in dynamically adding/removing route in Durandal Router. What I want is after user is logged in then I would be able to add or remove specific route depending upon logged in user's type.
I tried to add/remove route from visibleRoutes/allRoutes array ... but get binding exception from knockout library...
I was hoping it would be common scenario... but still couldn't find any solution ... please help me in fixing this issue.
Thanks.
Wasim
POST COMMENTS:
I tried this function to dynamically hide/show route... and similary tried to add/remove route from allRoutes[] ... but then get exception on knockout bidning
showHideRoute: function (url,show) {
var routeFounded = false;
var theRoute = null;
$(allRoutes()).each(function (route) {
if (url === this.url) {
routeFounded = true;
var rt = this;
theRoute = rt;
return false;
}
});
if (routeFounded)
{
if (show)
{
visibleRoutes.push(theRoute);
}
else
{
visibleRoutes.remove(theRoute);
}
}
}
In Durandal 2.0.
You can enumerate the routes to find the one you wish to show/hide.
Then change the value of: nav property
Then run buildNavigationModel();
here is an example:
// see if we need to show/hide 'flickr' in the routes
for (var index in router.routes) {
var route = router.routes[index];
if (route.route == 'flickr') {
if (vm.UserDetail().ShowFlickr) { // got from ajax call
// show the route
route.nav = true; // or 1 or 2 or 3 or 4; to have it at a specific order
} else if (route.nav != false) {
route.nav = false;
}
router.buildNavigationModel();
break;
}
}
Durandal 2.0 no longer has the method visibleRoutes. I found that the following works for me.
router.reset();
router.map([
{ route: 'home', moduleId: 'home/index', title: 'Welcome', nav: true },
{ route: 'flickr', moduleId: 'flickr/index', title: '', nav: true }
])
.buildNavigationModel()
.mapUnknownRoutes('home/index', 'not-found');
This removes all previous routes, if you want to maintain current routes you could try using the router.routes property to rebuild the array of routes.
I had a similar requirement. If I were you, I would take another approach. Rather than adding/removing routes when application loads - get the right routes to begin with per user type.
Two options, (I use both)
1) have a json service provide the proper routes per user type, this approach would be good if you need to 'protect/obscure' routes... i.e. I don't want the route referenced on any client resource.
2) A simpler solution see Durandal.js: change navigation options per area
You can have a settings property identify the user type.
I hope this helps.
I had a similar problem: First, router.visibleRoutes() is an observable array. In other words, when you change its value, the routes automatically update. However, the items in this array are not observable, so to make a change you need to replace the entire array and not just make a change to a single item in it.
So, all you have to do is find which item in this array you want to remove, and then create a new array without this item, and set router.visibleRoutes() to this new array.
If, for example, you find out the it is the 3rd item, then one way of doing it is:
router.visibleRoutes(router.visibleRoutes().splice(2, 1))
Note that splice() returns a new array where an item is removed. This new array is put into router.visibleRoutes.

Is it possible to render custom view (just custom .phtml) template with Phalcon\Mvc\View?

I need to render email templates in variable to send them later (which are stored in .phtml files), and i really don't want to implement my special class for handling this.
Is it possible to render not controller action view, but custom one?
I tried following code, but it outputs NULL :((
// Controller context
$view = new Phalcon\Mvc\View();
$view->setViewsDir('app/views/');
$view->setVar('var1', 'var2');
// Setting some vars...
$view->start();
$view->partial($emailTemplatePath);
$view->finish();
$result = $view->getContent();
var_dump($result); // Gives null
In addition to the response by Nikolaos, you can use $view->getRender() to render a single view returning its output.
$view->setViewsDir('apps/views/');
echo $view->getRender('partials', 'test'); // get apps/views/partials/test.phtml
You need to check the path of the $emailTemplatePath. It should point to the correct file i.e.
// points to app/views/partials/email.phtml
$view->partial('partials/email');
If you are using Volt and have registered that as your engine, then your file will need to be:
// app/views/partials/email.volt
I have a project where I use email and pdf templates and what I did was to have the rendering all take place within components.
Firstly, my folder structure contains (and I will only put here what is relevant) a cache, components and views directory. Let's look at the email setup rather than the PDF as this is more relevant to your situation.
/app
/cache
/email
/components
/views
/email
/elements
Of course there is public, controllers etc but let's not think about them for this.
I'm using Swift mailer for mine but I hope you will be able to use this all the same. In /app/components/Swift.php I have a __construct that calls for this->init_template_engine();
/**
* Create a volt templating engine for generating html
*/
private function init_template_engine() {
$this->_template = new \Phalcon\Mvc\View\Simple();
$di = new \Phalcon\DI\FactoryDefault();
$this->_template->setDI($di);
$this->_template->registerEngines([
'.volt' => function($view, $di) {
$volt = new \Phalcon\Mvc\View\Engine\Volt($view, $di);
$volt->setOptions([
'compiledPath' => APP_DIR."cache".DS."email".DS, // render cache in /app/cache/email/
'compiledSeparator' => '_'
]);
return $volt;
// or use ".phtml" => 'Phalcon\Mvc\View\Engine\Php' if you want,
// both will accept PHP code if ya don't fancy it being a 100% volt.
},
]);
// tell it where your templates are
$this->_template->setViewsDir(APP_DIR.'views'.DS.'email'.DS);
return $this->_template;
}
The constants above (like APP_DIR) are something I have already made in my bootstrap and all they do is store full paths to directories.
Once the $_template variable has a template engine set up I can then use it to render my templates.
/**
* Returns HTML via Phalcon's volt engine.
* #param string $template_name
* #param array $data
*/
private function render_template($template_name = null, $data = null) {
// Check we have some data.
if (empty($data)) {
return false; // or set some default data maybe?
}
// Use the template name given to render the file in views/email
if(is_object($this->_template) && !empty($template_name)) {
return $this->_template->render($template_name, ['data' => $data]);
}
return false;
}
A sample volt email template may look like this:
{{ partial('elements/email_head') }}
<h2>Your Order has been dispatched</h2>
<p>Dear {{ data.name }}</p>
<p>Your order with ACME has now been dispatched and should be with you within a few days.</p>
<p>Do not hesitate to contact us should you have any questions when your waste of money arrives.</p>
<p>Thank you for choosing ACME Inc.</p>
{{ partial('elements/email_foot') }}
All I have to do then is grab the html and use swiftmailer's setBody method and I'm done:
->setBody($this->render_template($template, $data), 'text/html');
You don't need to place separate view engines like this in components, it could become memory hungry like that, but it does show the whole process. Hope that makes sense :)
The easiest way to render a view and return it as a variable is to use the Phalcon\Mvc\View\Simple class. In your controller, declare a new instance of the Simple view class and attach a rendering engine to it. You can then use its render() method to select a view file and pass in variables:
// create a simple view to help render sections of the page
$simple_view = new \Phalcon\Mvc\View\Simple();
$simple_view->setViewsDir( __DIR__ . '/../views/' );
$simple_view->setDI( $this->di );
$simple_view->registerEngines(array(
'.volt' => 'Phalcon\Mvc\View\Engine\Volt'
));
// use the simple view to generate one or more widgets
$widget_html = array();
$widget_objects = $widget_search->getWidgetObjects();
forEach( $widget_objects as $widget ){
$widget_html[] = $simple_view->render('index/widgetview',array('widget'=>$widget));
}
// pass the html snippets as a variable into your regular view
$this->view->setVar('widget_html',$widget_html);
use $view->render('partials/email') instead of calling partial method.
I usually use Volt engine and a simple way is a redefine view in DI container, like that:
$view = $this->view;
$content = $view->getRender('mail', 'show',
array(
"var1" => "some value 1",
"var2" => "some value 2"
),
function($view) {
$view->setRenderLevel(\Phalcon\Mvc\View::LEVEL_LAYOUT);
}
);
echo $content;

Yii renderpartial (proccessoutput = true) Avoid Duplicate js request

Im creating a site who works with ajaxRequest, when I click a link, it will load using ajaxRequest. When I load for example user/login UserController actionLogin, I renderPartial the view with processOUtput to true so the js needed inside that view will be generated, however if I have clientScriptRegister inside that view with events, how can I avoid to generate the scriptRegistered twice or multiple depending on the ajaxRequest? I have tried Yii::app()->clientScript->isSCriptRegistered('scriptId') to check if the script is already registered but it seems that if you used ajaxRequest, the result is always false because it will only be true after the render is finished.
Controller code
if (Yii::app()->request->isAjaxRequest)
{
$this->renderPartial('view',array('model'=>$model),false,true);
}
View Code
if (!Yii::app()->clientScript->isScriptregistered("view-script"))
Yii::app()->clientScript->registerScript("view-script","
$('.link').live('click',function(){
alert('test');
})
");
If I request for the controller for the first time, it works perfectly (alert 1 time) but if I request again for that same controller without refreshing my page and just using ajaxRequest, the alert will output twice if you click it (because it keeps on generating eventhough you already registered it once)
This is the same if you have CActiveForm inside the view with jquery functionality.. the corescript yiiactiveform will be called everytime you renderPartial.
To avoid including core scripts twice
If your scripts have already been included through an earlier request, use this to avoid including them again:
// For jQuery core, Yii switches between the human-readable and minified
// versions based on DEBUG status; so make sure to catch both of them
Yii::app()->clientScript->scriptMap['jquery.js'] = false;
Yii::app()->clientScript->scriptMap['jquery.min.js'] = false;
If you have views that are being rendered both independently and as HTML fragments to be included with AJAX, you can wrap this inside if (Yii::app()->request->isAjaxRequest) to cover all bases.
To avoid including jQuery scripts twice (JS solution)
There's also the possibility of preventing scripts from being included twice on the client side. This is not directly supported and slightly more cumbersome, but in practice it works fine and it does not require you to know on the server side what's going on at the client side (i.e. which scripts have been already included).
The idea is to get the HTML from the server and simply strip out the <script> tags with regular expression replace. The important point is you can detect if jQuery core scripts and plugins have already been loaded (because they create $ or properties on it) and do this conditionally:
function stripExistingScripts(html) {
var map = {
"jquery.js": "$",
"jquery.min.js": "$",
"jquery-ui.min.js": "$.ui",
"jquery.yiiactiveform.js": "$.fn.yiiactiveform",
"jquery.yiigridview.js": "$.fn.yiiGridView",
"jquery.ba-bbq.js": "$.bbq"
};
for (var scriptName in map) {
var target = map[scriptName];
if (isDefined(target)) {
var regexp = new RegExp('<script.*src=".*' +
scriptName.replace('.', '\\.') +
'".*</script>', 'i');
html = html.replace(regexp, '');
}
}
return html;
}
There's a map of filenames and objects that will have been defined if the corresponding script has already been included; pass your incoming HTML through this function and it will check for and remove <script> tags that correspond to previously loaded scripts.
The helper function isDefined is this:
function isDefined(path) {
var target = window;
var parts = path.split('.');
while(parts.length) {
var branch = parts.shift();
if (typeof target[branch] === 'undefined') {
return false;
}
target = target[branch];
}
return true;
}
To avoid attaching event handlers twice
You can simply use a Javascript object to remember if you have already attached the handler; if yes, do not attach it again. For example (view code):
Yii::app()->clientScript->registerScript("view-script","
window.myCustomState = window.myCustomState || {}; // initialize if not exists
if (!window.myCustomState.liveClickHandlerAttached) {
window.myCustomState.liveClickHandlerAttached = true;
$('.link').live('click',function(){
alert('test');
})
}
");
The cleanest way is to override beforeAction(), to avoid any duplicated core script:
class Controller extends CController {
protected function beforeAction($action) {
if( Yii::app()->request->isAjaxRequest ) {
Yii::app()->clientScript->scriptMap['jquery.js'] = false;
Yii::app()->clientScript->scriptMap['jquery-2.0.0.js'] = false;
Yii::app()->clientScript->scriptMap['anything.js'] = false;
}
return parent::beforeAction($action);
}
}
Note that you have to put the exact js file name, without the path.
To avoid including script files twice, try this extension: http://www.yiiframework.com/extension/nlsclientscript/
To avoid attaching event handlers twice, see Jons answer: https://stackoverflow.com/a/10188538/729324