I've been stuck on this for a while now and I can't see where I'm going wrong.
I've made a class that extends the UserDefinedForm so I can have a page that also has a UserDefinedForm on it. The form loads on the page, but it isn't as easy as just saying $Form on the template file, instead I have to make and call the following function:
public function getUserForm() {
$page = UserDefinedForm::get()->byID($this->ID);
$controller = UserDefinedFormController::create($page);
return $controller->Form();
}
So when I call that function on the template it displays the form, however, none of my field rules are applied and when submitting the form it takes me to a blank page with "/finished" appended to the URL: "https://example.com/finished".
Can someone please help me out here, would be much appreciated.
I will put my code down below.
Class extending the UserDefinedForm:
<?php
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\FileHandleField;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Assets\Image;
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor;
use SilverStripe\Forms\DropdownField;
use SilverStripe\UserForms\Model\UserDefinedForm;
use SilverStripe\UserForms\Control\UserDefinedFormController;
use SilverStripe\UserForms\Model\EditableCustomRule;
class Package extends UserDefinedForm {
private static $db = [
'Date' => 'Text',
'Location' => 'Text',
'Availability' => 'Enum(array("Available","Hidden","Sold Out"))',
'Extras' => 'HTMLText',
'NeedTo' => 'HTMLText',
'Price' => 'Text'
];
private static $has_one =[
'Photo' => Image::class,
];
private static $has_many =[
'FileAttachments' => 'PackageFile'
];
private static $many_many = [
'SacredTexts' => 'ImportantText'
];
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Main', Injector::inst()->create(FileHandleField::class, 'Photo'));
$fields->addFieldToTab('Root.Main', new TextField('Date', 'Date'));
$fields->addFieldToTab('Root.Main', new TextField('Location', 'Location'));
$fields->addFieldToTab('Root.Main', new TextField('Price', 'Price'));
$fields->addFieldToTab('Root.Main', new HTMLEditorField('Extras', 'Extras'));
$fields->addFieldToTab('Root.Main', new HTMLEditorField('NeedTo', 'What you need to know'));
$fields->addFieldToTab('Root.Main', new DropdownField('Availability', 'Availability', singleton('Package')->dbObject('Availability')->enumValues()));
$fields->addFieldToTab('Root.Main', new GridField('SacredTexts', 'Important Texts', $this->SacredTexts(), GridFieldConfig_RecordEditor::create()),'Date');
$fields->addFieldToTab('Root.Main', new GridField('FileAttachments', 'File Attachments', $this->FileAttachments(), GridFieldConfig_RecordEditor::create()),'Content');
return $fields;
}
/* Look up SS4 docs on SS Sitetree URL parse function and what needs to be namespaced */
function onBeforeWrite () {
parent::onBeforeWrite ();
if($this->Name){
$this->Slug = str_replace(' ','-', strtolower($this->Name));;
}
}
public function ShortContent( $word_limit = 20 ) {
$NoHTML = htmlspecialchars_decode(strip_tags($this->NewsText),ENT_QUOTES);
$words = explode( ' ', $NoHTML );
return implode( ' ', array_slice( $words, 0, $word_limit ) );
}
public function ParentEvent(){
return $this->Parent()->URLSegment;
}
public function getUserForm() {
$page = UserDefinedForm::get()->byID($this->ID);
$controller = UserDefinedFormController::create($page);
return $controller->Form();
}
public function hasUserForm() {
if (count($this->getUserForm()->Fields()) > 1) {
return $this->getUserForm();
}
}
}
I figured it out.
Turns out the class controller I was extending to override wasn't working, instead it was referring to the UserDefinedFormController. I fixed it by copying the following function from the UserDefinedForm class:
public function getControllerName()
{
return UserDefinedFormController::class;
}
and copying it into my extended class from UserDefinedForm and renaming the return statement to the name of the class, for example:
public function getControllerName()
{
return PackageController::class;
}
Related
I am first day user of Twig, and I have some strange behaviour of engine.
I push some information to my view:
class MainController extends Controller {
public function actionIndex()
{
$template = self::$twig->loadTemplate('index.php');
$title = 'CRUD интерфейс';
$projects = MainList::showAll();
$workers = CompanyWorker::showAll();
$roles = Role::showAll();
$namesOfProjects = Project::showAll();
echo $template->render(array(
'title' => $title,
'projects' => $projects,
'workers' => $workers,
'roles' => $roles,
'namesOfProjects' => $namesOfProjects
));
}
}
In the end I have a good result but in the end of HTML file twig shows me my controller object. Why it's happened?
It's my Twig initialisation:
abstract class Controller {
public $loader;
static $twig;
function __construct()
{
$this->loader = new Twig_Loader_Filesystem('views');
//$twig = new Twig_Environment($loader, array('cache' => 'cache'));
self::$twig = new Twig_Environment($this->loader);
}
I'm were blind 8[. I really missed 'print_r' func.
I have some custom routes in my system:
---
Name: mysiteroutes
---
Director:
rules:
'create//$Action': 'CreateController'
Which has a custom controller to create a form:
class CreateController extends Page_Controller{
private static $allowed_actions = array(
'submit'
);
public function link($action = null) {
return $this->join_links('create/', $action);
}
public function index() {
$form = Form::create(
$this,
'',
FieldList::create(
TextField::create('Name', 'Name'),
$upload = new UploadField('Upload', 'Upload')
),
FieldList::create(
FormAction::create('submit', 'Submit')->setAttribute('class', 'btn btn-success')
),
RequiredFields::create('Name')
);
if($this->request->isPost()) return $form;
return $this->customise(array('Form'=>$form))->renderWith(array("Create", "Page"));
}
public function submit($data, $form = null) {
$params = $this->getRequest()->params();
var_dump($params);
}
}
When I try and upload something it calls Field() on my controller and then fails as it's not there. I can add it and it calls it correctly however I have no idea what to put in it. I've looked through the Field() function in UploadField.php however there is a lot of code there which I probably shouldn't just copy.
How should I manage the upload of the file in my custom controller or can I forward it to the core framework somehow?
UploadField expects to have a route based on the Form name, in your case ''.
If you would change the name of the form to form it would call form/field/Upload/upload. What this does is get the form, then get the field with the name Upload and call the method upload on that class.
Unfortunately, the way you are using the form (which I showed you in an earlier answer :( ) does not support this.
We could solve it like this;
CreateController
class CreateController extends Page_Controller
{
private static $allowed_actions = [
'form'
];
public function link($action = null)
{
return $this->join_links('create', $action);
}
public function index()
{
return $this->renderWith(array("Create", "Page"));
}
public function form()
{
return UploadForm::create($this, 'form', 'submit');
}
public function submit($data, $form = null)
{
$params = $this->getRequest()->params();
var_dump($params);
}
}
Form
// create an extra class for the form to keep your controller clean
class UploadForm extends Form
{
public function __construct($controller, $name, $action)
{
$fields = FieldList::create(
TextField::create('Name', 'Name'),
UploadField::create('Upload', 'Upload')
);
$actions = FieldList::create(
FormAction::create($action, 'Submit')
->setAttribute('class', 'btn btn-success')
);
$validator = RequiredFields::create('Name');
parent::__construct($controller, $name, $fields, $actions, $validator);
}
}
I am trying to render a template with my controller but does not work
it show me this error :
LogicException: The controller must return a response (Hello Bob! given). in Symfony\Component\HttpKernel\HttpKernel->handleRaw() (line 163 of core/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernel.php).
My function :
public function helloAction($name) {
$twigFilePath = drupal_get_path('module', 'acme') . '/templates/hello.html.twig';
$template = $this->twig->loadTemplate($twigFilePath);
return $template->render(array('name' => $name));
}
In Drupal 8 you either return a Response object or a render array from a controller. So you have two options:
1) Place the rendered template into a Response object:
public function helloAction($name) {
$twigFilePath = drupal_get_path('module', 'acme') . '/templates/hello.html.twig';
$template = $this->twig->loadTemplate($twigFilePath);
$markup = $template->render(array('name' => $name));
return new Response($markup);
}
2) Place the rendered template into a render array:
public function helloAction($name) {
$twigFilePath = drupal_get_path('module', 'acme') . '/templates/hello.html.twig';
$template = $this->twig->loadTemplate($twigFilePath);
$markup = $template->render(array('name' => $name));
return array(
'#markup' => $markup,
);
}
Also you can Use the second option without custom template, doing this:
public function helloAction($name) {
$markup = "<p> Without custom Template</p>";
return array(
'#markup' => $markup,
);
}
class ClientController extends ControllerBase implements ContainerInjectionInterface ,ContainerAwareInterface {
protected $twig ;
public function __construct(\Twig_Environment $twig)
{
$this->twig = $twig ;
}
public function index()
{
$twigFilePath = drupal_get_path('module', 'client') . '/templates/index.html.twig';
$template = $this->twig->loadTemplate($twigFilePath);
$user = ['user' => 'name'] ; // as example
$markup = [
'#markup' => $template->render( ['users' => $users ,'kit_form' => $output] ),
'#attached' => ['library' => ['client/index.custom']] ,
];
return $markup;
}
// this is called first then call constructor
public static function create(ContainerInterface $container)
{
return new static(
$container->get('twig') ,
);
}
}
this full example to render twig by dependency injection from controller
AR model Player:
public function scopes()
{
return array(
'proleague' => array(
'condition' => 'mode = "proleague"',
),
'main' => array(
'condition' => 'mode = "main"',
),
);
}
Using model Player:
Player::model()->
proleague()->
with('startposition')->
findAllByAttributes(... here some condition ...);
^^^ That's all ok. Scope-condition will be executed. But...
In my project I have many places where any scope for Player model doesn't specified and in this cases I need use this scope-condition as default:
'main' => array(
'condition' => 'mode = "main"',
)
If I add defaultScope() method to Player model like this
public function defaultScope()
{
return array(
'condition' => 'mode = "main"',
);
}
the next code
Player::model()->
proleague()->
with('startposition')->
findAllByAttributes(... here some condition ...);
won't run correct. I won't get mode = "proleague" condition, becouse I'll use defaultScope() with mode = "main".
Any suggestions? How can I resolve the problem?
You should just use the resetScope(true) method. It "removes" the defaultScope filter.
$model = Player::model()->resetScope(true)->proleague();
create a new Class for this.
<?php
## e.g. protected/models/
class MyCoreAR extends CActiveRecord
{
/**
* Switch off the default scope
*/
private $_defaultScopeDisabled = false; // Flag - whether defaultScope is disabled or not
public function setDefaultScopeDisabled($bool)
{
$this->_defaultScopeDisabled = $bool;
}
public function getDefaultScopeDisabled()
{
return $this->_defaultScopeDisabled;
}
public function noScope()
{
$obj = clone $this;
$obj->setDefaultScopeDisabled(true);
return $obj;
}
// see http://www.yiiframework.com/wiki/462/yii-for-beginners-2/#hh16
public function resetScope($bool = true)
{
$this->setDefaultScopeDisabled(true);
return parent::resetScope($bool);
}
public function defaultScope()
{
if(!$this->getDefaultScopeDisabled()) {
return array(
'condition' => 'mode = "main"',
);
} else {
return array();
}
}
}
In your code:
// no default scope
$model = Player::model()->noScope()->proleague();
// with default scope
$model = Player::model()->proleague();
I have the following model:
class Person
{
public $name;
function __Construct( $name )
{
$this->name = $name;
}
}
I have the following controller:
class NavigationController extends Controller
{
public function indexAction()
{
$people = array(
new Person("James"),
new Person("Bob")
);
return $this->render('FrameworkBundle:Navigation:index.html.php', $people);
}
}
How do I get access to the model array in the view. Is there a way to access the model directly or do I have to assign a property like so:?
class NavigationController extends Controller
{
public function indexAction()
{
$people = array(
new Person("James"),
new Person("Bob")
);
return $this->render('FrameworkBundle:Navigation:index.html.php', array( "model" => $people ) );
}
}
View:
<?php
foreach( $model as $person )
{
echo $person->title;
}
?>
The problem with the above will be that it can be changed by a user to
return $this->render( 'FrameworkBundle:Navigation:index.html.php', array( "marybloomingpoppin" => $people ) );
With the example view you used, you already had the correct implementation:
class NavigationController extends Controller
{
public function indexAction()
{
$people = array(
new Person("James"),
new Person("Bob")
);
return $this->render('FrameworkBundle:Navigation:index.html.php', array( "model" => $people ) );
}
}
You mentioned the concern that somebody could change the assignment in the controller, but this is something you always have if somebody changes the name of a variable only in one place and not in all. So I don't think this is an issue.