Browse Category

RedminPortal

Redmin Series Part 1: Building a Blog with RedminPortal

redminportal_logo_v2_smIn a series of tutorial, I’ll be showing you the steps of building a blog, a Podcast membership site, and an online shop for selling products, using the backend CMS RedminPortal.

RedminPortal is a Laravel 5 package as a flexible content management system for developers.

 

Prerequisite: This tutorial requires knowledge of HTML, PHP, Twitter Bootstrap and Laravel framework. It is meant for developers working on front end site. We’ll use Laravel 5.1 as the foundation due to its LTS support.

All the source codes will be uploaded to github project https://github.com/redooor/redminstore which will, by the end of this series, be a full front-end store system with RedminPortal. You can then use it as a base to build your own application.

In this first tutorial, we’ll be building a simple blog system (something like WordPress) as a Laravel package, using RedminPortal as the backend CMS.

You’ll learn how to:

  1. Build a Laravel package from scratch
  2. Create controller in Laravel
  3. Use Laravel’s Blade templating engine
  4. Use Laravel’s Localization
  5. Use RedminPortal to generate pages and posts
  6. Create dynamic routes to pages and posts
  7. Catch and show error messages such as HTTP 404

Installation

Laravel 5.1

You need to install a full Laravel 5.1 application. Follow the steps in this link and make sure you have a working local environment before proceeding. You should at least be able to access the home page of the application.

RedminPortal

Next, follow the steps in this link to install RedminPortal to your Laravel application. Take note that some features may not be available in version 0.3 yet. To use the latest (but unstable) version, change this line in composer.json:

"require": {
 "laravel/framework": "5.1.*",
 "redooor/redminportal": "0.3.*"
},

To

"require": {
 "laravel/framework": "5.1.*",
 "redooor/redminportal": "dev-develop"
},

Once you’ve installed, follow the migration and publishing steps to create the admin account. Make sure you can access the admin page and log in using the default admin account.

Laravel Package Creation

Folder Structure

Assuming you have your Laravel application installed inside a folder named “laravelapp”, let’s create a folder structure like this:

laravelapp
|- app
|- bootstrap
|- config
  /* other laravel folders omitted for brevity */
  /* create the following folder and its subfolders */
|- packages
  |- redooor
    |- redminstore
      |- src
        |- App
          |- Http
            |- Controllers
          |- Models
            |- UI
        |- facades
        |- resources
          |- lang
          |- views

Noticed that I have named the folders redooor/redminstore. We’ll be calling this package “Redminstore”. If you decide to rename it to something else, do take note that you need to change all references of “Redminstore” to your new name.

Also, because we’ll be using PSR-4 autoloading in our package, all folders inside folder “src” must stay the same. Otherwise the package will fail to work. Take note of the letter casing too. In Linux (most hosting providers use Linux) and Mac OS, the letter casing will affect autoloading.

I’ve left out the steps for setting up PHPUnit tests. If you’re interested, you may visit the github project to study how I did it.

From this point onwards, when I mention <package>, I’m referring to the path “laravelapp/packages/redooor/redminstore”.

Service Provider

Create a new file inside “<package>/src” folder, name it “RedminstoreServiceProvider.php” with this content:

<?php namespace Redooor\Redminstore;

use Illuminate\Support\ServiceProvider;

class RedminstoreServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        // Get routes
        include __DIR__.'/App/Http/routes.php';
        
        // Get views
        $this->loadViewsFrom(__DIR__.'/resources/views', 'redminstore');
        
        // Establish Translator Namespace
        $this->loadTranslationsFrom(__DIR__.'/resources/lang', 'redminstore');
        
        // Allow end users to publish and modify views
        $this->publishes([
            __DIR__.'/resources/views' => base_path('resources/views/vendor/redooor/redminstore'),
        ]);
        
        // Allow end users to publish and modify public assets
        $this->publishes([
            __DIR__.'/public' => public_path('vendor/redooor/redminstore'),
        ], 'public');
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        // Load autoload for package development environment only
        $autoloader = __DIR__ . '/../vendor/autoload.php';
        if (file_exists($autoloader)) {
            require_once $autoloader;
        }
        
        $this->app->register('Orchestra\Imagine\ImagineServiceProvider');
        
        $this->app->booting(function() {
            $loader = \Illuminate\Foundation\AliasLoader::getInstance();
            $loader->alias('Redminstore', 'Redooor\Redminstore\Facades\Redminstore');
            $loader->alias('Redminportal', 'Redooor\Redminportal\Facades\Redminportal');
            $loader->alias('Imagine', 'Orchestra\Imagine\Facade');
        });
    }
}

