Skip to main content

This site requires you to update your browser. Your browsing experience maybe affected by not having the most up to date version.

We've moved the forum!

Please use forum.silverstripe.org for any new questions (announcement).
The forum archive will stick around, but will be read only.

You can also use our Slack channel or StackOverflow to ask for help.
Check out our community overview for more options to contribute.

E-Commerce Modules /

Discuss about the various e-commerce modules available:
Ecommerce, SS Shop, SilverCart and SwipeStripe
Alternatively, have a look the shared mailinglist.

Moderators: martimiz, Nicolaas, Sean, Ed, frankmullenger, biapar, Willr, Ingo, Jedateach, swaiba

Swipestripe related Products?


Go to End


9 Posts   3823 Views

Avatar
helenclarko

Community Member, 166 Posts

28 January 2015 at 1:59pm

Edited: 29/01/2015 9:05am

Hi Araiko,

here are my files for reference.
A bunch of the stuff inside these files will be of no use to you i'm sure, but this should hopefully help to point you in the right direction.

Full product.php

<?php
/**
 * Represents a Product, which is a type of a {@link Page}. Products are managed in a seperate
 * admin area {@link ShopAdmin}. A product can have {@link Variation}s, in fact if a Product
 * has attributes (e.g Size, Color) then it must have Variations. Products are Versioned so that
 * when a Product is added to an Order, then subsequently changed, the Order can get the correct
 * details about the Product.
 */
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
	 */
	public static $db = array(
		'Price' => 'Decimal(19,8)',
		'Currency' => 'Varchar(3)'
	);
	/**
	 * 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 = Price::create();
		$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(
		'Images' => 'ProductImage',
		'Attributes' => 'Attribute',
		'Options' => 'Option',
		'Variations' => 'Variation',
		'ProductDropDowns' => 'ProductDropDowns',
		'Testimonials' => 'Testimonials'
	);
	
	private static $many_many = array(
		'RelatedProducts' => 'Product'
	);
	
	/**
	 * Defaults for Product
	 * 
	 * @var Array
	 */
	private static $defaults = array(
		'ParentID' => -1
	);
	
	/**
	 * Summary fields for displaying Products in the CMS
	 * 
	 * @var Array
	 */
	private static $summary_fields = array(
		'FirstImage' => 'Image',
		'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();
		
		
		//Gallery
		$gridFieldConfig = GridFieldConfig::create()->addComponents(
		  new GridFieldToolbarHeader(),
		  new GridFieldAddNewButton('toolbar-header-right'),
		  new GridFieldSortableHeader(),
		  new GridFieldDataColumns(),
		  new GridFieldPaginator(10),
		  new GridFieldEditButton(),
		  new GridFieldDeleteAction(),
		  new GridFieldDetailForm()
		);
		
		$manager = new GridField("Gallery", "Images list:", $this->Images(), $gridFieldConfig);
		$fields->addFieldToTab("Root.Gallery", new HeaderField(
			'GalleryHeading', 
			'Add images for this product, the first image will be used as a thumbnail',
		  3
		));
		$fields->addFieldToTab("Root.Gallery", $manager);

		//Product fields
		$fields->addFieldToTab('Root.Main', new PriceField('Price'), 'Content');
		
		//Related Products
		$gridFieldComplex = GridFieldConfig::create()->addComponents(
		  new GridFieldToolbarHeader(),
		  new GridFieldSortableHeader(),
		  new GridFieldDataColumns(),
		  new GridFieldManyRelationHandler(),
		  new GridFieldPaginator(10),
		  new GridFieldEditButton(),
		  new GridFieldDeleteAction(),
		  new GridFieldDetailForm()
		);
		
		$tablefield = new GridField(
            'RelatedProducts',
            'Related Products:',
			$this->RelatedProducts(),
            $gridFieldComplex
        );
        $fields->addFieldToTab('Root.RelatedProducts', $tablefield);
		
		//Testimonials
		$testimonialTable = new GridField(
            'Testimonials',
            'Testimonials:',
			$this->Testimonials(),
			$gridFieldConfig
        );   
        $fields->addFieldToTab('Root.Testimonials', $testimonialTable);
		
		//Expanding content area
		$contentTable = new GridField(
            'ProductDropDowns',
            'Expanding Content:',
            $this->ProductDropDowns(),
			$gridFieldConfig
        ); 
        $fields->addFieldToTab('Root.ExpandingContent', $contentTable);

		//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();
		
		$this->extend('updaterequiresVariation', $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;
	}

}

/**
 * Displays a product, add to cart form, gets options and variation price for a {@link Product} 
 * via AJAX.
 * 
 * @author Frank Mullenger <frankmullenger@gmail.com>
 * @copyright Copyright (c) 2011, Frank Mullenger
 * @package swipestripe
 * @subpackage product
 */
class Product_Controller extends Page_Controller {
	
	/**
	 * Allowed actions for this controller
	 * 
	 * @var Array
	 */
	private static $allowed_actions = array(
		'ProductForm'
	);
	
	/**
	 * Include some CSS and set the dataRecord to the current Product that is being viewed.
	 * 
	 * @see Page_Controller::init()
	 */
	public function init() {
		parent::init();
		
		Requirements::css('swipestripe/css/Shop.css');
		
		//Get current product page for products that are not part of the site tree
		//and do not have a ParentID set, they are accessed via this controller using
		//Director rules
		if ($this->dataRecord->ID == -1) {
			
			$params = $this->getURLParams();
			
			if ($urlSegment = Convert::raw2sql($params['ID'])) {

				$product = Product::get()
					->where("\"URLSegment\" = '$urlSegment'")
					->limit(1)
					->first();
				
				if ($product && $product->exists()) {
					$this->dataRecord = $product; 
					$this->failover = $this->dataRecord;
					
					$this->customise(array(
						'Product' => $this->data()
					));
				}
			}
		}
		
		$this->extend('onInit');
	}
	
	/**  
	 * Legacy function allowing access to product data via $Product variable in templates
	 */
	public function Product() {
		return $this->data();
	}

	public function ProductForm($quantity = null, $redirectURL = null) {

		return ProductForm::create(
			$this,
			'ProductForm',
			$quantity,
			$redirectURL
		)->disableSecurityToken();
	}
}

I have however got a different file which extends the page controller.

But if you add the following under class "Product_Controller extends Page_Controller" you should be fine:

Controller snippet

	function RelatedProductsLimit($limit = '') {
		return $this->owner->RelatedProducts("ProductID = " . $this->owner->ID, "", NUll, $limit);
	} 

This may have been where I previously messed up. I have told you to put both of these functions into the same class.
The second controller snippet needs to go into the controller class 3/4 of the way down product.php.

Then the last step is to add the code to the .ss file.

Mine looks like this:

<% if RelatedProducts %>
				<div id="relatedProductsWrap">
					<div id="relatedTitle">
						Related Products
					</div>
					<div id="related">
						<% loop RelatedProductsLimit(4) %>
							<a href="$Link" alt="Find out more about $Title" title="Find out more about $Title" class="eachRelated" <% if Last %>style="margin-right:0px;"<% end_if %> >
								<% loop Images.First %>
									$Image.SetRatioSize(160,180)
								<% end_loop %>
								<h4>$MenuTitle</h4>
								$Amount.Nice
							</a>
						<% end_loop %>
						
						<div class="clear"></div>
					</div>
				</div>
			<% end_if %>

of course, you will need to style this with css, but hopefully this solves the problem.

Go to Top