Below is information as to how you develop Shoporama themes
In general, a Shoporama theme consists of a series of HTML files located in a folder on our FTP server. The template language in the individual files is Smarty, as mentioned below.
In the individual templates, there is access to a range of objects and methods. You can read more about them in our Template API.
Contact us at support@shoporama.dk if you have any questions.
The engine in our template system is based on Smarty.
For backward compatibility reasons, we currently have version 2.6 installed, but we no longer maintain it and we recommend that everyone uses version 4.x. The version can be set under shop settings.
Please note that this documentation only applies to version 4.x.
The difference between standard Smarty and ours is that we use <{
and }
as delimiters. That means the syntax is:
<h1>Velkommen til <{$webshop->getName()|escape}></h1>
If you are unsure about what a variable is, or what a function returns, you can run it through var_dump
. If you have hold of an object, you will always be able to see which methods are available in our Template API.
You use var_dump
in the following way:
<{$order|var_dump}>
In some cases, it is relevant to ensure that if you use a variable as an object, it is also an object. Otherwise, the template will not work. We provide some global variables such as $product
and $webshop
and more. For those variables, it is not necessary. You can check in one of the following ways:
<{* Typisk tilstrækkelig *}> <{if $profile}> <{$profile->getName()}> <{/if}> <{* Lidt grundigere *}> <{if is_object($profile)}> <{$profile->getName()}> <{/if}> <{* Meget grundig *}> <{if is_a($profile, "SafeProfile")}> <{$profile->getName()}> <{/if}>
If you want to see which variables are available on the page you are developing, you can activate Smarty's debug mode and view the variables in a new window. Insert the following code into your template:
<{debug}>
Dive deeper into Smarty4 here: Smarty 4 Documentation.
Path | Function |
---|---|
/{navn} |
Main folder for your theme |
/{navn}/components |
Folder with components for Page Builder |
/{navn}/components/sections.json |
List of which sections can be inserted on which pages |
/{navn}/components/tags.json |
Tags to be used in theme |
/{navn}/components/sections |
Folder for the individual section files |
/{navn}/extensions |
Extensions to datatypes in Shoporama |
/{navn}/templates |
Folder to templates |
/{navn}/templates/mails |
Folder for email templates |
As a starting point, you are free to name the files in /{name}/templates
however you want. However, there are a few exceptions:
Path | Function | |||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
/{navn}/templates/global.html |
The main page that contains the structure for the entire shop. The actual content is inserted where this template shows <{$contents}> , which is a special variable that comes from the below index.html . In other words, a very simple global.html could look like this:
<!DOCTYPE html> <html> <head> <title><{$webshop->getName()|escape}></title> </head> <body> <nav> <!-- Din navigation --> </nav> <div> <{$contents}> </div> </body> </html> |
|||||||||||||||||||||||||||||||||||||||
/{navn}/templates/index.html |
This template is used to display the content of whatever you're looking at. That could be a product, a landing page, or the cart, and so on. A suggestion for how it could look is the following:
<{if $inc}> <{include file=$inc}> <{elseif $category}> <{include file="category.html"}> <{elseif $landing_page}> <{include file="landing_page.html"}> <{elseif $product}> <{include file="product.html"}> <{elseif $page}> <{include file="page.html"}> <{elseif $blog_post}> <{include file="blog_post.html"}> <{/if}> |
|||||||||||||||||||||||||||||||||||||||
/{navn}/templates/printed_invoice.html |
Used by admin if you want to print a packing slip. The content of the order will always be stored in the variable $order . |
|||||||||||||||||||||||||||||||||||||||
/{navn}/templates/mails |
Folder for mail templates. Should contain the following files:
General information about the structure of mail templatesBy default, all mail templates are divided into two parts. One part for the subject and the other part for the content. The way the template is divided is by checking if the variable <{if $subject}> Emnet på mailen <{else}> Indholdet af mailen <{/if}> However, with the exception of the two emails |
You are allowed to place your images, stylesheets, etc. wherever you want, but we recommend that you use a structure like
/{name}/images
and /{name}/css
. When referring to the files, use the variable
$theme_url
which contains the URL to the theme. Note that it can change, so you cannot always
manually enter the URL into your theme. But the variable will always work. If you want to link to the image
/{name}/images/img.gif
, you should therefore write
.
Shoporama contains various data types such as products and categories. These data types include predefined fields such as title, description, and price. It is possible to expand the most common data types with custom fields. Extensions should be placed in the theme in the file /{name}/extensions/{datatype}.json
, where the possible data types are product
, landing_page
, category
, static_page
, and blog_post
.
The json-file must contain an array
of groups. Each group contains a row of fields. The format is as this:
Field | Content | Type | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
name |
Group field name | string |
||||||||||||||||
description |
Longer description | string |
||||||||||||||||
fields |
Array of fields | array |
||||||||||||||||
|
Below is an example of json in the file extensions/product.json
:
[ { "name": "Billeder", "description": "Her kan du tilføje billeder", "fields": [ { "title": "Et enkelt billede", "id": "my-image", "type": "image" }, { "title": "En serie af billeder", "id": "my-images", "type": "images" } ] }, { "name": "Indstillinger", "description": "Herunder kan du indstille diverse", "fields": [ { "title": "my-setting", "description": "Vælg flere ..", "id": "tags", "type": "multi", "options": { "foo": "Foo", "bar": "Bar", "baz": "Baz" } } ] } ]
Will produce this interface:
On data types that support extensions, the values should be retrieved using the method getExtensionValue('ID')
,
where ID
is the ID from the JSON file. Depending on the data type of the field, the content can be returned as text, array, or object.
Example:
Materiale: <{$product->getExtensionValue('materiale')}>
Or:
<{if $img = $product->getExtensionValue('img')}> <{$img->getSrc(50, 50, 'fit')}> <{/if}>
Or if you want to loop images:
<{foreach $product->getExtensionValue('my-images') as $image}> <img src="<{$image->getSrc(50, 50, 'box')}>"> <{/foreach}>
Variable | Content | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
$webshop |
General object that contains the webshop, but also includes several functions to extract various data from the shop. Read more about the Webshop object. Example of how to use $webshop to extract a list of categories:<{foreach $webshop->getCategories() as $category}> <{$category->getName()|escape}> <{/foreach}> |
||||||||||||||||||||
$product |
Product object if you see a product page | ||||||||||||||||||||
$category |
Category-object if a category is shown | ||||||||||||||||||||
$landing_page |
Dynamic category-object if a dynamic category is shown | ||||||||||||||||||||
$page |
Object for static page. | ||||||||||||||||||||
$blog_post |
Blog post object if you are looking at a blog post | ||||||||||||||||||||
$inc |
The name of the special - non-dynamic, page being executed. The values for this can be return.html ,
order-return.html ,
return_received.html ,
also.html ,
search.html ,
basket.html ,
address.html ,
shipping.html ,
approve.html ,
payment.html ,
thanks.html ,
order.html ,
product_review.html ,
subscription.html ,
blog.html ,
user-sign-up.html ,
user-sign-in.html ,
user-sign-out.html ,
user-edit.html ,
user-reset-password.html ,
user-profile.html ,
user-orders.html ,
user-points.html ,
user-subscriptions.html ,
user-change-card.html ,
404.html ,
410.html .
When validating the content, you can advantageously use the code below to control the display in an include file: <{if $inc}> <{include file=$inc}> <{/if}> |
||||||||||||||||||||
$shipping |
Selected shipping method | ||||||||||||||||||||
$pager_array |
Notify if the current page contains pagination. The content of the array consists of the elements max , current , total , url , and first_url . |
||||||||||||||||||||
$pager |
Pager object | ||||||||||||||||||||
$current_url |
Absolute URL for the current page | ||||||||||||||||||||
$top_url |
Relative URL to the current page. | ||||||||||||||||||||
$get |
An array with all the GET variables. | ||||||||||||||||||||
$post |
An array with all POST variables. | ||||||||||||||||||||
$cookie |
An array with all COOKIE variables. | ||||||||||||||||||||
$user_id |
The ID of the shop owner logged into the admin. This variable is set if you follow a link to the shop from the admin. | ||||||||||||||||||||
$customer |
Customer object if the customer is logged in to the shop. | ||||||||||||||||||||
$remote_addr |
The IP address of the customer viewing the page. | ||||||||||||||||||||
$admin_url |
URL to Shoporama admin | ||||||||||||||||||||
$selected_payment_gateway |
The ID of the selected payment gateway. | ||||||||||||||||||||
$join_mailinglist |
An indicator indicating whether the customer has marked that they want to sign up for the newsletter. | ||||||||||||||||||||
$basket_url |
URL to the basket and its contents. | ||||||||||||||||||||
$products_matches |
IDs of products that have matched a discount or promotion | ||||||||||||||||||||
$campaign_ids |
IDs of campaigns if the products in the cart match a campaign. | ||||||||||||||||||||
$campaigns |
If there are active campaigns. | ||||||||||||||||||||
$campaign_discount |
The discount from the active campaigns. | ||||||||||||||||||||
$campaign_matches |
An array of products that match a campaign. If there are no products, the variable is null. | ||||||||||||||||||||
$unpaid_order |
If the customer has an unpaid order. | ||||||||||||||||||||
$unpaid_recurring_order |
Unpaid subscription. | ||||||||||||||||||||
$basket |
The content of the cart as an array, where the individual elements are the products in the cart, with the values:
Example of how the above is used: <{foreach $basket as $row}> <{if $image = $row.product->getImage()}> <img src="<{$image->getSrc(50, 50, 'box')|escape}>"> <{/if}> <{$row.amount}> x <{$row.product->getName()|escape}> i alt <{$row.product->getRealPrice($row.amount, $row.attributes)|number_format:2:",":"."}> <{$webshop->getCurrency()}> <{/foreach}> Check the Template API to see which methods are available for the different objects. |
||||||||||||||||||||
$subscriptions |
The curve's subscriptions. Works the same way as $basket |
||||||||||||||||||||
$price |
The subtotal of the cart's contents. That is, without shipping. | ||||||||||||||||||||
$shipping_price |
The shipping cost of the cart. | ||||||||||||||||||||
$total_price |
Total price of the content in the cart. | ||||||||||||||||||||
$vat |
The VAT on the contents of the cart. | ||||||||||||||||||||
$basket_weight |
The total weight of the curve (for e.g. shipping). | ||||||||||||||||||||
$total_amount |
Total number of products in the curve. | ||||||||||||||||||||
$voucher |
The discount code if there is one. | ||||||||||||||||||||
$voucher_discount |
The discount from the discount code. | ||||||||||||||||||||
$shipping_country |
The selected delivery country. | ||||||||||||||||||||
$nofollow |
Indicates whether nofollow is set on the page. | ||||||||||||||||||||
$meta_title |
The title of the page. | ||||||||||||||||||||
$meta_description |
Description of the page. | ||||||||||||||||||||
$canonical |
URL of the page's canonical. | ||||||||||||||||||||
$session_order |
Array with billing address. Contains the fields
name , company_name , vat_number , address , address2 , zipcode ,
city , email , phone , ean_number , comments |
||||||||||||||||||||
$session_del |
Array with the delivery address. Contains the fields:
name , company_name , address ,
address2 , zipcode , city |
||||||||||||||||||||
$session_extra |
Array with extra fields on the order. If a field is POSTed with
extra[test]=123
$session_extra.test will be 123, and it will be saved on the order. |
||||||||||||||||||||
$use_points |
Number of points the customer has entered that they want to use on the order. | ||||||||||||||||||||
$point_discount |
How many DKK (or other currency) the used points (from $use_points ) correspond to. |
||||||||||||||||||||
$earns |
How many points the customer earns by completing the order. |
The following are the Shoporama-specific features that can be used in templates:
Function | Description | ||||||||
---|---|---|---|---|---|---|---|---|---|
t |
It is possible to incorporate language support into your templates. However, not in the traditional sense where you can choose the text in multiple languages, but rather language support that allows you to change the text in the admin. This is done by wrapping the text in a <{t}>Din kurv<{/t}> Please note that if you change the text in your template files, the new text will appear in the admin as a new text. There are no IDs or names, other than the content, that make the text unique. You can use the attribute <{t section="basket" hint="Bruger i overskriften"}>Din kurv<{/t}> If you have variable content that you want to send without the value being displayed in the admin, you can use the syntax below: <{t amount=$total_amount price=$total_price|number_format:2:",":"." hint="Til kurven"}> Du har {amount} ting til {price} kroner i kurven. <{/t}> If it fits better into your structure, you can also use <{$title = "Min titel her"}> <{$title|t}> The above will display the following in admin:
|
||||||||
cache |
If a template contains a lot of heavy calls that do not need to be live updated - such as product variations, menu, or category overview - Shoporama contains a caching function that can make the shop faster. The functions used are respectively <{get_cache name="my_cache" ttl=3600 assign="c"}> <{if $c}> Fra cache: <{$c}> <{else}> Live: <{cache name="my_cache"}> <{$smarty.now}> <{/cache}> <{/if}> The function
|
||||||||
get_cache |
Function that returns the cached content from the
|
It is possible to add some general settings to themes by adding a
theme_settings.json
in the root of the theme, and it will result in something similar to the following:
The structure is as follows:
Name | Content | Datatype | |||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
info |
The name of the theme | string |
|||||||||||||||||||||||||||||||||||||||
features |
Array of features the theme contains. Only used for display in admin. Only useful if the theme is general and multiple users can choose it. For example ["Feature A", "Feature B", "Feature C"] |
array |
|||||||||||||||||||||||||||||||||||||||
demo |
URL to possible demo page | string |
|||||||||||||||||||||||||||||||||||||||
settings |
Array of editable fields |
array |
|||||||||||||||||||||||||||||||||||||||
Example of file contents{ "info": "Mit tema", "settings": [ { "path": "design", "name": "Design", "description": "Indstillinger for dit design", "fields": [ { "path": "logo", "name": "Toplogo", "description": "Upload dit logo her", "type": "image" }, { "path": "background_color", "name": "Baggrundsfarve", "description": "Baggrundsfarven på shoppen", "type": "color" }, { "path": "size", "name": "Bredde", "description": "Bredden på siden", "type": "list", "values": [ { "name": "Afgrænset", "value": "boxed" }, { "name": "Fuld bredde", "value": "fullwidth" } ] } ] }, { "path": "contact", "name": "Kontaktoplysninger", "description": "Dine kontaktoplysninger der vises i temaet", "fields": [ { "path": "mail", "name": "E-mail-adresse", "description": "Indtast din e-mail-adresse her", "type": "string" } ] }, ] } |
The Page Builder is a tool for building content through sections based on JSON files. These sections can be placed on different pages in the webshop. The pages in which they can be placed are defined in the file /{name}/components/sections.json
. The content of each section is defined in their own respective file, which is placed in /{name}/components/sections/{section}.json
.
If you have done the above, the Design link at the top of the admin will automatically lead to the Page Builder.
The structure in the construction of sections is as follows:
File | Content | |||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
/{navn}/components/sections.json |
Contains a list of page types as well as an array of permitted sections. In addition to page types, custom tags can also be used. { "Sidetype": ["sektionA", "sektionB"] } The following types of pages exist:
An example of a { "landing_page": ["slider", "photos", "quiz"], "product": ["slider"], "#footer": ["about"] } |
|||||||||||||||||||||||||||||||||||||||||||||||||||
/{navn}/components/tags.json |
Tags are used for custom locations that you create yourself and can be pulled out as needed.
It could be that you need a #footer that you pull out and display at the bottom of the shop,
or a #xmas setup that you only use in December. The format for tags.json is:
{ "tag1": "Beskrivelse ...", "tag2": "Beskrivelse ...", "tag3": "Beskrivelse ..." } The individual tags automatically appear in the admin when the user creates a new configuration. To extract the content of tags into the theme, the function getBlocks() is used in the following way: <{if $blocks = $webshop->getBlocks("#foo")}> <{foreach $blocks as $block=>$elements}> [...] <{/foreach}> <{/if}> |
|||||||||||||||||||||||||||||||||||||||||||||||||||
/{navn}/components/sections |
In this library, each section is placed in its own json file. Each file is named after the section's name. If the section is called slider , the content should be located in /{name}/components/sections/slider.json . The structure of the files is as follows.
|
There are different strategies for extracting data. As a starting point, there will be a $page_blocks
on the pages that match a setup. It can be looped through, or the content can be extracted using $webshop->getBlocks('#foo')
. Alternatively, the content can be accessed through the name of the section.
As a starting point, we would recommend that you place the display of each section in separate files, such as /{name}/templates/sections/{section}.html
. Then it is easy to loop through $page_blocks
and include the correct display as follows:
<{if $page_blocks}> <{foreach $page_blocks as $section}> <{$type = $section._type}> <{if $webshop->templateExists("sections/$type.html")}> <{include file="sections/$type.html"}> <{/if}> <{/foreach}> <{/if}>
Note that the row type is in the variable _type
Below section calls images
– hence content is in /{navn}/components/sections/images.json
.
{ "title": "Overskrift og billeder", "fields": [ { "id": "headline", "type": "text", "title": "Overskrift" }, { "id": "images", "type": "images", "title": "Billeder" } ] }
It will produce the following interface in admin
To extract content through the above method may /{navn}/templates/sections/images.html
contain the following:
<h1><{$section.headline|escape}></h1> <{foreach $section.images as $image}> <img src="<{$image->getSrc(150, 150, 'box')}>"> <{/foreach}>
Regarding data type, it is important to be aware of what each field contains. It can be either a string
, array
, or object
. Therefore, one should do something similar:
[...] <{foreach $section as $name=>$val}> <{if is_array($val)}> Repeater or array of images <{elseif is_object($val)}> An image <{else}> Text, number or such <{/if}> <{/foreach}> [...]
If you use the types product
, category
, or landing_page
, the selected ID will be returned in the theme, and you have to extract the object yourself using the methods $webshop->getProductById(id)
, $webshop->getCategory(id)
, or $webshop->getLandingPage(id)
. For example:
[...] <{if $product = $webshop->getProductById($section.product_id)}> <{$product->getName()|escape}> <{/if}> [...]
Please note that it is always possible to use var_dump
to examine what the variable contains. That way you can loop through it correctly.
Based on the previously mentioned structure, the individual views are reviewed below. To understand the individual objects and which methods are available, we recommend looking in our. Template API.
Download our Alaska theme to see examples of the different views.
The display of products can be located in product.html
, but it is optional. It is always index.html
that
executes the individual displays. However, we recommend using this structure. To know if a product view is being executed, it checks
if $product
is present.
Products are added to the cart by making a POST
call to any page - typically just the page already being displayed, with the product_id
, attributes[{attribute_id}]={attribute_value_id}
, and amount
arguments. The attribute should only be added if the products have variants.
A simple example of how to add products to basket may be:
<form action="" method="post"> <input type="hidden" name="product_id" value="<{$product->getProductId()}>"/> <input type="number" name="amount" value="1" min="1"/> <input type="submit" value="Læg i kurv"> </form>
With variants
<form action="" method="post"> <input type="hidden" name="product_id" value="<{$product->getProductId()}>"/> <{foreach $product->getProfile()->getAttributeList() as $attribute}> <{if $attribute->getIsVariant() && $attribute->getDataType() == "valuelist"}> <select name="attributes[<{$attribute->getAttributeId()}>]"> <{foreach $attribute->getValues() as $value}> <option value="<{$value->getAttributeValueId()}>"> <{$attribute->getName()|escape}>: <{$value->getVal()|escape}> </option> <{/foreach}> </select> <{/if}> <{/foreach}> <input type="number" name="amount" value="1" min="1"/> <input type="submit" value="Læg i kurv"> </form>
The above can of course be made more complicated if you wish to display stock availability, bundled packages, etc.
Landing pages contain a $landingpage
and $products
which is an array of
products to be displayed on the page.
If a pager is needed it will be included this way
<{if $pager}> <{$pager->render()}> <{/if}>
Categories contain a $category
and $products
which is an array of
products to be displayed on the page.
If a pager is needed it will be included this way
<{if $pager}> <{$pager->render()}> <{/if}>
Static pages will contain a $page
.
The blog can be divided into two pages: blog.html
and blog_post.html
, where the first one can retrieve a list of blog posts through:
<{$blog_posts = $webshop->getBlogPosts()}>
Viewing the individual blog posts is through the variable $blog_post
If the list of blogposts is from a category there will be a $category
available at the category.
In the product_review.html
file, there is access to $order
, which represents the order. You can only review products that you have purchased. Based on the order, you can retrieve the products using $order->getOrderProducts()
. To save the product reviews, you need to make a POST
call to the same page with the following content:
<{if $order}> <{if $get.voted}> Tak! <{else}> <form action="" method="post"> <{foreach $order->getOrderProducts() as $product}> <{$product->getName()|escape}> Stjerner: <input type="number" min="1" max="5" name="rating[<{$product->getProductId()}>]"> Anmeldelse <textarea name="description[<{$product->getProductId()}>]"></textarea> <{/foreach}> <input type="submit" value="Skriv anmeldelse"/> </form> <{/if}> <{/if}>
$get.voted
is set to true when the customer has written a review.
To link to the review page from your order use $order->getReviewUrl()
.
The search function works by making a GET
request to /search
where the argument
?search=
should be your search keyword. E.g.:
<form action="/search"> <input type="text" name="search" value="<{$get.search|escape}>"/> <input type="submit" value="Søg"/> </form>
The search is done in both products, categories, blog posts, and landing pages. To display the result, you need to look at the four variables
$products
, $categories
, $blog_posts
, $pages
, and $landing_pages
.
If searching in static pages is to be enabled, it must be activated in the admin.
If your customers can log in to your shop, you can provide them with the option to create wishlists. They can create as many wishlists as they want, and a wishlist has a name, a description, and a number of products. The wishlist itself has a public address so your customers can share a link.
The wishlist uses the following templates:
Template | URL | Description | Relevant variables |
---|---|---|---|
wishlist.html |
/wishlist |
The page with the public wish list. | $wishlist containing the wishlist. See methods here. |
user-wishlists.html |
/user-wishlists |
The page with the user's wishlists. Requires login. | $customer->getWishlists() to fetch wishlists. |
user-wishlist.html |
/user-wishlist |
The page used to edit the wishlist and products. | $wishlist containing the wishlist. See methods here. |
To create a wishlist, a POST request with the field name
and optionally
description
needs to be sent from user-wishlists.html
. For example,
<form action="" method="post"> Navn <input type="text" name="name"> Beskrivelse <textarea name="description"></textarea> <input type="submit" value="Opret"> </form>
Note that you can advantageously use the methods $wishlist->getRemoteUrl()
and $wishlist->getEditUrl()
to create links for displaying and editing the wishlists.
In the file user-wishlist.html
, where the wishlist is edited, the name and description should be sent as name
, description
, and status
which can be either active
or closed
.
The products themselves, along with their quantity and optional comment, should be sent as an array in the following format:
wishlist_product[{id}][name] = 'navn' wishlist_product[{id}][comments] = 'kommentar' wishlist_product[{id}][remove] = '1' // kun hvis produktet skal fjernes
Example code:
<{foreach $wishlist->getWishlistProducts() as $wp}> <{* Fordi der er forskel på produkter og ønskelisteprodukter }*> <{$product = $wp->getProduct()}> <input type="checkbox" name="wishlist_product[<{$wp->getWishlistProductId()}>][remove]" value="1"> <a href="<{$product->getUrl()|escape}>"><{$product->getName()|escape}></a> <{if $variant = $wp->getVariantValue()}> <{$wp->getVariantName()|escape}>: <{$variant|escape}> <{/if}> <input type="number" name="wishlist_product[<{$wp->getWishlistProductId()}>][amount]" value="<{$wp->getAmount()}>"> <textarea name="wishlist_product[<{$wp->getWishlistProductId()}>][comments]"><{$wp->getComments()|escape}></textarea> <{/foreach}>
If a variable edit
is sent, the page will return to the same editing page again. Otherwise, it will be redirected to the overview of wish lists. To delete the wish list, a variable named remove
must be sent. Example:
<input type="submit" name="remove" value="Slet" onclick="return confirm('Er du sikker?');">
To add products to a wishlist from the shop, a POST request can be made from any page with the fields wishlist_id
, product_id
, and an optional attribute_value_id
if the product has variants.
Example:
<{if $product && $customer}> <{if $wishlists = $customer->getWishlists()}> <form action="" method="post"> <input type="hidden" name="product_id" value="<{$product->getProductId()}>"/> <label>Vælg ønskeliste:</label> <select name="wishlist_id"> <{foreach $wishlists as $wishlist}> <option value="<{$wishlist->getWishlistId()}>"> <{$wishlist->getName()|escape}> </option> <{/foreach}> </select> <{if $variant = $product->getVariant()}> <label>Vælg variant:</label> <select name="attribute_value_id"> <{foreach $variant->getValues() as $value}> <option value="<{$value->getAttributeValueId()}>"> <{$variant->getName()|escape}>: <{$value->getVal()|escape}> </option> <{/foreach}> </select> <{/if}> <input type="submit" value="Tilføj til ønskeliste"> </form> <{/if}> <{/if}>
The returns center allows your customers to create returns themselves. It needs to be activated in the shop settings. Once a customer has created a return, it needs to be approved in the admin, and a credit note will be issued.
The return center uses the following templates:
Template | URL | Description | Relevant variables |
---|---|---|---|
return.html |
/return |
The page where the return is created | $order containing the order that the customer wants to return. See methods here. |
order-return.html |
/order-return |
The page where the return is subsequently displayed | $order_return with the return. See methods here. |
return_received.html |
Email sent to the customer when they create a return | $order_return with the return. See methods here. |
There are two ways for your customer to access the return page. One is by entering the order number and email
address associated with their order. The other is by following a direct link. The direct link can be retrieved from
the order using the $order->getReturnUrl()
function. For example,
<{if $webshop->useReturnCenter() && !$order->getIsCreditNote()}> <a href="<{$order->getReturnUrl()|escape}>">Retuner varer</a> <{/if}>
As seen in the example, it is a good idea to check that the order is not a credit note and if the webshop has activated the return center. The example can be used anywhere there is an $order
, such as the customer's order overview or in emails.
If you do not use login or in any other way want the customer to be able to search for their order, you need to implement a search with order number and email address on return.html
. The fields webshop_order_id
and email
should be POSTed to /return
. If the order is found, the customer will be redirected to the return process. If not, $get.not_found
will be set, and you can display an error message in the theme. A minimal form can look like the following:
<{if $get.not_found}> <p>Der blev ikke fundet nogen ordre.</p> <{/if}> <h1>Returcenter</h1> <form method="post" action=""> <input type="number" name="webshop_order_id" placeholder="Ordrenummer"> <input type="email" name="email" placeholder="E-mail-adresse"> <input type="submit" value="Søg"> </form>
In the file return.html
, a list of order lines must be extracted, and the customer can choose which products to return and why. Unlike regular order views, the return process should include one line per order line.
<form action="" method="post"> <{$line = 0}> <!-- Looper ordrelinjerne igennem --> <{foreach $order->getOrderProducts() as $product}> <!-- getReturned() returnerer antallet der tidligere er returneret --> <{$returned = $product->getReturned()}> <!-- Løkke for hver antal --> <{section name="i" loop=$product->getAmount()}> <{$line = $line+1}> <!-- Viser ikke flere end det er muligt at returnere --> <{if $line - $returned > 0}> <{$product->getName()|escape}> <{if $attributes = $product->getAttributes()}> <{foreach $attributes as $attribute}> <{$attribute.name}>: <{$attribute.val}> <{/foreach}> <{/if}> <!-- Check hvilken pris der skal vises --> <{if $webshop->getUseCalculatedUnitPrice()}> <{$webshop->getCurrency()}> <{$product->getCalculatedUnitPrice()|number_format:2:",":"."}> <{else}> <{$webshop->getCurrency()}> <{$product->getUnitPrice()|number_format:2:",":"."}> <{/if}> <!-- Checkbox og en grund --> <input type="checkbox" name="return[<{$line}>]" value="<{$product->getOrderProductId()}>">Vælg <input type="text" name="reason[<{$line}>]" placeholder="Evt. årsag til returnering"/> <{/if}> <{/section}> <{/foreach}> </form>
It is possible in admin to specify a fixed delivery method and price. If this is done, the delivery method is retrieved via
$webshop->getReturnShipping($country_id)
, and if the price is fixed, it is retrieved via
$webshop->getReturnShippingPrice($country_id)
. For example:
<{if $return_shipping = $webshop->getReturnShipping($order->getDelCountryId())}> Varer skal returneres via <{$return_shipping->getName()|escape}>. <{if $price = $webshop->getReturnShippingPrice($order->getDelCountryId())}> Prisen er <{$price|number_format:2:",":"."}> <{else}> Prisen er <{$return_shipping->getCost()|number_format:2:",":"."}> <{/if}> <{else}> <!-- Vis en liste over shoppens almindelige leveringsmetoder her --> <{/if}>
The selected delivery method, if optional, should be sent as shipping_id
.
When the return is created, it will be displayed in order-return.html
, which contains a $order_return
with the relevant methods.
also.html
is our page for upselling. As a starting point, there is access to $product
,
and based on that, suggestions can be made to the customer. It may be relevant to use $product->getRelatedProducts()
,
$product->getAlsoBought()
, or $webshop->getPopularProducts()
.
Only used to show 404 pages
Only used to show 410 pages
An automatic captcha is inserted when you try to sign up for a newsletter or write comments. This is to prevent spam. It is possible to design the page itself in captcha.html
, and it must contain at least:
<form action="" method="post"> <{$form}> <{if $error}>Fejl i koden<{/if}> <img src="<{$imgstr}>"> Skriv indholdet at ovenstående felt: <input type="text" name="c"> <input type="submit" value="OK"> </form>
It is possible to allow customers to log in to the shop. If the person is logged in, there will be a $customer
that contains information about the user. All pages that edit, log in, etc. should submit via POST to the same page.
File name | Description | Relevant variables/parameters in the form |
---|---|---|
/user-edit.html |
Uset to edit customer data. It is possible to add fields to the customer. View more under this table. | company , vat_number , name , email , phone , address , zipcode , city , country_id , shipping_name , shipping_address , shipping_zipcode , shipping_city , shipping_country_id , pass1 , pass2 . |
/user-orders.html |
Customer's previous orders | $my_orders |
/user-points.html |
Customer's point | $my_points |
/user-profile.html |
Showing customer data | $customer |
/user-reset-password.html |
Used to reset password | email , password |
/user-sign-in.html |
Login page. Use redir if the customer shall be redirectet to a certain URL after login. |
email , password , redir |
/user-sign-up.html |
A form used to sign up. | name , email , phone , address , zipcode , city , company , vat_number , og country_id |
In admin it is possible to define custom fields for users. These fields are edited this way:
<{foreach $webshop->getCustomerFields() as $field}> <{if $field->getType() == "list"}> <{$field->getName()|escape}> <select name="field[<{$field->getCustomerFieldId()}>]"> <{foreach $field->getValues() as $value}> <option <{if $field->getVal() == $value}>selected="selected"<{/if}> value="<{$value|escape}>"> <{$value|escape}> </option> <{/foreach}> </select> <{elseif $field->getType() == "string"}> <{$field->getName()|escape}> <input type="text" name="field[<{$field->getCustomerFieldId()}>]" value="<{$field->getVal()|escape}>"> <{/if}> <{/foreach}>
The loyalty program allows your customers to earn and use points when they place orders. The setup is managed under Customers > Loyalty Program.
You can use the following methods to check if the shop has enabled the loyalty program and if the customer is logged in and has earned points they can use:
<{if $webshop->hasLoyaltyProgram() && $customer && $customer->getActivePoints()}> [Visning af point m.m.] <{/if}>
To display the number of points the customer has earned, as well as how many they can use on the current order, you can use the following code example:
Du har optjent <{$customer->getActivePoints()|number_format:0:",":"."}> point. <{if $points = $customer->getPointsAvailable()}> Du kan bruge <{$points|number_format:0:",":"."}> point på denne ordre. <{else}> Du har ingen optjente point du kan bruge på denne ordre. <{/if}>
The field you POST to the cart, indicating how many points the customer wants to use, should be named use_points
, and can appear as follows:
<input type="number" name="use_points" value="<{$use_points}>" min="0" max="<{$customer->getPointsAvailable()}>"/>
You can use $earns
to see how much the customer earns on the order:
Du optjener <{$earns|number_format:0:",":"."}> point på denne ordre.
You can use the example below to describe your loyalty program:
Du optjener <{$webshop->getLoyaltyProgramBasePoints()|number_format:0:",":"."}> point hver gang du køber for 1 <{$webshop->getCurrency()}>. Når du betaler med point svarer <{$webshop->getLoyaltyProgramBaseCost()|number_format:0:",":"."}> point til 1 <{$webshop->getCurrency()}>.
If you want to create a page with a point overview, you can use user-points.html
, where you can loop through $my_points
and present the content. Take a look at the SafePoint class to see which methods are available.
Tip: You can style your order overview by using the following CSS classes: points_used
, points_earned
, and has_profile
.
The check-out flow is simply a series of HTML pages, each containing a form that POST
s some data to itself. If the next
field is set, Shoporama will proceed to the next page in the flow. Typically, the "Next" button will have name="next"
. It is always possible to link to previous pages in the flow. When the customer enters their information, it is stored in two arrays: $session_order
and $session_del
. For example, $session_order.name
.
File name | Description | Relevant variables/parameters in the form |
---|---|---|
basket.html |
Shows basket content. Simpel example of how to use $basket <{foreach $basket as $line}> <{$line.amount}> x <{$line.product->getName()}> <{foreach $line.attributes as $a}> <{$a.name|escape}>: <{$a.value|escape}> <{/foreach}> <{/foreach}> To change the quantity, it should be [...] <input name="amount[<{$line.id}>]" value="<{$line.amount}>" /> [...] To add a discount code, a The delivery country can be changed by |
$basket , $voucher , $voucher_discount , $campaign_ids ,
$campaign_discount , $price , $shipping_price , $total_price ,
$vat |
address.html |
The page where delivery information is entered. If If |
order_country_id , order_name , order_company_name , vat_number ,
ean_number , order_address , order_zipcode , order_city ,
email , phone , comments , del_name , del_company_name ,
del_address , del_zipcode , del_city , del_country_id ,
mailinglist , create_profile |
shipping.html |
Used to select the delivery method.
The individual delivery methods can be pulled out via
[...] <{if $shipping->getModule()}> <select name="shop"> <{foreach $shipping->getDeliveryShops() as $shop}> <option value="<{$shop.number}>"> <{$shop.name|escape}> <{$shop.street|escape}> <{$shop.zip|escape}> <{$shop.city|escape}> </option> <{/foreach}> </select> <{/if}> [...] |
shipping_id , shop |
approve.html |
Approval page. Used to display the order. If the shop contains multiple payment gateways, these can be selected on this page. They are retrieved via |
$basket , $shipping , payment_gateway_id |
thanks.html |
Thank you page. Used to display the completed order. | $order |
Shoporama has two built-in ajax calls. One can extract products in various ways (filtering), and the other is a general and simple search that searches in products, categories, and landing pages.
The filtering is located in the file /ajax
which is located at the root of all shops.
Argument | Description | Example |
---|---|---|
atags |
A list of tags on the attribute values, attribute values are settings on the attributes of the product profiles.
The values on atags should be separated by commas, but allow groupings separated by pipe as a logical
OR . |
ajax?atags=female,black|white |
product_ids |
List of product IDs separated by pipe. | ajax?product_ids=1|2|3|4 |
price_range |
Returns products that are within a price range. The amounts should be separated by a pipe. | ajax?price_range=100|200 |
categories |
A pipe-separated list of categories that the products should be placed in. If the argument exclude=1 is set,
a list of products that do not have the categories is returned. |
ajax?categories=5|9|2 ajax?categories=5|9|2&exclude=1 |
sort |
Sort order. You man set these values popular , weight , name , price , created .
Whether sorting should be ascending or descending is set by sort_order . Set it to eihter asc or desc . |
ajax?sort=price&sort_order=desc |
attribute_values |
A list of IDs for the attribute values that the products should have. The list is separated by pipes. If the argument exclude=1 is set, a list of products that do not match is returned. |
ajax?attribute_values=9|8|12 |
suppliers |
Pipe-separated list of supplier IDs. | ajax?suppliers=8|2 |
landing_pages |
Girl-separated list of IDs for landing pages the products should appear on. | ajax?landing_pages=5|24 |
extension.{id} |
If the theme uses extended fields, products can be extracted based on them.
Absolute searches are performed, so the fields that make the most sense to use are bool , multi ,
number , and list . Pipe-separated values can be used if the products need to match just
one of the values. |
ajax?extension.foo=1&extension.tags=foo|bar |
attribute_tags_in_stock |
List of tags for attribute values that should be in stock. The list can be piped separated. | ajax?attribute_tags_in_stock=foo|bar |
attribute_tags |
List of tags to be placed on the products. The list can be pipe-separated. | ajax?attribute_tags=foo|bar |
attribute_tag |
Same as attribute_tags however just an single tag |
ajax?attribute_tag=foo |
force_categories |
A pipe-separated list of categori IDs that must be set on all products. | ajax?force_categories=5|9|2 |
limit |
Maximum amount of products to be returned | ajax?limit=10 |
offset |
Starting position relative to where in the list of products you want to fetch from. | ajax?offset=100&limit=10 |
meta |
Pipe-separated list of additional fields you want to see on the products in the result. If you set meta=_all , all fields are returned. |
ajax?meta=foo|bar ajax?meta=_all |
only_in_stock_variants |
Set to 1 or 0 depending on whether each product in the result should only include variants that are in stock. Default is 0 . |
ajax?only_in_stock_variants=1 |
include_meta |
Set to 1 or 0 depending on whether you want an additional meta field in the result that contains descriptions of the products in the result. It provides information about the attributes, categories, and brands that exist. The default is 0 . |
ajax?include_meta=1 |
include_pagination |
Set to 1 or 0 depending on you want extra pagination -field in results for paging information. The array fields are offset , limit , count , total . Default setting is 0 |
ajax?include_pagination=1 |
pretty |
Set to 1 or 0 depending on whether the JSON response should be formatted nicely or not. |
ajax?pretty=1 |
The response from /ajax
is an array in the following format:
[ { "product_id": 139735, "own_id": "skunumme", "name": "Produktnavn", "supplier_id": 0, "supplier_name": "", "category_ids": [ 3661, 2113, 2106, 1973 ], "category_names": [ "Bob", "Forside", "Ged", "Giraf" ], "description": "<p>...</p>", "list_description": "", "profile_name": "Default m. variant", "allow_negative_stock": 1, "brand_name": "TESTBRAND", "sale_price": 0, "real_price": 80, "price": 80, "price_dk": "80,00", "approx_shipping": 0, "delivery_time": "", "delivery_time_not_in_stock": "", "url": "https://example.com/produkt", "stock": 0, "attr_stock": null, "variant_stock": [ { "attribute_id": 2740, "attribute_value_id": 16287, "name": "S", "weight": 10, "cnt": 4 }, { "attribute_id": 2740, "attribute_value_id": 16289, "name": "L", "weight": 30, "cnt": 9 } ], "stock_string_da": "Nej", "avg_rating": null, "thumbnail": "https://example.com/cache/1/9/6/9/bob-fit-200x200x90.png", "meta_values": [ null ], "online_since": 1651685711, "has_campaigns": true, "campaign_info": [ { "name": "Bob", "price_model": "cheapest_free", "min_product_count": 4, "price": 0, "percent": 0, "created": "2023-02-01 14:55:47", "expires": null } ] } ]
For speed reasons, we cache the response from the ajax file, but in a test environment, you can add rebuild=1
as an argument to rebuild the page. We recommend not doing this in a production environment as it can significantly slow down the page.
The page /ajax_search
contains a general search for products, categories, and landing pages. It can, for example, be used for autocomplete in your search field. It takes the following arguments:
Argument | Description | Example |
---|---|---|
term |
Keyword | ajax_search?term=ostemad |
limit |
Maximum results | ajax_search?term=ostemad&limit=25 |
include |
A comma-separated list of data types to search in. The values can be products , categories , blog_posts , pages , and landing_pages . |
ajax_search?term=ostemad&include=products,categories |
pretty |
Set to 1 or 0 depending on whether the JSON response should be formatted nicely or not. |
ajax_search?term=ostemad&pretty=1 |
Result is an array in this format:
[ { "name": "Produkt et", "supplier": "", "description": "<p>...</p>", "price": 80, "sale_price": null, "normal_price": 80, "currency": "DKK", "price_dk": "80,00", "url": "https://example.com/produkt-et", "stock": 0, "stock_string_da": "Nej", "review_avg": 0, "thumbnail": "https://example.com/cache/1/9/8/4/box-100x100x90.png", "type": "product" }, { "name": "Produkt to", "supplier": "", "description": "<p>...</p>", "price": 80, "sale_price": null, "normal_price": 80, "currency": "DKK", "price_dk": "80,00", "url": "https://example.com/produkt-to", "stock": 0, "stock_string_da": "Nej", "review_avg": 0, "thumbnail": "https://example.com/cache/1/9/6/9/bob-box-100x100x90.png", "type": "product" } ]