Package Facade

Create a new file inside “<package>/src/facades” folder, name it “Redminstore.php” with this content:

<?php namespace Redooor\Redminstore\Facades;

use Illuminate\Support\Facades\Facade;

class Redminstore extends Facade {

    /**
    * Get the registered name of the component.
    *
    * @return string
    */
    protected static function getFacadeAccessor() { return 'redminstore'; }

}

Routes

Create a new file inside “<package>/src/App/Http” folder, name it “routes.php” with blank content for now.

Base Controller

Create a new file inside “<package>/src/App/Http/Controllers” folder, name it “Controller.php” with this content:

<?php namespace Redooor\Redminstore\App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesCommands;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;

abstract class Controller extends BaseController {

	use DispatchesCommands, ValidatesRequests;

}

Package.json

This file contains the information of our package. It’s used when using with Composer and package repository such as Packagist.org.

Create a new file inside “<package>” folder and name it “package.json” with this content:

{
  "name": "redminstore",
  "version": "0.1.0",
  "description": "RedminStore is a frontend theme for Ecommerce sites using RedminPortal as backend.",
  "keywords": [
    "package",
    "laravel",
    "redooor",
    "redmin",
    "portal",
    "ecommerce",
    "frontend",
    "store"
  ],
  "repository": "https://github.com/redooor/redminstore",
  "homepage": "https://github.com/redooor/redminstore",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Redooor LLP",
  "license": "MIT",
  "engines": {
    "node": ">= 0.10.1"
  },
  "devDependencies": {
    "bower": "^1.7.1",
    "grunt": "^0.4.5",
    "grunt-autoprefixer": "^2.2.0",
    "grunt-banner": "^0.2.3",
    "grunt-contrib-clean": "^0.6.0",
    "grunt-contrib-concat": "^0.5.0",
    "grunt-contrib-connect": "^0.9.0",
    "grunt-contrib-copy": "^0.7.0",
    "grunt-contrib-cssmin": "^0.10.0",
    "grunt-contrib-less": "^0.12.0",
    "grunt-contrib-uglify": "^0.6.0",
    "grunt-contrib-watch": "^0.6.1",
    "grunt-exec": "^0.4.6"
  }
}

You may ignore those lines within devDependencies for now. They are meant for the full package development and not required in this tutorial.

Composer Setup

Create a new file inside “<package>” folder and name it “composer.json” with this content:

{
    "name": "redooor/redminstore",
    "description": "RedminStore is a frontend theme for Ecommerce sites using RedminPortal as backend.",
    "keywords": ["package", "laravel", "redooor", "redmin", "portal", "ecommerce", "frontend", "store"],
    "license": "MIT",
    "authors": [
        {
            "name": "Redooor LLP",
            "email": "support@redooor.com",
            "homepage": "http://www.redooor.com"
        }
    ],
    "require": {
        "php": ">=5.5.9",
        "laravel/framework": "5.1.*",
		"illuminate/html": "~5.0",
        "doctrine/dbal": "~2.4",
        "orchestra/imagine": "3.1.*",
        "phansys/getid3": "2.1.*@dev",
        "redooor/redminportal": "dev-develop"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.0",
		"mockery/mockery": "0.9.*",
        "orchestra/testbench": "3.1.*"
	},
    "autoload": {
        "classmap": [
            "tests/bases",
            "tests/RedminTestCase.php"
        ],
        "psr-4": {
            "Redooor\\Redminstore\\": "src/"
        }
    },
    "minimum-stability": "dev",
    "prefer-stable" : true
}

