Developer Documentation
Developer Documentation
- Account Area
- What are the default pages in the account area?
- What are the default sets in the account area?
- What are the default forms in the account area?
- How do I inject into a slot in the account area?
- How do I configure the account area?
- How do I create a custom page in the account area?
- How do I extend a form in the account area?
- How do I create a custom set in the account area?
- How do I extend a page or set in the account area?
- How do I add a custom CSS file to the account area?
- How do I create a new form for the account area?
- What are the available slots in the account area?
- Admin Dashboard Lenses
- What are the default dashboard lenses available?
- What is a dashboard lens?
- How do I create a custom dashboard lens?
- How do I use a custom view for my dashboard lens?
- How do I add a custom dashboard lens to the dashboard?
- How do I remove a dashboard lens from the dashboard?
- Admin Filters
- What is an admin filter?
- How do I create a custom admin filter?
- How do I create a checkbox list admin filter?
- How do I create an options date range admin filter?
- How do I create a dropdown admin filter?
- How do I create a date range admin filter?
- How do I create a searchable select admin filter?
- Admin Product Picker Component
- How do I use the product picker in my custom module?
- What are the available props for the product picker?
- What are the available events for the product picker?
- Admin Reports
- How do I create a custom report?
- How do I add lenses to my custom report?
- How do I add a table to my custom report?
- How do I add exporters to my custom report?
- How do I extend existing reports?
- How do I add admin filters to my custom report?
- Admin Slots
- Admin Transformers
- Admin Vue
- How do I add custom clips to the redactor editor
- How do I use Vue in the admin?
- How do I add a custom Vue component to the admin?
- Blocks
- Configuration
- How do I register new permissions?
- How do I check for permissions?
- How do I change how order references are generated?
- What is the robots file and how does it differ from normal?
- How do I store product images on S3
- Customization
- How do I remove the manufacturers name from the order items name?
- How do I remove the manufacturer from the product slug/url?
- Developer Faqs
- Does Aero support Laravel Homestead installation?
- Can I use Aero project credentials across multiple projects?
- Is Laravel required to run Aero?
- What versions of Elasticsearch does Aero support?
- Which versions of PHP will Aero run on?
- How do I configure the PHP memory limit?
- Events
- How do I create a new event listener?
- What are the events available?
- How do I add a custom event to a Service Provider?
- What are event listeners?
- How do I create a new event?
- How do I make custom mail notification events?
- Extending Core Functionality
- What are models and how do I use them?
- How do I add a custom field to the new and edit product page?
- What are the available commands?
- How to add a custom field to the address forms?
- How do I extend the address forms?
- How do I add settings to my model?
- How do I add a custom price to a product with a module?
- General
- How do I set up a slug validator?
- How do I add a route to the CSRF exception list?
- What queues does Aero use?
- How to Set the Page Title for the Admin
- Google Shopping Feed
- How do I configure the Google Shopping feed?
- How do I assign Google categories?
- How do I export the Google Shopping feed?
- How do I generate a Google Shopping feed?
- Installation Deployment
- Introduction
- Introduction to backend admin slots
- Introduction to collections for developers
- Introduction to permissions for developers
- Introduction to transformers
- Introduction to events for developers
- Introduction to pipelines
- Introduction to the account area
- Introduction to response builders
- Introduction to listings and search
- Introduction to payment drivers
- What is an Aero Commerce Agora account and why do I need it?
- How do I obtain package repository credentials?
- Introduction to the admin
- Introduction to resource lists
- Introduction to Laravel PHP Framework
- Listings And Search
- How do I create a custom sort order for the listings pages?
- What are the available documents for listings and search?
- How do I re-index listing and search documents?
- How do I add custom facets to listing and search?
- How do I setup the search index?
- How do I re-build the listings and search index?
- How do I extend the document structure for listings and seach?
- How do I filter listings?
- How to Extend the Elastic Search Listing Model
- Module Development
- How do I register a custom Vue component for my module
- Anatomy of a custom module
- How do I create a custom module?
- How do I setup routes for my custom module?
- Payment Drivers
- What is the payment driver interface in my payment driver?
- What are the capturing methods in my payment driver?
- How do I cancel a payment in my payment driver?
- How do I capture a payment in my payment driver?
- How do I refund a payment in my payment driver?
- What are operating modes in my payment driver?
- How do I support express checkout in my payment driver?
- Can I restricting availability of my payment driver?
- How do I return a payment response in my payment driver?
- How do I register the transaction in my payment driver?
- How do I complete the transaction in my payment driver?
- How do I display payment icons on checkout in my payment driver?
- How do I restrict the availability of my payment driver?
- Payment Gateways
- Php Traits
- Aero\\Common\\Traits\\CanExtendFillable
- Aero\\Common\\Traits\\CastsToJavaScript
- Aero\\Common\\Traits\\Sluggable
- Aero\\Catalog\\Traits\\Taggable
- Aero\\Common\\Traits\\CanHaveAdditionalAttributes
- Aero\\Catalog\\Traits\\HasPrices
- Aero\\Store\\Traits\\Seoable
- Aero\\Responses\\Traits\\HasSections
- Aero\\Admin\\Traits\\HasPermissions
- Aero\\Admin\\Traits\\IsExtendable
- Pipelines
- Resource Lists
- How do I create a custom bulk action?
- What are the default resource lists?
- How do I create my own resource list?
- How do I extend an exisiting resource list?
- What is a bulk action?
- How do I add a custom column to the product list?
- How do I add a custom column to the order list?
- Response Builders
- How do I access the properties of the response builder?
- How do I pass data to a response?
- How do I redirect a response?
- How do I set the response status code?
- How do I set response headers?
- How do I attach middlewear to a response builder?
- How do I extend responses?
- How do I change the response view?
- What are the available responses from the response builder?
- Settings
- What are the settings available to developers?
- Do do I set the default value of my custom setting?
- How do I register custom settings?
- How do I access a custom setting?
- What are the available setting types?
- How do I configure the admin edit page?
- How do I handle translations?
- How do I add settings to the product model?
- Validation
- How do I create custom validation requests?
- How do I use validation requests?
- How do I display validation error messages?
- What are the available validation requests?
- How do I set a custom order validator for shipping methods?
- How do I set a custom order validator for shipping rates?
- How do I use a custom total item price totalizer for a shipping rate?
- How do I use a custom total item weight totalizer for a shipping rate?
Account Area
Account Area
What are the default pages in the account area?
The following classes are in the Aero\AccountArea\Http\Responsesnamespace.
| Class | Route | Description |
|---|---|---|
| AccountLoginPage | login | A page where users can log in. |
| AccountRegisterPage | register | A page where guests can register. |
| AccountPasswordForgotPage | password.forgot | A page where users can send a password reset email to their email. |
| AccountPasswordResetPage | password.reset | A page where users can reset their password. |
| AccountOrdersPage | account.orders | A page that lists all of a user's orders and lets them filter/search. |
| AccountOrderViewPage | account.view-order | A page that shows more information about an order. |
| AccountAddressesPage | account.addresses | A page that lists all of the users addresses. |
| AccountAddressNewPage | account.new-address | A page where users can create a new address, |
| AccountAddressEditPage | account.edit-address | A page where users can edit one of their addresses. |
| AccountDetailsPage | account.details | A page where users can update their account details and change their password. |
| AccountInvoicePage | account.view-invoice | A simple invoice for an order - currently not linked to from anywhere. |
| AccountOverviewPage | account | The first page a user sees after they log in - currently redirects to the orders page. |
What are the default sets in the account area?
The following classes are in the Aero\AccountArea\Http\Responsesnamespace.
| Class | Method | Route |
|---|---|---|
| AccountLoginSet | Post | login |
| AccountLogoutSet | Post | logout |
| AccountRegisterSet | Post | register |
| AccountPasswordForgotSet | Post | password.forgot |
| AccountPasswordResetSet | Post | password.reset |
| AccountAddressNewSet | Post | account.new-address |
| AccountAddressEditSet | Put | account.edit-address |
| AccountAddressMakeDefaultSet | Put | account.make-default-address |
| AccountDetailsSet | Put | account.details |
| AccountAddressDelete | Delete | account.delete-address |
What are the default forms in the account area?
The following classes are in the Aero\AccountArea\Http\Formsnamespace.
| Class | Description |
|---|---|
| AccountAddressEditForm | The edit address form. |
| AccountAddressNewForm | The new address form. |
| AccountDetailsForm | The account details form. |
| AccountLoginForm | The login form. |
| AccountPasswordForgotForm | The forgotten password form. |
| AccountPasswordResetForm | The password reset form. |
| AccountRegisterForm | The register form. |
How do I inject into a slot in the account area?
You can inject into account area slots using the Aero\AccountArea\AccountAreaSlot::injectmethod. This method accepts two parameters, the key of the slot you want to inject into and then the view or a closure that defines the view to be injected. When passing a closure it enables you to add additional view data to use inside of your injected view.
<?php
namespace Acme\MyModule;
use Aero\AccountArea\AccountAreaSlot;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-module');
AccountAreaSlot::inject('orders.order.card.header', 'my-module::account-area-slot');
AccountAreaSlot::inject('orders.order.card.header', function ($data) {
$data['custom_data'] = 'custom value';
return view('my-module::account-area-slot', $data);
});
}
}
How do I configure the account area?
ENV Variables
| Variable | Default | Description |
|---|---|---|
| AERO_ACCOUNT_AREA_THEME | aerocommerce/account-area | Defines the namespace that should be registered as having the views and resources for the account area. This should only be changed under unique circumstances. |
| AERO_ACCOUNT_AREA_LAYOUT | layouts.main | Defines the layout that the account area views use. If the layout provided doesn’t exist, account-area::layouts.blank will be used. |
Config File
If you would like to change any of the config values you can publish the config file by running:
php artisan vendor:publish --provider="Aero\AccountArea\ServiceProvider"
Once the file is published you will be able to edit it through the config php file created at /config/aero/account-area.php.
Date Format
This defines the format used when displaying any dates in the account area.
Order Status State Classes
This defines the map used to find the colour for the order status state.
The colours are defined using CSS classes:
| Class | Colour |
|---|---|
| success | Green |
| warning | Orange |
| error | Red |
Order Status Radio Options
This defines the order statuses shown in the radio options on the orders page and what order status states they map to.
Order Years
This defines the number of previous years to display in the years dropdown on the orders page.
Page Sections
This defines the sections that make up each page by default. You can add/edit/remove sections through code, it is not necessary to adjust these values usually.
Slots
This defines the views that are injected into slots by default.
Links
This defines the links that should be in the navbar by default. You can add/edit/remove links through code, it is not necessary to adjust these values usually.
How do I create a custom page in the account area?
To create and register a custom account area page you need to use the **Aero\AccountArea\AccountArea::registerPage()**helper method. This method expects you to pass a class that extends Aero\AccountArea\AccountAreaPage.
<?php
namespace Acme\MyModule;
use Acme\MyModule\AccountArea\Pages\CustomPage;
use Aero\AccountArea\AccountArea;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
AccountArea::registerPage(CustomPage::class);
}
}
Your account area page class must implement the title, route, and routeNamemethods that all should return a string. It’s common practice to use Laravel Localization for the title and route so that they can change depending on the language used. You should not use this for the routeName as the routeName is used in code to refer to the route and therefore should never change.
You should also define a protected static steps and middleware array and a protected sections array. The static steps array can be used to define any extra response steps required for your page. The middleware array can be used to define any middleware required for your page (the most useful being ‘account’ which means users need to be logged in to view your page). The sections array defines the views that make up your page.
<?php
namespace Acme\MyModule\AccountArea\Pages;
use Aero\AccountArea\AccountAreaPage;
class CustomPage extends AccountAreaPage
{
protected static $steps = [
Steps\AttachWishlist::class,
];
protected static $middleware = [
'account',
];
protected $sections = [
'navbar' => 'account-area::sections.navbar',
'page-header' => 'account-area::partials.page-header',
'wishlist' => 'wishlist::account-area-wishlist',
];
static function title(): string
{
return __('My Custom Page');
}
static function route(): string
{
return __('custom-page');
}
static function routeName(): string
{
return 'acme.my-module.custom-page';
}
}
Adding your Page to the Navbar
To add your page to the navbar you need to make sure your page class uses the Aero\AccountArea\Traits\HasAccountAreaLinktrait and then add the link to the navbar using the addLink, addLinkBefore, or addLinkAftermethods from within the pipeline.
The Aero\AccountArea\Traits\HasAccountAreaLinktrait makes you implement a toLinkmethod that returns an array of data used to render your link. The array should have an icon and text key.
public static function toLink(): array
{
return [
'icon' => 'my-module::icon',
'text' => 'My Page',
];
}
<?php
namespace Acme\MyModule;
use Acme\MyModule\AccountArea\Pages\CustomPage;
use Aero\AccountArea\AccountAreaPage;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
AccountAreaPage::extend(function ($page) {
$page->addLink(CustomPage::class);
});
}
}
How do I extend a form in the account area?
Forms can be extended using a pipeline from your modules service providers setup method or your projects app service providers boot method. You can read more about pipelines in the article "Introduction to pipelines
Adding a Field
To add fields to a form you can use the addSection, addSectionBefore, or addSectionAftermethods from within the pipeline.
addSection Method
This method accepts a key and a view to be injected. A field added with this method will be added as the first field.
addSectionBefore Method
This method accepts the same parameters as the addSectionmethod but additionally accepts a third parameter, the key of the field this new field should be added before.
addSectionAfter Method
This method accepts the same parameters as the addSectionmethod but additionally accepts a third parameter, the key of the field this new field should be added after.
Finding the Current Field Keys
If you do not know the keys of the current fields you can use this code snippet to dump the current keys when visiting the page with the form.
<?php
namespace Acme\MyModule;
use Aero\AccountArea\Http\Forms\AccountAddressForm;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
AccountAddressForm::extend(function ($form) {
dd($form->getSections()->keys()->toArray());
});
}
}
<?php
namespace Acme\MyModule;
use Aero\AccountArea\Http\Forms\AccountAddressForm;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
AccountAddressForm::extend(function ($form) {
$form->addSection('my-section', 'my-view');
});
}
}
Removing a Field
You can use the removeSectionmethod within the pipeline to remove a field from a form. The methods expect one parameter, the key of the field that you would like to remove.
<?php
namespace Acme\MyModule;
use Aero\AccountArea\Http\Forms\AccountAddressForm;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
AccountAddressForm::extend(function ($form) {
$form->removeSection('line2');
});
}
}
How do I create a custom set in the account area?
Sets are used for POST/PUT/DELETE requests. To create and register a custom account area set you need to use the Aero\AccountArea\AccountArea::registerPost(), Aero\AccountArea\AccountArea::registerPut(), or Aero\AccountArea\AccountArea::registerDelete(), helper method. This method expects you to pass a class that extends Aero\AccountArea\AccountAreaSet.
<?php
namespace Acme\MyModule;
use Acme\MyModule\AccountArea\Pages\CustomSet;
use Aero\AccountArea\AccountArea;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
AccountArea::registerPost(CustomSet::class);
}
}
Your account area set class must implement the routeand routeNamemethods that all should return a string. You should also define a protected static steps and middleware array. The steps array can be used to define any extra response steps required for your set. The middleware array can be used to define any middleware required for your set (the most useful being ‘account’ which means the user needs to be logged in).
<?php
namespace Acme\MyModule\AccountArea\Pages;
use Aero\AccountArea\AccountAreaSet;
class CustomSet extends AccountAreaSet
{
protected static $steps = [
Steps\DeleteWishlist::class,
];
protected static $middleware = [
'account',
];
static function route(): string
{
return 'wishlist/delete';
}
static function routeName(): string
{
return 'acme.my-module.wishlist.delete';
}
}
How do I extend a page or set in the account area?
Account Area Pages/Sets are Response Builders (link) which means that they can be extended using a pipeline. You can use all of the usual Response Builder stuff and also for pages you can add/remove/update sections using addSection, addSectionAfter, addSectionBefore, and removeSection.
addSection Method
This method accepts a key string and a view string. A section added with this method will be added as the first section.
addSectionBefore Method
This method accepts the same parameters as the addSectionmethod but additionally accepts a third parameter, the key of the section this new section should be added before.
addSectionAfter Method
This method accepts the same parameters as the addSectionmethod but additionally accepts a third parameter, the key of the section this new section should be added after.
removeSection Method
This method accepts one parameter, the key of the section to remove.
Finding the Current Section Keys
If you do not know the keys of the current sections you can use this code snippet to dump the current keys out when visiting the page.
<?php
namespace Acme\MyModule;
use Aero\AccountArea\Http\Responses\AccountOrdersPage;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
AccountOrdersPage::extend(function (AccountOrdersPage $page) {
dd($page->getSections()->keys());
});
}
}
<?php
namespace Acme\MyModule;
use Aero\AccountArea\Http\Responses\AccountOrdersPage;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-module');
AccountOrdersPage::extend(function (AccountOrdersPage $page) {
$page->addSection('my-section', 'my-module::view');
$page->addSectionAfter('my-other-section', 'my-module::view', 'navbar');
});
}
}
How do I add a custom CSS file to the account area?
You can add a custom CSS file to the account area by creating a file named account-area.cssin your public directory.
Example contents where we're overriding the default colours:
:root {
--bpa-color-primary: deepskyblue;
--bpa-color-success: forestgreen;
--bpa-color-warning: darkorange;
--bpa-color-error: red;
--bpa-color-helpbox: aliceblue;
--bpa-color-content-background: ghostwhite;
}
How do I create a new form for the account area?
To create a form you need to create a class that extends the AccountAreaFormclass.
The $sectionsarray lists the views used to render the form.
The methodmethod defines the method that the form will use (this should be post, get, put, or delete).
The routemethod should return the URL for the form. A $datavariable is accessible in case the route requires parameters that are available in the data.
<?php
namespace Acme\MyModule;
use Aero\AccountArea\AccountAreaForm;
class MyForm extends AccountAreaForm
{
protected $sections = [
'email' => 'account-area::partials.email-input',
'password' => 'account-area::partials.password-input',
'forgot-password' => 'account-area::partials.login-forgot-password-link',
'submit' => 'account-area::partials.login-button',
];
protected function method(): string
{
return 'post';
}
protected function route($data): string
{
return route('login');
}
}
Registering a form
Any form you wish to use in the account area should be registered in your service provider.
<?php
namespace Acme\MyModule;
use Aero\AccountArea\AccountArea;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup(): void
{
AccountArea::registerForm(MyForm::class);
}
}
What are the available slots in the account area?
The following slots can be injected into.
orders.order.card.header-orders.order.card.dispatched-orders.order.card.not-dispatched-orders.order.card.footer-addresses.address.card.footer
Admin Dashboard Lenses
Admin Dashboard Lenses
What are the default dashboard lenses available?
The default dashboard lenses are stored in the admin configuration file as an array called dashboard_lenses. You can add or remove dashboard lens classes from this array.
| Key | Class | Permission |
|---|---|---|
| revenue-lens | Aero\Admin\Lenses\RevenueLens | reports.orders |
| average-order-value-lens | Aero\Admin\Lenses\AverageOrderValueLens | reports.orders |
| top-shipping-countries-lens | Aero\Admin\Lenses\TopShippingCountriesLens | reports.orders |
| returns-rate-lens | Aero\Admin\Lenses\ReturnsRateLens | reports.orders |
| orders-lens | Aero\Admin\Lenses\OrdersLens | reports.orders |
| top-selling-manufacturers-lens | Aero\Admin\Lenses\TopSellingManufacturersLens | reports.catalog |
| top-selling-items-lens | Aero\Admin\Lenses\TopSellingItemsLens | reports.catalog |
What is a dashboard lens?
Dashboard lenses are the blocks of content that make up the interactive part of the admin dashboard.
They provide an overview of various stats such as your total revenue, orders, and your top selling products.
There are a number of default lenses and you can also create your own
How do I create a custom dashboard lens?
To create a dashboard lens you need to create a class that extends Aero\Admin\AdminLensand implements the datamethod.
data Method
This method is executed every time the lens needs data (when the dashboard loads or when the dashboard date range is changed). This method must return an Illuminate\Http\JsonResponsebecause it is called on the frontend by javascript.
data Method Parameters
| Type | Description |
|---|---|
| Request | The first parameter is the request. |
| Array | The second parameter is an array that holds the start and end date selected for the dashboard stats. This array has a start and end key that has the dates. |
| Array | The third parameter is an array that holds information about the comparison dates. This array has a type key that is 1 for the same period prior, 2 for the same period 1 year ago, or 3 for a custom period. This array has a date key that has the same structure as the second parameter. This array also has a text key that holds text to be displayed to explain the comparison. |
Here is the code for our top shipping countries implementation:
<?php
namespace Aero\Admin\Lenses;
use Aero\Admin\AdminLens;
use Aero\Cart\Models\Order;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class TopShippingCountriesLens extends AdminLens
{
protected $title = 'Top Shipping Countries';
protected static $permission = 'reports.orders';
protected static $containerClass = 'row-span-2';
protected $view = 'admin::lenses.percentage-list';
public function data(Request $request, array $date, array $compare): JsonResponse
{
$countries = Order::visible()->with('shippingAddress.country')->whereHas('shippingAddress')
->whereBetween('ordered_at', [$date['start'], $date['end']])->get()
->groupBy('shippingAddress.country.name')
->map(function ($group, $key) {
return [
'name' => $key,
'count' => $group->count(),
'percentage' => 0,
];
})->sortByDesc('count')->take(5);
$total = $countries->reduce(function ($count, $country) {
return $count + $country['count'];
}, 0);
$countries->transform(function ($country) use ($total) {
$country['value'] = number_format(($country['count'] / $total) * 100, 2);
$country['text'] = $country['count'].' '.Str::plural('order', $country['count']);
$country['percentage'] = $country['value'].'%';
return $country;
});
return response()->json([
'items' => $countries,
]);
}
}
How do I use a custom view for my dashboard lens?
You can optionally make your dashboard lens use a custom view. To do this you need to set the protected $viewstring on your lens to the view you would like to use.
<?php
namespace Aero\Admin\Lenses;
use Aero\Admin\AdminLens;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class TopShippingCountriesLens extends AdminLens
{
protected $view = 'admin::lenses.percentage-list';
public function data(Request $request, array $date, array $compare): JsonResponse
{
return response()->json([]);
}
}
Your view will have access to Blade and Vue as it is rendered as a slot. You will have access to the following variables:
| Variable | Type | Description |
|---|---|---|
| lens.data | Vue | This is an object that holds the response from the data method of your admin lens. You can effectively pass any data you want in the json response and have access to it in Vue through this variable. |
| lens.date | Vue | This is an object that has start, end and compare keys. |
| lens.loading | Vue | This is a boolean that is true while the lens is making an API call to get its data. |
| lens.notLoading | Vue | This is a boolean that is false unless the lens is making an API call. |
| lens.noData | Vue | This is a boolean that is true if the store has no orders. |
| lens.hasData | Vue | This is a boolean that is true if the store has orders. |
| lens.renderDateGetVariables | Vue | This returns an empty string or the selected date formatted as the $dateGetVariables property on the lens class describes. |
| $title | Blade | This is the value of the $title property on your lens class. |
| $link | Blade | This is the value of the link() function on your lens class. |
| $noTextData | Blade | This is the value of the $noDataText property on your lens class. |
Here is the code for our default lens view:
<div class="card h-full relative">
<svg class="w-4 h-4 absolute pin-t pin-r m-4 text-grey-light stroke-current [ animation-spin ]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18.111 18.068" v-if="lens.loading">
<path d="M20,4V9h-.582M4.062,11A8,8,0,0,1,19.418,9m0,0H15M4,20V15h.581m0,0a8,8,0,0,0,15.357-2M4.581,15H9" transform="translate(-2.945 -2.964)" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
</svg>
<svg class="w-4 h-4 absolute pin-t pin-r m-4 text-success stroke-current [ animation-fade-out ]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18.11 18.07" v-else>
<g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M2.06 10.03l4 4 10-10" data-name="Path 103"/>
</g>
</svg>
<h3 class="uppercase text-base font-normal text-text h-auto p-0 mb-3">{{ $title }}</h3>
<div class="flex flex-wrap -mx-3">
<div class="w-1/2 px-3 mb-6" v-if="lens.loading || lens.noData">
<div class="mb-1">
<template v-if="lens.loading">
<skeleton-box height="2.125rem" width="4rem" />
</template>
<template v-else-if="lens.noData">
<p class="text-3xl font-medium mb-1">–</p>
</template>
</div>
<template v-if="lens.loading">
<skeleton-box width="9rem" height="0.8125rem" />
</template>
<template v-else-if="lens.noData">
<p class="text-xs">{{ $noDataText }}</p>
</template>
</div>
<div class="w-1/2 px-3 mb-6" v-if="lens.notLoading && lens.hasData" v-for="data in lens.data" :key="data.currency_code">
<div class="mb-1">
<p class="text-3xl font-medium" v-html="data.text"></p>
</div>
<p class="text-xs"><strong v-text="data.compare.percentage" class="font-medium" :class="{ 'text-success' : data.compare.up, 'text-error' : ! data.compare.up }"></strong> <span v-text="data.compare.text"></span></p>
</div>
</div>
@isset($link)
<a class="dashboard-link" v-if="lens.noData">{{ $link['text'] }}</a>
<a class="dashboard-link" :href="'{{ $link['route'] }}' + lens.renderDateGetVariables" v-else>{{ $link['text'] }}</a>
@endif
</div>
Adding Permissions to your Dashboard Lens
You can optionally make your dashboard lens require a permission (if the user doesn’t have the permission then the lens will not be rendered). To do this you need to set the protected static $permission string on your lens class to the permission you would like to use.
<?php
namespace Aero\Admin\Lenses;
use Aero\Admin\AdminLens;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class TopShippingCountriesLens extends AdminLens
{
protected static $permission = 'dashboard.lens.orders';
public function data(Request $request, array $date, array $compare): JsonResponse
{
return response()->json([]);
}
}
How do I add a custom dashboard lens to the dashboard?
To add your custom dashboard lens to the dashboard you need to register it with the admin and the admin dashboard.
To register your dashboard lens with the admin you need to call the Aero\Admin\Facades\Adminfacade registerLensmethod and pass in your dashboard lens class.
<?php
namespace Acme\MyModule;
use Aero\Admin\Facades\Admin;
use Aero\Admin\Lenses\RevenueLens;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
Admin::registerLens(RevenueLens::class);
}
}
Once you have registered your dashboard lens with the admin you are able to extend Aero\Admin\Http\Responses\AdminDashboardPageand add your lens using the setLens, addLens, addLensBefore, or addLensAftermethods.
setLens Method
This method accepts a key and a lens class. The key's current value will be replaced with the lens class you provide.
addLens Method
This method accepts a key and a lens class. When using this method your lens will be added as the first dashboard lens.
addLensBefore Method
This method accepts the same parameters as the addLensmethod but additionally accepts a third parameter, the key of the lens this new lens should be added before.
addLensAfter Method
This method accepts the same parameters as the addLensmethod but additionally accepts a third parameter, the key of the lens this new lens should be added after.
Finding the Current Dashboard Lenses Keys
If you do not know the keys of the current dashboard lenses you can use this code snippet to dump the current keys out when visiting the dashboard.
<?php
namespace Acme\MyModule;
use Aero\Admin\Http\Responses\AdminDashboardPage;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
AdminDashboardPage::extend(function (AdminDashboardPage $page) {
dd($page->getLenses()->keys());
});
}
}
<?php
namespace Acme\MyModule;
use Aero\Admin\Facades\Admin;
use Aero\Admin\Http\Responses\AdminDashboardPage;
use Aero\Admin\Lenses\RevenueLens;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
Admin::registerLens(RevenueLens::class);
AdminDashboardPage::extend(function (AdminDashboardPage $page) {
$page->addLensBefore('revenue-lens', RevenueLens::class, 'average-order-value-lens');
});
}
}
How do I remove a dashboard lens from the dashboard?
You are able to extend Aero\Admin\Http\Responses\AdminDashboardPageand use the removeLensmethod. You will need to pass in the key of the lens that you want to remove.
<?php
namespace Acme\MyModule;
use Aero\Admin\Http\Responses\AdminDashboardPage;
use Aero\Admin\Lenses\RevenueLens;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
AdminDashboardPage::extend(function (AdminDashboardPage $page) {
$page->removeLens('revenue-lens');
});
}
}
Admin Filters
Admin Filters
What is an admin filter?
Admin filters are the filters that populate the sidebars of reports and resource lists. As the name suggests, they allow users to filter the data they’re seeing through the report or resource list.
All admin filters ultimately extend Aero\Admin\Filters\AdminFilterand therefore all have these common methods that you can optionally override:
title Method
This is a public method that returns the title for the filter. This title is displayed on the frontend. By default the title is generated from the class name.
key Method
This is a public method that returns the key for the filter. The key is used for the request parameter for the filter. By default the key is generated from the class name.
You can read about the other methods in the "How do I create a custom Admin Filter?
How do I create a custom admin filter?
To create a custom admin filter you need to create a class that extends Aero\Admin\Filters\AdminFilterand implements the handle method.
handle Method
This method is executed on every request made to a report or resource list with your filter and accepts 2 parameters. The first parameter is a Symfony\Component\HttpFoundation\ParameterBag, and the second parameter is the query.
render Method
This method defines how the filter is rendered. By default it will return a view (that you can set with the protected $viewproperty) and pass in the **options()**and **viewData()**arrays.
stateFields Method
This method returns an array of names for the parameters this filter uses in the request. It’s important that if you use any parameters outside of the default parameter (which uses the **$this->key()**for the name) that you override this method and return them in the array. This method is used to gather all of the fields that should be reset to remove your filter.
options Method
This method returns an array of data that is passed to the view by the render method. If you’re creating a new filter that may be used by many classes it is a good idea to use this method instead of the viewData method so that classes that extend your class can use the viewData method if required.
viewData Method
This method returns an array of data that is passed to the view by the render method.
Here is the code for our date range admin filter implementation:
<?php
namespace Aero\Admin\Filters;
use Symfony\Component\HttpFoundation\ParameterBag;
abstract class DropdownAdminFilter extends AdminFilter
{
protected $view = AdminFilterTypes::DROPDOWN;
protected function selectedOption()
{
return request()->input($this->key());
}
protected function options(): array
{
return [
'title' => $this->title(),
'key' => $this->key(),
'options' => $this->dropdowns(),
'selected' => $this->selectedOption(),
];
}
public function handle(ParameterBag $parameters, $query)
{
if (($selected = $parameters->get($this->key())) && $selected != '') {
$this->handleDropdown($selected, $query);
}
}
abstract protected function handleDropdown($selected, $query);
abstract protected function dropdowns(): array;
}
How do I create a checkbox list admin filter?
To create a checkbox list admin filter you need to create a class that extends Aero\Admin\Filters\CheckboxListAdminFilterand implements the handleCheckboxListand checkboxesmethods.
handleCheckboxList Method
This method is executed when any checkboxes are checked and accepts two parameters. The first parameter is an array of the selected items and the second parameter is the query.
checkboxes Method
This method provides the results shown in the rendered checkbox list on the frontend. It needs to return an array that has id, name, and url as keys. The url key should use the **$this->getUrlFor()**helper method to create a url for the id.
<?php
namespace Acme\MyModule\Filters;
use Aero\Admin\Filters\CheckboxListAdminFilter;
use Aero\Cart\Models\OrderStatus;
class OrderStatusStateAdminFilter extends CheckboxListAdminFilter
{
protected function handleCheckboxList(array $selected, $query)
{
$query->whereIn('state', $selected);
}
protected function checkboxes(): array
{
return OrderStatus::query()->distinct('state')->get()->map(function ($status) {
return [
'id' => $status->state,
'name' => ucwords(implode(' ', explode('_', $status->state))),
'url' => $this->getUrlFor($status->state),
];
})->toArray();
}
}
How do I create an options date range admin filter?
The options date range admin filter works in the same way as the date range admin filter but displays some dynamic options to the user on the frontend. These dynamic options are especially useful when users save their applied filters.
To create an options date range admin filter you need to create a class that extends Aero\Admin\Filters\OptionsDateRangeAdminFilterand implements the handleDateRangemethod.
handleDateRange Method
This method is executed when a date is set and accepts 3 parameters. The first parameter is a Carbon object for the selected start date, the second parameter is a Carbon object for the selected end date, and the third parameter is the query.
You can switch this filter to not use past options (such as yesterday, last 7 days, last 30 days) by setting the $lastModeproperty to false. If this property is false the options will become future options (such as tomorrow, next 7 days, next 30 days).
<?php
namespace Aero\Admin\Filters\Order;
use Aero\Admin\Filters\OptionsDateRangeAdminFilter;
class OrderDeliverOnDateAdminFilter extends OptionsDateRangeAdminFilter
{
protected $lastMode = false;
protected function handleDateRange($startDate, $endDate, $query)
{
$query->whereBetween('deliver_on', [$startDate, $endDate]);
}
}
How do I create a dropdown admin filter?
To create an options date range admin filter you need to create a class that extends Aero\Admin\Filters\DropdownAdminFilter and implements the handleDropdown and dropdowns methods.
handleDropdown Method
This method is executed when a dropdown option is set and accepts 2 parameters. The first parameter is the value selected and the second parameter is the query.
dropdowns Method
This method provides the results shown in the rendered dropdown on the frontend. It needs to return an array that has name and value as keys. If you use an empty string for a value then your handleDropdown method will not be called when that option is selected. This is useful for allowing users to effectively turn your filter off.
<?php
namespace Aero\Admin\Filters\MailNotification;
use Aero\Admin\Filters\DropdownAdminFilter;
class MailNotificationLayoutAdminFilter extends DropdownAdminFilter
{
protected function handleDropdown($selected, $query)
{
switch ($selected) {
case 'system':
$query->where('layout', 'system');
break;
case 'customer':
$query->where('layout', 'customer');
break;
}
}
protected function dropdowns(): array
{
return [
[
'value' => '',
'name' => 'View All',
],
[
'value' => 'customer',
'name' => 'Customer',
],
[
'value' => 'system',
'name' => 'System',
],
];
}
}
How do I create a date range admin filter?
To create a date range admin filter you need to create a class that extends Aero\Admin\Filters\DateRangeAdminFilterand implements the handleDateRangemethod.
handleDateRange Method
This method is executed when a date is set and accepts 3 parameters. The first parameter is a Carbon object for the selected start date, the second parameter is a Carbon object for the selected end date, and the third parameter is the query.
<?php
namespace Acme\MyModule\Filters;
use Aero\Admin\Filters\DateRangeAdminFilter;
class CreatedAtAdminFilter extends DateRangeAdminFilter
{
protected function handleDateRange($startDate, $endDate, $query)
{
$query->whereBetween('created_at', [$startDate, $endDate]);
}
}
How do I create a searchable select admin filter?
To create an options date range admin filter you need to create a class that extends Aero\Admin\Filters\SearchableSelectAdminFilter and implements the handleSearchableSelect, model, modelName, and searchRoute methods.
handleSearchableSelect Method
This method is executed when an option has been selected and accepts 2 parameters. The first parameter is an array of selected values and the second parameter is the query.
model Method
This method needs to return an instance of the model that will be used for searching. This will be used to automatically fetch the selected data.
modelName Method
This method defines the name that should be used in the searchable select for the selected options.
searchRoute Method
This method defines the route that will be used by the searchable select component to fetch data.
<?php
namespace Aero\Admin\Filters\Order;
use Aero\Admin\Filters\SearchableSelectAdminFilter;
use Aero\Cart\Models\Discount;
use Illuminate\Database\Eloquent\Model;
class OrderDiscountAdminFilter extends SearchableSelectAdminFilter
{
protected $multiple = true;
protected function handleSearchableSelect(array $selected, $query)
{
$query->where(function ($query) use ($selected) {
$query->whereHas('discounts', static function ($query) use ($selected) {
$query->whereIn('id', $selected);
});
});
}
protected function model(): Model
{
return new Discount();
}
protected function modelName(Model $model)
{
return $model->code ?? ($model->name ?? 'Discount #'.$model->id);
}
protected function searchRoute(): string
{
return route('admin.discounts.search');
}
}
Admin Product Picker Component
Admin Product Picker Component
How do I use the product picker in my custom module?
The Product Picker provides a modal that lets users select a single or many products/variants with quantities. Parts of the admin use this such as the Price Lists and Create Order form. Modules such as aerocargo/upsellsalso make use of the Product Picker.
You can use the <product-picker></product-picker>Vue component from any admin view inside of your module that extends the official admin layout (admin::layouts.main). When using the Product Picker you need to provide a url for the get-products-url prop (this value is usually always the admin.catalog.products.pickerroute) and you need to provide a slot for the button that will be used to open the picker.
@extends('admin::layouts.main')
@section('content')
<h2>
<a href="{{ route('admin.modules') }}" class="btn">@include('admin::icons.back') Back</a>
<span class="ml-4">My Module</span>
</h2>
<div class="card">
<product-picker v-slot="picker"
get-products-url="{{ route('admin.catalog.products.picker') }}">
<a href="#" @click.prevent="picker.open" class="btn">Add Product</a>
</product-picker>
</div>
@endsection
What are the available props for the product picker?
| Prop | Type | Description |
|---|---|---|
| get-products-url | String | This is the only required prop. This is the URL for the API to get the products from. It’s common to set this value to {{ route('admin.catalog.products.picker') }}. |
| image-url-prefix | String | This is the image factory prefix to use for the product image URLs. This is set by default to use {{ image_factory(60, 60)->contain() }}. |
| no-image-url | String | This is the image URL to use when a product doesn’t have an image. This is set by default to use {{ asset('modules/aerocommerce/admin/no-image.svg') }}. |
| action-name | String | This is the button text shown on the confirm button of the product picker modal (bottom right). This is set by default to “Add Products”. |
| clear-selected-after-emit | Boolean | |
| pick-product | Boolean | This is a boolean that defaults to false. When true, products will be allowed to be selected as well as variants. |
| product-only | Boolean | This is a boolean that defaults to false. When true, variants will not be listed and not selectable. |
| pick-only | Boolean | This is a boolean that defaults to false. When true, variants will not need to be in stock to be selectable. |
| needs-quantity | Boolean | This is a boolean that defaults to true. When false, a quantity will not be able to be set for the selected variants/products. |
| emit-image | Boolean | This is a boolean that defaults to false. When true, the products/variants image will be emitted when selected. |
| emit-sku | Boolean | This is a boolean that defaults to false. When true, the products/variants sku will be emitted when selected. |
| emit-model | Boolean | This is a boolean that defaults to false. When true, the products/variants model will be emitted when selected. |
| emit-name | Boolean | This is a boolean that defaults to false. When true, the products/variants name will be emitted when selected. |
| emit-price | Boolean | This is a boolean that defaults to false. When true, the products/variants price will be emitted when selected. |
Example
You can make use of the props like you usually would with a Vue component
@extends('admin::layouts.main')
@section('content')
<h2>
<a href="{{ route('admin.modules') }}" class="btn">@include('admin::icons.back') Back</a>
<span class="ml-4">My Module</span>
</h2>
<div class="card">
<product-picker v-slot="picker"
get-products-url="{{ route('admin.catalog.products.picker') }}"
:max-selection="5"
:pick-product="true"
:emit-name="true"
:emit-model="true"
:emit-sku="true">
<a href="#" @click.prevent="picker.open" class="btn">Add Product</a>
</product-picker>
</div>
@endsection
What are the available events for the product picker?
| Event | Description |
|---|---|
| products-selected | This event emits when the action button is clicked (bottom right of the modal) and provides an array of all of the selected product(s)/variant(s). |
| selected | This event emits when the action button is clicked (bottom right of the modal) and is emitted for each product/variant that has been selected - only a single product/variant is provided. |
Example
The usual use-case for the Product Picker is to allow users to select products and/or variants to be added to a list. This example code is a Blade view that allows the user to select up to 5 products or variants and then displays them in a list with a custom message. It also allows the user to remove the selected products/variants. The next steps to make this code fully functional would be to wrap the table in a form and to add a submit button that saves the products/variants.
@extends('admin::layouts.main')
@section('content')
<h2>
<a href="{{ route('admin.modules') }}" class="btn">@include('admin::icons.back') Back</a>
<span class="ml-4">My Module</span>
</h2>
<div class="card p-0">
<table>
<thead>
<tr class="header">
<th class="whitespace-no-wrap">Model</th>
<th class="whitespace-no-wrap">Sku</th>
<th class="whitespace-no-wrap">Message</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in list" :key="item.buyable_id + item.buyable_type" v-cloak>
<td class="whitespace-no-wrap">
<input type="hidden" :name="'items[' + index + '][buyable_id]'" :value="item.buyable_id">
<input type="hidden" :name="'items[' + index + '][buyable_type]'" :value="item.buyable_type">
<span v-text="item.model"></span>
</td>
<td class="whitespace-no-wrap" v-if="item.sku" v-text="item.sku"></td>
<td class="whitespace-no-wrap" v-else><span class="text-grey">—</span></td>
<td class="whitespace-no-wrap">
<input type="text" :name="'items[' + index + '][message]'" v-model="item.message" placeholder="Message">
</td>
<td>
<div class="flex items-center justify-end">
<a href="#" @click.prevent="list.splice(index, 1)">@include('admin::icons.bin')</a>
</div>
</td>
</tr>
<tr v-if="list.length === 0">
<td colspan="4">No data to show</td>
</tr>
</tbody>
</table>
<product-picker v-slot="picker"
v-if="list.length < 5"
get-products-url="{{ route('admin.catalog.products.picker') }}"
:max-selection="5"
:pick-product="true"
:emit-name="true"
:emit-model="true"
:emit-sku="true"
@selected="selected">
<a href="#" @click.prevent="picker.open" class="btn m-4">Add Product</a>
</product-picker>
</div>
@endsection
@push('scripts')
<script>
window.AeroAdmin.addData('list', []);
window.AeroAdmin.addMethod('selected', function (item) {
if (this.findIndex(item.buyable_id, item.buyable_type) === -1) {
this.list.push({
...item,
message: '',
});
} else {
this.$notify({
type: 'error',
title: (item.sku ?? item.name) + ' has already been added to the list',
});
}
});
window.AeroAdmin.addMethod('findIndex', function (id, type) {
return this.list.findIndex((l) => l.buyable_id === id && l.buyable_type === type);
});
</script>
@endpush
Admin Reports
Admin Reports
How do I create a custom report?
To create and register a custom report you need to use the Aero\Admin\AdminReportfacade inside of your app service providers boot method or module service providers setup method.
The facade requires a class that extends Aero\Admin\Reports\Report. Additionally, you can provide a title and summary (shown in the reports list in the admin) and any permissions required to use the report.
<?php
namespace Acme\MyModule;
use Acme\MyModule\Reports\OrdersReport;
use Aero\Admin\AdminReport;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
AdminReport::create(OrdersReport::class)
->title('Simple Orders Report')
->summary('View a simple report for your orders')
->permissions('orders.view');
}
}
Your report class must implement a newQuerymethod that must return a Illuminate\Database\Eloquent\Builder. This method provides the query your report will run from.
<?php
namespace Acme\MyModule\Reports;
use Aero\Admin\Reports\Report;
use Aero\Cart\Models\Order;
use Illuminate\Database\Eloquent\Builder;
class OrdersReport extends Report
{
protected function newQuery(): Builder
{
return Order::with([
'status', 'items', 'currency', 'discounts', 'payments.method',
]);
}
}
How do I add lenses to my custom report?
You need to create a class for your lens that extends Aero\Admin\Reports\ReportLensand implements the required public value function. The value function will return a string used to display your lens.
<?php
namespace Acme\MyModule\Reports\Lenses;
use Aero\Admin\Reports\ReportLens;
class TotalOrdersLens extends ReportLens
{
protected function value()
{
return $this->query->count();
}
}
You can optionally override the rendermethod to change the view that your lens uses. The default rendermethod:
public function render()
{
return view('admin::reports.lens', ['title' => $this->title(), 'content' => $this->value()]);
}
You can also additionally override the titlemethod to change the title shown on the frontend. By default this method returns a title that is generated from the lens class name.
Registering a Lens with your Report
To register your newly created lens with your report you need to ensure the report uses the Aero\Admin\Reports\Traits\HasLenstrait and that your lens class is in the reports protected static $lenses array.
<?php
namespace Acme\MyModule\Reports;
use Acme\MyModule\Reports\Lenses\TotalOrdersLens;
use Aero\Admin\Reports\Report;
use Aero\Admin\Reports\Traits\HasLens;
use Aero\Cart\Models\Order;
use Illuminate\Database\Eloquent\Builder;
class OrdersReport extends Report
{
use HasLens;
protected function newQuery(): Builder
{
return Order::with([
'status', 'items', 'currency', 'discounts', 'payments.method',
]);
}
protected static $lenses = [
TotalOrdersLens::class,
];
}
How do I add a table to my custom report?
You need to create a class for your table that extends Aero\Admin\Reports\ReportTableand implements the required public columns function. This columns function will be used to return an array containing your tables columns.
<?php
namespace Acme\MyModule\Reports\Tables;
use Aero\Admin\Reports\ReportTable;
class OrdersReportTable extends ReportTable
{
public function columns(): array
{
return [
];
}
}
Registering a Table with your Report
To register your newly created table with your report you need to ensure the report uses the Aero\Admin\Reports\Traits\HasTablestrait and that your table class is in the reports protected static $tables array.
<?php
namespace Acme\MyModule\Reports;
use Acme\MyModule\Reports\Tables\OrdersReportTable;
use Aero\Admin\Reports\Report;
use Aero\Admin\Reports\Traits\HasTables;
use Aero\Cart\Models\Order;
use Illuminate\Database\Eloquent\Builder;
class OrdersReport extends Report
{
use HasTables;
protected function newQuery(): Builder
{
return Order::with([
'status', 'items', 'currency', 'discounts', 'payments.method',
]);
}
protected static $tables = [
OrdersReportTable::class,
];
}
Adding Columns to your Table
To add columns to your table you need to use the Aero\Admin\Reports\ReportTableColumnfacade in your tables columns array method.
ReportTableColumn::create Parameters
| Type | Description |
|---|---|
| String | The first parameter is the header of the column. |
| Closure | The second parameter is a closure that handles the display content for the column. The closure accepts a $row variable and returns a string or view value. |
| String | Null |
<?php
namespace Acme\MyModule\Reports\Tables;
use Aero\Admin\Reports\ReportTable;
use Aero\Admin\Reports\ReportTableColumn;
class OrdersReportTable extends ReportTable
{
public function columns(): array
{
return [
ReportTableColumn::create('Date', function ($row) {
if ($row->ordered_at) {
return $row->ordered_at->format('D jS M, H:i');
}
return view('admin::reports.partials.muted-created-at', compact('row'));
}),
ReportTableColumn::create('Order', function ($row) {
return $row->reference;
}),
];
}
}
Making your Column Searchable
You can use the setSearchActionmethod to define how your column is searched.
| Type | Description |
|---|---|
| Closure | The first parameter is a closure that is called when your column is being used for a search. The closure is passed the query and search term. |
| String | Null |
ReportTableColumn::create('Order', function ($row) {
return $row->reference;
})
->setSearchAction(function ($query, $term) {
$term = ltrim($term, '#');
$query->whereLower('reference', 'like', "%{$term}%");
}, 'Order Reference')
Making your Column Not Exportable
To make your column not exportable you can use the notExportablemethod.
ReportTableColumn::create('Order', function ($row) {
return $row->reference;
})
->notExportable()
Setting your Columns Export Content
You can use the setExportContentmethod to define how your column should return data when exporting. This is useful for times where instead of returning a view on the Aero Admin you want your column to return raw data.
The setExportContentmethod accepts a closure that is passed the row.
ReportTableColumn::create('Date', function ($row) {
if ($row->ordered_at) {
return $row->ordered_at->format('D jS M, H:i');
}
return view('admin::reports.partials.muted-created-at', compact('row'));
})
->setExportContent(function ($row) {
if ($row->ordered_at) {
return $row->ordered_at->format('D jS M, H:i');
}
return $row->created_at->format('D jS M, H:i');
})
Making your Column Invisible
You can use the invisible method to make your column invisible. Do note that your column will still be visible in an export.
ReportTableColumn::create('Order', function ($row) {
return $row->reference;
})
->invisible()
Setting your Columns Position
Columns are ordered by an Integer position value (lowest numbers go first). You can set a column's position using the position method. On the default Aero reports we increment each rows column.
ReportTableColumn::create('Order', function ($row) {
return $row->reference;
})
->position(1)
How do I add exporters to my custom report?
To add an exporter to your report you need to ensure the report uses the Aero\Admin\Reports\Traits\CanExporttrait and that the exporter class is in the reports protected static $exports array.
By default Aero provides a CSV, XML, and JSON exporter.
<?php
namespace Acme\MyModule\Reports;
use Aero\Admin\Reports\Exporters\CSVExporter;
use Aero\Admin\Reports\Exporters\JSONExporter;
use Aero\Admin\Reports\Exporters\XMLExporter;
use Aero\Admin\Reports\Report;
use Aero\Admin\Reports\Traits\CanExport;
use Aero\Cart\Models\Order;
use Illuminate\Database\Eloquent\Builder;
class OrdersReport extends Report
{
use CanExport;
protected function newQuery(): Builder
{
return Order::with([
'status', 'items', 'currency', 'discounts', 'payments.method',
]);
}
protected static $exports = [
CSVExporter::class,
XMLExporter::class,
JSONExporter::class,
];
}
Creating a Report Exporter
You need to create a class for your exporter that extends Aero\Admin\Reports\ReportExporterand implements the required public handle function.
Handle Parameters
| Type | Description |
|---|---|
| Collection | The first parameter is a collection of the exportable tables columns. |
| Builder | The second parameter is the query that should be used to get the data for the export. |
| String | The third parameter is the key of the table being exported. |
<?php
namespace Acme\MyModule\Reports\Exporters;
use Aero\Admin\Reports\ReportExporter;
use Carbon\Carbon;
class CustomExporter extends ReportExporter
{
public function handle($columns, $rows, $key)
{
$file = $key.'-'.Carbon::now().'.csv';
return response()->streamDownload(function () use ($columns, $rows) {
$file = fopen('php://output', 'wb');
fputcsv($file, $columns->map->header()->toArray());
$rows->chunk(100, function ($chunk) use ($file, $columns) {
foreach ($chunk as $row) {
$rowData = [];
foreach ($columns as $column) {
$rowData[] = $column->exportContent($row);
}
fputcsv($file, $rowData);
}
});
fclose($file);
}, $file,
ReportExporter::getHttpHeaders('text/csv', $file)
);
}
}
Now you need to add your newly created exporter to your report as shown at the start of this article.
How do I extend existing reports?
Reports can be extended using a pipeline from your modules service providers setup method or your projects app service providers boot method. You can read more about pipelines here (link).
Adding a Column
To add columns to a table you can use the addColumn, addColumnBefore, or addColumnAftermethods from within the pipeline. These methods return a usual report table column which allows you to chain on the other available column methods.
addColumn Method
This method accepts a column header and a closure that defines how the column will be rendered. A column added with this method will be added as the first column.
addColumnBefore Method
This method accepts the same parameters as the addColumnmethod but additionally accepts a third parameter, the key of the column this new column should be added before.
addColumnAfter Method
This method accepts the same parameters as the addColumnmethod but additionally accepts a third parameter, the key of the column this new column should be added after.
Finding the Current Column Keys
If you do not know the keys of the current columns you can use this code snippet to dump the current keys out when visiting the report.
<?php
namespace Acme\MyModule;
use Aero\Admin\Reports\Implementations\SalesReport;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
SalesReport::extend(function (SalesReport $report) {
dd($report->table()->getColumns()->map->key());
});
}
}
<?php
namespace Acme\MyModule;
use Aero\Admin\Reports\Implementations\SalesReport;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
SalesReport::extend(function (SalesReport $report) {
$report->table()->addColumn('My Column', function ($row) {
return 'example';
})
->setExportContent(function ($row) {
return 'export content';
});
$report->table()->addColumnBefore('Before SKU', function ($row) {
return $row->sku;
}, 'sku');
$report->table()->addColumnAfter('After Sku', function ($row) {
return $row->sku;
}, 'sku');
});
}
}
Adding a Lens
To add a lens to a report you can use the addLensmethod from within the pipeline. This method expects you to pass a report lens class. You can create a report lens class as shown in the article "How do I add Lenses to my custom Report?
<?php
namespace Acme\MyModule;
use Acme\MyModule\Reports\Lenses\TotalRevenueLens;
use Aero\Admin\Reports\Implementations\SalesReport;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
SalesReport::extend(function (SalesReport $report) {
$report->addLens(TotalRevenueLens::class);
});
}
}
Adding a Filter
To add a filter to a report you can use the addFilter method from within the pipeline. This method expects you to pass an admin filter class. You can create an admin filter class as shown in the article "How do I add Admin Filters to my custom Report?
<?php
namespace Acme\MyModule;
use Aero\Admin\Filters\Order\OrderStatusAdminFilter;
use Aero\Admin\Reports\Implementations\OrderBreakdownReport;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
OrderBreakdownReport::extend(function (OrderBreakdownReport $report) {
$report->addFilter(OrderStatusAdminFilter::class);
});
}
}
How do I add admin filters to my custom report?
You need to create an Admin Filter class as shown in the article "How do I create a custom report?
To register your newly created admin filter with your report you need to ensure the report uses the Aero\Admin\Reports\Traits\HasFilterstrait and that your admin filter class is in the reports protected static $filters array.
<?php
namespace Acme\MyModule\Reports;
use Aero\Admin\Filters\Order\OrderOrderedAtDateAdminFilter;
use Aero\Admin\Reports\Report;
use Aero\Admin\Reports\Traits\HasFilters;
use Aero\Cart\Models\Order;
use Illuminate\Database\Eloquent\Builder;
class OrdersReport extends Report
{
use HasFilters;
protected function newQuery(): Builder
{
return Order::with([
'status', 'items', 'currency', 'discounts', 'payments.method',
]);
}
protected static $filters = [
OrderOrderedAtDateAdminFilter::class,
];
}
Admin Slots
Admin Slots
What are the available backend admin slots?
For more information about using backend admin slots please see our article "How do I inject a view into a backend admin slot?
The following slots can be injected into.
app-scripts-login.header-login.form-login.footer-login.footer-catalog.product.new.cards-catalog.product.edit.cards-catalog.product.new.header.buttons-catalog.product.edit.header.buttons-catalog.category.new.cards-catalog.category.edit.cards-orders.order.view.header.buttons-orders.order.view.cards.top-orders.order.view.cards.middle-orders.order.view.cards.bottom-orders.order.view.cards.extra.top-orders.order.view.cards.extra.info-orders.order.view.cards.extra.middle-orders.order.view.cards.extra.bottom-customer.new.header.buttons-customer.new.cards-customer.edit.header.buttons-customer.edit.cards-customer.edit.extra.bottom-customer.edit.extra.sidebar-catalog.attribute-groups.index.header.buttons-catalog.categories.index.header.buttons-catalog.collections.index.header.buttons-catalog.manufacturers.index.header.buttons-catalog.products.index.header.buttons-catalog.tag-groups.index.header.buttons-content.blocks.index.header.buttons-content.combinations.index.header.buttons-content.missing-pages.index.header.buttons-content.pages.index.header.buttons-content.redirects.index.header.buttons-customers.groups.index.header.buttons-customers.index.header.buttons-discounts.edit.cards-discounts.edit.extra.sidebar-discounts.edit.header.buttons-discounts.index.header.buttons-discounts.new.cards-discounts.new.extra.sidebar-discounts.new.header.buttons-discounts.vouchers.index.header.buttons-orders.index.header.buttons-price-lists.index.header.buttons-settings.countries.index.header.buttons-settings.currencies.index.header.buttons-settings.customer-tax-groups.index.header.buttons-settings.fulfillment-methods.index.header.buttons-settings.mail.index.header.buttons-settings.order-statuses.index.header.buttons-settings.payment-methods.index.header.buttons-settings.price-groups.index.header.buttons-settings.product-tax-groups.index.header.buttons-settings.shipping-methods.index.header.buttons-settings.subscription-plans.index.header.buttons-settings.tax-rates.index.header.buttons-settings.tax-rules.index.header.buttons-settings.users.index.header.buttons-configuration.fulfillment-methods.edit.cards-configuration.fulfillment-methods.new.cards-configuration.shipping-methods.edit.cards-configuration.shipping-methods.new.cards-orders.fulfillment.edit.extra.info-orders.fulfillment.edit.cards-orders.fulfillment.edit.extra.sidebar-orders.fulfillment.new.extra.info-orders.fulfillment.new.cards-orders.fulfillment.new.sidebar-subscriptions.subscription.view.extra.form-subscriptions.subscription.view.cards-subscriptions.subscription.view.extra.info-subscriptions.subscription.view.extra.sidebar
How do I inject a view into a backend admin slot?
Any view you wish to inject into a slot should be defined in a Service Provider.
If the code is unique to a particular store it could be placed in the boot method of the AppServiceProvider. If it is to be included as part of a module, then it can be added in the setup method of the module's ServiceProvider.
When registering the view to be injected into the slot, you must provide the slot name along with the view, for example:
<?php
namespace Acme\MyModule;
use Aero\Admin\AdminSlot;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup(): void
{
AdminSlot::inject('catalog.product.edit.cards', 'my-module::admin-slot');
}
}
For advanced cases where additional data needs to be passed into your view, a closure can be used. The closure receives a $dataparameter that contains the current variables on the page.
AdminSlot::inject('catalog.product.edit.cards', function ($data) {
// add custom variables to the $data array
return view('my-module::admin-slot', $data);
});
Admin Transformers
Admin Transformers
What are the available transformers?
The following classes are in the Aero\Admin\Transformersnamespace.
| Class | Description |
|---|---|
| AttributeTransformer | This transformer is used in the ProductTransformer to transform the product's attribute groups. |
| BaseVariantTransformer | This transformer is used to generate the data for a new variant. |
| ProductTransformer | This transformer is used to transform a product for the product new/edit page. |
| VariantTransformer | This transformer is used in the ProductTransformer to transform the product's variants. |
| DiscountTransformer | This transformer is used to transform a discount for the discount new/edit page. |
How do I add custom data to a transformer?
To add custom data you need to use the static **add()**method on the relevant transformer. This method expects a closure that will return an array of the field(s) to merge into the transformers array. The closure will be passed an array of data that you can use.
This code example adds a customFieldthat will have the value of the products ID multiplied by two. This field will then be usable on the product new/edit page directly in Vue like product.customField. It’s important to note that this is used on the new product page where the product will not have an ID and some other data which is why the ?? 0is used.
<?php
namespace Acme\MyModule;
use Aero\Admin\Transformers\ProductTransformer;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
ProductTransformer::add(function ($data) {
/* @var \Aero\Catalog\Models\Product */
$product = $data['product'];
return [
'customField' => ($product->id ?? 0) * 2,
];
});
}
}
Casts
When the admin page fails validation and redirects back with the old data, the old data is casted to ensure it’s correct using the transformer. This is particularly important for booleans where a checkbox may set the value as 1 and it therefore should be casted to true to avoid problems.
Available Cast Types
You can currently use the following casts: boolean, array, string, and integer.
Adding a Cast
To add a cast you need to use the static **addCast()**method on the relevant transformer. This method expects two parameters, the key of the field and the value that the field should be casted to.
This code example ensures the customFieldis casted as an integer.
<?php
namespace Acme\MyModule;
use Aero\Admin\Transformers\ProductTransformer;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
ProductTransformer::addCast('customField', 'integer');
ProductTransformer::add(function ($data) {
/* @var \Aero\Catalog\Models\Product */
$product = $data['product'];
return [
'customField' => ($product->id ?? 0) * 2,
];
});
}
}
Admin Vue
Admin Vue
How do I add custom clips to the redactor editor
Clips is a plugin for the Redactor editor that allows you to create a list of frequently used code to be used in the editor.
In the following example we will add a label clip that will allow users to use a label in the Redactor editor that looks like this:
You need to use the stylesand scriptsslots to inject views across the whole admin that include the CSS for the clip (in this case the CSS for the label) and the JS for creating the clip. This can be done in a module service providers setupmethod or the app service providers bootmethod. If you use a module service provider, do not forget to register your views under your modules namespace using the $this->loadViewsFrommethod.
<?php
namespace Acme\MyModule;
use Aero\Admin\AdminSlot;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-module');
AdminSlot::inject('styles', 'my-module::clips-styles');
AdminSlot::inject('scripts', 'my-module::clips-scripts');
}
}
The my-module::clips-stylesview should contain a style tag with the CSS for the label. Any CSS for a clip should be scoped to the .redactor-boxclass in this view. You will also need to put this CSS in your themes CSS file so that it works on the storefront (this CSS doesn’t need to be scoped to the .redactor-boxclass).
<style>
.redactor-box .label-red {
display: inline-block;
background-color: #ec2d80;
color: #fff;
line-height: 1;
padding: 2px 8px;
border-radius: 4px;
font-weight: normal;
}
</style>
The my-module::clips-scriptsview should contain a script tag with the JS for the label. The first parameter is the text shown in the clips list and the second parameter is the code inserted when the clip is used.
<script>
window.RedactorClips.add('Red label', '<b class="label-red">Label</b>');
</script>
Completing these steps will add a clips button to the top of the Redactor editor that when clicked will list your registered clips that can then be clicked to insert their code.
How do I use Vue in the admin?
When in an admin's Blade view file you can use any Vue directive (such as v-for, v-if, @clicketc).
Putting Vue into Dev Mode
Putting Vue into dev mode allows you to use the Vue dev tools. You can put the Admin Vue into dev mode by putting this code snippet in your Blade view:
@push('scripts')
<script>
window.AeroAdmin.vue.use({
install(Vue) {
Vue.config.devtools = true;
},
});
</script>
@endpush
Extending Vue without a Custom Component
You can extend Vue without using a custom Vue component by using some of the window.AeroAdminmethods. Methods are provided for you to add data, methods, computed methods, watchers, and listeners.
Adding Data
You can make data available in Vue through the window.AeroAdmin.addDatamethod. This method expects two parameters, the variable name and the variable value. You can pass JSON as the values for complex data types such as arrays.
@push('scripts')
<script>
window.AeroAdmin.addData('myVariable', '{{ old('myVariable', 'value') }}');
window.AeroAdmin.addData('myOtherVariable', 'Example');
window.AeroAdmin.addData('myArrayVariable', {!! json_encode(['one', 'two', 'three', 'four']) !!});
</script>
@endpush
Adding a Method
You can make Vue methods using the window.AeroAdmin.addMethodmethod. This method expects two parameters, the method name and a closure defining the method. Inside of the closure you can use thisto reference the Vue instance (this allows you to call other methods or interact with the data variables (anything you can do from within Vue).
@push('scripts')
<script>
window.AeroAdmin.addMethod('test', function (value) {
console.log(value);
});
window.AeroAdmin.addMethod('testing', function (value) {
this.test(value);
})
</script>
@endpush
Adding a Watcher
You can make a Vue watcher using the window.AeroAdmin.addWatchmethod. This method expects two parameters, the watcher name and a closure defining the watcher. Inside of the closure you can use thisto reference the Vue instance (this allows you to call other methods or interact with the data variables (anything you can do from within Vue).
@push('scripts')
<script>
window.AeroAdmin.addData('testing', 'value');
window.AeroAdmin.addMethod('test', function (value) {
console.log(value);
});
window.AeroAdmin.addWatch('testing', function (value) {
this.test(value);
})
</script>
@endpush
Adding a Computed Method
You can make Vue computed methods using the window.AeroAdmin.addComputedmethod. This method expects two parameters, the computed method name and a closure defining the computed method. Inside of the closure you can use thisto reference the Vue instance (this allows you to call other methods or interact with the data variables (anything you can do from within Vue).
@push('scripts')
<script>
window.AeroAdmin.addComputed('computedMethod', function () {
return ['one', 'two', 'three', 'four'];
});
</script>
@endpush
Adding a Listener
You can add a listener to Vue using the window.AeroAdmin.addListenermethod. This method expects two parameters, the event name to listener for and a closure defining what to do when the event is emitted. Inside of the closure you can use thisto reference the Vue instance (this allows you to call other methods or interact with the data variables (anything you can do from within Vue).
@push('scripts')
<script>
window.AeroAdmin.addListener('loaded', function () {
console.log('Vue has been loaded');
});
</script>
@endpush
How do I add a custom Vue component to the admin?
This repo (https://github.com/aerocargo/admin-example-vue
You need to add the assetLinksfunction to your modules service provider so that your modules assets are publicly linkable. An example module service provider can be found here in the repo (https://github.com/aerocargo/admin-example-vue/blob/master/src/ServiceProvider.php
You also need to make your component .vuefile(s) and your main .jsfile that will register all of the components. An example javascript structure can be found here in the repo (https://github.com/aerocargo/admin-example-vue/tree/master/resources/js
Once you’ve got your files setup and have built the javascript files (using npm - npm run devor npm run production), you need to ensure that your module's components javascript file is loaded and the components are registered in the Blade view where you want to use the component(s). To do this you need to include the javascript file from your module and then register the components through the window.AeroAdmin.vue.usemethod.
@push('scripts')
<script src="{{ asset(mix('components.js', 'modules/aerocargo/admin-example-vue')) }}"></script>
<script>
window.AeroAdmin.vue.use(window.exampleAdminComponents);
</script>
@endpush
Once you have completed this you’ll be able to use your custom Vue component(s) anywhere in the Blade file where you have registered them.
Blocks
Blocks
How do I import content blocks?
In order to export content blocks, we will require an Aero package. To install this package, type the following command in the project root directory:
composer require aerocargo/blocks-import-export
After installing the package, we can navigate to the adminthen Content Managementand finally, Blocks. In the top right corner of this page, we now have a button for importing blocks.
The .zip file required by the importer can only be generated by the content blocks exporter. If we do have an exported .zip file we can simply upload it within the modal that appears on button click. After uploading the .zip file and letting the blocks import, they should appear in the list.
How do I export content blocks?
In order to export content blocks, we require an Aero package. To install this package, type the following command in the project root directory:
composer require aerocargo/blocks-import-export
After the package has been installed successfully, we can navigate to the admin, then to Content Management, and lastly to the Blocks. We can now use the bulk action to select which blocks we wish to export, otherwise if we wish to export all of the content blocks we select the checkbox next to the name header field of the table.
Finally, we apply the “Export blocks” bulk action at which point the export will generate the necessary blocks and download them to the user’s browser.
Introduction to blocks
Whilst the layout of the storefront is dictated by the theme, the displayed content is managed through blocks. This includes navigation (such as header and footer menus), promotional imagery, static text and HTML snippets. Each block can contain multiple items of different types, allowing for features like image carousels and mega menus.
Configuration
Configuration
How do I register new permissions?
// add a single permission
\Aero\Admin\Permissions::add('custom');
// add multiple permissions
\Aero\Admin\Permissions::add(['custom', 'custom.view', 'custom.manage']);
How do I check for permissions?
When checking for permission, you should always be as explicit as you can. For example, checking a user has the custom.viewor custom.managepermissions.
Guarding content in a Blade file:
@can('custom.view')
...
@endcan
@canany(['orders', 'custom'])
...
@endcan
Checking the admin user has permission to access an endpoint:
Route::get('/my-module', [MyModuleController::class, 'index'])
->name('admin.modules.my-module')
->middleware('can:custom.view');
Route::get('/my-module/manage', [MyModuleController::class, 'manage'])
->name('admin.modules.my-module.manage')
->middleware('can:custom.manage');
Guarding the execution of code in a Controller:
if ($this->can('custom')) {
// ...
}
if ($this->canAny(['orders', 'custom'])) {
// ...
}
How do I change how order references are generated?
You can set an order reference generator to define how order references are generated. To do this you need to use the static setReferenceGeneratormethod on the Aero\Cart\Models\Ordermodel.
The method accepts a closure that accepts the order as a parameter and expects you to return a string (the reference for the order passed into the closure).
If you do not set an order reference generator the default generator will be used that does the following:
<?php
namespace Acme\MyModule;
use Aero\Cart\Models\Order;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
Order::setReferenceGenerator(function (Order $order) {
$prefix = setting('order_reference_prefix');
$random = substr(str_shuffle(str_repeat('0123456789', 10)), 1, 3);
return "{$prefix}{$order->getKey()}-{$random}";
});
}
}
What is the robots file and how does it differ from normal?
The robots.txt file provides instructions for web crawlers and bots that index your store. Aero automatically registers core parts of the store that should avoid being indexed and visited by bots.
In order to extend this file and add modules or store specific restrictions, the robots.txt file is processed as a pipeline, where the content (the text) is made available and can be manipulated.
For ease of access, the $contentpassed through the pipeline is an Illuminate\Support\Collection, grouped by the user agent.
\Aero\Store\Pipelines\RobotsTxt::extend(static function ($content) {
if ($agent = $content->get('User-agent: *')) {
$agent->push('Disallow: /my_module/');
}
});
How do I store product images on S3
You need to configure and set up your Laravel project for the S3 storage driver. You can read more about this in the laravel documentationAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, and AWS_BUCKET) and installing a composer package (league/flysystem-aws-s3-v3 ~1.0) that lets Laravel support S3.
Once your project is configured you should set STORE_FILE_DRIVERand STORE_LOCAL_FILE_DRIVERto your S3 driver. If you have followed the Laravel documentation steps above and are using the default S3 disk already created you will simply add this to your env file:
STORE_FILE_DRIVER=s3
STORE_LOCAL_FILE_DRIVER=s3
STORE_FILE_DRIVER
This by default is set as public and is the disk used by Aero when uploading files that should be publicly accessible.
STORE_LOCAL_FILE_DRIVER
This by default is set as local and is the disk used by Aero when uploading files that should not be publicly accessible directly.
Customization
Customization
How do I remove the manufacturers name from the order items name?
By default if a product name doesn’t contain the manufacturer name already, the manufacturer name is appended to the front of the product name when converting a cart item to an order item.
If you don’t want the manufacturer name appended to the product name you can set the static Aero\Cart\Models\OrderItem::$nameContainsManufacturerboolean to false.
<?php
namespace Acme\MyModule;
use Aero\Cart\Models\OrderItem;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
OrderItem::$nameContainsManufacturer = false;
}
}
How do I remove the manufacturer from the product slug/url?
The manufacturer name is automatically prepended to the product slug (its URL). If you wish for this to not happen you can set the static Aero\Catalog\Models\Product::$slugContainsManufacturerboolean to false.
<?php
namespace Acme\MyModule;
use Aero\Catalog\Models\Product;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
Product::$slugContainsManufacturer = false;
}
}
Developer Faqs
Developer Faqs
Does Aero support Laravel Homestead installation?
Yes, Homestead is supported on Windows, Mac or Linux provided your Vagrant box configuration meets the system requirements
Can I use Aero project credentials across multiple projects?
No, Credentials must not be shared between projects.
Is Laravel required to run Aero?
Yes, the Aero platform is built on top of the Laravel framework. Knowledge of Laravel is not essential for basic Aero development but will help with more complex actions and configurations.
What versions of Elasticsearch does Aero support?
Aero supports both Elasticsearch 6.x and 7.x.
Support for Elasticsearch 6.x is provided by the 0.x version of aerocommerce/elastic-search.
Support for Elasticsearch 7.x is provided by the 1.x version of aerocommerce/elastic-search.
Which versions of PHP will Aero run on?
Aero requires PHP Version 7.2 and above.
How do I configure the PHP memory limit?
Due to the number of fields that are sometimes present in the admin, it may be necessary to adjust the PHP max_input_varsvalue. The default of 1,000 fields can be too small when managing a product that contains hundereds of variants. This limit is quickly reached when considering how many fields each variant has.
Data will be truncated by the server
A form that contains more fields than PHP is configured to accept will result in lost or corrupted data.
The max_input_varsvalue is defined in the php.ini file. On a Linux server, this is typically located:
# PHP 7.3
/etc/php/7.3/php-fpm/php.ini
# PHP 7.4
/etc/php/7.4/php-fpm/php.ini
# PHP 8.0
/etc/php/8.0/php-fpm/php.ini
On macOS when using Homebrew:
# PHP 7.3
/usr/local/etc/php/7.3/php.ini
# PHP 7.4
/usr/local/etc/php/7.4/php.ini
# PHP 8.0
/usr/local/etc/php/8.0/php.ini
We recommend a value of 10,000. You should either search for the existing entry, or add to the bottom of the file:
max_input_vars=10000
Once set, you'll need to reload PHP-fpm.
# Linux (PHP 7.3)
sudo service php7.3-fpm reload
# Linux (PHP 7.4)
sudo service php7.4-fpm reload
# Linux (PHP 8.0)
sudo service php8.0-fpm reload
# macOS (Laravel Valet)
valet restart
Events
Events
How do I create a new event listener?
To create a new listener class for one of our events, we can use Artisan to scaffold the listener from the project root directory:
php artisan make:listener UpdateDetails
After adding all the necessary functionality for our listener, we have to make Aero aware of it by adding it to a listen property, wrapped within an event like so:
protected $listen = [
Registered::class => [
UpdateDetails::class,
]
];
There is also an alternative, for events that should not be queued:
Event::listen(OrderPlaced::class, function ($event) {
$order = $event->order;
//...
});
Extending ManagedListener
In order to use a ManagedListener, we have to have an event extending ManagedEvent. This can be accomplished like so:
use Aero\Events\ManagedHandler;
protected $listen = [
FormSubmitted::class => [
ManagedHandler::class,
],
];
What are the events available?
Account
- AddressCreated - AddressDeleted - AddessUpdated - CustomerManualylCreated - CustomerRegistered - CustomerUpdated - CustomerChanged - PasswordResetRequest
Cart
- CartEmptied - CartItemAdded - CartItemRemoved - CartItemUpdated - OrderCanceled - OrderClosed - OrderComplete - OrderConfirmation - OrderDispatched - OrderItemBought - OrderOnHold - OrderPartiallyDispatched - OrderPartiallyReturned - OrderPlaced - OrderProcessing - OrderReturned - OrderStatusUpdated - OrderSuccessful - OrderUpdated
Catalog
- AttributesDeleted - AttributesUpdated - CategoryDeleted - CategoryUpdated - ListingCreated - ListingsDeleted - ListingsUpdated - ListingUpdated - ManufacturerUpdated - ProductCreated - ProductDeleted - ProductUpdated - TagsUpdated
Content
- BlockCreated - BlockDeleted - BlockUpdated
Fulfillment
- FulfillmentDispatched
Payment
- PaymentCanceled - PaymentCaptured - PaymentFailed - PaymentRefunded
Redirector
- RedirectHit - RedirectNotFound
Store
- FormSubmitted
Subscription
- SubscriptionCanceled - SubscriptionCardExpired - SubscriptionCreated - SubscriptionFailed - SubscriptionOrderFailed - SubscriptionPaused - SubscriptionPaymentFailed - SubscriptionPaymentSuccess - SubscriptionShippingFailed - SubscriptionSuccessful - SubscriptionSuccessful - SubscriptionUnpaused - SubscriptionUpcoming
How do I add a custom event to a Service Provider?
Laravel events provide an observer implementation, allowing you to listen and observe for specific tasks happening in your application. Adding a custom event to a ServiceProvidermeans that the application will listen to it as soon as it occurs. In terms of choosing the right ServiceProviderfor the job, it breaks down into two options: a Module ServiceProvider, or any of the Providers listed under app/Providersof our shop’s root directory.
Event directory:
app/Events
Listener directory:
app/Listeners
The ServiceProviderclass has a $listenproperty, to which we simply add on the Event that we wish to add, alongside its listener.
What are event listeners?
Event listeners, as the name suggests, listen to events that have been assigned to them. This means that listeners have to first be manually mapped for which events they should listen to.
Mappings for Event Listenersare declared in the appropriate Service Provider, depending on what part of the platform it affects - for example, it could be a Module Service Provideror the EventServiceProviderin the app directory of Aero.
How do I add a listener to an event?
<?php
namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();
//
}
}
The listener is always nested within the event which it affects so that it can pass on the event to the listener’s handle method. Remember that the listener always goes within the array instantiating from the event class.
How do I create a new event?
To create a new event, we first have to scaffold the class using Artisan in the project root directory:
php artisan make:event Registered
The above command scaffolds a class into the app/Eventsand we can modify it accordingly to our needs.
After applying all the necessary functionality to the event, we have to also create a listener to listen to an event happening. In order for Aero to recognize the event, we have to specify it in a Service Providerof choice, for example, a module Service Provider:
protected $listen = [
Registered::class => [
// insert your listener here
]
];
Extending ManagedEvent
Aero’s ManagedEventis an optional classmade specifically for Aero events and is used mainly for mailing events. These are a special type of event as all the events that extend ManagedEventare actions.
It’s important to note that the listener that listens to an event extending ManagedEvent, must also extend the ManagedListenerclass.
Variables
With ManagedEvent, we can create variables that can then be accessible in a mailing template. In essence, variables create helper text and allow the user to use data from the event itself. To define some variables, we have to create a property ‘variables’ in our event:
Adding variables
public static $variables = [
'product.slug',
'product.name',
'product.model',
'product.summary',
'product.description',
'product.thumbnail',
'product.heading',
'product.url',
'product.categories.*.name',
'product.categories.*.has_featured_image',
'product.categories.*.featured_image_file',
'product.manufacturer.name',
'product.manufacturer.has_logo',
'product.manufacturer.logo_file',
'product.attributes.*.name',
'product.attributes.*.image_file',
'product.images.*.image_file',
'product.all_images.*.image_file',
'product.variants.*.sku',
'product.variants.*.has_stock',
'product.highest_price',
'product.lowest_price',
'product.has_reductions',
];
The above variables will then be accessible in our mail template.
An alternative method of adding variables:
EventClass::addVariable(‘product.lowest_price’)
Or multiple, as an array:
EventClass::addVariables([‘product.manufacturer.name’, ‘product.lowest_price’])
How do I make custom mail notification events?
To create an event that shows up in the Mail Notifications part of the admin your event needs to extend Aero\Events\ManagedEventand have Aero\Events\ManagedHandleras a registered listener.
To create an event you need to create a class that extends Aero\Events\ManagedEvent.
<?php
namespace Acme\MyModule\Events;
use Aero\Events\ManagedEvent;
class MyEvent extends ManagedEvent
{
//
}
To register Aero\Events\ManagedHandleras a listener of your event you need to add your event and the managed handler to a $listenarray inside of a service provider.
<?php
namespace Acme\MyModule;
use Acme\MyModule\Events\MyEvent;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Events\ManagedHandler;
class ServiceProvider extends ModuleServiceProvider
{
protected $listen = [
MyEvent::class => [
ManagedHandler::class,
],
];
public function setup()
{
//
}
}
Extending Core Functionality
Extending Core Functionality
What are models and how do I use them?
All models provided as part of the Aero core platform are Eloquent models
For example, if a module provided reviewsfor products, the reviewsmethod can be added to the Product model using a macro:
\Aero\Catalog\Models\Product::macro('reviews', function () {
return $this->hasMany(\Acme\MyModule\Models\Review::class);
});
The relationship query builder can be accessed by referencing the method:
$approvedReviewCount = $product->reviews()->where('approved', true)->count();
Just like a typical Eloquent relationship on a model, the resulting Collectionof reviews can be accessed through the magic property:
$reviews = $product->reviews;
{% for review in product.reviews %}
...
{% endfor %}
How do I add a custom field to the new and edit product page?
In this mini tutorial we will add a notes field to the product new and edit page for the product and each variant.
This mini tutorial assumes that you have a module setup or you’re happy working from the app service provider (using the boot method). You can see how to set up a module here (link).
Adding the Database Migration
To get started we will add a migration file that will update the products and variants tables to have a notes column. To do this we’ll need to create the migration file and load them from within the modules service provider.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddNotesFieldsToProductsAndVariants extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('products', function (Blueprint $table) {
$table->string('notes')->nullable()->after('description');
});
Schema::table('variants', function (Blueprint $table) {
$table->string('notes')->nullable()->after('name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('notes');
});
Schema::table('variants', function (Blueprint $table) {
$table->dropColumn('notes');
});
}
}
Adding the Slot View
Now we’ll create two views, one that will be injected for the product and one that will be injected for the variants. These views will have a text area in them for the notes input.
product-notes-field.blade.php
<div class="card mt-4">
<label for="notes" class="block mb-2">Notes</label>
<textarea name="notes" id="notes" v-model="product.notes" class="w-full"></textarea>
</div>
variant-notes-field.blade.php
<div class="p-4" v-if="!isSimpleProduct">
<label :for="'variant-notes-' + key" class="block mb-2">Notes</label>
<textarea :name="'variants[' + key + '][notes]'" :id="'variant-notes-' + key" v-model="variants[key].notes" class="w-full"></textarea>
</div>
After creating the views we will load them in the module service provider using the $this->loadViewsFrommethod and inject them into the relevant slots using the Aero\Admin\AdminSlot::injectmethod.
<?php
namespace Acme\MyModule;
use Aero\Admin\AdminSlot;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
if ($this->app->runningInConsole()) {
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-module');
AdminSlot::inject('catalog.product.new.cards', 'my-module::product-notes-field');
AdminSlot::inject('catalog.product.edit.cards', 'my-module::product-notes-field');
AdminSlot::inject('catalog.product.new.variant', 'my-module::variant-notes-field');
AdminSlot::inject('catalog.product.edit.variant', 'my-module::variant-notes-field');
}
}
Making the Notes Save
Adding Notes as a Fillable
We need to make the notes fillable as Laravel will only mass assign fillable attributesAero\Catalog\Models\Product and Aero\Catalog\Models\Variant).
<?php
namespace Acme\MyModule;
use Aero\Admin\AdminSlot;
use Aero\Catalog\Models\Product;
use Aero\Catalog\Models\Variant;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
if ($this->app->runningInConsole()) {
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-module');
AdminSlot::inject('catalog.product.new.cards', 'my-module::product-notes-field');
AdminSlot::inject('catalog.product.edit.cards', 'my-module::product-notes-field');
AdminSlot::inject('catalog.product.new.variant', 'my-module::variant-notes-field');
AdminSlot::inject('catalog.product.edit.variant', 'my-module::variant-notes-field');
Product::makeFillable('notes');
Variant::makeFillable('notes');
}
}
Adding Notes to the Validators
To let the notes input data get through validation it needs to be added to the Aero\Admin\Http\Requests\Catalog\CreateProductRequestand Aero\Admin\Http\Requests\Catalog\UpdateProductRequestvalidators. To do this we need to use the expectsmethod on the two validators and pass in the notes field and its rules.
<?php
namespace Acme\MyModule;
use Aero\Admin\AdminSlot;
use Aero\Admin\Http\Requests\Catalog\CreateProductRequest;
use Aero\Admin\Http\Requests\Catalog\UpdateProductRequest;
use Aero\Catalog\Models\Product;
use Aero\Catalog\Models\Variant;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
if ($this->app->runningInConsole()) {
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-module');
AdminSlot::inject('catalog.product.new.cards', 'my-module::product-notes-field');
AdminSlot::inject('catalog.product.edit.cards', 'my-module::product-notes-field');
AdminSlot::inject('catalog.product.new.variant', 'my-module::variant-notes-field');
AdminSlot::inject('catalog.product.edit.variant', 'my-module::variant-notes-field');
Product::makeFillable('notes');
Variant::makeFillable('notes');
CreateProductRequest::expects('notes', 'nullable|string');
UpdateProductRequest::expects('notes', 'nullable|string');
}
}
Adding Notes to the Transformers
Adding notes to the transformers will ensure that it’s available in our views through Vue. We need to use the add method on the Aero\Admin\Transformers\BaseVariantTransformer, Aero\Admin\Transformers\ProductTransformer, and Aero\Admin\Transformers\VariantTransformertransformers.
<?php
namespace Acme\MyModule;
use Aero\Admin\AdminSlot;
use Aero\Admin\Http\Requests\Catalog\CreateProductRequest;
use Aero\Admin\Http\Requests\Catalog\UpdateProductRequest;
use Aero\Admin\Transformers\BaseVariantTransformer;
use Aero\Admin\Transformers\ProductTransformer;
use Aero\Admin\Transformers\VariantTransformer;
use Aero\Catalog\Models\Product;
use Aero\Catalog\Models\Variant;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
if ($this->app->runningInConsole()) {
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-module');
AdminSlot::inject('catalog.product.new.cards', 'my-module::product-notes-field');
AdminSlot::inject('catalog.product.edit.cards', 'my-module::product-notes-field');
AdminSlot::inject('catalog.product.new.variant', 'my-module::variant-notes-field');
AdminSlot::inject('catalog.product.edit.variant', 'my-module::variant-notes-field');
Product::makeFillable('notes');
Variant::makeFillable('notes');
CreateProductRequest::expects('notes', 'nullable|string');
UpdateProductRequest::expects('notes', 'nullable|string');
ProductTransformer::add(function ($data) {
return [
'notes' => $data['product']->notes ?? '',
];
});
VariantTransformer::add(function ($data) {
return [
'notes' => $data['variant']->notes ?? '',
];
});
BaseVariantTransformer::add(function ($data) {
return [
'notes' => '',
];
});
}
}
What are the available commands?
php artisan make:module
This command scaffolds a module for you (you need to provide a valid name for the module such as aerocargo/my-module). This involves setting up composer and publishing some default module stubs.
php artisan aero:configure
This command walks you through configuring your store. This includes things such as setting the store's name, database connection, and Elasticsearch connection.
php artisan aero:install
This command runs you through the installation process for your store. This includes completing migrations, configuring Elasticsearch, linking storage, seeding data, and clearing the applications cache.
php artisan aero:link
This command calls the patch command, links theme/module assets, clears the Twig views cache, and clears the Laravel views cache.
php artisan module:install
This command will install a specific module after you have composer required the module. This command seeds any data for the module, optionally clears the application cache, and links the module assets. You should note that this command doesn’t have to be used and is automatically run when composer requiring a module.
php artisan aero:patch
This command patches your store's database. This command is run automatically when updating and ensures that your database is kept up to date with Aeros latest updates.
php artisan aero:search:install
This command sets up the necessary environment for your search driver. By default when using Elasticsearch this command will create the required indexes.
php artisan aero:search:rebuild
This command resets up and reindexes your search by running the search install and reindex commands.
php artisan aero:search:reindex
This command will reindex your database data into your searches index. By default when using Elasticsearch this command will clear the index and then index your database data.
php artisan aero:seed:emails
This command will seed some default email templates to your mail notifications.
php artisan aero:sitemap:cms
This command will generate the pages-sitemap.xml sitemap file in your public storage directory. This sitemap covers any pages you create through the admin.
php artisan aero:sitemap:combinations
This command will generate the combinations-sitemap.xml file in your public storage directory. This sitemap covers any combinations you create through the admin.
php artisan aero:sitemap:generate
This command will generate a complete sitemap for your store. This generates a sitemap.xml file in your public storage directory that references individual sitemap files for products, combinations, and pages.
php artisan aero:sitemap:products
This command will generate the products-sitemap.xml sitemap file in your public storage directory. This sitemap covers any products you create through the admin.
php artisan aero:subscriptions:alert-upcoming
This command will look for upcoming subscriptions and emit the **Aero\Subscription\Events\SubscriptionUpcoming **event for them.
php artisan aero:subscriptions:check-cards
This command will look for expired cards on subscriptions and then cancel the subscription and emit the Aero\Subscription\Events\SubscriptionCardExpiredevent for them.
php artisan aero:subscriptions:process
This command will process any subscriptions that need processing.
How to add a custom field to the address forms?
This short tutorial will walk you through adding an address line 3 field to all of the address forms (found in the admin, account-area, and checkout).
Migrations
The first step is to add your field to all of the address tables so that it can be stored. To do this we’ll create and register a migration that adds a line_3field to the addresses, order_addresses, and fulfillment_addressestables.
Migration Code
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddLine3ToAddressTables extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('addresses', function (Blueprint $table) {
$table->string('line_3')->nullable()->after('line_2');
});
Schema::table('order_addresses', function (Blueprint $table) {
$table->string('line_3')->nullable()->after('line_2');
});
Schema::table('fulfillment_addresses', function (Blueprint $table) {
$table->string('line_3')->nullable()->after('line_2');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('fulfillment_addresses', function (Blueprint $table) {
$table->dropColumn('line_3');
});
Schema::table('order_addresses', function (Blueprint $table) {
$table->dropColumn('line_3');
});
Schema::table('addresses', function (Blueprint $table) {
$table->dropColumn('line_3');
});
}
}
Service Provider Code
<?php
namespace Acme\MyModule;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
if ($this->app->runningInConsole()) {
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
}
}
Views
The next step is to create a view for the line 3 field. To achieve this we’ll create and register a Twig view for the field. The view will include the forms::components.inputview to get a simple input field.
View Code
{% include "forms::components.input" with {
type: 'text',
error: errors.first((inputName ?? group) ~ '.line_3'),
half: false,
label: 'Address Line 3 (Optional)',
class: (inputName ?? group) ~ '-' ~ 'line-3',
name: (inputName ?? group) ~ '[line_3]',
value: old((inputName ?? group) ~ '.line_3', address.line_3),
autocomplete: type ? type ~ ' line_3' : 'line_3',
required: true
} only %}
Service Provider Code
<?php
namespace Acme\MyModule;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
if ($this->app->runningInConsole()) {
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-module');
}
}
Adding the Field to the Forms
Now we need to add the field to the address forms. To do this we’ll make the field fillable and add it to the validation requests using the Aero\Common\Helpers\Address::addField()helper method. After that we’ll extend Aero\Forms\AddressFormto add the field to the frontend address forms (checkout and account-area) and Aero\Admin\Http\Forms\AdminAddressFormto add the field to the admin address forms.
<?php
namespace Acme\MyModule;
use Aero\Admin\Http\Forms\AdminAddressForm;
use Aero\Common\Helpers\Address;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Forms\AddressForm;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
if ($this->app->runningInConsole()) {
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-module');
Address::addField('line_3');
AddressForm::extend(function ($form) {
$form->addSectionAfter('line3', 'my-module::line-3-field', 'line2');
});
AdminAddressForm::extend(function ($form) {
$form->addSectionAfter('line3', 'my-module::line-3-field', 'line2');
});
}
}
Adding the Field to the Rendered Addresses
This step is optional and will add the line 3 field to the address when it’s rendered (such as when viewing an order or viewing your addresses in the account area). To do this we’ll extend Aero\Address\Pipelines\AddressFormatterand Aero\Address\Pipelines\AddressStringFormatter.
<?php
namespace Acme\MyModule;
use Aero\Address\Pipelines\AddressFormatter;
use Aero\Address\Pipelines\AddressStringFormatter;
use Aero\Admin\Http\Forms\AdminAddressForm;
use Aero\Common\Helpers\Address;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Forms\AddressForm;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
if ($this->app->runningInConsole()) {
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-module');
Address::addField('line_3');
AddressForm::extend(function ($form) {
$form->addSectionAfter('line3', 'my-module::line-3-field', 'line2');
});
AdminAddressForm::extend(function ($form) {
$form->addSectionAfter('line3', 'my-module::line-3-field', 'line2');
});
AddressFormatter::extend(function ($formatter) {
$formatter->putAfter('line3', $formatter->fields['line_3'], 'line2');
});
AddressStringFormatter::extend(function ($formatter) {
$formatter->putAfter('line3', $formatter->fields['line_3'], 'line2');
});
}
}
How do I extend the address forms?
Aero has address forms in the checkout, account-area, and admin that can be extended.
Storefront Address Forms
The storefront address forms are address forms found on your storefront (the checkout and the account-area). These forms are customer facing and all extend Aero\Forms\AddressForm.
Structure
In all of the address forms there’s a top_sections, sections, and bottom_sectionssection (some forms have an additional section). These sections build up the form. The reason that there are sections is so when the form is in lookup mode, the address fields can be hidden as they’re all in the sectionssection.
Top Sections
| Key | View |
|---|---|
| first_name | forms::address.fields.first-name |
| last_name | forms::address.fields.last-name |
| lookup | forms::address.lookup |
Sections
| Key | View |
|---|---|
| company | forms::address.fields.company |
| line1 | forms::address.fields.line1 |
| line2 | forms::address.fields.line2 |
| city | forms::address.fields.city |
| zone | forms::address.fields.zone |
| postcode | forms::address.fields.postcode |
| country | forms::address.fields.country |
Bottom Sections
| Key | View |
|---|---|
| phone | forms::address.fields.phone |
Additional Sections
Some storefront address forms have some additional sections on top of the ones above. To adjust these sections you’d have to specifically extend the specific form class instead of the generic Aero\Forms\AddressFormclass.
Aero\Checkout\Http\Forms\CustomerAddressForm
This address form has additional customer sections.
| Key | View |
|---|---|
| customer | account-area::forms.fields.address.name |
Aero\AccountArea\Http\Forms\AccountAddressForm
This address form is extended by all of the other account area address forms. This form injects 2 additional things to the top and bottom sections.
The top sections has this injected right at the top of all of the top sections:
| Key | View |
|---|---|
| address_name | checkout::sections.customer |
The bottom sections has this injected right at the bottom of all of the bottom sections:
| Key | View |
|---|---|
| is_default | account-area::forms.fields.address.is_default |
Admin Address Forms
The admin address forms are address forms found on your admin. These forms all extend Aero\Admin\Http\Forms\AdminAddressForm.
Structure
The structure for the admin forms is the same as the structure for the storefront forms but the views are different.
Top Sections
| Key | View |
|---|---|
| first_name | admin::forms.fields.address.first-name |
| last_name | admin::forms.fields.address.last-name |
| lookup | admin::forms.partials.address-lookup |
Sections
| Key | View |
|---|---|
| company | admin::forms.fields.address.company |
| line1 | admin::forms.fields.address.line1 |
| line2 | admin::forms.fields.address.line2 |
| city | admin::forms.fields.address.city |
| zone | admin::forms.fields.address.zone |
| postcode | admin::forms.fields.address.postcode |
| country | admin::forms.fields.address.country |
Bottom Sections
| Key | View |
|---|---|
| phone | admin::forms.fields.address.phone |
Address Form Validation
All address forms take validation from Aero\Forms\Validation\AddressRules. Instead of adding rules to this class through the extendsstatic method you should make use of the **Aero\Common\Helpers\Address::addField()**method. This method accepts the same parameters as if you were extending a validator (you can learn more about that here, link). The difference is that it adds the rules but also makes your field fillable on all of the address models. These models are Aero\Account\Models\Address, Aero\Cart\Models\OrderAddress, and Aero\Fulfillment\Models\FulfillmentAddress.
How do I add settings to my model?
This tutorial will explain the steps required to add settings to your model and assumes you have your model setup with create and edit pages.
Adding a Trait to your Model
The first step is to add the Aero\Common\Traits\CanHaveSettingstrait to your model.
<?php
namespace Acme\MyModule\Models;
use Aero\Common\Models\Model;
use Aero\Common\Traits\CanHaveSettings;
class MyModel extends Model
{
use CanHaveSettings;
}
Defining the Settings for your Model
Now that your model has the Aero\Common\Traits\CanHaveSettingstrait you can use the static settings()function on your models class to define your settings.
The settings are defined in the same way as when you create a normal setting group, the only difference being that you use your models class instead of the normal settings facade. You can read more about settings here
<?php
namespace Acme\MyModule;
use Acme\MyModule\Models\MyModel;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Common\Settings\SettingGroup;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
MyModel::settings(function (SettingGroup $group) {
$group->encrypted('password');
$group->boolean('require_password_to_view')->default(false);
});
}
}
Adding the Settings Fields to your Create/Edit Pages
Now that you have defined the settings for your model you can update your models create/edit pages to let users update the settings.
To add a card that lets user edit the models settings to your create/edit pages you need to include the admin::settings.model-settingsview and pass in your model, like this:
@include('admin::settings.model-settings', ['model' => $myModel])
If you don’t have a model to pass in (because you’re on a create page so the model hasn’t been created yet), pass in a fresh instance of your model, like this:
@include('admin::settings.model-settings', ['model' => new \Acme\MyModule\Models\MyModel()])
It’s important to ensure that the include is inside of your form. A more “complete” example may look something like this:
@extends('admin::layouts.main')
@section('content')
<div class="max-w-2xl mx-auto">
<div class="flex w-full justify-between">
<h2><a href="{{ route('admin.modules', request()->all()) }}" class="btn mr-4">@include('admin::icons.back') Back</a> Managing My Model</h2>
</div>
@include('admin::partials.alerts')
<form action="#" method="post" class="flex flex-wrap">
@csrf
@method('put')
<fieldset class="w-full">
{{-- Your other fields etc--}}
@include('admin::settings.model-settings', ['model' => $myModel])
<div class="form-buttons fieldset-disabled-hide">
<div class="card w-full">
<button class="btn btn-secondary" type="submit">Save</button>
</div>
</div>
</fieldset>
</form>
</div>
@endsection
Adding the Settings Validation to your Create/Edit Requests
You need to update your request validators so that the settings data will be correctly validated and formatted.
Adding the Rules
You need to update your validators rulesmethod to merge in the settings rules with your current rules. To do this you need to array_mergeyour rules with the array of rules returned by the Aero\Admin\Utils\SettingHelpers::getRulesForModelmethod. The Aero\Admin\Utils\SettingHelpers::getRulesForModel method expects you to pass the class string of your model.
<?php
namespace Acme\MyModule\Requests;
use Acme\MyModule\Models\MyModel;
use Aero\Admin\Utils\SettingHelpers;
use Aero\Common\Requests\AeroRequest;
class StoreMyModelRequest extends AeroRequest
{
public function rules(): array
{
return array_merge([
'name' => 'required|max:255', // You can add your models settings here
], SettingHelpers::getRulesForModel(MyModel::class));
}
}
Adding the Attributes
You need to do the same thing for the attributes.
<?php
namespace Acme\MyModule\Requests;
use Acme\MyModule\Models\MyModel;
use Aero\Admin\Utils\SettingHelpers;
use Aero\Common\Requests\AeroRequest;
class StoreMyModelRequest extends AeroRequest
{
public function attributes(): array
{
return array_merge([], SettingHelpers::getRuleAttributesForModel(MyModel::class));
}
public function rules(): array
{
return array_merge([
'name' => 'required|max:255', // You can add your models settings here
], SettingHelpers::getRulesForModel(MyModel::class));
}
}
Formatting the Data for Validation
You need to add a prepareForValidationmethod to your validator and ensure it calls the SettingHelpers::formatRequestDataForModel method like shown below. This method will format the incoming settings request data so that it is ready to be validated.
<?php
namespace Acme\MyModule\Requests;
use Acme\MyModule\Models\MyModel;
use Aero\Admin\Utils\SettingHelpers;
use Aero\Common\Requests\AeroRequest;
class StoreMyModelRequest extends AeroRequest
{
public function prepareForValidation()
{
$this->replace(
SettingHelpers::formatRequestDataForModel(MyModel::class, $this->all())
);
}
public function attributes(): array
{
return array_merge([], SettingHelpers::getRuleAttributesForModel(MyModel::class));
}
public function rules(): array
{
return array_merge([
'name' => 'required|max:255', // You can add your models settings here
], SettingHelpers::getRulesForModel(MyModel::class));
}
}
Saving the Validated Settings for your Model
To save the settings you need to pass your model and data into the Aero\Admin\Utils\SettingHelpers::saveForModelmethod, like this:
<?php
namespace Acme\MyModule\Http\Controllers;
use Acme\MyModule\Models\MyModel;
use Acme\MyModule\Requests\UpdateMyModelRequest;
use Aero\Admin\Http\Controllers\Controller;
use Aero\Admin\Utils\SettingHelpers;
class MyModelController extends Controller
{
public function update(UpdateMyModelRequest $request, MyModel $model)
{
$model->update($data = $request->validated());
SettingHelpers::saveForModel($model, $request->validated()['settings'] ?? []);
return redirect(route('my-model.index'))->with([
'message' => __('Your changes have been saved'),
]);
}
}
How do I add a custom price to a product with a module?
In this mini tutorial we will add some checkboxes to the product page that when checked add an additional charge to the product. The additional charge will be shown dynamically on the product page as it changes or the product's price changes (due to different variants being selected).
This mini tutorial assumes that you have a module setup or you’re happy working from the app service provider (using the boot method). You can see how to set up a module here (link).
Adding the Prices Data
To get started we will add a $pricesarray to our module service provider that will be the source of truth for our extra prices. We are using a simple array instead of database data due to this being a mini tutorial. The $pricesarray will have id, name, and price (this will also have inc and ex values and be in pence) keys.
<?php
namespace Acme\MyModule;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
protected $prices = [
[
'id' => 1,
'name' => 'Pay £1 more',
'price' => [
'inc' => 100,
'ex' => 83.33,
],
],
[
'id' => 2,
'name' => 'Pay £5 more',
'price' => [
'inc' => 500,
'ex' => 416.6,
],
],
[
'id' => 3,
'name' => 'Pay £10 more',
'price' => [
'inc' => 1000,
'ex' => 833.33,
],
],
];
public function setup()
{
//
}
}
Creating a Javascript View and Adding the Prices Data and Javascript to the Product Page
Now we’re going to create a Twig file called javascript where we will put the frontend javascript that will be used on the product page. This view is registered using the $this->loadViewsFrommethod in the module service providers setupmethod.
Next we’re going to use the Aero\Store\Http\Responses\ProductPageresponse builder to extend the product page. We will inject the $prices data using the setData method and then we’ll use the Aero\Store\Pipelines\ContentForBodypipeline to add our javascript view to the product page.
<?php
namespace Acme\MyModule;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Store\Http\Responses\ProductPage;
use Aero\Store\Pipelines\ContentForBody;
class ServiceProvider extends ModuleServiceProvider
{
protected $prices = [
[
'id' => 1,
'name' => 'Pay £1 more',
'price' => [
'inc' => 100,
'ex' => 83.33,
],
],
[
'id' => 2,
'name' => 'Pay £5 more',
'price' => [
'inc' => 500,
'ex' => 416.6,
],
],
[
'id' => 3,
'name' => 'Pay £10 more',
'price' => [
'inc' => 1000,
'ex' => 833.33,
],
],
];
public function setup()
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-module');
ProductPage::extend(function (ProductPage $page) {
$page->setData('extra_prices', $this->prices);
ContentForBody::extend(function (&$content) {
$content .= view('my-module::javascript');
});
});
}
}
Adding the Extra Prices to the Product Page
In the product.twigfile we’re going to add some Vue code to show the total price and the total extra price. Then we’ll add some Twig code to loop over the extra_pricesvariable that we injected into the product page earlier. This loop will display the name as a label and then add a checkbox with a value of the extra price id and some data attributes for the price inc and ex values.
Total Price
<p v-text="total_price.inc"></p>
Total Extra Price
<p v-text="additional_prices['extra-prices'].inc" v-if="additional_prices['extra-prices']"></p>
{% for extra in extra_prices %}
<div>
<input type="checkbox" id="extraPrice{{ extra.id }}" value="{{ extra.id }}" data-extra-price-inc="{{ extra.price.inc }}" data-extra-price-ex="{{ extra.price.ex }}">
<label for="extraPrice{{ extra.id }}"> {{ extra.name }}</label>
</div>
{% endfor %}
Coding the Javascript for the Product Page
Now we’re going to add javascript code to the javascript twig view we previously created. This javascript code will be responsible for setting the additional price when the checkboxes are checked and sending any checked extra prices to the server when the product is added to the cart.
To update the additional price when the checkboxes are checked we’ll set some code to be executed on the product.loadedAero Event. This code will loop over all input checkbox elements that have the data-extra-price-incattribute and add an input event listener. We’ll add a updateAndSetAdditionalPricesfunction that will be executed when the input changes and also initially once the product has loaded (in case you have some extra prices that are checked by default). This function loops over all input checked checkboxes that have the data-extra-price-incattribute and adds up their inc and ex prices. Then it runs the product.set-additional-price Aero Event to tell Aero the new total additional price.
To send the checked extra prices to the server we’ll set some code to be executed on the product.add-to-cartAero Event. This code loops over all input checked checkboxes that have the data-extra-price-incattribute and adds their values (the extra price id) to an array. This array is then added to the payload that will be sent to the server.
<script>
window.AeroEvents.on('product.loaded', function () {
var extraPriceElements = document.querySelectorAll('input[type="checkbox"][data-extra-price-inc]');
for (var i = 0; i < extraPriceElements.length; i++) {
extraPriceElements[i].addEventListener('input', updateAndSetAdditionalPrices);
}
function updateAndSetAdditionalPrices() {
var price = { key: 'extra-prices', inc: 0, ex: 0 };
var extraPriceElements = document.querySelectorAll('input[type=checkbox]:checked[data-extra-price-inc]');
for (var i = 0; i < extraPriceElements.length; i++) {
price.inc += parseInt(extraPriceElements[i].dataset.extraPriceInc);
price.ex += parseInt(extraPriceElements[i].dataset.extraPriceEx);
}
window.Aero.runEvent('product.set-additional-price', price);
}
updateAndSetAdditionalPrices();
});
window.AeroEvents.on('product.add-to-cart', function (data) {
var extras = [];
var extraPriceElements = document.querySelectorAll('input[type=checkbox]:checked[data-extra-price-inc]');
for (var i = 0; i < extraPriceElements.length; i++) {
extras.push(extraPriceElements[i].value);
}
data.extras = extras;
return data;
});
</script>
Adding any Selected Extra Prices to the Cart Item
We’ll add some code in the module service provider below the current code that extends the Aero\Store\Pipelines\CartItemBuilderand gets the extra price ids from the request, maps them to the relevant extra price, filters out any that are null (because they were not valid extra price ids), and then adds a Aero\Cart\CartItemOptionto the Aero\Cart\CartItemwith the extra price details.
<?php
namespace Acme\MyModule;
use Aero\Cart\CartItem;
use Aero\Cart\CartItemOption;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Store\Http\Responses\ProductPage;
use Aero\Store\Pipelines\CartItemBuilder;
use Aero\Store\Pipelines\ContentForBody;
class ServiceProvider extends ModuleServiceProvider
{
protected $prices = [
[
'id' => 1,
'name' => 'Pay £1 more',
'price' => [
'inc' => 100,
'ex' => 83.33,
],
],
[
'id' => 2,
'name' => 'Pay £5 more',
'price' => [
'inc' => 500,
'ex' => 416.6,
],
],
[
'id' => 3,
'name' => 'Pay £10 more',
'price' => [
'inc' => 1000,
'ex' => 833.33,
],
],
];
public function setup()
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-module');
ProductPage::extend(function (ProductPage $page) {
$page->setData('extra_prices', $this->prices);
ContentForBody::extend(function (&$content) {
$content .= view('my-module::javascript');
});
});
CartItemBuilder::extend(function (CartItem $item) {
collect(request()->input('extras', []))->map(function ($extra) {
return collect($this->prices)->firstWhere('id', $extra);
})->filter()->each(function ($extra) use ($item) {
$option = CartItemOption::create($extra['name']);
$option->setPriceInc($extra['price']['inc']);
$item->addOption($option);
});
});
}
}
General
General
How do I set up a slug validator?
You can control if a collection of slugs is valid or not by setting your own validator:
\Aero\Store\Routing\Slugs::setValidator(function ($slugs, $resolver) {
if ($resolver !== 'listings') return true;
if ($slugs->count() < 3 && $slugs->where('model_type', 'tag')->count()) return false;
return true;
});
How do I add a route to the CSRF exception list?
If you do not want your route to require a valid CSRF token
Code
To add a route to the exceptions list through code you need to add to the static VerifyCsrfToken::$exceptionsarray.
<?php
namespace Acme\MyModule;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Store\Http\Middleware\VerifyCsrfToken;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
VerifyCsrfToken::$exceptions[] = '/my-route';
}
}
Configuration
The routingconfiguration file has a csrf_exceptionsarray where you can list routes for the exceptions list.
You can publish the routingconfiguration file with this command:
php artisan vendor:publish --provider="Aero\Routing\Providers\RoutingServiceProvider"
Once this file is published you can view it by navigating to config/aero/routing.phpin your project. You can edit the csrf_exceptionsarray directly in this file.
You may need to clear the config cache for your changes to take effect.
php artisan config:clear
What queues does Aero use?
Queues are used so that HTTP requests can remain snappy and more intensive code can be executed later. You can learn more about queues here
| Queue | Description |
|---|---|
| search | This queue has search jobs. The majority of these jobs will be responsible for reindexing listings. |
| This queue has email jobs. Whenever an email is sent and queued in your application, it will be in this queue. | |
| subscriptions | This queue has subscription jobs. The majority of these jobs are responsible for creating and processing subscriptions. |
| default | This queue has any jobs that are not in one of the other queues. These jobs can be anything from order created event handlers to customer created event handlers. |
Aero uses four queues so that you can have different workers completing different tasks without a risk of locks or jobs being handled more than once. It’s important to note that some queues should be considered more important than others (for example, processing events such as an order created event is more important than updating your search index). In a production environment you should use tools like Supervisor to manage your queue workers.
You can use the queue work command to run all of or only specific queues (if you adjust the list of queues in the command). It’s important to note that the order of the queues in the command defines their priority.
How to Set the Page Title for the Admin
By default the page title for every admin page is generated from the route name. If you would like to manually set the page title you can set a title specifically for your route or you can return an adminPageTitlevariable to your view (this includes extending a response builder and adding this to the data).
To set the title manually for a specific route you can use the Aero\Admin\Http\Middleware\SetTitle::addTitle()method. This method expects a route name and a page title.
<?php
namespace Acme\MyModule;
use Aero\Admin\Http\Middleware\SetTitle;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
SetTitle::addTitle('admin.dashboard', 'The Dashboard Title');
}
}
Google Shopping Feed
Google Shopping Feed
How do I configure the Google Shopping feed?
Generating the taxonomy
Generating the taxonomy table is a required task after we’ve initially installed the package, and is the source of all the categories provided by google shopping.
To generate the taxonomies table we can:
php artisan aero:google:taxonomies
Setting a CRON run at time
We can specify the time at which the taxonomies should be updated automatically. This can be changed by:
- Editing the config.php file and manually adding the CRON time 2. Setting the value in the admin modules’ section under Configuration in the Google Shopping module.
Query parameters
Query parameters are used for every variant link that is input into the XML feed. These can be used to grab different prices such as included and excluded VAT. To set query parameters:
KEY=VALUE for a single parameter
KEY=VALUE, KEY2=VALUE2 and so on for multiple parameters
- Click on save
Each of our variants should now be output into the XML feed with the query parameters we have specified.
How do I assign Google categories?
Google categories can be assigned to categories, or directly to the product. It can be set up either way as the mapping tool automatically detects the ones that have been set up. There is also a way of mapping whether a google category is grabbed from categories or the products. It can be done the following way:
To categories
To products
How do I export the Google Shopping feed?
To export the feed we need to navigate into the module’s section in the admin, and then to Google Shopping. Provided we’ve generated the taxonomies and have configured the package correctly, we can click on the Exportbutton to begin mapping out the feed.
Mapping allows the feed to use alternative fields as a data source for a certain mapping as well as setting a fallback, in case the data source is empty.
How do I generate a Google Shopping feed?
In order to generate a Google Shopping feed, it is necessary to install an Aero dependency in form of the aerocargo/google-shoppingpackage from agora.aerocommerce.com
More developer-specific information about the Google Shopping feed can be found on the Google Shopping module page in agora.
Installing the package
To install the package, simply run the following command from the project’s root directory:
composer require aerocargo/google-shopping
After the package has been installed:
php artisan migrate
And finally, if we wish to modify any of the configuration:
php artisan vendor:publish --provider="Aerocargo\GoogleShopping\ServiceProvider"
Installation Deployment
Installation Deployment
How do I install Aero on Mac?
Before continuing, please ensure your Mac is running macOS Mojave (10.14) or newer.
There are a couple of ways to develop locally on the Mac:
- Valet – manually install the services directly on your Mac. - Homestead – a virtual machine that runs the required services.
Valet
Laravel Valet is a development environment for Mac minimalists. It provides a blazing fast local development environment with minimal resource consumption, and points all requests on the *.testdomain to sites installed on the local machine.
Step 1: Install Homebrew
Visit the Homebrew website and follow the instructions to install Homebrew.
Make sure everything is up-to-date by running:
brew update
Step 2: Install the services
Run the following command to install the services needed:
brew install php@7.4 mysql openjdk@8 elasticsearch@6 node composer
The services can then be started with the following commands.
Start MySQL:
brew services start mysql
Start Elasticsearch:
brew services start elasticsearch@6
Make sure to place Composer's system‑wide vendor bin directory in your $PATH:
export PATH="$PATH:$HOME/.composer/vendor/bin"
Step 3: Install Valet
You can then pull in the Valet package using Composer and install it using the following commands.
First require the Valet package:
composer global require laravel/valet
Then install the package:
valet install
Step 4: Create a database
To connect to the local MySQL server, run the following in your terminal application:
mysql -u root
Once connected, create a new database for your project. It should be named something that relates to your project to ease managing multiple local projects in the future. Run the following SQL, replacing [project-name]with the name of your project:
create database [project-name];
If your database name contains hyphens, you must wrap the name in back-ticks, e.g. my-first-store.
You can then disconnect from the MySQL server by typing:
exit;
Step 5: Install the Aero Commerce CLI tool
If this is your first project, you'll need to download the command‑line tool using Composer:
composer global require aerocommerce/cli
Once installed, you'll have access to the aero newcommand. This will create a fresh project installation in the directory you specify.
For instance, aero new myfirststorewill create a directory named myfirststorecontaining an Aero project with all dependencies installed:
aero new [project-name]
**During installation, you'll need to enter the package repository credentials and database connection details for the project.**The installer will setup the database tables and seed the project with essential information.
Step 6: Link the project to Valet
The final thing to do is configure the project with Valet. This is achieved by running the valet linkcommand from within the project's directory. You can provide a name to use as the domain, for example, to proxy all requests made to http://myfirststore.testto the project, the command would be valet link myfirststore:
valet link [project-name]
You can now open your web browser and visit your newly created ecommerce store!
Homestead
We are currently unable to offer support for the configuration of Homestead, or any issues associated during the installation process.
Laravel Homestead is a pre-packaged Vagrant box that provides a development environment without requiring you to install any server software on your local machine.
Step 1: Install VirtualBox
Firstly, you'll need to download VirtualBox 6.x. Select the OS X hosts binary and run the installer.
During installation, you may encounter the following error screen:
This is due to security restrictions in macOS. To resolve this, open your terminal application and run the command:
sudo spctl --master-disable
Retry the installation. If the problem still persists, try restarting your Mac and running the installer again.
Once you've successfully completed the installation wizard, you should re-enable Gatekeeper by running the command:
sudo spctl --master-enable
Step 2: Install Vagrant
Head over to the Vagrant download page and select the macOS 64-bit link. Follow the prompts on the installation wizard.
Once complete, you can confirm Vagrant is on your machine by typing the following command into your terminal application to display the Vagrant version:
vagrant -v
Step 3: Install the Homestead Vagrant box
Run the following command in your terminal application:
vagrant box add laravel/homestead
The download may take several minutes, depending on your internet connection.
Step 4: Install Homestead
The Homestead GitHub repository needs to be cloned to your local machine. To do this, run the command:
git clone https://github.com/laravel/homestead.git ~/Homestead
This will create a copy of the repository in a new folder named Homesteadwithin your home directory.
Step 5: Configure Homestead
Next, you'll need to create a default Homestead.yamlfile which will serve as the instructions to configure your virtual machine. This can be done using the following commands:
$ cd ~/Homestead
$ git checkout release
$ bash init.sh
If you open the newly created Homestead.yaml file, you should see a format similar to this:
---
ip: "192.168.10.10"
memory: 2048
cpus: 2
provider: virtualbox
authorize: ~/.ssh/id_rsa.pub
keys:
- ~/.ssh/id_rsa
folders:
- map: ~/myfirststore
to: /home/vagrant/myfirststore
sites:
- map: myfirststore.test
to: /home/vagrant/myfirststore/public
php: "7.4"
databases:
- homestead
features:
- elasticsearch:
version: "6.8.6"
ssh-keygen
The foldersproperty lists all of the folders to be shared between your Mac and the virtual machine. This allows you to develop using your local IDE (code editor), whilst also providing the web server and other services on the virtual machine access to the project files.
In the example Homestead.yamlshown above, we've specified to share the ~/myfirststorefolder. This is going to be the local directory of our new Aero project, however the folder does not yet exist. To create this directory, run the following command, replacing [project-name]with the name of your project:
mkdir ~/[project-name]
This empty directory will be populated later on, from within the virtual machine. The tovalue of this folder mapping is the project's path on the virtual machine, e.g. /home/vagrant/[project-name].
The sitesproperty allows you to define which domains will provide access to the projects. Using the example above, you can see the myfirststore.testdomain is configured to point to the project's publicdirectory, which is the web server entry point to the project. Ensure the tovalue is the virtual machine's project path and not the local one, i.e. /home/vagrant/[project-name]/public. Remember to replace [project-name]with the name of your project.
Homestead will automatically create the databases listed in the databaseproperty of the configuration file. By default, the database name is homestead. The installer will automatically detect this database later on, however, if you plan on developing multiple projects, you should name the databases in this list to match your projects.
Finally, you need to ensure that your Homestead.yamlconfiguration file contains elasticsearchin the featuresproperty, along with the version: 6declaration.
Step 6: Add the domain to the hosts file
As the project is hosted locally, we need to add an entry to the /etc/hostsfile, which will route all requests made to the development domain to your project:
sudo vi /etc/hosts
Open the /etc/hostsfile in viand add a record to point the domain to the IP address of the virtual machine (the IP listed in the Hometead.yamlconfiguration file). Press i to enter insert mode, and use the arrow keys on the keyboard to move the cursor to the bottom of the file. Add the following line, replacing [project-name]with the domain name entered in the sites property of your Homestead.yamlconfiguration file:
192.168.10.10 [project-name].test
To save the changes and exit the file, press Control + c to leave insert mode, and then type :wq! followed by pressing the Enter key.
Step 7: Provision the virtual machine
You are now ready to boot up the virtual machine. To do so, open your terminal application and navigate to the directory that Homestead was installed in earlier. You can then run the vagrant upcommand, which will read from the Homestead.yamlfile and configure the virtual machine.
$ cd ~/Homestead
$ vagrant up
Step 8: Install the Aero Commerce CLI tool
The CLI tool needs to be installed on the virtual machine. To do this, SSH into the virtual machine using the command:
vagrant ssh
From within the virtual machine you will need to set the default PHP version by running:
php74
Next, download the command‑line tool using Composer:
composer global require aerocommerce/cli
Once installed, you'll have access to the aero newcommand, which needs to be ran from within the project directory on the virtual machine. For instance, running aero new .from within the directory myfirststorewill scaffold an Aero project with all dependencies installed:
$ cd ~/myfirststore
$ aero new .
**During installation, you'll need to enter the package repository credentials and database connection details for the project.**The installer will setup the database tables and seed the project with essential information.
You can now open your web browser and visit your newly created ecommerce store!
How do I install Aero on Windows?
Homestead
We are currently unable to offer support for the configuration of Homestead, or any issues associated during the installation process.
Laravel Homestead is a pre-packaged Vagrant box that provides a development environment without requiring you to install any server software on your local machine.
Step 1: Enable hardware virtualization
Modern CPUs include hardware virtualization features that help accelerate virtual machines created in VirtualBox. We recommend consulting your computer's user guide on how to enable VT-x/AMD-V in your BIOS or UEFI firmware.
Step 2: Install VirtualBox and Vagrant
Firstly, you'll need to download VirtualBox 6.x. Select the Windows hosts binary and run the installer.
Next we'll head over to the Vagrant download page and select the Windows link. Follow the prompts on the installation wizard.
Once complete, you can confirm Vagrant is on your machine by typing the following command into a PowerShell window:
vagrant -v
Step 3: Install the Homestead Vagrant box
Open up a PowerShell window and run the following command:
vagrant box add laravel/homestead
The download may take several minutes, depending on your internet connection.
Step 4: Install Homestead
The Homestead GitHub repository needs to be cloned to your local machine.
If you have not yet installed Git on your machine, head over to gitforwindows.org to download.
With Git available on your machine, run the following command from within a PowerShell window:
git clone https://github.com/laravel/homestead.git ~/Homestead
This will create a copy of the repository in a new folder named Homesteadwithin your home directory.
Next, you'll need to create a default Homestead.yamlfile which will serve as the instructions to configure your virtual machine. This can be done using the following commands:
cd ~/Homestead
git checkout release
./init.bat
If you open the newly created Homestead.yaml file, you should see a format similar to this:
---
ip: "192.168.10.10"
memory: 2048
cpus: 2
provider: virtualbox
authorize: C:\Users\user\.ssh\id_rsa.pub
keys:
- C:\Users\user\.ssh\id_rsa
folders:
- map: C:\Users\user\Code\myfirststore
to: /home/vagrant/myfirststore
type: "nfs"
sites:
- map: myfirststore.test
to: /home/vagrant/myfirststore/public
php: "7.4"
databases:
- homestead
features:
- elasticsearch:
version: "6.8.6"
ssh-keygen
The foldersproperty lists all of the folders to be shared between your Windows host and the virtual machine. This allows you to develop using your local IDE (code editor), whilst also providing the web server and other services on the virtual machine access to the project files.
The ~/syntax is not valid when running on Windows. Instead use the full path, e.g. C:\Users\user\myfirststore
In the example Homestead.yamlshown above, we've specified to share the C:\Users\user\Code\myfirststorefolder. This is going to be the local directory of our new Aero project, however the folder does not yet exist. Create a directory using Windows Explorerand update the Homestead.yamlfile.
This empty directory will be populated later on, from within the virtual machine. The tovalue of this folder mapping is the project's path on the virtual machine, e.g. /home/vagrant/[project-name].
The sitesproperty allows you to define which domains will provide access to the projects. Using the example above, you can see the myfirststore.testdomain is configured to point to the project's publicdirectory, which is the web server entry point to the project. Ensure the tovalue is the virtual machine's project path and not the local one, i.e. /home/vagrant/[project-name]/public.Remember to replace [project-name]with the name of your project.
Homestead will automatically create the databases listed in the databaseproperty of the configuration file. By default, the database name is homestead. The installer will automatically detect this database later on, however, if you plan on developing multiple projects, you should name the databases in this list to match your projects.
Finally, you need to ensure that your Homestead.yamlconfiguration file contains elasticsearchin the featuresproperty, along with the version: 6declaration.
Step 5: Add the domain to the hosts file
As the project is hosted locally, we need to add an entry to the C:\Windows\System32\drivers\etc\hostsfile, which will route all requests made to the development domain to your project.
Press the Windowskey and type notepadin the search field. Right-click on Notepadin the search results and select Run as administrator. Open the C:\Windows\System32\Drivers\etc\hostsfile and add the following line, replacing [project-name]with the domain name entered in the sites property of your Homestead.yamlconfiguration file:
192.168.10.10 [project-name].test
Save the file and close Notepad.
Step 6: Add the NFS for Vagrant plugin
A plugin is required when using NFS to sync folders on Windows and will maintain the correct user / group permissions. To install the plugin, run the command in a PowerShell window:
vagrant plugin install vagrant-winnfsd
You'll then need to open up the Vagrantfilefound in your Homestead directory and add the following line to the end of the file (within the config area):
config.vm.network "private_network", type: "dhcp"
Step 7: Provision the virtual machine
You are now ready to boot up the virtual machine. To do so, open a PowerShell window and navigate to the directory that Homestead was installed in earlier. You can then run the vagrant upcommand, which will read from the Homestead.yamlfile and configure the virtual machine. The virtual machine must be ran with administrator privileges to ensure files are synced correctly.
cd ~/Homestead
Start-Process powershell -Verb runAs
vagrant up
Step 8: Install the Aero Commerce CLI tool
The CLI tool needs to be installed on the virtual machine. To do this, SSH into the virtual machine using the following command from a PowerShell window:
vagrant ssh
From within the virtual machine you will need to set the default PHP version by running:
php74
Next, download the command‑line tool using Composer:
composer global require aerocommerce/cli
Once installed, you'll have access to the aero newcommand, which needs to be ran from within the project directory on the virtual machine. For instance, running aero new .from within the directory myfirststorewill scaffold an Aero project with all dependencies installed:
cd ~/myfirststore
aero new .
**During installation, you'll need to enter the package repository credentials and database connection details for the project.**The installer will setup the database tables and seed the project with essential information.
You can now open your web browser and visit your newly created ecommerce store!
What are the system requirements?
Your system must satisfy the following requirements:
PHP (>= 7.2) with BCMath, Ctype, JSON, Mbstring, OpenSSL, PDO, Tokenizer, XML, cURL,GD/ImageMagick
Node.js (>= 8.10)
MySQL (>= 5.7)
Elasticsearch (6.*)
Introduction
Introduction
Introduction to backend admin slots
Slots allow for a module to include its own view on existing admin pages.
See our section on backend admin slots for more information.
Introduction to collections for developers
Collections are not what the developer might associate with Laravel’s immutable structure, but a collection of rules being applied to the listing on the store’s frontend.
Aero has a variety of collection rules which can alter the filters, more specifically, the search filters that usually would be modified by the customer where products are listed. These include:
Collection rules
For more information about collections please see our admin documentation for collections
Introduction to permissions for developers
As an agency, you will likely need to restrict the information and actions of admin accounts belonging to the retailer.
This is easily set up using our user configuration page. For more information please see "How do I set permissions for users?"
As a developer you can expand on these permissions in order to add anything else you may need;
Introduction to transformers
Transformers are used by the admin on some edit pages to transform data from PHP into an json encoded array that can be passed to Vue.
See our section on admin transformers for more information.
Introduction to events for developers
Events are a method of notifying different parts of Aero of tasks that have either been completed or need completing right after something has occurred. Events are only fired when a specified listener has been assigned to it and appropriately notified.
The events are always declared first in any Service Provider, while the listeners are wrapped within the event class. This is because we might need a number of listeners for a given event, each responsible for carrying out different tasks.
See our section on events for more information.
Example of an event:
<?php
namespace Aero\Account\Events;
use Aero\Account\Models\Customer;
use Aero\Events\ManagedEvent;
class CustomerUpdated extends ManagedEvent
{
public $customer;
public $customerName;
public $customerEmail;
public static $variables = [
'customer',
'customerName',
'customerEmail',
];
public function __construct(Customer $customer)
{
$this->customer = $customer;
$this->customerName = $customer->name;
$this->customerEmail = $customer->email;
}
public function getNotifiable()
{
return $this->customer;
}
}
Introduction to pipelines
Pipelines provide a convenient way to manipulate a given payload. This payload can be an object or a simple string.
See our section on pipelines for more information
Introduction to the account area
The account area provides an extendable area where your customers can view their orders and manage their accounts.
See our section on the account area for more information.
Introduction to response builders
Response builders are Aero's unique solution to serving storefront pages. Based on the concept of pipelines used in Laravel middleware
See our section on response builders for more information.
Introduction to listings and search
Aero offers out-of-the-box listings and search system which is powered by Elasticsearch
Please note the Elasticsearch version is 6.8.
See our section on listings and search for more information.
Introduction to payment drivers
A payment driver is the brain behind the payment method and serves as a bridge to the payment gateway. Whilst there are several integrations that are maintained by the Aero team, a store may require a bespoke integration in order to meet the requirements of a retailer.
See our section on payment drivers for more information.
What is an Aero Commerce Agora account and why do I need it?
To get started you'll need an Agora account.
Your Agora account is used to download the platform and create projects. You can register for an account here
Projects are assigned unique credentials which allows them to purchase themes and modules. These credentials must not be shared between projects as doing so will invalidate them.
Without valid credentials, a project cannot receive platform updates and security fixes.
How do I obtain package repository credentials?
Once logged in to Agora, head over to the projects section and click the New project button. You'll need to give the project a name, for example, the website name or your client's company. In the example below, we've used a fictional company of "Acme Inc".
A domain is optional at this point; you can enter this later when the project is ready to go live. Click the "create new project" button to create the project.
Your new project now appears in the list of projects along with the package repository credentials.
Introduction to the admin
The backend admin (the "admin") is the password-protected back office where administrative tasks can be performed. This includes managing the store's catalog (products, categories, manufacturers, etc.), viewing and processing orders, managing discounts, viewing reports, editing the content of the storefront (promotional imagery, menus and SEO content, etc.), and much more.
Introduction to resource lists
Resource lists allow a table to be extended through additional columns, filters, or sort bys and for bulk actions to be applied.
Please see our section on resource lists for more information.
Introduction to Laravel PHP Framework
The Aero platform is built using the Laravel PHP framework
Before developing with Aero, we recommend that you become acquainted with the core concepts of Laravel. We recommend the following resources:
The official Laravel documentation is a great place to start.
Laracasts offer an on‑demand library of screencast video tutorials.
Listings And Search
Listings And Search
How do I create a custom sort order for the listings pages?
When you set the sort get parameter in a listing pages url (e.g. ?sort=name-az), a method is called on the \Aero\Search\Elastic\Query\Builderclass (for name-az the method sortByNameAzis called). You can find the default sort by methods in the Aero\Search\Elastic\Concerns\CanBeSortedtrait.
To add a custom sort to the listings page you therefore just need to macro a method to the \Aero\Search\Elastic\Query\Builderclass using the correct naming convention.
This example adds an ascending and descending sort by for the summary (to use it you need to add ?sort=summary-az or ?sort=summary-za to the listing page url):
<?php
namespace Acme\MyModule;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
\Aero\Search\Elastic\Query\Builder::macro('sortBySummaryAz', function () {
$this->query->addSort([
'string-sort.summary' => [
'order' => 'asc',
],
]);
});
\Aero\Search\Elastic\Query\Builder::macro('sortBySummaryZa', function () {
$this->query->addSort([
'string-sort.summary' => [
'order' => 'desc',
],
]);
});
}
}
If you try the above code you will notice that it throws an Elastica\Exception\ResponseExceptionerror. This is because the string-sort.summaryfield used in the Elasticsearch sort by query does not exist.
To add summaryas a field to the string-sortkey you need to use the **\Aero\Search\Elastic\Documents\ListingDocument::add()**method to define a closure that will return an array of data to merge into the document structure. It’s important to know that when you change the document structure you need to reindex. You can reindex using the php artisan aero:search:reindexcommand.
<?php
namespace Acme\MyModule;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
\Aero\Search\Elastic\Documents\ListingDocument::add('string-sort', function ($document) {
return [
'summary' => $document->getModel()->product->getTranslation('summary', request()->store()->language),
];
});
\Aero\Search\Elastic\Query\Builder::macro('sortBySummaryAz', function () {
$this->query->addSort([
'string-sort.summary' => [
'order' => 'asc',
],
]);
});
\Aero\Search\Elastic\Query\Builder::macro('sortBySummaryZa', function () {
$this->query->addSort([
'string-sort.summary' => [
'order' => 'desc',
],
]);
});
}
}
Advanced Query
It’s possible to do a more advanced query for your Elasticsearch sort by. You can make use of Elasticsearch’s Painless scripting language
This example code shows how to add a rand sort that will execute a Java script to sort the listings.
<?php
namespace Acme\MyModule;
use Aero\Common\Providers\ModuleServiceProvider;
use Elastica\Script\Script;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
\Aero\Search\Elastic\Query\Builder::macro('sortByRand', function () {
$this->query->addSort([
'_script' => [
'type' => 'number',
'script' => [
'lang' => Script::LANG_PAINLESS,
'source' => $this->loadJavaFile(__DIR__.'/../scripts/random-sort.java'),
'params' => $this->parameters,
],
'order' => 'asc',
],
]);
});
}
}
This Java file is located in a scripts folder outside of the src folder:
def sorts = new ArrayList();
Random rand = new Random();
sorts.add(rand.nextInt(1000000));
if (sorts.size() === 0) {
sorts.add(0);
}
Collections.min(sorts.stream().map(Double::doubleToRawLongBits).collect(Collectors.toList()));
What are the available documents for listings and search?
There are 2 documents available:
Aero\Search\Elastic\Documents\ListingsDocument
This document is used to hold information about the listings that are shown on the storefront.
Aero\Search\Elastic\Documents\ProductDocument
This document is used to hold information about the products within the store’s catalog. It is generally used in the backend admin, to allow for quick access when managing products.
How do I re-index listing and search documents?
Aero automatically manages indexing changes to documents. For example, if a product's stock level changes the document is updated to reflect the current version. Adding and removing documents is also taken care of for you, sparing the need to schedule in daily tasks to update the store's entire catalog. However, in cases where a large amount of data has been manually changed, or an addition to the document structure has been made, a re-index of all documents can be carried out using the following command:
php artisan aero:search:reindex
If there have only been modifications to a certain document (for example, only the listing document structure has been extended) then the document typecan be passed as an option, which will only re-index the documents with the provided type. The available types correspond to the names of the elastic documents, listing, and product.
php artisan aero:search:reindex --type=listing
How do I add custom facets to listing and search?
In order to define facets against a document, you must first register the facet group. This should be done within the **boot()**method of a ServiceProvideror the **setup()**method of a module ServiceProvider.
\Aero\Search\FacetSettings::registerFacet('stock', 'Stock Status');
The document structure can then be extended to add the facet to the document’s data.
\Aero\Search\Elastic\Documents\ListingDocument::add('string-facets', function ($document) {
$stock = $document->getModel()->availableStock() > 0;
return [
\Aero\Search\Elastic\Facet::create('stock', 'Stock Status', (int) $stock, $stock ? 'In-stock' : 'Out of stock'),
];
});
Re-index the documents after any modifications to the document structure.
How do I setup the search index?
Firstly, you should ensure that the Elasticsearch host information is defined in the store's .env. If you're running the service directly on your machine (either natively or through Docker), you'll most likely use the following details:
ELASTICSEARCH_HOST=localhost
ELASTICSEARCH_PORT=9200
When using a remote connection, ensure the host does not contain the port and URI scheme.
It is also important to ensure the STORE_IDENTIFIERis defined. This value is automatically added to the project's .envwhen using the Aero CLI tool to install and configure a store.
To create the Elasticsearch index and apply the mappings, run the following command within the root directory of your project:
php artisan aero:search:install
How do I re-build the listings and search index?
At times (especially during the development phase of a store build), it may be necessary to delete and documents and freshly index them. This process can be achieved using the following command:
php artisan aero:search:rebuild
Be careful running the re-build command on a production store.
Due to the nature of how this process runs, there will be a temporary period where there are no listings or products shown.
How do I extend the document structure for listings and seach?
One of the most useful features of the listings and search system is the ability to add custom data into the document. Typically, this will be data to be consumed in the code that forms the storefront listings page.
To extend a document, use the **add()**method, to define a closure that will return an arrayof data to merge into the document structure. This is typically done from within a ServiceProvider. If the code is unique to a particular store it could be placed in the boot method of the AppServiceProvider. If it is to be included as part of a module, then it can be added in the setup method of the module's ServiceProvider.
\Aero\Search\Elastic\Documents\ListingDocument::add('search-result', function ($document) {
$colors = $document->getModel()->visibleVariants()->pluck('attributes')->flatten()->filter(function ($attribute) {
return strpos(strtolower($attribute->group->name), 'color') !== false;
})->pluck('name')->unique()->values()->all();
return [
'colors' => $colors,
];
});
The available section of the document that can be extended are:
- search-result - search-data
How do I filter listings?
There may be occasions where you need to pre-filter listings. For example, you may want to ensure that all listings are connected to a certain sub-store or Dropship provider, or all share a tag that is set by your application rather than applied by the customer. Below is an example that extends the Elasticsearch listings query to scope the results to only those that have stock:
\Aero\Search\Elastic\Repositories\ElasticListings::extend(function ($listings) {
$filter = new \Elastica\Query\Term(['number-sort.has-stock' => ['value' => 1]]);
$listings->base()->getQuery()->addFilter($filter);
});
How to Extend the Elastic Search Listing Model
The Aero\Search\Elastic\Models\Listingmodel is built up from an array of data returned from elastic search. You can modify the model's attributes through the Aero\Search\Elastic\Pipelines\ListingAttributespipeline.
For example you can change every listing’s name to be “Example”.
It’s important to note that this pipeline is run for every listing that is loaded, everytime. If you use this approach to add a resource intensive calculation it’s important to take steps such as caching so performance isn’t impacted.
<?php
namespace Acme\MyModule;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Search\Elastic\Pipelines\ListingAttributes;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
ListingAttributes::extend(function ($attributes) {
$attributes['name'] = 'Example';
return $attributes;
});
}
}
Module Development
Module Development
How do I register a custom Vue component for my module
To register a Vue component for our module, we will need to create a JavaScript file for our components and in order to initialize Vue.
Link assets
In order to link all the assets, which is a necessary step of registering a Vue component, we need to add a function to the module Service Provider. This is the code we need in the service provider:
public function assetLinks()
{
return [
'vendor/module-name' => __DIR__ . '/../public',
];
}
Bear in mind this is the exact path we have to supply when loading components into a view.
Initialize components
import NewComponent from './components/NewComponent
window.newModule = {
install(Vue) {
Vue.component(‘new-component', NewComponent)
},
}
The above code gives us access to the <new-component></new-component>tag, provided we’ve loaded the components into a view.
Loading components into a view
@push('scripts')
<script src="{{ asset(mix(new-module.js', 'modules/aerocargo/new-module')) }}"></script>
<script>
window.AeroAdmin.vue.use(window.newModule);
</script>
@endpush
Provided all of the namespacing in the two files matches correctly, the Vue components that we have declared will now be accessible in our view.
If there is a problem with the mix of our assets, remember to run php artisan aero:linkin the root directoryof your Aero store.
Enabling Vue devtools
In order to enable Vue devtools for our module, we simply add the following line of code into the file where we initialize our components:
import NewComponent from './components/NewComponent
window.new-module = {
install(Vue) {
Vue.config.devtools = true;
Vue.component(‘new-component', NewComponent)
},
}
Anatomy of a custom module
└─ module
└─ database
└─ migrations
└─ public
└─ resources
└─ css
└─ js
└─ views
└─ admin
└─ store
└─ routes
└─ src
└─ Http
└─ Controllers
└─ Responses
└─ Steps
└─ Requests
└─ Models
ServiceProvider.php
.gitignore
composer.json
README.md
Database
The database folder is only required for purposes of migrating, seeding, or storing data in formats such as JSON.
Migrations
Migrations is a folder within the database directory structure used for storing migration files which allow for populating the database with missing tables, or updating them.
Running php artisan migratein the root project directoryafter installing a module detects migrations from all modules installed on Aero, which have not yet been migrated, and migrates them into Aero.
Public
All files within the public directory of a module can be published, to then be accessed anywhere in Aero. Some modules might require the public folder published before it can be used.
This might include images or files used to instantiate a JavaScript file for the use of Vue or any other JS framework.
Resources
The resources are required by the module for rendering views and storing custom CSS and JS files. Separating views into sub-folders allows for more readability and clarity on where particular views are used.
For example, the adminand storesub-folders of resources/viewscan be used for separating the different routing the module has.
Routes
Modules can have custom routes which are then served to the rest of the app. These might serve as callback URLs for APIs in the module or rendering views.
Example:
<?php
use Aerocargo\Csv\Http\Controllers\CsvController;
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'csv'], function ($route) {
$route->get('/', [CsvController::class, 'index'])->name('admin.csv.import');
$route->get('/mapping/{hash?}', [CsvController::class, 'mapping'])->name('admin.csv.mapping');
$route->get('/search', [CsvController::class, 'aeroFieldsSearch'])->name('admin.csv.search.aero');
$route->get('/search-csv', [CsvController::class, 'csvFieldsSearch'])->name('admin.csv.search.csv');
$route->get('/processing', [CsvController::class, 'processing'])->name('admin.csv.processing');
$route->post('/importing', [CsvController::class, 'importing'])->name('admin.csv.importing');
$route->post('/process', [CsvController::class, 'process'])->name('admin.csv.process');
$route->post('/import', [CsvController::class, 'import'])->name('admin.csv.process.import');
$route->post('/ping-import', [CsvController::class, 'pingImport'])->name('admin.csv.process.ping');
$route->post('/get-mapping/{id?}', [CsvController::class, 'getMappingData'])->name('admin.csv.get.mapping');
});
Source (src)
The source folder contains all of the backend serving the entire module, whether it is controllers, models, or traits.
Service Provider
The module Service Provideris used to instantiate the module, and usually contains configuration for the resources the module uses. This could range from routes, views to being able to display the module and its UI within the Modules section of Aero.
The scaffolded Service Provider has several functions that are hidden from view.
These include:
- setup()- assetLinks()- boot()- seed()- getSeeds()
setup()
Every scaffolded Service Provider will include the setup() function, which has to be configured to include all necessary resources.
Example:
public function setup()
{
AdminSlot::inject('catalog.products.index.header.buttons', function () {
return view('csv::buttons.import');
});
Router::addAdminRoutes(__DIR__.'/../routes/admin.php');
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
$this->loadViewsFrom(__DIR__.'/../resources/views', 'csv');
BulkAction::create(ExportProducts::class, ProductsResourceList::class)
->notRunnable()
->permissions('products.export')
->title('Export products');
}
assetLinks()
Asset links are used to publish the resources included in the public folder of the module so that they can be used anywhere on the Aero platform.
Example:
public function assetLinks()
{
return [
'vendor/module-name' => __DIR__ . '/../public',
];
}
boot()
The boot() method is run in the background if the setup() method is present within the Service Provider. This is also the case for the booted() method.
seed()
With the **seed()**method, it is possible to add a seed to a collection of seeds for the module.
getSeeds()
Returns a unique collection of seeds that have been added to the module.
If we require our module to listen to certain events, they can be added into an array property of the class:
Listen property
The listen property is an array of events for each given module. The syntax for adding a listener to an event looks like so:
protected $listen = [
\Aero\Cart\Events\OrderSuccessful::class => [
\Aerocargo\Testing\Listeners\ExportOrder::class,
],
];
Custom directories
It is possible to create custom directories for different types of classes, for example, Traits, Jobs, or Helpers.
How do I create a custom module?
When interacting with the admin, especially when making a custom module, it is important to require aerocommerce/adminin the modules’ composer.jsonfile.
Scaffolding modules
Modules can be created using artisan, a command line interface supplied with Laravel. The easiest way to create a new module is to open the terminal, navigate to the root folder of the project and paste in:
php artisan make:module vendor/module-name
It is important to stick to module naming conventionsand include the vendor(such as aerocommerce or aerocargo) and the module name, with dashes replacing any space in the name.
Running the above command in the terminal results in a couple of brief messages, reporting on the status of the creation and installation process of our module. After all is complete, the module can be accessed in the root directory of Aero under “modules”.
Configuring modules
As mentioned above, each module that interacts with the admin needs to require aerocommerce/adminin the modules’ composer.jsonfile.
Composer.json
{
"name": "aerocargo/new-module",
"description": "",
"require": {
"php": "^7.2|^8.0",
"aerocommerce/core": "^0",
"aerocommerce/admin": "^0"
},
"autoload": {
"psr-4": {
"Aerocargo\\NewModule\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Aerocargo\\NewModule\\ServiceProvider"
]
}
}
}
Service Provider
The module Service Providerensures that our modules are connected to the rest of the platform through the **setup()**method that is automatically scaffolded into the class.
Adding modules to a visible list in the admin
Some modules might not require any interaction with the admin in terms of UI, so they don’t need to necessarily be listed in the modules section of the admin. If we wish to give our module an interface and allow users to access the module through the admin interface, we have to add the following code to our **Service Provider’s setup()**function:
AdminModule::create(‘new-module’)
->title('New Aero Module')
->summary('A brand new Aero module.')
->routes(__DIR__.'/../routes/admin.php')
->route('admin.new-module.index');
This module should now be listed in the admin.
Loading migrations
In order for the module to be able to detect all the migrations that a module has, it has to have a specified path in the modules’ **Service Provider setup()**function.
If the migration folder follows the original anatomy of module structure, all we have to do is paste the following code into the **Service Provider setup()**function:
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
You should now be able to call php artisan migratein the root Aero directory to migrate data from the module.
Loading views
Loading views works the same way as loading migrations, all we have to do it to specify the path to our views and also a namespace.
$this->loadViewsFrom(__DIR__.'/../resources/views', 'new-module');
The second parameter in the above function is the namespace assigned to all of the module views. This is extra useful as we can then use that namespace to render views in the controller:
return view(‘new-module::index’);
Loading routes
There are two different types of routes that the module has access to. One of them is the admin routeswhich only give access to routes provided the user is an administrator. The other is the store routeswhich affect the store/frontend side of the system.
Admin routes
There are two ways of setting upadmin routes, depending on whether we choose to use the AdminModule facade and load the module into the admin interface.
If we do choose to add our module to a list in the admin, apply the following chained function to the AdminModule facade in the **setup()**function in the Service Provider:
AdminModule::create('new-module')
->title('New Aero Module')
->summary('A brand new Aero module.')
->routes(__DIR__.'/../routes/admin.php');
This allows us to create the necessary admin routes, for navigating the module in the admin.
If we don’t choose to add our module to a list in the admin, we simply add the following piece of code to the **setup()**function in the module Service Provider:
Router::addAdminRoutes(__DIR__.'/../routes/admin.php');
Store routes
In order to add store (frontend) routes to the module, we simply add a piece of code to the **setup()**function of the module Service Provider:
Router::addStoreRoutes(__DIR__.’/../routes/store.php');
Asset Linking
In order to publish any resources from our module to the rest of Aero, we have to specify the path for those resources in a special function. This function is added to the module Service Provider:
public function assetLinks()
{
return [
'aerocargo/new-module' => __DIR__.'/../public',
];
}
After defining any asset paths to link, you'll need to run the command:
php artisan aero:link
How do I setup routes for my custom module?
There are a few necessary steps to create and properly configure module routing.
Creating routes
The first step to creating module routes is to create a directory named routeson the same level as the srcdirectory – for more information refer to "Anatomy of a custom module.phpfile in the routesdirectory. If my module only requires admin routes, I’ll create a file called admin.php in the routes directory:
└─ module
└─ database
└─ migrations
└─ public
└─ resources
└─ css
└─ js
└─ views
└─ admin
└─ store
└─ routes
└─ admin.php
└─ src
└─ Http
└─ Controllers
└─ Responses
└─ Steps
└─ Requests
└─ Models
ServiceProvider.php
.gitignore
composer.json
README.md
The contents of the file become:
<?php
use Illuminate\Support\Facades\Route;
The file can then be populated with the necessary routes the module needs. For more information on routes see the laravel documentaiton on routing
Loading routes in the Service Providers
The module needs to be made aware of all the routes that are available for it, and this is how it can be done:
Single routes file
$this->loadRoutesFrom(__DIR__.'/../routes/routes.php');
Store routes
Router::addStoreRoutes(__DIR__ . '/../routes/web.php');
Admin routes
Router::addAdminRoutes(__DIR__ . '/../routes/admin.php');
Using AdminModule facade
Routes can also be added through the AdminModulefacade which allows the chaining for both **route()**and routes(). This should only be used with modules that have an interface/access point in the admin modules section. It can be done like so:
AdminModule::create('csv')
->title('CSV Import & Export')
->summary('Used to import and export a variety of platform data.')
->routes(__DIR__.'/../routes/admin.php')
->route('admin.csv.index');
Where the **route()**accesses a declared route that returns a view or routes(), with the same principle as the above examples.
Payment Drivers
Payment Drivers
What is the payment driver interface in my payment driver?
All payment gateway drivers must implement the Aero\Payment\Contracts\PaymentDriverinterface.
The Aero\Payment\PaymentDriverabstract class can be used as the starting point, which supports the ECOMoperation mode by default.
It's important to register the payment driver with the payment processor, which is used by the checkout and admin modules. This should be done in a ServiceProviderusing the following call:
\Aero\Payment\PaymentProcessor::registerDriver('my_gateway', \Acme\PaymentDriver::class);
What are the capturing methods in my payment driver?
here are two types of capturing methods:
- Automatic– the payment is both authorised and captured when the customer clicks the pay button - Manual– the payment is authorised when the customer clicks the pay button, the funds are ring-fenced and captured at a later date
How do I cancel a payment in my payment driver?
If capturing of the payment is done at a later stage to the initial authorisation, the payment gateway should support the ability to cancel the transaction. The driver should therefore implement a cancelmethod.
The cancelmethod should return a Aero\Payment\Responses\PaymentResponseinstance.
public function cancel(Payment $payment)
{
$response = new \Aero\Payment\Responses\PaymentResponse();
// request to cancel the transaction using the 3rd party API
$capture = AcmePaymentsInc::cancelTransaction($payment->reference);
// check if the transaction status is "cancelled"
if ($capture->status !== AcmePaymentsInc::STATUS_CANCELLED) {
// set an error on the response
$response->setError('The transaction could not be cancelled.');
return $response;
}
// internally mark the aero payment as cancelled
$payment->cancel();
// mark the response as successful
$response->setSuccessful(true);
return $response;
}
How do I capture a payment in my payment driver?
If the gateway provides the option to defer capturing the payment (authorising and capturing are performed at different times), the capturemethod should be implemented. This captured amount may be equal to or less than the authorised amount, and is always passed to the method in the lowest form of currency (pennies, cents, etc.).
The capturemethod should return a Aero\Payment\Responses\PaymentResponseinstance.
public function capture(int $amount, Payment $payment)
{
$response = new \Aero\Payment\Responses\PaymentResponse();
// request to capture the transaction using the 3rd party API
$capture = AcmePaymentsInc::captureTransaction($payment->reference, $amount);
// check if the transaction status is "captured"
if ($capture->status !== AcmePaymentsInc::STATUS_CAPTURED) {
// update the status of the aero payment to "failed"
$payment->update([
'state' => \Aero\Payment\Models\Payment::FAILED,
]);
// set an error on the response
$response->setError('The transaction failed to be captured.');
return $response;
}
// internally capture the amount for the payment
$payment->capture([
'amount' => $amount,
]);
// mark the response as successful
$response->setSuccessful(true);
return $response;
}
Aero will attempt to auto-capture any authorised payments against an order when the order is marked as dispatched.
How do I refund a payment in my payment driver?
If the payment gateway provides the ability to externally make refund requests, the payment driver can use the Aero\Payment\SupportsRefundingtrait.
The refundmethod should then be implemented, which returns an instance of Aero\Payment\Responses\PaymentResponse.
public function refund(int $amount, Payment $payment)
{
$response = new \Aero\Payment\Responses\PaymentResponse();
// request to refund the transaction using the 3rd party API, with a given amount
$capture = AcmePaymentsInc::refundTransaction($payment->reference, $amount);
// check if the transaction status is "refunded"
if ($capture->status !== AcmePaymentsInc::STATUS_REFUNDED) {
// set an error on the response
$response->setError('The transaction could not be refunded.');
return $response;
}
// internally refund the amount for the aero payment
$payment->refund([
'amount' => $amount,
]);
// mark the response as successful
$response->setSuccessful(true);
return $response;
}
What are operating modes in my payment driver?
There are two modes a gateway can operate:
ECOM– a standard ecommerce online transaction, typically originating from the storefront -MOTO– a transaction that is taken via telephone or through the mail, typically originating from the backend admin
As the processing environment differs when the customer isn't present (i.e. no 3D-secure for telephone orders), stores may require different merchant credentials for each mode.
ECOM mode
In order to indicate the gateway supports processing storefront orders, ensure the Aero\Payment\SupportsEcomModetrait is used.
MOTO mode
In order to indicate the gateway supports processing telephone and mail orders, ensure the Aero\Payment\SupportsMotoModetrait is used.
How do I support express checkout in my payment driver?
If the payment gateway stores customer information, this can be used to populate the contact information, billing and/or shipping address.
To specify that the payment gateway supports the express checkout flow, use the Aero\Payment\OffersExpressCheckouttrait.
Since the express flow may not always be selected, the payment gateway must indicate if the customer is using the express checkout. This could be achieved by storing the status in their session:
public function isExpress(): bool
{
return session()->has('acme_using_express_checkout');
}
Can I restricting availability of my payment driver?
It is likely that a payment gateway could have restrictions on when it can be used. For example, a finance based provider may require a certain order total amount, be restricted by geographical location or the items being purchased.
To determine if the gateway should be offered for the customer's session, use the availableForCartmethod within the payment driver:
public function availableForCart(Cart $cart): bool
{
$minAmount = setting('my_gateway.min_order_amount');
if ($minAmount !== null) {
return $cart->total()->inc()->value >= $minAmount;
}
return true;
}
How do I return a payment response in my payment driver?
To unify all payment gateway driver communication, an Aero\Payment\Responses\PaymentResponseclass is used. This common class must be returned from the register, complete, capture, refundand cancelmethods.
Setting the payment view
Many payment gateways use an iframe or JavaScript code to bridge the store with the gateway provider. In order to output this to the customer on the checkout, or telephone operator for MOTO payments, the PaymentResponsecan define a view to output. To do so, pass the view to the setViewmethod. If no view is set, a JSON response will be returned.
Passing data to the payment view
If there is data to be passed to the view or JSON response, the setDatamethod should be used.
Marking as successful
To indicate that the action carried out in the method was successful, then the response should be flagged as successful calling the setSuccessfulmethod on the PaymentResponse.
Adding error messages
If an error occurred during the action, these messages can be passed to the PaymentResponseusing the setErrormethod.
Redirecting the user
If further action is required where the user needs to be redirected to a different URL (to complete 3D-secure on the card issuers website for example), a redirect can be set using the setRedirectmethod.
How do I register the transaction in my payment driver?
When a payment method is selected that uses the gateway driver, the registermethod is called. Within this method, the bootstrapping of the transaction should be carried out. This may involve connecting to the payment gateway's API to register the transaction. For example, the gateway may need to know the total amount to request from the customer's card, along with where to redirect the browser once the transaction has been processed.
The registermethod should return a Aero\Payment\Responses\PaymentResponseinstance.
public function register()
{
// make an API call to create the transaction with Acme Payments (the fake 3rd party gateway company)
$request = AcmePaymentsInc::createTransaction([
'merchant_reference' => $this->order->reference,
'amount' => $this->order->total_rounded,
'currency' => $this->order->currency_code,
'billing_address' => [
'line_1' => $this->order->billingAddress->line_1,
'postcode' => $this->order->billingAddress->postcode,
'country' => $this->order->billingAddress->country_code,
],
'return_url' => route("payments-{$this->getMerchantMode()}.complete-payment"),
]);
$response = new \Aero\Payment\Responses\PaymentResponse();
// set the response as successful if the transaction status is "created"
$response->setSuccessful($request->status === AcmePaymentsInc::STATUS_CREATED);
// set the data to pass to the view
$response->setData([
'transaction_reference' => $request->reference,
]);
// set the view to use (this is typically an iframe or JavaScript)
$response->setView("acme-payments::{$this->getMerchantMode()}-form");
return $response;
}
The callback URL(s) may differ depending on the operating mode. If the driver supports both ECOMand MOTOmodes, the getMerchantModemethod can be used:
$request = AcmePaymentsInc::createTransaction([
// ...
'return_url' => route("payments-{$this->getMerchantMode()}.complete-payment"),
]);
How do I complete the transaction in my payment driver?
Once the payment gateway has handled the customer inputting their secure details (such as card information), they will most likely be redirected back to the retailer's store. On doing so, the completemethod is called on the payment driver, allowing for the transaction to be validated and capturing of the payment.
The completemethod should return a Aero\Payment\Responses\PaymentResponseinstance.
public function complete()
{
$reference = $this->request->input('reference');
// don't continue if a reference isn't provided
abort_unless($reference, 404);
$response = new \Aero\Payment\Responses\PaymentResponse();
// the order total
$amount = $this->order->total_rounded;
// obtain the transaction from the 3rd party API
$request = AcmePaymentsInc::getTransaction($reference);
// validate if the currency matches
if ($request->currency !== $this->order->currency_code) {
$response->setError('Currency does not match.');
return $response;
}
// validate if the amount matches
if ($request->amount !== $this->order->total_rounded) {
$response->setError('Total amount does not match.');
return $response;
}
// validate if the transaction status is "authorized"
if ($request->status !== AcmePaymentsInc::STATUS_AUTHORIZED) {
$response->setError('The transaction was not authorized.');
return $response;
}
// create the payment record in the aero database
$payment = $this->order->payments()->updateOrCreate([
'reference' => $reference,
], [
'id' => (string) \Illuminate\Support\Str::uuid(),
'payment_method_id' => $this->method->getKey(),
'state' => \Aero\Payment\Models\Payment::AUTHORIZED,
'amount' => $amount,
'currency_code' => $this->order->currency->code,
'exchange_rate' => $this->order->currency->exchange_rate,
'merchant_mode' => $this->getMerchantMode(),
]);
// request to capture the transaction using the 3rd party API
$capture = AcmePaymentsInc::captureTransaction($reference, $amount);
// check if the transaction status is "captured"
if ($capture->status !== AcmePaymentsInc::STATUS_CAPTURED) {
// update the status of the aero payment to "failed"
$payment->update([
'state' => \Aero\Payment\Models\Payment::FAILED,
]);
// set an error on the response
$response->setError('The transaction failed to be captured.');
return $response;
}
// internally capture the amount for the payment
$payment->capture([
'amount' => $amount,
]);
// mark the response as successful
$response->setSuccessful(true);
return $response;
}
How do I display payment icons on checkout in my payment driver?
To improve brand awareness of the payment provider during the checkout process, a label view can be used. The payment driver should implement the labelView method, which returns a string. This is typically a namespaced view that lives in the payment gateway module.
public function labelView(PaymentMethod $method): ?string
{
return view('my_gateway::label', compact('method'));
}
How do I restrict the availability of my payment driver?
It is likely that a payment gateway could have restrictions on when it can be used. For example, a finance based provider may require a certain order total amount, be restricted by geographical location or the items being purchased.
To determine if the gateway should be offered for the customer's session, use the availableForCartmethod within the payment driver:
public function availableForCart(Cart $cart): bool
{
$minAmount = setting('my_gateway.min_order_amount');
if ($minAmount !== null) {
return $cart->total()->inc()->value >= $minAmount;
}
return true;
}
Payment Gateways
Payment Gateways
What are the available payment gateways?
A cash payment method is provided out of the box, which allows developers to complete orders through the checkout without having to worry about installing and configuring a payment gateway.
The cash payment method should be disabled before the store is put live.
The following payment methods are built into the platform:
- Cash - Gift Voucher - Paymentless (for when no payment is needed)
Supported Gateways
If you are a payment gateway interested in partnering with Aero, or the one you're looking for isn't listed below, then please contact us to discuss further.
Aero offers and maintains the following gateway integrations, with more being added all the time.
Some gateways also support additional functionality which is indicated by the icons below. You - or your retailer - should consult with the gateway regarding any extra requirements needed from them in order to use these features.
💾 Supports saving of card details
Customers can save their card details and re-use them on future orders. This is typically done via generating a payment token which identifies the card to the gateway, and no sensitive card information is collected or stored on your server.
➕ **Supports additional payments
**An Aero feature allowing you to collect additional payments via our MOTO flow for orders which have not been completed by the customer or that have been modified, leading to extra charges.
📞 Supports MOTO
MOTO orders are taken by mail or telephone and these drivers will allow you to submit "cardholder not present" payments through the Aero admin.
🔗 Supports Pay By Link
An Aero feature allowing you to email a link to your customer so that they can pay for their order online even if it was taken over the phone or had previously been abandoned
| Card Payments | Finance / BNPL |
|---|---|
| Adyen 💾Barclaycard ePDQBarclaycard Fuse 💾 ➕ 📞 🔗Barclaycard Smartpaycheckout.com 💾 📞DNA Payments 💾 📞Opayo PI 💾 ➕ 📞 🔗PayPal (including Advanced Credit / Debit Cards)SagePay (VSP Server) 📞 🔗Secure Trading (Trust Payments) 📞Stripe (including Klarna and Link) 💾 🔗Total Processing 💾 📞 🔗Tyl by NatWestWindcave 💾 📞Worldline 💾 📞Worldpay (Access / Worldwide Payment Gateway) 💾 📞 🔗 | ClearpayDeko NewpayDuologiHumm GroupKlarnaOmni CapitalV12 FinanceOpen BankingBoodil 🔗 |
Apple Pay / Google Pay
Gateways supporting Apple Pay and Google Pay are as follows - please note additional integration such as domain verification and certificates, as well as Apple Pay and Google Pay accounts may be required in order to take payments through digital wallets.
Please ensure you are on the latest available version of your integration, and check the integration's README for further instructions on what is required.
| Driver | Apple Pay | Google Pay |
|---|---|---|
| Adyen * | Yes | Yes |
| Barclaycard Fuse | Yes | Yes |
| checkout.com | Yes | Yes |
| DNA Payments * | Yes | Yes |
| Opayo | Yes * | Yes |
| Secure Trading | Yes | No |
| Stripe * | Yes | Yes |
| Total Processing | Yes | Yes |
| Tyl by NatWest * | Yes | Yes |
| Windcave | Yes | Yes |
| Worldline * | Yes | Yes |
| Worldpay * | Yes | Yes |
-
- Support provided by gateway without requiring separate Apple Pay / Google Pay account. If the asterisk appears alongside the gateway name, this means the gateway supports this for both.
Other Available Gateways
We also have integrations available for the following gateways but do not officially support them as they have been built by third parties or have been deprecated. You can still install and use them but we will be unable to offer assistance if you experience issues:
- Biller / NOTYD (now only available in Belgium / Netherlands) - Payl8r (deprecated) - Zip (no longer supports UK merchants)
Need a payment gateway that isn't listed above?
See our "Introduction to payment drivers
How do I restrict payment methods?
There may be circumstances where a retailer wishes to restrict the payment methods that are available to a customer when checking out. Whilst a payment gateway may have restrictions, these may not be sufficient, or the limitations may need to apply to multiple methods.
The payment methods can be refined for a store, by adding a cart validator resolver, for example:
\Aero\Payment\Models\PaymentMethod::setCartValidator(function ($method, $cart) {
if ($method->driver === 'cash' && $cart->total()->inc()->value > 1000) {
return false;
}
return $method->getDriver()->validateForCart($cart);
});
Note that the validator falls back to the driver validation.
Php Traits
Php Traits
Aero\\Common\\Traits\\CanExtendFillable
This trait can be added to a model class. When added your model will have the ability to have fillables dynamically added. The trait adds the following methods:
static makeFillable Method
This method accepts a key for the attribute to make fillable.
Aero\\Common\\Traits\\CastsToJavaScript
This trait can be added to a model class. This trait provides a way of defining attributes that should be kept when casting to javascript. You can define these attributes in a protected $castsToJsarray. The trait adds the following methods:
static addCastToJs Method
This static method provides a way to dynamically add attributes to the models $castsToJsarray. It expects you to pass the attribute key to be added.
jsSerialize Method
This method returns the value of the attributesToJsmethod.
toJavaScript Method
This method returns the JSON value of the jsSerializemethod.
attributesToJs Method
This method returns an array of the attributes that should be casted to javascript.
getCastsToJs Method
This method returns the attribute keys that should be cast to javascript.
Aero\\Common\\Traits\\Sluggable
This trait can be added to a model class. The trait adds a relationship to Aero\Store\Models\Slugand helper methods for adding a slug to the model.
Aero\\Catalog\\Traits\\Taggable
This trait can be added to a model class. The trait adds the ability to add tags to the model. The trait adds the following methods:
tag Method
This method accepts a Aero\Catalog\Models\Tagand will attach the tag to the model without detaching any other tags.
tags Method
This method defines the Eloquent relationship with Aero\Catalog\Models\Tag.
untag Method
This method accepts a Aero\Catalog\Models\Tagand will detach the tag from the model without detaching any other tags.
Aero\\Common\\Traits\\CanHaveAdditionalAttributes
This trait can be added to a model class. When added your model will have the ability to have additional attributes. The trait adds the following methods:
additionals Method
This is the relationship method. This provides the relationship to Aero\Common\Models\AdditionalAttribute.
hasAdditional Method
This method accepts a key string parameter and will do a database query to return a boolean value if the additional attribute with the key you provided exists.
scopeHavingAdditional Method
This method is a Laravel local scope method that will allow you to scope a query on your module to require that the model has a specific additional attribute.
getAdditionalAttribute Method
This method accepts an additional attributes key and allows you to get an additional attributes value using its key.
saveAdditionalAttribute Method
This method accepts key and value parameters and will save the key and value you provide as an additional attribute.
Aero\\Catalog\\Traits\\HasPrices
This trait can be added to a model class. This trait adds prices to the model. The trait adds the following methods:
currentPrices Method
This method returns a collection of the current viable price based upon the date and time.
basePrice Method
This method returns the base price.
prices Method
This method defines the eloquent relationship with Aero\Catalog\Models\Price.
futurePrices Method
This method returns a collection of prices that are set for the future.
Aero\\Store\\Traits\\Seoable
This trait can be added to a model class. The trait adds a relationship to Aero\Store\Models\Seoand helper methods for adding SEO data to the model.
Aero\\Responses\\Traits\\HasSections
This trait can be added to a response builder class. It adds useful methods for managing sections of the page. There are other traits (HasBottomSections, HasTopSections, and HasSidebarSections) in the same namespace that add more section helpers (these traits all require that the class uses the HasSections trait as well). The trait adds the following methods:
getSectionKey Method
This method defines the key that is used to store the sections data on the response builder (defaults to ‘sections’).
setSection Method
This method will set a section. It accepts a key and value and optionally a section key (the section key will default to the getSectionKeymethod).
setSections Method
This method will set the sections. It accepts an array of sections and optionally a section key (the section key will default to the getSectionKeymethod).
getSection Method
This method will return a specified section. It accepts a key for the section and optionally a section key (the section key will default to the getSectionKeymethod).
getSections Method
This method will return a collection of the sections. It optionally accepts a section key (the section key will default to the getSectionKeymethod).
addSection Method
This method accepts a key and value to add to the sections. Using this method will put your section at the top of the sections collection.
addSectionBefore Method
This method works the same way as the addSectionmethod but also accepts a key for the section that this new section should be added before.
addSectionAfter Method
This method works the same way as the addSectionmethod but also accepts a key for the section that this new section should be added after.
removeSection Method
This method accepts a key of the section to remove and optionally accepts a section key (the section key will default to the getSectionKeymethod).
hasSections Method
This method returns if there are sections. It optionally accepts a section key (the section key will default to the getSectionKeymethod).
Aero\\Admin\\Traits\\HasPermissions
This trait adds the ability to add permissions to a class easily. The trait adds the following methods:
permissions Method
This method accepts an array that will set the permissions required.
allowed Method
This method optionally accepts a user and will return if the user has the required permissions.
Aero\\Admin\\Traits\\IsExtendable
This trait can be added to a class. This trait can be used on Pipelines (more information "introduction to pipelines
Pipelines
Pipelines
What are the available pipelines
| Class | Description | Payload |
|---|---|---|
| CartItemBuilder | The process that occurs when adding an item to the cart. Extending this class allows for manipulation of the cart item before it is saved to the cart. | Aero\Cart\CartItem $item |
| ContentForHead | The HTML content that will be inserted into the tag of the page. | String $content |
| ContentForBody | The HTML content that will be inserted just before the closing tag of the page. | String $content |
| RobotsTxt | The robots.txt file. For more information, click here. | Illuminate\Support\Collection $content |
How do I extend pipelines?
The payload data can be altered using the **extend()**method. This is typically done from within a Service Provider. If the code is unique to a particular store it could be placed in the **boot()**method of the AppServiceProvider. If it is to be included as part of a module, then it can be added to the setup method of the module's Service Provider.
For example, if a module needs to add an option to items to flag them as requiring gift wrap, the CartItemBuilderpipeline class should be extended in the module's ServiceProvideras shown below:
<?php
namespace Acme\MyModule;
use Aero\Cart\CartItemOption;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Store\Pipelines\CartItemBuilder;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
CartItemBuilder::extend(function ($item) {
if (request()->has('gift_wrap')) {
$option = CartItemOption::create('Gift wrap');
$item->addOption($option);
}
});
}
}
To adjust a payload which is a simple string, ensure that the payload variable is passed by reference so that it can be directly modified:
\Aero\Store\Pipelines\ContentForHead::extend(function (&$content) {
$content .= "";
// or ...
$content .= view('my_module::script-snippet');
});
Resource Lists
Resource Lists
How do I create a custom bulk action?
If we wish to add a new bulk action to a screen within the admin, all we have to do is create the bulk action using the BulkActionfacade. The bulk action also requires a specific Resource List, for example for Products.
BulkAction::create(BulkActionClass::class, ResourceList::class)
->permissions('bulkaction.permission')
->title('Title');
The above code would be instantiated within a Service Provider, whether it is a module service provider or the AppServiceProvider.
Example:
If we were to create a new bulk action for exporting products, we would first need to create a class extending BulkActionJob:
BulkAction::create(ExportProducts::class, ProductsResourceList::class)
->notRunnable()
->permissions('products.export')
->title('Export products');
Properties
Bulk Actions have a number of properties, each responsible for affecting a different aspect of the bulk action.
Wide Content
We can choose to display the contents of our bulk action in a wider container, which can prove to be more useful for certain modules. In order to display its contents in a container stretched to the width of the screen, apply the following function to the facade while creating it:
BulkAction::create(BulkActionClass::class, ResourceList::class)
->wideContent()
->permissions('bulkaction.permission')
->title('Title');
Runnable & Not Runnable
Not every bulk action is the same, and it’s possible to remove the Runbutton from the bulk action in order to replace it with a custom button, for example calling the API. In order to remove the built-in Runbutton for a particular bulk action, apply the following function to the facade while creating it:
BulkAction::create(BulkActionClass::class, ResourceList::class)
->notRunnable()
->permissions('bulkaction.permission')
->title('Title');
Confirm
We can set the bulk action to prompt the user before performing its intended action by simply chaining a method to the BulkAction creation:
BulkAction::create(BulkActionClass::class, ResourceList::class)
->confirm()
->permissions('bulkaction.permission')
->title('Title');
The prompt will now ask the user if they are sure of their selection.
Title
We can set a custom title/main heading for the bulk action content, this can be done by chaining a function to the facade we use for creating the bulk action, e.g.:
BulkAction::create(BulkActionClass::class, ResourceList::class)
->title('Your title')
->permissions('bulkaction.permission')
->title('Title');
View
Aero has the ability to display a specific view when it performs the bulk action, this can be done by chaining a function to the facade we use for creating the bulk action, e.g.:
BulkAction::create(BulkActionClass::class, ResourceList::class)
->view(‘namespace::bulk.form’)
->permissions('bulkaction.permission')
->title('Title');
What are the default resource lists?
The following classes are in the Aero\Admin\ResourceListsnamespace and any class that doesn’t have a view uses the admin::resource-lists.indexview.
| Class | View |
|---|---|
| AttributeGroupsResourceList | – |
| BlocksResourceList | – |
| CategoriesResourceList | admin::catalog.categories.index |
| CollectionsResourceList | – |
| CombinationsResourceList | – |
| CountriesResourceList | – |
| CurrenciesResourceList | – |
| CustomerGroupsResourceList | – |
| CustomersResourceList | – |
| CustomerTaxGroupsResourceList | – |
| DiscountsResourceList | – |
| FulfillmentMethodsResourceList | – |
| FulfillmentsResourceList | – |
| MailNotificationsResourceList | – |
| ManufacturersResourceList | – |
| MissingPagesResourceList | – |
| OrdersResourceList | – |
| OrderStatusesResourceList | – |
| PagesResourceList | – |
| PaymentMethodsResourceList | – |
| PriceListsResourceList | – |
| ProductsResourceList | admin::catalog.products.index |
| ProductTaxGroupsResourceList | – |
| RedirectsResourceList | admin::content.redirects.index |
| ShippingMethodsResourceList | – |
| SubscriptionPlansResourceList | – |
| SubscriptionsResourceList | – |
| TagGroupsResourceList | – |
| TaxRatesResourceList | – |
| TaxRulesResourceList | – |
| UsersResourceList | – |
| VouchersResourceList | – |
How do I create my own resource list?
To create a resource list you need a normal Laravel controller and a class that extends Aero\Admin\ResourceListsAbstractResourceList.
<?php
namespace Acme\MyModule\ResourceLists;
use Aero\Admin\ResourceLists\AbstractResourceList;
class OrdersResourceList extends AbstractResourceList
{
//
}
Your controller should be like this example but for your new resource list:
<?php
namespace Acme\MyModule\Http\Controllers;
use Aero\Admin\Http\Controllers\Controller;
use Aero\Admin\ResourceLists\OrdersResourceList;
use Illuminate\Http\Request;
class OrdersController extends Controller
{
public function index(OrdersResourceList $list, Request $request)
{
return view('admin::resource-lists.index', [
'list' => $list = $list(),
'results' => $list->apply($request->all())
->with('status', 'customer', 'items', 'currency')
->paginate($request->input('per_page', 24) ?? 24),
]);
}
}
Adding Columns to your Resource List
To add columns to your resource list you need to implement the protected **columns()**method and make it return an array of your columns. The entries in the array should use the Aero\Admin\ResourceLists\ResourceListColumn::createhelper method. This method expects a string parameter for the columns heading, a closure parameter for how to render the columns data (this can return a string), and an optional string parameter for the columns key (by default this is generated from the columns header).
<?php
namespace Acme\MyModule\ResourceLists;
use Aero\Admin\ResourceLists\AbstractResourceList;
use Aero\Admin\ResourceLists\ResourceListColumn;
class OrdersResourceList extends AbstractResourceList
{
protected function columns(): array
{
return [
ResourceListColumn::create('Order', function ($row) {
return view('admin::resource-lists.link', [
'route' => route('admin.orders.view', array_merge(request()->all(), ['order' => $row])),
'text' => '#'.$row->reference,
])->render();
}),
];
}
}
Making Columns Searchable
You can use the setSearchActionmethod to define how your column is searched. This method accepts a closure (that is passed the query and search term) that defines how the search should happen and an optional string for the text to show in the search field dropdown (this is column header by default).
ResourceListColumn::create('Order', function ($row) {
return view('admin::resource-lists.link', [
'route' => route('admin.orders.view', array_merge(request()->all(), ['order' => $row])),
'text' => '#'.$row->reference,
])->render();
})
->setSearchAction(function ($query, $term) {
$searchTerm = ltrim($search, '#');
$query->whereLower('reference', 'like', "%{$searchTerm}%");
}, 'Order Reference')
Making Columns Invisible
You can use the invisiblemethod to make your column invisible. The main reason for wanting to do this is to add a search for a column that shouldn’t be rendered.
ResourceListColumn::create('Order', function ($row) {
return view('admin::resource-lists.link', [
'route' => route('admin.orders.view', array_merge(request()->all(), ['order' => $row])),
'text' => '#'.$row->reference,
])->render();
})
->invisible()
Handling Searching for your Resource List
When your resource list is searched without a specific column being selected a **handleSearch()**function will be called to handle the search. The method will be passed the search term and you are able to use $this->queryto access the query.
<?php
namespace Acme\MyModule\ResourceLists;
use Aero\Admin\ResourceLists\AbstractResourceList;
class OrdersResourceList extends AbstractResourceList
{
protected function handleSearch($search)
{
$searchTerm = ltrim($search, '#');
$this->query->whereLower('reference', 'like', "%{$searchTerm}%");
}
}
Adding Row Views to your Resource List
Row views are additional rows rendered after each complete row (the best example of this is how the order items are listed after each row of the order on the orders page). To add a row view you simply need to provide your view in the protected $rowViewsarray.
<?php
namespace Acme\MyModule\ResourceLists;
use Aero\Admin\ResourceLists\AbstractResourceList;
class OrdersResourceList extends AbstractResourceList
{
protected $rowViews = [
'admin::resource-lists.orders.items',
];
}
Adding Buttons to your Resource List Header
Most resource list pages will require a create button. You can easily add buttons to the top of your resource list page by defining a header slot. Once you have defined a header slot you can inject your view using the admin slots helper.
<?php
namespace Acme\MyModule\ResourceLists;
use Aero\Admin\ResourceLists\AbstractResourceList;
class OrdersResourceList extends AbstractResourceList
{
protected $headerSlot = 'my-list.index.header.buttons';
}
Adding Admin Filters to your Resource List
First you will need to create an Admin Filter class as shown in the artile "How do I create a custom admin filter?
To register your newly created admin filter with your resource list you need to ensure that your admin filter class is in the resource lists protected static $filters array.
<?php
namespace Acme\MyModule\ResourceLists;
use Aero\Admin\Filters\Order\OrderStatusAdminFilter;
use Aero\Admin\ResourceLists\AbstractResourceList;
class OrdersResourceList extends AbstractResourceList
{
protected $filters = [
OrderStatusAdminFilter::class,
];
}
Adding Sort Bys to your Resource List
To add sort bys to your resource list you need to implement the protected **sortBys()**method and make it return an array of your sort bys. The entries in the array should use the **Aero\Admin\ResourceLists\ResourceListSortBy::create()**helper method. This method expects an array parameter holding the dropdown options that will be shown in the frontend dropdown, and a closure that will be executed when the sort option is active.
<?php
namespace Acme\MyModule\ResourceLists;
use Aero\Admin\ResourceLists\AbstractResourceList;
use Aero\Admin\ResourceLists\ResourceListSortBy;
class OrdersResourceList extends AbstractResourceList
{
protected function sortBys(): array
{
return [
ResourceListSortBy::create([
'order-az' => 'Order A to Z',
'order-za' => 'Order Z to A',
], function ($sortBy, $query) {
return $sortBy === 'order-az' ? $query->orderBy('reference') : $query->orderByDesc('reference');
}),
];
}
}
If you would like your sort by to be enabled by default then you call pass null for the first parameter like this:
<?php
namespace Acme\MyModule\ResourceLists;
use Aero\Admin\ResourceLists\AbstractResourceList;
use Aero\Admin\ResourceLists\ResourceListSortBy;
class OrdersResourceList extends AbstractResourceList
{
protected function sortBys(): array
{
return [
ResourceListSortBy::create(null, function ($sortBy, $query) {
return $query->orderByRaw('coalesce(`orders`.`ordered_at`, `orders`.`created_at`) desc');
}),
];
}
}
How do I extend an exisiting resource list?
Adding a New Column
To add columns to a resource list you can use the addColumn, addColumnBefore, or addColumnAftermethods from within the pipeline. These methods return a usual resource list column which allows you to chain on the other available column methods.
addColumn Method
This method accepts a column header and a closure that defines how the column will be rendered. A column added with this method will be added as the first column.
addColumnBefore Method
This method accepts the same parameters as the addColumnmethod but additionally accepts a third parameter, the key of the column this new column should be added before.
addColumnAfter Method
This method accepts the same parameters as the addColumn method but additionally accepts a third parameter, the key of the column this new column should be added after.
Finding the Current Column Keys
If you do not know the keys of the current columns you can use this code snippet to dump the current keys out.
<?php
namespace Acme\MyModule;
use Aero\Admin\ResourceLists\OrdersResourceList;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
dd((app(OrdersResourceList::class)())->getColumns()->map->key());
}
}
<?php
namespace Acme\MyModule;
use Aero\Admin\ResourceLists\OrdersResourceList;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
OrdersResourceList::extend(function (OrdersResourceList $list) {
$list->addColumn('My Column', function($row) {
return 'Example';
});
$list->addColumnBefore('Before Ref', function($row) {
return $row->reference;
}, 'order');
$list->addColumnAfter('After Ref', function($row) {
return $row->reference;
}, 'order');
});
}
}
Adding a New Filter
To add a filter to a resource list you can use the addFiltermethod from within the pipeline. This method expects you to pass an admin filter class. You can create an admin filter class as shown in the article "How do I create a custom Admin Filter?
<?php
namespace Acme\MyModule;
use Acme\MyModule\Filters\CreatedAtAdminFilter;
use Aero\Admin\ResourceLists\OrdersResourceList;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
OrdersResourceList::extend(function (OrdersResourceList $list) {
$list->addFilter(CreatedAtAdminFilter::class);
});
}
}
Adding a New Sort By
To add a sort by to a resource list you can use the addSortBymethod from within the pipeline. This method expects you to pass a Aero\Admin\ResourceLists\ResourceListSortBy. You can create a resource list sort by class as shown in the article "How do I create my own Resource List?
<?php
namespace Acme\MyModule;
use Aero\Admin\ResourceLists\OrdersResourceList;
use Aero\Admin\ResourceLists\ResourceListSortBy;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
OrdersResourceList::extend(function (OrdersResourceList $list) {
$list->addSortBy(ResourceListSortBy::create([
'latest' => 'Latest',
'oldest' => 'Oldest',
], function ($sortBy, $query) {
return $sortBy === 'oldest' ? $query->orderBy('created_at') : $query->orderByDesc('created_at');
}));
});
}
}
What is a bulk action?
A bulk action allows for the selection of multiple values from a list, and apply some logic to items that have only been selected. Bulk actions exist for many different Resource Lists, which is what is used for determining items that have been selected.
Bulk actions are usually dispatched as a job, given the bulk nature of the action, which carries out tasks in a sequential manner. There are exceptions to this rule however and Aero can also return responses straight to the browser, e.g. downloading all selected content blocks as a .zip.
How do I add a custom column to the product list?
As the products list is a resource list you can add a custom column like this:
More information on resource lists can be found in the article "How do I extend an existing Resource List?
<?php
namespace Acme\MyModule;
use Aero\Admin\ResourceLists\ProductsResourceList;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
ProductsResourceList::extend(function (ProductsResourceList $list) {
$list->addColumn('My Column', function ($row) {
return $row->id;
});
});
}
}
How do I add a custom column to the order list?
As the orders list is a resource list you can add a custom column like this:
More information on resource lists can be found in the article "How do I extend an existing Resource List?
<?php
namespace Acme\MyModule;
use Aero\Admin\ResourceLists\OrdersResourceList;
use Aero\Common\Providers\ModuleServiceProvider;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
OrdersResourceList::extend(function (OrdersResourceList $list) {
$list->addColumn('My Column', function ($row) {
return '#'.$row->reference;
});
});
}
}
Response Builders
Response Builders
How do I access the properties of the response builder?
All response builders hold properties that were assigned to them upon creation from the request. These properties are typically the arguments that would be passed to a conventional Laravel controller and subsequently used in the code within the controller.
For example on the ProductPage, the Aero\Catalog\Models\Product modelfor the requested URL can be accessed as follows:
\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
$product = $page->product;
// do things with the $product model
});
How do I pass data to a response?
The most common task performed when extending responses, is setting additional data. Typically, for HTML responses that render from a view, this involves sending data to the view to be used as a variable. If the response returns JSON, it may be adding additional key/value pairs.
For example, if you needed to obtain customer reviews for a given product and output them on the product page, you could do the following, which passes a product to a 3rd party reviews API and then adds the reviews it returns to the response data, which is then passed to the product.twig view file ready to be loop around and outputted.
\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
$reviews = \Acme\ReviewsAPI::forProduct($page->product);
$page->setData('reviews', $reviews);
});
Another example is getting the latest 5 blog posts from a blog module and sending them to the homepage to be displayed:
\Aero\Store\Http\Responses\Homepage::extend(function ($page) {
$posts = \Acme\BlogPosts::latest()->limit(5)->get();
$page->setData('posts', $posts);
});
You can obtain the existing data set against the response builder using the **getData()**method and remove data using the **removeData()**method.
How do I redirect a response?
Sometimes you may wish to redirect the user to another location, either to an external site or within the store. To do so, pass the URL, route(), or an Illuminate\Http\RedirectResponseinstance to the **setRedirect()**method. For example, if you stored the age of customers (computed from their provided date of birth), and needed to prevent access to those under the age of 18:
\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
if ($page->request->customer()->age < 18) {
$page->setRedirect('/denied');
}
});
As extension code is evaluated in the order it's added to the response builder, code that runs after this step may override the redirect. To force a redirect and prevent execution of the code that follows in the pipeline, you should return an instance of the **redirect()**helper:
\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
if (! $page->product->canBeViewedByIp($page->request->ip())) {
return redirect('/denied');
}
});
How do I set the response status code?
There may be situations where the HTTP response status needs to be altered. By default, the returned status is 200, however, this can be changed by passing the new status code to the **setStatus()**method:
\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
$page->setStatus(403);
});
How do I set response headers?
When extending the response builder, you can set HTTP headers to be sent with the response:
\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
$page->setHeader('foo', 'bar');
});
How do I attach middlewear to a response builder?
Since Aero does not expose the underlying routes and controllers, middleware can be attached directly to the response builder. This allows for code to be executed before the main pipeline is run.
For example, to restrict all products from being accessible to guest visitors, you could register the authmiddleware to the ProductPageresponse:
\Aero\Store\Http\Responses\ProductPage::middleware('auth');
When assigning middleware, you can also pass the fully qualified class name:
\Aero\Store\Http\Responses\ProductPage::middleware(\App\Http\Middleware\CheckAge::class);
Alternatively, you can provide a Closure. For example, middleware could be added to the homepage to set a tracking cookie from the referrer:
\Aero\Store\Http\Responses\Homepage::middleware(function ($request, $next) {
return tap($next($request), function ($response) use ($request) {
$response->cookie('referer', $request->header('Referer'));
});
});
Refer to the Laravel documentation for more information on middleware.
How do I extend responses?
Adding additional code to run on the response is as simple as calling the **extend()**method on the response builder class. This is typically done from within a Service Provider. If the code is unique to a particular store it could be placed in the boot method of the AppServiceProvider. If it is to be included as part of a module, then it can be added to the setup method of the module's Service Provider.
For example, if a module needs to send a variable footo the product.twigview file for it to be outputted in the HTML, the ProductPageresponse builder class should be extended in the module's Service Provideras shown below:
<?php
namespace Acme\MyModule;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Store\Http\Responses\ProductPage;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
ProductPage::extend(function ($page) {
$page->setData('foo', 'bar');
});
}
}
When passing a Closureto the extend method, the $pageargument is the response builder instance that will process the response.
For more advanced use cases, you can pass the response builder on for further processing in order to return the Illuminate\Http\Responseinstance. This is useful in situations when you may wish to modify the response before it is passed to the browser. A common use case for this is to add cookies to the response:
\Aero\Store\Http\Responses\ProductPage::extend(function ($page, $next) {
$response = $next($page);
$response->cookie('foo', 'bar');
return $response;
});
Alternatively, a fully qualified class name can be passed to the **extend()**method, which reduces bloat in the Service Providerand provides a better indication of what the code is responsible for. When doing so, the class must implement a **handle()**method:
<?php
namespace App\Http\Extensions;
class AddFooVariableToProductPage
{
public function handle($page)
{
$page->setData('foo', 'bar');
}
}
\Aero\Store\Http\Responses\ProductPage::extend(AddFooVariableToProductPage::class);
Note that this code will only evaluate when Aero routes a request to the particular response builder, i.e. code that is set to run on the ProductPagewill not be processed on a request for the Homepage.
How do I change the response view?
For responses that return HTML, a Twig view file is used. Whilst these are originally defined, the view can be changed. For example, to change the product page view based on product data:
\Aero\Store\Http\Responses\ProductPage::extend(function ($page) {
if ($view = $page->product->additional('view')) {
$page->setView($view);
}
});
Setting the view to nullwill result in a JSON response.
You can get the current view for a response using $page->getView().
What are the available responses from the response builder?
Core
| Class | Description | View |
|---|---|---|
| Homepage | The homepage of the store. | homepage.twig |
| ProductPage | The product page providing information on a particular product. | product.twig |
| ListingsPage | The listings page which displays available products for the particular criteria defined against the URL | listings.twig |
| ListingsJson | The JSON response of listings for the particular criteria defined against the URL. | - |
| SearchPage | The search results for a particular search term. | search.twig |
| SearchJson | The JSON response of search results for a particular search term. | - |
| CartPage | The cart summary page, where customers can see an overview of the items in their cart and update quantities. | cart.twig |
| CartItemAdd | Actions to take when an item is added to the cart. | - |
| CartItemUpdate | Actions to take when an item is updated in the cart. | - |
| CartEmpty | Actions to take when the cart is emptied. | - |
| CartDiscountCodeApply | Actions to take when a discount code is applied to the cart. | - |
| CartDiscountCodeRemove | Actions to take when a discount code is removed from the cart. | |
| InformationPage | A static page used to display information, for example the About Us or Terms & Conditions. | information-page.twig |
| Error404Page | The 404 page that displays when a page does not exist. | errors/404.twig |
| FormSubmit | Actions to take when a form is submitted. | - |
| AccountOverviewPage | A static page used to display account overview information. | account/account-overview.twig |
Admin
Catalog
| Class | Description | View |
|---|---|---|
| AdminDashboardPage | Used to display the admin dashboard. | admin/dashboard.blade.php |
| AdminCategoryCreatePage | Used to display the category creation page in the admin. | admin/catalog/categories/new.blade.php |
| AdminCategoryEditPage | Used to display the category edit page in the admin | admin/catalog/categories/edit.blade.php |
| AdminCategoryStore | Actions for creating a new category. | - |
| AdminCategoryUpdate | Actions for updating an existing category. | - |
| AdminCollectionCreatePage | Used to display the collection creation page in the admin. | admin/catalog/collections/index.blade.php |
| AdminCollectionEditPage | Used to display the collection edit page in the admin. | admin/catalog/collection/edit.blade.php |
| AdminCollectionStore | Actions for creating a new collection. | - |
| AdminCollectionUpdate | Actions for updating an existing collection. | - |
| AdminManufacturerCreatePage | Used to display the manufacturer creation page in the admin. | admin/catalog/manufacturers/new.blade.php |
| AdminProductCreatePage | Used to display the product creation page in the admin. | admin/catalog/products/new.blade.php |
| AdminProductStore | Action for creating a new product. | - |
| AdminProductUpdate | Actions for updating an existing product. | - |
Discounts
| Class | Description | View |
|---|---|---|
| AdminDiscountCreatePage | Used to display the discount creation page in the admin. | |
| AdminDiscountEditPage | Used to display the discount edit page in the admin. | admin/discounts/edit.blade.php |
| AdminDiscountStore | Actions for creating a new discount. | - |
| AdminDiscountUpdate | Actions for updating an existing discount. | - |
Orders
| Class | Description | View |
|---|---|---|
| AdminOrderViewPage | Used to display the order view page in the admin. | admin/orders/view.blade.php |
Settings
Settings
What are the settings available to developers?
Settings allow you to add dynamic values to your modules that can be modified by admin users easily through the admin.
The values of your settings (unless the value is the default you set) are stored in json files inside of your storage directory.
└─ storage
└─ app
└─ settings
Do do I set the default value of my custom setting?
You can add a default value to any setting by using the defaultmethod and passing your default value.
<?php
namespace Acme\MyModule;
use Aero\Common\Facades\Settings;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Common\Settings\SettingGroup;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
Settings::group('acme-my-module', function (SettingGroup $group) {
$group->string('denied_message')->default('Access Denied');
$group->array('allow_list')->default(['admin']);
$group->boolean('allow_list_enabled')->default(true);
});
}
}
How do I register custom settings?
To register settings you need to use the Aero\Common\Facades\Settingsfacade from the setupmethod of your modules service provider. You should call the groupmethod on the facade and pass in a unique name for your settings group and a closure that defines your settings group.
<?php
namespace Acme\MyModule;
use Aero\Common\Facades\Settings;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Common\Settings\SettingGroup;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
Settings::group('acme-my-module', function (SettingGroup $group) {
$group->string('denied_message');
$group->array('allow_list');
$group->boolean('allow_list_enabled');
});
}
}
How do I access a custom setting?
You can access a setting by using the settingfunction that is available in PHP, Blade, and Twig. You simply need to pass your setting group name, a full stop, and then the settings name.
setting(‘acme-my-module.allow_list’)
What are the available setting types?
| Setting | Description |
|---|---|
| $group->array(‘allow_list’); | Returns an array. |
| $group->boolean(‘enabled’); | Returns a boolean, true or false. |
| $group->eloquent('order_status', OrderStatus::class); | Stores the key of the eloquent model you pass and returns an instance of the model. |
| $group->encrypted('api_key'); | Stores a string encrypted and returns the string decrypted. |
| $group->float('min_value'); | Returns a number as a float. |
| $group->integer('per_page'); | Returns a number as an integer. |
| $group->string(‘message’); | Returns a string. |
| $group->date('go_live'); | Returns a Carbon date instance. |
| $group->dateRange('sale_period'); | Returns an array with start and end keys that hold a Carbon date instance. |
How do I configure the admin edit page?
By default your setting group is editable through the admin by a generated edit page. You cannot remove this generated edit page but you can stop users from being able to edit the settings using it. It’s important to note that if you disable editing through this edit page that users can still edit the settings manually through the storage json file.
To make your setting group not editable you should call the notEditablemethod when defining your group.
<?php
namespace Acme\MyModule;
use Aero\Common\Facades\Settings;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Common\Settings\SettingGroup;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
Settings::group('acme-my-module', function (SettingGroup $group) {
$group->string('denied_message');
$group->array('allow_list');
$group->boolean('allow_list_enabled');
$group->notEditable();
});
}
}
Validation Rules
These are helper methods that map to their equivalent Laravel Validation Rules
| Rule | Works For |
|---|---|
| $group->string('key')->required(); | All |
| $group->string('key')->between($min, $max); | String, Integer, Float, Array, Encrypted |
| $group->string('key')->min($min); | String, Integer, Float, Array, Encrypted |
| $group->string('key')->max($max); | String, Integer, Float, Array, Encrypted |
| $group->string('key')->size($size); | String, Integer, Float, Array, Encrypted |
| $group->string('key')->greaterThan($size); | String, Integer, Float, Array, Encrypted |
| $group->string('key')->lessThan($size); | String, Integer, Float, Array, Encrypted |
| $group->string('key')->in($needles); | String, Integer, Float |
| $group->string('key')->email(); | String, Encrypted |
| $group->string('key')->uuid(); | String, Encrypted |
| $group->string('key')->url(); | String, Encrypted |
| $group->string('key')->ip(); | String, Encrypted |
| $group->string('key')->startsWith($needle); | String, Encrypted |
| $group->string('key')->startsWithOneOf($needles); | String, Encrypted |
| $group->float('key')->step($step); | Float |
| $group->date('key')->beforeToday(); | Date |
| $group->date('key')->before($date); | Date |
| $group->date('key')->afterToday(); | Date |
| $group->date('key')->after($date); | Date |
Methods
| Method | For | Description |
|---|---|---|
| $group->title($title); | - | Set the title shown in the list of settings (by default this is generated from the group name). |
| $group->summary($summary); | - | Set the summary shown in the list of settings. |
| $group->notEditable(); | - | Disable editing through the generated admin page. |
| $group->string('key')->label($label); | All | Set the label for the edit field (by default this is generated from the settings key). |
| $group->string('key')->hint($hint); | All | Add a helpful hint to the edit field. |
| $group->string('key')->textarea(); | String | Make the string field appear as a textarea. |
| $group->string('key')->wysiwyg(); | String | Make the string field appear as a wysiwyg. |
| $group->eloquent('order_status', OrderStatus::class)->searchField($field); | Eloquent | Define the field used for searching in the searchable select. |
| $group->eloquent('order_status', OrderStatus::class)->searchFields($field); | Eloquent | Define multiple fields used for searching in the searchable select (first field is the value). |
| $group->array('key')->associative(); | Array | Define the array as an associative array - an array that has a key and value. |
| $group->array('key')->definition($definition); | Array | Set a custom definition for your array (link:section below this). |
Complex Array Definitions
If you have a complex array (an array of arrays that have multiple keys), you need to define the contents of the array and any validation for each key.
Here is a code example that defines an additional_checkboxes array that is an array of arrays. The child arrays have a text key (that has validation ensuring it is a string and required) and a required key (that has validation ensuring it is a boolean).
Although this example uses the string, required, and boolean rules you can use any Laravel Validation rule
<?php
namespace Acme\MyModule;
use Aero\Common\Facades\Settings;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Common\Settings\SettingGroup;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
Settings::group('acme-my-module', function (SettingGroup $group) {
$group->array('additional_checkboxes')->definition([
'text' => ['string', 'required'],
'required' => ['boolean']
]);
});
}
}
How do I handle translations?
When defining your setting groups title/summary, or a settings label/hint you can make use of Laravel Localization for translations.
<?php
namespace Acme\MyModule;
use Aero\Common\Facades\Settings;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Common\Settings\SettingGroup;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
Settings::group('acme-my-module', function (SettingGroup $group) {
$group->title(__('acme-my-module.title'));
$group->summary(__('acme-my-module.summary'));
$group->string('message')
->label(__('acme-my-module.labels.message'))
->hint(__('acme-my-module.hints.message'));
});
}
}
How do I add settings to the product model?
Using the same syntax as when you create settings
<?php
namespace Acme\MyModule;
use Aero\Catalog\Models\Product;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Common\Settings\SettingGroup;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
Product::settings('my-module', function (SettingGroup $group) {
$group->encrypted('password');
$group->boolean('require_password_to_view')->default(false);
});
}
}
To access your settings value you need to use the settings method on the product model, passing in the key for the setting you want.
<?php
namespace Acme\MyModule;
use Aero\Catalog\Models\Product;
use Aero\Common\Providers\ModuleServiceProvider;
use Aero\Common\Settings\SettingGroup;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
Product::settings('my-module', function (SettingGroup $group) {
$group->encrypted('password');
$group->boolean('require_password_to_view')->default(false);
});
$product = Product::first();
$password = $product->settings('my-module.password');
dd($password);
}
}
Validation
Validation
How do I create custom validation requests?
To create a custom validation request, we need to extend the AeroRequestabstract class that’s located in aerocommerce/core.
To create a custom request, open the terminal in the project root directory and type the following command:
php artisan make:request MyRequest
The above command generates a class file that we now have to modify to suit the needs of both AeroRequestand the data we’re working with. The first change is the class our brand new request extends, instead of FormRequest, we now need the class to extend AeroRequestfrom namespace App\Http\Requests.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MyRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
}
$address = $this->route('address');
return $address && $this->user()->can('update', $address);
The above code checks if a user has been permitted to update for a given route, in this case, the address.
The **rules()**function allows creating all the necessary rules for fields in a given request. The Laravel documentation on validation showcases ways of writing validation rules, alongside mentioning several useful dev tips.
Adding rules
If you wish to add rules to a brand new validation request, all we have to do is populate the array in the return of the **rules()**method.
For example, if we wanted to add validation rules for a basic contact form, they’d look like this:
**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'first_name' => 'required|string|max:50',
'last_name' => 'required|string|max:50',
'email' => ‘required|email',
'message' => 'required|string',
];
}
Adding messages
The messaged method will not be automatically scaffolded when creating a new request and we have to do this manually. To add custom messages for our validation errors, we simply create a new method under rules(), called **messages()**like so:
public function messages(): array
{
return parent::messages(); // TODO: Change the autogenerated stub
}
If we wish to set custom validation messages
public function messages(): array
{
return [
'email.required' => 'The email address is required!',
];
}
How do I use validation requests?
To use a custom validation request, we have to replace the request that is passed to the controller function we wish to affect.
Change from:
public function updateAddress(Request $request, Customer $customer)
Change to:
public function updateAddress(UpdateAddressRequest $request, Customer $customer)
How do I display validation error messages?
There are multiple ways of displaying your validation errors. From the backend perspective, the request always returns errors to the view if validation had not passed.
Alert partial view
Aero has an error partial which automatically displays all the validation errors, provided that it is included in the view we want validation errors to display. To achieve this, simply paste this code under any @extendsblade directive:
@include('admin::partials.alerts')
Admin
In the admin, we’d use Bladeto display all validation errors at once like so:
@if($errors->isNotEmpty())
<ul class="msg msg-error">
@foreach($errors->all() as $error)
<li class="font-bold">{{ $error }}</li>
@endforeach
</ul>
@endif
On the other hand, if we wish to display an error only for a specifed field - in this case firstname, we’d use:
@error('firstname')
<div class="error">{{ $message }}</div>
@enderror
Store
In the frontend, we’re using Twigto display errors from a request. If we wish to display all validation errors, we’d use something along the lines of:
{% if errors %}
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
<ul>
{% endif %}
If we wish to display an error for a particular field, in this case email:
{% if errors.has('email') %}
{{ errors.first('email') }}
{% endif %}
What are the available validation requests?
Example:
<?php
namespace Aero\Store\Http\Requests;
use Aero\Common\Requests\AeroRequest;
class UpdateAddressRequest extends AeroRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$address = $this->route('address');
return $address && $this->user()->can('update', $address);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'company' => 'nullable|string|max:255',
'line_1' => 'required|string|max:255',
'line_2' => 'nullable|string|max:255',
'city' => 'required|string|max:255',
'zone_name' => 'nullable|string|max:255',
'postcode' => 'required|string|max:255',
'country_code' => 'required|string|size:2|exists:countries,code',
'mobile' => 'nullable|string|max:20',
'phone' => 'nullable|string|max:20',
];
}
}
| ValidateOrderAddress | FilterSaveRequest |
|---|---|
| SeoFormRequest | UpdateSubscriptionPlanRequest |
| ValidateCartCustomer | ValidateGuestConversionRequest |
| ValidatePaymentMethod | ValidateShippingMethod |
| CreateAddressRequest | SearchRequest |
| UpdateAccountDetailsRequest | UpdateAccountPasswordRequest |
| UpdateAddressRequest | ValidateAccountDetails |
| ValidateAddress | ValidateEmail |
| ValidateLogin | ValidateRegister |
| ValidatePasswordReset | ValidatePasswordUpdate |
How do I set a custom order validator for shipping methods?
You can define logic that will be used to confirm if a shipping method is available for a specific order. By default all shipping methods that have available shipping rates (you can set a custom order validator for shipping rates too) for an order are available.
To set the logic you need to use the **\Aero\Cart\Models\ShippingMethod::setOrderValidator()**method. This method expects you to pass a closure that will return trueor false. The closure will be passed the shipping method and order to validate.
In this example code if the shipping method id is not 1, nothing will happen but if the shipping method id is 1 then the shipping method will only be available if the order is being placed on a Monday.
<?php
namespace Acme\MyModule;
use Aero\Cart\Models\Order;
use Aero\Cart\Models\ShippingMethod;
use Aero\Common\Providers\ModuleServiceProvider;
use Carbon\Carbon;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
ShippingMethod::setOrderValidator(function (ShippingMethod $method, Order $order) {
if ($method->id !== 1) {
return true;
}
return Carbon::now()->isDayOfWeek(1);
});
}
}
How do I set a custom order validator for shipping rates?
You can override the default logic that decides whether a shipping rate is applicable to an order. By default shipping rates are validated against their configuration set in the admin (allowed/disallowed tags, total weight, and total price).
To set the logic you need to use the **\Aero\Cart\Models\ShippingRate::setOrderValidator()**method. This expects you to pass a closure that will return true, false, or null(if null is returned then the default shipping rate logic is used). The closure will be passed the shipping rate and the order to validate.
In this example code if the shipping rate price is free then the shipping rate will be invalid unless the current day is Monday. If the shipping rate price is not free then nullis returned so that the normal shipping rate validation code runs.
<?php
namespace Acme\MyModule;
use Aero\Cart\Models\Order;
use Aero\Cart\Models\ShippingRate;
use Aero\Common\Providers\ModuleServiceProvider;
use Carbon\Carbon;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
ShippingRate::setOrderValidator(function (ShippingRate $rate, Order $order) {
if ($rate->price_inc === 0) {
return Carbon::now()->isDayOfWeek(1);
}
return null;
});
}
}
How do I use a custom total item price totalizer for a shipping rate?
Setting a custom item price totalizer allows you to define the logic used to get the total price of a collection of order items (this value will be used when validating that a shipping rate is available for an order).
To set the logic you need to use the **\Aero\Cart\Models\ShippingRate::setItemPriceTotalizer()**method. This method expects you to pass a closure that will return a numberfor the total price or null(if null is returned then the default total price logic is used). The closure will be passed the order items and the shipping rate.
In this example code if an order item costs less than 500 then the item will not be used in the calculation for the total price of the order items.
<?php
namespace Acme\MyModule;
use Aero\Cart\Models\OrderItem;
use Aero\Cart\Models\ShippingRate;
use Aero\Common\Providers\ModuleServiceProvider;
use Illuminate\Support\Collection;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
ShippingRate::setItemPriceTotalizer(function (Collection $items, ShippingRate $rate) {
$items = $items->filter(function (OrderItem $item) {
return ($item->total + $item->total_tax) <= 500;
});
return round($items->sum('total') + $items->sum('total_tax'));
});
}
}
How do I use a custom total item weight totalizer for a shipping rate?
Setting a custom item weight totalizer allows you to define the logic used to get the total weight of a collection of order items (this value will be used when validating that a shipping rate is available for an order).
To set the logic you need to use the **\Aero\Cart\Models\ShippingRate::setItemWeightTotalizer()**method. This method expects you to pass a closure that will return a numberfor the total price or null(if null is returned then the default total weight logic is used). The closure will be passed the order items and the shipping rate.
In this example code if an order item weighs less than 50 then the item will not be used in the calculation for the total weight of the order items.
<?php
namespace Acme\MyModule;
use Aero\Cart\Models\OrderItem;
use Aero\Cart\Models\ShippingRate;
use Aero\Common\Providers\ModuleServiceProvider;
use Illuminate\Support\Collection;
class ServiceProvider extends ModuleServiceProvider
{
public function setup()
{
ShippingRate::setItemWeightTotalizer(function (Collection $items, ShippingRate $rate) {
$items = $items->filter(function (OrderItem $item) {
return $item->total_weight <= 50;
});
return $items->sum('total_weight');
});
}
}