SilverStripe 3: function in controller called within a loop in the template - silverstripe

I've written a simple custom function to take care of more than (>)
This works fine and returns 1 or 0.
It stops working when I pass $Pos to the function when it's called withing a loop.
SilverStripe Version 3
Controller
function MoreThen($pos, $value) {
if($pos > $value) {
return TRUE;
} else {
return FALSE;
}
}
Template.ss
<% loop GalleryObjects %>
<% if Top.MoreThen($Pos,2) %>
$Pos
<% end_if %>

AFAIK it is not possible to use variables as arguments of function calls, only concrete values. Depending on what you want to do, you might want to look at using GalleryObjects.limit() in your template, or write a specific getter that would return only GalleryObjects with an offset greater than 2. Hope this helps

Related

SilverStripe How to get Children from multiple page types (back-end and output)

I didn't know how to write the question using just words so here's a nifty little ascii diagram of what I got going on for your reference before I begin explaining...
+-----+
Department Department |Shows |
+ + +--+--+
| | ^
+->ShowAssignment +>ShowAssignment |
+ |
+------------------------+ |
| | | |
v v v |
AssignShow AssignShow AssignShow |
+ |
| +------+ |
+-->Content| |
| | |
+-->Content+--------------------------------------+
| |
+-->Content|
+------+
BTW - moving forward lets pretend that we're trying to get the first AssignShow with it's 3 child articles.
Alrighty, so now you've got a visual of what's up let's get to it.
In every Department there is a ShowAssignment page and under it every child page is an AssignShow page that can have Content pages relating to the work a particular department is doing on a show.
In the Shows pages there is a section which finds all the departments that have been assigned to it and lists it out on the page. I have been able to get as far as retrieving all the assigned departments but cannot seem to be able to get down to getting the all the content of what each department is doing for that show...
More visuals :) I have the departments in big blue and where 'Static fo da mo' is I need the title of those articles and a link to its page
Here is the code I currently have :
Shows.php
# Get Departments assigned to this show
public function getAssignedDepartments(){
$result = new ArrayList();
# use this shows ID to find out what shows have been selected by departments
$assignedShowID = AssignShow::get()->filter('ShowsID', $this->ID);
if(count($assignedShowID) > 0){
foreach($assignedShowID as $dept){
$department = Department::get()->byID($dept->DepartmentID);
$result->add(new ArrayData(array(
'DepartmentTitle' => $department->Title
)
));
# This here is where i'm super stuck... I've managed to drill down
# this far but dont know how to get those darn kids!
$x = $department
->Children()
->find('ClassName', 'ShowAssignments')
->Children();
}
# Title (on the first echo) returns the
# show title so I know it's targeting correctly...
foreach ($x as $key) {
echo $key->Title . '<br>';
echo $key->Children()->Title . '<br>';
}
return $result;
}
else
return null;
}
So all in all my latest attempt of getting the children got me as far as getting the Title of the AssignShow page but using something like that $key->Children() doesn't let me go 1 step deeper... What do I need to do?
Edit, new info
Righty-o, so I've managed to get the children and able to access their info from the help given by bummzack
Update
If you just need to traverse the hierarchy in code, you should be aware that Children is a List, so something like $this->Children()->Title won't work. You'll need something like:
$children2LevelsBelow = array();
foreach ($this->Children() as $child) {
// Go one level deeper…
foreach ($child->Children() as $subChild) {
$children2LevelsBelow[] = $subChild;
}
}
I think this is the key part that was missing from the code posted with your question.
The code I have at the moment is like this (still in development so its a bit incomplete, but the answer above has assisted me in getting closer)
Show.php
# Get Departments assigned to this show
public function getAssignedDepartments(){
$result = new ArrayList();
# use this shows ID to find out what shows have been selected in departments
$assignedShowID = AssignShow::get()->filter('ShowsID', $this->ID);
if(count($assignedShowID) > 0){
foreach($assignedShowID as $dept){
$department = Department::get()->byID($dept->DepartmentID);
$result->add(new ArrayData(array(
'DepartmentTitle' => $department->Title,
'Link' => '/film/departments/' . $department->URLSegment
)
));
# this gets me what I need... ALMOST -_-
foreach($dept->Children() as $x) {
echo '<br>' . $x->Title . '<br>'; # get AssignShow child Title
echo $x->Content; # gets AssignShow child content (not that i need it)
}
}
return $result;
}
else
return null;
}
What I don't understand is how do I output a list of content under every department...
So for every grid-listing in the HTML below it'll need 1 or more <li>'s to go with it before the next block runs...
Here is a snippet of the HTML / template I'm using this for
Shows.ss
<% loop AssignedDepartments %>
<div class="grid-listing">
<h2>$DepartmentTitle</h2>
<ul>
<%-- How do I loop in a loop to get a list of ALL li --%>
<%-- before moving onto the next department in the main loop? --%>
<li>› $ContentTitle</li>
<%-- end_loop --%>
</ul>
</div><!-- . grid-listing -->
<% end_loop %>
I guess you have a relation from Shows to Department, maybe has_many or many_many?
So you should be able to do something like this in your Shows template:
<% loop $Departements %>
<div class="departement">
<h1>$Title</h1>
<% loop $Children %>
<div class="show-assignment">
<h2>$Title</h2>
<% loop $Children %>
<div class="assign-show">
<h3>$Title</h3>
<% loop $Children %>
<div class="content">
<h4>$Title</h4>
</div>
<% end_loop %>
</div>
<% end_loop %>
</div>
<% end_loop %>
</div>
<% end_loop %>
While it looks mesmerizing, a template like this is rather ugly and doesn't play well if you ever plan to change the hierarchy…
Instead, you could just have a special method in your Page class that renders it's children recursively.
Eg. create a method like this in your Page class:
public function RecursiveChildren(){
return $this->renderWith(array('RC' . $this->ClassName, 'RCPage'));
}
This simply renders the current page with an RC<ClassName>, or RCPage template, depending on what is available. The minimal required template would be RCPage.ss and could look like this.
<div class="$ClassName">
<h1>$Title</h1>
<% if $Children %><% loop $Children %>
$RecursiveChildren
<% end_loop %><% end_if %>
</div>
What you can then do, is replace the complex template above with something like this:
<% loop $Departements %>
$RecursiveChildren
<% end_loop %>
And it will create pretty much the same output as the complex template above.
To design different templates for each page type, you could go ahead and create: RCDepartment.ss, RCShowAssignment.ss etc. each responsible for rendering the fragment of said page-type.
Update
If you just need to traverse the hierarchy in code, you should be aware that Children is a List, so something like $this->Children()->Title won't work. You'll need something like:
$children2LevelsBelow = array();
foreach ($this->Children() as $child) {
// Go one level deeper…
foreach ($child->Children() as $subChild) {
$children2LevelsBelow[] = $subChild;
}
}
I think this is the key part that was missing from the code posted with your question.
I done did it! It was the querying that was a bit complicated and I was going to resort to using SQL but managed to stick with SilverStripes ORM. Additionally I was trying to get the data within the same function and using the same $result->add() which is probably why I found it so difficult...
This is the order of how it works.
Shows.php
We get the departments that are assigned to the show we're viewing. We put in a return value for the ID of the department, we'll be using that later
# Get Departments assigned to this show
public function getAssignedDepartments(){
$result = ArrayList::create();
# use this shows ID to find out what shows have been selected in departments
$assignedShowID = AssignShow::get()->filter('ShowsID', $this->ID);
if(count($assignedShowID) > 0){
foreach($assignedShowID as $dept){
$department = Department::get()->byID($dept->DepartmentID);
$result->add(ArrayData::create(array(
'Title' => $department->Title,
'Link' => '/film/departments/' . $department->URLSegment,
'DepartmentID' => $dept->DepartmentID
)
));
}
return $result;
}
else
return null;
}
Shows.ss
We use the above to get the departments assigned to the show and loop through them. While looping we need a second loop to get the children of that department, we need it to loop through all the children before moving onto the next department loop. To ensure we're getting the right content we pass in the DepartmentID to use in the getDepartmentContent function
<% loop AssignedDepartments %>
<div class="grid-listing">
<h2>$Title</h2>
<ul>
<% loop $Up.getDepartmentContent($DepartmentID) %>
<li>› $Title</li>
<% end_loop %>
</ul>
</div><!-- . grid-listing -->
<% end_loop %>
Shows.php
Now we get the children, this was the part I had quite a lot of trouble with. We use the DepartmentID we passed in to help us filter relevant content, otherwise it'll get all the content assigned to the show and output it for every department, which isn't accurate. We also build the URL using the URLSegments
public function getDepartmentContent($DepartmentID){
# filter result to get the AssignShow which matches this show
# and the department id supplied param
$assignedShowID = AssignShow::get()->filter(array(
'ShowsID' => $this->ID,
'DepartmentID' => $DepartmentID
)
);
$result = ArrayList::create();
foreach($assignedShowID as $key){
foreach($key->Children() as $children){
# AssignShow
$assignShow = AssignShow::get()->byID($children->ParentID);
# ShowAssignment
$showAssignment = ShowAssignments::get()->byID($assignShow->ParentID);
# Department
$department = Department::get()->byID($showAssignment->ParentID);
# full url path
$link = $department->URLSegment . '/'
. $showAssignment->URLSegment . '/'
. $assignShow->URLSegment . '/'
. $children->URLSegment;
$result->add(ArrayData::create(array(
'Title' => $children->Title,
'Link' => 'film/departments/' . $link
)
));
}
}
return $result;
}
This way here provides me with exactly what I needed to retrieve in the way I needed it. It also kept everything contained in the Controller to keep it neat.

