From f12756a1f26fe79e79becf679942bdfda91c65e0 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Fri, 10 Oct 2014 10:35:14 +0100 Subject: [PATCH 01/45] First commit. Adding backend products and categories --- .gitignore | 1 + Plugin.php | 63 +++++++++++++++++++++++ controllers/Categories.php | 25 +++++++++ controllers/Products.php | 25 +++++++++ controllers/categories/_list_toolbar.htm | 3 ++ controllers/categories/config_form.yaml | 31 +++++++++++ controllers/categories/config_list.yaml | 44 ++++++++++++++++ controllers/categories/create.htm | 46 +++++++++++++++++ controllers/categories/index.htm | 2 + controllers/categories/preview.htm | 19 +++++++ controllers/categories/update.htm | 54 +++++++++++++++++++ controllers/products/_list_toolbar.htm | 3 ++ controllers/products/config_form.yaml | 31 +++++++++++ controllers/products/config_list.yaml | 44 ++++++++++++++++ controllers/products/create.htm | 46 +++++++++++++++++ controllers/products/index.htm | 2 + controllers/products/preview.htm | 19 +++++++ controllers/products/update.htm | 54 +++++++++++++++++++ models/.Product.php.swp | Bin 0 -> 12288 bytes models/Category.php | 39 ++++++++++++++ models/Product.php | 45 ++++++++++++++++ models/category/columns.yaml | 14 +++++ models/category/fields.yaml | 11 ++++ models/product/.columns.yaml.swp | Bin 0 -> 12288 bytes models/product/columns.yaml | 27 ++++++++++ models/product/fields.yaml | 35 +++++++++++++ updates/create_categories_table.php | 26 ++++++++++ updates/create_products_table.php | 31 +++++++++++ updates/version.yaml | 4 ++ 29 files changed, 744 insertions(+) create mode 100644 .gitignore create mode 100644 Plugin.php create mode 100644 controllers/Categories.php create mode 100644 controllers/Products.php create mode 100644 controllers/categories/_list_toolbar.htm create mode 100644 controllers/categories/config_form.yaml create mode 100644 controllers/categories/config_list.yaml create mode 100644 controllers/categories/create.htm create mode 100644 controllers/categories/index.htm create mode 100644 controllers/categories/preview.htm create mode 100644 controllers/categories/update.htm create mode 100644 controllers/products/_list_toolbar.htm create mode 100644 controllers/products/config_form.yaml create mode 100644 controllers/products/config_list.yaml create mode 100644 controllers/products/create.htm create mode 100644 controllers/products/index.htm create mode 100644 controllers/products/preview.htm create mode 100644 controllers/products/update.htm create mode 100644 models/.Product.php.swp create mode 100644 models/Category.php create mode 100644 models/Product.php create mode 100644 models/category/columns.yaml create mode 100644 models/category/fields.yaml create mode 100644 models/product/.columns.yaml.swp create mode 100644 models/product/columns.yaml create mode 100644 models/product/fields.yaml create mode 100644 updates/create_categories_table.php create mode 100644 updates/create_products_table.php create mode 100644 updates/version.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48b8bf9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor/ diff --git a/Plugin.php b/Plugin.php new file mode 100644 index 0000000..0972809 --- /dev/null +++ b/Plugin.php @@ -0,0 +1,63 @@ + 'Shop', + 'description' => 'No description provided yet...', + 'author' => 'Dave Shoreman', + 'icon' => 'icon-shopping-cart' + ]; + } + + 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'], + ], + ], + ], + ]; + } + + public function registerPermissions() + { + return [ + 'dshoreman.shop.access_products' => ['label' => "Manage the shop's products"], + 'dshoreman.shop.access_categories' => ['label' => "Manage the shop categories"], + ]; + } + +} 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 @@ + + 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): ?> + + 'layout-item stretch layout-column']) ?> + + formRender() ?> + +
+
+ + + + or Cancel + +
+
+ + + + + +

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 @@ + +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): ?> + +
+ formRenderPreview() ?> +
+ + + +

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): ?> + + 'layout-item stretch layout-column']) ?> + + formRender() ?> + +
+
+ + + + + or Cancel + +
+
+ + + + + +

fatalError) ?>

+

Return to categories 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 @@ +
+ New Product +
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): ?> + + 'layout-item stretch layout-column']) ?> + + formRender() ?> + +
+
+ + + + or Cancel + +
+
+ + + + + +

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 @@ + +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): ?> + +
+ formRenderPreview() ?> +
+ + + +

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): ?> + + 'layout-item stretch layout-column']) ?> + + formRender() ?> + +
+
+ + + + + or Cancel + +
+
+ + + + + +

fatalError) ?>

+

Return to products list

+ + \ No newline at end of file diff --git a/models/.Product.php.swp b/models/.Product.php.swp new file mode 100644 index 0000000000000000000000000000000000000000..25504c73b36ba7754b93a60d1f8553101326f7fa GIT binary patch literal 12288 zcmeI2zfTlF6vqcG{TZl?7NcIWa1j>81OuWb8Wojb%vcByKKT2rY7sVjL1T(t%SBKm+VkRI`D`=?KxfDDiUGC&5%02v?y|F;1X_OLZ9abLd7!Ti|Mam+92KnBPF86X2>fDDiU zGC&5%02v?yWPl9pK?A~N?DYZ09vwvS`2WB8`~TY!#=e3t;4}CD-hxfA4xWQ&;3=2^ zx4{W;9Q1)7hZ*}0K7o(mJ=g&2paGUa2uk1%7y-w???a5e0vlirNN@`bfnM+nb^ZkJ zz$SPCRzX_-9`sKolL0b72FL&zAOmE843L4nY~V5D*s^_c7D*FFKEEoAtOikIY)pCD z=zhz1M*YiJX`SJg=UM~fEJ?eP?!&mOJcn0e-!Wd`^Qtt>2Ti)uVRBFR7aq#StWdG! z6WouL8erS!7KAavsZ9yLf%x1)mOf9M2dDjHZfC3>MBz^J(%3H3NckdEWBQ<+72!t9kl< kS^A7(E12C(og!y8W}LB$m(eKCeNmS>6prLwKb3Lz2NkYj)c^nh literal 0 HcmV?d00001 diff --git a/models/Category.php b/models/Category.php new file mode 100644 index 0000000..e464e82 --- /dev/null +++ b/models/Category.php @@ -0,0 +1,39 @@ + ['DShoreman\Shop\Models\Category', 'foreign_key' => 'category_id'] + ]; + public $belongsToMany = []; + public $morphTo = []; + public $morphOne = []; + public $morphMany = []; + public $attachOne = []; + public $attachMany = []; + + public function getCategoryIdOptions($keyValue = null) + { + return Category::lists('title', 'id'); + } +} diff --git a/models/category/columns.yaml b/models/category/columns.yaml new file mode 100644 index 0000000..27ce3f6 --- /dev/null +++ b/models/category/columns.yaml @@ -0,0 +1,14 @@ +# =================================== +# List Column Definitions +# =================================== + +columns: + id: + label: ID + searchable: true + title: + label: Title + searchable: true + description: + label: Description + searchable: false diff --git a/models/category/fields.yaml b/models/category/fields.yaml new file mode 100644 index 0000000..1ed3807 --- /dev/null +++ b/models/category/fields.yaml @@ -0,0 +1,11 @@ +# =================================== +# Form Field Definitions +# =================================== + +fields: + title: + label: Title + required: true + description: + label: Description + type: textarea diff --git a/models/product/.columns.yaml.swp b/models/product/.columns.yaml.swp new file mode 100644 index 0000000000000000000000000000000000000000..6660ffc0ebdcc4c4ab1bfa753fd41c614e098c67 GIT binary patch literal 12288 zcmeI2y^9nv7>BcpcBi712ok;8oAU!!2Ca4r56(jFqY@=Mlf4aOK1ebLhoGgUjfH}( zjjexxAox!x))w|x`okNe?13@_a1s;;wym^!N*=m@KR=3w4%Kpk7fp$TNAMg8{ z?@mAHeS9KB>L-QMnabJvyQs9)j>&!0 zh2a2YlohGAJ}O<2R!NAG!d6-CSN2q97jDS_8lZvNKo!O}?kLD8q&;Sk401eOp4bT7$(7=CUAe24v0=1rM>%G$Uz2ExwksS@t01eOp4bT7$&;Sk4 z01eOp4bZ>|G@w!;;w2$IgZlgb?EU}ic_BW5x8MzU1zv&*JO+=zJ+KTegEQdgIU&A- zFW>`s4W5AnY=Azv3eJLk?DuW9?+NtABhvs4&;Sk401eOp4bT7$(7-=7AiL(Za>+!E ztd3=Hl@_xpt47+!a$Wf#x5`w7=~UGW)2AxL^H7uT8gKqgu_*TtN#($4bZL_;nU4Q1 zxz|=F0o9mbyKMMjFt6W>URAG?x8n1JqCxb zJ52EA{Fu#wkH2tj*z9J~ShSbhW4u_)`->Z*engine = 'InnoDB'; + $table->increments('id'); + $table->string('title')->index(); + $table->string('description')->nullable(); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('dshoreman_shop_categories'); + } + +} 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/version.yaml b/updates/version.yaml new file mode 100644 index 0000000..1051802 --- /dev/null +++ b/updates/version.yaml @@ -0,0 +1,4 @@ +1.0.1: + - First version of Shop + - create_categories_table.php + - create_products_table.php From ec9bb252c15749d74da958cffe9a87da340ea966 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Fri, 10 Oct 2014 21:27:01 +0100 Subject: [PATCH 02/45] Add slug to categories Forgot to give categories a slug before, so this adds it. While we're at it, we'll remove vim's swapfiles from the repo too. --- .gitignore | 2 ++ models/.Product.php.swp | Bin 12288 -> 0 bytes models/category/columns.yaml | 4 ++++ models/category/fields.yaml | 7 +++++++ models/product/.columns.yaml.swp | Bin 12288 -> 0 bytes updates/create_categories_table.php | 1 + 6 files changed, 14 insertions(+) delete mode 100644 models/.Product.php.swp delete mode 100644 models/product/.columns.yaml.swp diff --git a/.gitignore b/.gitignore index 48b8bf9..3dec0e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ vendor/ +*.swp +*.swo diff --git a/models/.Product.php.swp b/models/.Product.php.swp deleted file mode 100644 index 25504c73b36ba7754b93a60d1f8553101326f7fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2zfTlF6vqcG{TZl?7NcIWa1j>81OuWb8Wojb%vcByKKT2rY7sVjL1T(t%SBKm+VkRI`D`=?KxfDDiUGC&5%02v?y|F;1X_OLZ9abLd7!Ti|Mam+92KnBPF86X2>fDDiU zGC&5%02v?yWPl9pK?A~N?DYZ09vwvS`2WB8`~TY!#=e3t;4}CD-hxfA4xWQ&;3=2^ zx4{W;9Q1)7hZ*}0K7o(mJ=g&2paGUa2uk1%7y-w???a5e0vlirNN@`bfnM+nb^ZkJ zz$SPCRzX_-9`sKolL0b72FL&zAOmE843L4nY~V5D*s^_c7D*FFKEEoAtOikIY)pCD z=zhz1M*YiJX`SJg=UM~fEJ?eP?!&mOJcn0e-!Wd`^Qtt>2Ti)uVRBFR7aq#StWdG! z6WouL8erS!7KAavsZ9yLf%x1)mOf9M2dDjHZfC3>MBz^J(%3H3NckdEWBQ<+72!t9kl< kS^A7(E12C(og!y8W}LB$m(eKCeNmS>6prLwKb3Lz2NkYj)c^nh diff --git a/models/category/columns.yaml b/models/category/columns.yaml index 27ce3f6..c2e55a7 100644 --- a/models/category/columns.yaml +++ b/models/category/columns.yaml @@ -9,6 +9,10 @@ columns: title: label: Title searchable: true + slug: + label: Slug + searchable: false description: label: Description searchable: false + invisible: true diff --git a/models/category/fields.yaml b/models/category/fields.yaml index 1ed3807..89b5696 100644 --- a/models/category/fields.yaml +++ b/models/category/fields.yaml @@ -6,6 +6,13 @@ fields: title: label: Title required: true + slug: + label: Slug + required: true + attributes: + data-input-preset: 'input[name="Category[title]"]' + data-input-preset-type: 'slug' + data-input-preset-closest-parent: 'form' description: label: Description type: textarea diff --git a/models/product/.columns.yaml.swp b/models/product/.columns.yaml.swp deleted file mode 100644 index 6660ffc0ebdcc4c4ab1bfa753fd41c614e098c67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2y^9nv7>BcpcBi712ok;8oAU!!2Ca4r56(jFqY@=Mlf4aOK1ebLhoGgUjfH}( zjjexxAox!x))w|x`okNe?13@_a1s;;wym^!N*=m@KR=3w4%Kpk7fp$TNAMg8{ z?@mAHeS9KB>L-QMnabJvyQs9)j>&!0 zh2a2YlohGAJ}O<2R!NAG!d6-CSN2q97jDS_8lZvNKo!O}?kLD8q&;Sk401eOp4bT7$(7=CUAe24v0=1rM>%G$Uz2ExwksS@t01eOp4bT7$&;Sk4 z01eOp4bZ>|G@w!;;w2$IgZlgb?EU}ic_BW5x8MzU1zv&*JO+=zJ+KTegEQdgIU&A- zFW>`s4W5AnY=Azv3eJLk?DuW9?+NtABhvs4&;Sk401eOp4bT7$(7-=7AiL(Za>+!E ztd3=Hl@_xpt47+!a$Wf#x5`w7=~UGW)2AxL^H7uT8gKqgu_*TtN#($4bZL_;nU4Q1 zxz|=F0o9mbyKMMjFt6W>URAG?x8n1JqCxb zJ52EA{Fu#wkH2tj*z9J~ShSbhW4u_)`->Z*engine = 'InnoDB'; $table->increments('id'); $table->string('title')->index(); + $table->string('slug')->unique(); $table->string('description')->nullable(); $table->timestamps(); }); From f05f9e1330dd2395889f52e55274f00c5c33af73 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Fri, 10 Oct 2014 23:37:00 +0100 Subject: [PATCH 03/45] Add products component for category pages --- Plugin.php | 7 +++++ components/Products.php | 51 +++++++++++++++++++++++++++++++++ components/products/default.htm | 8 ++++++ components/products/product.htm | 21 ++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 components/Products.php create mode 100644 components/products/default.htm create mode 100644 components/products/product.htm diff --git a/Plugin.php b/Plugin.php index 0972809..450aa66 100644 --- a/Plugin.php +++ b/Plugin.php @@ -24,6 +24,13 @@ public function pluginDetails() ]; } + public function registerComponents() + { + return [ + 'DShoreman\Shop\Components\Products' => 'shopProducts', + ]; + } + public function registerNavigation() { return [ diff --git a/components/Products.php b/components/Products.php new file mode 100644 index 0000000..10e4386 --- /dev/null +++ b/components/Products.php @@ -0,0 +1,51 @@ + 'Shop Product List', + 'description' => 'Display products from a given category', + ]; + } + + public function defineProperties() + { + return [ + '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' => '' + ], + ]; + } + + public function onRun() { + $this->category = $this->page['category'] = $this->loadCategory(); + $this->products = $this->page['products'] = $this->listProducts(); + } + + public function loadCategory() + { + if (!$categoryId = $this->propertyOrParam('categoryFilter')) + return null; + + if (!$category = ShopCategory::whereSlug($categoryId)) + return null; + + return $category->first(); + } + + public function listProducts() + { + return ShopProduct::whereCategoryId($this->category->id)->get(); + } + +} diff --git a/components/products/default.htm b/components/products/default.htm new file mode 100644 index 0000000..e9ca5e8 --- /dev/null +++ b/components/products/default.htm @@ -0,0 +1,8 @@ +

{{ category.title }}

+

{{ category.description }}

+ +
+ {% 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..727919a --- /dev/null +++ b/components/products/product.htm @@ -0,0 +1,21 @@ +
+
+ {{ product.title }} + +
+ +

{{ product.title }}

+
+
+
+
£{{ product.price }}
+
+
+ +
+
+
+
+
From babfc11440078acfd63b03135d7d329feefb7318 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 11 Oct 2014 01:36:29 +0100 Subject: [PATCH 04/45] Add product component Much of this markup really shouldn't be here. When we've got some functionality to speak of, start stripping the markup down into its own separated theme, which will rely on this plugin. That way people can either install the theme for a fully functioning site, or just install the Shop plugin and write the markup themselves. --- Plugin.php | 1 + components/Product.php | 40 +++++++++++++++++++++++++++++++++ components/product/default.htm | 41 ++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 components/Product.php create mode 100644 components/product/default.htm diff --git a/Plugin.php b/Plugin.php index 450aa66..2ef3ec4 100644 --- a/Plugin.php +++ b/Plugin.php @@ -27,6 +27,7 @@ public function pluginDetails() public function registerComponents() { return [ + 'DShoreman\Shop\Components\Product' => 'shopProduct', 'DShoreman\Shop\Components\Products' => 'shopProducts', ]; } diff --git a/components/Product.php b/components/Product.php new file mode 100644 index 0000000..c4925cb --- /dev/null +++ b/components/Product.php @@ -0,0 +1,40 @@ + 'Shop Product', + 'description' => 'Display a single product', + ]; + } + + public function defineProperties() + { + return [ + 'idParam' => [ + 'title' => 'Slug', + 'default' => ':slug', + 'type' => 'string', + ], + ]; + } + + public function onRun() { + $this->product = $this->page['product'] = $this->loadProduct(); + } + + public function loadProduct() + { + $productId = $this->propertyOrParam('idParam'); + + return ShopProduct::whereSlug($productId)->first(); + } + +} diff --git a/components/product/default.htm b/components/product/default.htm new file mode 100644 index 0000000..27112f0 --- /dev/null +++ b/components/product/default.htm @@ -0,0 +1,41 @@ +
+
+ +
+
+ +

