diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3dec0e6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+*.swp
+*.swo
diff --git a/Plugin.php b/Plugin.php
new file mode 100644
index 0000000..8106bfc
--- /dev/null
+++ b/Plugin.php
@@ -0,0 +1,116 @@
+alias('Cart', '\Gloudemans\Shoppingcart\Facades\Cart');
+ }
+
+ /**
+ * Returns information about this plugin.
+ *
+ * @return array
+ */
+ public function pluginDetails()
+ {
+ return [
+ 'name' => 'Shop',
+ 'description' => 'No description provided yet...',
+ 'author' => 'Dave Shoreman',
+ 'icon' => 'icon-shopping-cart'
+ ];
+ }
+
+ public function registerComponents()
+ {
+ return [
+ 'DShoreman\Shop\Components\Basket' => 'shopBasket',
+ 'DShoreman\Shop\Components\Categories' => 'shopCategories',
+ 'DShoreman\Shop\Components\Product' => 'shopProduct',
+ 'DShoreman\Shop\Components\Products' => 'shopProducts',
+ ];
+ }
+
+ public function registerNavigation()
+ {
+ return [
+ 'shop' => [
+ 'label' => 'Shop',
+ 'url' => Backend::url('dshoreman/shop/products'),
+ 'icon' => 'icon-shopping-cart',
+ 'permissions' => ['dshoreman.shop.*'],
+ 'order' => 300,
+
+ 'sideMenu' => [
+ 'products' => [
+ 'label' => 'Products',
+ 'url' => Backend::url('dshoreman/shop/products'),
+ 'icon' => 'icon-gift',
+ 'permissions' => ['dshoreman.shop.access_products']
+ ],
+ 'categories' => [
+ 'label' => 'Categories',
+ 'url' => Backend::url('dshoreman/shop/categories'),
+ 'icon' => 'icon-list-ul',
+ 'permissions' => ['dshoreman.shop.access_categories'],
+ ],
+ 'orders' => [
+ 'label' => 'Orders',
+ 'url' => Backend::url('dshoreman/shop/orders'),
+ 'icon' => 'icon-gbp',
+ 'permissions' => ['dshoreman.shop.access_orders'],
+ ],
+ ],
+ ],
+ ];
+ }
+
+ public function registerFormWidgets()
+ {
+ return [
+ 'Dshoreman\Shop\FormWidgets\ItemGrid' => [
+ 'label' => 'Order Item Grid',
+ 'alias' => 'itemgrid',
+ ],
+ ];
+ }
+
+ public function registerPermissions()
+ {
+ return [
+ 'dshoreman.shop.access_products' => ['label' => "Manage the shop's products"],
+ 'dshoreman.shop.access_categories' => ['label' => "Manage the shop categories"],
+ 'dshoreman.shop.access_orders' => ['label' => "Manage the shop orders"],
+ ];
+ }
+
+ public function registerSettings()
+ {
+ return [
+ 'settings' => [
+ 'label' => 'Stripe Settings',
+ 'description' => 'Manage your Stripe API keys.',
+ 'category' => 'Shop',
+ 'icon' => 'icon-credit-card',
+ 'class' => 'Dshoreman\Shop\Models\Settings',
+ 'order' => 200,
+ ],
+ ];
+ }
+
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8a4371e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+# Octoshop - A Shop Plugin for October CMS
+
+Pretty much does what it says on the tin. Octoshop adds all the basic functionality
+needed to sell products from your website running on October CMS. Still in its infancy,
+it's fairly barebones right now, though the process from browsing to buying is already
+covered. Payments are powered by Stripe JS for now, letting me spend more time worrying
+about the things that matter most.
+
+
+## Demo
+
+Frontend: http://octoshop.demo.dsdev.io/
+Backend: http://octoshop.demo.dsdev.io/backend/
+
+Username and password for the backend are both `admin`.
+
+
+## Documentation
+
+Not yet... Check out the demo, browse the source, and have a gander at the optional
+[theme](https://github.com/dshoreman/octoshop-theme) if you want to play about.
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..6e8bf73
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.1.0
diff --git a/components/Basket.php b/components/Basket.php
new file mode 100644
index 0000000..1e8ef49
--- /dev/null
+++ b/components/Basket.php
@@ -0,0 +1,204 @@
+ 'Basket Component',
+ 'description' => 'No description provided yet...'
+ ];
+ }
+
+ public function defineProperties()
+ {
+ return [
+ 'paymentPage' => [
+ 'title' => 'Checkout Page',
+ 'description' => 'Name of the page to redirect to when a user clicks Proceed to Checkout.',
+ 'default' => 'checkout/payment',
+ 'group' => 'Links',
+ ],
+ 'productPage' => [
+ 'title' => 'Product Page',
+ 'description' => 'Name of the product page for the product titles. This property is used by the default component partial.',
+ 'type' => 'dropdown',
+ 'default' => 'shop/product',
+ 'group' => 'Links',
+ ],
+ 'basketComponent' => [
+ 'title' => 'Basket Component',
+ 'description' => 'Component to use when adding products to basket',
+ 'default' => 'shopBasket',
+ ],
+ 'basketPartial' => [
+ 'title' => 'Basket Partial',
+ 'description' => 'Partial to use when adding products to basket',
+ 'default' => 'shopBasket::default',
+ ],
+ 'tableClass' => [
+ 'title' => 'Table',
+ 'group' => 'CSS Classes',
+ ],
+ 'nameColClass' => [
+ 'title' => 'Name Column',
+ 'group' => 'CSS Classes',
+ ],
+ 'qtyColClass' => [
+ 'title' => 'Quaantity Column',
+ 'group' => 'CSS Classes',
+ ],
+ 'priceColClass' => [
+ 'title' => 'Price Column',
+ 'group' => 'CSS Classes',
+ ],
+ 'subtotalColClass' => [
+ 'title' => 'Subtotal Column',
+ 'group' => 'CSS Classes',
+ ],
+ 'totalLabelClass' => [
+ 'title' => 'Total Label',
+ 'group' => 'CSS Classes',
+ 'description' => 'Class given to the cell containing the "Total" label.',
+ ],
+ ];
+ }
+
+ public function getProductPageOptions()
+ {
+ return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
+ }
+
+ public function onRun()
+ {
+ $this->prepareVars();
+ }
+
+ public function onRender()
+ {
+ $this->prepareVars('render');
+ }
+
+ public function prepareVars($on = 'run')
+ {
+ if ($on == 'run') {
+ $this->registerBasketInfo();
+ $this->registerPages();
+
+ $this->basketComponent = $this->page['basketComponent'] = $this->property('basketComponent');;
+ $this->basketPartial = $this->page['basketPartial'] = $this->property('basketPartial');;
+ }
+
+ if ($on == 'render') {
+ $this->registerClasses();
+ }
+
+ if (Session::has('orderId')) {
+ $this->orderId = $this->page['orderId'] = Session::get('orderId');
+ }
+ }
+
+ public function registerBasketInfo()
+ {
+ $this->basketCount = $this->page['basketCount'] = Cart::count();
+ $this->basketItems = $this->page['basketItems'] = Cart::content();
+ $this->basketTotal = $this->page['basketTotal'] = Cart::total();
+ }
+
+ public function registerPages()
+ {
+ $this->paymentPage = $this->page['paymentPage'] = $this->property('paymentPage');
+ $this->productPage = $this->page['productPage'] = $this->property('productPage');
+ }
+
+ public function registerClasses()
+ {
+ $this->tableClass = $this->page['tableClass'] = $this->propertyOrParam('tableClass');
+ $this->nameColClass = $this->page['nameColClass'] = $this->property('nameColClass');
+ $this->qtyColClass = $this->page['qtyColClass'] = $this->property('qtyColClass');
+ $this->priceColClass = $this->page['priceColClass'] = $this->property('priceColClass');
+ $this->subtotalColClass = $this->page['subtotalColClass'] = $this->property('subtotalColClass');
+ $this->totalLabelClass = $this->page['totalLabelClass'] = $this->property('totalLabelClass');
+ }
+
+ public function onAddProduct()
+ {
+ $id = post('id');
+ $quantity = post('quantity') ?: 1;
+
+ $product = ShopProduct::find($id);
+
+ Cart::add($id, $product->title, $quantity, $product->price);
+
+ $this->registerBasketInfo();
+ }
+
+ public function onRemoveProduct()
+ {
+ Cart::remove(post('row_id'));
+
+ $this->registerBasketInfo();
+
+ return [
+ 'total' => $this->basketTotal,
+ 'count' => $this->basketCount,
+ ];
+ }
+
+ public function onCheckout()
+ {
+ $this->prepareVars();
+
+ $this->stripMissingItems();
+
+ if ($this->items_removed > 0) {
+
+ $removed_many = $this->items_removed > 1;
+
+ Flash::error(sprintf(
+ "%d %s couldn't be found and %s removed automatically. Please checkout again.",
+ $this->items_removed,
+ ($removed_many ? 'item' : 'items'),
+ ($removed_many ? 'were' : 'was')
+ ));
+
+ return Redirect::back();
+ }
+
+ $order = new ShopOrder;
+ $order->items = json_encode(Cart::content()->toArray());
+ $order->total = Cart::total();
+ $order->save();
+
+ Session::put('orderId', $order->id);
+
+ return Redirect::to($this->paymentPage);
+ }
+
+ protected function stripMissingItems()
+ {
+ $this->items_removed = 0;
+
+ foreach (Cart::content() as $item)
+ {
+ if ( ! ShopProduct::find($item->id)) {
+ Cart::remove($item->rowid);
+
+ $this->items_removed++;
+ }
+ }
+
+ return;
+ }
+
+}
diff --git a/components/Categories.php b/components/Categories.php
new file mode 100644
index 0000000..cc74a8f
--- /dev/null
+++ b/components/Categories.php
@@ -0,0 +1,87 @@
+ 'Shop Category List',
+ 'description' => 'Displays a list of shop categories on the page.',
+ ];
+ }
+
+ public function defineProperties()
+ {
+ return [
+ 'categoryPage' => [
+ 'title' => 'Category page',
+ 'description' => 'Name of the page file to use for the category links. This property is used by the default component partial.',
+ 'type' => 'dropdown',
+ 'default' => 'shop/category',
+ 'group' => 'Links',
+ ],
+ 'showItemCount' => [
+ 'title' => 'Show Item Count',
+ 'description' => 'Whether or not to add the number of child products next to category titles when generating the menu.',
+ 'type' => 'checkbox',
+ 'default' => '',
+ ],
+ 'itemCountClass' => [
+ 'title' => 'Item Count class',
+ 'description' => 'The classes to apply to the optional product count, shown next to the category titles in menus.',
+ 'group' => 'Styles',
+ ],
+ 'listClass' => [
+ 'title' => 'List class',
+ 'description' => 'The classes to apply to the
element in the default partial.',
+ 'default' => '',
+ 'group' => 'Styles',
+ ],
+ 'listItemClass' => [
+ 'title' => 'List item class',
+ 'description' => 'The classes to apply to child elements in the default partial.',
+ 'default' => '',
+ 'group' => 'Styles',
+ ],
+ ];
+ }
+
+ public function getCategoryPageOptions()
+ {
+ return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
+ }
+
+ public function onRun()
+ {
+ $this->prepareVars();
+
+ $this->categories = $this->page['categories'] = $this->listCategories();
+ }
+
+ public function prepareVars()
+ {
+ $this->categoryPage = $this->page['categoryPage'] = $this->property('categoryPage');
+ $this->showItemCount = $this->page['showItemCount'] = $this->property('showItemCount');
+ $this->itemCountClass = $this->page['itemCountClass'] = $this->property('itemCountClass');
+ $this->listClass = $this->page['listClass'] = $this->property('listClass');
+ $this->listItemClass = $this->page['listItemClass'] = $this->property('listItemClass');
+ }
+
+ public function listCategories()
+ {
+ $categories = ShopCategory::orderBy('sort_order')->get();
+
+ $categories->each(function($category)
+ {
+ $category->setUrl($this->categoryPage, $this->controller);
+ });
+
+ return $categories;
+ }
+
+}
diff --git a/components/Product.php b/components/Product.php
new file mode 100644
index 0000000..6f6cb29
--- /dev/null
+++ b/components/Product.php
@@ -0,0 +1,143 @@
+ 'Shop Product',
+ 'description' => 'Display a single product',
+ ];
+ }
+
+ public function defineProperties()
+ {
+ return [
+ 'idParam' => [
+ 'title' => 'Slug',
+ 'default' => ':slug',
+ 'type' => 'string',
+ ],
+ 'basketContainer' => [
+ 'title' => 'Basket container element',
+ 'description' => 'Basket container element to update when adding products to cart',
+ ],
+ 'addButtonText' => [
+ 'title' => 'Buy button text',
+ ],
+ 'mainImageSize' => [
+ 'title' => 'Main Image Size',
+ ],
+ 'subImageSize' => [
+ 'title' => 'Secondary Image Size',
+ ],
+ 'rowClass' => [
+ 'title' => 'Row',
+ 'group' => 'CSS Classes',
+ 'default' => 'row',
+ ],
+ 'imageClass' => [
+ 'title' => 'Image',
+ 'group' => 'CSS Classes',
+ ],
+ 'imageLinkClass' => [
+ 'title' => 'Image Link',
+ 'group' => 'CSS Classes',
+ ],
+ 'mainImageContainerClass' => [
+ 'title' => 'Main Image Container',
+ 'group' => 'CSS Classes',
+ ],
+ 'subImageContainerClass' => [
+ 'title' => 'Secondary Image Containers',
+ 'group' => 'CSS Classes',
+ ],
+ 'imagesetContainerClass' => [
+ 'title' => 'Imageset Container',
+ 'group' => 'CSS Classes',
+ ],
+ 'detailContainerClass' => [
+ 'title' => 'Detail container',
+ 'group' => 'CSS Classes',
+ ],
+ 'priceClass' => [
+ 'title' => 'Price',
+ 'group' => 'CSS Classes',
+ ],
+ 'priceContainerClass' => [
+ 'title' => 'Price container',
+ 'group' => 'CSS Classes',
+ ],
+ 'qtyClass' => [
+ 'title' => 'Quantity',
+ 'group' => 'CSS Classes',
+ ],
+ 'qtyWrapperClass' => [
+ 'title' => 'Quantity Wrapper',
+ 'group' => 'CSS Classes',
+ ],
+ 'qtyLabelClass' => [
+ 'title' => 'Quantity Label',
+ 'group' => 'CSS Classes',
+ ],
+ 'qtyContainerClass' => [
+ 'title' => 'Quantity container',
+ 'group' => 'CSS Classes',
+ ],
+ 'addButtonClass' => [
+ 'title' => 'Buy button',
+ 'group' => 'CSS Classes',
+ ],
+ 'addButtonContainerClass' => [
+ 'title' => 'Buy button container',
+ 'group' => 'CSS Classes',
+ ],
+ ];
+ }
+
+ public function onRun()
+ {
+ $this->prepareVars();
+
+ $this->product = $this->page['product'] = $this->loadProduct();
+ }
+
+ public function prepareVars()
+ {
+ $this->basketContainer = $this->page['basketContainer'] = $this->property('basketContainer');
+ $this->addButtonText = $this->page['addButtonText'] = $this->property('addButtonText');
+ $this->rowClass = $this->page['rowClass'] = $this->property('rowClass');
+ $this->imageClass = $this->page['imageClass'] = $this->property('imageClass');
+ $this->imageLinkClass = $this->page['imageLinkClass'] = $this->property('imageLinkClass');
+ $this->mainImageSize = $this->page['mainImageSize'] = $this->property('mainImageSize');
+ $this->mainImageContainerClass = $this->page['mainImageContainerClass'] = $this->property('mainImageContainerClass');
+ $this->subImageSize = $this->page['subImageSize'] = $this->property('subImageSize');
+ $this->subImageContainerClass = $this->page['subImageContainerClass'] = $this->property('subImageContainerClass');
+ $this->imagesetContainerClass = $this->page['imagesetContainerClass'] = $this->property('imagesetContainerClass');
+ $this->detailContainerClass = $this->page['detailContainerClass'] = $this->property('detailContainerClass');
+ $this->qtyClass= $this->page['qtyClass'] = $this->property('qtyClass');
+ $this->qtyWrapperClass= $this->page['qtyWrapperClass'] = $this->property('qtyWrapperClass');
+ $this->qtyLabelClass= $this->page['qtyLabelClass'] = $this->property('qtyLabelClass');
+ $this->qtyContainerClass = $this->page['qtyContainerClass'] = $this->property('qtyContainerClass');
+ $this->priceClass= $this->page['priceClass'] = $this->property('priceClass');
+ $this->priceContainerClass = $this->page['priceContainerClass'] = $this->property('priceContainerClass');
+ $this->addButtonContainerClass = $this->page['addButtonContainerClass'] = $this->property('addButtonContainerClass');
+ $this->addButtonClass = $this->page['addButtonClass'] = $this->property('addButtonClass');
+ }
+
+ public function loadProduct()
+ {
+ $productId = $this->propertyOrParam('idParam');
+
+ return ShopProduct::whereSlug($productId)->with(['images' => function($query)
+ {
+ $query->orderBy('sort_order', 'asc');
+ }])->first();
+ }
+
+}
diff --git a/components/Products.php b/components/Products.php
new file mode 100644
index 0000000..a813a73
--- /dev/null
+++ b/components/Products.php
@@ -0,0 +1,89 @@
+ 'Shop Product List',
+ 'description' => 'Display products from a given category',
+ ];
+ }
+
+ public function defineProperties()
+ {
+ return array_merge(parent::defineProperties(), [
+ 'categoryFilter' => [
+ 'title' => 'Category filter',
+ 'description' => 'Enter a category slug or URL parameter to filter the posts by. Leave empty to show all posts.',
+ 'type' => 'string',
+ 'default' => ''
+ ],
+ 'productPage' => [
+ 'title' => 'Product Page',
+ 'description' => 'Name of the product page for the product titles. This property is used by the default component partial.',
+ 'type' => 'dropdown',
+ 'default' => 'shop/product',
+ 'group' => 'Links',
+ ],
+ 'productColumnClass' => [
+ 'title' => 'Product column',
+ 'group' => 'CSS Classes',
+ ],
+ ]);
+ }
+
+ public function getProductPageOptions()
+ {
+ return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
+ }
+
+ public function onRun()
+ {
+ $this->prepareVars();
+
+ if ($this->category) {
+ $this->products = $this->page['products'] = $this->listProducts();
+ }
+ }
+
+ public function prepareVars()
+ {
+ parent::prepareVars();
+
+ $this->productPage = $this->page['productPage'] = $this->property('productPage');
+ $this->category = $this->page['category'] = $this->loadCategory();
+
+ $this->productColumnClass = $this->page['productColumnClass'] = $this->property('productColumnClass');
+ }
+
+ public function loadCategory()
+ {
+ if (!$categoryId = $this->propertyOrParam('categoryFilter'))
+ return null;
+
+ if (!$category = ShopCategory::whereSlug($categoryId))
+ return null;
+
+ return $category->first();
+ }
+
+ public function listProducts()
+ {
+ $products = ShopProduct::whereCategoryId($this->category->id)->get();
+
+ $products->each(function($product)
+ {
+ $product->setUrl($this->productPage, $this->controller);
+ });
+
+ return $products;
+ }
+
+}
diff --git a/components/basket/default.htm b/components/basket/default.htm
new file mode 100644
index 0000000..0d63a63
--- /dev/null
+++ b/components/basket/default.htm
@@ -0,0 +1,35 @@
+
+
+
+
+ Product
+ Qty.
+ Price
+ Subtotal
+
+
+
+ {% for item in basketItems %}
+
+
+
+
+
+
+ {{ item.name }}
+ {{ item.qty }}
+ {{ item.price }}
+ {{ item.subtotal }}
+
+ {% endfor %}
+
+
+ Total:
+
+ £{{ basketTotal }}
+
+
+
diff --git a/components/categories/default.htm b/components/categories/default.htm
new file mode 100644
index 0000000..8254ead
--- /dev/null
+++ b/components/categories/default.htm
@@ -0,0 +1,17 @@
+
diff --git a/components/product/default.htm b/components/product/default.htm
new file mode 100644
index 0000000..a57bce8
--- /dev/null
+++ b/components/product/default.htm
@@ -0,0 +1,59 @@
+
+
+
+ {% for image in product.images %}
+ {% if image.sort_order == '1' %}
+ {% set imageContainerClass = mainImageContainerClass %}
+ {% set imageSize = mainImageSize %}
+ {% else %}
+ {% set imageContainerClass = subImageContainerClass %}
+ {% set imageSize = subImageSize %}
+ {% endif %}
+
+
+ {% endfor %}
+
+
+
+
+
{{ product.title }}
+
+ {{ product.description|raw }}
+
+
+
+
+
diff --git a/components/products/default.htm b/components/products/default.htm
new file mode 100644
index 0000000..b61d687
--- /dev/null
+++ b/components/products/default.htm
@@ -0,0 +1,7 @@
+
+ {% for product in products %}
+
+ {% partial 'shopProducts::product' product=product %}
+
+ {% endfor %}
+
diff --git a/components/products/product.htm b/components/products/product.htm
new file mode 100644
index 0000000..9a50f69
--- /dev/null
+++ b/components/products/product.htm
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..343c0ed
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,17 @@
+{
+ "name": "dshoreman/shop",
+ "description": "Standalone eCommerce plugin for October CMS",
+ "homepage": "http://dsdev.io/",
+ "keywords": ['october', 'ecommerce', 'shop', 'stripe', 'product', 'plugin'],
+ "authors": [
+ {
+ "name": "Dave Shoreman",
+ "email": "code@dsdev.io"
+ }
+ ],
+ "require": {
+ "php": ">=5.4.0",
+ "gloudemans/shoppingcart": "~1.2",
+ "stripe/stripe-php": "dev-master"
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..cc0f0ea
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,161 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "hash": "fd3287e12c88ec7cdc32cea7e986b9c7",
+ "packages": [
+ {
+ "name": "gloudemans/shoppingcart",
+ "version": "1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Crinsane/LaravelShoppingcart.git",
+ "reference": "909d54baa6fe9ec76bbbe518de63217eb3c40b0f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Crinsane/LaravelShoppingcart/zipball/909d54baa6fe9ec76bbbe518de63217eb3c40b0f",
+ "reference": "909d54baa6fe9ec76bbbe518de63217eb3c40b0f",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/support": "~4",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "0.9.*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Gloudemans\\Shoppingcart": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Rob Gloudemans",
+ "email": "Rob_Gloudemans@hotmail.com"
+ }
+ ],
+ "description": "Laravel 4 Shoppingcart",
+ "keywords": [
+ "laravel",
+ "shoppingcart"
+ ],
+ "time": "2014-08-22 09:00:06"
+ },
+ {
+ "name": "illuminate/support",
+ "version": "v4.2.9",
+ "target-dir": "Illuminate/Support",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/support.git",
+ "reference": "b6716d34afce6e69523725f2534a5c3a54dba964"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/support/zipball/b6716d34afce6e69523725f2534a5c3a54dba964",
+ "reference": "b6716d34afce6e69523725f2534a5c3a54dba964",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "jeremeamia/superclosure": "~1.0",
+ "patchwork/utf8": "1.1.*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Illuminate\\Support": ""
+ },
+ "files": [
+ "Illuminate/Support/helpers.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylorotwell@gmail.com"
+ }
+ ],
+ "time": "2014-09-12 21:42:43"
+ },
+ {
+ "name": "stripe/stripe-php",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/stripe/stripe-php.git",
+ "reference": "7192b556801973954e21bf37ec572772c29727c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/stripe/stripe-php/zipball/7192b556801973954e21bf37ec572772c29727c0",
+ "reference": "7192b556801973954e21bf37ec572772c29727c0",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "php": ">=5.2"
+ },
+ "require-dev": {
+ "simpletest/simpletest": "*"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "lib/Stripe/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Stripe and contributors",
+ "homepage": "https://github.com/stripe/stripe-php/contributors"
+ }
+ ],
+ "description": "Stripe PHP Library",
+ "homepage": "https://stripe.com/",
+ "keywords": [
+ "api",
+ "payment processing",
+ "stripe"
+ ],
+ "time": "2014-10-09 01:21:53"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {
+ "stripe/stripe-php": 20
+ },
+ "prefer-stable": false,
+ "platform": {
+ "php": ">=5.4.0"
+ },
+ "platform-dev": []
+}
diff --git a/controllers/Categories.php b/controllers/Categories.php
new file mode 100644
index 0000000..b197720
--- /dev/null
+++ b/controllers/Categories.php
@@ -0,0 +1,25 @@
+vars['ordersTotal'] = Order::count();
+ $this->vars['ordersTotalLast'] = 17;
+
+ $this->vars['ordersPaid'] = Order::where('is_paid', 1)->count();
+ $this->vars['ordersPending'] = $this->vars['ordersTotal'] - $this->vars['ordersPaid'];
+
+ $c = $this->vars['ordersCount'] = Order::createdThisMonth()->count();
+ $cl = $this->vars['ordersCountLast'] = Order::createdLastMonth()->count();
+ $this->vars['ordersCountClass'] = $this->scoreboardClass($cl, $c);
+
+ $v = $this->vars['ordersValue'] = Order::createdThisMonth()->sum('total');
+ $vl = $this->vars['ordersValueLast'] = Order::createdLastMonth()->sum('total');
+ $this->vars['ordersValueClass'] = $this->scoreboardClass($vl, $v);
+
+ return $this->asExtension('ListController')->index();
+ }
+
+ public function scoreboardClass($oldVal, $newVal)
+ {
+ if ($newVal > $oldVal)
+ return 'positive';
+
+ if ($oldVal > $newVal)
+ return 'negative';
+
+ return '';
+ }
+
+ public function update($recordId, $context = null)
+ {
+ $this->vars['itemCount'] = 0;
+
+ $order = Order::find($recordId);
+
+ foreach (json_decode($order->items) as $item) {
+ $this->vars['itemCount'] += $item->qty;
+ }
+
+ return $this->asExtension('FormController')
+ ->update($recordId, $context);
+ }
+
+}
diff --git a/controllers/Products.php b/controllers/Products.php
new file mode 100644
index 0000000..05e5038
--- /dev/null
+++ b/controllers/Products.php
@@ -0,0 +1,25 @@
+
+ New Category
+
diff --git a/controllers/categories/config_form.yaml b/controllers/categories/config_form.yaml
new file mode 100644
index 0000000..b09fec5
--- /dev/null
+++ b/controllers/categories/config_form.yaml
@@ -0,0 +1,31 @@
+# ===================================
+# Form Behavior Config
+# ===================================
+
+# Record name
+name: Category
+
+# Model Form Field configuration
+form: $/dshoreman/shop/models/category/fields.yaml
+
+# Model Class name
+modelClass: DShoreman\Shop\Models\Category
+
+# Default redirect location
+defaultRedirect: dshoreman/shop/categories
+
+# Create page
+create:
+ title: Create Category
+ redirect: dshoreman/shop/categories/update/:id
+ redirectClose: dshoreman/shop/categories
+
+# Update page
+update:
+ title: Edit Category
+ redirect: dshoreman/shop/categories
+ redirectClose: dshoreman/shop/categories
+
+# Preview page
+preview:
+ title: Preview Category
diff --git a/controllers/categories/config_list.yaml b/controllers/categories/config_list.yaml
new file mode 100644
index 0000000..f61a074
--- /dev/null
+++ b/controllers/categories/config_list.yaml
@@ -0,0 +1,44 @@
+# ===================================
+# List Behavior Config
+# ===================================
+
+# Model List Column configuration
+list: $/dshoreman/shop/models/category/columns.yaml
+
+# Model Class name
+modelClass: DShoreman\Shop\Models\Category
+
+# List Title
+title: Manage Categories
+
+# Link URL for each record
+recordUrl: dshoreman/shop/categories/update/:id
+
+# Message to display if the list is empty
+noRecordsMessage: backend::lang.list.no_records
+
+# Records to display per page
+recordsPerPage: 25
+
+# Displays the list column set up button
+showSetup: true
+
+# Displays the sorting link on each column
+showSorting: true
+
+# Default sorting column
+defaultSort:
+ column: sort_order
+ direction: asc
+
+# Display checkboxes next to each record
+showCheckboxes: true
+
+# Toolbar widget configuration
+toolbar:
+ # Partial for toolbar buttons
+ buttons: list_toolbar
+
+ # Search widget configuration
+ search:
+ prompt: backend::lang.list.search_prompt
diff --git a/controllers/categories/create.htm b/controllers/categories/create.htm
new file mode 100644
index 0000000..b9f247b
--- /dev/null
+++ b/controllers/categories/create.htm
@@ -0,0 +1,46 @@
+
+
+
+
+fatalError): ?>
+
+ = Form::open(['class'=>'layout-item stretch layout-column']) ?>
+
+ = $this->formRender() ?>
+
+
+
+ = Form::close() ?>
+
+
+
+ = e($this->fatalError) ?>
+ Return to categories list
+
+
diff --git a/controllers/categories/index.htm b/controllers/categories/index.htm
new file mode 100644
index 0000000..766877d
--- /dev/null
+++ b/controllers/categories/index.htm
@@ -0,0 +1,2 @@
+
+= $this->listRender() ?>
diff --git a/controllers/categories/preview.htm b/controllers/categories/preview.htm
new file mode 100644
index 0000000..2d5f64e
--- /dev/null
+++ b/controllers/categories/preview.htm
@@ -0,0 +1,19 @@
+
+
+
+
+fatalError): ?>
+
+
+ = $this->formRenderPreview() ?>
+
+
+
+
+ = e($this->fatalError) ?>
+ Return to categories list
+
+
diff --git a/controllers/categories/update.htm b/controllers/categories/update.htm
new file mode 100644
index 0000000..17eeb7c
--- /dev/null
+++ b/controllers/categories/update.htm
@@ -0,0 +1,54 @@
+
+
+
+
+fatalError): ?>
+
+ = Form::open(['class'=>'layout-item stretch layout-column']) ?>
+
+ = $this->formRender() ?>
+
+
+
+ = Form::close() ?>
+
+
+
+ = e($this->fatalError) ?>
+ Return to categories list
+
+
diff --git a/controllers/orders/_list_toolbar.htm b/controllers/orders/_list_toolbar.htm
new file mode 100644
index 0000000..cab5839
--- /dev/null
+++ b/controllers/orders/_list_toolbar.htm
@@ -0,0 +1,3 @@
+
diff --git a/controllers/orders/config_form.yaml b/controllers/orders/config_form.yaml
new file mode 100644
index 0000000..c4f0551
--- /dev/null
+++ b/controllers/orders/config_form.yaml
@@ -0,0 +1,31 @@
+# ===================================
+# Form Behavior Config
+# ===================================
+
+# Record name
+name: Order
+
+# Model Form Field configuration
+form: $/dshoreman/shop/models/order/fields.yaml
+
+# Model Class name
+modelClass: DShoreman\Shop\Models\Order
+
+# Default redirect location
+defaultRedirect: dshoreman/shop/orders
+
+# Create page
+create:
+ title: Create Order
+ redirect: dshoreman/shop/orders/update/:id
+ redirectClose: dshoreman/shop/orders
+
+# Update page
+update:
+ title: Edit Order
+ redirect: dshoreman/shop/orders
+ redirectClose: dshoreman/shop/orders
+
+# Preview page
+preview:
+ title: Preview Order
diff --git a/controllers/orders/config_list.yaml b/controllers/orders/config_list.yaml
new file mode 100644
index 0000000..7356a3b
--- /dev/null
+++ b/controllers/orders/config_list.yaml
@@ -0,0 +1,44 @@
+# ===================================
+# List Behavior Config
+# ===================================
+
+# Model List Column configuration
+list: $/dshoreman/shop/models/order/columns.yaml
+
+# Model Class name
+modelClass: DShoreman\Shop\Models\Order
+
+# List Title
+title: Manage Orders
+
+# Link URL for each record
+recordUrl: dshoreman/shop/orders/update/:id
+
+# Message to display if the list is empty
+noRecordsMessage: backend::lang.list.no_records
+
+# Records to display per page
+recordsPerPage: 20
+
+# Displays the list column set up button
+showSetup: true
+
+# Displays the sorting link on each column
+showSorting: true
+
+# Default sorting column
+defaultSort:
+ column: created_at
+ direction: desc
+
+# Display checkboxes next to each record
+showCheckboxes: true
+
+# Toolbar widget configuration
+toolbar:
+ # Partial for toolbar buttons
+ buttons: list_toolbar
+
+ # Search widget configuration
+ search:
+ prompt: backend::lang.list.search_prompt
diff --git a/controllers/orders/create.htm b/controllers/orders/create.htm
new file mode 100644
index 0000000..dede4ed
--- /dev/null
+++ b/controllers/orders/create.htm
@@ -0,0 +1,46 @@
+
+
+ Orders
+ = e($this->pageTitle) ?>
+
+
+
+fatalError): ?>
+
+ = Form::open(['class'=>'layout-item stretch layout-column']) ?>
+
+ = $this->formRender() ?>
+
+
+
+ = Form::close() ?>
+
+
+
+ = e($this->fatalError) ?>
+ Return to orders list
+
+
diff --git a/controllers/orders/index.htm b/controllers/orders/index.htm
new file mode 100644
index 0000000..a399687
--- /dev/null
+++ b/controllers/orders/index.htm
@@ -0,0 +1,26 @@
+
+
+
+
+ Paid = $ordersPaid ?>
+ Pending = $ordersPending ?>
+
+
+
+
All Orders
+
= $ordersTotal ?>
+
+
+
Orders this Month
+
= $ordersCount ?>
+
previous: = $ordersCountLast ?>
+
+
+
Sales
+
£= $ordersValue ?>
+
previous: £= $ordersValueLast ?>
+
+
+
+
+= $this->listRender() ?>
diff --git a/controllers/orders/preview.htm b/controllers/orders/preview.htm
new file mode 100644
index 0000000..3d69d7e
--- /dev/null
+++ b/controllers/orders/preview.htm
@@ -0,0 +1,19 @@
+
+
+ Orders
+ = e($this->pageTitle) ?>
+
+
+
+fatalError): ?>
+
+
+ = $this->formRenderPreview() ?>
+
+
+
+
+ = e($this->fatalError) ?>
+ Return to orders list
+
+
diff --git a/controllers/orders/update.htm b/controllers/orders/update.htm
new file mode 100644
index 0000000..8e5c43e
--- /dev/null
+++ b/controllers/orders/update.htm
@@ -0,0 +1,96 @@
+
+
+ Orders
+ = e($this->pageTitle) ?>
+
+
+fatalError): ?>
+
+
+
+
+
+
Order #
+
= $formModel->id ?>
+
+
+
Customer
+
+ = $formModel->billing_name ?>
+
+
+ Email: = $formModel->email ?>
+
+
+
+
Current Status
+ = $formModel->is_paid
+ ? '
Paid
'
+ : '
Pending
'
+ ?>
+
+
+
+
Total Value
+
+ £= $formModel->total ?>
+
+
+ for = $itemCount ?> items
+
+
+
+
Date Placed
+
= $formModel->created_at ?>
+
+ Updated = $formModel->updated_at ?>
+
+
+
+
+
+ = Form::open(['class'=>'layout-item stretch layout-column']) ?>
+
+ = $this->formRender() ?>
+
+
+
+ = Form::close() ?>
+
+
+
+ = e($this->fatalError) ?>
+ Return to orders list
+
+
diff --git a/controllers/products/_list_toolbar.htm b/controllers/products/_list_toolbar.htm
new file mode 100644
index 0000000..8d4de19
--- /dev/null
+++ b/controllers/products/_list_toolbar.htm
@@ -0,0 +1,3 @@
+
diff --git a/controllers/products/config_form.yaml b/controllers/products/config_form.yaml
new file mode 100644
index 0000000..e960e53
--- /dev/null
+++ b/controllers/products/config_form.yaml
@@ -0,0 +1,31 @@
+# ===================================
+# Form Behavior Config
+# ===================================
+
+# Record name
+name: Product
+
+# Model Form Field configuration
+form: $/dshoreman/shop/models/product/fields.yaml
+
+# Model Class name
+modelClass: DShoreman\Shop\Models\Product
+
+# Default redirect location
+defaultRedirect: dshoreman/shop/products
+
+# Create page
+create:
+ title: Create Product
+ redirect: dshoreman/shop/products/update/:id
+ redirectClose: dshoreman/shop/products
+
+# Update page
+update:
+ title: Edit Product
+ redirect: dshoreman/shop/products
+ redirectClose: dshoreman/shop/products
+
+# Preview page
+preview:
+ title: Preview Product
diff --git a/controllers/products/config_list.yaml b/controllers/products/config_list.yaml
new file mode 100644
index 0000000..e604a17
--- /dev/null
+++ b/controllers/products/config_list.yaml
@@ -0,0 +1,44 @@
+# ===================================
+# List Behavior Config
+# ===================================
+
+# Model List Column configuration
+list: $/dshoreman/shop/models/product/columns.yaml
+
+# Model Class name
+modelClass: DShoreman\Shop\Models\Product
+
+# List Title
+title: Manage Products
+
+# Link URL for each record
+recordUrl: dshoreman/shop/products/update/:id
+
+# Message to display if the list is empty
+noRecordsMessage: backend::lang.list.no_records
+
+# Records to display per page
+recordsPerPage: 25
+
+# Displays the list column set up button
+showSetup: true
+
+# Displays the sorting link on each column
+showSorting: true
+
+# Default sorting column
+defaultSort:
+ column: created_at
+ direction: desc
+
+# Display checkboxes next to each record
+showCheckboxes: true
+
+# Toolbar widget configuration
+toolbar:
+ # Partial for toolbar buttons
+ buttons: list_toolbar
+
+ # Search widget configuration
+ search:
+ prompt: backend::lang.list.search_prompt
diff --git a/controllers/products/create.htm b/controllers/products/create.htm
new file mode 100644
index 0000000..d38eefa
--- /dev/null
+++ b/controllers/products/create.htm
@@ -0,0 +1,46 @@
+
+
+
+
+fatalError): ?>
+
+ = Form::open(['class'=>'layout-item stretch layout-column']) ?>
+
+ = $this->formRender() ?>
+
+
+
+ = Form::close() ?>
+
+
+
+ = e($this->fatalError) ?>
+ Return to products list
+
+
\ No newline at end of file
diff --git a/controllers/products/index.htm b/controllers/products/index.htm
new file mode 100644
index 0000000..766877d
--- /dev/null
+++ b/controllers/products/index.htm
@@ -0,0 +1,2 @@
+
+= $this->listRender() ?>
diff --git a/controllers/products/preview.htm b/controllers/products/preview.htm
new file mode 100644
index 0000000..bb2a533
--- /dev/null
+++ b/controllers/products/preview.htm
@@ -0,0 +1,19 @@
+
+
+
+
+fatalError): ?>
+
+
+ = $this->formRenderPreview() ?>
+
+
+
+
+ = e($this->fatalError) ?>
+ Return to products list
+
+
\ No newline at end of file
diff --git a/controllers/products/update.htm b/controllers/products/update.htm
new file mode 100644
index 0000000..38faa54
--- /dev/null
+++ b/controllers/products/update.htm
@@ -0,0 +1,54 @@
+
+
+
+
+fatalError): ?>
+
+ = Form::open(['class'=>'layout-item stretch layout-column']) ?>
+
+ = $this->formRender() ?>
+
+
+
+ = Form::close() ?>
+
+
+
+ = e($this->fatalError) ?>
+ Return to products list
+
+
\ No newline at end of file
diff --git a/formwidgets/ItemGrid.php b/formwidgets/ItemGrid.php
new file mode 100644
index 0000000..fda52c2
--- /dev/null
+++ b/formwidgets/ItemGrid.php
@@ -0,0 +1,28 @@
+ 'Item Grid',
+ 'description' => 'Renders a grid of items from an order',
+ ];
+ }
+
+ public function render()
+ {
+ $this->prepareVars();
+
+ return $this->makePartial('itemgrid');
+ }
+
+ public function prepareVars()
+ {
+ $this->vars['items'] = json_decode($this->formField->value);
+ }
+
+}
diff --git a/formwidgets/itemgrid/partials/_itemgrid.htm b/formwidgets/itemgrid/partials/_itemgrid.htm
new file mode 100644
index 0000000..ac5faa9
--- /dev/null
+++ b/formwidgets/itemgrid/partials/_itemgrid.htm
@@ -0,0 +1,22 @@
+
+
+
+ Item #
+ Name
+ Price
+ Quantity
+ Subtotal
+
+
+
+
+
+ = $item->id ?>
+ = $item->name ?>
+ = $item->price ?>
+ = $item->qty ?>
+ = $item->subtotal ?>
+
+
+
+
diff --git a/models/Category.php b/models/Category.php
new file mode 100644
index 0000000..495a70e
--- /dev/null
+++ b/models/Category.php
@@ -0,0 +1,51 @@
+ ['Dshoreman\Shop\Models\Product'],
+ ];
+ public $belongsTo = [];
+ public $belongsToMany = [];
+ public $morphTo = [];
+ public $morphOne = [];
+ public $morphMany = [];
+ public $attachOne = [];
+ public $attachMany = [];
+
+ public function setUrl($pageName, $controller)
+ {
+ $params = [
+ 'id' => $this->id,
+ 'slug' => $this->slug,
+ ];
+
+ return $this->url = $controller->pageUrl($pageName, $params);
+ }
+
+}
diff --git a/models/Order.php b/models/Order.php
new file mode 100644
index 0000000..8fe0ed6
--- /dev/null
+++ b/models/Order.php
@@ -0,0 +1,53 @@
+where('created_at', '>=', Carbon::now()->startOfMonth());
+ }
+
+ public function scopeCreatedLastMonth($query)
+ {
+ return $query->whereBetween('created_at', [
+ Carbon::now()->subMonth()->startOfMonth(),
+ Carbon::now()->subMonth()->endOfMonth()
+ ]);
+ }
+
+}
diff --git a/models/Product.php b/models/Product.php
new file mode 100644
index 0000000..1502866
--- /dev/null
+++ b/models/Product.php
@@ -0,0 +1,63 @@
+ ['DShoreman\Shop\Models\Category', 'foreign_key' => 'category_id']
+ ];
+ public $belongsToMany = [];
+ public $morphTo = [];
+ public $morphOne = [];
+ public $morphMany = [];
+ public $attachOne = [];
+ public $attachMany = [
+ 'images' => ['System\Models\File']
+ ];
+
+ public function getCategoryIdOptions($keyValue = null)
+ {
+ return Category::lists('title', 'id');
+ }
+
+ public function getSquareThumb($size, $image)
+ {
+ return $image->getThumb($size, $size, ['mode' => 'crop']);
+ }
+
+ public function setUrl($pageName, $controller)
+ {
+ $params = [
+ 'id' => $this->id,
+ 'slug' => $this->slug,
+ ];
+
+ return $this->url = $controller->pageUrl($pageName, $params);
+ }
+
+}
diff --git a/models/Settings.php b/models/Settings.php
new file mode 100644
index 0000000..30e7adc
--- /dev/null
+++ b/models/Settings.php
@@ -0,0 +1,13 @@
+ 'shop'], function()
+{
+ Route::post('order/payment/process', function()
+ {
+ $token = post('stripeToken');
+ $orderId = Session::get('orderId');
+
+ if (!$order = ShopOrder::find($orderId)) {
+
+ // Todo: Add some flash data and utilise the ajax stuff instead of redirect
+ return Redirect::to('shop/checkout/payment/'.$orderId);
+ }
+
+ $order->email = Input::get('stripeEmail');
+ $order->stripe_token = $token;
+
+ $order->billing_name = post('stripeBillingName');
+ $order->billing_street = post('stripeBillingAddressLine1');
+ $order->billing_town = post('stripeBillingAddressCity');
+ $order->billing_county = post('stripeBillingAddressState');
+ $order->billing_postcode = post('stripeBillingAddressZip');
+ $order->billing_country = post('stripeBillingAddressCountry');
+
+ $order->shipping_name = post('stripeShippingName');
+ $order->shipping_street = post('stripeShippingAddressLine1');
+ $order->shipping_town = post('stripeShippingAddressCity');
+ $order->shipping_county = post('stripeShippingAddressState');
+ $order->shipping_postcode = post('stripeShippingAddressZip');
+ $order->shipping_country = post('stripeShippingAddressCountry');
+
+ Settings::get('stripe_active_keys') == 'live'
+ ? Stripe::setApiKey(Settings::get('stripe_live_sec_key'))
+ : Stripe::setApiKey(Settings::get('stripe_test_sec_key'));
+
+ try {
+ $charge = Stripe_Charge::create([
+ 'amount' => $order->total * 100,
+ 'currency' => 'gbp',
+ 'card' => $token,
+ ]);
+
+ $order->is_paid = true;
+ $order->save();
+ }
+ catch (Stripe_CardError $e)
+ {
+ $error = $e->getJsonBody()['error'];
+ Flash::error($error['message']);
+
+ $order->save();
+
+ Redirect::to('shop/checkout/payment/'.$orderId)->withInput();
+ }
+
+ return Redirect::to('shop/order/'.$orderId);
+ });
+
+});
diff --git a/updates/create_categories_table.php b/updates/create_categories_table.php
new file mode 100644
index 0000000..b080e67
--- /dev/null
+++ b/updates/create_categories_table.php
@@ -0,0 +1,28 @@
+engine = 'InnoDB';
+ $table->increments('id');
+ $table->string('title')->index();
+ $table->string('slug')->unique();
+ $table->integer('sort_order')->default(1);
+ $table->string('description')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('dshoreman_shop_categories');
+ }
+
+}
diff --git a/updates/create_orders_table.php b/updates/create_orders_table.php
new file mode 100644
index 0000000..8a5871a
--- /dev/null
+++ b/updates/create_orders_table.php
@@ -0,0 +1,41 @@
+engine = 'InnoDB';
+ $table->increments('id');
+ $table->string('email');
+ $table->text('items');
+ $table->decimal('total', 7, 2);
+ $table->boolean('is_paid')->default(false);
+ $table->string('stripe_token');
+ $table->string('billing_name');
+ $table->string('billing_street');
+ $table->string('billing_town');
+ $table->string('billing_county');
+ $table->string('billing_postcode');
+ $table->string('billing_country');
+ $table->string('shipping_name');
+ $table->string('shipping_street');
+ $table->string('shipping_town');
+ $table->string('shipping_county');
+ $table->string('shipping_postcode');
+ $table->string('shipping_country');
+ $table->timestamps();
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('dshoreman_shop_orders');
+ }
+
+}
diff --git a/updates/create_products_table.php b/updates/create_products_table.php
new file mode 100644
index 0000000..4e94820
--- /dev/null
+++ b/updates/create_products_table.php
@@ -0,0 +1,31 @@
+engine = 'InnoDB';
+ $table->increments('id');
+ $table->integer('category_id')->unsigned();
+ $table->string('title')->index();
+ $table->string('slug')->index()->unique();
+ $table->longText('description');
+ $table->decimal('price', 7, 2);
+ $table->timestamps();
+
+ $table->foreign('category_id')->references('id')->on('dshoreman_shop_categories');
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('dshoreman_shop_products');
+ }
+
+}
diff --git a/updates/demo_seed.php b/updates/demo_seed.php
new file mode 100644
index 0000000..6511966
--- /dev/null
+++ b/updates/demo_seed.php
@@ -0,0 +1,69 @@
+createCategories([[
+ 'Books', 'books', 1,
+ 'Here be some books up fer grabs!'
+ ],
+ [
+ 'Games & Consoles', 'games-consoles', 2,
+ 'Bored? Twiddle yer thumbs on these new titles...'
+ ]]);
+
+ $this->createProducts([[
+ 1, 'Hogfather', 'hogfather', '9.99',
+ "IT'S THE NIGHT BEFORE HOGSWATCH. AND IT'S TOO QUIET.
Where is the big jolly fat man? Why is Death creeping down chimneys and trying to say Ho Ho Ho? The darkest night of the year is getting a lot darker...
Susan the gothic governess has got to sort it out by morning, otherwise there won't be a morning. Ever again...
The 20th Discworld novel is a festive feast of darkness and Death (but with jolly robins and tinsel too).
As they say: 'You'd better watch out...'
"
+ ],
+ [
+ 2, 'Battlefield 3', 'battlefield-3', '18.00',
+ "Battlefield 3 leaps ahead of its time with the power of Frostbite 2, the next instalment of DICE's game engine. This state-of-the-art technology is the foundation on which Battlefield 3 is built, delivering enhanced visual quality, a grand sense of scale, massive destruction, dynamic audio and incredibly lifelike character animations. As bullets whiz by, walls crumble, and explosions throw you to the ground, the battlefield feels more alive and interactive than ever before. In Battlefield 3, players step into the role of the elite U.S. Marines where they will experience heart-pounding missions across diverse locations including Paris, Tehran and New York.
"
+ ],
+ [
+ 2, 'Battlefield 4', 'battlefield-4', '38.95',
+ "The genre-defining action blockbuster returns as Battlefield 4 blazes onto Xbox One, PlayStation 4, Xbox 360, PlayStation 3 and PC!
Destroying the buildings that shield your enemies. Leading an assault from the back of a gunboat. Only in Battlefield can you do this and more.
Battlefield 4 ramps up the traditional Battlefield action to the next level thanks to the next-generation power and fidelity of the Frostbite 3 engine, with an intense single-player campaign that will push you to your limits as you and your squad struggle against the odds to return American VIPs home.
On top of this is the hallmark multiplayer that will continue to immerse you and your fellow players in the glorious chaos of all-out war, with a new addition - Battlepacks. Packed with a combination of new weapon accessories, dog tags, XP boosts, and more, Battlepacks are awarded during gampelay, adding a new elements of persistence and chance.
Carve your path to victory and order Battlefield 4 today!
Xbox One Release Date 22/11/2013 PlayStation 4 Release Date 29/11/2013 "
+ ],
+ [
+ 1, 'Anathem', 'anathem-neil-stephenson', '6.99',
+ "This is the latest magnificent creation from the award-winning author of \"Cryptonomicon and the Baroque Cycle\" trilogy. Erasmas, 'Raz', is a young avout living in the Concent, a sanctuary for mathematicians, scientists, and philosophers. Three times during history's darkest epochs, violence has invaded and devastated the cloistered community. Yet the avout have always managed to adapt in the wake of catastrophe. But they now prepare to open the Concent's gates to the outside world, in celebration of a once-a-decade rite. Suddenly, Erasmas finds himself a major player in a drama that will determine the future of his world - as he sets out on an extraordinary odyssey that will carry him to the most dangerous, inhospitable corners of the planet...and beyond.
"
+ ],
+ [
+ 1, 'Going Postal', 'going-postal-terry-pratchett', '8.99',
+ "Moist von Lipwig is a con artist...
... and a fraud and a man faced with a life choice: be hanged, or put Ankh-Morpork's ailing postal service back on its feet.
It's a tough decision.
But he's got to see that the mail gets through, come rain, hail, sleet, dogs, the Post Office Workers' Friendly and Benevolent Society, the evil chairman of the Grand Trunk Semaphore Company, and a midnight killer.
Getting a date with Adora Bell Dearheart would be nice, too...
"
+ ]]);
+ }
+
+ public function createCategories($categories)
+ {
+ foreach ($categories as $category)
+ {
+ $c = new Category;
+ $c->title = $category[0];
+ $c->slug = $category[1];
+ $c->sort_order = $category[2];
+ $c->description = $category[3];
+ $c->save();
+ }
+ }
+
+ public function createProducts($products)
+ {
+ foreach ($products as $product)
+ {
+ $p = new Product;
+ $p->category_id = $product[0];
+ $p->title = $product[1];
+ $p->slug = $product[2];
+ $p->description = $product[4];
+ $p->price = $product[3];
+ $p->save();
+ }
+ }
+
+}
diff --git a/updates/version.yaml b/updates/version.yaml
new file mode 100644
index 0000000..f51bf2b
--- /dev/null
+++ b/updates/version.yaml
@@ -0,0 +1,6 @@
+0.1.0:
+ - First version of Shop
+ - create_categories_table.php
+ - create_products_table.php
+ - create_orders_table.php
+ - demo_seed.php