Silverstripe Multiple Userforms on one page

I am trying to create a single page that will display multiple userforms in a tabbed view. For example basic contact form, request a quote form etc.
I thought I could make a new page type and loop through the children to display the forms, but the $Form variable isn't rendering the form.
<% loop $Children %>
<div>
<h2>$Title</h2>
$Form
</div>
<% end_loop %>
Am I missing something here, or is there a different way to render a form using a its ID in a template file?
You could try the following.
Create a function in your page holder controller to get the form from a specific child (must be a UserDefinedForm page). To do this you'll need to create the controller of this child page.
public function ChildForm($pageID) {
$page = UserDefinedForm::get()->byID($pageID);
$controller = UserDefinedForm_Controller::create($page);
return $controller->Form();
}
afterwards you'll call this function in your loop and pass the current child id to it
<% loop $Children %>
<div>
<h2>$Title</h2>
$Top.ChildForm($ID)
</div>
<% end_loop %>
This should (code is untested) return the forms you want.
The problem at play here is the difference between the DataObject/Page and the Controller. Looping over $Children returns you a DataObject whereas the Form function and template variable are part of UserDefinedForm's controller.
The other answer shows one working solution however it has some hair on it:
Jumping scope to your controller to pass an ID to get your form
Additional DB query
Requires all the child pages to be of type UserDefinedForm
We can implement a more generic solution that removes some of those elements and making your code a little more maintainable.
Take the following which would be added to the Page class (not the controller):
function getInLoopForm() {
if (in_array('UserDefinedForm', $this->ClassAncestry)) {
$controllerName = $this->ClassName . '_Controller';
$controller = $controllerName::create($this);
if ($controller->hasMethod('Form')) {
return $controller->Form();
}
}
return false;
}
The first part of that checks whether the current object has UserDefinedForm in its class ancestry. If it is, we then create the appropriate controller and return the form.
Your template code would look like this instead:
<% loop $Children %>
<div>
<h2>$Title</h2>
$InLoopForm
</div>
<% end_loop %>
This solution is generic for three reasons:
In our getInLoopForm function, the value "UserDefinedForm" can be replaced with any class that extends Page. It could even be brought out to a YML value if you were so inclined.
For SilverStripe, controller names for pages must match "{PageClassName}_Controller" so we can abuse that by working out the controller name dynamically. This allows for you to extend UserDefinedForm and its controller and we can still call the right function.
You only require your DataObject to access the form, you don't need your own controller.
For SS 4 there is a small code change needed:
public function getInLoopForm() {
if (in_array('SilverStripe\UserForms\Model\UserDefinedForm', $this->ClassAncestry)) {
$controller = UserDefinedFormController::create($this);
if ($controller->hasMethod('Form')) {
return $controller->Form();
}
}
return false;
}