Once you’ve the composer.json ready, you will need to run composer to install all the dependencies.

Open a Terminal and run the commands:

cd laravelapp/packages/redooor/redminstore
composer install --prefer-dist -vvv --profile

–prefer-dist tells the installer to use distribution source if available.

-vvv is verbose mode, telling the installer to print its actions.

–profile tells the installer to save a cache in the system so next time you run it will be faster.

Visit https://getcomposer.org/ to learn more about Composer.

Root’s Composer.json

Now we need to tell the main application about the location of our newly created package.

Go to “laravelapp/composer.json” and add 1 more line to “autoload” -> “psr-4” section, like this (line 8):

"autoload": {
    "classmap": [
        "database"
    ],
    "psr-4": {
        "App\\": "app/",
        "Redooor\\Redminportal\\": "packages/redooor/redminportal/src",
        "Redooor\\Redminstore\\": "packages/redooor/redminstore/src"
    }
},

Root’s Config App.php

Next, go to “laravelapp/config/app.php” and add 1 more line under “providers” section, like this (line 18):

'providers' => [

    /*
     * Laravel Framework Service Providers...
     */
    Illuminate\Foundation\Providers\ArtisanServiceProvider::class,
    /* omitted for brevity */

    /*
     * Application Service Providers...
     */
    App\Providers\AppServiceProvider::class,
    App\Providers\AuthServiceProvider::class,
    App\Providers\EventServiceProvider::class,
    App\Providers\RouteServiceProvider::class,

    Redooor\Redminportal\RedminportalServiceProvider::class,
    Redooor\Redminstore\RedminstoreServiceProvider::class,

],

Run Composer Update

Open a Terminal and run the command inside the root laravalapp folder:

composer update --prefer-dist -vvv --profile

Master Template

Now, we’re almost ready to get into business. Let’s begin with the master template for the entire User Interface of our site. This template will contain the header and footer of the HTML layout, so we don’t have to repeat them in the rest of the pages we create. It will also define the top navigation bar for the site here.

Create a file inside “<package>/src/resources/views/layouts” folder and name it “master.blade.php” with this content:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
        <!--[if lt IE 9]>
        <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
        @section('head')
        <title>RedminStore by Redooor</title>
        @show
    </head>
    <body>
        <header id="header">
            <div class="navbar navbar-default">
                <div class="container">
                    <div class="navbar-header">
                        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                            <span class="icon-bar"></span>
                            <span class="icon-bar"></span>
                            <span class="icon-bar"></span>
                        </button>
                        <a href="{{ URL::to('/') }}" class="navbar-brand">{{ trans('redminstore::menus.brandname') }}</a>
                    </div>
                    <div class="navbar-collapse collapse">
                        <?php $menu_pages = Redooor\Redminstore\App\Models\UI\Menu::getPages(); ?>
                        @if (count($menu_pages) > 0)
                        <ul class="nav navbar-nav">
                            <li class="dropdown">
                                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ trans('redminstore::menus.pages') }} <span class="caret"></span></a>
                                <ul class="dropdown-menu">
                                    @foreach ($menu_pages as $menu_page)
                                    <li><a href="{{ URL::to('page/' . $menu_page->slug) }}">{{ $menu_page->title }}</a></li>
                                    @endforeach
                                </ul>
                            </li>
                        </ul>
                        @endif
                        <ul class="nav navbar-nav navbar-right">
                            <li><a href="{{ url('/admin') }}">{{ trans('redminstore::menus.adminlogin') }}</a></li>
                        </ul>
                    </div><!--/.nav-collapse -->

                </div>
            </div>
        </header>

        <div id="main">
            <div class="container">
                @yield('content')
            </div>
        </div><!--End main-->
        
        <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>

        @section('footer')
        @show
    </body>
</html>

If you’re new to Laravel’s Blade template, take special attention to all the code beginning with an @ sign. Read here to study more about Blade template.

