Laravel Shop tutorial #1 – Building a review system

The first tutorial in “Building a shop” series is going to be a practical example of implementing a rating/review system into an application. Review systems or rating systems are very common in all kinds of e-Commerce projects, social networks, anything that requires user’s input to be some sort of concrete value on some scale.

DEMO: http://demos.maxoffsky.com/shop-reviews Source on Github : https://github.com/msurguy/laravel-shop-reviews

A very common example of a review system is when the user can submit a rating of 1-5 stars and a comment that goes with the rating like on Etsy.com:

Imagine this, you have an online shop that has products, products have reviews that consist of a rating and a comment.

This is the idea that we will replicate in this tutorial.

Here’s how this tutorial will follow the steps to reach that goal:

  1. See the Demo of the project in action to get an idea of what you will make (the link is above at the beginning of tutorial)
  2. Think about efficiency
  3. Define the routes for showing the products, showing individual product and submitting the reviews
  4. Define the database structure and the models for the products and for the reviews
  5. Create the view templates to display the shop home page and the individual product page.
  6. Create functions that will calculate and store ratings/reviews
  7. Create logic for the defined routes
  8. Refactor the code if necessary

I have looked at lots of jQuery plugins that make it a bit easier to create a nice user experience for rating a product by the number of stars/hearts, and I have found this little plugin that does the job efficiently: https://github.com/dobtco/starrr I have modified it to work with Bootstrap 3 and the modified version is included in the source code of this application.

What are the difficulties of implementing such a review system?

Well, first of all efficiency is an important factor that we will have to include in making architectural decisions. As an example, let’s think about how the product will store user’s reviews.

One way to store reviews is to store everything separately – the rating, the comment and the product each in a separate database table. We could relate the rating to the comment (one to one relationship) and then store the comment under a product as one to many relationship (a product has many reviews).

A disadvantage of this way of storing the data for reviews is that there is quite a bit of overhead (a bit too much nesting which mean lots of inefficient SQL joins). Why don’t we combine the rating and the comment into one entity and call it “Review”? Perhaps this will be a better structure for storing the comment and rating data together.

When the user submits a review, we will store both a rating (consisting of number of stars the user gives this product) and a comment (perhaps the user has something to say about the product) as a new row in table designated to store our reviews.

We also need to somehow calculate an average rating for each product based on submitted reviews and show it on product’s page and on the page that lists all products.

Online shops usually have pages that display many products at once. Take for example the homepage that shows a list of latest products. It is vital for shop’s performance that displaying products and a review count/rating will be a quick task for our application, with as few SQL queries as possible.

Taking this into an account, if we try to calculate the average rating for the product on the fly we will run into performance issues. Therefore as a step of precaution, let’s save the calculated ratings in the products table (for example as rating_cache) as well as saving all the reviews in the designated table. This way when we retrieve a product from the database, we will have all necessary information to display right away instead of executing too many unnecessary SQL queries.

Well, enough talking! Let’s see the application in action! I have made the code available on Github here and the live application is at http://demos.maxoffsky.com/shop-reviews play with it, leave some reviews for products and see how the application feels.

As we have thought about the efficiency and have checked out the demo of the application in action, let’s define the routes for the application.

Defining the routes for the shop homepage, displaying the product and submitting a review.

Our application will have 3 simple routes, a route that shows all products, a route that shows individual product and a a route that is executed upon submittal of a review.

Let’s open up the routes.php file and create a placeholder for these routes:

<?php

// Route for Homepage - displays all products from the shop
Route::get('/', function()
{
  return 'Shop homepage';
});

// Route that shows an individual product by its ID
Route::get('products/{id}', function($id)
{
  return 'Product: '.$id;
});

// Route that handles submission of review - rating/comment
Route::post('products/{id}', array('before'=>'csrf', function($id)
{	
  return 'Review submitted for product '.$id;
}));

This seems like a good start. These route placeholders will be filled out as we are progressing through the tutorial. Now let’s define what the database for this application will look like and what its data models will look like.

Defining the database structure and the data models:

Let’s think a bit about where the data will live for the application. I have created a simple diagram showing the different components of the product and reviews that users will leave on the product. Please look at the diagram below.

Let’s define a database structure that will store the data according to the diagram above.

The database will have the following tables:

  • Users (to store user information like name, email, password to log in)
  • Products (Will store product – related data such as name, descriptions, icon for the product, and finally rating cache and rating count)
  • Reviews (Will store reviews that users will leave for the product)

