I have views from various controller actions which are solely to be run from an iframe placed in another view.
Currently, when the iframe loads, and I go to the log in page to log in, on success the login controller (using yii2 user module) calls $this->goBack(), redirecting me to the iframe source URL (since it's the last page visited), rather than the original page containing the iframe.
Basically, I'd like to exclude specific controller actions from being set as the return URL when $this->goBack() is called. Bonus points if all actions loaded in iframes are automatically excluded from $this->goBack().
Ok, I'll have a go at this! This code is totally untested! Your problem is that the action has no way of knowing whether it's been called from an iframe or not, unless you give it one. So, the basis of my attempt at an answer is that all urls for iframes should have an additional get parameter. Lets call that caller. So each iframe should look something like
<iframe url="index.php?r=controller/action&caller=this-controller/action</iframe>
Now you can always test the request url to see if it was called from an iframe. In addition, every link within the iframe should have this parameter added to it's url.
So, now we have at least two problems. Firstly, how to automatically add caller as a get parameter, without having to re-write every url, and secondly, how to reconfigure the goBack() method so it knows the difference between the two types of request.
The first problem can be relatively easily resolved by adding another view layer in between the controller and the view you want I'll call it iframe. So in your controller action, add this;
$view = 'The name of the view you want to render';
$this->render('iframe', 'view' => $view);//Add in any other parameters you want to pass
Your iframe view file should contain something like this;
<iframe src="<?php Url::to(['however you generate the url for your iframe', 'caller' => Url::to($this->context->route)]); ?>">
<?php $this->render($view); ?>//Pass additional parameters to the view if needed
</iframe>
Now we have a way of testing a controller/action call to see if it being requested by am iframe. The caller parameter is important because it allows us to extract a string to use as the value for goBack() and other methods.
Next, we need to extend UrlManager, as all request, response, Url:to() and goBack() methods and classes ultimately use the UrlManager to complete the methods for generating urls.
So, create a new UrlManager. We'll copy most of the code from the existing UrlManager, just adding some spiciness of our own. I've stored mine in commands, but put your where you like and change the namespace accordingly.
<?php
namespace app\commands;
use Yii;
use yii\web\UrlManager;
class CustomUrlManager extends UrlManager {
public function createUrl($params){
$request = Yii::$app()->request;
$caller = $request->get('caller');
if ($caller && !$params['caller']){
$params['caller'] = $caller;
}
return parent::createUrl($params);
}
}
So now, the iframe generates a caller parameter, and every link within the iframe will also have caller appended as a parameter, as long ass you've used either Url::to() (or variants on that method) or Yii::$app->UrlManager to generate your links.
Now all we need to do is customise the goBack() method of your controller to send any goBack() requests to the original source iframe.
public function goBack($defaultUrl = null)
{
$caller = Yii::$app->request->get('caller');
if ($caller){
return Yii::$app->getResponse()->redirect($caller);
}
return Yii::$app->getResponse()->redirect(Yii::$app->getUser()->getReturnUrl($defaultUrl));
}
Finally you need to configure Yii to use your new UrlManager, in your config file;
'components' => [
'urlManager' => [
'class' => 'app/commands/CustomUrlManager'
]
]
I'd love to know if this works, it's been an interesting challenge!
Related
I am using nginx as a Reverse Proxy in front of a website and intercept download/preview requests for the files stored on the site. The download requests come from within an iframe and if the user is not authorised, I redirect them to the logout page. But this does not take the main page (outside of the iframe) to the logout page. Any idea how to go about this?
If the user is not authorised you may want to send a message from the iframe to its parent. On receiving the message you would then redirect the parent window to the logout page. An example implementation is found here.
However this becomes much harder if you are not able to modify the iframe page's source. Since you are using nginx, one solution would be script injection using the ngx_http_sub_module module. This module replaces one string in the response with another. Note that this module is not included by default, you may need to build nginx with the --with-http_sub_module parameter. See the module page for more information, including an example.
The iframe needs the line:
parent.postMessage( "redirect", "http://www.your-domain.com" );
To inject this with nginx you might try:
location / {
sub_filter '</head>' '<script language="javascript">parent.postMessage( "redirect", "http://www.your-domain.com" );</script></head>';
sub_filter_once on;
}
The parent window would need the corresponding code:
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[ eventMethod ];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
// Listen to message from child window
eventer( messageEvent, function( e ) {
// normally if the message was meant to come from your domain
// you would check e.origin to verify that it's not someone
// sending messages you don't want
if ( e.data = "redirect" ) {
window.location.replace( "your-logout-url" );
}
}, false );
A more advanced solution might include the redirect url in the message; you could then handle the iframe redirecting to different locations.
How can one send <head> contents before the controller finishes? The idea is to start loading CSS as soon as possible (don't wait for controller action).
Sample scenario:
// in the controller
sleep(5);
This gives:
blank page for 5 seconds -> display the head -> start loading CSS -> body
The flow I want to get is:
Send head -> start loading CSS -> wait for the controller -> send rest of the page (body)
The <head> is now in layout.phtml, which later includes the index controller script (index.phtml).
Maybe I could have <head> as a partial and send it somehow before the whole layout?
One approach is to create an abstract controller that all controllers extend, and in the onDispatch function render the head template and flush:
public function onDispatch(MvcEvent $e) {
$renderer = $this->getServiceLocator()->get('ViewRenderer');
$content = new ViewModel();
$content->setTemplate('path/to/head.phtml');
$content = $renderer->render($content);
echo $content;
flush();
parent::onDispatch($e);
}
Drawbacks to this approach:
You have no access to the headTitle, headMeta, headLink, headScript and other view helpers elsewhere in your application (it is possible in a controller or viewscript to add a style sheet and js plugin for just that page).
You will be unable to perform redirects as a response has already been sent
You can't gzip the content as well as flushing it
Some versions of Microsoft Internet Explorer will only start to display the page after they have received 256 bytes of output, so you may need to send extra whitespace before flushing to get those browsers to display the page.
In theory, you could use this approach to load all static content in the layout before echoing $this->content - such as logo, navigation, search bar, etc etc.
As I've stated, this breaks redirects meaning helpers and plugins such a PostRedirectGet will not work.
I have the following function in my Node.js code that renders an HTML page and passes it an javascript object called htmlParamObj
exports.getPage = function (req, res, next) {
var htmlParamObj= {
propertyOne: 'yada',
propertyTwo: 'yada yada'
};
res.render('myPage.html',htmlParamObj);
};
I can access the incoming parameter (htmlParamObj) with EJS like so: <% propertyOne %>, but I don't know how to access htmlParamObj via the document itself. I believe htmlParamOb' will be attached to the document of the html - but what field in the document can I find it in? Is it in the head, the body, the childNodes? Where?
The object passed is only used while rendering the HTML, and will not be passed to the browser in any way.
If you need that data inside the page you need to put it there.
The solution I've used when I need to pass complex data to a client side script is to place a script tag near the top of my HTML EJS file and populate that with my data. For example I might add the following to my template:
<script>
window.MY_DATA = <%= JSON.stringify(myData) %>
</script>
Notice that since JSON is a subset of javascript, I can use JSON.stringify to serialize my data into a form suitable for placement inside a script tag, and assign it to whatever variable I want.
The limitation here is that you can't send any data that can't be serialized with JSON.stringify. Not a heavy burden, but could trip you up if you want to send a function or other object.
The solution I found is to define a global attribute in my HTML like so:
<a name="team" value="<%=team._id%>"></a>
then I can access it in any script like so:
<script>
var team = document.getElementsByName('team');
</script>
This will return the correct object.
However, I don't think this is the best answer, especially given that any globally defined variable is usually a bad idea. I am hoping another answer is given to this question.
I have been researching dynamic content for MVC views and partial views but have not successfully found an architecture to fit my needs.
Basically I am required to create a landing page based on parameters pass by the URL.
For basics
http://mydns.com/myconroller/myview/?landingpage=Param1
The controller will need to find the HTML that will be used to create the view.
The view is going to be different based on the landing page.
(for the sake of the question, I am using landingpage as an example)
My goal is to be able to deploy a Landing page and based on the URL use that HTML Landing page in the view based on the landingpage parameter that is passed.
There are other views that are working currently in the controller. I am trying to add functionality to be able to add a new one time page without having to recompile.
I have searched through various ideas on how to load dynamic views but cannot seem to find a solution that fits this need based on what I have read.
I can possibly RedirectToAction but I am still in the dark on where to deploy and I am getting several problems with Razor as it is not in the shared directory and then I am stuck with deployment issues as I want to organize the landing pages differently than I am organizing the views.
Solution:
I decided to take a different approach and use the ContentResult Action in the controller. I still have the Main View and I use the HTML extensions to render the HTML pages that I have deployed in my customer's directory.
#{
Html.RenderAction("LandingPageContent", "Controller", Model);
}
Then in the controller I load the HTML directly and return the ContentResult
public ContentResult LandingPageContent(object model, FormCollection collection)
{
MySRCHelper helper = new MySRCHelper();
ContentVariables variables = helper.getContentSRC(model.EntryCode);
model.ContentSRC = variables.LandingPageSRC;
return Content(System.IO.File.ReadAllText(Server.MapPath(model.ContentSRC)));
}
I can then configure the path to the raw HTML file to be used and it will be loaded into the View. The View can then house all of the paths to load jQuery, CSS and other necessary javascript to integrate with the raw HTML and allow me to deploy the HTML files into any directory structure that I want. The configuration XML file allows me to find XML elements and use those values for any HTML that I am looking for, like a welcome and thank you page. The helper object will open the XML and find the configuration based on the parameters passed to the View.
<ContentLandingItem entrycode="1" customerID="Cutomer1">
<ContentLandingPageSRC>~/Customers/Customer1/Customer1Landing.htm</ContentLandingPageSRC>
<ContentThankyouSRC>~/Content/Default/GenericThankyou.htm</ContentThankyouSRC>
</ContentLandingItem>
<ContentLandingItem entrycode="2" customerID="Cutomer2">
<ContentLandingPageSRC>~/Customers/Customer2/Customer2Landing.htm</ContentLandingPageSRC>
<ContentThankyouSRC>~/Customers/Customer2/Customer2Thankyou.htm</ContentThankyouSRC>
</ContentLandingItem>
The view still performs its duties and works independently on it own letting the raw HTML decorate the View. The model is still intact and can be used as I wish. The FormCollection is there in case a form submit posts the values to the view and provides some things that I omitted from this question as it did not pertain to this subject.
I don't want to answer my own question and I found the pieces that helped me on another site, so I am putting what I did here in case anyone needs this functionality.
This sounds like using the you can inherit from the virtual path provider view engine and decide based on the URL parameters (or other) which view to return. Some example that you can adjust to your needs:
public class CustomViewEngine : VirtualPathProviderViewEngine
{
public MyViewEngine()
{
this.ViewLocationFormats = new string[] { "~/Views/{1}/{2}.mytheme ", "~/Views/Shared/{2}.mytheme" };
this.PartialViewLocationFormats = new string[] { "~/Views/{1}/{2}.mytheme ", "~/Views/Shared/{2}. mytheme " };
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
var physicalpath = controllerContext.HttpContext.Server.MapPath(partialPath);
return new RazorView(controllerContext, physicalpath);
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
var physicalpath = controllerContext.HttpContext.Server.MapPath(viewPath);
return new RazorView(controllerContext, physicalpath);
}
}
In there you can return a RazorView or WebFormView and set your desired path for the view to use.
i have a kohana website, and i want that, if someone modifies the current url (in the browser when he visits the website), and that specific page doesn't exist, he should be redirected to the current page (where he is now), or on the homepage, but without displaying any error.
any idea about how this can be done?
The guide gives the basic steps you need to take.
You basically replace the existing exception handler by defining a class called Kohana_Exception.
In that handler, you would check the error number and if it's a 404, then do a redirect based on the http referer.
class Kohana extends Kohana_Core
{
public static function handler(Exception $e)
{
if($e instanceof Kohana_Request_Exception)
{
Request::current()->redirect(Request::initial()->referrer());
}
}
}
This should be placed in for example application/classes/kohana.php
Note that this is the basic gist. You should expand on this and check if HTTP_Referer is set and based that the user actually came from your site.
Also note that this can cause confusion as people often don't notice they have been redirected.
Check the guide for other things you should do in the exception handler (for example, pass it on to the default handler.