I'm trying to create a very simple performance class that extends dataobject and has a date for one of the fields but for some reason if I fill it out when I press 'Add' it just hangs. I've checked in the Chrome inspector and it fires off the Ajax request but the request never returns anything, I've even tried leaving it for a few minutes and still nothing returns and nothing gets puts in the error log either.
If I leave the date blank and just fill out the text field it works fine, here is the code I'm using.
class Performance extends DataObject {
static $db = array(
'Title' => 'Varchar(255)',
'StartDate' => 'Date',
);
static $summary_fields = array(
'Title' => 'Title',
'Starts' => 'StartDate',
);
static $has_one = array(
'Production' => 'ProductionPage'
);
}
What's really weird is if I grab the ArticlePage class from the extending Silverstripe tutorial it works fine, it's just if I try and do it with a DataObject rather than a page that I run into this.
Would really appreciate any help with this, I've been struggling with it for hours now.
Try the Legacydatetimefields module: http://www.silverstripe.org/legacydatetimefields-module/
There was a change of how Silverstripe handles date and time in the latest version (2.4) which means some older code dealing with date and time doesn't work.
Hope this helps.
Related
Been pulling my hair out over this for a day and exhausted my google foo. I have inherited a Silverstripe 3.4 site that we have upgraded to 4.4. But something odd has been going on with certain images after running MigrateFilesTask.
I think this is something to do with a file being attached to an unversioned objects that are accessed via ModelAdmin. But I have not been able to find a definitive solution.
Code for this object below. Problems experienced are under it.
<?php
use SilverStripe\Assets\Image;
use gorriecoe\Link\Models\Link;
use SilverStripe\Security\Member;
use SilverStripe\Control\Controller;
use SilverStripe\View\Parsers\URLSegmentFilter;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\FieldGroup;
use gorriecoe\LinkField\LinkField;
use SilverStripe\TagField\TagField;
use SilverStripe\ORM\DataObject;
use SilverStripe\SelectUpload\SelectUploadField;
class Person extends DataObject
{
private static $db = array(
'FirstName' => 'Varchar(128)',
'LastName' => 'Varchar(128)',
'Role' => 'Varchar(128)',
'DirectDialNumber' => 'Varchar(128)',
'Email' => 'Varchar(128)',
'CellphoneNumber' => 'Varchar(30)',
'DirectDial' => 'Varchar(30)',
'UrlSegment' => 'Varchar(255)',
'Blurb' => 'HTMLText',
'SortOrder' => 'Int'
);
private static $has_one = array(
'Image' => Image::class,
'Office' => 'Office',
'LinkedIn' => Link::class,
'Member' => Member::class
);
private static $many_many = array(
'Interests' => 'Section'
);
private static $belongs_many_many = array(
'ElementCollection' => 'ElementCollection'
);
static $sort_fields = array(
'FirstName' => 'First name',
'LastName' => 'Last name',
'Role' => 'Role'
);
private static $summary_fields = array(
'Name' => 'Name',
'Role' => 'Role',
'Office.Name' => 'Office'
);
private static $searchable_fields = array(
'FirstName',
'LastName',
'Role'
);
// For use with the ElementCollection
public static $templates = array(
'ElementPeople' => 'Default',
'ElementPeopleAlternative' => 'Alternative'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeByName( ['SortOrder', 'ElementCollection', 'FirstName', 'LastName', 'Interests'] );
$firstname = TextField::create('FirstName', 'First name');
$lastname = TextField::create('LastName', 'Last name');
$fields->addFieldsToTab('Root.Main', FieldGroup::create($firstname, $lastname)->setTitle('Name')->setName('Name'), 'Role');
$image = UploadField::create('Image', 'Photo');
$image->setFolderName('Uploads/People');
$image->setCanSelectFolder(false);
$fields->addFieldToTab('Root.Main', $image);
$linkedin = LinkField::create('LinkedIn', 'LinkedIn', $this);
$fields->addFieldToTab('Root.Main', $linkedin);
$interests = TagField::create(
'Interests',
'Interests Tags',
Section::get(),
$this->Interests()
)->setShouldLazyLoad(true)
->setCanCreate(false);
$fields->addFieldToTab('Root.Main', $interests);
return $fields;
}
public function onBeforeWrite()
{
$count = 1;
$this->UrlSegment = $this->generateURLSegment();
while (!$this->validURLSegment()) {
$this->UrlSegment = preg_replace('/-[0-9]+$/', null, $this->UrlSegment) . '-' . $count;
$count++;
}
parent::onBeforeWrite();
}
}
Problem #1 is after running MigrateFileTask, ALL existing images attached to instances of this class get moved from /assets/Uploads/People to /assets/.protected/Uploads/People. The confusing part here is that there is one other class called Company that is structurally near identical, yet images for that remain in /assets/Uploads/Companies as expected.
Problem #2 is if I create a new Person object and attach an image, that image is in Draft, sitting in /assets/.protected/Uploads/People with no method of actually publishing it. Meanwhile, if I do the same with a Company object, the image is still in Draft, but I can see it in the CMS.
Can someone offer some guidance on the above? At this point I'd be happy to just be able for images to be published when the DO is and I'll manually go through every single Person record and hit save myself just to get this upgrade over the line.
You should be able to fix this issue by adding the image to your DataObejct's owns property. Basically add this:
private static $owns = [
'Image'
];
Basically owns tells a DataObject which objects to publish when it is saved:
More info in the docs: https://docs.silverstripe.org/en/4/developer_guides/model/versioning/#defining-ownership-between-related-versioned-dataobjects
The cause of issue #1 was found. Leaving this here in case it helps someone in future:
The database table File has a row for every File and Folder in the system. This table has a column called "CanViewType". It exists in both Silverstripe 3 and 4.
For the particular Folder that was causing trouble during the Migration process, I found it was the only one with that column set to "OnlyTheseUsers". The rest were set to "Inherit". This was the state of the table before the upgrade.
I'm unsure how or by what mechanism that row is ever changed, but the solution to problem #1 was to manually change that field to "Inherit" before running FileMigrationTask.
Issue #2 persists, but it looks like there are two very different issues here.
OK. So sorted problem #2 finally (see other answer for solution to #1), but it's put a massive dent in our confidence in Silverstripe and sparked a meeting here.
Code for future readers:
In your unversioned DataObject, add this. In this case, my file object is called "Image". If you had more than one file to publish on save, you would have to add one of these IF blocks for each.
public function onAfterWrite()
{
if ($this->Image()->exists() && !$this->Image()->isPublished()) {
$this->Image()->doPublish();
}
parent::onAfterWrite();
}
SIDENOTE:
This Object/File relationship really is a strange design choice. Are there actually any situations where you would want to attach a file or image to a data object and NOT publish that file at the same time as you save/publish the object/page? Developers even need to explicitly define that on Versioned objects using $owns - which I'm happy to bet that most developers have to add more times than NOT. Which should really tell a us something is around wrong way.
Adding an image to a CMS system shouldn't be hard. It should take reading basic docs at the most. Not Googling, deep API doc dives (which don't answer much) or posting on StackOverlfow (where no one really knows the answer) over three days. It's an image. A core function of the product.
I've been working with SS since v2.4 and seen all the hard lessons learned to get to v4. But this appears to be a textbook case of the simple being over-engineered.
I want to extend the DataObject (EditableDateField) of a module that I use (UserDefinedForms). There I want to overwrite a certain method (getFormField). I was trying to extend with DataExtension. But it does not work.
Here the code...
config.php:
EditableDateField::add_extension("CustomEditableDateField");
CustomEditableDateField:
class CustomEditableDateField extends DataExtension {
public function getFormField() {
//test is function called
echo 'test';
exit();
}
}
Also I tried to use Object::useCustomClass in the config to replace the whole EditableDateField with my CustomClass, but also no success.
What is the best way to do that?
Many thanx,
Florian
To do what you are wanting to achieve, you don't need to extend EditableDateField.
What you are looking for is EditableDateField_FormField which extends DateField. DateField does the heavy lifting for generating the HTML and doing the validation etc. One thing that DateField does have is a static variable called default_config which looks like this:
static $default_config = array(
'showcalendar' => false,
'jslocale' => null,
'dmyfields' => false,
'dmyseparator' => ' <span class="separator">/</span> ',
'dmyplaceholders' => true,
'dateformat' => null,
'datavalueformat' => 'yyyy-MM-dd',
'min' => null,
'max' => null,
);
Using the configuration system in Silverstripe, you can change the default dateformat to your dd.MM.yyyy format by using:
EditableDateField_FormField:
default_config:
'dateformat': 'dd.MM.yyyy'
You will want to use EditableDateField_FormField like my example above rather than change the default_config of DateField itself otherwise you may encounter issues in the CMS.
Technical Description
This works due to the constructor of DateField setting the instance config to the value of the default_config.
Because the value of dateformat is normally NULL in default_config, an if-statement passes which causes dateformat to be set the result from i18n::get_date_format().
So i have a GUI for a simple database made with Symfony2 + Sonata Admin Bundle + Doctrine 2. That database will hold billions of rows so the dates are stored as timestamps (to save space) but in the GUI displayed as dates (ex: 2013/10/17 10:05:06). Everything works in the GUI except the filtering by dates. I tried all sorts of configurations in the class that extends the Admin, method configureDatagridFilters(). I cannot make it to work... can you help me?
I found a work around that i think it can be useful in many other situations as well:
So in the class that extends Admin.php we have the method configureDatagridFilters(). There one can add an input like
->add('startDateFrom', 'doctrine_orm_callback', array(
'label' => 'Start Date from',
'callback' => function($queryBuilder, $alias, $field, $value) {
if (!$value['value']) {
return;
}
$inputValue = $this->dateStringToTimestamp($value['value']);
$queryBuilder->andWhere("{$alias}.startDate >= :startDateFrom");
$queryBuilder->setParameter('startDateFrom', $inputValue);
return true;
},
'field_type' => 'text'
), null, array('attr' => array('class' => 'calendar',
)))
As you can see, in this way we can manipulate the field value as we wish. I hope it will help others too :)
is what I get, everytime I use this,
$certpath = APP."Plugin".DS."PaypalIpn".DS."Controller".DS."Certificates".DS;
in my code, it ruins my css. Well, not necessarily ruins but my page is as if not using any css for its alignment or other stuff.
i need that DS since i have to call a file in that directory
$encryption['cert_file'] = $certpath.$encryption['cert_file'];
I tried this:
$encryption['key_file'] = $certpath.DS.$encryption['key_file'];
$encryption['cert_file'] = $certpath.DS.$encryption['cert_file'];
$encryption['paypal_cert_file'] = $certpath.DS.$encryption['paypal_cert_file'];
Still no luck. The first two lines work that way, but the third one doesn't.
And I tried googling with these keywords:
directory separator in cakephp ruins my css
uses of directory separator in cakephp
calling files using directory separator in cakephp
but none of it helped me.
DS is just a directory separator right? What could possibly be wrong?
.......... edited part
well this is another issue, I found the culprit from my previous issue but please do explain so how it is related to my problem. this is my PaypalHelper.php
<?php
$importConfig = array(
'type' => 'File',
'name' => 'PaypalIpn.ppcrypto',
//'file' => CONFIGS .'paypal_ipn_config.php'
'file' => APP."Plugin".DS."paypal_ipn".DS."libs".DS."ppcrypto.php"
);
//... other codes
//..other functions
function button($title, $options = array(), $buttonOptions = array()) {
//..other codes
$certpath = APP."Plugin".DS."PaypalIpn".DS."Controller".DS."Certificates".DS;
//..other codes
}
?>
see this code?
$importConfig = array(
'type' => 'File',
'name' => 'PaypalIpn.ppcrypto',
//'file' => CONFIGS .'paypal_ipn_config.php'
'file' => APP."Plugin".DS."paypal_ipn".DS."libs".DS."ppcrypto.php"
);
and this?
$certpath = APP."Plugin".DS."PaypalIpn".DS."Controller".DS."Certificates".DS;
$encryption['paypal_cert_file'] = $certpath.$encryption['paypal_cert_file'];
the $importConfig..it was wrong coz it should be
$importConfig = array(
'type' => 'File',
'name' => 'PaypalIpn.ppcrypto',
//'file' => CONFIGS .'paypal_ipn_config.php'
'file' => APP."Plugin".DS."PaypalIpn".DS."libs".DS."ppcrypto.php"
);
..but when i tried to correct it, $encryption['paypal_cert_file'] worked fine. It doesn't ruins/(disables??) my css
i put this as an answer first but i believe there are still questions raising in my head..
I am almost certain whatever is or was wrong is either a fatal error or some kind of other error output messing up your HTML output.
Sometimes even with debugging enabled the error isn't visible on the screen and you must check the HTML source, especially if the error occurs in the <head> section of the HTML, inside Javascript or before the closing > of a tag. I would suggest that when you fixed the error, it fixed your problem.
I would double check that you have debugging enabled. See CakePHP Core Configuration from the cake documentation.
If you have fixed the problem but still want to know what caused it, if possible, undo your changes and check what I have mentioned above. Other than that I don't see anything immediately obvious in your code which could have cause the problem.
I wanted to add a tab to the user edit page ie user/%/edit/foo, and was using the twitter module as a model. After much spelunking and stepping through with a debugger, I realised that I needed to add a hook_user function in my module so that the %user_category part of the menu router path would work.
It's now functioning as expected, but I don't really have a solid idea of what I just did, and haven't found a good explanation anywhere.
Can anyone explain to me what it's about?
user_category_loads fails if given a category that does not exist, which happens at user/uid/edit/not_a_category, and it passes that category for access checks to user/uid/edit/is_a_category and thus access to those is set to false, so bam, wonky menu :'(.
When you use %user_category it means that user_category_load function is called with that argument (the uid).
The function is defined in the user module. These functions serve as a validation, if False it return FALSE, it will result in a 404, but if it return something else, like a user object, that will be passed to whatever callback function / form is run for that url.
In your case, you could probably have used %user instead which would have called user_load which is more simple, and you wouldn't have needed to do all the extra stuff to make user_category_load pass.
Summary
So user_category_load does two things.
Check that the category exist so you just can't do user/%/edit/foo.
Caches the user object.
After much trial and error, I was able to get a page working as a child of the user/%/edit path using code like this:
<?php
/**
* Implementation of hook_menu().
*/
function noc_profile_privacy_menu() {
return array(
'user/%user_category/edit/privacy' => array(
'title' => 'Portfolio privacy',
'page callback' => 'drupal_get_form',
'page arguments' => array('noc_profile_privacy_form', 1),
'access callback' => 'content_profile_page_access',
'access arguments' => array('profile', 1),
'type' => MENU_LOCAL_TASK,
'load arguments' => array('%map', '%index'),
),
);
}
/**
* Implementation of hook_user().
*/
function noc_profile_privacy_user($op, &$edit, &$account, $category = NULL) {
if ($op === 'categories') {
return array(array(
'name' => 'privacy',
'title' => t('Profile privacy'),
'weight' => 0,
));
}
}
Note that the 'name' key of what I'm returning in hook_user() is the same as what comes after user/%user/category/edit in my hook_menu() definition. I believe that is key. You will also get an error if you omit the 'load arguments' item, with exactly that value.
So I believe what the user category is is the 'privacy' in my case - the bit after edit in the path of the menu item definition.
Is this an unnecessary complication? Yes, so it seems.
Edit: I see my co-worker hefox beat me to replying to this question. I wouldn't have been able to figure all of this out without Fox's help, so mad props to the Fox.