diff --git a/package.json b/package.json index 88cbe757f..103d618f3 100755 --- a/package.json +++ b/package.json @@ -13,12 +13,16 @@ "devDependencies": { "axios": "^0.18", "bootstrap": "^4.0.0", - "popper.js": "^1.12", "cross-env": "^5.1", "jquery": "^3.2", - "laravel-mix": "^2.0", + "laravel-mix": "^5.0.1", "lodash": "^4.17.4", - "vue": "^2.5.7" + "popper.js": "^1.12", + "resolve-url-loader": "^3.1.0", + "sass": "^1.24.5", + "sass-loader": "^8.0.2", + "vue": "^2.5.7", + "vue-template-compiler": "^2.6.11" }, "dependencies": { "opencollective-postinstall": "^2.0.1", diff --git a/packages/Webkul/Admin/src/Config/system.php b/packages/Webkul/Admin/src/Config/system.php index d9a39ee19..39148ce20 100644 --- a/packages/Webkul/Admin/src/Config/system.php +++ b/packages/Webkul/Admin/src/Config/system.php @@ -87,10 +87,21 @@ return [ 'key' => 'catalog.products', 'name' => 'admin::app.admin.system.products', 'sort' => 2 + ], [ + 'key' => 'catalog.products.guest-checkout', + 'name' => 'admin::app.admin.system.guest-checkout', + 'sort' => 1, + 'fields' => [ + [ + 'name' => 'allow-guest-checkout', + 'title' => 'admin::app.admin.system.allow-guest-checkout', + 'type' => 'boolean' + ] + ] ], [ 'key' => 'catalog.products.review', 'name' => 'admin::app.admin.system.review', - 'sort' => 1, + 'sort' => 2, 'fields' => [ [ 'name' => 'guest_review', diff --git a/packages/Webkul/Admin/src/Resources/lang/en/app.php b/packages/Webkul/Admin/src/Resources/lang/en/app.php index 71aa08dac..adae4ecc7 100755 --- a/packages/Webkul/Admin/src/Resources/lang/en/app.php +++ b/packages/Webkul/Admin/src/Resources/lang/en/app.php @@ -1202,6 +1202,9 @@ return [ 'system' => [ 'catalog' => 'Catalog', 'products' => 'Products', + 'guest-checkout' => 'Guest Checkout', + 'allow-guest-checkout' => 'Allow Guest Checkout', + 'allow-guest-checkout-hint' => 'Hint: If turned on, this option can be configured for each product specifically.', 'review' => 'Review', 'allow-guest-review' => 'Allow Guest Review', 'inventory' => 'Inventory', diff --git a/packages/Webkul/Admin/src/Resources/views/catalog/products/edit.blade.php b/packages/Webkul/Admin/src/Resources/views/catalog/products/edit.blade.php index e76ff2035..6adba4afc 100755 --- a/packages/Webkul/Admin/src/Resources/views/catalog/products/edit.blade.php +++ b/packages/Webkul/Admin/src/Resources/views/catalog/products/edit.blade.php @@ -77,6 +77,10 @@ @foreach ($customAttributes as $attribute) code == 'guest_checkout' && ! core()->getConfigData('catalog.products.guest-checkout.allow-guest-checkout')) { + continue; + } + $validations = []; if ($attribute->is_required) { diff --git a/packages/Webkul/Admin/src/Resources/views/configuration/index.blade.php b/packages/Webkul/Admin/src/Resources/views/configuration/index.blade.php index cf1de3a02..a8b39c936 100755 --- a/packages/Webkul/Admin/src/Resources/views/configuration/index.blade.php +++ b/packages/Webkul/Admin/src/Resources/views/configuration/index.blade.php @@ -65,6 +65,11 @@ @include ('admin::configuration.field-type', ['field' => $field]) + @php ($hint = $field['title'] . '-hint') + @if ($hint !== __($hint)) + {{ __($hint) }} + @endif + @endforeach diff --git a/packages/Webkul/Attribute/src/Database/Seeders/AttributeFamilyTableSeeder.php b/packages/Webkul/Attribute/src/Database/Seeders/AttributeFamilyTableSeeder.php index c93bec117..9926ecd3d 100755 --- a/packages/Webkul/Attribute/src/Database/Seeders/AttributeFamilyTableSeeder.php +++ b/packages/Webkul/Attribute/src/Database/Seeders/AttributeFamilyTableSeeder.php @@ -3,16 +3,20 @@ namespace Webkul\Attribute\Database\Seeders; use Illuminate\Database\Seeder; -use DB; +use Illuminate\Support\Facades\DB; class AttributeFamilyTableSeeder extends Seeder { public function run() { + DB::statement('SET FOREIGN_KEY_CHECKS=0;'); + DB::table('attribute_families')->delete(); DB::table('attribute_families')->insert([ ['id' => '1','code' => 'default','name' => 'Default','status' => '0','is_user_defined' => '1'] ]); + + DB::statement('SET FOREIGN_KEY_CHECKS=1;'); } } \ No newline at end of file diff --git a/packages/Webkul/Attribute/src/Database/Seeders/AttributeGroupTableSeeder.php b/packages/Webkul/Attribute/src/Database/Seeders/AttributeGroupTableSeeder.php index 92fa5db6c..394c5ac48 100755 --- a/packages/Webkul/Attribute/src/Database/Seeders/AttributeGroupTableSeeder.php +++ b/packages/Webkul/Attribute/src/Database/Seeders/AttributeGroupTableSeeder.php @@ -3,12 +3,17 @@ namespace Webkul\Attribute\Database\Seeders; use Illuminate\Database\Seeder; -use DB; +use Illuminate\Support\Facades\DB; class AttributeGroupTableSeeder extends Seeder { public function run() { + DB::statement('SET FOREIGN_KEY_CHECKS=0;'); + + DB::table('attribute_groups')->delete(); + DB::table('attribute_group_mappings')->delete(); + DB::table('attribute_groups')->delete(); DB::table('attribute_groups')->insert([ @@ -42,9 +47,12 @@ class AttributeGroupTableSeeder extends Seeder ['attribute_id' => '20','attribute_group_id' => '5','position' => '2'], ['attribute_id' => '21','attribute_group_id' => '5','position' => '3'], ['attribute_id' => '22','attribute_group_id' => '5','position' => '4'], - ['attribute_id' => '23','attribute_group_id' => '1','position' => '9'], - ['attribute_id' => '24','attribute_group_id' => '1','position' => '10'], - ['attribute_id' => '25','attribute_group_id' => '1','position' => '11'] + ['attribute_id' => '23','attribute_group_id' => '1','position' => '10'], + ['attribute_id' => '24','attribute_group_id' => '1','position' => '11'], + ['attribute_id' => '25','attribute_group_id' => '1','position' => '12'], + ['attribute_id' => '26','attribute_group_id' => '1','position' => '9'] ]); + + DB::statement('SET FOREIGN_KEY_CHECKS=0;'); } } \ No newline at end of file diff --git a/packages/Webkul/Attribute/src/Database/Seeders/AttributeOptionTableSeeder.php b/packages/Webkul/Attribute/src/Database/Seeders/AttributeOptionTableSeeder.php index 992c625c0..5bdc0ba9e 100755 --- a/packages/Webkul/Attribute/src/Database/Seeders/AttributeOptionTableSeeder.php +++ b/packages/Webkul/Attribute/src/Database/Seeders/AttributeOptionTableSeeder.php @@ -3,7 +3,7 @@ namespace Webkul\Attribute\Database\Seeders; use Illuminate\Database\Seeder; -use DB; +use Illuminate\Support\Facades\DB; class AttributeOptionTableSeeder extends Seeder { @@ -11,6 +11,7 @@ class AttributeOptionTableSeeder extends Seeder public function run() { DB::table('attribute_options')->delete(); + DB::table('attribute_option_translations')->delete(); DB::table('attribute_options')->insert([ ['id' => '1', 'admin_name' => 'Red', 'sort_order' => '1', 'attribute_id' => '23'], diff --git a/packages/Webkul/Attribute/src/Database/Seeders/AttributeTableSeeder.php b/packages/Webkul/Attribute/src/Database/Seeders/AttributeTableSeeder.php index 595b37411..829ce982f 100755 --- a/packages/Webkul/Attribute/src/Database/Seeders/AttributeTableSeeder.php +++ b/packages/Webkul/Attribute/src/Database/Seeders/AttributeTableSeeder.php @@ -2,9 +2,9 @@ namespace Webkul\Attribute\Database\Seeders; -use Illuminate\Database\Seeder; -use DB; use Carbon\Carbon; +use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\DB; class AttributeTableSeeder extends Seeder { @@ -12,6 +12,7 @@ class AttributeTableSeeder extends Seeder public function run() { DB::table('attributes')->delete(); + DB::table('attribute_translations')->delete(); $now = Carbon::now(); @@ -59,9 +60,11 @@ class AttributeTableSeeder extends Seeder ['id' => '23','code' => 'color','admin_name' => 'Color','type' => 'select','validation' => NULL,'position' => '23','is_required' => '0','is_unique' => '0','value_per_locale' => '0','value_per_channel' => '0','is_filterable' => '1','is_configurable' => '1','is_user_defined' => '1','is_visible_on_front' => '0', 'use_in_flat' => '1','created_at' => $now,'updated_at' => $now], ['id' => '24','code' => 'size','admin_name' => 'Size','type' => 'select','validation' => NULL,'position' => '24','is_required' => '0','is_unique' => '0','value_per_locale' => '0','value_per_channel' => '0','is_filterable' => '1','is_configurable' => '1','is_user_defined' => '1','is_visible_on_front' => '0', - 'use_in_flat' => '1','created_at' => $now,'updated_at' => $now], + 'use_in_flat' => '1','created_at' => $now,'updated_at' => $now], ['id' => '25','code' => 'brand','admin_name' => 'Brand','type' => 'select','validation' => NULL,'position' => '25','is_required' => '0','is_unique' => '0','value_per_locale' => '0','value_per_channel' => '0','is_filterable' => '1','is_configurable' => '0','is_user_defined' => '0','is_visible_on_front' => '1', - 'use_in_flat' => '1','created_at' => $now,'updated_at' => $now] + 'use_in_flat' => '1','created_at' => $now,'updated_at' => $now], + ['id' => '26','code' => 'guest_checkout','admin_name' => 'Guest Checkout','type' => 'boolean','validation' => NULL,'position' => '8','is_required' => '1','is_unique' => '0','value_per_locale' => '0','value_per_channel' => '0','is_filterable' => '0','is_configurable' => '0','is_user_defined' => '0','is_visible_on_front' => '0', + 'use_in_flat' => '1','created_at' => $now,'updated_at' => $now], ]); @@ -90,7 +93,8 @@ class AttributeTableSeeder extends Seeder ['id' => '22','locale' => 'en','name' => 'Weight','attribute_id' => '22'], ['id' => '23','locale' => 'en','name' => 'Color','attribute_id' => '23'], ['id' => '24','locale' => 'en','name' => 'Size','attribute_id' => '24'], - ['id' => '25','locale' => 'en','name' => 'Brand','attribute_id' => '25'] + ['id' => '25','locale' => 'en','name' => 'Brand','attribute_id' => '25'], + ['id' => '26','locale' => 'en','name' => 'Allow Guest Checkout','attribute_id' => '26'] ]); } } \ No newline at end of file diff --git a/packages/Webkul/Attribute/src/Database/Seeders/DatabaseSeeder.php b/packages/Webkul/Attribute/src/Database/Seeders/DatabaseSeeder.php index b1f9df4a8..148b85486 100755 --- a/packages/Webkul/Attribute/src/Database/Seeders/DatabaseSeeder.php +++ b/packages/Webkul/Attribute/src/Database/Seeders/DatabaseSeeder.php @@ -13,9 +13,9 @@ class DatabaseSeeder extends Seeder */ public function run() { - $this->call(AttributeTableSeeder::class); - $this->call(AttributeOptionTableSeeder::class); $this->call(AttributeFamilyTableSeeder::class); $this->call(AttributeGroupTableSeeder::class); + $this->call(AttributeTableSeeder::class); + $this->call(AttributeOptionTableSeeder::class); } } diff --git a/packages/Webkul/CMS/src/Database/Seeders/CMSPagesTableSeeder.php b/packages/Webkul/CMS/src/Database/Seeders/CMSPagesTableSeeder.php index c8ab922f7..92430ab43 100644 --- a/packages/Webkul/CMS/src/Database/Seeders/CMSPagesTableSeeder.php +++ b/packages/Webkul/CMS/src/Database/Seeders/CMSPagesTableSeeder.php @@ -2,15 +2,16 @@ namespace Webkul\CMS\Database\Seeders; -use Illuminate\Database\Seeder; -use DB; use Carbon\Carbon; +use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\DB; class CMSPagesTableSeeder extends Seeder { public function run() { DB::table('cms_pages')->delete(); + DB::table('cms_page_translations')->delete(); DB::table('cms_pages')->insert([ [ diff --git a/packages/Webkul/Checkout/src/Models/Cart.php b/packages/Webkul/Checkout/src/Models/Cart.php index 9088531fa..936215c1c 100755 --- a/packages/Webkul/Checkout/src/Models/Cart.php +++ b/packages/Webkul/Checkout/src/Models/Cart.php @@ -116,11 +116,11 @@ class Cart extends Model implements CartContract } /** - * Checks if cart have downloadable items + * Checks if cart has downloadable items * * @return boolean */ - public function haveDownloadableItems() + public function hasDownloadableItems() { foreach ($this->items as $item) { if ($item->type == 'downloadable') @@ -129,4 +129,20 @@ class Cart extends Model implements CartContract return false; } + + /** + * Checks if cart has items that allow guest checkout + * + * @return boolean + */ + public function hasGuestCheckoutItems() + { + foreach ($this->items as $item) { + if ($item->product->getAttribute('guest_checkout') === 0) { + return false; + } + } + + return true; + } } \ No newline at end of file diff --git a/packages/Webkul/Core/src/Database/Seeders/ConfigTableSeeder.php b/packages/Webkul/Core/src/Database/Seeders/ConfigTableSeeder.php new file mode 100644 index 000000000..fc8e7ec13 --- /dev/null +++ b/packages/Webkul/Core/src/Database/Seeders/ConfigTableSeeder.php @@ -0,0 +1,27 @@ +delete(); + + $now = Carbon::now(); + + DB::table('core_config')->insert([ + 'id' => 1, + 'code' => 'catalog.products.guest-checkout.allow-guest-checkout', + 'value' => '1', + 'channel_code' => null, + 'locale_code' => null, + 'created_at' => $now, + 'updated_at' => $now + ]); + } +} \ No newline at end of file diff --git a/packages/Webkul/Core/src/Database/Seeders/DatabaseSeeder.php b/packages/Webkul/Core/src/Database/Seeders/DatabaseSeeder.php index 919a033ef..6d5013d4f 100755 --- a/packages/Webkul/Core/src/Database/Seeders/DatabaseSeeder.php +++ b/packages/Webkul/Core/src/Database/Seeders/DatabaseSeeder.php @@ -18,5 +18,6 @@ class DatabaseSeeder extends Seeder $this->call(CountriesTableSeeder::class); $this->call(StatesTableSeeder::class); $this->call(ChannelTableSeeder::class); + $this->call(ConfigTableSeeder::class); } } diff --git a/packages/Webkul/Core/src/Helpers/Laravel5Helper.php b/packages/Webkul/Core/src/Helpers/Laravel5Helper.php new file mode 100644 index 000000000..99a4f1a1e --- /dev/null +++ b/packages/Webkul/Core/src/Helpers/Laravel5Helper.php @@ -0,0 +1,115 @@ + 'integer_value', + 'sku' => 'text_value', + 'name' => 'text_value', + 'url_key' => 'text_value', + 'tax_category_id' => 'integer_value', + 'new' => 'boolean_value', + 'featured' => 'boolean_value', + 'visible_individually' => 'boolean_value', + 'status' => 'boolean_value', + 'short_description' => 'text_value', + 'description' => 'text_value', + 'price' => 'float_value', + 'cost' => 'float_value', + 'special_price' => 'float_value', + 'special_price_from' => 'date_value', + 'special_price_to' => 'date_value', + 'meta_title' => 'text_value', + 'meta_keywords' => 'text_value', + 'meta_description' => 'text_value', + 'width' => 'integer_value', + 'height' => 'integer_value', + 'depth' => 'integer_value', + 'weight' => 'integer_value', + 'color' => 'integer_value', + 'size' => 'integer_value', + 'brand' => 'text_value', + 'guest_checkout' => 'boolean_value', + ]; + if (!array_key_exists($attribute, $attributes)) { + return null; + } + return $attributes[$attribute]; + } + /** + * @param array $attributeValueStates + * + * @return \Webkul\Product\Models\Product + * @part ORM + */ + public function haveProduct( + array $configs = [], + array $productStates = [] + ): Product { + $I = $this; + /** @var Product $product */ + $product = factory(Product::class)->states($productStates)->create($configs['productAttributes'] ?? []);; + $I->createAttributeValues($product->id,$configs['attributeValues'] ?? []); + $I->have(ProductInventory::class, array_merge($configs['productInventory'] ?? [], [ + 'product_id' => $product->id, + 'inventory_source_id' => 1, + ])); + Event::fire('catalog.product.create.after', $product); + return $product; + } + private function createAttributeValues($id, array $attributeValues = []) + { + $I = $this; + $productAttributeValues = [ + 'sku', + 'url_key', + 'tax_category_id', + 'price', + 'cost', + 'name', + 'new', + 'visible_individually', + 'featured', + 'status', + 'guest_checkout', + 'short_description', + 'description', + 'meta_title', + 'meta_keywords', + 'meta_description', + 'weight', + ]; + foreach ($productAttributeValues as $attribute) { + $data = ['product_id' => $id]; + if (array_key_exists($attribute, $attributeValues)) { + $fieldName = self::getAttributeFieldName($attribute); + if (! array_key_exists($fieldName, $data)) { + $data[$fieldName] = $attributeValues[$attribute]; + } else { + $data = [$fieldName => $attributeValues[$attribute]]; + } + } + $I->have(ProductAttributeValue::class, $data, $attribute); + } + } +} \ No newline at end of file diff --git a/packages/Webkul/Inventory/src/Database/Factories/InventorySourceFactory.php b/packages/Webkul/Inventory/src/Database/Factories/InventorySourceFactory.php new file mode 100644 index 000000000..53234a1a5 --- /dev/null +++ b/packages/Webkul/Inventory/src/Database/Factories/InventorySourceFactory.php @@ -0,0 +1,27 @@ +define(InventorySource::class, function (Faker $faker) { + $now = date("Y-m-d H:i:s"); + $code = $faker->unique()->word; + return [ + 'code' => $faker->unique()->word, + 'name' => $code, + 'description' => $faker->sentence, + 'contact_name' => $faker->name, + 'contact_email' => $faker->safeEmail, + 'contact_number' => $faker->phoneNumber, + 'country' => $faker->countryCode, + 'state' => $faker->state, + 'city' => $faker->city, + 'street' => $faker->streetAddress, + 'postcode' => $faker->postcode, + 'priority' => 0, + 'status' => 1, + 'created_at' => $now, + 'updated_at' => $now, + ]; +}); \ No newline at end of file diff --git a/packages/Webkul/Product/src/Database/Factories/ProductAttributeValueFactory.php b/packages/Webkul/Product/src/Database/Factories/ProductAttributeValueFactory.php new file mode 100644 index 000000000..4da6ddaf0 --- /dev/null +++ b/packages/Webkul/Product/src/Database/Factories/ProductAttributeValueFactory.php @@ -0,0 +1,261 @@ +defineAs(ProductAttributeValue::class, 'sku', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'text_value' => $faker->uuid, + 'attribute_id' => 1, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'name', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'locale' => 'en', //$faker->languageCode, + 'channel' => 'default', + 'text_value' => $faker->words(2, true), + 'attribute_id' => 2, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'url_key', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'text_value' => $faker->unique()->slug, + 'attribute_id' => 3, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'tax_category_id', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'channel' => 'default', + 'integer_value' => null, // ToDo + 'attribute_id' => 4, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'new', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'boolean_value' => 1, + 'attribute_id' => 5, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'featured', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'boolean_value' => 1, + 'attribute_id' => 6, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'visible_individually', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'boolean_value' => 1, + 'attribute_id' => 7, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'status', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'boolean_value' => 1, + 'attribute_id' => 8, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'short_description', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'locale' => 'en', //$faker->languageCode, + 'channel' => 'default', + 'text_value' => $faker->sentence, + 'attribute_id' => 9, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'description', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'locale' => 'en', //$faker->languageCode, + 'channel' => 'default', + 'text_value' => $faker->sentences(3, true), + 'attribute_id' => 10, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'price', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'float_value' => $faker->randomFloat(4, 0, 1000), + 'attribute_id' => 11, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'cost', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'channel' => 'default', + 'float_value' => $faker->randomFloat(4, 0, 10), + 'attribute_id' => 12, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'special_price', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'float_value' => $faker->randomFloat(4, 0, 100), + 'attribute_id' => 13, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'special_price_from', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'channel' => 'default', + 'date_value' => $faker->dateTimeBetween('-5 days', 'now', 'Europe/Berlin'), + 'attribute_id' => 14, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'special_price_to', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'channel' => 'default', + 'date_value' => $faker->dateTimeBetween('now', '+ 5 days', 'Europe/Berlin'), + 'attribute_id' => 15, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'meta_title', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'locale' => 'en', //$faker->languageCode, + 'channel' => 'default', + 'text_value' => $faker->words(2, true), + 'attribute_id' => 16, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'meta_keywords', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'locale' => 'en', //$faker->languageCode, + 'channel' => 'default', + 'text_value' => $faker->words(5, true), + 'attribute_id' => 17, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'meta_description', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'locale' => 'en', //$faker->languageCode, + 'channel' => 'default', + 'text_value' => $faker->sentence, + 'attribute_id' => 18, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'width', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'integer_value' => $faker->numberBetween(1, 50), + 'attribute_id' => 19, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'height', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'integer_value' => $faker->numberBetween(1, 50), + 'attribute_id' => 20, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'depth', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'integer_value' => $faker->numberBetween(1, 50), + 'attribute_id' => 21, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'weight', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'integer_value' => $faker->numberBetween(1, 50), + 'attribute_id' => 22, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'color', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'integer_value' => $faker->numberBetween(1, 5), + 'attribute_id' => 23, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'size', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'integer_value' => $faker->numberBetween(1, 5), + 'attribute_id' => 24, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'brand', function (Faker $faker) { + return [ + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'attribute_id' => 25, + 'integer_value' => function () { + return factory(AttributeOption::class)->create()->id; + }, + ]; +}); +$factory->defineAs(ProductAttributeValue::class, 'guest_checkout', function ( Faker $faker) { + return [ + 'product_id' => function() { + return factory(Product::class)->create()->id; + }, + 'boolean_value' => 1, + 'attribute_id' => 26, + ]; +}); \ No newline at end of file diff --git a/packages/Webkul/Product/src/Database/Factories/ProductFactory.php b/packages/Webkul/Product/src/Database/Factories/ProductFactory.php new file mode 100644 index 000000000..0caf3ded5 --- /dev/null +++ b/packages/Webkul/Product/src/Database/Factories/ProductFactory.php @@ -0,0 +1,23 @@ +define(Product::class, function (Faker $faker) { + $now = date("Y-m-d H:i:s"); + return [ + 'sku' => $faker->uuid, + 'created_at' => $now, + 'updated_at' => $now, + 'attribute_family_id' => 1, + ]; +}); + +$factory->state(Product::class, 'simple', [ + 'type' => 'simple', +]); +$factory->state(Product::class, 'downloadable_with_stock', [ + 'type' => 'downloadable', +]); \ No newline at end of file diff --git a/packages/Webkul/Product/src/Database/Factories/ProductInventoryFactory.php b/packages/Webkul/Product/src/Database/Factories/ProductInventoryFactory.php new file mode 100644 index 000000000..fc3b9d122 --- /dev/null +++ b/packages/Webkul/Product/src/Database/Factories/ProductInventoryFactory.php @@ -0,0 +1,20 @@ +define(ProductInventory::class, function (Faker $faker) { + return [ + 'qty' => $faker->numberBetween(1, 20), + 'product_id' => function () { + return factory(Product::class)->create()->id; + }, + 'inventory_source_id' => function () { + return factory(InventorySource::class)->create()->id; + }, + ]; +}); \ No newline at end of file diff --git a/packages/Webkul/Product/src/Providers/ProductServiceProvider.php b/packages/Webkul/Product/src/Providers/ProductServiceProvider.php index 902f5261b..e95c7563f 100755 --- a/packages/Webkul/Product/src/Providers/ProductServiceProvider.php +++ b/packages/Webkul/Product/src/Providers/ProductServiceProvider.php @@ -2,6 +2,7 @@ namespace Webkul\Product\Providers; +use Illuminate\Database\Eloquent\Factory as EloquentFactory; use Illuminate\Support\ServiceProvider; use Webkul\Product\Models\ProductProxy; use Webkul\Product\Observers\ProductObserver; @@ -32,14 +33,21 @@ class ProductServiceProvider extends ServiceProvider * * @return void */ - public function register() + public function register(): void { $this->registerConfig(); $this->registerCommands(); + + $this->registerEloquentFactoriesFrom(__DIR__ . '/../Database/Factories'); } - public function registerConfig() { + /** + * Register Configuration + * + * @return void + */ + public function registerConfig(): void { $this->mergeConfigFrom( dirname(__DIR__) . '/Config/product_types.php', 'product_types' ); @@ -47,10 +55,24 @@ class ProductServiceProvider extends ServiceProvider /** * Register the console commands of this package + * + * @return void */ - protected function registerCommands() + protected function registerCommands(): void { - if ($this->app->runningInConsole()) + if ($this->app->runningInConsole()) { $this->commands([PriceUpdate::class,]); + } + } + + /** + * Register factories. + * + * @param string $path + * @return void + */ + protected function registerEloquentFactoriesFrom($path): void + { + $this->app->make(EloquentFactory::class)->load($path); } } \ No newline at end of file diff --git a/packages/Webkul/Product/src/Type/Downloadable.php b/packages/Webkul/Product/src/Type/Downloadable.php index aaf97acdf..fa7cd8c9b 100644 --- a/packages/Webkul/Product/src/Type/Downloadable.php +++ b/packages/Webkul/Product/src/Type/Downloadable.php @@ -22,14 +22,14 @@ class Downloadable extends AbstractType { /** * ProductDownloadableLinkRepository instance - * + * * @var ProductDownloadableLinkRepository */ protected $productDownloadableLinkRepository; /** * ProductDownloadableSampleRepository instance - * + * * @var ProductDownloadableSampleRepository */ protected $productDownloadableSampleRepository; @@ -39,11 +39,11 @@ class Downloadable extends AbstractType * * @var array */ - protected $skipAttributes = ['width', 'height', 'depth', 'weight']; + protected $skipAttributes = ['width', 'height', 'depth', 'weight', 'guest_checkout']; /** * These blade files will be included in product edit page - * + * * @var array */ protected $additionalViews = [ @@ -111,7 +111,7 @@ class Downloadable extends AbstractType if (request()->route()->getName() != 'admin.catalog.products.massupdate') { $this->productDownloadableLinkRepository->saveLinks($data, $product); - + $this->productDownloadableSampleRepository->saveSamples($data, $product); } @@ -127,11 +127,11 @@ class Downloadable extends AbstractType { if (! $this->product->status) return false; - + if ($this->product->downloadable_links()->count()) return true; - return false; + return false; } /** @@ -177,7 +177,7 @@ class Downloadable extends AbstractType return $products; } - + /** * * @param array $options1 diff --git a/packages/Webkul/Shop/src/Http/Controllers/OnepageController.php b/packages/Webkul/Shop/src/Http/Controllers/OnepageController.php index 12dad01aa..06fa3dacc 100755 --- a/packages/Webkul/Shop/src/Http/Controllers/OnepageController.php +++ b/packages/Webkul/Shop/src/Http/Controllers/OnepageController.php @@ -64,13 +64,22 @@ class OnepageController extends Controller */ public function index() { + if (! auth()->guard('customer')->check() && ! core()->getConfigData('catalog.products.guest-checkout.allow-guest-checkout')) { + return redirect()->route('customer.session.index'); + } + if (Cart::hasError()) return redirect()->route('shop.checkout.cart.index'); $cart = Cart::getCart(); - if (! auth()->guard('customer')->check() && $cart->haveDownloadableItems()) + if (! auth()->guard('customer')->check() && $cart->hasDownloadableItems()) { return redirect()->route('customer.session.index'); + } + + if (! auth()->guard('customer')->check() && ! $cart->hasGuestCheckoutItems()) { + return redirect()->route('customer.session.index'); + } Cart::collectTotals(); diff --git a/tests/_support/AcceptanceTester.php b/tests/_support/AcceptanceTester.php index 95c00ec21..9adb03862 100644 --- a/tests/_support/AcceptanceTester.php +++ b/tests/_support/AcceptanceTester.php @@ -23,4 +23,20 @@ class AcceptanceTester extends \Codeception\Actor /** * Define custom actions here */ + + /** + * Logging in as an Admin + */ + public function loginAsAdmin() + { + $I = $this; + $I->amOnPage('/admin'); + $I->see('Sign In'); + $I->fillField('email', 'admin@example.com'); + $I->fillField('password', 'admin123'); + $I->dontSee('The "Email" field is required.'); + $I->dontSee('The "Password" field is required.'); + $I->click('Sign In'); + $I->see('Dashboard', '//h1'); + } } diff --git a/tests/_support/FunctionalTester.php b/tests/_support/FunctionalTester.php index ea07902b1..38b53fd7d 100644 --- a/tests/_support/FunctionalTester.php +++ b/tests/_support/FunctionalTester.php @@ -2,6 +2,7 @@ use Illuminate\Routing\RouteCollection; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Route; use Webkul\User\Models\Admin; @@ -58,4 +59,26 @@ class FunctionalTester extends \Codeception\Actor $I->assertContains('admin', $middlewares, 'check that admin middleware is applied'); } + /** + * Set specific Webkul/Core configuration keys to a given value + * + * // TODO: change method as soon as there is a method to set core config data + * + * @param $data array containing 'code => value' pairs + * @return void + */ + public function setConfigData($data): void { + foreach ($data as $key => $value) { + if (DB::table('core_config')->where('code', '=', $key)->exists()) { + DB::table('core_config')->where('code', '=', $key)->update(['value' => $value]); + } else { + DB::table('core_config')->insert([ + 'code' => $key, + 'value' => $value, + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s') + ]); + } + } + } } diff --git a/tests/acceptance.suite.yml b/tests/acceptance.suite.yml index 267af5b80..ba234fcea 100644 --- a/tests/acceptance.suite.yml +++ b/tests/acceptance.suite.yml @@ -7,7 +7,23 @@ actor: AcceptanceTester modules: enabled: - - PhpBrowser: - url: http://localhost - - \Helper\Acceptance - step_decorators: ~ \ No newline at end of file + - \Helper\Acceptance + - Asserts + - WebDriver: + url: http://nginx/ + host: selenium-chrome + browser: chrome + window_size: 1920x1080 + restart: true + wait: 20 + pageload_timeout: 10 + connection_timeout: 60 + request_timeout: 60 + log_js_errors: true + - Laravel5: + part: ORM + cleanup: false + environment_file: .env + database_seeder_class: DatabaseSeeder + url: http://nginx +step_decorators: ~ \ No newline at end of file diff --git a/tests/acceptance/GuestCheckoutCest.php b/tests/acceptance/GuestCheckoutCest.php new file mode 100644 index 000000000..f862a34bf --- /dev/null +++ b/tests/acceptance/GuestCheckoutCest.php @@ -0,0 +1,59 @@ +faker = Factory::create(); + } + + function testToConfigureGlobalGuestCheckout(AcceptanceTester $I) + { + $I->loginAsAdmin(); + + $I->amGoingTo('turn ON the global guest checkout configuration'); + $I->amOnPage('/admin/configuration/catalog/products'); + $I->see(__('admin::app.admin.system.allow-guest-checkout')); + $I->selectOption('catalog[products][guest-checkout][allow-guest-checkout]', 1); + $I->click(__('admin::app.configuration.save-btn-title')); + $I->seeRecord('core_config', ['code' => 'catalog.products.guest-checkout.allow-guest-checkout', 'value' => 1]); + + $I->amGoingTo('assert that the product guest checkout configuration is shown'); + $I->amOnPage('admin/catalog/products'); + $I->click(__('admin::app.catalog.products.add-product-btn-title')); + $I->selectOption('attribute_family_id', 1); + $I->fillField('sku', $this->faker->uuid); + $I->dontSeeInSource('The "SKU" field is required.'); + $I->click(__('admin::app.catalog.products.save-btn-title')); + $I->seeInCurrentUrl('admin/catalog/products/edit'); + $I->scrollTo('#new'); + $I->see('Guest Checkout'); + $I->seeInSource('amGoingTo('turn OFF the global guest checkout configuration'); + $I->amOnPage('/admin/configuration/catalog/products'); + $I->see(__('admin::app.admin.system.allow-guest-checkout')); + $I->selectOption('catalog[products][guest-checkout][allow-guest-checkout]', 0); + $I->click(__('admin::app.configuration.save-btn-title')); + $I->seeRecord('core_config', ['code' => 'catalog.products.guest-checkout.allow-guest-checkout', 'value' => 0]); + + $I->amGoingTo('assert that the product guest checkout configuration is not shown'); + $I->amOnPage('admin/catalog/products'); + $I->click(__('admin::app.catalog.products.add-product-btn-title')); + $I->selectOption('attribute_family_id', 1); + $I->fillField('sku', $this->faker->uuid); + $I->dontSeeInSource('The "SKU" field is required.'); + $I->click(__('admin::app.catalog.products.save-btn-title')); + $I->seeInCurrentUrl('admin/catalog/products/edit'); + $I->scrollTo('#new'); + $I->dontSee('Guest Checkout'); + $I->dontSeeInSource('selectOption('select#attribute_family_id', $attributeFamily->id); - $sku = $this->faker->randomNumber(3); + $sku = $this->faker->uuid; $I->fillField('sku', $sku); $I->click(__('admin::app.catalog.products.save-btn-title')); diff --git a/tests/functional/Shop/GuestCheckoutCest.php b/tests/functional/Shop/GuestCheckoutCest.php new file mode 100644 index 000000000..d6a66f732 --- /dev/null +++ b/tests/functional/Shop/GuestCheckoutCest.php @@ -0,0 +1,90 @@ +faker = Factory::create(); + + $pConfigDefault = [ + 'productInventory' => ['qty' => $this->faker->randomNumber(2)], + 'attributeValues' => [ + 'status' => true, + 'new' => 1, + 'guest_checkout' => 0 + ], + ]; + $pConfigGuestCheckout = [ + 'productInventory' => ['qty' => $this->faker->randomNumber(2)], + 'attributeValues' => [ + 'status' => true, + 'new' => 1, + 'guest_checkout' => 1 + ], + ]; + + $this->productNoGuestCheckout = $I->haveProduct($pConfigDefault, ['simple']); + $this->productNoGuestCheckout->refresh(); + + $this->productGuestCheckout = $I->haveProduct($pConfigGuestCheckout, ['simple']); + $this->productGuestCheckout->refresh(); + } + + public function testGuestCheckout(FunctionalTester $I) { + $I->amGoingTo('try to add products to cart with guest checkout turned on or off'); + + $scenarios = [ + [ + 'name' => 'false / false', + 'globalConfig' => 0, + 'product' => $this->productNoGuestCheckout, + 'expectedRoute' => 'customer.session.index' + ], + [ + 'name' => 'false / true', + 'globalConfig' => 0, + 'product' => $this->productGuestCheckout, + 'expectedRoute' => 'customer.session.index' + ], + [ + 'name' => 'true / false', + 'globalConfig' => 1, + 'product' => $this->productNoGuestCheckout, + 'expectedRoute' => 'customer.session.index' + ], + [ + 'name' => 'true / true', + 'globalConfig' => 1, + 'product' => $this->productGuestCheckout, + 'expectedRoute' => 'shop.checkout.onepage.index' + ], + ]; + + foreach ($scenarios as $scenario) { + $I->wantTo('test conjunction "' . $scenario['name'] . '" with globalConfig = ' . $scenario['globalConfig'] . ' && product config = ' . $scenario['product']->getAttribute('guest_checkout')); + $I->setConfigData(['catalog.products.guest-checkout.allow-guest-checkout' => $scenario['globalConfig']]); + $I->assertEquals($scenario['globalConfig'], core()->getConfigData('catalog.products.guest-checkout.allow-guest-checkout')); + $I->amOnRoute('shop.home.index'); + $I->see($scenario['product']->name, '//div[@class="product-information"]/div[@class="product-name"]'); + $I->click(__('shop::app.products.add-to-cart'), + '//form[input[@name="product_id"][@value="' . $scenario['product']->id . '"]]/button'); + $I->seeInSource(__('shop::app.checkout.cart.item.success')); + $I->amOnRoute('shop.checkout.cart.index'); + $I->see('Shopping Cart', '//div[@class="title"]'); + $I->makeHtmlSnapshot('guestCheckout_'.$scenario['globalConfig'].'_'.$scenario['product']->getAttribute('guest_checkout')); + $I->see($scenario['product']->name, '//div[@class="item-title"]'); + $I->click( __('shop::app.checkout.cart.proceed-to-checkout'), '//a[@href="' . route('shop.checkout.onepage.index') . '"]'); + $I->seeCurrentRouteIs($scenario['expectedRoute']); + $cart = Cart::getCart(); + $I->assertTrue(Cart::removeItem($cart->items[0]->id)); + } + } +} \ No newline at end of file