The HTML is a bare minimum site to get things working with Bootstrap. You may have a different HTML template altogether. But do take note of the following lines.

Line 10 to 12: I’ve created a section “head” that can be overwritten by any child template that inherits from this master template. You will see this in action soon when we create a child template.

Line 27: I’m calling a static function to grab all the public pages from RedminPortal, and then use the returned list of pages to generate the dropdown menu in lines 28 to 39. We don’t have that static function Menu::getPages() yet. Let’s create it soon under section “Create UI Menu”.

Line 28 to 39: This dropdown menu is generated with an if-else statement and a returned list of public pages from static function Menu::getPages(). If the function returns a null, the dropdown menu will not be created. So you will not see this dropdown menu until you create at least 1 page with “Private” option unchecked in RedminPortal.

Line 24 and 41: The curly brackets {{}} is Laravel’s equivalent of PHP’s echo. So whatever is within the curly brackets will be printed on the page. I’m using the helper function trans() here to print the localization text. Let’s create the localization file soon under section “Localization”. Read more here about Laravel’s Localization.

Line 51: This is where the child template gets its content printed. We will see it in action soon when we create child template.

Line 58 to 59: This is another section where child template can insert its own footer into master template. This is useful if you have specific JavaScript that is used in 1 child template only and doesn’t make sense to put it in master template.

Create UI Menu

As mentioned previously, we need to create a static function to retrieve all public pages to return to the master template.

Create a file inside “<package>/src/App/Models/UI” folder and name it “Menu.php” with this content:

<?php namespace Redooor\Redminstore\App\Models\UI;

use Redooor\Redminportal\App\Models\Page;

class Menu
{
    /**
    /* Get all pages.
    /*
    /* @param bool $private To retrieve private or public pages, default public.
    /* @return array(Page) or null if nothing found
     */
    public static function getPages($private = false)
    {
        $pages = Page::where('private', $private)->get();
        
        return $pages;
    }
}

We’re using eloquent model Redooor\Redminportal\App\Models\Page from the CMS RedminPortal. Only those pages with flag “private” marked as false will be returned. To study more about Eloquent model in Laravel, read here.

Localization

All localization files must be stored in the folder “<package>/src/resources/lang”. The language are denoted by the subfolder with “en” as the default. For example, if you want to localize to Simplified Chinese, simply add a subfolder name “sc”. Learn more about Laravel’s Localization here.

Now, let’s create a file inside “<package>/src/resources/lang/en” folder and name it “menus.php” with this content:

<?php

return array(
    'brandname' => 'RedminStore',
    'adminlogin' => 'Admin Login',
    'pages' => 'Pages'
);

In this tutorial, we’ve only created 3 items for “brandname”, “adminlogin” and “pages”.

To use them in any template, simply call the helper function trans() like this:

{{ trans('redminstore::menus.brandname') }}
{{ trans('redminstore::menus.adminlogin') }}
{{ trans('redminstore::menus.pages') }}

The convention for our tutorial will be:

trans('redminstore::menus.<key>')

Why do we use localization in this simple tutorial? Well, I just wanted to show you how you can use localization. But most importantly, if you decide to use RedminStore as the base to build your product, you can always overwrite the localization file without changing the Master Template. Just go to your root laravelapp folder, and add that edited “menus.php” file to the folder “laravelapp/resources/lang/packages/en/redminstore/” and you’re good to go.

Child Templates

Let’s create our first child templates to test our setup.

404 Template

We’ll start with a simple 404 page to tell users that the page they are trying to view cannot be found. This is useful for returning non-existence page later on.

Create a new file inside “<package>/src/resources/views/general” folder and name it “404.blade.php” with this content:

@extends('redminstore::layouts.master')

@section('content')
    <div class='row'>
        <div class='col-md-12'>
            <div class="jumbotron">
                <h1>Oops! 404, Page Not Found.</h1>
                <p>Unfortunately, we're not able to find that URL.</p>
            </div>
        </div>
    </div>
@stop

