Simple form for Model Admin in Silverstripe - php

By default, Model Admin is used to manage a model/s, and if the model is skipped, the result is an error.
/* private static $managed_models = array(
'OneModel'
); */
I want to display a simple form (Textfield for a password and an action button) first, then if the password is correct, it should go the to a the gridfield.
I tried to change the getCMSfields inside the model, but the field is visible only if i click on one of the records from the gridfield:
public function getCMSfields(){
$fields = FieldList::create(TabSet::create('Root', $login = Tab::create('Authorise',
TextField::create('Password')
)));
return $fields;
}
Edit:
This secondary password it's the key to decrypt the data for that DataObject, is not a regular login, so it's an additional security method to keep safe some sensitive data.

I figured out, for those in similar situation. Instead of using ModelAdmin, we can use LeftAndMain. so the code will be:
class Applications extends LeftAndMain {
static $url_segment = 'applications';
static $menu_title = 'Applications';
static $url_rule = '$Action/$ID';
public function init(){
parent::init();
}
private static $allowed_actions = array(
'login'
);
public function getEditForm($id = null, $fields = null) {
$fields = new FieldList(
TextField::create('Password', ' Password')
);
$actions = new FieldList(new FormAction('applicationPassword'));
return new Form($this, "EditForm", $fields, $actions);
}
public function applicationPassword($data, Form $form){
$pass = $data['Password'];
$form->sessionMessage('Password submited for testing : '.$pass, 'success');
return $this->redirect('login');
}
public function login(){
return 'success';
}
}
One more need would be, after validation, in the nest step to show the regular gridfield with the model records, but when i succed, i will return with an answer as well.

Related

check with voters single field in symfony

I would like to know how to implement a check for a field inside voters of an entity.
I have for example my entity Post where I want that a user not admin can't edit title field. Only admin can edit this field.
So I have created my voters but I don't know how to create this check because inside $post there is the old post entity and I don't know how to implement the check for title field
This is my easy voters file
class PostVoter extends Voter
{
const VIEW = 'view';
const EDIT = 'edit';
private $decisionManager;
public function __construct(AccessDecisionManagerInterface $decisionManager)
{
$this->decisionManager = $decisionManager;
}
protected function supports($attribute, $subject)
{
if (!in_array($attribute, array(self::VIEW, self::EDIT))) {
return false;
}
if (!$subject instanceof Post) {
return false;
}
return true;
}
protected function voteOnAttribute(
$attribute,
$subject,
TokenInterface $token
) {
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
if ($this->decisionManager->decide($token, array('ROLE_SUPER_ADMIN'))) {
return true;
}
/** #var Post $post */
$post = $subject;
switch ($attribute) {
case self::VIEW:
return $this->canView($post, $user);
case self::EDIT:
return $this->canEdit($post, $user);
}
throw new \LogicException('This code should not be reached!');
}
private function canView(Post $post, User $user)
{
if ($this->canEdit($post, $user)) {
return true;
}
return true;
}
private function canEdit(Post $post, User $user)
{
return $user === $post->getUser();
}
}
I would like to implement inside canEdit a check for the title field.
I have tried to print $post but there is only old value not some information for new value.
Couple of possible approaches.
The one I would use is to add a 'edit_title' permission to the voter then adjust my form to make the title read only if the edit_title permission was denied. This not only eliminates the need to check for a changed title but also makes things a bit friendlier for the users. One might imagine them being a bit frustrated with a form that allows them to change the title but then the app rejects the change.
If you really wanted to detect a title change then you could adjust the setTitle method in your post entity. Something like:
class Post {
private $titleWasChanged = false;
public function setTitle($title) {
if ($title !== $this->title) $this->titleWasChanged = true;
$this->title = $title;
And then of course check $titleWasChanged from the voter.
If you really wanted to go all out, the Doctrine entity manager actually has some change checking capability. You could probably access it via the voter but that would probably be overkill. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/change-tracking-policies.html

Upload field with custom form requiring a custom implementation of Field()

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);
}
}

getting validate method for newly created model with foreign key to pass

I'm trying to allow a user to create 2 models in one form. Post has a one to one relationship with Story.
My code looks something like this :
public function actionCreate()
{
post = new post();
story = new story();
if (isset($_POST['post']))
{
$post->attributes = $_POST['post'];
$story->attributes = $_POST['story'];
// force the views to show errors for both $post and $story
$post->validate();
// this one will always fail because the required foreign key field is not set until the post is saved.
$story->validate();
if ($post->save())
{
$story->post_id = $post->id;
$story->save();
}
}
}
I need to call validate so that the view shows errors on all fields, however since the model is just being created, post doesn't have an id yet so I can't assign it to story. This means validate for story will always fail.
Is there a way I can validate the model when it's still new without throwing away the required rule for the foreign key.
you can do it with a "scenario" in which indicate when to use it and what fields does .. for example ..
<?php
class User extends Model
{
public $name;
public $email;
public $password;
public function rules(){
return [
[['name','email','password'],'required'],
['email','email'],
[['name', 'email', 'password'], 'required', 'on' => 'register'],
];
}
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['login'] = ['name','password'];//Scenario Values Only Accepted
return $scenarios;
}
}
?>
<?php
...
class UserController extends Controller
{
..
// APPLY SCENARIOS
// scenario is set as a property
............
public function actionLogin(){
$model = new User;
$model->scenario = 'login';
.............
}
// scenario is set through configuration
public function actionRegister(){
$model = new User(['scenario' => 'register']);
..............
}
}
?>
In this sample you ca use two scenario 'login' validate two field 'register' validate three..
see this doc for moore sample and thisi from Yii