{{ product.title }}

+ + {{ product.description|raw }} + +
+
+
+

+ £ {{ product.price }} +

+
+
+ +
+ +
+ + +
+
+
+
+
+
+ +
+
+
+ +
+
From 9b4c43d77dc768dd84c4e527a1c17dbea9b047b8 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 11 Oct 2014 01:51:18 +0100 Subject: [PATCH 05/45] Add category listing component --- Plugin.php | 1 + components/Categories.php | 32 +++++++++++++++++++++++++++++++ components/categories/default.htm | 7 +++++++ 3 files changed, 40 insertions(+) create mode 100644 components/Categories.php create mode 100644 components/categories/default.htm diff --git a/Plugin.php b/Plugin.php index 2ef3ec4..e372661 100644 --- a/Plugin.php +++ b/Plugin.php @@ -27,6 +27,7 @@ public function pluginDetails() public function registerComponents() { return [ + 'DShoreman\Shop\Components\Categories' => 'shopCategories', 'DShoreman\Shop\Components\Product' => 'shopProduct', 'DShoreman\Shop\Components\Products' => 'shopProducts', ]; diff --git a/components/Categories.php b/components/Categories.php new file mode 100644 index 0000000..2e82b9c --- /dev/null +++ b/components/Categories.php @@ -0,0 +1,32 @@ + 'Shop Category List', + 'description' => 'Displays a list of shop categories on the page.', + ]; + } + + public function defineProperties() + { + return []; + } + + public function onRun() + { + $this->categories = $this->page['categories'] = $this->listCategories(); + } + + public function listCategories() + { + return ShopCategory::all(); + } + +} diff --git a/components/categories/default.htm b/components/categories/default.htm new file mode 100644 index 0000000..fc88d14 --- /dev/null +++ b/components/categories/default.htm @@ -0,0 +1,7 @@ + From 22edf1f6b9ee0c10e28c70c6db0745dd014be5fb Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 11 Oct 2014 02:45:47 +0100 Subject: [PATCH 06/45] Fix category and product URLs --- components/Categories.php | 27 +++++++++++++++++++++++++-- components/Products.php | 27 +++++++++++++++++++++++++-- components/categories/default.htm | 2 +- components/products/product.htm | 2 +- models/Category.php | 10 ++++++++++ models/Product.php | 11 +++++++++++ 6 files changed, 73 insertions(+), 6 deletions(-) diff --git a/components/Categories.php b/components/Categories.php index 2e82b9c..1cdeab1 100644 --- a/components/Categories.php +++ b/components/Categories.php @@ -1,5 +1,6 @@ [ + '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', + ], + ]; + } + + public function getCategoryPageOptions() + { + return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName'); } public function onRun() { + $this->categoryPage = $this->page['categoryPage'] = $this->property('categoryPage'); + $this->categories = $this->page['categories'] = $this->listCategories(); } public function listCategories() { - return ShopCategory::all(); + $categories = ShopCategory::all(); + + $categories->each(function($category) + { + $category->setUrl($this->categoryPage, $this->controller); + }); + + return $categories; } } diff --git a/components/Products.php b/components/Products.php index 10e4386..a270cbf 100644 --- a/components/Products.php +++ b/components/Products.php @@ -1,5 +1,6 @@ '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', + ], ]; } - public function onRun() { + public function getProductPageOptions() + { + return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName'); + } + + public function onRun() + { + $this->productPage = $this->page['productPage'] = $this->property('productPage'); + $this->category = $this->page['category'] = $this->loadCategory(); $this->products = $this->page['products'] = $this->listProducts(); } @@ -45,7 +61,14 @@ public function loadCategory() public function listProducts() { - return ShopProduct::whereCategoryId($this->category->id)->get(); + $products = ShopProduct::whereCategoryId($this->category->id)->get(); + + $products->each(function($product) + { + $product->setUrl($this->productPage, $this->controller); + }); + + return $products; } } diff --git a/components/categories/default.htm b/components/categories/default.htm index fc88d14..7e99e8a 100644 --- a/components/categories/default.htm +++ b/components/categories/default.htm @@ -1,7 +1,7 @@ diff --git a/components/products/product.htm b/components/products/product.htm index 727919a..0f01d00 100644 --- a/components/products/product.htm +++ b/components/products/product.htm @@ -3,7 +3,7 @@ {{ product.title }}
- +

{{ product.title }}