This is a much shorter template than our master template because we’re extending all layout from layouts.master template.

Line 1: This template extends the content from views/layouts/master.blade.php.

Line 3 and 12: We can now insert our child template’s content into master within this section “content”.

Home Template

This template will be used as the landing page for our site. So when user goes to the root site (e.g. laravelapp.app), we will show this page to them.

Create a new file inside “<package>/src/resources/views/pages” folder and name it “home.blade.php” with this content:

@extends('redminstore::layouts.master')

@section('content')
    <div class='row'>
        <div class='col-md-8'>
            <div class="panel panel-default">
                <div class="panel-body">
                    <h1>RedminStore <small>by Redooor</small></h1>
                    <p>This is a demo template to help you get started with RedminPortal CMS.</p>
                    <p>Try adding a few pages in RedminPortal and watch the page being generated automatically.</p>
                </div>
            </div>
        </div>
        <div class='col-md-4'>
            <div class="well well-sm shadow-depth-1">
                <img src="{{ URL::to('vendor/redooor/redminstore/img/redminportal_logo.png') }}" title="ReminPortal" class="img-responsive pull-right">
                <h3>Contribution</h3>
                <p>Source code at <a href="https://github.com/redooor/redminstore">Github</a></p>
                <h3>Maintenance</h3>
                <p>Maintained by the core team with the help of our contributors.</p>
                <h3>License</h3>
                <p>RedminPortal is open-sourced software licensed under the <a href="http://opensource.org/licenses/MIT">MIT license</a>.</p>
            </div>
        </div>
    </div>
@stop

Our First Routes

In previous section, we’ve created 2 templates but there’s no way to see these pages yet. Do view these pages, we need to set up some routes.

If you go to your root site now (e.g. laravelapp.app), you will see the default page with a big Laravel text. That default page comes with every new Laravel installation.

Removing Default Routes

Let’s remove all the default routes from new Laravel installation by deleting the content in “laravalapp/app/Http/routes.php” file.

When you’re done deleting the content, go to your root site again (e.g. laravelapp.app) and you should see an error message saying the route does not exist.

Inserting Our Package Routes

If you look at previously created file RedminstoreServiceProvider.php (Line 15), you would notice that we are loading our routes.php file on boot. This ensures that our package’s routes come after the application’s routes. With that in place, we can save as many routes in our package’s routes.php as we want without changing the root laravelapp’s routes.php.

Custom Routes

That said, we can now edit our package’s routes at “<package>/src/App/Http/routes.php” with this content:

<?php

/*
|--------------------------------------------------------------------------
| Package Routes
|--------------------------------------------------------------------------
*/

Route::group(['namespace' => 'Redooor\Redminstore\App\Http\Controllers'], function () {
    Route::get('/', 'PageController@showHome');
    Route::get('page', 'PageController@show404');
    Route::get('page/{slug}', 'PageController@loadPage');
});

Line 10: This line replaces the root path (e.g. laravelapp.app) which we had previously deleted from the root’s routes.php. It’s loading PageController’s function showHome().

Line 11: This line points to a new route with “/page” appended after the site path (e.g. laravelapp.app/page). This page serves as an error page when no slug is given. It’s loading PageController’s function show404().

Line 12: This line points to any route with “/page/<slug>” appended after the site path (e.g. laravelapp.app/page/<slug>). This page shows the content of RedminPortal::Page model that matches the given slug. It’s loading PageController’s function loadPage().

These routes will not work yet. We need the PageController to provide the content of the page to the router.

Next, let’s create the PageController.

PageController

This PageController is responsible of returning the content of the page to the router. I’ve introduced 3 functions in the previous section. They are:

  • showHome()
  • show404()
  • loadPage($slug)

Create a new file in “<package>/src/App/Http/Controllers” folder and name it “PageController.php” with this content:

<?php namespace Redooor\Redminstore\App\Http\Controllers;

use Validator;
use Redooor\Redminportal\App\Models\Page;

class PageController extends Controller
{
    public function showHome()
    {
        return view('redminstore::pages.home');
    }
    
