Note: version 3.1
Trying to validate the input of this fields from CMS:
expiry date must be after publish date.
I tried even a simple validation (required) but doesn't work.
public function updateCMSFields(FieldList $fields) {
$publishDatetimeField = new DatetimeField( 'PublishDate', 'Publish Date' );
$expiryDatetimeField = new DatetimeField( 'ExpiryDate', 'Expiry Date' );
$fields->addFieldToTab('Root.Options', $publishDatetimeField);
$fields->addFieldToTab('Root.Options', $expiryDatetimeField);
}
public function getCMSValidator(){
return new RequiredFields('publishDatetimeField');
}
I can manipulate the values and compare them, but i can't access them.
Any ideas, are welcome.
You should be able to use the basic validator in terms of checking a field isn't blank... but you should be using the name of the field "PublishDate", not "publishDatetimeField".
In general this is how validations are fully set in silverstripe...
class MyDataObject extends DataObject {
static $db = array(
'MyDateField' => 'SS_DateTime',
);
function getCMSValidator() {
return new MyDataObject_Validator();
}
}
class MyDataObject_Validator extends RequiredFields {
function php($data) {
$bRet = parent::php($data);
//do checking here
if (empty($data['MyDateField']))
$this->validationError('MyDateField','MyDateField cannot be empty','required');
return count($this->getErrors());
}
}
you can check for what the data...
die(var_dump($data));
and this should be a string in MYSQL format... like "2016-03-24 11:41:00"
Related
I have a DataObject class called AdminUpload that stores two variables: UploadDate (which is always going to bet set to the current date) and Total, which is an int.
The function updateUploads() is called and stores the current date and increments the Total by 1 each time its called.
<?php
class AdminUpload extends DataObject {
private static $db = array(
'UploadDate' => 'Date',
'Total' => 'int'
);
// Tell the datagrid what fields to show in the table
private static $summary_fields = array(
'ID' => 'ID',
'UploadDate' => 'Current Date',
'Total' => 'Version Number'
);
public function updateUploads(){
$uploadDate = SS_Datetime::now();
$this->UploadDate = $uploadDate;
$this->Total++;//increment the value currently stored in the database each time
$this->write();
}
}
What I want to to is, when someone uploads a new image in the admin view, then the updateCache() function is called during the onAfterWrite() process. I only want to maintain one entry in the database, though, so every time I upload an image, I want to have just one entry in the AdminUpload database table.
public function onAfterWrite(){
$updateGallery = parent::onAfterWrite();
$adminUploading = AdminUpload::get();
$adminUploading -> updateUploads();
return $updateGallery;
}
I've never tried to do a function call in SilverStripe like this--it seems simple enough but since I am not going to add a new entry to the database with each call to the updateUploads() function, that's where I'm stuck. Any tips would be helpful...
It is incorrect approach to create a whole table for just one record. If you were going to use theses two fields on a page, then adding them to that page (create a new page type) would be a better idea.
If you are talking about file uploads, then you can always query this information directly from database.
$uploadedFilesCount = File::get()->count();
$lastUploadedFileDate = File::get()->sort('CreatedDate', 'DESC')->first()->CreatedDate;
onAfterWrite is a hook and used from DataExtension. There are cases when hooks are called directly on DO and then on extensions.
Your extension code might look like this to handle 'onCreated' state:
class UploadsCounter extends DataExtension {
protected $isCreating = false;
public function onBeforeWrite() {
if (!$this->owner->isInDB()) {
$this->isCreating = true;
}
}
// called on validation or database error
public function onAfterSkippedWrite() {
$this->isCreating = false;
}
public function onAfterWrite() {
if (!$this->isCreating) return;
$this->isCreating = false;
$adminUploading = AdminUpload::get()->first();
if (!$adminUploading ) {
$adminUploading = new AdminUpload();
$adminUploading->write();
}
$adminUploading->updateUploads();
}
}
You should define UploadsCounter extension on the dataobject that you are going to count, for example:
mysite/_config/config.yml
File:
extensions:
- UploadsCounter
First off I know the developer stopped working on this extension full time, but from what I understand there are still alot of people using it. Hoping someone can help me with this (hopefully simple problem!)
I'm trying to read the data I just inserted into MongoDB using the YiiMongoDbSuite extension.
The data is successfully inserted as I can see it via the mongo console.
The problem is when I try to retrieve it. Here's the code I'm running together - it inserts it (no problem), but can't retrieve it:
<?php
$new = new Character();
$new->playerName = "Yii-Insert-Test";
$new->playerId = "123456789";
$new->playerScore = "9001";
$new->save();
$findAll = Character::model()->findAll();
foreach($findAll as $result) {
echo $result->playerName; // result should be "Yii-Insert-Test", instead it's NULL
}
?>
It does get inserted though:
{
"_id" : ObjectId("54a0deda60fc21843100002a"),
"playerName" : "Yii-Insert-Test",
"playerId" : "123456789",
"playerScore" : "9001"
}
And here's my very simple model Character.php :
<?php
class Character extends EMongoDocument
{
public $_id;
public $playerName;
public $playerId;
public $playerScore;
public function getCollectionName()
{
return 'usercollection';
}
public function rules()
{
return array(
array('playerName', 'required'),
);
}
public function attributeNames()
{
return array(
'playerName' => 'Character Name',
'playerId' => 'Character ID',
'playerScore' => 'Character Score',
);
}
public static function model($className=__CLASS__)
{
return parent::model($className);
}
}
I feel like it's right in front of me but can't see what the problem is.
edit:
slowly figuring it out - looks like I am receiving data, but it's all empty - this is the mongoDB cursor. I'm already doing a forloop to iterate it, so I'll have to figure out what else I'm not doing.
Lets see the action (form is based on model)
$this->form->bind ($request->getParameter('task'));
if ($this->form->isValid())
{
// cakk
}
This all works good, its not valid when its really not valid etc.
But I want to edit some fields, for example a date must be always set to now. Or a password must be encoded. How can I do this?
You can override the doSave() method in the form .. something like this :
public function doSave($con = null)
{
$this->values['form field'] = 'newvalue';
parent::doSave($con);
}
$this->values is an array containing the values on the form.
Update
You could use a post validator .. like this (again in the form class) :
$this->validatorSchema->setPostValidator(
new sfValidatorCallback(array('callback' => array($this, 'methodName')))
);
public function methodName($validator, $values)
{
// check / change what you need to
$values['fieldname'] = 'new value';
// return values
return $values;
}
I've got a Form where the user can check a checkbox "create new address" and can then fill out the fields for this new address in the same form.
Now I want to validate the fields of this new address ONLY if the checkbox has been checked. Otherwise, they should be ignored.
How can I do that using Zend_Form with Zend_Validate?
Thanks!
I think that the best, and more correct way to do this is creating a custom validator.
You can do this validator in two different ways, one is using the second parameter passed to the method isValid, $context, that is the current form being validated, or, inject the Checkbox element, that need to be checked for validation to occur, in the constructor. I prefer the last:
<?php
class RequiredIfCheckboxIsChecked extends Zend_Validate_Abstract {
const REQUIRED = 'required';
protected $element;
protected $_messageTemplates = array(
self::REQUIRED => 'Element required'
);
public function __construct( Zend_Form_Element_Checkbox $element )
{
$this->element = $element;
}
public function isValid( $value )
{
$this->_setValue( $value );
if( $this->element->isChecked() && $value === '' ) {
$this->_error( self::REQUIRED );
return false;
}
return true;
}
}
Usage:
class MyForm extends Zend_Form {
public function init()
{
//...
$checkElement = new Zend_Form_Element_Checkbox( 'checkbox' );
$checkElement->setRequired();
$dependentElement = new Zend_Form_Element_Text( 'text' );
$dependentElement->setAllowEmpty( false )
->addValidator( new RequiredIfCheckboxIsChecked( $checkElement ) );
//...
}
}
I have not tested the code, but I think it should work.
I didn't actually run this, but it should work within reason. I've done something similar before that worked, but couldn't remember where the code was.
<?php
class My_Form extends Zend_Form
{
public function init()
{
$checkbox = new Zend_Form_Element_Checkbox("checkbox");
$checkbox->setValue("checked");
$textField = new Zend_Form_Element_Text("text");
$this->addElements(array("checkbox", "text"));
$checkbox = $this->getElement("checkbox");
if ($checkbox->isChecked() )
{
//get textfield
$textField = $this->getElement("text");
//make fields required and add validations to it.
$textField->setRequired(true);
}
}
}
Here's what I do if I need to validate multiple elements against each other
$f = new Zend_Form();
if($_POST && $f->isValid($_POST)) {
if($f->checkbox->isChecked() && strlen($f->getValue('element')) === 0) {
$f->element->addError('Checkbox checked, but element empty');
$f->markAsError();
}
if(!$f->isErrors()) {
// process
...
...
}
}
I've been wondering how to do that in ZF as well, though never had to implement such form feature.
One idea that comes to mind is to create a custom validator that accepts the checkbox field as a parameter, and run it in a validator chain, as documented. If the checkbox is checked, validator could return failure. Then you can check whether all validations failed and only then treat form as having failed validation.
That level of customization of form validation could be inconvenient, so maybe using form's isValidPartial method would be better.
I created a custom validator that will make your element required based on the value of another zend form element.
Here's the full code. I hope this helps someone.
The idea is to create a custom validator and pass in the name of the conditional element and the value of that conditional element into the constructor. Zend_Validor's isValid method already has access to the value of the element you are attaching the validtor to along with all the form element names and values.
So, inside the isValid method you have all the information you need to determine if your form element should be a required element.
On Zend 1 extend the isValid method, where you set required depending on posted data for example:
public function isValid($data)
{
if (!empty($data['companyCar'])) {
$this->getElement('carValue')->setRequired(true);
}
return parent::isValid($data);
}
Thank you JCM for your good solution.
However 2 things I've noticed:
The isValid method of your validator has to return true in case of success.
The validator will not be executed during form validation without pass the allowEmpty option to false to the text field.
Once you're OK with basic record form built after example from Tutorial, you realize you want more professionally designed Record Form. E.g. I don't want to duplicate record form for the same table in User and Admin areas.
1) Does anyone use some mechanism, possibly inheritance, to reduce duplication of almost similar admin and user forms? Is that burdensome or sometimes you better just do with copy-pasting?
2) Has anyone considered it to be a good idea to build some basic Record class
that can determine that among several record forms on this page, the current post is addressed specifically to this record form
that can distinguish between Edit or Delete buttons clicks in some organized fashion.
3) My current practice includes putting all form config code (decorators, validations, initial values) into constructor and form submit handling is put into a separate ProcessSubmit() method to free controller of needless code.
All the above addresses to some expected Record Form functionality and I wonder if there is any guideline, good sample app for such slightly more advanced record handling or people are still reinveting the wheel. Wondering how far you should go and where you should stop with such impovements...
Couple of suggestions:
First of all - Use the init() function instead of constructors to add your elements when you are subclassing the form. The init() function happens after the parameters you pass to the class are set.
Second - Instead of subclassing your form - you can just set an "option" to enable the admin stuff:
class My_Record_Form extends Zend_Form {
protected $_record = null;
public function setRecord($record) {
$this->_record = $record;
}
public function getRecord() {
if ($this->_record === null || (!$this->_record instanceOf My_Record)) {
throw new Exception("Record not set - or not the right type");
}
return $this->_record;
}
protected $_admin = false;
public function setAdmin($admin) {
$this->_admin = $admin;
}
public function getAdmin() { return $this->_admin; }
public function init() {
$record = $this->getRecord();
$this->addElement(......);
$this->addElement(......);
$this->addElement(......);
if ($this->getAdmin()) {
$this->addElement(.....);
}
$this->setDefaults($record->toArray());
}
public function process(array $data) {
if ($this->isValid($data)) {
$record = $this->getRecord();
if (isset($this->delete) && $this->delete->getValue()) {
// delete button was clicked
$record->delete();
return true;
}
$record->setFromArray($this->getValues());
$record->save();
return true;
}
}
}
Then in your controller you can do something like:
$form = new My_Record_Form(array(
'record'=>$record,
'admin'=>My_Auth::getInstance()->hasPermission($record, 'admin')
));
There is nothing "wrong" with making a My_Record_Admin_Form that handles the admin stuff as well - but I found this method keeps all the "record form" code in one single place, and a bit easier to maintain.
To answer section 2: The edit forms in my code are returned from a function of the model: $record->getEditForm() The controller code ends up looking a little like this:
protected $_domain = null;
protected function _getDomain($allowNew = false)
{
if ($this->_domain)
{
return $this->view->domain = $this->_domain;
} else {
$id = $this->_request->getParam('id');
if (($id == 'new' || $id=='') && $allowNew)
{
MW_Auth::getInstance()->requirePrivilege($this->_table, 'create');
$domain = $this->_table->createRow();
} else {
$domain = $this->_table->find($id)->current();
if (!$domain) throw new MW_Controller_404Exception('Domain not found');
}
return $this->view->domain = $this->_domain = $domain;
}
}
public function editAction()
{
$domain = $this->_getDomain(true);
MW_Auth::getInstance()->requirePrivilege($domain,'edit');
$form = $domain->getEditForm();
if ($this->_request->isPost() && $form->process($this->_request->getPost()))
{
if ($form->delete && $form->delete->getValue())
{
return $this->_redirect($this->view->url(array(
'controller'=>'domain',
'action'=>'index',
), null, true));
} else {
return $this->_redirect($this->view->url(array(
'controller'=>'domain',
'action'=>'view',
'id'=>$form->getDomain()->id,
), null, true));
}
}
$this->view->form = $form;
}
So - the actual id of the record is passed in the URI /domain/edit/id/10 for instance. If you were to put multiple of these forms on a page - you should make sure to set the "action" attribute of the form to point to an action specific to that form.
I created a SimpleTable extends Zend_Db_Table and SimpleForm extends Zend_Db_Form classes. Both of these assume that your table has an auto-incrementing ID column.
SimpleTable has a saveForm(SimpleForm $form) function which uses the dynamic binding to match form element names to the columns of the record. I also included an overridable saveFormCustom($form) for any special handling.
The SimpleForm has an abstract setup() which must be overridden to setup the form. I use the init() to do the initial setup (such as adding the hidden ID field).
However, to be honest, I really don't like using the Zend_Form object, I feel like that should be handled in the View, not the Model or Controller.