diff --git a/models/Category.php b/models/Category.php index e464e82..bbf68ad 100644 --- a/models/Category.php +++ b/models/Category.php @@ -36,4 +36,14 @@ class Category extends Model 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/Product.php b/models/Product.php index c6b86b8..148a9f7 100644 --- a/models/Product.php +++ b/models/Product.php @@ -42,4 +42,15 @@ public function getCategoryIdOptions($keyValue = null) { return Category::lists('title', 'id'); } + + public function setUrl($pageName, $controller) + { + $params = [ + 'id' => $this->id, + 'slug' => $this->slug, + ]; + + return $this->url = $controller->pageUrl($pageName, $params); + } + } From 1a549b0af4005c6dde615e55d844ca0a03f5102e Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 11 Oct 2014 06:41:23 +0100 Subject: [PATCH 07/45] We're in business! Well, nearly... Adds composer.json and register facade/service provider to get the Cart package running, and also adds a basket component to list the contents of a user's basket. Last but not least, strips out them ridiculous +/- buttons from the qty box and adds Ajax magic to the product forms such that things can be added to the basket. --- Plugin.php | 13 ++++ components/Basket.php | 60 +++++++++++++++++ components/basket/default.htm | 24 +++++++ components/product/default.htm | 18 +++--- components/products/product.htm | 6 +- composer.json | 16 +++++ composer.lock | 111 ++++++++++++++++++++++++++++++++ 7 files changed, 237 insertions(+), 11 deletions(-) create mode 100644 components/Basket.php create mode 100644 components/basket/default.htm create mode 100644 composer.json create mode 100644 composer.lock diff --git a/Plugin.php b/Plugin.php index e372661..50e359e 100644 --- a/Plugin.php +++ b/Plugin.php @@ -1,7 +1,9 @@ alias('Cart', '\Gloudemans\Shoppingcart\Facades\Cart'); + } + /** * Returns information about this plugin. * @@ -27,6 +39,7 @@ public function pluginDetails() public function registerComponents() { return [ + 'DShoreman\Shop\Components\Basket' => 'shopBasket', 'DShoreman\Shop\Components\Categories' => 'shopCategories', 'DShoreman\Shop\Components\Product' => 'shopProduct', 'DShoreman\Shop\Components\Products' => 'shopProducts', diff --git a/components/Basket.php b/components/Basket.php new file mode 100644 index 0000000..5d7344e --- /dev/null +++ b/components/Basket.php @@ -0,0 +1,60 @@ + 'Basket Component', + 'description' => 'No description provided yet...' + ]; + } + + public function defineProperties() + { + return [ + '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', + ], + ]; + } + + public function getProductPageOptions() + { + return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName'); + } + + public function onRun() + { + $this->productPage = $this->page['productPage'] = $this->property('productPage'); + + $this->basketItems = $this->page['basketItems'] = Cart::content(); + $this->basketCount = $this->page['basketCount'] = Cart::count(); + $this->basketTotal = $this->page['basketTotal'] = Cart::total(); + } + + public function onAddProduct() + { + $id = post('id'); + $quantity = post('quantity'); + + $product = ShopProduct::find($id); + + Cart::add($id, $product->title, $quantity, $product->price); + + $this->page['basketCount'] = Cart::count(); + $this->page['basketItems'] = Cart::content(); + $this->page['basketTotal'] = Cart::total(); + } + +} diff --git a/components/basket/default.htm b/components/basket/default.htm new file mode 100644 index 0000000..14d2368 --- /dev/null +++ b/components/basket/default.htm @@ -0,0 +1,24 @@ +

You have {{ basketCount }} item(s) in your basket!

+ + + + + + + + + + + + {% for item in basketItems %} + + + + + + + {% endfor %} + +
ProductQty.PriceSubtotal
{{ item.name }}{{ item.qty }}{{ item.price }}{{ item.subtotal }}
+ +

Total: £{{ basketTotal }}

diff --git a/components/product/default.htm b/components/product/default.htm index 27112f0..d8b242d 100644 --- a/components/product/default.htm +++ b/components/product/default.htm @@ -8,7 +8,11 @@

{{ product.title }}

{{ product.description|raw }} -
+

@@ -18,15 +22,9 @@

{{ product.title }}

- -
- - -
+
diff --git a/components/products/product.htm b/components/products/product.htm index 0f01d00..65e1203 100644 --- a/components/products/product.htm +++ b/components/products/product.htm @@ -11,7 +11,11 @@

{{ product.title }}

£{{ product.price }}
-
diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..a2e8a07 --- /dev/null +++ b/composer.json @@ -0,0 +1,16 @@ +{ + "name": "dshoreman/shop", + "description": "", + "homepage": "http://dsdev.io/", + "keywords": [], + "authors": [ + { + "name": "Dave Shoreman", + "email": "code@dsdev.io" + } + ], + "require": { + "php": ">=5.4.0", + "gloudemans/shoppingcart": "~1.2" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..f2e75c9 --- /dev/null +++ b/composer.lock @@ -0,0 +1,111 @@ +{ + "_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": "3f8e21fe939dc2f4ea92f36cab4fc4ce", + "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" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "platform": { + "php": ">=5.4.0" + }, + "platform-dev": [] +} From a282ee7b83a7f9163dabbb3f0168155d42658e9a Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 11 Oct 2014 13:15:44 +0100 Subject: [PATCH 08/45] Add orders backend bits --- Plugin.php | 7 ++++ controllers/Orders.php | 25 +++++++++++++ controllers/orders/_list_toolbar.htm | 3 ++ controllers/orders/config_form.yaml | 31 ++++++++++++++++ controllers/orders/config_list.yaml | 44 +++++++++++++++++++++++ controllers/orders/create.htm | 46 ++++++++++++++++++++++++ controllers/orders/index.htm | 2 ++ controllers/orders/preview.htm | 19 ++++++++++ controllers/orders/update.htm | 54 ++++++++++++++++++++++++++++ models/Address.php | 39 ++++++++++++++++++++ models/Order.php | 42 ++++++++++++++++++++++ models/address/columns.yaml | 29 +++++++++++++++ models/address/fields.yaml | 20 +++++++++++ models/order/columns.yaml | 16 +++++++++ models/order/fields.yaml | 34 ++++++++++++++++++ updates/create_addresses_table.php | 30 ++++++++++++++++ updates/create_orders_table.php | 34 ++++++++++++++++++ updates/version.yaml | 2 ++ 18 files changed, 477 insertions(+) create mode 100644 controllers/Orders.php create mode 100644 controllers/orders/_list_toolbar.htm create mode 100644 controllers/orders/config_form.yaml create mode 100644 controllers/orders/config_list.yaml create mode 100644 controllers/orders/create.htm create mode 100644 controllers/orders/index.htm create mode 100644 controllers/orders/preview.htm create mode 100644 controllers/orders/update.htm create mode 100644 models/Address.php create mode 100644 models/Order.php create mode 100644 models/address/columns.yaml create mode 100644 models/address/fields.yaml create mode 100644 models/order/columns.yaml create mode 100644 models/order/fields.yaml create mode 100644 updates/create_addresses_table.php create mode 100644 updates/create_orders_table.php diff --git a/Plugin.php b/Plugin.php index 50e359e..66d93ae 100644 --- a/Plugin.php +++ b/Plugin.php @@ -69,6 +69,12 @@ public function registerNavigation() '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'], + ], ], ], ]; @@ -79,6 +85,7 @@ 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"], ]; } diff --git a/controllers/Orders.php b/controllers/Orders.php new file mode 100644 index 0000000..14a02b3 --- /dev/null +++ b/controllers/Orders.php @@ -0,0 +1,25 @@ + + New Order +
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..eca89f7 --- /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 @@ + + + + +fatalError): ?> + + 'layout-item stretch layout-column']) ?> + + formRender() ?> + +
+
+ + + + or Cancel + +
+
+ + + + + +

fatalError) ?>

+

Return to orders list

+ + diff --git a/controllers/orders/index.htm b/controllers/orders/index.htm new file mode 100644 index 0000000..766877d --- /dev/null +++ b/controllers/orders/index.htm @@ -0,0 +1,2 @@ + +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 @@ + + + + +fatalError): ?> + +
+ formRenderPreview() ?> +
+ + + +

fatalError) ?>

+

Return to orders list

+ + diff --git a/controllers/orders/update.htm b/controllers/orders/update.htm new file mode 100644 index 0000000..5572332 --- /dev/null +++ b/controllers/orders/update.htm @@ -0,0 +1,54 @@ + + + + +fatalError): ?> + + 'layout-item stretch layout-column']) ?> + + formRender() ?> + +
+
+ + + + + or Cancel + +
+
+ + + + + +

fatalError) ?>

+

Return to orders list

+ + diff --git a/models/Address.php b/models/Address.php new file mode 100644 index 0000000..3780caf --- /dev/null +++ b/models/Address.php @@ -0,0 +1,39 @@ + ['DShoreman\Shop\Models\Address'], + 'shippingAddress' => ['DShoreman\Shop\Models\Address'], + ]; + public $hasMany = []; + public $belongsTo = []; + public $belongsToMany = []; + public $morphTo = []; + public $morphOne = []; + public $morphMany = []; + public $attachOne = []; + public $attachMany = []; + +} diff --git a/models/address/columns.yaml b/models/address/columns.yaml new file mode 100644 index 0000000..6633454 --- /dev/null +++ b/models/address/columns.yaml @@ -0,0 +1,29 @@ +# =================================== +# List Column Definitions +# =================================== + +columns: + id: + label: ID + searchable: true + name: + label: Name + searchable: true + street: + label: Street + searchable: true + invisible: true + town: + label: Town + searchable: true + county: + label: County + searchable: true + invisible: true + postcode: + label: Post Code + searchable: true + country: + label: Country + searchable: true + invisible: true diff --git a/models/address/fields.yaml b/models/address/fields.yaml new file mode 100644 index 0000000..73a1f9f --- /dev/null +++ b/models/address/fields.yaml @@ -0,0 +1,20 @@ +# =================================== +# Form Field Definitions +# =================================== + +fields: + id: + label: ID + disabled: true + name: + label: Name + street: + label: Street + town: + label: Town + county: + label: County + postcode: + label: Post Code + country: + label: Country diff --git a/models/order/columns.yaml b/models/order/columns.yaml new file mode 100644 index 0000000..7b3c2d8 --- /dev/null +++ b/models/order/columns.yaml @@ -0,0 +1,16 @@ +# =================================== +# List Column Definitions +# =================================== + +columns: + id: + label: ID + searchable: true + email: + label: Email Address + searchable: true + total: + label: Order Value + created_at: + label: Date Placed + searchable: true diff --git a/models/order/fields.yaml b/models/order/fields.yaml new file mode 100644 index 0000000..961c93b --- /dev/null +++ b/models/order/fields.yaml @@ -0,0 +1,34 @@ +# =================================== +# Form Field Definitions +# =================================== + +fields: + id: + label: ID + disabled: true + email: + label: Email Address + required: true + status: + label: Order Status + type: dropdown + options: + new: New + abort: Cancelled + paid: Paid + sent: Dispatched + rma: Return requested + refund: Refunded + billingAddress: + label: Billing Address + type: textarea + span: left + shippingAddress: + label: Shipping Address + type: textarea + span: right + items: + label: Ordered Items + disabled: true + total: + label: Order Total diff --git a/updates/create_addresses_table.php b/updates/create_addresses_table.php new file mode 100644 index 0000000..7ee3483 --- /dev/null +++ b/updates/create_addresses_table.php @@ -0,0 +1,30 @@ +engine = 'InnoDB'; + $table->increments('id'); + $table->string('name'); + $table->string('street'); + $table->string('town'); + $table->string('county'); + $table->string('postcode'); + $table->string('country'); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('dshoreman_shop_addresses'); + } + +} diff --git a/updates/create_orders_table.php b/updates/create_orders_table.php new file mode 100644 index 0000000..2d2e01d --- /dev/null +++ b/updates/create_orders_table.php @@ -0,0 +1,34 @@ +engine = 'InnoDB'; + $table->increments('id'); + $table->string('email'); + $table->string('stripe_token'); + $table->integer('billing_address')->unsigned(); + $table->integer('shipping_address')->unsigned(); + $table->text('items'); + $table->decimal('total', 7, 2); + $table->boolean('is_paid')->default(false); + $table->timestamps(); + + $table->foreign('billing_address')->references('id')->on('dshoreman_shop_addresses'); + $table->foreign('shipping_address')->references('id')->on('dshoreman_shop_addresses'); + }); + } + + public function down() + { + Schema::dropIfExists('dshoreman_shop_orders'); + } + +} diff --git a/updates/version.yaml b/updates/version.yaml index 1051802..dc676b8 100644 --- a/updates/version.yaml +++ b/updates/version.yaml @@ -2,3 +2,5 @@ - First version of Shop - create_categories_table.php - create_products_table.php + - create_addresses_table.php + - create_orders_table.php From 2a6610409adae1d849f6d99a444451fa38269702 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 11 Oct 2014 14:00:41 +0100 Subject: [PATCH 09/45] Add seed data, refreshing the plugin kills time --- updates/demo_seed.php | 68 +++++++++++++++++++++++++++++++++++++++++++ updates/version.yaml | 1 + 2 files changed, 69 insertions(+) create mode 100644 updates/demo_seed.php diff --git a/updates/demo_seed.php b/updates/demo_seed.php new file mode 100644 index 0000000..9266113 --- /dev/null +++ b/updates/demo_seed.php @@ -0,0 +1,68 @@ +createCategories([[ + 'Books', 'books', + 'Here be some books up fer grabs!' + ], + [ + 'Games & Consoles', 'games-consoles', + '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->description = $category[2]; + $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 index dc676b8..d931719 100644 --- a/updates/version.yaml +++ b/updates/version.yaml @@ -4,3 +4,4 @@ - create_products_table.php - create_addresses_table.php - create_orders_table.php + - demo_seed.php From fd211e7de2aa3638bd5e7cfcfd6f0324103155e7 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 11 Oct 2014 14:30:02 +0100 Subject: [PATCH 10/45] Remove addresses table, keep everything in orders After spending all that time sorting the addresses stuff, I noticed there'd be an issue creating the orders. Rather than faff about making the database happy with no address relations, we'll just ditch them. No reason they can't live with orders anyway. --- models/Address.php | 39 ------------------------------ models/address/columns.yaml | 29 ---------------------- models/address/fields.yaml | 20 --------------- updates/create_addresses_table.php | 30 ----------------------- updates/create_orders_table.php | 17 ++++++++----- updates/version.yaml | 1 - 6 files changed, 11 insertions(+), 125 deletions(-) delete mode 100644 models/Address.php delete mode 100644 models/address/columns.yaml delete mode 100644 models/address/fields.yaml delete mode 100644 updates/create_addresses_table.php diff --git a/models/Address.php b/models/Address.php deleted file mode 100644 index 3780caf..0000000 --- a/models/Address.php +++ /dev/null @@ -1,39 +0,0 @@ -engine = 'InnoDB'; - $table->increments('id'); - $table->string('name'); - $table->string('street'); - $table->string('town'); - $table->string('county'); - $table->string('postcode'); - $table->string('country'); - $table->timestamps(); - }); - } - - public function down() - { - Schema::dropIfExists('dshoreman_shop_addresses'); - } - -} diff --git a/updates/create_orders_table.php b/updates/create_orders_table.php index 2d2e01d..213b31d 100644 --- a/updates/create_orders_table.php +++ b/updates/create_orders_table.php @@ -13,16 +13,21 @@ public function up() $table->engine = 'InnoDB'; $table->increments('id'); $table->string('email'); - $table->string('stripe_token'); - $table->integer('billing_address')->unsigned(); - $table->integer('shipping_address')->unsigned(); $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('shipping_name'); + $table->string('shipping_street'); + $table->string('shipping_town'); + $table->string('shipping_county'); + $table->string('shipping_postcode'); $table->timestamps(); - - $table->foreign('billing_address')->references('id')->on('dshoreman_shop_addresses'); - $table->foreign('shipping_address')->references('id')->on('dshoreman_shop_addresses'); }); } diff --git a/updates/version.yaml b/updates/version.yaml index d931719..786a37a 100644 --- a/updates/version.yaml +++ b/updates/version.yaml @@ -2,6 +2,5 @@ - First version of Shop - create_categories_table.php - create_products_table.php - - create_addresses_table.php - create_orders_table.php - demo_seed.php From 7ec4b9ab7e46deec7c5758c93317a2645781a316 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Thu, 16 Oct 2014 05:32:27 +0100 Subject: [PATCH 11/45] Add payment stuff on the frontend with Stripe --- components/Basket.php | 18 ++++++++++ composer.json | 3 +- composer.lock | 54 +++++++++++++++++++++++++++-- routes.php | 60 +++++++++++++++++++++++++++++++++ updates/create_orders_table.php | 2 ++ 5 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 routes.php diff --git a/components/Basket.php b/components/Basket.php index 5d7344e..1a5c9b0 100644 --- a/components/Basket.php +++ b/components/Basket.php @@ -1,8 +1,11 @@ basketItems = $this->page['basketItems'] = Cart::content(); $this->basketCount = $this->page['basketCount'] = Cart::count(); $this->basketTotal = $this->page['basketTotal'] = Cart::total(); + + if (Session::has('orderId')) { + $this->orderId = $this->page['orderId'] = Session::get('orderId'); + } } public function onAddProduct() @@ -57,4 +64,15 @@ public function onAddProduct() $this->page['basketTotal'] = Cart::total(); } + public function onCheckout() + { + $order = new ShopOrder; + $order->items = json_encode(Cart::content()->toArray()); + $order->total = Cart::total(); + $order->save(); + + Session::put('orderId', $order->id); + + return Redirect::to('shop/checkout/payment'); + } } diff --git a/composer.json b/composer.json index a2e8a07..ef549bc 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ ], "require": { "php": ">=5.4.0", - "gloudemans/shoppingcart": "~1.2" + "gloudemans/shoppingcart": "~1.2", + "stripe/stripe-php": "dev-master" } } diff --git a/composer.lock b/composer.lock index f2e75c9..cc0f0ea 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "3f8e21fe939dc2f4ea92f36cab4fc4ce", + "hash": "fd3287e12c88ec7cdc32cea7e986b9c7", "packages": [ { "name": "gloudemans/shoppingcart", @@ -97,12 +97,62 @@ } ], "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": [], + "stability-flags": { + "stripe/stripe-php": 20 + }, "prefer-stable": false, "platform": { "php": ">=5.4.0" diff --git a/routes.php b/routes.php new file mode 100644 index 0000000..1d1e1be --- /dev/null +++ b/routes.php @@ -0,0 +1,60 @@ + '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'); + + Stripe::setApiKey('sk_test_NHbBmLzRSL7G06gpyLVraQ2Z'); + + 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_orders_table.php b/updates/create_orders_table.php index 213b31d..8a5871a 100644 --- a/updates/create_orders_table.php +++ b/updates/create_orders_table.php @@ -22,11 +22,13 @@ public function up() $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(); }); } From 822fb52fab03e23547b477d8c6e25f406bcbeb93 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Thu, 16 Oct 2014 05:33:54 +0100 Subject: [PATCH 12/45] More detail on backend order list Adds checkboxes because I forgot before. Also adds address columns, most of which will be invisible by default, and early order status in the form of a boolean is_paid column. --- controllers/orders/config_list.yaml | 2 +- models/order/columns.yaml | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/controllers/orders/config_list.yaml b/controllers/orders/config_list.yaml index eca89f7..7356a3b 100644 --- a/controllers/orders/config_list.yaml +++ b/controllers/orders/config_list.yaml @@ -32,7 +32,7 @@ defaultSort: direction: desc # Display checkboxes next to each record -# showCheckboxes: true +showCheckboxes: true # Toolbar widget configuration toolbar: diff --git a/models/order/columns.yaml b/models/order/columns.yaml index 7b3c2d8..6965e6d 100644 --- a/models/order/columns.yaml +++ b/models/order/columns.yaml @@ -9,8 +9,32 @@ columns: email: label: Email Address searchable: true + shipping_name: + label: Name + searchable: true + shipping_street: + label: Street + searchable: true + invisible: true + shipping_town: + label: Town + searchable: true + shipping_county: + label: County + searchable: true + invisible: true + shipping_postcode: + label: Post Code + searchable: true + invisible: true + shipping_country: + label: Country + searchable: true total: label: Order Value + is_paid: + label: Status + searchable: true created_at: label: Date Placed searchable: true From e62313906a5976e5d1f79162e919078143e485b4 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Thu, 16 Oct 2014 06:27:12 +0100 Subject: [PATCH 13/45] Update backend order addresses Strips out old address model stuff since they were ditched in favour of dumping everything in one table. Moves the address textarea to its own tab, and splits it out into multiple fields instead. Would be nice if I could find a nice way of adding titles to each side though, rather than prefixing every field with 'Postage' or 'Payment'. --- models/Order.php | 5 +--- models/order/fields.yaml | 59 ++++++++++++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/models/Order.php b/models/Order.php index 2e69ddf..ceebc10 100644 --- a/models/Order.php +++ b/models/Order.php @@ -26,10 +26,7 @@ class Order extends Model /** * @var array Relations */ - public $hasOne = [ - 'billingAddress' => ['DShoreman\Shop\Models\Address'], - 'shippingAddress' => ['DShoreman\Shop\Models\Address'], - ]; + public $hasOne = []; public $hasMany = []; public $belongsTo = []; public $belongsToMany = []; diff --git a/models/order/fields.yaml b/models/order/fields.yaml index 961c93b..85cb4c3 100644 --- a/models/order/fields.yaml +++ b/models/order/fields.yaml @@ -19,16 +19,59 @@ fields: sent: Dispatched rma: Return requested refund: Refunded - billingAddress: - label: Billing Address - type: textarea - span: left - shippingAddress: - label: Shipping Address - type: textarea - span: right items: label: Ordered Items disabled: true total: label: Order Total + +tabs: + fields: + billingName: + label: Payment Name + span: right + tab: Addresses + shippingName: + label: Postage Name + span: left + tab: Addresses + billingStreet: + label: Payment Address + span: right + tab: Addresses + shippingStreet: + label: Postage Address + span: left + tab: Addresses + billingTown: + label: Payment Town + span: right + tab: Addresses + shippingTown: + label: Postage Town + span: left + tab: Addresses + billingCounty: + label: Payment County + span: right + tab: Addresses + shippingCounty: + label: Postage County + span: left + tab: Addresses + billingPostcode: + label: Payment Post Code + span: right + tab: Addresses + shippingPostcode: + label: Postage Post Code + span: left + tab: Addresses + billingCountry: + label: Payment Country + span: right + tab: Addresses + shippingCountry: + label: Postage Country + span: left + tab: Addresses From 440e8b2e3a8953206332edc09bea4eca02a39122 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Thu, 16 Oct 2014 06:48:15 +0100 Subject: [PATCH 14/45] More field rejigging Adds created/updated at dates, moves items and total to their own tab, and rearranges the top fields so they look somewhat presentable. --- models/order/fields.yaml | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/models/order/fields.yaml b/models/order/fields.yaml index 85cb4c3..ab6fc3d 100644 --- a/models/order/fields.yaml +++ b/models/order/fields.yaml @@ -4,14 +4,19 @@ fields: id: - label: ID + label: Order ID + span: left disabled: true - email: - label: Email Address - required: true + createdAt: + label: Date placed + span: right + updatedAt: + label: Date updated + span: right status: - label: Order Status + label: Current Status type: dropdown + span: left options: new: New abort: Cancelled @@ -19,11 +24,9 @@ fields: sent: Dispatched rma: Return requested refund: Refunded - items: - label: Ordered Items - disabled: true - total: - label: Order Total + email: + label: Email Address + required: true tabs: fields: @@ -75,3 +78,12 @@ tabs: label: Postage Country span: left tab: Addresses + + items: + label: Order Items + tab: Items + total: + label: Order Value + span: right + tab: Items + disabled: true From cea2b8325b6e14c433c75d708152728ccdb000d8 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Fri, 17 Oct 2014 19:53:12 +0100 Subject: [PATCH 15/45] Fix create/update dates in backend order update Also disiables the fields, as they're handled automatically --- models/order/fields.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/models/order/fields.yaml b/models/order/fields.yaml index ab6fc3d..9520fbe 100644 --- a/models/order/fields.yaml +++ b/models/order/fields.yaml @@ -7,12 +7,14 @@ fields: label: Order ID span: left disabled: true - createdAt: + created_at: label: Date placed span: right - updatedAt: + disabled: true + updated_at: label: Date updated span: right + disabled: true status: label: Current Status type: dropdown From 4def26df96d751fea9bdc354ccddbf9c2b6d85ff Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Fri, 17 Oct 2014 21:11:26 +0100 Subject: [PATCH 16/45] Add item grid to backend order form Creates a new form widget for that lists items, and uses this on the backend to display order items. Read-only at the moment, but it's better than nothing... Also set a blank label for the field --- Plugin.php | 10 ++++++++ formwidgets/ItemGrid.php | 28 +++++++++++++++++++++ formwidgets/itemgrid/partials/_itemgrid.htm | 22 ++++++++++++++++ models/order/fields.yaml | 3 ++- 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 formwidgets/ItemGrid.php create mode 100644 formwidgets/itemgrid/partials/_itemgrid.htm diff --git a/Plugin.php b/Plugin.php index 66d93ae..2615bf9 100644 --- a/Plugin.php +++ b/Plugin.php @@ -80,6 +80,16 @@ public function registerNavigation() ]; } + public function registerFormWidgets() + { + return [ + 'Dshoreman\Shop\FormWidgets\ItemGrid' => [ + 'label' => 'Order Item Grid', + 'alias' => 'itemgrid', + ], + ]; + } + public function registerPermissions() { return [ 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 #NamePriceQuantitySubtotal
id ?>name ?>price ?>qty ?>subtotal ?>
diff --git a/models/order/fields.yaml b/models/order/fields.yaml index 9520fbe..295c40c 100644 --- a/models/order/fields.yaml +++ b/models/order/fields.yaml @@ -82,8 +82,9 @@ tabs: tab: Addresses items: - label: Order Items + label: tab: Items + type: itemgrid total: label: Order Value span: right From e9f5a6635b104eb9323f7890251f53687ad28b4e Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Fri, 17 Oct 2014 21:30:30 +0100 Subject: [PATCH 17/45] Form rearrangement Move addresses to two separate tabs, and rename items tab to products, shifting its position to be the first tab. All's left now is to shift the order "value" to the main area in a scoreboard, and rename to Order Total --- models/order/fields.yaml | 91 ++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 51 deletions(-) diff --git a/models/order/fields.yaml b/models/order/fields.yaml index 295c40c..0bffdb0 100644 --- a/models/order/fields.yaml +++ b/models/order/fields.yaml @@ -32,61 +32,50 @@ fields: tabs: fields: - billingName: - label: Payment Name - span: right - tab: Addresses - shippingName: - label: Postage Name - span: left - tab: Addresses - billingStreet: - label: Payment Address - span: right - tab: Addresses - shippingStreet: - label: Postage Address - span: left - tab: Addresses - billingTown: - label: Payment Town - span: right - tab: Addresses - shippingTown: - label: Postage Town - span: left - tab: Addresses - billingCounty: - label: Payment County - span: right - tab: Addresses - shippingCounty: - label: Postage County - span: left - tab: Addresses - billingPostcode: - label: Payment Post Code - span: right - tab: Addresses - shippingPostcode: - label: Postage Post Code - span: left - tab: Addresses - billingCountry: - label: Payment Country - span: right - tab: Addresses - shippingCountry: - label: Postage Country - span: left - tab: Addresses - items: label: - tab: Items + tab: Products type: itemgrid total: label: Order Value span: right - tab: Items + tab: Products disabled: true + + billingName: + label: Name + tab: Payment Address + billingStreet: + label: Address + tab: Payment Address + billingTown: + label: Town + tab: Payment Address + billingCounty: + label: County + tab: Payment Address + billingPostcode: + label: Post Code + tab: Payment Address + billingCountry: + label: Country + tab: Payment Address + + shippingName: + label: Name + tab: Postage Address + shippingStreet: + label: Address + tab: Postage Address + shippingTown: + label: Town + tab: Postage Address + shippingCounty: + label: County + tab: Postage Address + shippingPostcode: + label: Post Code + tab: Postage Address + shippingCountry: + label: Country + tab: Postage Address From 14eebdd8a834cc40b7a88667dd054a1ae4d82d82 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Fri, 17 Oct 2014 23:50:58 +0100 Subject: [PATCH 18/45] Replace random fields with scoreboard in order This strips out all the non-tabbed fields in addition to the order value field that was lurking below the items widget. Instead of half a dozen fields over 3 lines, there's now a basic scoreboard that shows all the same data, which will always be read-only anyway. --- controllers/Orders.php | 16 +++++++++++++ controllers/orders/update.htm | 44 ++++++++++++++++++++++++++++++++++- models/order/fields.yaml | 31 ------------------------ 3 files changed, 59 insertions(+), 32 deletions(-) diff --git a/controllers/Orders.php b/controllers/Orders.php index 14a02b3..566e8f2 100644 --- a/controllers/Orders.php +++ b/controllers/Orders.php @@ -2,6 +2,7 @@ use BackendMenu; use Backend\Classes\Controller; +use Dshoreman\Shop\Models\Order; /** * Orders Back-end Controller @@ -22,4 +23,19 @@ public function __construct() BackendMenu::setContext('DShoreman.Shop', 'shop', 'orders'); } + + 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/orders/update.htm b/controllers/orders/update.htm index 5572332..8e5c43e 100644 --- a/controllers/orders/update.htm +++ b/controllers/orders/update.htm @@ -4,9 +4,51 @@
  • pageTitle) ?>
  • - fatalError): ?> +
    +
    + +
    +

    Order #

    +

    id ?>

    +
    +
    +

    Customer

    +

    + billing_name ?> +

    +

    + Email: email ?> +

    +
    +
    +

    Current Status

    + is_paid + ? '

    Paid

    ' + : '

    Pending

    ' + ?> +

    +
    +
    +

    Total Value

    +

    + £total ?> +

    +

    + for items +

    +
    +
    +

    Date Placed

    +

    created_at ?>

    +

    + Updated updated_at ?> +

    +
    +
    +
    + 'layout-item stretch layout-column']) ?> formRender() ?> diff --git a/models/order/fields.yaml b/models/order/fields.yaml index 0bffdb0..6818bb2 100644 --- a/models/order/fields.yaml +++ b/models/order/fields.yaml @@ -3,32 +3,6 @@ # =================================== fields: - id: - label: Order ID - span: left - disabled: true - created_at: - label: Date placed - span: right - disabled: true - updated_at: - label: Date updated - span: right - disabled: true - status: - label: Current Status - type: dropdown - span: left - options: - new: New - abort: Cancelled - paid: Paid - sent: Dispatched - rma: Return requested - refund: Refunded - email: - label: Email Address - required: true tabs: fields: @@ -36,11 +10,6 @@ tabs: label: tab: Products type: itemgrid - total: - label: Order Value - span: right - tab: Products - disabled: true billingName: label: Name From 413d40263a93365ecf93547627f1597623507188 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Fri, 17 Oct 2014 23:56:44 +0100 Subject: [PATCH 19/45] Update address field names No idea why, but addresses were camelCase. Fix the fields to use snake_case instead, and voila! The update form works properly. --- models/order/fields.yaml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/models/order/fields.yaml b/models/order/fields.yaml index 0bffdb0..0aa2235 100644 --- a/models/order/fields.yaml +++ b/models/order/fields.yaml @@ -42,40 +42,40 @@ tabs: tab: Products disabled: true - billingName: + billing_name: label: Name tab: Payment Address - billingStreet: + billing_street: label: Address tab: Payment Address - billingTown: + billing_town: label: Town tab: Payment Address - billingCounty: + billing_county: label: County tab: Payment Address - billingPostcode: + billing_postcode: label: Post Code tab: Payment Address - billingCountry: + billing_country: label: Country tab: Payment Address - shippingName: + shipping_name: label: Name tab: Postage Address - shippingStreet: + shipping_street: label: Address tab: Postage Address - shippingTown: + shipping_town: label: Town tab: Postage Address - shippingCounty: + shipping_county: label: County tab: Postage Address - shippingPostcode: + shipping_postcode: label: Post Code tab: Postage Address - shippingCountry: + shipping_country: label: Country tab: Postage Address From d3fbc5e514dc79abc295bf2b3c03a7d38c2a40d0 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 18 Oct 2014 01:16:42 +0100 Subject: [PATCH 20/45] Add Stripe API Key settings to close #11 Removes the hardcoded API key and replaces it with a new Settings page in the backend. Also adds a setting for the active keyset, in case anyone wants to use test mode while in production. --- Plugin.php | 14 ++++++++++++++ models/Settings.php | 13 +++++++++++++ models/settings/fields.yaml | 26 ++++++++++++++++++++++++++ routes.php | 5 ++++- 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 models/Settings.php create mode 100644 models/settings/fields.yaml diff --git a/Plugin.php b/Plugin.php index 2615bf9..8106bfc 100644 --- a/Plugin.php +++ b/Plugin.php @@ -99,4 +99,18 @@ public function registerPermissions() ]; } + 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/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() { @@ -32,7 +33,9 @@ $order->shipping_postcode = post('stripeShippingAddressZip'); $order->shipping_country = post('stripeShippingAddressCountry'); - Stripe::setApiKey('sk_test_NHbBmLzRSL7G06gpyLVraQ2Z'); + 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([ From 0eb8c5fb776b502a7aff7f34229444a9d0759a23 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 18 Oct 2014 01:27:28 +0100 Subject: [PATCH 21/45] Autoset qty if not defined by user, closes #3 If you try passing a null qty to the Cart class, it bails. We get round that simply by falling back to a qty of 1 if it's not set in the post vars. --- components/Basket.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Basket.php b/components/Basket.php index 1a5c9b0..1b43acf 100644 --- a/components/Basket.php +++ b/components/Basket.php @@ -53,7 +53,7 @@ public function onRun() public function onAddProduct() { $id = post('id'); - $quantity = post('quantity'); + $quantity = post('quantity') ?: 1; $product = ShopProduct::find($id); From 19aa42cbd3f82f24d191f6c956b90857de5023cb Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 18 Oct 2014 02:17:51 +0100 Subject: [PATCH 22/45] Remove basket items that don't exist, closes #5 There's always the chance someone could add something to their basket, only for the store owner to delete it before they get to checkout. Rather than having non-existant items added to orders, this code will remove any missing products from the basket and go back to the checkout page so they have chance to decide whether they still want to order. --- components/Basket.php | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/components/Basket.php b/components/Basket.php index 1b43acf..1f8c040 100644 --- a/components/Basket.php +++ b/components/Basket.php @@ -1,6 +1,7 @@ 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(); @@ -75,4 +92,21 @@ public function onCheckout() return Redirect::to('shop/checkout/payment'); } + + 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; + } + } From 597cb7551c38c314d4796cd1e4bbeb0934ed76b2 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 18 Oct 2014 03:52:36 +0100 Subject: [PATCH 23/45] Update version This is pre-release shit, there will more than likely be at least a couple pre-1.0 versions before the "actual" release. --- updates/version.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/updates/version.yaml b/updates/version.yaml index 786a37a..f51bf2b 100644 --- a/updates/version.yaml +++ b/updates/version.yaml @@ -1,4 +1,4 @@ -1.0.1: +0.1.0: - First version of Shop - create_categories_table.php - create_products_table.php From 4a2c4c757b680616688586b85011e9470dc3c230 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 18 Oct 2014 14:06:20 +0100 Subject: [PATCH 24/45] Add class options to generated category lists Convert the other components to take this sort of format at some point, too. Much better this way, just basic markup but the user can apply their own classes, or Bootstrap's, for easy styling without having to reinvent the wheel themselves with markup. --- components/Categories.php | 21 ++++++++++++++++++++- components/categories/default.htm | 4 ++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/components/Categories.php b/components/Categories.php index 1cdeab1..5eec076 100644 --- a/components/Categories.php +++ b/components/Categories.php @@ -25,6 +25,18 @@ public function defineProperties() 'default' => 'shop/category', 'group' => 'Links', ], + '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', + ], ]; } @@ -35,11 +47,18 @@ public function getCategoryPageOptions() public function onRun() { - $this->categoryPage = $this->page['categoryPage'] = $this->property('categoryPage'); + $this->prepareVars(); $this->categories = $this->page['categories'] = $this->listCategories(); } + public function prepareVars() + { + $this->categoryPage = $this->page['categoryPage'] = $this->property('categoryPage'); + $this->listClass = $this->page['listClass'] = $this->property('listClass'); + $this->listItemClass = $this->page['listItemClass'] = $this->property('listItemClass'); + } + public function listCategories() { $categories = ShopCategory::all(); diff --git a/components/categories/default.htm b/components/categories/default.htm index 7e99e8a..e4215b1 100644 --- a/components/categories/default.htm +++ b/components/categories/default.htm @@ -1,6 +1,6 @@ -
        +
          {% for category in categories %} -
        • +
        • {{ category.title }}
        • {% endfor %} From ff0e4db8dfbe0a36eee999f108e53da09d2864fd Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 18 Oct 2014 14:09:50 +0100 Subject: [PATCH 25/45] Check category exists before loading products The demo theme is using the category page as the page that lists products as well, since it makes no sense to *just* have a page that lists the categories like I did in dev. This extra bit of code makes sure a category has been passed in before trying to load any products to prevent errors. This whole one-page-deal should nicely open the doors for filters too. --- components/Products.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/Products.php b/components/Products.php index a270cbf..11d1145 100644 --- a/components/Products.php +++ b/components/Products.php @@ -45,7 +45,10 @@ public function onRun() $this->productPage = $this->page['productPage'] = $this->property('productPage'); $this->category = $this->page['category'] = $this->loadCategory(); - $this->products = $this->page['products'] = $this->listProducts(); + + if ($this->category) { + $this->products = $this->page['products'] = $this->listProducts(); + } } public function loadCategory() From 726953f7851a2d3fdc8c9d1b6764e2c9feccdefb Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 18 Oct 2014 14:12:06 +0100 Subject: [PATCH 26/45] "Property-ise" the redirect URL on checkout For some reason I hard-coded the redirect URL. Probably laziness. This finally adds a property to the basket component so the URL can be overridden by the user and other themes. --- components/Basket.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/components/Basket.php b/components/Basket.php index 1f8c040..a11eab4 100644 --- a/components/Basket.php +++ b/components/Basket.php @@ -23,6 +23,12 @@ public function componentDetails() 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.', @@ -40,6 +46,12 @@ public function getProductPageOptions() public function onRun() { + $this->prepareVars(); + } + + public function prepareVars() + { + $this->paymentPage = $this->page['paymentPage'] = $this->property('paymentPage'); $this->productPage = $this->page['productPage'] = $this->property('productPage'); $this->basketItems = $this->page['basketItems'] = Cart::content(); @@ -67,6 +79,8 @@ public function onAddProduct() public function onCheckout() { + $this->prepareVars(); + $this->stripMissingItems(); if ($this->items_removed > 0) { @@ -90,7 +104,7 @@ public function onCheckout() Session::put('orderId', $order->id); - return Redirect::to('shop/checkout/payment'); + return Redirect::to($this->paymentPage); } protected function stripMissingItems() From 151b41fb1426ef8d36ad73d45d052df44eedb101 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 18 Oct 2014 17:49:50 +0100 Subject: [PATCH 27/45] Add optional product count to category lists Two new options for displaying the number of products contained within a category. First simply enables it, second allows the user to assign a class, such as 'badge'. Also adds the products relation to the Catgory model, which apparently was missing before. --- components/Categories.php | 13 +++++++++++++ components/categories/default.htm | 9 ++++++++- models/Category.php | 4 +++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/components/Categories.php b/components/Categories.php index 5eec076..112e3c7 100644 --- a/components/Categories.php +++ b/components/Categories.php @@ -25,6 +25,17 @@ public function defineProperties() '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.', @@ -55,6 +66,8 @@ public function onRun() 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'); } diff --git a/components/categories/default.htm b/components/categories/default.htm index e4215b1..d5dafd0 100644 --- a/components/categories/default.htm +++ b/components/categories/default.htm @@ -1,7 +1,14 @@ diff --git a/models/Category.php b/models/Category.php index bbf68ad..495a70e 100644 --- a/models/Category.php +++ b/models/Category.php @@ -27,7 +27,9 @@ class Category extends Model * @var array Relations */ public $hasOne = []; - public $hasMany = []; + public $hasMany = [ + 'products' => ['Dshoreman\Shop\Models\Product'], + ]; public $belongsTo = []; public $belongsToMany = []; public $morphTo = []; From cc0781f8a687a4f1c18f065d88fb9dddb80a9447 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 18 Oct 2014 18:19:07 +0100 Subject: [PATCH 28/45] Add support for active class Also renames the category variable used inside the loop, for obvious reasons. --- components/categories/default.htm | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/components/categories/default.htm b/components/categories/default.htm index d5dafd0..8254ead 100644 --- a/components/categories/default.htm +++ b/components/categories/default.htm @@ -1,14 +1,17 @@ From a6ce7802ef118956fa20f1b58c2bd96f711d9e73 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 18 Oct 2014 22:02:52 +0100 Subject: [PATCH 29/45] Cleanup product partials and add a ton of options Adds class parameters for pretty much everything, and removes the non-template stuff like the category title and description. That should be added on the page/layout, not in a partial, otherwise ain't gonna be able to reuse this shit later for other things, such as featured products. --- components/Products.php | 64 +++++++++++++++++++++++++++++++-- components/products/default.htm | 9 +++-- components/products/product.htm | 40 ++++++++++----------- 3 files changed, 85 insertions(+), 28 deletions(-) diff --git a/components/Products.php b/components/Products.php index 11d1145..d205177 100644 --- a/components/Products.php +++ b/components/Products.php @@ -25,6 +25,12 @@ public function defineProperties() 'type' => 'string', 'default' => '' ], + 'basketContainer' => [ + 'title' => 'Basket container element to update when adding products to cart', + ], + 'addButtonText' => [ + 'title' => 'Buy button text', + ], 'productPage' => [ 'title' => 'Product Page', 'description' => 'Name of the product page for the product titles. This property is used by the default component partial.', @@ -32,6 +38,43 @@ public function defineProperties() 'default' => 'shop/product', 'group' => 'Links', ], + 'imageClass' => [ + 'title' => 'Image', + 'group' => 'CSS Classes', + ], + 'rowClass' => [ + 'title' => 'Row', + 'group' => 'CSS Classes', + 'default' => 'row', + ], + 'productColumnClass' => [ + 'title' => 'Product column', + 'group' => 'CSS Classes', + ], + 'productContainerClass' => [ + 'title' => 'Product container', + 'group' => 'CSS Classes', + ], + 'detailContainerClass' => [ + 'title' => 'Detail container', + 'group' => 'CSS Classes', + ], + 'priceClass' => [ + 'title' => 'Price', + 'group' => 'CSS Classes', + ], + 'priceContainerClass' => [ + 'title' => 'Price container', + 'group' => 'CSS Classes', + ], + 'addButtonClass' => [ + 'title' => 'Buy button', + 'group' => 'CSS Classes', + ], + 'addButtonContainerClass' => [ + 'title' => 'Buy button container', + 'group' => 'CSS Classes', + ], ]; } @@ -42,15 +85,30 @@ public function getProductPageOptions() public function onRun() { - $this->productPage = $this->page['productPage'] = $this->property('productPage'); - - $this->category = $this->page['category'] = $this->loadCategory(); + $this->prepareVars(); if ($this->category) { $this->products = $this->page['products'] = $this->listProducts(); } } + public function prepareVars() + { + $this->productPage = $this->page['productPage'] = $this->property('productPage'); + $this->category = $this->page['category'] = $this->loadCategory(); + + $this->productColumnClass = $this->page['productColumnClass'] = $this->property('productColumnClass'); + $this->productContainerClass = $this->page['productContainerClass'] = $this->property('productContainerClass'); + $this->imageClass = $this->page['imageClass'] = $this->property('imageClass'); + $this->detailContainerClass = $this->page['detailContainerClass'] = $this->property('detailContainerClass'); + $this->rowClass = $this->page['rowClass'] = $this->property('rowClass'); + $this->priceContainerClass = $this->page['priceContainerClass'] = $this->property('priceContainerClass'); + $this->addButtonContainerClass = $this->page['addButtonContainerClass'] = $this->property('addButtonContainerClass'); + $this->addButtonClass = $this->page['addButtonClass'] = $this->property('addButtonClass'); + $this->basketContainer = $this->page['basketContainer'] = $this->property('basketContainer'); + $this->addButtonText = $this->page['addButtonText'] = $this->property('addButtonText'); + } + public function loadCategory() { if (!$categoryId = $this->propertyOrParam('categoryFilter')) diff --git a/components/products/default.htm b/components/products/default.htm index e9ca5e8..b61d687 100644 --- a/components/products/default.htm +++ b/components/products/default.htm @@ -1,8 +1,7 @@ -

            {{ category.title }}

            -

            {{ category.description }}

            - -
            +
            {% for product in products %} - {% partial 'shopProducts::product' product=product %} +
            + {% partial 'shopProducts::product' product=product %} +
            {% endfor %}
            diff --git a/components/products/product.htm b/components/products/product.htm index 65e1203..4710664 100644 --- a/components/products/product.htm +++ b/components/products/product.htm @@ -1,24 +1,24 @@ -
            -
            - {{ product.title }} +
            + {{ product.title }} -
            - -

            {{ product.title }}

            -
            -
            -
            -
            £{{ product.price }}
            -
            -
            - -
            +
            + +

            {{ product.title }}

            +
            +
            +
            +
            £{{ product.price }}
            +
            +
            +
            From ee989ed770eb3d220d2c52ba550b7fdb434a2fe4 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sun, 19 Oct 2014 18:11:47 +0100 Subject: [PATCH 30/45] Parameterise the product component Adds all the necessary new parameters to the Product component, changing out the hard-coded versions in the view at the same time. A lot of these are reused on the Products (plural) component, so this changes that to extend the singular Product component instead, thus inheriting many of the parameters. --- components/Product.php | 79 +++++++++++++++++++++++++++++++++- components/Products.php | 56 +++--------------------- components/product/default.htm | 32 +++++++------- 3 files changed, 100 insertions(+), 67 deletions(-) diff --git a/components/Product.php b/components/Product.php index c4925cb..8740535 100644 --- a/components/Product.php +++ b/components/Product.php @@ -23,13 +23,90 @@ public function defineProperties() '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', + ], + 'rowClass' => [ + 'title' => 'Row', + 'group' => 'CSS Classes', + 'default' => 'row', + ], + 'imageClass' => [ + 'title' => 'Image', + 'group' => 'CSS Classes', + ], + 'productContainerClass' => [ + 'title' => 'Product 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() { + 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->productContainerClass = $this->page['productContainerClass'] = $this->property('productContainerClass'); + $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'); diff --git a/components/Products.php b/components/Products.php index d205177..a813a73 100644 --- a/components/Products.php +++ b/components/Products.php @@ -5,7 +5,7 @@ use DShoreman\Shop\Models\Category as ShopCategory; use DShoreman\Shop\Models\Product as ShopProduct; -class Products extends ComponentBase +class Products extends Product { public function componentDetails() @@ -18,19 +18,13 @@ public function componentDetails() public function defineProperties() { - return [ + 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' => '' ], - 'basketContainer' => [ - 'title' => 'Basket container element to update when adding products to cart', - ], - 'addButtonText' => [ - 'title' => 'Buy button text', - ], 'productPage' => [ 'title' => 'Product Page', 'description' => 'Name of the product page for the product titles. This property is used by the default component partial.', @@ -38,44 +32,11 @@ public function defineProperties() 'default' => 'shop/product', 'group' => 'Links', ], - 'imageClass' => [ - 'title' => 'Image', - 'group' => 'CSS Classes', - ], - 'rowClass' => [ - 'title' => 'Row', - 'group' => 'CSS Classes', - 'default' => 'row', - ], 'productColumnClass' => [ 'title' => 'Product column', 'group' => 'CSS Classes', ], - 'productContainerClass' => [ - 'title' => 'Product container', - 'group' => 'CSS Classes', - ], - 'detailContainerClass' => [ - 'title' => 'Detail container', - 'group' => 'CSS Classes', - ], - 'priceClass' => [ - 'title' => 'Price', - 'group' => 'CSS Classes', - ], - 'priceContainerClass' => [ - 'title' => 'Price container', - 'group' => 'CSS Classes', - ], - 'addButtonClass' => [ - 'title' => 'Buy button', - 'group' => 'CSS Classes', - ], - 'addButtonContainerClass' => [ - 'title' => 'Buy button container', - 'group' => 'CSS Classes', - ], - ]; + ]); } public function getProductPageOptions() @@ -94,19 +55,12 @@ public function onRun() 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'); - $this->productContainerClass = $this->page['productContainerClass'] = $this->property('productContainerClass'); - $this->imageClass = $this->page['imageClass'] = $this->property('imageClass'); - $this->detailContainerClass = $this->page['detailContainerClass'] = $this->property('detailContainerClass'); - $this->rowClass = $this->page['rowClass'] = $this->property('rowClass'); - $this->priceContainerClass = $this->page['priceContainerClass'] = $this->property('priceContainerClass'); - $this->addButtonContainerClass = $this->page['addButtonContainerClass'] = $this->property('addButtonContainerClass'); - $this->addButtonClass = $this->page['addButtonClass'] = $this->property('addButtonClass'); - $this->basketContainer = $this->page['basketContainer'] = $this->property('basketContainer'); - $this->addButtonText = $this->page['addButtonText'] = $this->property('addButtonText'); } public function loadCategory() diff --git a/components/product/default.htm b/components/product/default.htm index d8b242d..c575cbe 100644 --- a/components/product/default.htm +++ b/components/product/default.htm @@ -1,8 +1,8 @@ -
            -
            - +
            +
            + {{ product.title }}
            -
            +

            {{ product.title }}

            @@ -10,27 +10,29 @@

            {{ product.title }}

            -
            -
            -

            +

            +
            +

            £ {{ product.price }}

            -
            - -
            - + +
            +
            -
            -
            - +
            +
            +
            From 3afc228ad17b117a6e66c79ac3691fc1b0c672bd Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sun, 19 Oct 2014 20:53:23 +0100 Subject: [PATCH 31/45] Cleanup basket component Remove the "you have x items" message, and moves the total into the table. Finally, change out all the classes for parameters. --- components/Basket.php | 32 ++++++++++++++++++++++++++++++++ components/basket/default.htm | 20 +++++++++++--------- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/components/Basket.php b/components/Basket.php index a11eab4..120f9a6 100644 --- a/components/Basket.php +++ b/components/Basket.php @@ -36,6 +36,31 @@ public function defineProperties() 'default' => 'shop/product', 'group' => 'Links', ], + '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.', + ], ]; } @@ -54,6 +79,13 @@ public function prepareVars() $this->paymentPage = $this->page['paymentPage'] = $this->property('paymentPage'); $this->productPage = $this->page['productPage'] = $this->property('productPage'); + $this->tableClass = $this->page['tableClass'] = $this->property('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'); + $this->basketItems = $this->page['basketItems'] = Cart::content(); $this->basketCount = $this->page['basketCount'] = Cart::count(); $this->basketTotal = $this->page['basketTotal'] = Cart::total(); diff --git a/components/basket/default.htm b/components/basket/default.htm index 14d2368..3cc1cf9 100644 --- a/components/basket/default.htm +++ b/components/basket/default.htm @@ -1,12 +1,10 @@ -

            You have {{ basketCount }} item(s) in your basket!

            - - +
            - - - - + + + + @@ -18,7 +16,11 @@ {% endfor %} + + + +
            ProductQty.PriceSubtotalProductQty.PriceSubtotal
            {{ item.subtotal }}
            + Total: + £{{ basketTotal }}
            - -

            Total: £{{ basketTotal }}

            From d6370c13dd5031bcc065a80b63196c47703555e7 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Mon, 20 Oct 2014 00:57:36 +0100 Subject: [PATCH 32/45] Fix missing parameter for image container --- components/Product.php | 1 + 1 file changed, 1 insertion(+) diff --git a/components/Product.php b/components/Product.php index 8740535..3a5f55b 100644 --- a/components/Product.php +++ b/components/Product.php @@ -95,6 +95,7 @@ public function prepareVars() $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->imageContainerClass = $this->page['imageContainerClass'] = $this->property('imageContainerClass'); $this->productContainerClass = $this->page['productContainerClass'] = $this->property('productContainerClass'); $this->detailContainerClass = $this->page['detailContainerClass'] = $this->property('detailContainerClass'); $this->qtyClass= $this->page['qtyClass'] = $this->property('qtyClass'); From 47df5970a462335a0eb51e13ddbe8f157a94b5d8 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Mon, 20 Oct 2014 01:15:52 +0100 Subject: [PATCH 33/45] Add support for specifying sort order, closes #7 --- components/Categories.php | 2 +- models/category/columns.yaml | 3 +++ models/category/fields.yaml | 4 ++++ updates/create_categories_table.php | 1 + updates/demo_seed.php | 7 ++++--- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/components/Categories.php b/components/Categories.php index 112e3c7..cc74a8f 100644 --- a/components/Categories.php +++ b/components/Categories.php @@ -74,7 +74,7 @@ public function prepareVars() public function listCategories() { - $categories = ShopCategory::all(); + $categories = ShopCategory::orderBy('sort_order')->get(); $categories->each(function($category) { diff --git a/models/category/columns.yaml b/models/category/columns.yaml index c2e55a7..3f4ee3c 100644 --- a/models/category/columns.yaml +++ b/models/category/columns.yaml @@ -12,6 +12,9 @@ columns: slug: label: Slug searchable: false + sort_order: + label: Sort Order + searchable: false description: label: Description searchable: false diff --git a/models/category/fields.yaml b/models/category/fields.yaml index 89b5696..68b9dae 100644 --- a/models/category/fields.yaml +++ b/models/category/fields.yaml @@ -16,3 +16,7 @@ fields: description: label: Description type: textarea + sort_order: + label: Sort Order + required: true + type: number diff --git a/updates/create_categories_table.php b/updates/create_categories_table.php index e212932..b080e67 100644 --- a/updates/create_categories_table.php +++ b/updates/create_categories_table.php @@ -14,6 +14,7 @@ public function up() $table->increments('id'); $table->string('title')->index(); $table->string('slug')->unique(); + $table->integer('sort_order')->default(1); $table->string('description')->nullable(); $table->timestamps(); }); diff --git a/updates/demo_seed.php b/updates/demo_seed.php index 9266113..6511966 100644 --- a/updates/demo_seed.php +++ b/updates/demo_seed.php @@ -9,11 +9,11 @@ class DemoSeed extends Seeder { public function run() { $this->createCategories([[ - 'Books', 'books', + 'Books', 'books', 1, 'Here be some books up fer grabs!' ], [ - 'Games & Consoles', 'games-consoles', + 'Games & Consoles', 'games-consoles', 2, 'Bored? Twiddle yer thumbs on these new titles...' ]]); @@ -46,7 +46,8 @@ public function createCategories($categories) $c = new Category; $c->title = $category[0]; $c->slug = $category[1]; - $c->description = $category[2]; + $c->sort_order = $category[2]; + $c->description = $category[3]; $c->save(); } } From ecd23e1c042f34c171e03e48dfc1fea0e0e1c8d8 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Tue, 21 Oct 2014 22:46:05 +0100 Subject: [PATCH 34/45] Fix issue with component not accepting params When creating two instances of the Basket partial on a single page, eg one in the header and one for the checkout view, in-line parameters were not working. Add an onRender method that allows for these values to be parsed, and separate parameters based on when they run. Also adds two new parameters for specifying the basket component and partial, which helps to fix some other issues I had with updating the minibasket on the demo theme. --- components/Basket.php | 49 +++++++++++++++++++++++---------- components/products/product.htm | 4 +-- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/components/Basket.php b/components/Basket.php index 120f9a6..4341ab5 100644 --- a/components/Basket.php +++ b/components/Basket.php @@ -36,6 +36,16 @@ public function defineProperties() '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', @@ -74,21 +84,32 @@ public function onRun() $this->prepareVars(); } - public function prepareVars() + public function onRender() + { + $this->prepareVars('render'); + } + + public function prepareVars($on = 'run') { - $this->paymentPage = $this->page['paymentPage'] = $this->property('paymentPage'); - $this->productPage = $this->page['productPage'] = $this->property('productPage'); - - $this->tableClass = $this->page['tableClass'] = $this->property('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'); - - $this->basketItems = $this->page['basketItems'] = Cart::content(); - $this->basketCount = $this->page['basketCount'] = Cart::count(); - $this->basketTotal = $this->page['basketTotal'] = Cart::total(); + if ($on == 'run') { + $this->paymentPage = $this->page['paymentPage'] = $this->property('paymentPage'); + $this->productPage = $this->page['productPage'] = $this->property('productPage'); + + $this->basketComponent = $this->page['basketComponent'] = $this->property('basketComponent');; + $this->basketPartial = $this->page['basketPartial'] = $this->property('basketPartial');; + $this->basketItems = $this->page['basketItems'] = Cart::content(); + $this->basketCount = $this->page['basketCount'] = Cart::count(); + $this->basketTotal = $this->page['basketTotal'] = Cart::total(); + } + + if ($on == 'render') { + $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'); + } if (Session::has('orderId')) { $this->orderId = $this->page['orderId'] = Session::get('orderId'); diff --git a/components/products/product.htm b/components/products/product.htm index 4710664..9a50f69 100644 --- a/components/products/product.htm +++ b/components/products/product.htm @@ -11,9 +11,9 @@
            £{{ product.price }}

            Orders this Month

            -

            +

            previous:

            Sales

            -

            £

            +

            £

            previous: £

            From 0f5a2d90a8f37937f125e46e37aa5b06f8d9a675 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sat, 8 Nov 2014 10:17:48 +0000 Subject: [PATCH 39/45] Cleanup order controller Creates a couple query scopes in the Order model so we can strip all the carbon bits out of the controller --- controllers/Orders.php | 17 ++++++----------- models/Order.php | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/controllers/Orders.php b/controllers/Orders.php index fb91609..ae213e4 100644 --- a/controllers/Orders.php +++ b/controllers/Orders.php @@ -2,7 +2,6 @@ use BackendMenu; use Backend\Classes\Controller; -use Carbon\Carbon; use Dshoreman\Shop\Models\Order; /** @@ -33,17 +32,13 @@ public function index() $this->vars['ordersPaid'] = Order::where('is_paid', 1)->count(); $this->vars['ordersPending'] = $this->vars['ordersTotal'] - $this->vars['ordersPaid']; - $this_month_start = Carbon::now()->startOfMonth(); - $last_month_start = Carbon::now()->subMonth()->startOfMonth(); - $last_month_end = Carbon::now()->subMonth()->endOfMonth(); + $c = $this->vars['ordersCount'] = Order::createdThisMonth()->count(); + $cl = $this->vars['ordersCountLast'] = Order::createdLastMonth()->count(); + $this->vars['ordersCountClass'] = $this->scoreboardClass($cl, $c); - $this->vars['ordersCount'] = Order::where('created_at', '>=', $this_month_start)->count(); - $this->vars['ordersCountLast'] = Order::whereBetween('created_at', [$last_month_start, $last_month_end])->count(); - $this->vars['ordersCountClass'] = $this->scoreboardClass($this->vars['ordersCountLast'], $this->vars['ordersCount']); - - $this->vars['ordersValue'] = Order::where('created_at', '>=', $this_month_start)->sum('total'); - $this->vars['ordersValueLast'] = Order::whereBetween('created_at', [$last_month_start, $last_month_end])->sum('total'); - $this->vars['ordersValueClass'] = $this->scoreboardClass($this->vars['ordersValueLast'], $this->vars['ordersValue']); + $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(); } diff --git a/models/Order.php b/models/Order.php index ceebc10..8fe0ed6 100644 --- a/models/Order.php +++ b/models/Order.php @@ -1,5 +1,6 @@ where('created_at', '>=', Carbon::now()->startOfMonth()); + } + + public function scopeCreatedLastMonth($query) + { + return $query->whereBetween('created_at', [ + Carbon::now()->subMonth()->startOfMonth(), + Carbon::now()->subMonth()->endOfMonth() + ]); + } + } From 377fcb261073defe89fbc782633c5ea5335f3b3b Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sun, 9 Nov 2014 16:51:01 +0000 Subject: [PATCH 40/45] Add images, finally closes #1 Basically adds all the stuff for product images in the plugin. Removes the old product container class, instead using the row class that's also used on other components. Adds a few new properties for image sizes and classes as well. Also adds the necessary stuff to get the uploader running in the backend product editor and tweaks the component view to display mutiple images. --- components/Product.php | 35 +++++++++++++++++++++++++++++----- components/product/default.htm | 20 ++++++++++++++++--- models/Product.php | 9 ++++++++- models/product/fields.yaml | 7 +++++++ 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/components/Product.php b/components/Product.php index 3a5f55b..6f6cb29 100644 --- a/components/Product.php +++ b/components/Product.php @@ -30,6 +30,12 @@ public function defineProperties() 'addButtonText' => [ 'title' => 'Buy button text', ], + 'mainImageSize' => [ + 'title' => 'Main Image Size', + ], + 'subImageSize' => [ + 'title' => 'Secondary Image Size', + ], 'rowClass' => [ 'title' => 'Row', 'group' => 'CSS Classes', @@ -39,8 +45,20 @@ public function defineProperties() 'title' => 'Image', 'group' => 'CSS Classes', ], - 'productContainerClass' => [ - 'title' => 'Product container', + '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' => [ @@ -95,8 +113,12 @@ public function prepareVars() $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->imageContainerClass = $this->page['imageContainerClass'] = $this->property('imageContainerClass'); - $this->productContainerClass = $this->page['productContainerClass'] = $this->property('productContainerClass'); + $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'); @@ -112,7 +134,10 @@ public function loadProduct() { $productId = $this->propertyOrParam('idParam'); - return ShopProduct::whereSlug($productId)->first(); + return ShopProduct::whereSlug($productId)->with(['images' => function($query) + { + $query->orderBy('sort_order', 'asc'); + }])->first(); } } diff --git a/components/product/default.htm b/components/product/default.htm index 20df6ea..1441f16 100644 --- a/components/product/default.htm +++ b/components/product/default.htm @@ -1,6 +1,20 @@ -
            -
            - {{ product.title }} +
            +
            +
            + {% for image in product.images %} +
            + + {{ product.title }} + +
            + {% endfor %} +
            diff --git a/models/Product.php b/models/Product.php index 148a9f7..1502866 100644 --- a/models/Product.php +++ b/models/Product.php @@ -36,13 +36,20 @@ class Product extends Model public $morphOne = []; public $morphMany = []; public $attachOne = []; - public $attachMany = []; + 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 = [ diff --git a/models/product/fields.yaml b/models/product/fields.yaml index 12ae063..90c5756 100644 --- a/models/product/fields.yaml +++ b/models/product/fields.yaml @@ -33,3 +33,10 @@ tabs: label: Description type: Backend\FormWidgets\RichEditor tab: Details + + images: + type: Backend\FormWidgets\FileUpload + mode: image + imageHeight: 260 + imageWidth: 260 + tab: Images From fc23fae6e2d8f19a43655bd5408c86ddd8479325 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sun, 9 Nov 2014 18:23:21 +0000 Subject: [PATCH 41/45] Add description and keywords to composer file --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index ef549bc..343c0ed 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { "name": "dshoreman/shop", - "description": "", + "description": "Standalone eCommerce plugin for October CMS", "homepage": "http://dsdev.io/", - "keywords": [], + "keywords": ['october', 'ecommerce', 'shop', 'stripe', 'product', 'plugin'], "authors": [ { "name": "Dave Shoreman", From b7d58b56cb44f39d801a2e143f01cca8eeb38ab1 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sun, 9 Nov 2014 18:23:54 +0000 Subject: [PATCH 42/45] Add version file --- VERSION | 1 + 1 file changed, 1 insertion(+) create mode 100644 VERSION diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 From ba8f74303c211a726621571d2d0714527314e7d2 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Sun, 9 Nov 2014 18:40:39 +0000 Subject: [PATCH 43/45] Cleanup product component --- components/product/default.htm | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/components/product/default.htm b/components/product/default.htm index 1441f16..a57bce8 100644 --- a/components/product/default.htm +++ b/components/product/default.htm @@ -2,14 +2,18 @@
            {% for image in product.images %} -
            + {% if image.sort_order == '1' %} + {% set imageContainerClass = mainImageContainerClass %} + {% set imageSize = mainImageSize %} + {% else %} + {% set imageContainerClass = subImageContainerClass %} + {% set imageSize = subImageSize %} + {% endif %} + + From 28880274f75a4d4212bfa55d75db9964bd062c7b Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Thu, 4 Dec 2014 06:45:31 +0000 Subject: [PATCH 44/45] Add ability to remove products from basket Adds a new method that removes a given row from the cart to begin implementing task #4. Also updates the default basket component to include a remove button which will remove that row and update basket total and item count with the aid of new ocshop-x helper classes, where 'x' is 'total', 'count' or 'row-{ROWID}'. Finally rejigs the prepareVars method to clean things up, since much of it is used in the onFoo listeners. --- components/Basket.php | 53 ++++++++++++++++++++++++++--------- components/basket/default.htm | 15 ++++++++-- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/components/Basket.php b/components/Basket.php index 4341ab5..1e8ef49 100644 --- a/components/Basket.php +++ b/components/Basket.php @@ -92,23 +92,15 @@ public function onRender() public function prepareVars($on = 'run') { if ($on == 'run') { - $this->paymentPage = $this->page['paymentPage'] = $this->property('paymentPage'); - $this->productPage = $this->page['productPage'] = $this->property('productPage'); + $this->registerBasketInfo(); + $this->registerPages(); $this->basketComponent = $this->page['basketComponent'] = $this->property('basketComponent');; $this->basketPartial = $this->page['basketPartial'] = $this->property('basketPartial');; - $this->basketItems = $this->page['basketItems'] = Cart::content(); - $this->basketCount = $this->page['basketCount'] = Cart::count(); - $this->basketTotal = $this->page['basketTotal'] = Cart::total(); } if ($on == 'render') { - $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'); + $this->registerClasses(); } if (Session::has('orderId')) { @@ -116,6 +108,29 @@ public function prepareVars($on = 'run') } } + 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'); @@ -125,9 +140,19 @@ public function onAddProduct() Cart::add($id, $product->title, $quantity, $product->price); - $this->page['basketCount'] = Cart::count(); - $this->page['basketItems'] = Cart::content(); - $this->page['basketTotal'] = Cart::total(); + $this->registerBasketInfo(); + } + + public function onRemoveProduct() + { + Cart::remove(post('row_id')); + + $this->registerBasketInfo(); + + return [ + 'total' => $this->basketTotal, + 'count' => $this->basketCount, + ]; } public function onCheckout() diff --git a/components/basket/default.htm b/components/basket/default.htm index 3cc1cf9..0d63a63 100644 --- a/components/basket/default.htm +++ b/components/basket/default.htm @@ -1,6 +1,7 @@ + @@ -9,7 +10,15 @@ {% for item in basketItems %} - + + @@ -17,10 +26,10 @@ {% endfor %} - - +
              Product Qty. Price
            + + {{ item.name }} {{ item.qty }} {{ item.price }}
            + Total: £{{ basketTotal }}£{{ basketTotal }}
            From 25749cdd1836879477c9a4608115fbde340e540d Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Thu, 4 Dec 2014 07:13:26 +0000 Subject: [PATCH 45/45] Add readme --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 README.md 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.