    public function show404()
    {
        return view('redminstore::general.404');
    }
    
    public function loadPage($slug)
    {
        $inputs = array('slug' => $slug);
        $rules = array('slug' => 'required|alpha_dash');
        $validation = Validator::make($inputs, $rules);
        
        if ($validation->passes()) {
            $page = Page::where('slug', $slug)->where('private', false)->first();
            
            if ($page) {
                return view('redminstore::pages.view')
                    ->with('page', $page);
            }
        }
        
        return view('redminstore::general.404');
    }
}

Return View

showHome() and show404() simply return the templates we had created earlier on. showHome() return the view from pages.home while show404() return the view from general.404. In these templates, the content are static and we’re not expect any variable inside.

Notice the naming convention we’re using here.

view('redminstore::pages.home')

The package’s name must be included before the folder.file name convention. Without the package’s name, Laravel will attempt to load the files from the root laravelapp resources/views folder instead.

So in Laravel, this

return view('pages.home');
>>> result folder: laravelapp/resources/views/pages/home.blade.php

is different from this

return view('redminstore::pages.home');
>>> result folder: laravelapp/packages/redooor/redminstore/src/resources/views/pages/home.blade.php

Now, try loading the root site (e.g. laravelapp.app) and ‘/page’ site (e.g. laravelapp.app/page). You should see the respective page. If not, revisit all the steps above to ensure all files and folders are in place.

If you try loading the ‘/page/<slug>’ site (e.g. laravelapp.app/page/test-page’), you should see an error message. This is because we have not created the template for pages.view yet.

Create Dynamic Page View

PageController::loadPage($slug) function searches all non-private (i.e. public) pages that have slug matches the given $slug, and then pass the RedminPortal::Page model to the template before returning the view.

The passing of data into the template is this line:

return view('redminstore::pages.view')->with('page', $page);

And now, create a new file in “<package>/src/resources/views/pages” folder and name it “view.blade.php” with this content:

@extends('redminstore::layouts.master')

@section('head')
<title>Redminstore: {{ $page->title }}</title>
@stop

@section('content')
    {!! $page->content !!}
@stop

As you can see, it is a very simple template that extends from layouts.master.

Line 4: Overwrites the <title> of the page with the Page’s title.

Line 8: Outputs the content of the returned Page result. We want the full content (except the navigation bar) to be whatever the blogger has written.

Catching non-existence page

If no Page is found for the given slug, the loadPage($slug) function will return the 404 page to the router.

You can try it by passing any non-existence slug to the site path (e.g. laravelapp.app/page/no-such-page-exist).

Create Page in RedminPortal

Finally, to see our work in action, login to RedminPortal CMS via “/admin” site (e.g. laravelapp.app/admin) with the default credential (if you’re lost, refer to RedminPortal installation guide).

In the menu, go to “Pages” under “Content Management”. Create a few pages with the “Private” flag unchecked.

Then go to root site (e.g. laravelapp.app) and you should see the “Pages” dropdown menu. Open it to select the pages you have created. It should be (almost) What-You-See-Is-What-You-Get.

End of Part 1

“What? How about Post? I thought you said we will learn how to create posts as well?”

Ah, well, in a way, I lied. :p

However, the steps for generating Post is exactly the same as generating Page. It’s just a matter of duplicating and changing the name.

Give it a try and see if you can create your own PostController, view and route.

I’ve uploaded my code in github. If you really can’t get it, study my code in github or drop me a comment below. I’ll try as much as possible to answer your questions.

RedminPortal Developer Guide: How to use Coupon?

In this tutorial, I’ll show you how to apply coupon to your products and calculate the discounted price.

Note: This tutorial refers to the latest develop branch which features may not be in the master branch yet. These features will eventually be ported to develop-v0.2 branch, which supports PHP 5.4.

Overview of RedminPortal

redminportal_logo_v2_sm

RedminPortal is a content management system for developers. It only offers backend system for end users and leave the front end solely to the developer. Hence, in many cases, it is up to the developers to design their business logic for their front end.

