Controller routing not working as expected in silverstripe 3.1 - php

I'm setting up routing to a controller and I keep getting either a 404, or the 'getting started with the silverstripe framework' page.
In routes.yaml I have:
---
Name: nzoaroutes
After: framework/routes#coreroutes
---
Director:
rules:
'view-meetings/$Action/$type': 'ViewMeeting_Controller'
My controller looks like this:
class ViewMeeting_Controller extends Controller {
public static $allowed_actions = array('HospitalMeetings');
public static $url_handlers = array(
'view-meetings/$Action/$ID' => 'HospitalMeetings'
);
public function init() {
parent::init();
if(!Member::currentUser()) {
return $this->httpError(403);
}
}
/* View a list of Hospital meetings of a specified type for this user */
public function HospitalMeetings(SS_HTTPRequest $request) {
print_r($arguments, 1);
}
}
And I've created a template (ViewMeeting.ss) that simply outputs $Content, but when I flush the site cache and visit /view-meetings/HospitalMeetings/6?flush=1
I get the default 'getting started with the Silverstripe framework' page
I know the routing in routes.yaml is working, because if I change the route there and visit the old URL I get a 404, but the request doesn't seem to fire my $Action...

You had 2 different rules in your YAML and controller ($type vs $ID). Also, I don't think you need to define the route in both YAML and the Controller.
Try this, the YAML tell SS to send everything that starts with 'view-meetings' to your Controller, then $url_handlers tell the Controller what to do with the request depending on everything after 'view-meetings' in the URL.
routes.yaml
---
Name: nzoaroutes
After: framework/routes#coreroutes
---
Director:
rules:
'view-meetings': 'ViewMeeting_Controller'
ViewMeeting_Controller.php
class ViewMeeting_Controller extends Controller {
private static $allowed_actions = array('HospitalMeetings');
public static $url_handlers = array(
'$Action/$type' => 'HospitalMeetings'
);
public function init() {
parent::init();
if(!Member::currentUser()) {
return $this->httpError(403);
}
}
public function HospitalMeetings(SS_HTTPRequest $request) {
}
}

The Silverstripe documentation on routing isn't at all clear on this point, but for $Action to be correctly interpreted you should use a double slash before it in the routes.yml file:
view-meetings//$Action/$type
According to the documentation, this sets something called the 'shift point'. Exactly what this means isn't described very well either in the documentation or in the source code which matches URLs against rules.

I'm doing some guessing here, but what if you drop the
public static $url_handlers = array(
'view-meetings/$Action/$ID' => 'HospitalMeetings'
);
part and change the Action method to:
// View a list of Hospital meetings of a specified type for this
public function HospitalMeetings(SS_HTTPRequest $request) {
// Should print 8 if url is /view-meetings/HospitalMeetings/6
print_r($request->param('type');
}

Related

How to create dynamic URLs at the base URL of a CMS?

I'm displaying user profiles on a PHP website using usernames as part of the URL that links to the given user profile.
I can achieve this through a controller, the ProfileController, but the URL will look like this thewebsite.com/profile/show_profile/ANYUSERNAMEHERE
What i want is something similar to Facebook, where the username is appended just after the base URL:
https://www.facebook.com/zuck
I tried passing a variable to the Index function (Index()) of the home page controller (IndexController), but the URL becomes thewebsite.com/index/ANYUSERNAMEHERE and the base url thewebsite.com throws an error:
Too few arguments to function IndexController::index(), 0 passed and exactly 1 expected.
The home page controller:
<?php
class IndexController extends Controller
{
public function __construct()
{
parent::__construct();
}
// IF LEFT, THE VARIABLE $profile THROWS AN ERROR AT THE BASE URL
public function index($profile)
{
/** AFTER REMOVING THE $profile VARIABLE ABOVE AND THE 'if'
* STATEMENT BELOW, THE ERROR THROWN AT THE BASE URL VANISHES AND
* THE WEBSITE GOES BACK TO IT'S NORMAL STATE. THIS CODE WAS USED
* TRYING TO RENDER THE URL thewebsite.com/ANYUSERNAMEHERE BUT IT
* ONLY WORKS WITH thewebsite.com/index/ANYUSERNAMEHERE
*/
if (isset($profile)) {
$this->View->render('profiles/show_profile', array(
'profiles' => ProfileModel::getSelectedProfile($profile))
);
} else {
$this->View->render('index/index', array(
'profiles' => ProfileModel::getAllProfiles()));
}
}
The profile controller:
<?php
class ProfileController extends Controller
{
public function __construct()
{
parent::__construct();
Auth::checkAuthentication();
}
public function index()
{
$this->View->render('profiles/index', array(
'profiles' => ProfileModel::getAllProfiles())
);
}
public function show_profile($profile)
{
if (isset($profile)) {
$this->View->render('profiles/show_profile', array(
'profiles' => ProfileModel::getSelectedProfile($profile))
);
} else {
Redirect::home();
}
}
}
I was expecting the base URL to pass the argument (the username) to the IndexController's Index($profile) function, but the webpage throws an error and the expected result is being displayed from the wrong URL: thewebsite.com/index/ANYUSERNAMEHERE
You would need to use a router based on regular expressions, like FastRoute, or Aura.Router.
For example, with FastRoute you'd define and add a route to the so-called route collector ($r) like this:
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
// The /{profile} suffix is optional
$r->addRoute('GET', '[/{profile}]', 'handler');
});
where handler is just a generic name for a customizable route handler in form of a callable. For example, if you'd additionally use the PHP-DI/Invoker library, the route handler ('handler') could look like one of the following callables (at least):
[ProfileController::class, 'show_profile']
'ProfileController::show_profile'
So the complete route definition would be like:
$r->addRoute('GET', '[/{profile}]', [ProfileController::class, 'show_profile']);
$r->addRoute('GET', '[/{profile}]', 'ProfileController::show_profile');
The placeholder name (profile) corresponds to the name of the parameter of the method ProfileController::show_profile:
class ProfileController extends Controller {
public function show_profile($profile) {
...
}
}
Even though the URL would look like you want it, e.g. thewebsite.com/zuck, I imagine that the placeholder {profile} of the above route definition would come in conflict with the fixed pattern parts defined in other route definitions, like /books in:
$r->addRoute('GET', '[/books/{bookName}]', 'handler');
So I suggest to maintain a URL of the form thewebsite.com/profiles/zuck, with the route definition:
$r->addRoute('GET', '/profiles/{profile}', 'handler');
I also suggest to read and apply the PHP Standards Recommendations in your code. Especially PSR-1, PSR-4 and PSR-12.

