I'm trying to create a 'link_field' in Drupal through a module, I've got the following code but I'd like to configure the field settings to do the following:
Not require title
Update the number of values to unlimited
I'm struggling to find any information around what setting key/pair values you can pass through for these, anyone able to offer some guidance on these?
<?php
/**
* Implements hook_enable().
*
* Create a field. Fields can be created without any needs to attach them to
* entities.
*/
function youtube_carousel_enable() {
$field = array(
'field_name' => 'ytcarousel_field',
'type' => 'link_field',
);
field_create_field($field);
/**
* Bind field to a entity bundle.
*/
$instance = array(
'field_name' => $field['field_name'],
'entity_type' => 'node',
'bundle' => 'homepage',
'label' => 'YouTube Video'
);
field_create_instance($instance);
}
/**
* Implements hook_disable().
*
* Remove field from node bundle (content type) and then delete the field.
*/
function youtube_carousel_disable() {
$instance = array(
'field_name' => 'ytcarousel_field',
'entity_type' => 'node',
'bundle' => 'homepage',
'label' => 'YouTube Video'
);
field_delete_instance($instance);
field_delete_field($instance['field_name']);
print 'Removed ' . $instance['field_name'] . "\n";
}
?>
Cheers
After viewing the HTML source to get key/value pairs I've managed to do something similar to the above with the following code:
<?php
/**
* Implements hook_enable().
*
* Create a field. Fields can be created without any needs to attach them to
* entities.
*/
function youtube_carousel_enable() {
$field = array(
'field_name' => 'ytcarousel_field',
'type' => 'link_field',
'cardinality' => -1
);
field_create_field($field);
/**
* Bind field to a entity bundle.
*/
$instance = array(
'field_name' => $field['field_name'],
'entity_type' => 'node',
'bundle' => 'homepage',
'label' => 'YouTube Video',
'settings' => array('title' => 'required')
);
field_create_instance($instance);
}
/**
* Implements hook_disable().
*
* Remove field from node bundle (content type) and then delete the field.
*/
function youtube_carousel_disable() {
$instance = array(
'field_name' => 'ytcarousel_field',
'entity_type' => 'node',
'bundle' => 'homepage',
'label' => 'YouTube Video',
'settings' => array('title' => 'required')
);
field_delete_instance($instance);
field_delete_field($instance['field_name']);
print 'Removed ' . $instance['field_name'] . "\n";
}
?>
The cardinality controls have many instances the value can have, "-1" is unlimited.
'cardinality' => -1
The title attribute is controlled via the instance not the field itself, I believe, and it uses text based values rather than numeric.
'settings' => array('title' => 'required')
Related
I'm trying to rename fields and update values on already inserted rows in cakephp migrations.
Workflow:
In before method i fetch all already added rows in my db so i will be able to update them in after method.
Then migration happens, which create columns value_from and value_to and also drops columns price_from and price_to
Then i try in my after method fetch all "new" rows, where i wan't to update values from old ones, but error happens, because find('all') method throws errors
My code:
class ShippingTypeCostCalculationM1483610977ProjectPluginLogistics extends CakeMigration {
/**
* Data which will be inserted in modified table shipping costs
* #var
*/
private $data;
/**
* Migration description
*
* #var string
* #access public
*/
public $description = '';
/**
* Actions to be performed
*
* #var array $migration
* #access public
*/
public $migration = array(
'up' => array(
'create_field' => array(
'shipping_costs' => array(
'shipping_type' => array('type' => 'string', 'null' => false, 'default' => 'order_sum', 'length' => 20, 'collate' => 'utf8_unicode_ci', 'charset' => 'utf8', 'after' => 'customer_type'),
'value_from' => array('type' => 'decimal', 'null' => false, 'default' => NULL, 'length' => '10,4', 'after' => 'shipping_type'),
'value_to' => array('type' => 'decimal', 'null' => false, 'default' => NULL, 'length' => '10,4', 'after' => 'value_from'),
),
),
'drop_field' => array(
'shipping_costs' => array('price_from', 'price_to',),
)
),
'down' => array(
'drop_field' => array(
'shipping_costs' => array('shipping_type', 'value_from', 'value_to',),
),
),
'create_field' => array(
'shipping_costs' => array(
'price_from' => array('type' => 'decimal', 'null' => false, 'default' => NULL, 'length' => '10,4'),
'price_to' => array('type' => 'decimal', 'null' => false, 'default' => NULL, 'length' => '10,4'),
),
)
);
/**
* Before migration callback
*
* #param string $direction, up or down direction of migration process
* #return boolean Should process continue
* #access public
*/
public function before($direction) {
$shippingCost = ClassRegistry::init('Logistics.ShippingCost');
$this->data = $shippingCost->find('all');
return true;
}
/**
* After migration callback
*
* #param string $direction, up or down direction of migration process
* #return boolean Should process continue
* #access public
*/
public function after($direction) {
$shippingCost = ClassRegistry::init('Logistics.ShippingCost');
// This is where error happens
$shippingCosts = $shippingCost->find('all');
foreach ($this->data as $item) {
$shippingCost = $shippingCosts->get($item['shipping_costs']['id']);
$shippingCost->value_from = $item['shipping_costs']['price_from'];
$shippingCost->value_to = $item['shipping_costs']['price_to'];
$shippingCosts->save($shippingCost);
}
return true;
}
}
PROBLEM:
In after method, cake php is still trying to fetch values like price_from or price_to which are already deleted.
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'ShippingCost.price_from' in 'field list'
How can i overcome my problem?
If you need any additional informations, please let me know and i will provide. Thank you in advance!
You run the callbacks in any case, the code is always executed.
public function after($direction) {...}
See the argument? This can be up and down. You need to wrap your code that you want to run before or after applying the migration accordingly in a check like if ($direction === 'down') { ... }.
Also I don't think your approach is well done. If you have a lot of data you can run out of memory.
Create a migration that will add the new fields. Then another migration file that will just do the data transformation and process the data in chunks. And a third one that will delete the no longer needed fields after that. The simple reason is: Separate data from schema changes.
Unable to locate in the SilverStripe Documentation how to have a DataObject Model inject a collection of default records on /dev/build
Anybody able to point me in the right direction
This is what I currently have, and obviously I would like to inject pre-configured options into this aptly named Configuration model for my Module.
class Configuration extends DataObject
{
private static $db = array(
'Option' => 'Varchar',
'Value' => 'Varchar'
);
private static $summary_fields = array(
'Option' => 'Option',
'Value' => 'Value',
);
}
Thanks in advance for any direction/pointers.
UPDATE
I was turned onto SiteConfig by #Barry below
However in following his practice, requireDefaultRecords() is not injecting defaults
Note: I have since revisited /dev/build?flush
class RMSConfiguration extends DataExtension
{
private static $db = array(
'username' => 'Varchar',
'password' => 'Varchar',
'agent_id' => 'Varchar(15)',
'client_id' => 'Varchar(15)',
'testMode' => 'Int(1)',
'timezone' => 'Varchar',
'apiUrl' => 'Varchar(255)'
);
public function updateCMSFields(FieldList $fields)
{
$fields->addFieldsToTab(
"Root.RMSConfig",
array(
TextField::create('username', 'RMS Username'),
TextField::create('password', 'RMS Password'),
TextField::create('agent_id', 'RMS Agent ID'),
TextField::create('client_id', 'RMS Client ID'),
TextField::create('apiUrl', 'API Url'),
CheckboxField::create("testMode", 'Toggle Test Mode'),
DropdownField::create("timezone", 'Timezone', static::$timezones)
)
);
}
public function requireDefaultRecords()
{
parent::requireDefaultRecords();
$arrOptions = array(
'timezone' => 'Australia/Sydney',
'apiUrl' => 'https://api.example.com.au/',
'testMode' => 0
);
foreach ($arrOptions as $strOption => $strValue) {
if (!$configuration = self::get()->filter('Option', $strOption)->first()) {
$configuration = self::create(array( 'Option' => $strOption ));
}
$configuration->Value = $strValue;
$configuration->write();
}
}
/**
* List of timezones supported by PHP >=5.3.x
*
* #var array
*/
public static $timezones = array(
"Africa/Abidjan",
"Africa/Accra",
"Africa/Addis_Ababa",
"Africa/Algiers",
...
...
"Zulu"
);
}
Using the function requireDefaultRecords in the DataObject - this is called during every dev/build.
Note: First check if the option exists to prevent duplicates as this will be called every time you dev build.
class Configuration extends DataObject {
private static $db = array(
'Option' => 'Varchar',
'Value' => 'Varchar'
);
private static $summary_fields = array(
'Option' => 'Option',
'Value' => 'Value',
);
function requireDefaultRecords() {
parent::requireDefaultRecords();
$arrOptions = array(
'Option1' => 'Value1',
'Option2' => 'Value2',
'Option3' => 'Value3',
);
foreach ($arrOptions as $strOption => $strValue) {
if (!$configuration = Configuration::get()->filter('Option',$strOption)->first())
$configuration = Configuration::create(array('Option' => $strOption));
$configuration->Value = $strValue;
$configuration->write();
}
}
}
One final comment is that there is a module for SiteConfig which is used by SilverStripe, most modules and where I would recommend you put configuration values like this instead.
If you do choose SiteConfig then please see the function populateDefaults and documentation for it's use, this is an example...
/**
* Sets the Date field to the current date.
*/
public function populateDefaults() {
$this->Date = date('Y-m-d');
parent::populateDefaults();
}
(if the above is used in an extensions it might need $this->owner->Date instead of $this->Date)
The above function isn't needed if all the values are static, instead it will read them just from this array (again within DataObject)
public static $defaults = array(
'Option1' => 'Value1',
'Option2' => 'Value2'
);
This works on any DataObject as well, but as SiteConfig manages one record and this populates that record once upon creation this is much more convenient for to use instead of requireDefaultRecords.
First of all thanks for looking into this.
I'm building a form to add categories to the db table and these categories can have a parent category (self referring). there is a dropdown to select the parent category. I'm using ZF2 and Doctrine 2 to build this form. Everything works fine but the only issue i have is that on the edit page, the parent category dropdown it shows the current category as well. I'd like to know how to exclude it from the dropdown. I'm posting some of my codes below. To keep it simple i removed some unrelated lines and shorten some names.
I defined the self referring relationship on the model
//Category model
use Doctrine\ORM\Mapping as ORM;
Class Category {
/**
*
* #var integer
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
.....
.....
/**
* Parent category if available
* #var self
* #ORM\OneToOne(targetEntity="Category")
* #ORM\JoinColumn(name="parent", referencedColumnName="id", nullable=true)
*/
protected $parent;
On the form I have a dropdown listing all the categories
$parent = new \DoctrineModule\Form\Element\ObjectSelect('parent');
$parent->setOptions(array(
'label' => 'Parent Category',
'class' => '',
'object_manager' => $this->em,
'target_class' => \Data\Entity\Category::class,
'property' => 'name',
'display_empty_item' => true,
'empty_item_label' => '- Select Category -',
'required' => false
))->setAttributes(array(
'class' => 'form-control'
));
On the edit controller i load the form and and bind it to the db entry
public function editAction()
{
//get id from url
$id = $this->params()->fromRoute('id', 0);
$request = $this->getRequest();
//load selected category from database
$category = $this->em->getRepository(\Data\Entity\Category::class)->find($id);
//create form
$form = new Form\Category($this->em);
//bind selected category to form
$form->bind($category);
......
}
Thanks.
You need to pass the category id of the category being edited to the form and set object selects search params to pass the id to the entity repository. You will then need to create a search query in the repository to exclude the category id from being returned in the search results.
You can pass the category id to the form with a simple setter.
protected $categoryId;
public function setCategoryId($categoryId)
{
$this->categoryId = $categoryId;
}
In your form you will need something like
$parent->setOptions(array(
'label' => 'Parent Category',
'class' => '',
'object_manager' => $this->em,
'target_class' => \Data\Entity\Category::class,
'property' => 'name',
'is_method' => true,
'find_method' => array(
'name' => 'findCategories',
'params' => array(
'searchParams' => array('id' => $this->categoryId),
),
),
'display_empty_item' => true,
'empty_item_label' => '- Select Category -',
'required' => false
))->setAttributes(array(
'class' => 'form-control'
));
and in your categories repository
public function findCategories($searchParams)
{
$builder = $this->getEntityManager()->createQueryBuilder();
$builder->select('c')
->from(\Data\Entity\Category::class, 'c')
->where('c.id != ?1')
->setParameter(1, $searchParams['id'])
->orderBy('c.category', 'ASC');
return $builder->getQuery()->getResult(Query::HYDRATE_OBJECT);
}
note the orderBy is optional.
I hope this makes sense.
So, disclaimer first: I'm a bit of a noob when it comes to SilverStripe, but this one is vexxing me.
I'm using GridField to add and edit the entries in a DataObject. This is all well and good, and works perfectly. The only thing I can't figure out is how to change the order of the EDITABLE fields - this isn't the initial table display of the entries (which is set by $config), it's the actual input fields once you click "add new" or go to edit a record.
At the moment the Image uploadForm and the Signature <select> box are below the Body HTMLText field, which is messy and doesn't work right. I want them up the top, right below the Summary element.
I've tried playing around with changeFieldOrder(), but that doesn't work on a GridField object type and $fields doesn't know anything about the input elements (I dump()'ed it and had a look).
MediaReleaseItem.php:
class MediaReleaseItem extends DataObject {
static $db = array (
'Title' => 'Varchar',
'DateUpdated' => 'Date',
'Summary' => 'Varchar',
'Image' => 'Varchar',
'Body' => 'HTMLText',
);
private static $has_one = array(
"Image" => "Image",
"MediaReleaseItem" => "MediaReleases",
"Signature" => "MediaReleaseSignature",
);
}
And MediaReleases.php:
class MediaReleases extends Page {
private static $has_many = array(
"MediaReleaseItems" => "MediaReleaseItem",
"Signature" => "MediaReleaseSignature",
);
function getCMSFields() {
$fields = parent::getCMSFields();
$config = GridFieldConfig_RecordEditor::create();
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'Title'=> 'Title',
'DateUpdated' => 'Date',
'Summary' => 'Summary',
));
$mediaReleasesField = new GridField(
'MediaReleaseItem', // Field name
'Media Releases', // Field title
$this->MediaReleaseItems(),
$config
);
$fields->addFieldToTab('Root.MediaReleaseItems', $mediaReleasesField);
return $fields;
}
}
(Signature is just another DataObject with a different GridField on a different tab, I didn't include the code for it because it's almost identical.)
so, you mean when you edit a MediaReleaseItem the fields are not the way you want then to be?
simple: just also define a method getCMSFields() on the class MediaReleaseItem.
<?php
class MediaReleaseItem extends DataObject {
private static $db = array (
'Title' => 'Varchar',
'DateUpdated' => 'Date',
'Summary' => 'Varchar',
'Image' => 'Varchar',
'Body' => 'HTMLText',
);
private static $has_one = array(
"Image" => "Image",
"MediaReleaseItem" => "MediaReleases",
"Signature" => "MediaReleaseSignature",
);
public function getCMSFields() {
$arrayOfSignatures = MediaReleaseSignature::get()->map()->toArray();
$fields = FieldList::create(array(
TextField::create('Title', 'Title for this Item'),
DateField::create('DateUpdated', 'Updated')->setConfig('showcalendar', true),
TextField::create('Image', 'Image'),
// not sure if it works to have both a DB field and a has_one with the same name
UploadField::create('ImageID', 'Image'),
DropdownField::create('Signature', 'Signature', $arrayOfSignatures),
// you can add more fields here
));
// but you can also add fields here
$fields->insertBefore(TextField::create('Summay', 'Summary'), 'DateUpdated');
$fields->push(HTMLEditorField::create('Body', 'Body Content'));
return $fields;
}
}
I'm trying to set custom values for Select Options using Form types and 'choice_list'.
Type:
->add('status', 'choice', array(
'constraints' => array(
new Assert\NotBlank(array('message' => 'Required field missing: status'))
),
'error_bubbling' => true,
'choice_list' => new StatusChoiceList()
StatusChoiceList file:
class StatusChoiceList extends LazyChoiceList
{
/**
* Loads the choice list
*
* Should be implemented by child classes.
*
* #return ChoiceListInterface The loaded choice list
*/
protected function loadChoiceList()
{
$array = array(
'Preview' => 'Preview',
'Hidden' => 'Hidden',
'Live' => 'Live'
);
$choices = new ChoiceList($array, $array);
return $choices;
}
}
the select tag have a wrong values 0,1,2 and a good labels
ChoiceList class used for choices of arbitrary data types. In your case you should use SimpleChoiceList instead. First parameter is an array with choices as keys and labels as values.
protected function loadChoiceList()
{
$array = array(
'Preview' => 'Preview',
'Hidden' => 'Hidden',
'Live' => 'Live'
);
$choices = new SimpleChoiceList($array);
return $choices;
}