I need to perform an upsert command in yiimongodbsuite.
I tried
$model = new Murls();
$model->userid=$userid;
$model->title=$title;
$model->edits[0] = new Medithtml();
$model->edits[0]->path= $htm;
$model->edits[0]->html=$path;
$model->edits[0]->ci=$ci;
$model->update(array('_id'=>$rec->_id ),array('userid', 'title','edits' ), true );
But this shows an error.
Murls model is defined as follows
class Murls extends EMongoDocument
{
public $userid;
public $title;
public $edits;
public static function model($className=__CLASS__)
{
return parent::model($className);
}
// This method is required!
public function getCollectionName()
{
return 'murls';
}
public function attributeLabels()
{
return array(
'html'=>'Html',
);
}
public function embeddedDocuments()
{
return array(
// property name => embedded document class name
'edits'=>'Medithtml',
);
}
public function behaviors(){
return array(
'embeddedArrays' => array(
'class' => 'ext.YiiMongoDbSuite.extra.EEmbeddedArraysBehavior',
'arrayPropertyName' => 'edits', // name of property, that will be used as an array
'arrayDocClassName' => 'Medithtml' // class name of embedded documents in array
),
);
}
}
and model Medithtml as
class Medithtml extends EMongoEmbeddedDocument{
public $html;
public $path;
public $ci;
public static function model($className=__CLASS__)
{
return parent::model($className);
}
}
What I need to achieve is that a record with $title can have n number of $html , $path and $ci.
Any help will be appreciated.
What I am looking is to store data like this
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '51ee1956d39c2c7e078d80da',
)),
'userid' => '12',
'title' => 'Mongo',
'edits' =>
array (
0 =>
array (
'html' => 'html>body>div:nth-child(2)>a>div>a>div',
'path' => 'ssssss',
'ci' => '1',
),
1 =>
array (
'html' => 'html>body>div:nth-child(2)>a>div:nth-child(3)>a>h2',
'path' => '/assets/img/demo/demo-avatar9604.jpg',
'ci' => '2',
),
2 =>
array (
'html' => ' html>body>div:nth-child(2)>a>div:nth-child(3)>a>center:nth-child(16)>a>h1',
'path' => '333',
'ci' => '3',
),
),
)
Only the comments array will be updated if record with a particular combination of 'title' and 'userid' exists.If it doesn not exists a new record will be inserted
You are inheriting from wrong class. To save document you must inherit from EMongoDocument not EMongoEmbeddedDocument. These classes are similar but have different purpose.
EMongoEmbeddedDocument Is for embedded documents only, it should be used only for embedded documents
EMongoDocument extends from EMongoEmbeddedDocument with methods to actually save data to db.
For array of comments, you have two options:
Use plain php array - simple less maintanable, less power, erron prone..
Use array of embedded documents - each comment is document, so can be validated, has rigid structure etc.
By default save/insert/update stores all attributes. For partial updates use combination of $attributes and set $modify to true. Warning: Passing array of attributes without $modify will store only passed attributes, discarding rest of document.
public function save($runValidation = true, $attributes = null)
...
public function insert(array $attributes = null)
...
public function update(array $attributes = null, $modify = false)
...
So in your case you can update like that:
$model->update(array('comments'), true);
Or if it's ok for you to ovverride whole document just save:
$model->save();
Note: for composite pk ovverride primaryKey():
public function primaryKey()
{
return array('title', 'userid');
}
Uh, good that stackoverflow have drafts autosave feature:)
Finally I got solution in this way:
$rec = $model->find($criteria) ;
if($rec){
foreach($rec->edits as $editarray){
$var[]=$editarray;
}
$edits_new= new Medithtml();
$edits_new['html']=$htm;
$edits_new['ci']=$ci;
$edits_new['path']=$path;
$var[]=$edits_new;
$rec->edits=$var;
$rec->userid=$userid;
$rec->title=$title;
$rec->update(array('edits' ), true);
}
Related
I am trying to use a Has_many relation as the summary fields for a DataObject and can't seem to get it working.
Basically:
I have a Form
each form has many submissions/entries
each form has many fields
Each field has many answers.
I'm trying to create a gridfield in the back end admin area of each form which displays the entries for each form.
In the summary fields for the entry, i'd like to display the Date created, and the first 3 fields for that form.
So, for example if we had a form with a name, email, and phone field the summary fields would be as follows:
Date Created
Name
Email
Phone
with the relevent entry data/responses as summary information for the entry.
Here is what I have so far. This is the form:
<?php
class ContactBlock extends Block {
private static $db = array(
// Fields for the form/block go here
);
private static $has_many = array(
'ContactBlockFields' => 'ContactBlockField',
'ContactBlockEntries' => 'ContactBlockEntry'
);
public function getCMSFields() {
// Irrelevant code goes here!
// CONTACT BLOCK ENTRIES
$entriesInfo = new GridFieldDataColumns();
$entriesInfo->setDisplayFields(singleton('ContactBlockEntry')->summaryFields());
$entriesConfig = GridFieldConfig::create();
$entriesConfig->addComponents(
new GridFieldToolbarHeader(),
new GridFieldAddNewButton('toolbar-header-right'),
$entriesInfo,
new GridFieldSortableHeader(),
new GridFieldPaginator(50),
new GridFieldEditButton(),
new GridFieldDeleteAction(),
new GridFieldDetailForm()
);
$entriesGrid = GridField::create('ContactBlockEntries', 'Form Entries', $this->ContactBlockEntries(), $entriesConfig);
$fields->addFieldToTab('Root.FormEntries', $entriesGrid);
return $fields;
}
}
This is the Entry DataObject:
<?php
class ContactBlockEntry extends DataObject {
private static $has_one = array(
'ContactBlock' => 'ContactBlock'
);
private static $has_many = array(
'ContactBlockFieldAnswers' => 'ContactBlockFieldAnswer',
);
private static $many_many = array(
'FormFields' => 'ContactBlockField'
);
static $summary_fields = array(
'Date'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
//=== REMOVE FIELDS ====
$fields->removeFieldFromTab('Root','FormFields');
$fields->removeFieldFromTab('Root','ContactBlockFieldAnswers');
$fields->removeFieldFromTab('Root.Main','ContactBlockID');
//=== REMOVE FIELDS ====
return $fields;
}
public function onBeforeDelete() {
parent::onBeforeDelete();
// Delete answers that are associated with this block.
$data = ContactBlockFieldAnswer::get()
->filter('ContactBlockEntry', $this->ID);
foreach( $data as $d) {
$d->delete();
}
}
public function getDate() {
$date = date('d/m/Y',strtotime($this->Created));
return $date;
}
}
This is the field code:
<?php
class ContactBlockField extends DataObject {
private static $db = array(
'SortOrder' => 'Int',
'FieldName' => 'Varchar',
'FieldType' => 'Varchar',
'DropdownValues' => 'Varchar(255)',
'Label' => 'Varchar',
'Placeholder' => 'Varchar',
'Required' => 'Boolean'
);
private static $has_one = array(
'ContactBlock' => 'ContactBlock',
);
private static $has_many = array(
'ContactBlockFieldAnswer' => 'ContactBlockFieldAnswer',
);
private static $belongs_many_many = array(
'Entries' => 'ContactBlockEntry'
);
static $searchable_fields = array(
'FieldType',
'FieldLabel',
'Required'
);
static $summary_fields = array(
'FieldType' => 'Field Type',
'Label' => 'Field Label',
'Required' => 'Required Field?'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
// Unrelated stuff here
return $fields;
}
}
I can't seem to figure out how to get the column labels, and their relevant data showing on the gridfield. Any help or advice would be much appreciated.
UPDATE 24/3/17:
OK I've got a little further with this. On the ContactBlockEntry DataObject, after implementing the changes suggested by UncleCheese, I have discovered the following:
public function getFirstField() {
return $this->FormFields()->first();
}
public function getSecondField() {
return $this->FormFields()->offsetGet(1);
}
public function getThirdField() {
return $this->FormFields()->offsetGet(2);
}
public function summaryFields() {
return [
'Date' => 'Submitted',
'Answers.First.Value' => $this->getFirstField()->Label,
'Answers.Second.Value' => $this->getSecondField()->Label,
'Answers.Third.Value' => $this->getThirdField()->Label,
];
}
$this->getFirstField()->Label is returning [Notice] Trying to get property of non-object however, when I echo/print this function in getCMSFields() I can see the label value.
Answers.First.Value works. It returns the value/answer submitted in the first field. The problem is, I can't seem to get the second and third values, as I can't figure out the method to retrieve them. I tried offsetGet() and it said the method isn't available.
In this case, you can define a summaryFields() method in lieu of the static array. This will allow you to return a computed value for the headings.
What complicates things is getting the values. The first three fields are not properties of the Entry object, so you'll need to provide getters for the Entry dataobject to compute them on the fly as "virtual" properties. It's a bit clunky, but something like this should work.
public function getFirstField()
{
return $this->FormFields()->first();
}
public function getSecondField()
{
return $this->FormFields()->offsetGet(1);
}
public function getThirdField()
{
return $this->FormFields()->offsetGet(2);
}
public function summaryFields()
{
return [
'Created' => 'Created',
'FirstField.Answer' => $this->getFirstField()->FieldName,
'SecondField.Answer' => $this->getSecondField()->FieldName,
'ThirdField.Answer' => $this->getThirdField()->FieldName,
];
}
I'm not too sure about your data model, though. You have the answers has a has_many on your field object. You would in that case have to create another getter on your Field object for AnswerLabel that somehow concatenated all the answers into a string, or maybe just got the first one. Whatever business logic you choose. Just use FirstField.AnswerLabel etc. in your array, or whatever you choose to call that method. The point being, you need to resolve a plural relationship into a single readable value. How you do that is up to you.
Can implement dynamic validation on element level? I used this example to implement validation of one element dependent on the value of the other element. But for this purpose I'll need to implement this for every single form where I use this element (comment) with this validation. I have many forms like that. Is there way to do the following:
to take this filter/validation logic to the element level using some kind of "data-comment-for" attribute and retrieving the value of the element on which it depends from the parent form.
This is my current code (but I need to have it for every form now. It does not look elegant at all) :
class CompetencyAdvanceHumanRightsAndJusticeFormFilter extends InputFilter
{
public function isValid($context = null)
{
$figradeCommentName = 'applJusticeFIGrade'.'Comment';
$forGrade = $this->get('applJusticeFIGrade');
$gradeComment = $this->get($figradeCommentName);
$applJusticeFIGradeRawValue = $forGrade->getRawValue('applJusticeFIGrade');
if(is_numeric($applJusticeFIGradeRawValue)){
$gradeValue = intval($applJusticeFIGradeRawValue);
}else{
$gradeValue = $applJusticeFIGradeRawValue;
}
if ($gradeValue != 'na' && $gradeValue > 0) {
$gradeComment->setRequired(true);
$validatorChain = new Validator\ValidatorChain();
$validatorChain->attach(
new Validator\NotEmpty(),
true
);
$gradeComment->setValidatorChain($validatorChain);
}
return parent::isValid($context);
}
public function __construct(){
$this->add(array(
'name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
));
$this->add(array(
'name' => 'studEvalId',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
));
}
}
EDIT:
I added code for the custom element to the question. There are some "leftovers" of my attempts to place this logic to the element level.
Comment Element
class Comment extends Element implements InputProviderInterface
{
/**
* #var ValidatorInterface
*/
protected $validator;
// set its type
protected $attributes = array(
'type' => 'comment'
);
public function init()
{
if (null === $this->validator) {
$validator = new StringLength();
$validator->setMax(10);
$validator->setMessage('The comment should not exceed 1000 letters!', StringLength::INVALID);
$this->validator = $validator;
}
}
/**
* Get a validator if none has been set.
*
* #return ValidatorInterface
*/
public function getValidator()
{
return $this->validator;
}
/**
* #param ValidatorInterface $validator
* #return $this
*/
public function setValidator(ValidatorInterface $validator)
{
$this->validator = $validator;
return $this;
}
/**
* remove require and validator defaults because we have none
*
* #return array
*/
public function getInputSpecification()
{
// return array(
// 'name' => $this->getName(),
// 'required' => false,
// 'validators' => array(
// $this->getValidator(),
// ),
// 'filters' => array(
// new FIGradeCommentDynamicBufferFilter()
// ),
// );
return array(
'name' => $this->getName(),
'required' => false,
'filters' => array(
array('name' => 'Zend\Filter\StringTrim'),
),
'validators' => array(
$this->getValidator(),
),
);
}
// tell it where to find its view helper, so formRow and the like work correctly
public function getViewHelperConfig()
{
return array('type' => '\OnlineFieldEvaluation\View\Helper\FormComment');
}
}
You could make a base abstract input-filter class and an interface and make all your form filters extend the base class that implements the interface with the methods you expect inside your form classes to make the thing work correctly.
Make an interface with the methods:
interface GradeCommentFormFilterInterface()
{
protected function getGradeInput();
protected function getCommentInput();
}
Then you move the common code to your base class:
abstract class BaseGradeCommentFormFilter extends InputFilter implements GradeCommentFormFilterInterface
{
protected function getGradeInput()
{
return $this->get(static::GRADE_NAME);
}
protected function getCommentInput()
{
return $this->get(static::GRADE_NAME . 'Comment');
}
public function isValid($context = null)
{
$gradeInput = $this->getGradeInput();
$commentInput = $this->getCommentInput();
$rawValue = $this->getRawValue($gradeInput);
if(is_numeric($rawValue))
{
$gradeValue = intval($rawValue);
}
else
$gradeValue = $rawValue;
if ($gradeValue != 'na' && $gradeValue > 0) {
$commentInput->setRequired(true);
$validatorChain = new Validator\ValidatorChain();
$validatorChain->attach(
new Validator\NotEmpty(),
true
);
$commentInput->setValidatorChain($validatorChain);
}
return parent::isValid($context);
}
}
Now you can use your abstract class like this:
class CompetencyAdvanceHumanRightsAndJusticeFormFilter extends BaseGradeCommentFormFilter
{
const GRADE_NAME = 'applJusticeFIGrade';
//... other code
}
I quickly tried to make it work for your case, but this isn't tested, and probably there are ways to optimize this, but it gives you an idea of what you can do.
I got a many to many relationship between Products and Categories using Silverstripes' ORM. All is working except when I try to add a Category for a product the choices for Category is shown by user id instead of Category name. I tried to map ID to name but did not work. Below is what I tried, what am I missing?
class Product extends DataObject {
private static $db = array(
'ProductName' => 'Varchar(32)',
);
private static $many_many = array (
'Category' => 'Category'
);
}
class Category extends DataObject {
private static $db = array(
'Category' => 'Varchar(32)',
);
public function searchableFields() {
return array (
'Category' => array (
'filter' => 'ExactMatchFilter',
'title' => 'Category',
'field' => 'TextField'->setSource(
$this::get()->map('ID','Category')
)
)
);
}
private static $belongs_many_many = array (
'Product' => 'Product'
);
}
When Silverstripe needs to display a shorthand for a DataObject, it will call the getTitle() method on that DataObject.
getTitle() will first check if the DataObject has a Title field and return that value if it does.
If your DataObject doesn't have Title field, it will try searching for a Name field.
If it can't find a Name field either, it will default to returning the ID of your DataObject, which is probably what is happening for you.
How to fix your specific example
There's 2 ways you can fix your specific example:
Rename your DataObjects DB fields to Name or Title
Override the getTitle() method on your 2 DataObjects
Solution #1
class Product extends DataObject {
private static $db = array(
'Name' => 'Varchar(32)',
);
private static $many_many = array (
'Category' => 'Category'
);
}
class Category extends DataObject {
private static $db = array(
'Name' => 'Varchar(32)',
);
public function searchableFields() {
return array (
'Category' => array (
'filter' => 'ExactMatchFilter',
'title' => 'Category',
'field' => 'TextField'->setSource(
$this::get()->map('ID','Category')
)
)
);
}
private static $belongs_many_many = array (
'Product' => 'Product'
);
}
Solution #2
class Product extends DataObject {
private static $db = array(
'ProductName' => 'Varchar(32)',
);
private static $many_many = array (
'Category' => 'Category' );
public function getTitle() {
return $this->ProductName;
}
}
class Category extends DataObject {
private static $db = array(
'Category' => 'Varchar(32)',
);
public function searchableFields() {
return array (
'Category' => array (
'filter' => 'ExactMatchFilter',
'title' => 'Category',
'field' => 'TextField'->setSource(
$this::get()->map('ID','Category')
)
)
); }
private static $belongs_many_many = array (
'Product' => 'Product' );
public function getTitle() {
return $this->Category;
}
}
Which solution is best?
In your specific case, I would go with solution #1, because your DB fields are functionally name/title fields.
I would use solution #2 if my DataObject's title needs to use many fields. Let's say you a have Person DataObject with a first name and a last name:
public function getTitle() {
return $this->FirstName . ' ' . $this->LastName;
}
I've created a DataObject called Service and I've got a many_many relationship to RelatedServices as follows:
class Service extends DataObject {
private static $db = array (
'Name' => 'Varchar',
'Description' => 'Varchar',
);
private static $many_many = array (
'RelatedServices' => 'RelatedService'
);
public function getCMSFields() {
$fields = FieldList::create(TabSet::create('Root'));
$services = $this->get()->where("\"Service\".\"Name\" != '$this->Name'")->map('ID', 'Name')->toArray();
$fields->addFieldsToTab('Root.Main', array(
TextField::create('Name'),
TextField::create('Description'),
ListBoxField::create('RelatedServices', 'Related services')->setMultiple(true)->setSource($services)
));
return $fields;
}
}
and:
class RelatedService extends DataObject {
private static $db = array (
'Name' => 'Varchar',
);
private static $belongs_many_many = array (
'RelatedServices' => 'RelatedService'
);
}
This is being used in a ModelAdmin and the service works right including the related services text area, however it doesn't save. I did it previously that it was in a seperate tab in the CMS and had RelatedService have and admin section which looking through the DB looked like it worked however I thought it was an unnecessary section so tried to make it all in one and now no longer saves to the DB.
You don't need the 'RelatedService' class, as you are referencing to the class you are working in. So relating to the class 'Service' itself would make more sense.
The reason why your code won't work is because you have got your relations mixed up.
class Service extends DataObject{
private static $db = array (
'Name' => 'Varchar',
'Description' => 'Varchar',
);
private static $many_many = array (
'RelatedServices' => 'Service'
);
private static $belongs_many_many = array (
'ParentServices' => 'Service'
);
public function getCMSFields() {
$fields = FieldList::create(TabSet::create('Root'));
$services = $this->get()->where("\"Service\".\"Name\" != '$this->Name'")->map('ID', 'Name')->toArray();
$fields->addFieldsToTab('Root.Main', array(
TextField::create('Name'),
TextField::create('Description'),
ListBoxField::create('RelatedServices', 'Related services')->setMultiple(true)->setSource($services)
));
return $fields;
}
}
I have two user groups Administrator and Inhaltsautoren
My LandingPage has a Tab Teaser with a gridField. The normal user can not see the entries and i dont know why?
I cant find something for setting the permissions for Inhaltsautoren. Has someone an idea why there are no entries in the gridField?
Teaser.php
<?php
class Teaser extends DataObject {
private static $db = array (
'Title' => 'Varchar',
'Description' => 'HTMLText'
);
private static $has_one = array (
'Photo' => 'Image',
'Link' => 'Link'
);
private static $many_many = array(
'Tags' => 'Tag'
);
private static $summary_fields = array (
'GridThumbnail' => '',
'Title' => 'Titel',
'Description' => 'Beschreibung'
);
public function getGridThumbnail() {
if($this->Photo()->exists()) {
return $this->Photo()->SetWidth(100);
}
return "(no image)";
}
public function getCMSFields() {
$fields = FieldList::create(
TextField::create('Title'),
$tags = TagField::create('Tags','Tags',Tag::get(),$this->Tags()),
HTMLEditorField::create('Description', 'Beschreibung'),
LinkField::create('LinkID', 'Weiterleitung'),
$uploader = UploadField::create('Photo')
);
$tags->setShouldLazyLoad(true); // tags should be lazy loaded
$tags->setCanCreate(true); // new tag DataObjects can be created
$uploader->setFolderName('teaser');
$uploader->getValidator()->setAllowedExtensions(array('png','jpeg','jpg'));
return $fields;
}
}
and my LadingPage.php
$fields->addFieldToTab('Root.Teaser', $gridField = GridField::create(
'Teasers',
'Landing Page Teaser',
$this->Teasers(),
GridFieldConfig_RecordEditor::create()
));
$gridField->getConfig()->getComponentByType("GridFieldDataColumns")->setFieldCasting(array("Description"=>"HTMLText->BigSummary"));
Use canView() on your dataobject and check inside this function if your user is allowed to see this object or not.
public function canView($member = null) {
return Permission::check('ADMIN', 'any');
}