In Vapor, specifically in the class for a custom Leaf tag, how can you retrieve values stored in a context?
I'm trying to implement a tag that takes a string and a path, and renders a link unless the path is the current page, so, for example, #navElement("About Us", "/about") will produce a link to the site's about page on every page except the about page itself. On that page, it should display the text without a link on it.
I don't want to have to pass the current path to the tag every time I use it, so I've stored the request's path in the context, roughly like this (checks omitted):
drop.get(":page"){ request in
return try drop.view.make(thePage, ["path": request.uri.path])
}
I can use #(path) in a template and see the path I expect.
My custom tag is derived from Tag, and its run method receives the context as an argument, and I can see the stored value in there in the debugger – but how do I get at it? The get method in the Context class, which seems to do this, is internal, so I can't use it. There is a comment that says subscripts are to be done, and I assume that this will ultimately be the way to extract values from the context, but in the meantime, is there any way to retrieve them?
Just make the current path one of the arguments to your tag.
Droplet route:
drop.get(":page") { request in
return try drop.view.make(thePage, ["currentPath": request.uri.path])
}
In template:
#navElement("About Us", "/about", currentPath)
Tag:
class NavElement: Tag {
let name = "navElement"
public func run(stem: Stem, context: LeafContext, tagTemplate: TagTemplate, arguments: [Argument]) throws -> Node? {
guard
let linkText = arguments[0].value?.string,
let linkPath = arguments[1].value?.string,
let currentPath = arguments[2].value?.string
else { return nil }
if linkPath == currentPath {
return Node("We are at \(currentPath)")
} else {
return Node("Link \(linkText) to \(linkPath)")
}
}
}
Edit:
I have spoken to the developers of Vapor, and they do not intend to open up access to Context's contents publicly. However, since the queue: List<Node>() is public, you can just copy the get() function into your own extension and then you'll be able to do as you wanted.
Related
From my previous question:
How to use token between action chains, properly?
, Now I know that inside my action#1 method, I have to generate a token name and value using TokenHelper programmatically and add them to ActionContext.getContext().getParameters() before chaining to my action#2. If I do, then tokenSessionInterceptor in action#2 will consume this token and does not return invalid.token.
My searchs say if I use ActionContext.getContext().getParameters().put then I'll get following exception:
Cannot find message associated with key parameterMap.locked
So I searched for ActionContext.setParameters usage. I see Struts itself has used it in one of it's interceptors, org.apache.struts2.interceptor.ActionMappingParametersInteceptor.addParametersToContext(ActionContext, Map) as below:
#Override
protected void addParametersToContext(ActionContext ac, Map newParams) {
Map previousParams = ac.getParameters();
Map combinedParams;
if (previousParams != null) {
combinedParams = new TreeMap(previousParams);
} else {
combinedParams = new TreeMap();
}
combinedParams.putAll(newParams);
ac.setParameters(combinedParams);
}
Is it safe to do same in my own action method rather than an interceptor?! If no, what are alternatives?! Is it possible at all?! I really need to do this in my action chains because it's so hard to rewrite these actions to use redirectAction instead of chain.
I want to know how I can access the parsed model of my program. I have a validation check written in xtend which accepts a rule A as it parameter. however I want to search the entire parsed tree and make sure that any other reference to this specific instance of A follows certain specifications.
#Check
def checkActionBelongsToAssociatedRole(ActionDsc act){
var pRole = act.parentRole
var rs = new ResourceSetImpl()
//DONT KNOW IF THIS IS RIGHT
var resource = rs.getResource(URI.createURI("./model/generated/Protocol.ecore"), true)
for(r:resource.allContents.toIterable.filter(typeof(RoleDec))){
if(r.name == pRole.name){
//DO SOMETHING
}
}
}
In the generator file that I have I already get the Resource object as a parameter.
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
//Generate code
}
How can I do the same thing for my validator. Thank you in advance!
act.eResource() allows to access the resource that contains the action.
I need to log any action made by users in sfDoctrineGuard plugin. Basically I'll need to log:
module/action
date
IP from where users are accessing the application
Any plugin? Is that possible? How?
This could be probably the plugin you need, sfDoctrineGuardLoginHistoryPlugin and allows to extend the information that you save.
Check for more plugins here.
Take a look at the code of the plugin, you just need to change the following file: PluginUserLoginHistoryTable.class.php
Add in the function writeLoginHistory and createHistoryEntry the information you want:
writeLoginHistory(sfEvent $event) {
//... same code than in the plugin
//lets save module and action
if (!isset($request) )
{
$sActionName = sfContext::getInstance()->getActionName();
$sModuleName = sfContext::getInstance()->getModuleName();
}
else
{
if (isset($request["module"]))
{
$sActionName = $request["action"];
$sModuleName = $request["module"];
}
}
//get values from the http bar URL
if (!isset($sModuleName))
{
$sFullURL = sfContext::getInstance()->getRouting()->getCurrentInternalUri();
///... strip action and module name from the above URL
}
}
Remember to pass those values to createHistoryEntry function and also to update that function with more input values to be saved.
I have a page with links. These links all end in the same way. For example www.site.com/fruit/apples, www.site.com/fruit/bananas, www.site.com/fruit/oranges, etc. I want all these links to call the same Dart app and have the app do some processing and then redirect you wherever you need to go (the bananas page vs. the oranges page). This way, I avoid having an actual HTML file for every single fruit. I can instead have a single landing template that gets populated with variable fruit data.
The part I'm hung up on is passing the url into the Dart app so it can do the handling. I understand main() cannot receive arguments, so what's another way?
You can use the route package to handle the URL's for you.
For example:
import 'package:route/client.dart';
final fruitUrl = new UrlPattern(r'/fruit/(\w+)');
main() {
var router = new Router()
..addHandler(fruitUrl, showFruit)
..listen();
}
void showFruit(String path) {
var fruit = fruitUrl.parse(req.path)[0];
// Display the page according to the fruit type
}
If you don't need to handle actual routes, and you just want to handle any query parameters passed of the form ?fruit=apple you don't have to use the routes package and can instead manually parse the URL:
Map params = {};
// If arguments were provided, decode them into params map
if(window.location.search.length > 1) {
// Break all arguments into form: fruit=apple
List<String> queryPairs = window.location.search.substring(1).split('&');
for(String queryPair in queryPairs) {
// Add arguments into params map: key=fruit, value=apple
List<String> queryPairList = queryPair.split('=');
params[queryPairList[0]] = queryPairList[1];
}
}
// Handle the proper action based on the fruit
switch(params['fruit']) {
case 'apple':
// ...
break;
// ...
case 'orange':
// ...
break;
}
I want to give admin the option to change the URL identifier of MyCustomModule from backend.
E.g.: www.mydomain.com/identifier
What I did is the following:
In etc/system.xml
<identifier translate="label">
<label>SELF URL Identifier</label>
<frontend_type>text</frontend_type>
**<backend_model>press/config_identifier</backend_model>** // edited after answer
<sort_order>1</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<comment>(eg: domain.com/identifier)</comment>
</identifier>
In helper/data.php
public function getUrl($identifier = null)
{
if (is_null($identifier)) {
$url = Mage::getUrl('').self::getListIdentifier();
} else {
//$url = Mage::getUrl(self::getListIdentifier()).$identifier;
**$url = Mage::getUrl(self::getListIdentifier(), array('identifier' => $identifier,'_use_rewrites'=>true)); //edited
}**
return $url;
}
after that i created a model file identifier.php :
class FME_Press_Model_Config_Identifier extends Mage_Core_Model_Config_Data
{
protected function _afterSave()
{
if ($this->isValueChanged()) {
$path = $this->getValue();
// for each $store and $id combination...
Mage::getModel('core/url_rewrite')
->loadByIdPath('press/'.$store.'/'.$identifier)
->setRequestPath($path.'/'.$identifier)
->save();
}
}
}
in config.xml i wrote this:
<events>
<controller_front_init_routers>
<observers>
<press>
<type>singleton</type>
<class>FME_Pres_Controller_Router</class>
<method>initControllerRouters</method>
</press>
</observers>
</controller_front_init_routers>
</events>
and also this is present in my file, m not sure whether it is relevant :
<adminhtml>
<args>
<modules>
<FME_Press_Override before="Mage_Adminhtml">FME_Press_Override_Admin</FME_Press_Override>
</modules>
</args>
</adminhtml>
NOTE: I had been told to make some changes in Controller/Router.php but I don't know what changes to make.
If you want I can add that code also?
Now, what else should I do?
I feel changing the application's router is entirely the wrong approach to take. It is messy and can be easily broken if another module overrode it for a similar purpose. The clean way is with URL rewrites.
You want it to be alterable so you cannot use a fixed XML based rewrite. Instead let's look at the built in rewrite system.
First in your module's etc/config.xml file set up a normal controller.
<frontend>
<routers>
<MyCustomModule>
<use>standard</use>
<args>
<module>Example_MyCustomModule</module>
<frontName>customlist</frontName>
</args>
</MyCustomModule>
</routers>
</frontend>
Here the front name used is customlist, that will always work and shouldn't conflict with any other front name, the rewritten name shall be in addition to this. Now whenever you generate an URL (perhaps in a helper function) you do so to this apparently fixed front name.
$url = Mage::getUrl('customlist', array(
'id' => $id, // 'id' will get used in the "target path" later
'_use_rewrites' => true
));
Note that the variable identifier ($id) is passed to the getUrl function rather than simply appending to it's result. If the function returns an URL with a query (&) or fragment (#) the identifier could have been appended to the wrong part.
The next step is to create rewrite records for every possible combination of identifier and store. You probably have a finite number of lists so this is possible, perhaps identifiers are particular to stores so only need to be defined once each. Either loop through all your lists in an installer script or have each list create rewrites when it is saved.
$path = Mage::getStoreConfig('custom/config/identifier', $storeId);
// Change 'custom/config/identifier' to match the path used in system.xml
$rewrite = Mage::getModel('core/url_rewrite')
->loadByIdPath('customlist/'.$store.'/'.$id);
if ($rewrite->getId()) {
// A rewrite already exists, you might want to skip creating another
continue;
}
Mage::getModel('core/url_rewrite')
->setStoreId($storeId)
->setIsSystem(true) // set to false to allow admin to edit directly
->setOptions('RP') // Redirect Permanent 301
->setIdPath('customlist/'$storeId.'/'.$id) // should never change
->setTargetPath('customlist/index/index/id/'.$id) // what gets used
->setRequestPath($path.'/'.$id) // the path used in the browser
->save();
So now if the admin sets the URL path to be "foo/bar" and requests the page "www.mydomain.com/foo/bar/3" it will be rewritten to "customlist/index/index/id/3" and the method Example_MyCustomModule_IndexController::indexAction() will be called. The file containing that will of course be app/code/local/Example/MyCustomModule/controllers/IndexController.php and the 3 value is retrieved there:
public function indexAction()
{
$id = $this->getRequest()->getParam('id'); // 'id' was specified in getUrl()
// use $id here...
}
It should work by now but what if a list is removed? The rewrites need to be updated for each store. Models have a _beforeDelete method, override it for your list objects.
protected function _beforeDelete()
{
Mage::getModel('core/url_rewrite')
->loadByIdPath('customlist/'.$storeId.'/'.$this->getId())
->delete();
return parent::_beforeDelete();
}
Similarly they need to be updated to match changes in configuration.
etc/system.xml
<identifier translate="label">
<label>SELF URL Identifier</label>
<frontend_type>text</frontend_type>
<backend_model>myCustomModule/config_identifier</backend_model>
...
</identifier>
Model/Config/Identifier.php
class Example_MyCustomModule_Model_Config_Identifier
extends Mage_Core_Model_Config_Data
{
protected function _afterSave()
{
if ($this->isValueChanged()) {
$path = $this->getValue();
// for each $store and $id combination...
Mage::getModel('core/url_rewrite')
->loadByIdPath('customlist/'.$store.'/'.$id)
->setRequestPath($path.'/'.$id)
->save();
}
}
}