Empty route leeds to 404-Error - SilverStripe 3.5

Like superficial descriped in SilverStripe Docs, I'm trying to set a custom controller for my homepage.
I changed the default homepage link to 'custom-home' and added those two routes.
The second one, with the path in it works and directs me to my controller. The first (empty) one just sends me to an 404-error page.
Couldn't figure out how to fix that. Any suggestions?
routes.yml
Director:
rules:
'': 'MyHome_Controller'
'custom-home': 'MyHome_Controller
_config.php
RootURLController::set_default_homepage_link('custom-home');
MyHome_Controller.php
<?php
class MyHome_Controller extends Page_Controller {
private static $allowed_actions = [];
private static $url_handlers = [];
public function init() {
parent::init();
}
public function Link($action = null) {
return Director::baseURL() . 'custom-home';
}
public function index() {
$data = [
'Title' => 'Hello World',
'ClassName' => __CLASS__,
];
return $this
->customise($data)
->renderWith([__CLASS__, 'Page']);
}
}
I believe the way the empty route (RootURLController) works is that you're telling it the URLSegment of a page in the CMS that should resolve to the root URL. So I think what you need to do is go into the CMS and change the URLSegment of your CustomHomePage to 'custom-home'.

Calling an action on ModelAsController subclass

I have a custom route in my routes.yml that forwards any unknown request to BaseController
'$URLSegment/$Name/$Action/$ID': 'BaseController'
And from there the the request is routed (among other places) to my main Controller where it is handled by the index() of the controller.
However it will always use the index() so if I go to the URL test1/test2/action/5 it will still be run by index()
This is my BaseController
class BaseController extends ModelAsController {
public function getNestedController() {
$action
$params = $this->getRequest()->params();
$this->loadMain($params['URLSegment'], $params['Name'], $params['Action'], $params['ID']);
}
private function loadMain($first, $name, $action, $id) {
$main = new MainController();
$main->{$action}();
}
}
and this will call the function however the index() function has already run and set the template.
I know I could call the function from index() and return the template like that however I'm fairly sure this would bypass the security features of the allowed actions which I'm keen to maintain.
I've defined $allowed_actions in my MainController and have added:
private static $url_handlers = array(
'$URLSegment/$Name/something/$ID' => 'something'
);
but it still just calls index().
How can I maintain the same action routing of SilverStripe through the custom routed MainController
You need to define private static $allowed_actions and maybe private static $url_handlers also, the latter providing custom routing.
Basically all the things from: https://docs.silverstripe.org/en/3/developer_guides/controllers/routing/
This isn't well documented but if you are wanting to reverse the URL convention of /path/$Action/$Name/$ID to something like /path/$Name/$Action then you put:
private static $url_handlers = array(
'path//$Name/$Action' => 'handleAction'
);
The handleAction is important as it calls the parents (Controller`) handleAction and routes it correctly applying permissions and so forth.

Silverstripe: Not able to access Page fields in controller with custom route

I'm using SilverStripe 3.3.1 and have a custom route set up to handle urls with many parameters. That works.
However, the routing rule causes Page fields and functions to be inaccessible in the Page_Controller and templates. Any ideas how to fix this?
//MyPage class
class MyPage extends Page {
//Not accessible if route to controller specified in config.yml
private static $db = array(
'MyPageVar' => 'Int',
);
//Not accessible if route to controller specified in config.yml
public function getMySpecialVar() {
return $this->MyPageVar;
}
}
//MyPage_Controller class
class MyPage_Controller extends Page_Controller {
private static $allowed_actions = array(
'index',
'detailsearch',
);
private static $url_handlers = array (
'detailsearch/$Key1/$Value1/$Key2/$Value2/$Key3/$Value3/$Key4/$Value4/$Key5/$Value5' => 'detailsearch',
);
/**
* UseMyPageVar()
*
* #return Boolean
*/
public function UseMyPageVar() {
//Empty if route to controller specified in config.yml
Debug::show($this->MyPageVar);
Debug::show($this->Title);
Debug::show($this->Content);
//Error if route to controller specified in config.yml
Debug::show($this->getMySpecialVar());
return true;
}
}
MyPage.ss
<!-- This work as expected if no route is specified. -->
<!-- But all vars are empty if route is specified in config.yml -->
<p>MyVar: $MyPageVar</p>
<p>Title: $Title</p>
<p>Content: $Content</p>
Routing rule in config.yml
Director:
rules:
'mypage': 'MyPage_Controller'
This question is also posted on the Silverstripe forum:
http://www.silverstripe.org/community/forums/general-questions/editpost/413506
It's not pretty, but for now I've solved the problem by using a private var in the Controller class to hold a reference to the page.
//MyPage_Controller class
class MyPage_Controller extends Page_Controller {
private $_page; //reference to page that's lost with custom routing
//ContentController uses route, which has been changed to
// 'MyPage_Controller' by routing rule, to initialize
// page reference. Can't find anything so reference
// not set. (set to -1)
public function init() {
parent::init();
//Initialize using default route overwritten in routing rule
// This will break if URL segment changed in CMS
$route = array_search($this->URLSegment,
Config::inst()->get('Director', 'rules'));
$link = str_replace($this->URLSegment, $route, $this->Link());
$this->_page = $this->Page($link);
}
//Use private var to access page fields
public function MyPageVar() {
Debug::show($this->_page->MyPageVar);
}
//expose $Content to templates
public function Content() {
return $this->_page->Content;
}
//Can't use Title() so expose Page Title as $PageTitle
public function PageTitle() {
return $this->_page->Title;
}
}
Three things spring to mind when I look at your code:
That "mypage" in config.yml should be the name of a public method on MyPage_Controller. As it is, SilverStripe cannot find a matching method called mypage and will default to calling index() instead.
Routes should really go in a separate routes.yml file so you can "namespace" it to be invoked before or after SilverStripe's own core routes. If you don't do this, then it may result in the weird behaviour you're experiencing.
Did you know that you can debug your routes using the ?debug_request=1 URL param? See: https://docs.silverstripe.org/en/3.3/developer_guides/debugging/url_variable_tools#general-testing

ZF2 - pass variables to zend build-in helper

I would like to pass variable (that would be service menager) to a build-in helper of zend. Is it possible? To be more clearly:
There is a zend helper called Url, which constructs url's
In this helper I would like to get some data from database, so I need to pass there connection or model (doesn't matter really)
Depends on data get in point 2. I would like to construct my custom link
Well, the thing looks like this: I'm trying to make own custom routing. So in database I have controller, action and it's alias. For example:
Home\Controller\Home | index | myalias
Routing works fine, that means that if I type url:
example.com/myalias
Then Zend will open Home controller and index action. But on whole page I have url's made by Zend build-in Url helper, which looks like this:
$this->url('home', array('action' => 'index'));
So link looks:
example.com/home/index
I would like to change link to
example.com/myalias
without changing links generated by Url helper on whole page. So before helper return url, should check if that url have alias, and if so then should return that alias exept regular url.
In Module.php of the module where you have he helper class file, write the following -
//use statements
class Module {
//public function getAutoloaderConfig() { [...] }
//public function getConfig() { [...] }
public function getViewHelperConfig() {
return array(
'factories' => array(
'Url' => function ($sm) {
$locator = $sm->getServiceLocator();
$viewHelper = new View\Helper\Url;
//passing ServiceLocator to Url.php
$viewHelper->setServiceLocator($locator); //service locator is passed.
return $viewHelper;
},
),
);
}
}
Now in the Url.php, we need a function setServiceLocator() and getServiceLocator().
//use statements
class Url {
public $serviceLocator;
public function getServiceLocator() {
return $this->serviceLocator;
}
public function setServiceLocator($serviceLocator) {
if($this->serviceLocator == null)
$this->serviceLocator = $serviceLocator;
return;
}
}
I hope it helps.

Categories