SilverStripe enum values to menu

I'm trying to pull out enum values from a dataobject to act as a menu/filter. I have not been able to find any documentation on how to do this and my attempts have all failed.
For example I have:
class Specification extends DataObject {
private static $db = array (
'Standard' => 'Enum("BS 1400,AS 1565")'
);
}
I'm trying to do something like:
public function Standards() {
$stnds = Specification::get()->dbObject('Standard')->enumValues();
$list = ArrayList::create();
foreach ($stnds as $stnd) {
$list->push($stnd);
}
return $list;
}
I want to be able to loop the resulting values in the template, but can't access the labels - if I do:
<% loop Standards %>
$Pos
<% end_loop %>
This gives me 1 2, which suggests it is working, but cannot access the enum value labels in the template.
How do I correctly return these values so they can be looped through in the template?
When you push items into an ArrayList object, they are just stored inside a php array. The SS template parser does not deal with php arrays, therefore one solution to your issue is to wrap your item inside an ArrayData before pushing it, like below:
public function Standards(){
$stnds = Specification::get()->dbObject('Standard')->enumValues();
$list = ArrayList::create();
foreach ($stnds as $stnd) {
$list->push(new ArrayData(array('Standard' => $stnd)));
}
return $list;
}
Then, in your template:
<% loop Standards %>
<h1>$Pos $Standard</h1>
<% end_loop %>