Refactoring code in controller with Symfony2

I have this code in two methods (create and update). Each time I need to update or create a new user I need to encode the user password with the salt.
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($entity);
$password = $encoder->encodePassword($entity->getPassword(), $entity->getSalt());
$entity->setPassword($password);
To avoid code duplication what should I do?
Create a new method in controller getEncondedPassword($entity) : return $encodedPassword
Add this logic to the Form using DI injecting the $encoder as required field
Add this logic to model, and pass the $encoder in the constructor of the entity object.
Thank you!
If your create and edit are fairly simple and pretty much the same, you can combine it to one function which actually generates and validates the form.
Some code:
class ProductController extends Controller
{
/**
* #Route("/create", name="_product_create")
*/
public function createAction()
{
$product = new Product();
return $this->productForm($product, $this->getRequest(), 'create');
}
/**
* #Route("/edit/{product_id}", name="_product_edit_id")
*/
public function editIdAction($product_id)
{
$entity_manager = $this->getDoctrine()->getEntityManager();
$product_repository = $entity_manager->getRepository('VendorBundle:Product');
$product = $product_repository->findOneBy(
array('id' => $product_id)
);
return $this->productForm($product, $this->getRequest(), 'editId');
}
protected function productForm(Product $product, Request $request, $twig_name)
{
$form = $this->createForm(new ProductType(), $product);
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
// Do whatever we want before persisting/flushing
return $this->redirect($redirect_url);
}
}
$twig_params = array(
);
return $this->render(
'VendorBundle:Product:' . $twig_name . '.html.twig', $twig_params
);
}
}
this will render create.html.twig and editId.html.twig depending on the route.
if $product->getId() === null we are creating a new entity, else we are editing.
I think that the correct option is the model/entity approach.
So, I leave here the my solution:
public function hashPassword($container)
{
$factory = $container->get('security.encoder_factory');
$encoder = $factory->getEncoder($this);
$password = $encoder->encodePassword($this->getPassword(), $this->getSalt());
return $password;
}
In the controller:
//hash user password
$userEntity->setPassword($userEntity->hashPassword($this->container));
Right now I have improved(I at least think...) the answer to this question.
I have created an class that will receive the $encoderFactory form the DI
#services.yml
parameters:
password_encoder.class: Beubi\SignatureBundle\Handler\PasswordEncoder
services:
password_encoder:
class: %password_encoder.class%
arguments: [#security.encoder_factory]
So, I create a class that will be used in Service container:
class PasswordEncoder
{
protected $encoderFactory;
public function __construct(EncoderFactory $encoderFactory)
{
$this->encoderFactory = $encoderFactory;
}
public function encodePassword($entity){
$encoder = $this->encoderFactory->getEncoder($entity);
return $encoder->encodePassword($entity->getPassword(), $entity->getSalt());
}
}
And then in my controller:
$password = $this->get('password_encoder')->encodePassword($entity);
$entity->setPassword($password);
This way, my User object doesn't have any knowledge of $factoryEncoder or how to encode an password.
I'm expecting more comments on this question...

How can I retrieve data using a model and pass it to be used in a controller using CodeIgniter?

From a user login form i need to use their username ($this->input->post('username')) to query against the membership table in my database so that I can retrieve the users firstname and lastname, which I believe should be done via a model called from my controller. I then need to pass that data back to my controller to use the firstname and lastname to add in to my session userdata which is set in my controller, as shown below.
$data = array(
'username' => $this->input->post('username'),
'firstname' => //need to retrieve value from database,
'lastname' => //need to retrieve value from database,
'is_logged_in' => true
);
$this->session->set_userdata($data);
Thanks,
Kristan.
In your model, make a function to retrieve the first and last name of a user:
public function get_user_details($username){
// retrieve contents from db:
// the first username here is the name of the column in your table
$query = $this->db->select('firstname, lastname')
->where('username', $username)
->get('membership');
return $query->result_array();
}
In your controller, like you were doijng, grab the POST contents in your controller:
$username = $this->input->post('username');
Then, from your controller, call your model function but save the output of that model function in a variable:
$data = $this->user_model->get_user_details($username);
After that, you can do what you were trying to do:
$this->session->set_userdata($data);
Hope that helps. I wrote this from the top of my head but it should work. I also suggest you read the CI documentation through because it really is some of the best documentation I have ever seen for a framework. Good luck.
Please check CodeIgniter user guide for model. The example of Blog controller and Blog model will answer you.
class Blogmodel extends CI_Model {
var $title = '';
var $content = '';
var $date = '';
function __construct()
{
// Call the Model constructor
parent::__construct();
}
function get_last_ten_entries()
{
$query = $this->db->get('entries', 10);
return $query->result();
}
function insert_entry()
{
$this->title = $_POST['title']; // please read the below note
$this->content = $_POST['content'];
$this->date = time();
$this->db->insert('entries', $this);
}
function update_entry()
{
$this->title = $_POST['title'];
$this->content = $_POST['content'];
$this->date = time();
$this->db->update('entries', $this, array('id' => $_POST['id']));
}
}
class Blog_controller extends CI_Controller {
function blog()
{
$this->load->model('Blog');
$data['query'] = $this->Blog->get_last_ten_entries();
$this->load->view('blog', $data);
}
}
You first need to create a model.
class Membership_model extends CI_Model {
function __construct() {
parent::__construct();
}
function getUser($username) {
$query = $this->db->get_where('membership', array('username' => $username));
return $query->result_array();
}
}
then in your controller, use the membership model to retrieve data. Calling this function: $this->membership_model->getUser($this->input->post('username'));

Categories