I am using the Silverstripe Swipestripe module for an online store. Because of the number of products the client has, it's not practical to nave them navigate to each individual product through the site tree when they want to make changes to a product (which happens fairly regularly) so I'd like to have a modeladmin to list all products and allow them to search for a product by name/stockcode.
I thought this would be solvable in the same way as DataObjects (and searches seem to suggest that people have done achieved this), however when I navigate to products in ModelAdmin view, I get:
Fatal Error : Call to a member function stat() on a non-object in /path/to/folder/wwwroot/framework/model/DataObject.php on line 3192
<?php
class ProductAdmin extends ModelAdmin
{
private static $managed_models = array('Product');
private static $url_segment = 'product';
private $menu_title = 'Products';
}
Interestingly, pages and other extensions of the page class do work.
Here is the code for the Product class:
class Product extends Page {
/**
* Flag for denoting if this is the first time this Product is being written.
*
* @var Boolean
*/
protected $firstWrite = false;
/**
* DB fields for Product.
*
* @var Array
*/
private static $db = array(
'Price' => 'Decimal(19,4)',
'Currency' => 'Varchar(3)',
'StockCode' => 'Varchar(255)',
'Stock' => 'Int',
'Featured' => 'Boolean',
'YouTubeID' => 'Varchar(255)'
);
/**
* Actual price in base currency, can decorate to apply discounts etc.
*
* @return Price
*/
public function Amount() {
// TODO: Multi currency
$shopConfig = ShopConfig::current_shop_config();
$amount = new Price();
$amount->setAmount($this->Price);
$amount->setCurrency($shopConfig->BaseCurrency);
$amount->setSymbol($shopConfig->BaseCurrencySymbol);
//Transform amount for applying discounts etc.
$this->extend('updateAmount', $amount);
return $amount;
}
/**
* Display price, can decorate for multiple currency etc.
*
* @return Price
*/
public function Price() {
$amount = $this->Amount();
//Transform price here for display in different currencies etc.
$this->extend('updatePrice', $amount);
return $amount;
}
/**
* Has many relations for Product.
*
* @var Array
*/
private static $has_many = array(
'Attributes' => 'Attribute',
'Options' => 'Option',
'Variations' => 'Variation'
);
/**
* Defaults for Product
*
* @var Array
*/
private static $defaults = array(
'ParentID' => -1,
'Stock' => 999
);
/**
* Summary fields for displaying Products in the CMS
*
* @var Array
*/
private static $summary_fields = array(
'Amount.Nice' => 'Price',
'Title' => 'Title'
);
private static $searchable_fields = array(
'Title' => array(
'field' => 'TextField',
'filter' => 'PartialMatchFilter',
'title' => 'Name'
)
);
/**
* Set firstWrite flag if this is the first time this Product is written.
*
* @see SiteTree::onBeforeWrite()
* @see Product::onAfterWrite()
*/
public function onBeforeWrite() {
parent::onBeforeWrite();
if (!$this->ID) $this->firstWrite = true;
//Save in base currency
$shopConfig = ShopConfig::current_shop_config();
$this->Currency = $shopConfig->BaseCurrency;
}
/**
* Unpublish products if they get deleted, such as in product admin area
*
* @see SiteTree::onAfterDelete()
*/
public function onAfterDelete() {
parent::onAfterDelete();
if ($this->isPublished()) {
$this->doUnpublish();
}
}
/**
* Set some CMS fields for managing Products
*
* @see Page::getCMSFields()
* @return FieldList
*/
public function getCMSFields() {
$shopConfig = ShopConfig::current_shop_config();
$fields = parent::getCMSFields();
//Product fields
$fields->addFieldToTab('Root.Main', new PriceField('Price'), 'Content');
$fields->addFieldToTab('Root.Main', new TextField('StockCode'), 'Price');
$fields->addFieldToTab('Root.Main', new TextField('Stock'), 'Price');
$fields->addFieldToTab('Root.Main', new CheckBoxField('Featured'), 'Content');
$fields->addFieldToTab('Root.Images', new TextField('YouTubeID', 'YouTube Video ID (Taken from the end of the video url. ie https://www.youtube.com/watch?v=ABC123 would be ABC123)'));
//Replace URL Segment field
if ($this->ParentID == -1) {
$urlsegment = new SiteTreeURLSegmentField("URLSegment", 'URLSegment');
$baseLink = Controller::join_links(Director::absoluteBaseURL(), 'product/');
$url = (strlen($baseLink) > 36) ? "..." .substr($baseLink, -32) : $baseLink;
$urlsegment->setURLPrefix($url);
$fields->replaceField('URLSegment', $urlsegment);
}
if ($this->isInDB()) {
//Product attributes
$listField = new GridField(
'Attributes',
'Attributes',
$this->Attributes(),
GridFieldConfig_BasicSortable::create()
);
$fields->addFieldToTab('Root.Attributes', $listField);
//Product variations
$attributes = $this->Attributes();
if ($attributes && $attributes->exists()) {
//Remove the stock level field if there are variations, each variation has a stock field
$fields->removeByName('Stock');
$variationFieldList = array();
foreach ($attributes as $attribute) {
$variationFieldList['AttributeValue_'.$attribute->ID] = $attribute->Title;
}
$variationFieldList = array_merge($variationFieldList, singleton('Variation')->summaryFields());
$config = GridFieldConfig_HasManyRelationEditor::create();
$dataColumns = $config->getComponentByType('GridFieldDataColumns');
$dataColumns->setDisplayFields($variationFieldList);
$listField = new GridField(
'Variations',
'Variations',
$this->Variations(),
$config
);
$fields->addFieldToTab('Root.Variations', $listField);
}
}
//Ability to edit fields added to CMS here
$this->extend('updateProductCMSFields', $fields);
if ($warning = ShopConfig::base_currency_warning()) {
$fields->addFieldToTab('Root.Main', new LiteralField('BaseCurrencyWarning',
'<p class="message warning">'.$warning.'</p>'
), 'Title');
}
return $fields;
}
/**
* Get the URL for this Product, products that are not part of the SiteTree are
* displayed by the {@link Product_Controller}.
*
* @see SiteTree::Link()
* @see Product_Controller::show()
* @return String
*/
public function Link($action = null) {
if ($this->ParentID > -1) {
return parent::Link($action);
}
return Controller::join_links(Director::baseURL() . 'product/', $this->RelativeLink($action));
}
/**
* A product is required to be added to a cart with a variation if it has attributes.
* A product with attributes needs to have some enabled {@link Variation}s
*
* @return Boolean
*/
public function requiresVariation() {
$attributes = $this->Attributes();
return $attributes && $attributes->exists();
}
/**
* Get options for an Attribute of this Product.
*
* @param Int $attributeID
* @return ArrayList
*/
public function getOptionsForAttribute($attributeID) {
$options = new ArrayList();
$variations = $this->Variations();
if ($variations && $variations->exists()) foreach ($variations as $variation) {
if ($variation->isEnabled()) {
$option = $variation->getOptionForAttribute($attributeID);
if ($option) $options->push($option);
}
}
$options = $options->sort('SortOrder');
return $options;
}
/**
* Validate the Product before it is saved in {@link ShopAdmin}.
*
* @see DataObject::validate()
* @return ValidationResult
*/
public function validate() {
$result = new ValidationResult();
//If this is being published, check that enabled variations exist if they are required
$request = Controller::curr()->getRequest();
$publishing = ($request && $request->getVar('action_publish')) ? true : false;
if ($publishing && $this->requiresVariation()) {
$variations = $this->Variations();
if (!in_array('Enabled', $variations->map('ID', 'Status')->toArray())) {
$result->error(
'Cannot publish product when no variations are enabled. Please enable some product variations and try again.',
'VariationsDisabledError'
);
}
}
return $result;
}
}
Can anyone suggest what I'm doing wrong here or an alternative way to do what I'm trying to achieve?
Cheers
Just in case anyone still having the same problem. So, it happens if you have installed Product Categories module for swipestripe. The cause of this is private static $searchable_fields in ProductCategory_Extension class in ProductCategory.php file in that module. Just comment out that field and it will work. It is because the dataobject class tries to stat Category as a class - which obviously doesnt exist.
I will fix it and push to github if I get some time. Just wanted to update here so that others dont waste time scratching head why it doesnt work.