Added permission validation to non admin users

react_convert
Mark Moffat 2018-02-05 20:51:04 +01:00
parent bf67621a86
commit 4533e23993
9 changed files with 249 additions and 191 deletions

2
app.js
View File

@ -235,7 +235,7 @@ app.use('/', customer);
app.use('/', product);
app.use('/', order);
app.use('/', user);
app.use('/admin', admin);
app.use('/', admin);
app.use('/paypal', paypal);
app.use('/stripe', stripe);
app.use('/authorizenet', authorizenet);

View File

@ -11,7 +11,35 @@ const nodemailer = require('nodemailer');
const escape = require('html-entities').AllHtmlEntities;
let ObjectId = require('mongodb').ObjectID;
const restrictedRoutes = [
{route: '/admin/product/new', response: 'redirect'},
{route: '/admin/product/insert', response: 'redirect'},
{route: '/admin/product/edit/:id', response: 'redirect'},
{route: '/admin/product/update', response: 'redirect'},
{route: '/admin/product/delete/:id', response: 'redirect'},
{route: '/admin/product/published_state', response: 'json'},
{route: '/admin/product/setasmainimage', response: 'json'},
{route: '/admin/product/deleteimage', response: 'json'},
{route: '/admin/order/statusupdate', response: 'json'},
{route: '/admin/settings/update', response: 'json'},
{route: '/admin/settings/option/remove', response: 'json'},
{route: '/admin/settings/pages/new', response: 'redirect'},
{route: '/admin/settings/pages/edit/:page', response: 'redirect'},
{route: '/admin/settings/pages/update', response: 'json'},
{route: '/admin/settings/pages/delete/:page', response: 'redirect'},
{route: '/admin/settings/menu/new', response: 'redirect'},
{route: '/admin/settings/menu/update', response: 'redirect'},
{route: '/admin/settings/menu/delete/:menuid', response: 'redirect'},
{route: '/admin/settings/menu/save_order', response: 'json'},
{route: '/admin/file/upload', response: 'redirect'},
{route: '/admin/file/delete', response: 'json'}
];
// common functions
exports.restrict = (req, res, next) => {
exports.checkLogin(req, res, next);
};
exports.checkLogin = (req, res, next) => {
// if not protecting we check for public pages and don't checkLogin
if(req.session.needsSetup === true){
@ -26,6 +54,26 @@ exports.checkLogin = (req, res, next) => {
res.redirect('/admin/login');
};
// Middleware to check for admin access for certain route
exports.checkAccess = (req, res, next) => {
const routeCheck = _.find(restrictedRoutes, {'route': req.route.path});
// If the user is not an admin and route is restricted, show message and redirect to /admin
if(req.session.isAdmin === 'false' && routeCheck){
if(routeCheck.response === 'redirect'){
req.session.message = 'Unauthorised. Please refer to administrator.';
req.session.messageType = 'danger';
res.redirect('/admin');
return;
}
if(routeCheck.response === 'json'){
res.status(400).json({message: 'Unauthorised. Please refer to administrator.'});
}
}else{
next();
}
};
exports.showCartCloseBtn = (page) => {
let showCartCloseButton = true;
if(page === 'checkout' || page === 'pay'){
@ -64,10 +112,6 @@ exports.addSitemapProducts = (req, res, cb) => {
});
};
exports.restrict = (req, res, next) => {
exports.checkLogin(req, res, next);
};
exports.clearSessionValue = (session, sessionVar) => {
let temp;
if(session){

View File

@ -50,7 +50,7 @@ $(document).ready(function (){
showNotification(msg, 'success');
})
.fail(function(msg){
showNotification(msg.responseText, 'danger');
showNotification(msg.responseJSON.message, 'danger');
});
});
@ -118,10 +118,10 @@ $(document).ready(function (){
data: {id: this.id, state: this.checked}
})
.done(function(msg){
showNotification(msg, 'success');
showNotification(msg.message, 'success');
})
.fail(function(msg){
showNotification(msg.responseText, 'danger');
showNotification(msg.responseJSON.message, 'danger');
});
});
@ -540,7 +540,7 @@ $(document).ready(function (){
showNotification(msg, 'success');
})
.fail(function(msg){
showNotification(msg.responseText, 'danger');
showNotification(msg.responseJSON.message, 'danger');
});
}else{
showNotification('Please enter a permalink to validate', 'danger');

File diff suppressed because one or more lines are too long

View File

@ -10,12 +10,12 @@ const glob = require('glob');
const router = express.Router();
// Admin section
router.get('/', common.restrict, (req, res, next) => {
router.get('/admin', common.restrict, (req, res, next) => {
res.redirect('/admin/orders');
});
// logout
router.get('/logout', (req, res) => {
router.get('/admin/logout', (req, res) => {
req.session.user = null;
req.session.message = null;
req.session.messageType = null;
@ -23,7 +23,7 @@ router.get('/logout', (req, res) => {
});
// login form
router.get('/login', (req, res) => {
router.get('/admin/login', (req, res) => {
let db = req.app.db;
db.users.count({}, (err, userCount) => {
@ -54,7 +54,7 @@ router.get('/login', (req, res) => {
});
// login the user and check the password
router.post('/login_action', (req, res) => {
router.post('/admin/login_action', (req, res) => {
let db = req.app.db;
db.users.findOne({userEmail: req.body.email}, (err, user) => {
@ -92,7 +92,7 @@ router.post('/login_action', (req, res) => {
});
// setup form is shown when there are no users setup in the DB
router.get('/setup', (req, res) => {
router.get('/admin/setup', (req, res) => {
let db = req.app.db;
db.users.count({}, (err, userCount) => {
@ -119,7 +119,7 @@ router.get('/setup', (req, res) => {
});
// insert a user
router.post('/setup_action', (req, res) => {
router.post('/admin/setup_action', (req, res) => {
const db = req.app.db;
let doc = {
@ -156,7 +156,7 @@ router.post('/setup_action', (req, res) => {
});
// settings update
router.get('/settings', common.restrict, (req, res) => {
router.get('/admin/settings', common.restrict, (req, res) => {
res.render('settings', {
title: 'Cart settings',
session: req.session,
@ -172,7 +172,7 @@ router.get('/settings', common.restrict, (req, res) => {
});
// settings update
router.post('/settings/update', common.restrict, (req, res) => {
router.post('/admin/settings/update', common.restrict, common.checkAccess, (req, res) => {
let result = common.updateConfig(req.body);
if(result === true){
res.status(200).json({message: 'Settings successfully updated'});
@ -182,7 +182,7 @@ router.post('/settings/update', common.restrict, (req, res) => {
});
// settings update
router.post('/settings/option/remove', common.restrict, (req, res) => {
router.post('/admin/settings/option/remove', common.restrict, common.checkAccess, (req, res) => {
const db = req.app.db;
db.products.findOne({_id: common.getId(req.body.productId)}, (err, product) => {
if(err){
@ -209,7 +209,7 @@ router.post('/settings/option/remove', common.restrict, (req, res) => {
});
// settings update
router.get('/settings/menu', common.restrict, async (req, res) => {
router.get('/admin/settings/menu', common.restrict, async (req, res) => {
const db = req.app.db;
res.render('settings_menu', {
title: 'Cart menu',
@ -224,7 +224,7 @@ router.get('/settings/menu', common.restrict, async (req, res) => {
});
// settings page list
router.get('/settings/pages', common.restrict, (req, res) => {
router.get('/admin/settings/pages', common.restrict, (req, res) => {
const db = req.app.db;
db.pages.find({}).toArray(async (err, pages) => {
if(err){
@ -246,7 +246,7 @@ router.get('/settings/pages', common.restrict, (req, res) => {
});
// settings pages new
router.get('/settings/pages/new', common.restrict, async (req, res) => {
router.get('/admin/settings/pages/new', common.restrict, common.checkAccess, async (req, res) => {
const db = req.app.db;
res.render('settings_page_edit', {
@ -263,7 +263,7 @@ router.get('/settings/pages/new', common.restrict, async (req, res) => {
});
// settings pages editor
router.get('/settings/pages/edit/:page', common.restrict, (req, res) => {
router.get('/admin/settings/pages/edit/:page', common.restrict, common.checkAccess, (req, res) => {
const db = req.app.db;
db.pages.findOne({_id: common.getId(req.params.page)}, async (err, page) => {
if(err){
@ -299,7 +299,7 @@ router.get('/settings/pages/edit/:page', common.restrict, (req, res) => {
});
// settings update page
router.post('/settings/pages/update', common.restrict, (req, res) => {
router.post('/admin/settings/pages/update', common.restrict, common.checkAccess, (req, res) => {
const db = req.app.db;
let doc = {
@ -339,7 +339,7 @@ router.post('/settings/pages/update', common.restrict, (req, res) => {
});
// settings delete page
router.get('/settings/pages/delete/:page', common.restrict, (req, res) => {
router.get('/admin/settings/pages/delete/:page', common.restrict, common.checkAccess, (req, res) => {
const db = req.app.db;
db.pages.remove({_id: common.getId(req.params.page)}, {}, (err, numRemoved) => {
if(err){
@ -355,7 +355,7 @@ router.get('/settings/pages/delete/:page', common.restrict, (req, res) => {
});
// new menu item
router.post('/settings/menu/new', common.restrict, (req, res) => {
router.post('/admin/settings/menu/new', common.restrict, common.checkAccess, (req, res) => {
let result = common.newMenu(req, res);
if(result === false){
req.session.message = 'Failed creating menu.';
@ -365,7 +365,7 @@ router.post('/settings/menu/new', common.restrict, (req, res) => {
});
// update existing menu item
router.post('/settings/menu/update', common.restrict, (req, res) => {
router.post('/admin/settings/menu/update', common.restrict, common.checkAccess, (req, res) => {
let result = common.updateMenu(req, res);
if(result === false){
req.session.message = 'Failed updating menu.';
@ -375,7 +375,7 @@ router.post('/settings/menu/update', common.restrict, (req, res) => {
});
// delete menu item
router.get('/settings/menu/delete/:menuid', common.restrict, (req, res) => {
router.get('/admin/settings/menu/delete/:menuid', common.restrict, common.checkAccess, (req, res) => {
let result = common.deleteMenu(req, res, req.params.menuid);
if(result === false){
req.session.message = 'Failed deleting menu.';
@ -385,7 +385,7 @@ router.get('/settings/menu/delete/:menuid', common.restrict, (req, res) => {
});
// We call this via a Ajax call to save the order from the sortable list
router.post('/settings/menu/save_order', common.restrict, (req, res) => {
router.post('/admin/settings/menu/save_order', common.restrict, common.checkAccess, (req, res) => {
let result = common.orderMenu(req, res);
if(result === false){
res.status(400).json({message: 'Failed saving menu order'});
@ -395,7 +395,7 @@ router.post('/settings/menu/save_order', common.restrict, (req, res) => {
});
// validate the permalink
router.post('/api/validate_permalink', (req, res) => {
router.post('/admin/api/validate_permalink', (req, res) => {
// if doc id is provided it checks for permalink in any products other that one provided,
// else it just checks for any products with that permalink
const db = req.app.db;
@ -412,18 +412,16 @@ router.post('/api/validate_permalink', (req, res) => {
console.info(err.stack);
}
if(products > 0){
res.writeHead(400, {'Content-Type': 'application/text'});
res.end('Permalink already exists');
res.status(400).json({message: 'Permalink already exists'});
}else{
res.writeHead(200, {'Content-Type': 'application/text'});
res.end('Permalink validated successfully');
res.status(200).json({message: 'Permalink validated successfully'});
}
});
});
// upload the file
let upload = multer({dest: 'public/uploads/'});
router.post('/file/upload', common.restrict, upload.single('upload_file'), (req, res, next) => {
router.post('/admin/file/upload', common.restrict, common.checkAccess, upload.single('upload_file'), (req, res, next) => {
const db = req.app.db;
if(req.file){
@ -479,15 +477,15 @@ router.post('/file/upload', common.restrict, upload.single('upload_file'), (req,
});
// delete a file via ajax request
router.post('/testEmail', common.restrict, (req, res) => {
router.post('/admin/testEmail', common.restrict, (req, res) => {
let config = common.getConfig();
// TODO: Should fix this to properly handle result
common.sendEmail(config.emailAddress, 'expressCart test email', 'Your email settings are working');
res.status(200).json('Test email sent');
res.status(200).json({message: 'Test email sent'});
});
// delete a file via ajax request
router.post('/file/delete', common.restrict, (req, res) => {
router.post('/admin/file/delete', common.restrict, common.checkAccess, (req, res) => {
req.session.message = null;
req.session.messageType = null;
@ -503,7 +501,7 @@ router.post('/file/delete', common.restrict, (req, res) => {
});
});
router.get('/files', common.restrict, (req, res) => {
router.get('/admin/files', common.restrict, (req, res) => {
// loop files in /public/uploads/
glob('public/uploads/**', {nosort: true}, (er, files) => {
// sort array

View File

@ -131,7 +131,7 @@ router.get('/admin/order/delete/:id', common.restrict, (req, res) => {
});
// update order status
router.post('/admin/order/statusupdate', common.restrict, (req, res) => {
router.post('/admin/order/statusupdate', common.restrict, common.checkAccess, (req, res) => {
const db = req.app.db;
db.orders.update({_id: common.getId(req.body.order_id)}, {$set: {orderStatus: req.body.status}}, {multi: false}, (err, numReplaced) => {
if(err){

View File

@ -74,7 +74,7 @@ router.get('/admin/product/new', common.restrict, common.checkAccess, (req, res)
});
// insert new product form action
router.post('/admin/product/insert', common.restrict, (req, res) => {
router.post('/admin/product/insert', common.restrict, common.checkAccess, (req, res) => {
const db = req.app.db;
let doc = {
@ -145,7 +145,7 @@ router.post('/admin/product/insert', common.restrict, (req, res) => {
});
// render the editor
router.get('/admin/product/edit/:id', common.restrict, (req, res) => {
router.get('/admin/product/edit/:id', common.restrict, common.checkAccess, (req, res) => {
const db = req.app.db;
common.getImages(req.params.id, req, res, (images) => {
@ -176,7 +176,7 @@ router.get('/admin/product/edit/:id', common.restrict, (req, res) => {
});
// Update an existing product form action
router.post('/admin/product/update', common.restrict, (req, res) => {
router.post('/admin/product/update', common.restrict, common.checkAccess, (req, res) => {
const db = req.app.db;
db.products.findOne({_id: common.getId(req.body.frmProductId)}, (err, product) => {
@ -256,7 +256,7 @@ router.post('/admin/product/update', common.restrict, (req, res) => {
});
// delete product
router.get('/admin/product/delete/:id', common.restrict, (req, res) => {
router.get('/admin/product/delete/:id', common.restrict, common.checkAccess, (req, res) => {
const db = req.app.db;
// remove the article
@ -283,23 +283,21 @@ router.get('/admin/product/delete/:id', common.restrict, (req, res) => {
});
// update the published state based on an ajax call from the frontend
router.post('/admin/product/published_state', common.restrict, (req, res) => {
router.post('/admin/product/published_state', common.restrict, common.checkAccess, (req, res) => {
const db = req.app.db;
db.products.update({_id: common.getId(req.body.id)}, {$set: {productPublished: req.body.state}}, {multi: false}, (err, numReplaced) => {
if(err){
console.error(colors.red('Failed to update the published state: ' + err));
res.writeHead(400, {'Content-Type': 'application/text'});
res.end('Published state not updated');
res.status(400).json('Published state not updated');
}else{
res.writeHead(200, {'Content-Type': 'application/text'});
res.end('Published state updated');
res.status(200).json('Published state updated');
}
});
});
// set as main product image
router.post('/admin/product/setasmainimage', common.restrict, (req, res) => {
router.post('/admin/product/setasmainimage', common.restrict, common.checkAccess, (req, res) => {
const db = req.app.db;
// update the productImage to the db
@ -313,7 +311,7 @@ router.post('/admin/product/setasmainimage', common.restrict, (req, res) => {
});
// deletes a product image
router.post('/admin/product/deleteimage', common.restrict, (req, res) => {
router.post('/admin/product/deleteimage', common.restrict, common.checkAccess, (req, res) => {
const db = req.app.db;
// get the productImage from the db

View File

@ -2,15 +2,19 @@
<h2>Menu</h2>
<ul class="list-group">
<li class="list-group-item"><strong>Products</strong></li>
{{#ifCond session.isAdmin '===' 'true'}}
<li class="list-group-item"><i class="fa fa-plus-square-o fa-icon" aria-hidden="true"></i> &nbsp; <a href="/admin/product/new">New</a></li>
{{/ifCond}}
<li class="list-group-item"><i class="fa fa-list fa-icon" aria-hidden="true"></i> &nbsp; <a href="/admin/products">List</a></li>
<li class="list-group-item"><strong>Orders</strong></li>
<li class="list-group-item"><i class="fa fa-cube fa-icon" aria-hidden="true"></i> &nbsp; <a href="/admin/orders">List</a></li>
<li class="list-group-item"><strong>Customers</strong></li>
<li class="list-group-item"><i class="fa fa-users fa-icon" aria-hidden="true"></i> &nbsp; <a href="/admin/customers">List</a></li>
<li class="list-group-item"><strong>Users</strong></li>
{{#ifCond session.isAdmin '===' 'true'}}
<li class="list-group-item"><i class="fa fa-user-plus fa-icon" aria-hidden="true"></i> &nbsp; <a href="/admin/user/new">New</a></li>
<li class="list-group-item"><i class="fa fa-user fa-icon" aria-hidden="true"></i> &nbsp; <a href="/admin/users">Edit</a></li>
{{/ifCond}}
<li class="list-group-item"><i class="fa fa-user-circle-o fa-icon" aria-hidden="true"></i> &nbsp; <a href="/admin/user/edit/{{session.userId}}">My Account</a></li>
<li class="list-group-item"><strong>Settings</strong></li>
<li class="list-group-item"><i class="fa fa-cog fa-icon" aria-hidden="true"></i> &nbsp; <a href="/admin/settings">General settings</a></li>

View File

@ -1,145 +1,159 @@
{{> menu}}
<div class="col-lg-9">
<div class="row">
<div class="col-md-10">
<form method="post" id="settingsForm" action="/admin/settings/update" data-toggle="validator">
<h2 class="clearfix">General Settings <div class="pull-right"><button type="submit" id="btnSettingsUpdate" class="btn btn-success">Update</button></h2>
<div class="form-group">
<label>Cart name *</label>
<input type="text" class="form-control" name="cartTitle" value="{{config.cartTitle}}" required>
<p class="help-block">
This element is critical for search engine optimisation. Cart title is displayed if your logo is hidden.
</p>
</div>
<div class="form-group">
<label>Cart description *</label>
<input type="text" class="form-control" name="cartDescription" value="{{config.cartDescription}}" required>
<p class="help-block">This description shows when your website is listed in search engine results.</p>
</div>
<div class="form-group">
<label>Cart image/logo</label>
<input type="text" class="form-control" name="cartLogo" value="{{config.cartLogo}}">
</div>
<div class="form-group">
<label>Cart URL *</label>
<input type="text" class="form-control" name="baseUrl" value="{{config.baseUrl}}" required>
<p class="help-block">This URL is used in sitemaps and when your customer returns from completing their payment.</p>
</div>
<div class="form-group">
<label>Cart Email *</label>
<input type="email" class="form-control" name="emailAddress" value="{{config.emailAddress}}" required>
<p class="help-block">This is used as the "from" email when sending receipts to your customers.</p>
</div>
<div class="form-group">
<label>Flat shipping rate *</label>
<input type="text" class="form-control" name="flatShipping" value="{{config.flatShipping}}" required>
<p class="help-block">A flat shipping rate applied to all orders.</p>
</div>
<div class="form-group">
<label>Free shipping threshold</label>
<input type="text" class="form-control" name="freeShippingAmount" value="{{config.freeShippingAmount}}">
<p class="help-block">Orders over this value will mean the shipped will the FREE. Set to high value if you always want to charge shipping.</p>
</div>
<div class="form-group">
<label>Payment gateway</label>
<select class="form-control" name="paymentGateway">
<option {{selectState 'paypal' config.paymentGateway}} value="paypal">Paypal</option>
<option {{selectState 'stripe' config.paymentGateway}} value="stripe">Stripe</option>
</select>
<p class="help-block">You will also need to configure your payment gateway credentials in the `/config/&lt;gateway_name&gt;.json` file.</p>
</div>
<div class="form-group">
<label>Currency symbol</label>
<input type="text" class="form-control" name="currencySymbol" value="{{currencySymbol config.currencySymbol}}">
<p class="help-block">Set this to your currency symbol. Eg: $, £, €</p>
</div>
<div class="form-group">
<label>Theme</label>
<select class="form-control" name="theme">
<option {{selectState '' config.theme}} value="">Default</option>
{{#each themes}}
<div class="row">
<div class="col-md-10">
<form method="post" id="settingsForm" action="/admin/settings/update" data-toggle="validator">
<h2 class="clearfix">General Settings
<div class="pull-right">
<button type="submit" id="btnSettingsUpdate" class="btn btn-success">Update</button>
</h2>
<div class="form-group">
<label>Cart name *</label>
<input type="text" class="form-control" name="cartTitle" value="{{config.cartTitle}}" required>
<p class="help-block">
This element is critical for search engine optimisation. Cart title is displayed if your logo is hidden.
</p>
</div>
<div class="form-group">
<label>Cart description *</label>
<input type="text" class="form-control" name="cartDescription" value="{{config.cartDescription}}" required>
<p class="help-block">This description shows when your website is listed in search engine results.</p>
</div>
<div class="form-group">
<label>Cart image/logo</label>
<input type="text" class="form-control" name="cartLogo" value="{{config.cartLogo}}">
</div>
<div class="form-group">
<label>Cart URL *</label>
<input type="text" class="form-control" name="baseUrl" value="{{config.baseUrl}}" required>
<p class="help-block">This URL is used in sitemaps and when your customer returns from completing their payment.</p>
</div>
<div class="form-group">
<label>Cart Email *</label>
<input type="email" class="form-control" name="emailAddress" value="{{config.emailAddress}}" required>
<p class="help-block">This is used as the "from" email when sending receipts to your customers.</p>
</div>
<div class="form-group">
<label>Flat shipping rate *</label>
<input type="text" class="form-control" name="flatShipping" value="{{config.flatShipping}}" required>
<p class="help-block">A flat shipping rate applied to all orders.</p>
</div>
<div class="form-group">
<label>Free shipping threshold</label>
<input type="text" class="form-control" name="freeShippingAmount" value="{{config.freeShippingAmount}}">
<p class="help-block">Orders over this value will mean the shipped will the FREE. Set to high value if you always want to charge
shipping.</p>
</div>
<div class="form-group">
<label>Payment gateway</label>
<select class="form-control" name="paymentGateway">
<option {{selectState 'paypal' config.paymentGateway}} value="paypal">Paypal</option>
<option {{selectState 'stripe' config.paymentGateway}} value="stripe">Stripe</option>
</select>
<p class="help-block">You will also need to configure your payment gateway credentials in the `/config/&lt;gateway_name&gt;.json`
file.</p>
</div>
<div class="form-group">
<label>Currency symbol</label>
<input type="text" class="form-control" name="currencySymbol" value="{{currencySymbol config.currencySymbol}}">
<p class="help-block">Set this to your currency symbol. Eg: $, £, €</p>
</div>
<div class="form-group">
<label>Theme</label>
<select class="form-control" name="theme">
<option {{selectState '' config.theme}} value="">Default</option>
{{#each themes}}
<option {{selectState this ../config.theme}} value="{{this}}">{{this}}</option>
{{/each}}
</select>
<p class="help-block">Themes are loaded from `/public/themes/`</p>
</div>
<div class="form-group">
<label>Products per row</label>
<select class="form-control" name="productsPerRow">
<option value="{{config.productsPerRow}}" hidden="hidden" selected="selected">{{config.productsPerRow}}</option>
<option {{selectState '1' config.productsPerRow}}>1</option>
<option {{selectState '2' config.productsPerRow}}>2</option>
<option {{selectState '3' config.productsPerRow}}>3</option>
<option {{selectState '4' config.productsPerRow}}>4</option>
</select>
<p class="help-block">The number of products to be displayed across the page.</p>
</div>
<div class="form-group">
<label>Products per page</label>
<input type="number" class="form-control" name="productsPerPage" value="{{config.productsPerPage}}">
<p class="help-block">The number of products to be displayed on each page.</p>
</div>
<div class="form-group">
<label>Menu Enabled: </label>
<div class="checkbox">
<label><input class="settingsMenuEnabled" type="checkbox" {{checkedState config.menuEnabled}} id="menuEnabled" name="menuEnabled"></label>
{{/each}}
</select>
<p class="help-block">Themes are loaded from `/public/themes/`</p>
</div>
<p class="help-block">If a menu is set you can set it up <a href="/admin/settings/menu">here</a>.</p>
</div>
<div class="form-group">
<label>Menu header</label>
<input type="text" class="form-control" name="menuTitle" value="{{config.menuTitle}}" placeholder="Menu">
<p class="help-block">The heading text for your menu.</p>
</div>
<div class="form-group">
<label>Menu location: </label>
<select class="form-control" name="menuLocation">
<option {{selectState 'top' config.menuLocation}}>top</option>
<option {{selectState 'side' config.menuLocation}}>side</option>
</select>
<p class="help-block">The location of your menu.</p>
</div>
<div class="form-group">
<label>Footer HTML</label>
<textarea class="form-control codemirrorArea" rows="5" id="footerHtml" name="footerHtml">{{footerHtml}}</textarea>
<input type="hidden" id="footerHtml_input" name="footerHtml_input">
</div>
<div class="form-group">
<label>Google analytics</label>
<textarea class="form-control" rows="3" id="googleAnalytics" name="googleAnalytics">{{googleAnalytics}}</textarea>
<input type="hidden" id="googleAnalytics_input" name="googleAnalytics_input">
<p class="help-block">Your Google Analytics code. Please also inlude the "script" tags - <a href="https://support.google.com/analytics/answer/1032385?hl=en" target="_blank">Help</a></p>
</div>
<div class="form-group">
<label>Custom CSS</label>
<textarea class="form-control" rows="10" id="customCss" name="customCss">{{config.customCss}}</textarea>
<input type="hidden" id="customCss_input" name="customCss_input">
</div>
<div class="form-group">
<label>Email SMTP Host</label>
<input type="text" class="form-control" name="emailHost" value="{{config.emailHost}}" autocomplete="off" required>
</div>
<div class="form-group">
<label>Email SMTP Port</label>
<input type="text" class="form-control" name="emailPort" value="{{config.emailPort}}" autocomplete="off" required>
</div>
<div class="form-group">
<label>Email SMTP secure </label>
<div class="checkbox">
<label><input class="settingsMenuEnabled" type="checkbox" {{checkedState config.emailSecure}} name="emailSecure"></label>
<div class="form-group">
<label>Products per row</label>
<select class="form-control" name="productsPerRow">
<option value="{{config.productsPerRow}}" hidden="hidden" selected="selected">{{config.productsPerRow}}</option>
<option {{selectState '1' config.productsPerRow}}>1</option>
<option {{selectState '2' config.productsPerRow}}>2</option>
<option {{selectState '3' config.productsPerRow}}>3</option>
<option {{selectState '4' config.productsPerRow}}>4</option>
</select>
<p class="help-block">The number of products to be displayed across the page.</p>
</div>
</div>
<div class="form-group">
<label>Email SMTP Username</label>
<input type="text" class="form-control" name="emailUser" value="{{config.emailUser}}" autocomplete="off" required>
</div>
<div class="form-group">
<label>Email SMTP Password</label>
<input type="password" class="form-control" name="emailPassword" value="{{config.emailPassword}}" autocomplete="off" required>
</div>
<div class="form-group">
<button id="sendTestEmail" class="btn btn-success">Send test email</button>
</div>
</div>
</div>
</div>
<div class="form-group">
<label>Products per page</label>
<input type="number" class="form-control" name="productsPerPage" value="{{config.productsPerPage}}">
<p class="help-block">The number of products to be displayed on each page.</p>
</div>
<div class="form-group">
<label>Menu Enabled: </label>
<div class="checkbox">
<label>
<input class="settingsMenuEnabled" type="checkbox" {{checkedState config.menuEnabled}} id="menuEnabled"
name="menuEnabled">
</label>
</div>
<p class="help-block">If a menu is set you can set it up
<a href="/admin/settings/menu">here</a>.</p>
</div>
<div class="form-group">
<label>Menu header</label>
<input type="text" class="form-control" name="menuTitle" value="{{config.menuTitle}}" placeholder="Menu">
<p class="help-block">The heading text for your menu.</p>
</div>
<div class="form-group">
<label>Menu location: </label>
<select class="form-control" name="menuLocation">
<option {{selectState 'top' config.menuLocation}}>top</option>
<option {{selectState 'side' config.menuLocation}}>side</option>
</select>
<p class="help-block">The location of your menu.</p>
</div>
<div class="form-group">
<label>Footer HTML</label>
<textarea class="form-control codemirrorArea" rows="5" id="footerHtml" name="footerHtml">{{footerHtml}}</textarea>
<input type="hidden" id="footerHtml_input" name="footerHtml_input">
</div>
<div class="form-group">
<label>Google analytics</label>
<textarea class="form-control" rows="3" id="googleAnalytics" name="googleAnalytics">{{googleAnalytics}}</textarea>
<input type="hidden" id="googleAnalytics_input" name="googleAnalytics_input">
<p class="help-block">Your Google Analytics code. Please also inlude the "script" tags -
<a href="https://support.google.com/analytics/answer/1032385?hl=en"
target="_blank">Help</a>
</p>
</div>
<div class="form-group">
<label>Custom CSS</label>
<textarea class="form-control" rows="10" id="customCss" name="customCss">{{config.customCss}}</textarea>
<input type="hidden" id="customCss_input" name="customCss_input">
</div>
<div class="form-group">
<label>Email SMTP Host</label>
<input type="text" class="form-control" name="emailHost" value="{{config.emailHost}}" autocomplete="off" required>
</div>
<div class="form-group">
<label>Email SMTP Port</label>
<input type="text" class="form-control" name="emailPort" value="{{config.emailPort}}" autocomplete="off" required>
</div>
<div class="form-group">
<label>Email SMTP secure </label>
<div class="checkbox">
<label>
<input class="settingsMenuEnabled" type="checkbox" {{checkedState config.emailSecure}} name="emailSecure">
</label>
</div>
</div>
<div class="form-group">
<label>Email SMTP Username</label>
<input type="text" class="form-control" name="emailUser" value="{{config.emailUser}}" autocomplete="off" required>
</div>
<div class="form-group">
<label>Email SMTP Password</label>
<input type="password" class="form-control" name="emailPassword" value="{{config.emailPassword}}" autocomplete="off" required>
</div>
<div class="form-group">
<button id="sendTestEmail" class="btn btn-success">Send test email</button>
</div>
</div>
</div>
</div>