Let’s Begin

In this tutorial, I’ll be showing you one way of using the coupon. However, it is up to your imagination and creativity to come up with many other ways. Treat this as a guiding ground but keeping in mind that this is not the only way.

Product creation

From the portal, create as many products as you see fit.Redminportal products created

Coupon Creation

Next, create coupons that are “restricted to” these products. You can either restrict the coupons to Categories, Products, Module/Membership or Bundles.Redminportal coupons created

How to create a new order?

Let’s assume we want to have a Cart class that creates a new order and add products into it. Here’s the code:

use Auth;
use Redooor\Redminportal\App\Models\Order;

class Cart
{
  public $order;

  function __construct()
  {
    $user = Auth::user();

    $this->order = new Order;
    $this->order->user_id = $user->id;
    $this->order->paid = 0;
    $this->order->transaction_id = date('Ymd') . '_order_' . $user->id;
    $this->order->payment_status = 'Pending';
    $this->order->options = ''; //Optional
    $this->order->save();
  }
}

Now, to create a new cart in your code, simply do:

$cart = new Cart;

How to add products to an order?

Most (if not all) of the models in Redminportal use Eloquent relationships. To add any product into an order, you just need to save the Product object into Order object. I’ll create a new function in Cart class for adding product.

class Cart
{
  public $order;
  
  function __construct()
  {
    /* omitted */
  }

  public function addProduct($product_id)
  {
    $product = Product::find($product_id);
    
    if ($product) {
      $this->order->products()->save($product);
    }
    
    return $product; // return null if no product found
  }
}

Notice in line 15 we’re using relationship insertion to add product into order.

How to get the total price of an order?

There’s a method in Order class for you to get the total sum of all the product prices. It’s located at <redminportal>/src/App/Models/Order.php.

In our example, if you want to get the total sum of all the prices in a cart, do this:

$totalprice = $cart->order->getTotalprice();

NOTE: This total price does not include discounted value from coupon. It is purely the summation of the prices of all products/pricelists/bundles.

How to apply a coupon to an order?

Using the same relationship insertion, you can add any coupon into an order like this:

$coupon = Coupon::find($coupon_id);

if ($coupon) {
  $cart->order->coupons()->save($coupon);
}

 

This insertion doesn’t check if the coupon is applicable to any product within the order. It merely adds the coupon to the order.

To add a coupon and making sure that it checks the “multiple_coupons” flag, you can use this method addCoupon():

$coupon = Coupon::find($coupon_id);

if ($coupon) {
  $cart->order->addCoupon($coupon);
}

All other checks are up to you, the developers.

How to get the total discounts in an order?

You can use the method getDiscounts() to get a list of all the products with applicable coupons. And then calculate the discount from there.

Note: the value of each discount is stored in the record key “value”.

I will add this function to the Cart class.

public function getTotaldiscount()
{
  $all_discounts = $this->order->getDiscounts();

  $totalDiscount = 0;

  foreach ($all_discounts as $discount) {
    $totalDiscount += $discount['value'];
  }

  return $totalDiscount;
}

// Alternative, shorter code with Laravel's collection
public function getTotaldiscount()
{
  return collect($order->getDiscounts())->sum('value');
}

You can then calculate the total payable amount like this:

$totalpayable = $cart->order->getTotalprice() - $cart->getTotaldiscount();
Update 14 Dec 2015:

Using the latest develop branch, you can now retrieve the total discount from an order like this:

$cart->order->getTotaldiscount();
Caution:

Take note that getDiscounts() saves all the discount information in the $order->options[‘discounts’] array. This is to reduce recalculation whenever you call this method.

However, when you add or remove any coupon or product from the order, you need to call setDiscounts() method to recalculate the new value. Like this:

/* after adding some products */
/* and then add/remove some coupons */

$cart->order->setDiscounts();

$new_totalpayable = $cart->order->getTotalprice() - $cart->getTotaldiscount();

 

That’s it. Do leave a comment if there’s any mistake and I’ll update the post.