Here is the database structure visualized (click to see it bigger):

And here is the SQL for the database, including some sample data for the products:

CREATE TABLE `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `published` tinyint(1) NOT NULL DEFAULT '0',
  `rating_cache` float(2,1) unsigned NOT NULL DEFAULT '3.0',
  `rating_count` int(11) unsigned NOT NULL DEFAULT '0',
  `name` varchar(255) NOT NULL,
  `pricing` float(9,2) unsigned NOT NULL DEFAULT '0.00',
  `short_description` varchar(255) NOT NULL,
  `long_description` text NOT NULL,
  `icon` varchar(255) NOT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

INSERT INTO `products` (`id`, `published`, `rating_cache`, `rating_count`, `name`, `pricing`, `short_description`, `long_description`, `icon`, `created_at`, `updated_at`) VALUES
(1, 1, 3.0, 0, 'First product', 20.99, 'This is a short description asdf as This is a short description asdf as', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum', '', '2013-11-06 05:11:00', '2013-11-12 05:51:07'),
(2, 1, 3.0, 0, 'Second product', 55.00, 'This is a short description', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum', '', '2013-11-06 05:11:00', '2013-11-11 16:17:23'),
(3, 1, 3.0, 0, 'Third product', 65.00, 'This is a short description', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum', '', '2013-11-06 05:11:00', '2013-11-06 06:08:00'),
(4, 1, 3.0, 0, 'Fourth product', 85.00, 'This is a short description', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum', '', '2013-11-06 05:11:00', '2013-11-06 06:08:00'),
(5, 1, 3.0, 0, 'Fifth product', 95.00, 'This is a short description', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum', '', '2013-11-06 05:11:00', '2013-11-06 06:08:00');

CREATE TABLE `reviews` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `product_id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  `rating` int(11) NOT NULL,
  `comment` text NOT NULL,
  `approved` tinyint(1) unsigned NOT NULL DEFAULT '1',
  `spam` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_type` int(10) unsigned NOT NULL DEFAULT '0',
  `email` varchar(128) NOT NULL,
  `password` varchar(128) NOT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

Whew! Now we can relate all this data together using Laravel’s models. Shall we?!

First, let’s create the model for Products that will relate reviews by one to many relationship, name it “Product.php” and place it into app/models folder:

<?php

class Product extends Eloquent
{
  public function reviews()
  {
	return $this->hasMany('Review');
  }
}

Then let’s relate the reviews to the products, to users and create some scopes for convenience, each review belongs to one user, and review can belong to only one product, the scopes will serve as shortcuts when we need to filter out reviews that are marked as spam(let’s say by the administrator), place the following into “app/models/Review.php“:

<?php

class Review extends Eloquent
{

  public function user()
  {
    return $this->belongsTo('User');
  }

  public function product()
  {
    return $this->belongsTo('Product');
  }

  public function scopeApproved($query)
  {
    return $query->where('approved', true);
  }

  public function scopeSpam($query)
  {
    return $query->where('spam', true);
  }

  public function scopeNotSpam($query)
  {
    return $query->where('spam', false);
  }
}

This is all we need for our relationships! We will add some additional functions into the models a bit later to do the necessary rating calculations/storing of ratings, but for now this will be enough for us to continue. Up next, creating view templates for displaying the products and the reviews.

Creating view templates to show products and reviews

I assume you know a thing or two about Laravel at this point so I won’t get too much into building templates and layouts, but couple things I wanted to mention is that for the review UI I have created a snippet on my Bootsnipp titled “Expanding review and rating box” which you can see below (using Bootstrap 3 for the user interface):

And for outputting user submitted reviews as a part of the product template I use the following foreach loop:

@foreach($reviews as $review)
  <hr>
  <div class="row">
    <div class="col-md-12">
    @for ($i=1; $i <= 5 ; $i++)
      <span class="glyphicon glyphicon-star{{ ($i <= $review->rating) ? '' : '-empty'}}"></span>
    @endfor

    {{ $review->user ? $review->user->name : 'Anonymous'}} <span class="pull-right">{{$review->timeago}}</span> 

    <p>{{{$review->comment}}}</p>
    </div>
  </div>
@endforeach

The $review->timeago is a presenter function that I will add to the app/models/Review.php:

// Attribute presenters
...
public function getTimeagoAttribute()
{
  $date = CarbonCarbon::createFromTimeStamp(strtotime($this->created_at))->diffForHumans();
  return $date;
}
...

Also, you might have noticed the triple brace used to output the $review->comment. This is Laravel’s way to not allow things like <script></script> in displaying the comments. I chose to not filter the comment data on input but rather when the comment is displayed by using Blade’s triple braces trick.

You can see the complete view templates at https://github.com/msurguy/laravel-shop-reviews.

At this point there are only few things left to finish, store the user’s reviews somehow and also connect all the routes to the functionality that we have built.

Creating helper functions to store reviews and calculate ratings

First, let’s create a helper function (let’s call it storeReviewForProduct) that will do the following :

  • take product ID, user submitted comment and rating
  • attach the comment and the rating as a child model to the product
  • call a function to recalculate all ratings for the product

This function will be a part of the app/models/Review.php model, so let’s add it there:

...

public function getTimeagoAttribute()
{
  ...
}

// this function takes in product ID, comment and the rating and attaches the review to the product by its ID, then the average rating for the product is recalculated
public function storeReviewForProduct($productID, $comment, $rating)
{
  $product = Product::find($productID);

  // this will be added when we add user's login functionality
  //$this->user_id = Auth::user()->id;

  $this->comment = $comment;
  $this->rating = $rating;
  $product->reviews()->save($this);

  // recalculate ratings for the specified product
  $product->recalculateRating();
}

For now we will not save logged in user’s id along with the comment simply because we have not built a login system.

As you might have noticed, we are calling recalculateRating function on the product instance, that function is not defined yet, so let’s create it in the app/models/Product.php model (I include comments that explain what happens when that function is executed):

<?php

class Product extends Eloquent
{
  public function reviews()
  { ... }

  // The way average rating is calculated (and stored) is by getting an average of all ratings, 
  // storing the calculated value in the rating_cache column (so that we don't have to do calculations later)
  // and incrementing the rating_count column by 1

  public function recalculateRating()
  {
    $reviews = $this->reviews()->notSpam()->approved();
    $avgRating = $reviews->avg('rating');
    $this->rating_cache = round($avgRating,1);
    $this->rating_count = $reviews->count();
    $this->save();
  }
}

Alright. The major part of the application is done! The only thing left is to connect all of our routes so that the applications becomes alive!

Connecting routes to views

In the beginning of the tutorial we have defined three route placeholders for the application, one that shows all products, one that shows an individual product and one that’s handling the review submission. Now let’s fill those routes with some logic that the application needs.

Home page route only needs to display all products from the shop:

// Route for Homepage - displays all products from the shop
Route::get('/', function()
{
  $products = Product::all();
  return View::make('index', array('products'=>$products));
});

The route to display a single product (when you click on a product from the homepage) needs to do two things, it needs to retrieve a product by id specified in the route parameter and it needs to retrieve a paginated collection of reviews that are marked as approved and not spam:

// Route that shows an individual product by its ID
Route::get('products/{id}', function($id)
{
  $product = Product::find($id);
  // Get all reviews that are not spam for the product and paginate them
  $reviews = $product->reviews()->with('user')->approved()->notSpam()->orderBy('created_at','desc')->paginate(100);

  return View::make('products.single', array('product'=>$product,'reviews'=>$reviews));
});

And lastly, when the review is submitted from the product page, we will validate that the review corresponds to validation rules(I put them in the Review model to make the route a bit cleaner), and if the validation passes, we will store the review in DB and redirect the user back to the product page with a message that the review has been posted:

// Route that handles submission of review - rating/comment
Route::post('products/{id}', array('before'=>'csrf', function($id)
{
  $input = array(
	'comment' => Input::get('comment'),
	'rating'  => Input::get('rating')
  );
  // instantiate Rating model
  $review = new Review;

  // Validate that the user's input corresponds to the rules specified in the review model
  $validator = Validator::make( $input, $review->getCreateRules());

  // If input passes validation - store the review in DB, otherwise return to product page with error message 
  if ($validator->passes()) {
	$review->storeReviewForProduct($id, $input['comment'], $input['rating']);
	return Redirect::to('products/'.$id.'#reviews-anchor')->with('review_posted',true);
  }

  return Redirect::to('products/'.$id.'#reviews-anchor')->withErrors($validator)->withInput();
}));

For a complete routes.php file please look in the application repository. I have omitted showing complete blade templates in this post as they are quite large, you can see them in the repository as well.

Refactoring ideas :
1) Move the logic from the routes to controllers (for example create shop controller and product controller and display index page from shop controller and all things related to individual product from a product controller).
2) Clean up the models by separating view presenters scopes and relationships.
3) Prevent the same user from submitting more than one review on a product (when you have a login system implemented first)

At this point the application should work as intended and you will be able to have users submit reviews to your shop/gallery/whatever website!

This concludes the first part of “Building a shop” comprehensive tutorial! Do you like the series? Do you want more of comprehensive tutorials like this? Let me know in the comments and make sure to follow me on Twitter!

You may also like

  • kenny

    Just order your book.

    Hope more tutorial from shop serial.

  • Great post, Maks. Looking forward to the rest of the series.

    I’m working on a project where a review system will be implemented. Will most likely take your considerations into account 😉

    If I may add, wouldnt using interfaces and repositories create more flexible controllers regarding your refactoring ideas? The practice of coding to an interface and (more or less) abstracting the database layer seems to me to be a good start to insure future complications/changes. This should also make testing and mocking easier/better, no?

    Thanks for the tip regarding Laravel hosting. Pagoda has been working out great so far.

    Greetings from Denmark 🙂

  • David M.

    Hi Maks,
    This was far the most thorough and most practical Laravel tutorial that I have seen to date, so THANK YOU and I hope you can continue to make time to share your Laravel knowledge in your blog by demonstrating a practical application, as you have been doing.

    I really hope I can get to the point where I can bang out a simple Laravel application within couple of days like you do with most of the Laravel sites you have deployed.

    There’s one thing that I want to pick your brain on. It makes sense track the authenticated user id for reviews and even bookmark/favorites but I also want to support “Likes” features where unregistered user can “thumbs up” on a product by “liking it” but at the same time, I want to prevent the same user doing it more than once. I know that only way this can be accomplished is by tracking the IP address but I’m more concerned about the best way of implementing this since efficiency/performance is my top concern. Rather than tracking IP address as a child (one-to-many) relationship, I was thinking maybe appending the IP addresses in a text field at the product level then check to see if that matching IP address exists in that field to prevent multiple likes. What do you think about this approach?

    Any chance you can also do enhancements to this tutorial series by implementing the “likes” features that I have mentioned? (with support for unique unregistered users)

    Cheers!
    -David

  • Maks Surguy

    This is possible to accomplish in few ways. One of the ways I am thinking of implementing is to save the IDs of products that the user liked in the session and not allow to like the same products again if the ID is already in the session. Sessions don’t require the users to be registered, it simply creates a cookie on the client side(browser) and a special object on the server side.
    One aspect that you would need to adjust is the amount of time that the session is active (not expired), Laravel apps have those configurations in the app/session.php config file.

    What do you think?

  • Maks Surguy

    Thanks! I am glad this is helpful.

    Definitely using interfaces and repositories would organize the code at refactoring stage but if I start saying which way it should be done I am sure I will get tons of people that would disagree so I try to keep out of those arguments by providing the basics (even using routes) and letting people figure out which way they want to do this on their own.

  • David M.

    That would be too easy 🙂
    I thought about that approach but people can abuse the “Likes” by not allowing the cookies on the browser setting or even deleting the cookies. The only way to have a full control of this without forcing the user to register/login is by tracking it on the server side (although they can use different IP address to cheat the “Likes” this is harder than simply deleting the cookies).

  • Maks Surguy

    I personally think you are overthinking the problem. Honestly, how many people do even know about disabling cookies, let alone deleting them? Unless of course your website’s audience is 100% developers.

    From my experience about 1-5% of general population knows what cookies are. And much less know where they are stored and even less know how to manipulate them.

  • David M.

    Thanks Maks. Yeah, I might be overthinking the problem… but imagine that you have a very popular product review sites, such as webhosting reviews or bunch of competitive products review sites. If one of my products or hosting company was on that site and if it allowed the users to vote/like without registration then I probably would try to bump up the count by eventually figuring out how to go around it (deleting the cookies).

    Anyways, thanks for your feedback. I probably will limit to the registered users for now… 🙂

  • Maks Surguy

    yeah, if you have thousands of users every day on it, and if anything depends on that count, it should be properly secured 🙂 Otherwise I suggest you focus on other parts of the site and save some time.

  • David M.

    Hi Maks,
    I was trying to follow your tutorial and realized that you provided raw sql script rather than in Laravel’s migration style. If you are doing a tutorial for Laravel, why would you use the raw sql script rather than Laravel’s migration?
    I personally do prefer raw sql for creating the db structure myself but I was curious if you had other reasons.

    Finally, while products and reviews tables are defined as InnoDB engine, the users table is defined as MyISAM engine. Was this just an oversight or intentional?

    Thanks again.

  • Hello,
    I think this is a shopping cart left…

  • TSGarpian

    Good stuff, looking forward for more.

    Isn’t the avg rating thing a perfect case for mysql triggers?

  • dcrystalj

    very very nice!!
    But, “Clean up the models by separating view presenters scopes and relationships.”

    How can we do that?

  • Maks Surguy

    You can do this through the use of so called “Repository pattern” (Google that + Laravel to see lots of examples), also using Laravel’s scopes like I am using for “Review” model (like scopeSpam, scopeApproved) will be essential when you build out a bigger system.
    The view presenters would be very useful for the output of the ratings. Right now the system has a for loop to display the review stars and this could easily be factored out into a view presenter or into it’s own Blade template that is then “include”d in the template that shows the reviews.

    Hope this helps, otherwise I could maybe write out a tutorial on how to go about this.

  • This was just too too helpful. I’ve been wanting to implement this just right now and i found it a freebies here. Wonderful tutorial. Please improve more on it so we can learn more from the masters. we appreciate your contributions so far.

  • Maks Surguy

    I’m glad this is helpful! Keep coming back to my blog!

  • Pingback: Using polymorphic relationships of Laravel for SEO content - Maxoffsky |()

  • Pingback: P1 | Ylyt()

  • Elena Alexie

    Really great tutorial! I am a newbie to Laravel and, choosing this tutorial to build my very first Laravel PHP application, was the best choice. Thank you so much! 🙂

  • Glad you found it useful!

  • Great post. I am pretty new Laravel framework but I have found the post very useful. At the moment reviews are captured as from Anonymous. What changes do I have to make to the current code so that I can capture reviewer’s details such as name etc

  • You would have to add the user_id to the reviews table and then set it to equal to Auth::user()->id (id of the currently logged in user).

  • Phebeon Vera

    I have set $this->user_id = Auth::user()->id; in review.php and added user_id to the reviews table. When I try the leave a review I am getting an error: Trying to get property of non-object. This error is reported on the same line I added above. What am I missing?

  • Ah, it seems you don’t have a user currently logged in? You need to create a table for users (username, email, etc) and then you need to authenticate the user (for example with a login form or manually through Auth::login()), only then the code I provided would work.

  • This is a very nice and complete tutorial….exellent

  • Affan

    I am Affan… I upload the template from bootsrap, and its work. But, when I upload your source code, why it doesn’t work? I alreadey create the database.

  • maxsurguy

    I’d need a bit more details on that… What do you have currently?

  • chirag bhatia

    hi, i am new to laravel where i can get this complete code

  • maxsurguy

    The complete code is available on Github.

  • tnk2006

    Great tutorial! Thanks!

  • Finsok Yagman

    it wont work because there is missing vendor files….

  • Finsok Yagman

    Great tutorial but can i get this code in laravel 4 @maxsurguy:disqus

  • I’d like to see more please.

  • Vishal Shah

    I have couple of questions.. can we use this for “generic php” rather then using ” laravel” framework? Also, how can be integrate social media login for end users?

  • Andrea Lizzy Frost

    Hi folks, im new to this and downloaded the complete code, but I am struggling to understand it, is there anyone who can help, I installed all files to server but nothing happens, is there anyone with experience that could set this up for me with a fee

  • Andrea Lizzy Frost

    Hi folks, im new to this and downloaded the complete code, but I am struggling to understand it, is there anyone who can help, I installed all files to server but nothing happens

  • Javier Ceballos

    Bueno, es que aqui lo que sucede es que te dan un leve repaso de como hacer las cosas, por eso si bajan el codigo, no creo que les soluciones la vida, hay que hacer una base de datos, enlazarla al codigo del sitio mediante, javascrip o php, o algun otro y entonces empezaran a ver resultados

  • Pee Ny

    great! thank you. 🙂

  • Liudmila Kosyanova

    It very nice, but I would like to use it in the MVC .

  • Liudmila Kosyanova

    I not use hph

  • Rob

    then dont use laravel

More in Code Blog
laravel-shop-intro
Building a shop with Laravel – tutorial series announcement

Dear readers of my blog! I have a lot of exciting announcements that I will make next week right here...

Close