Concrete5 - Why is my block controller set() not working?

I have a custom block which has a default view with a form in it. When that form is submitted I set a controller flag and the block is (should be) updated to display more information.
The problem is my view is treating it like I have no data/variables set
Controller.php
public $unlocked = false;
public $employer;
public $shortname = "not loaded";
public function on_page_view() { //already overridden because I'm compiling LESS
...
$this->setViewVariables();
}
function setViewVariables() {
$this->set('shortname', $this->shortname);
$this->set('is_unlocked', $this->unlocked);
...
}
public function action_accesscode_unlock() {
$this->unlocked = true;
$this->shortname = "fred";
//Have also tried calling $this->setViewVariables(); as well,
//before I realised view() and on_page_view() were called after this anyway
}
View.php
<?php if ( !$is_unlocked ) {
echo $shortname; //does correctly display the default value
?>
<form action="<?php echo $this->action('accesscode_unlock')?>" id="accessform" method="post">
...
</form>
<?php } else {
//THIS section is never displayed (always reloads form with default name)
echo $shortname;
} ?>
What am I doing wrong here so that the new variable values are never set in the view?
Edit
After replying to JohnTheFish I just realised, the LESS compilation code I use includes the following lines (used to get block path). Could this be changing the instance used for different parts of the lifecycle?
$bv = new BlockView();
$bv->setController($this);
$bv->setBlockObject($this->getBlockObject());
on_page_view runs before action_accesscode_unlock, so the logic of action_accesscode_unlock does not happen until after the variables are set.
You could try adding a call to setViewVariables to the end of action_accesscode_unlock.
(In answer to your edit, yes, it could)

How can I get the title of a form element in Drupal?

For example, in the registration form, there is "Username" and the text field for it which has the input type="text" name="name" ....
I need to know how can I get the title from the input field's name.
I'm expecting a function like:
$title = get_title_for_element('name');
Result:
assert($title == 'Username'); // is true
Is there something like this in Drupal?
Thanks.
You have the form and the form state variables available to your validation function. You should use form_set_error() to set the error.
There is no function that I am aware of which will map from the values array to the form array. But it is not dificult to work it out. Understanding the form data structure is one of the key skills you need when building drupal.
In this case the form in question is generated (in a roundabout way) by user_edit_form, you can see the data structure in there.
$form['account']['name'] is the username field. and the array key for the title is '#title' as it will be in most cases for form elements.
You can do it in two different ways as I see it. Let's create a module called mycustomvalidation.module (remember to create the mycustomvalidation.info file also).
Note: The code below has not been tested, so you might have to do some minor adjustments. This is Drupal 6.x code by the way.
1) Using hook_user()
What you need is a custom module containing your own implementation of hook_user() http://api.drupal.org/api/function/hook_user/6.
<?php
function mycustomvalidation_user($op, &$edit, &$account, $category = NULL) {
if ($op == 'validate') {
// Checking for an empty 'profile_fullname' field here, but you should adjust it to your needs.
if ($edit['profile_fullname'] != '') {
form_set_error('profile_fullname', t("Field 'Fullname' must not be empty."));
}
}
}
?>
2) Using form_alter() and a custom validation function
Personally, I would go for this option because I find it cleaner and more "correct". We're adding a custom validation function to our profile field here.
<?php
function mycustomvalidation_form_alter(&$form, $form_state, $form_id) {
// Check if we are loading 'user_register' or 'user_edit' forms.
if ($form_id == 'user_register' || $form_id == 'user_edit') {
// Add a custom validation function to the element.
$form['User information']['profile_fullname']['#element_validate'] = array('mycustomvalidation_profile_fullname_validate');
}
}
function mycustomvalidation_profile_fullname_validate($field) {
// Checking for an empty 'profile_fullname' field here, but you should adjust it to your needs.
if ($field['#value'] != '') {
form_set_error('profile_fullname', t("Field %title must not be empty.", array('%title' => $field['#title']));
}
}
?>

Resources