Sujip Thapa's Blog - Purely about Laravel, PHP, Web Development. https://sujipthapa.co A modern web development blog in PHP, Laravel and other interesting tutorials explaining the real features, tips & other cool stuff. en-us 2018-10-22T17:45:12+05:45 <![CDATA[Laravel 5.7: Adding Support for Login with Username or Email]]> https://sujipthapa.co/blog/laravel-57-adding-support-for-login-with-username-or-email 577722fb-dc4c-46f5-a20b-f37742194e38 2018-10-17T00:00:00+05:45 Mr. Sujip Thapa I've already published a few articles on Laravel Login to support username or email. In this article, I'm going to cover the process of adding the username or email support with Laravel 5.7 default authentication system.

The process below helps you to understand the following things.

  • Adds support for email verification which is supported by default in the framework now.
  • Adds support for register with the username, which is not available on a default authentication system.
  • Adds support for login with either username or email.

The reason behind writing this article is peoples, preferring my previous articles about Laravel login and register with username or email support for earlier versions of Laravel.

Compatibility

If you are starting your fresh Laravel 5.7 project, and want to add the support for username or email for login and register, you could follow the steps from the beginning.

For any existing project just upgraded to Laravel 5.7, it is, of course, useful to follow the article to customize your authentication system.

Customization

Let's begin with the registration part to add support for username or email login with Laravel 5.7.

Register

For a fresh Laravel 5.7 setup, begin with generating the default auth scaffolding with an artisan console command, which ships with the framework out of the box.

Routes

// no email verification
Auth::routes();

// after adding, email verification
Auth::routes(['verify' => true]);


I would like to recommend you to follow the earlier article [Email Verification after Registration with Laravel 5.7] side by side with this article to implement email verification with Laravel 5.7 authentication system.

Database

For existing project add a new migration file using an artisan command to add a new field to the users' table, and for the new fresh project, you can easily edit the migration file generated with auth scaffolding and add a single line below.

$table->string('username')->unique();

To fill value in the database for username field, we've to add a key username to the $fillable property under app/User.php database model.

protected $fillable = ['name', 'email', 'password', 'username'];

View

The user registration form also requires a little bit of customization to add the username field.

<div class="form-group row">
    <label for="username" class="col-md-4 col-form-label text-md-right">{{ __('Username') }}</label>

    <div class="col-md-6">
        <input id="username" type="text" class="form-control{{ $errors->has('username') ? ' is-invalid' : '' }}" name="username" value="{{ old('username') }}" required>

        @if ($errors->has('username'))
            <span class="invalid-feedback" role="alert">
                <strong>{{ $errors->first('username') }}</strong>
            </span>
        @endif
    </div>
</div>

Controller

The default auth system ships with a controller class, which handles the necessary methods for the registration system. We've to edit a few methods under that class to add the username to the database.

Login

We've already added the necessary routes, email verification steps linked with another article. For the login system to support username or email we need to override a few methods on the login controller.

The login form requires a very little change on the input type field from email to text.

<input id="email" type="text" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>

The controller class for login also ships with the framework and uses an AuthenticatesUsers trait, which handles the specific methods necessary for the default auth system.

To apply the new changes we need, we have to override the methods under the controller class for the login system.

    // LoginController.php

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(Request $request)
    {
        $field = $this->field($request);

        return [
            $field => $request->get($this->username()),
            'password' => $request->get('password'),
        ];
    }

    /**
     * Determine if the request field is email or username.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string
     */
    public function field(Request $request)
    {
        $email = $this->username();

        return filter_var($request->get($email), FILTER_VALIDATE_EMAIL) ? $email : 'username';
    }

    /**
     * Validate the user login request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    protected function validateLogin(Request $request)
    {
        $field = $this->field($request);

        $messages = ["{$this->username()}.exists" => 'The account you are trying to login is not registered or it has been disabled.'];

        $this->validate($request, [
            $this->username() => "required|exists:users,{$field}",
            'password' => 'required',
        ], $messages);
    }

Code

The full source code for the all above customization is available on the github repository.

Conclusion

Thanks for following up my regular articles, and reading up to the end. If you have any feedback for me, feel free to leave your comment. Do not forget to share if you think worth sharing, and reading my articles.

Happy Coding!

]]>
<![CDATA[Hacktoberfest 2018 — win a T-shirt just by contributing to open source projects.]]> https://sujipthapa.co/blog/hacktoberfest-2018-win-a-t-shirt-just-by-contributing-to-open-source-projects 4986ddb1-ce8b-45fe-995f-d33207679b4b 2018-09-05T10:40:00+05:45 Mr. Sujip Thapa Hacktoberfest a month-long celebration of open source software is happening again. I started my Hacktoberfest challenge journey in 2016 and following the years to contribute and get the cool T-shirt.

I'm writing my second blog post about the Hacktoberfest challenge, but the competition is the third edition for me in real, by regularly winning the T-shirts from this great event.

The Hacktoberfest 2018 is the 5th edition, which was continuously organized by two giant tech companies @DigitalOcean, @Github in the previous years but this year the newest partner @Twilio also joining to run this challenge for the developers all over the world.

I'm super excited this time as well by looking at the excellent design of the Hacktober 2018 T-shirt.

Who can participate?
It is open to everyone in our global community!

How can I participate?
Open a minimum of 5 pull requests on Github, by choosing your favorite projects. If you need the contribution guide, follow this official contribution guide provided by Github.

When to participate?
You can sign up at the official website of Hacktoberfest 2018 anytime between October 1 and October 31.

The organizers are the world famous technology companies providing following great services to their customers.

Digital Ocean, Inc - An American based cloud infrastructure service provider, serving customers over the world with more than 13 data centers across different countries.

GitHub, Inc - A web-based code hosting service for version control using Git. It is a widely known source code repository platform for different types of applications.

Twilio - A cloud communications platform as a service company based in San Francisco, California. Twilio allows developers to programmatically make and receive phone calls, send and receive SMS.

Since last year, the Hacktoberfest fan created a platform to check if you completed the Hacktoberfest challenge to receive the T-shirt or not by viewing the pull request and activity.

Credit: The above preview image credit goes to Hacktoberfest 2018.

Thanks for reading the article up to the end, if you are interested and need help in participating in this challenge, please feel free to leave your comment below. I will look back and try to help as much as possible.

Happy Coding!

]]>
<![CDATA[Email Verification after Registration with Laravel 5.7]]> https://sujipthapa.co/blog/email-verification-after-registration-with-laravel-57 6176c3b6-78d4-45c9-b693-b68e2e362801 2018-09-01T13:16:17+05:45 Mr. Sujip Thapa Nowadays most of the modern applications force their users to verify the ownership by sending an activation email after they complete the account registration. Moving ahead, with the release of Laravel 5.7 the user email verification is shipping with the framework out of the box. People were creating the custom feature to implement this before the version 5.7.
This feature now makes easier to implement email verification on each application people build by using the Laravel framework.

In this article, we're implementing the complete email verification process in depth. We're going to achieve the verification feature by using the standard framework code without doing any modification.

Let's start the coding together.

Auth Scaffolding

Let's start with publishing the default auth scaffoldings by using the artisan command.

After publishing the necessary files, we don't have to worry about the routes, views, controllers required for authentication, as they ship with the framework, but you can always customize them if needed.

php artisan make:auth

Database

The new class is introduced in the framework to implement the email verification system.

So, now the App\User model should implement the Illuminate\Contracts\Auth\MustVerifyEmail contract.

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements MustVerifyEmail
{
    // ...
}

We need to add one more field email_verified_at to the user's table to store the verified timestamp when a user clicks the activation link sent to their email address.

    Schema::create('users', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name');
        $table->string('email')->unique();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->rememberToken();
        $table->timestamps();
    });

Routing

A new controller Auth\VerificationController class is introduced to handle the necessary logic to send an email verification link to the user's email address.

Also, register the verification routes by passing the parameter like below.

// before
Auth::routes();

// after
Auth::routes(['verify' => true]);

Middleware

A new middleware Illuminate\Auth\Middleware\EnsureEmailIsVerified class is introduced to protect the routes from being accessed by the unauthorized users.

You can have a look at the Kernel.php to see the registered middleware.

    protected $routeMiddleware = [
        ...
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    ];

Also, you may want to protect other routes, like profile, the dashboard to non-activated users by attaching the middleware on routes.

// example
Route::get('/home', 'HomeController@index')
    ->name('home')
    ->middleware('verified');

Route::get('/member/profile', function () {
    // verified users only
})->middleware('verified');

Views

The necessary views for login, register, and email verification are created inside resources/views/auth directory when you publish the auth scaffoldings.

After Verification

After the successful verification, the application redirects a user to the /home page by default.

You are free to customize the redirect location by defining a redirectTo method or property on the VerificationController.php.

protected $redirectTo = '/dashboard';

Also,

If you want to control the page redirection when the user interacts with login, register pages, you want them to go back to the previous page.

Read: Redirect to Previous URL After Logging In

Conclusion

Thanks for reading the article up to the end, please let me know through comments if you have any issue on the integration.

Happy Coding!

]]>
<![CDATA[What's New Coming to Laravel 5.7 Release]]> https://sujipthapa.co/blog/whats-new-coming-to-laravel-57-release 86e89f5e-5943-4ae6-8aa1-018040ea28ff 2018-08-17T10:15:15+05:45 Mr. Sujip Thapa Laravel a most popular PHP framework, which is actively supported and contributed open source project, which is reaching to its next release 5.7 in August 2018.

The release will receive bug fixes until February 2019 and security fixes until August 2019.

This release continues to improvements for the previous version 5.6, and also includes some exciting new features.

In this blog post, I'm listing some cool features that are already announced by the Laravel team and taking some reference from the github repository.

I'll keep on updating this post up to the final release is announced from the official team, as new pull requests are still coming from the community contributors.

1. Laravel Nova

Laravel Nova%2C beautifully designed admin panel package for Laravel.

The most awaited laravel package that was announced by Taylor during Laracon US 2018, and which is a beautiful admin panel package for Laravel application. It is a code-driven admin panel for your new or existing laravel project. The previous version of Laravel still supports Nova, as it is a simple composer package. To know in depth about it follow the official documentation, also go through the introduction article on the medium by Taylor.

Laravel Nova is officially released now on Aug 22, 2018, the initial release v1.0.* (Orion) is now available to purchase from the official website.

2. Email Verification

The framework introducing optional email verification feature with this release. To utilize this feature, you have to add the email_verified_at timestamp column to the user's table migration that ships with the framework.

To advise newly joined users to verify their email, the User model should implement the MustVerifyEmail interface.

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements MustVerifyEmail
{
    // ...
}

After implementing the interface, the newly registered members will receive an email containing a signed verification link. Once the user clicks this link, the application will automatically update the database and redirect the user to the intended location.

This new feature also introduced a verified middleware. If you wish to protect your application's routes from only verified members, it can be attached to the application routes.

'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,


3. Guest User Gates / Policies

The previous version of Laravel used to return false if an unauthenticated user wants to access the application. However, with the new version (5.7) it will now allow declaring an "optional" type-hint or supplying a null default value to permit the guest user to pass through the authorization checks.

Gate::define('update-product', function (?User $user, Product $product) {
    // ...
});


4. Symfony Dump Server

The Symfony's dump-server is coming to Laravel 5.7. It is a command via package built by a Laravel community member Marcel Pociot.

This feature will be a lot useful to debug an error on your application without interrupting the application runtime.

Laravel 5.7 Dump Server

The command runs in the background and collects data transmitted from the application and shows output through the console mode.

php artisan dump-server

// Output to the HTML file.
php artisan dump-server --format=html > report.html

This package is open sourced by the author, contribute if you have any idea for it.

5. URL Generator & Callable Syntax

To generate the URLs to controller actions, Laravel now supports callable array syntax, instead of strings only.

Previously,

$url = action('BlogController@index');

$url = action('BlogController@view', ['id' => 1]);

Now,

use App\Http\Controllers\HomeController;

$url = action([BlogController::class, 'index']);

$url = action([BlogController::class, 'view'], ['id' => 1]);

6. Paginator Links

You can now control the number of pagination links on each side of the paginated URLs design. By default, there will be three links created on each side of the paginator links. The newly introduced method onEachSide() allows to control them with Laravel 5.7.

{{ $pages->onEachSide(5)->links() }}

7. Read / Write Streams method in Filesystem

Laravel 5.7 introduces new methods for the Filesystem integration.

    Storage::disk('s3')->writeStream(
        'destination-file.zip',
        Storage::disk('local')->readStream('source-file.zip')
    );

8. Notification Localization
You can now assign locale for the notifications to send other than the current language.

The Illuminate\Notifications\Notification class adds new locale method to assign the desired language.

$user->notify((new NewUser($user))->locale('np'));

You could also utilize the facade to set localization for multiple notification entries.

    Notification::locale('np')
        ->send($subscribers, new WeeklyNewsletter($newsletter));

9. Improved Error Messages

You can now better track your errors messages with your Laravel application. As of Laravel 5.7, you will get a cleaner concise message saying that the method doesn't exist on the specific model.

Improved Error Messages

10. Resources Directory Changes

A little bit changes to the resources directory coming with the new release.

This change was announced by Taylor with a tweet, as assets directly will go away and js, sass, lang, views coming out into the resources directory.

Laravel 5.7 resources directory changes.

When you upgrade your application to 5.7, you don’t need to reconstruct the resources/asset directory according to the newer directory structure. The older structure will still work.

11. Testing Artisan Commands

The first employee of Laravel (Mohamed Said) recently contributed a great feature in the framework to test artisan commands. He announced via his tweet about this feature, and which is also documented in official documentation already. Now, with this addition, framework now provides a simple API for testing console applications that ask for user input.

class InstallCommandTest extends TestCase
{
    public function testInstallTest()
    {
        $this->artisan('app:setup', [
            'name' => 'Setup New Project'
        ])
            ->expectsQuestion('Are you sure you want to start installation ?', 'Yes')
            ->expectsOutput('Initializing...')
            ->expectsQuestion('Please select your preferred version', 'v2.5')
            ->expectsOutput('Installing...')
            ->expectsQuestion('Do you want to run composer dump -o ?', 'Yes')
            ->expectsOutput('Generating Optimized Autoload Files...')
            ->assertExitCode(0);
    }
}

Conclusion

Thanks for reading this article up to the end, please follow this article in later dates to know more new features coming to the 5.7 release.

Happy Coding!

]]>
<![CDATA[Building a Password Less Authentication System with Laravel Signed Routes]]> https://sujipthapa.co/blog/building-a-password-less-authentication-system-with-laravel-signed-routes fdd631b4-7d56-4667-84f8-43b4ed8c5d6f 2018-08-12T14:22:18+05:45 Mr. Sujip Thapa I've seen many moderns apps are offering passwordless authentication in their platform. A lot of social media, email platforms are also offering the login with their API system by providing limited data necessary for user registration.

A lot of modern webs, mobile apps using social login to give a great user experience while using their platforms.

Today in this blog post, I'm explaining the process of customizing to use own passwordless authentication system with the Laravel framework.

Let's start together.

This article will utilize features from Laravel 5.6 version.

laravel new passwordless-auth

cd passwordless-auth

php artisan make:auth

After publishing the default Laravel auth scaffoldings, we now need to remove unnecessary files listed below.

ResetPasswordController.php
ForgotPasswordController.php

passwords/email.blade.php
passwords/reset.blade.php

The register, login pages come up with password fields, as we're building passwordless auth, so we have to tweak on those files.

login.blade.php
register.blade.php

Note: It is better to give some instructions on each pages describing how our passwordless authentication system works.

Now, adding auth routes needed for this integration.

We're not going to use the default routes which ship with the framework for the auth system.

We'll care the naming convention of routes, but we have to avoid unnecessary ones coming with the framework which are not necessary for this integration.

Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('login/attempt', 'Auth\LoginController@attempt')->name('login.attempt');
Route::get('login/{token}/validate', 'Auth\LoginController@login')
    ->name('login.token.validate')
    ->middleware('signed');
Route::post('logout', 'Auth\LoginController@logout')->name('logout');
Route::get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
Route::post('register', 'Auth\RegisterController@register');

Let's start with registering a user without a password to log in.

Before writing any codes for login, register process, let me give you some idea on what things need to update.

Signed Route Middleware
The middleware prevents the user to misuse the expired URLs by checking the signature validity.

    protected $routeMiddleware = [
        ...
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
    ];​

The new feature signed route was introduced to the framework with version 5.6.

Handle InvalidSignatureException
We need to handle the exception for invalid signature, if a user tries to login with the expired signed URL show message to them to generate new URL.

    use Illuminate\Routing\Exceptions\InvalidSignatureException;

    public function render($request, Exception $exception)
    {
        if (is_a($exception, InvalidSignatureException::class)) {
            return response()->view('_signature-expired');
        }

        return parent::render($request, $exception);
    }
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">
                        <h2>{{ __('Error') }}</h2>
                    </div>
                    <div class="card-body">
                        <div class="alert alert-danger text-center text-muted">
                            The signature seems to be expired, please try generating a new one and try again.
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Remove Password Fields
We're not using the password field anymore, so remove from the migration file and User model.

After all, these make sure you run the migration to generate database tables.
Register

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class RegisterController extends Controller
{
    use RegistersUsers;

    /**
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
        ]);
    }

    /**
     * Handle a registration request for the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        $this->validator($request->all())->validate();

        event(new Registered($user = $this->create($request->all())));

        return redirect()->route('login')
            ->with(['success' => 'Success! your account is registered.']);
    }
}

The main updates in this file are, remove password form validation, create methods.

Another major update is to prevent a user from logging in automatically into the application after registration inside the register method.

Tip: I've seen BuySell Ads, a popular advertising platform using this kind of system as only admin adds the user.

Login

I've added a new trait for the Login process to keep the overrides a little bit cleaner.

Previously with the framework's default auth, there is a trait called AuthenticatesUsers.

Now, with this integration, we will be importing new trait which itself imports the existing AuthenticatesUsers.

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Auth\Traits;
use App\Http\Controllers\Controller;

class LoginController extends Controller
{
    use Traits\PasswordLessAuth;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }
}

Also, the Login Throttling feature will still work.

<?php

namespace App\Http\Controllers\Auth\Traits;

use App\LoginAttempt;
use App\Notifications\NewLoginAttempt;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

trait PasswordLessAuth
{
    use AuthenticatesUsers;

    /**
     * Validate the user login request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    protected function validateLogin(Request $request)
    {
        $messages = ['exists' => trans('auth.exists')];

        $this->validate($request, [
            $this->username() => 'required|email|exists:users',
        ], $messages);
    }

    /**
     * Handle a login attempt request to the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function attempt(Request $request)
    {
        $this->incrementLoginAttempts($request);

        if ($this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        $this->validateLogin($request);

        if ($this->createLoginAttempt($request)) {
            return $this->sendAttemptResponse($request);
        }

        return $this->sendFailedLoginResponse($request);
    }

    /**
     * Handle a login request to the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function login($token, Request $request)
    {
        if ($this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        if ($this->attemptLogin($token, $request)) {
            return $this->sendLoginResponse($request);
        }

        $this->incrementLoginAttempts($request);

        return $this->sendFailedLoginResponse($request);
    }

    /**
     * Attempt to log the user into the application.
     *
     * @param string $token
     * @param \Illuminate\Http\Request  $request
     * @return bool
     */
    protected function attemptLogin($token, Request $request)
    {
        $user = LoginAttempt::userFromToken($token);

        if (is_object($user)) {
            return $this->guard()->login($user);
        }
    }

    /**
     * Attempt to log the user into the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \App\LoginAttempt
     */
    protected function createLoginAttempt(Request $request)
    {
        $authorize = LoginAttempt::create([
            'email' => $request->input($this->username()),
            'token' => str_random(40) . time(),
        ]);

        $authorize->notify(new NewLoginAttempt($authorize));

        return $authorize;
    }

    /**
     * @param $request
     */
    public function sendAttemptResponse($request)
    {
        return \View::make('auth._link-sent');
    }
}

To customize the validation message for the user exists rule, open up resources/lang/en/auth.php and update like below.

<?php

return [

    ...
    'exists' => 'The provided email address does not match our records.'
];

Let me give some run down about the login feature and new methods added to the PasswordLessAuth.php trait.

  • The method validateLogin() now, only validates email field and if that exists in the database.
  • The new attempt() method is responsible for sending an email with a signed URL after validating the request.
  • The new createLoginAttempt(), is being called via attempt() method, which creates a new login attempt which will be validated later while user clicks on the link sent via email.
  • After creating a login attempt, we display a message to the user with sendAttemptResponse() method.

Let's create a model, migration and notification class for the login attempt process.

<?php

namespace App;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;

class LoginAttempt extends Model
{
    use Notifiable;

    /**
     * @var string
     */
    protected $table = 'login_attempts';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'email', 'token',
    ];

    /**
     * @return mixed
     */
    public function user()
    {
        return $this->hasOne(User::class, 'email', 'email');
    }

    /**
     * @param $token
     */
    public static function userFromToken($token)
    {
        $query = self::where('token', $token)
            ->where('created_at', '>', Carbon::parse('-15 minutes'))
            ->first();

        return $query->user ?? null;
    }
}

Add, those fields by creating a new migration file like below.

    Schema::create('login_attempts', function (Blueprint $table) {
        $table->increments('id');
        $table->string('email')->index();
        $table->string('token')->index();
        $table->timestamps();
    });

To send the login link in an email, we have a new notification class.

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\URL;

class NewLoginAttempt extends Notification
{
    use Queueable;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($attempt)
    {
        $this->attempt = $attempt;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        return with(new MailMessage)
            ->from(env('ADMIN_MAIL_ADDRESS'))
            ->subject('Login Your Account')
            ->greeting("Hello {$this->attempt->user->name}!")
            ->line('Please click the button below to get access to the application, which will be valid only for 15 minutes.')
            ->action('Login to your account', URL::temporarySignedRoute('login.token.validate', now()->addMinutes(15), [$this->attempt->token]))
            ->line('Thank you for using our application!');
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
}

After a user makes a valid attempt to login into the application, we display a message to the user with a view file like below.

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">
                        <h2>{{ __('Success') }}</h2>
                    </div>
                    <div class="card-body">
                        <div class="alert text-center text-muted">
                            Please check your email for a login link, which will be valid for next 15 minutes only.
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-8 offset-md-4">
                                <a class="btn btn-link" href="{{ route('login') }}">
                                    {{ __('Get Another Link') }}
                                </a>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Conclusion

The full source code now freely available on github, you are free to modify as per your application logic.

Also Read: Laravel 5.6 Login, Register, Activation with Username or Email Support

Thanks for reading this article up to the end, please don't forget to give your feedback in the comment section below.

Happy Coding!

]]>
<![CDATA[Testing Packages on Composer with Specific Commit Before Creating New Tag]]> https://sujipthapa.co/blog/testing-packages-on-composer-with-specific-commit-before-creating-new-tag f416d66e-3cad-41f3-a6ec-11f5b47ddca1 2018-07-21T08:18:18+05:45 Mr. Sujip Thapa Composer, which is a modern developer tool for dependency management for PHP. It is used to declare the dependencies your project depends on it and it manages them very well for you. It is incredibly flexible to manage the dependencies in various stage of your project.

Let's suppose you're releasing a new package, but before announcing a new major stable version, you want to test it thoroughly in the development branch.

Now, to use the specific git commit before publishing a new tag, composer offers a way to include the commit hash value directly via the command line or via the require block in the composer.json file.

composer require vendor/package:dev-master#0fcf728

or

    "require": {
        "vendor/package": "dev-master#0fcf728"
    }

The composer command above will check out your package version to that specific commit, so you can test the code properly before finally releasing a stable version.

Also, sometime you may want to install a specific released version of the package.

composer require vendor/package:version

// Example 
composer require sudiptpa/paypal-ipn:1.0.x-dev

Also Read: Speed up your Composer Install or Update Process

Thanks for reading the article up to the end, if you have any feedback feel free to leave your comment below.

If it is worth reading, let other people know about it by sharing this post.

Happy Coding!

]]>
<![CDATA[Laravel 5.6 Login, Register, Activation with Username or Email Support]]> https://sujipthapa.co/blog/laravel-56-register-login-activation-with-username-or-email-support e3e9db5e-d8de-4922-907b-f6b166af7c2c 2018-06-10T00:00:00+05:45 Mr. Sujip Thapa This is another interesting blog post about Laravel 5.6 login, register with username or email support. I've already published two articles about customizing the laravel authentication process. In this article, I will be helping you to build following things below, with Laravel v5.6.

  • Adding username support on default register, login process.
  • Activating user after the registration process is completed via email.
  • Disabling default login after register, only allowing to log in for the activated user.
  • Using Laravel notification to send email to the user about registration.

To follow this article you should have existing or fresh laravel setup with version 5.6, as you will be seeing some codes samples only supporting this particular version and they may not be compatible with other older versions.

The regular laravel installation comes without auth scaffolding with the recent releases, to generate the default auth scaffolding it ships with an artisan command out of the box.

If you haven't already generated them, use the below artisan command to populate them.

php artisan make:auth

Let's start with the additional routes for authentication under routes/web.php

Auth::routes();

Route::get('activate/{token}', 'Auth\RegisterController@activate')
    ->name('activate');

To add username support on register process, let's add few new fields in the migration file.

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('username')->unique();
            $table->string('email')->unique();
            $table->string('password');
            $table->string('token');
            $table->integer('active')->default(0);
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

We've added three new fields (username, token, active), to store unique username, activation token, active field to main active flag for users.

Now, updating model app/User.php with new fields.

<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    const ACTIVE = 1;
    const INACTIVE = 0;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'username', 'email', 'password', 'token', 'active',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
}

Register

Adding username field for a registration form inside resources/views/auth/register.blade.php

<div class="form-group row">
    <label for="username" class="col-md-4 col-form-label text-md-right">{{ __('Username') }}</label>

    <div class="col-md-6">
        <input id="username" type="text" class="form-control{{ $errors->has('username') ? ' is-invalid' : '' }}" name="username" value="{{ old('username') }}" required>

        @if ($errors->has('username'))
            <span class="invalid-feedback">
                <strong>{{ $errors->first('username') }}</strong>
            </span>
        @endif
    </div>
</div>

To view, the full source code for the registration form read the article up to end.

To make the username support coming through the registration form, we need to modify few methods inside the controller.

    // App\Http\Controllers\Auth\RegisterController.php
    
    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'username' => 'required|string|max:20|unique:users',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        $user = User::create([
            'name' => $data['name'],
            'username' => $data['username'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
            'token' => str_random(40) . time(),
        ]);

        $user->notify(new UserActivate($user));

        return $user;
    }

    /**
     * Handle a registration request for the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        $this->validator($request->all())->validate();

        event(new Registered($user = $this->create($request->all())));

        return redirect()->route('login')
            ->with(['success' => 'Congratulations! your account is registered, you will shortly receive an email to activate your account.']);
    }

    /**
     * @param $token
     */
    public function activate($token = null)
    {
        $user = User::where('token', $token)->first();

        if (empty($user)) {
            return redirect()->to('/')
                ->with(['error' => 'Your activation code is either expired or invalid.']);
        }

        $user->update(['token' => null, 'active' => User::ACTIVE]);

        return redirect()->route('login')
            ->with(['success' => 'Congratulations! your account is now activated.']);
    }

Let's point out the updates made in the above controller.

  • We've added a validation rule for username field inside the validator() method.
  • We've updated create() method to call a notification class to send an activation email to the user after registration is completed.
  • Overriding register() method originally called from the RegistersUsers trait to protect from being auto-login to non-activated users.
  • Also created new method activate() to allow activation to the new users.

We've added a new notification class, UserActivate.php to send an activation link to the users while they register their new account.

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class UserActivate extends Notification
{
    use Queueable;

    /**
     * Create a new notification instance.
     *
     * @param $user
     * @return void
     */
    public function __construct($user)
    {
        $this->user = $user;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->from(env('ADMIN_MAIL_ADDRESS'))
            ->subject('Activate Account!')
            ->greeting(sprintf('Hi, %s', $this->user->name))
            ->line('We just noticed that you created a new account. You will need to activate your account to sign in into this account.')
            ->action('Activate', route('activate', [$this->user->token]))
            ->line('Thank you for using our application!');
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
}

The customization for registration process is finally done with above changes, now we will be looking at login process below.

Login

We've updated the file login.blade.php to add support for email or username. To make that addition we've changed the input type email to be text to support both username or email but the input type name remains same.

<div class="form-group row">
    <label for="email" class="col-sm-4 col-form-label text-md-right">{{ __('E-Mail / Username') }}</label>

    <div class="col-md-6">
        <input id="email" type="text" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus>

        @if ($errors->has('email'))
            <span class="invalid-feedback">
                <strong>{{ $errors->first('email') }}</strong>
            </span>
        @endif
    </div>
</div>

Now, we're adding another method to the controller.

    // App\Http\Controllers\Auth\LoginController.php

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(Request $request)
    {
        $field = $this->field($request);

        return [
            $field => $request->get($this->username()),
            'password' => $request->get('password'),
            'active' => User::ACTIVE,
        ];
    }

    /**
     * Determine if the request field is email or username.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string
     */
    public function field(Request $request)
    {
        $email = $this->username();

        return filter_var($request->get($email), FILTER_VALIDATE_EMAIL) ? $email : 'username';
    }

    /**
     * Validate the user login request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    protected function validateLogin(Request $request)
    {
        $field = $this->field($request);

        $messages = ["{$this->username()}.exists" => 'The account you are trying to login is not activated or it has been disabled.'];

        $this->validate($request, [
            $this->username() => "required|exists:users,{$field},active," . User::ACTIVE,
            'password' => 'required',
        ], $messages);
    }

Let's point out what has changed inside the controller above.

  • The method credentials(), originally called from AuthenticatesUsers trait which collects credential to attempt the authentication is now overridden to the controller.
  • The field username or email is determined inside the credentials() method.
  • Added active field in the authentication process.

After updating the above files the login process also now supports username or email.

The full source code is freely available on github, you are free to modify and use it for your application.

Thanks for reading this article up to the end, please don't forget to give your feedback in the comment section below.

Happy Coding!

]]>
<![CDATA[SecurePay Integration Guide for Omnipay v3 PHP Library with Laravel]]> https://sujipthapa.co/blog/securepay-integration-guide-for-omnipay-v3-php-library-with-laravel 51d273ef-a3da-4b71-818d-5f952e36beb9 2018-05-30T20:02:17+05:45 Mr. Sujip Thapa I've already covered few Omnipay integration guides with my previous blog posts. In this post, I'm going to explain in detail about SecurePay, a leading payment solution for businesses of different sizes over Australia. I've recently contributed to the v3 release of the omnipay-securepay open source library for PHP on github.

By using this package (omnipay/securepay) we can implement different gateways listed below.

  • SecurePay_DirectPost (DirectPost v2 Integration)
  • SecurePay_SecureXML (Secure XML API Integration)

We're going to integrate SecurePay DirectPost v2 in this article below.

  • The customer submits the payment information directly to the Direct Post payment service from the website.
  • The payment gateway validates and redirects back to the website to fulfill the order.

Before starting the coding let's install the package via composer.

We're going to integrate this package with Laravel v5.6 for this article, use the command below to install the package.

composer require league/omnipay omnipay/securepay

Let's add some new routes for this integration under app/routes/web.php

Route::get('/checkout/payment', [
    'name' => 'Payment',
    'as' => 'checkout.payment',
    'uses' => 'PaymentController@checkout',
]);

Route::post('/checkout/payment/{order}/process', [
    'name' => 'Payment',
    'as' => 'checkout.payment.process',
    'uses' => 'PaymentController@payment',
]);

Route::get('/checkout/payment/{order}/completed', [
    'name' => 'Payment Completed',
    'as' => 'checkout.payment.completed',
    'uses' => 'PaymentController@completed',
]);

Route::get('/checkout/payment/{order}/failed', [
    'name' => 'Payment Failed',
    'as' => 'checkout.payment.failed',
    'uses' => 'PaymentController@failed',
]);

Adding new model toapp/Order.php handle database operations to store orders coming through the website.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    const PAYMENT_COMPLETED = 1;
    const PAYMENT_PENDING = 0;
    /**
     * @var string
     */
    protected $table = 'orders';

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['transaction_id', 'amount', 'payment_status'];
}

This model has really basic stuff to show the process while building this integration guide, you are free to customize as per the requirement of your application.

A new database table is needed to store the orders. Let's create a table orderswith a console command.

php artisan make:migration create_table_orders --create=orders
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateOrdersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->increments('id');
            $table->string('transaction_id')->nullable();
            $table->float('amount')->unsigned()->nullable();
            $table->integer('payment_status')->unsigned()->default(0);
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('orders');
    }
}

Let's add a new classapp/SecurePay.php which interacts with library classes and framework classes to interact with API services.

<?php

namespace App;

use Exception;
use Illuminate\Http\Request;
use Omnipay\Common\CreditCard;
use Omnipay\Common\Exception\InvalidCreditCardException;
use Omnipay\Omnipay;

/**
 * Class SecurePay
 * @package App
 */
class SecurePay
{
    /**
     * @return mixed
     */
    public function gateway()
    {
        $gateway = Omnipay::create('SecurePay_DirectPost');

        $gateway->setMerchantId(config('services.securepay.merchant_id'));
        $gateway->setTransactionPassword(config('services.securepay.password'));
        $gateway->setTestMode(config('services.securepay.sandbox'));

        return $gateway;
    }

    /**
     * @param array $parameters
     * @param Request $request
     */
    public function card(array $parameters, Request $request)
    {
        return [
            'card' => new CreditCard([
                'firstName' => $request->get('first_name'),
                'lastName' => $request->get('last_name'),
                'number' => $request->get('card_number'),
                'expiryMonth' => $request->get('expiry_month'),
                'expiryYear' => $request->get('expiry_year'),
                'cvv' => $request->get('cvc'),
            ]),
        ];
    }

    /**
     * @param array $parameters
     * @param Request $request
     * @return mixed
     */
    public function purchase(array $parameters, Request $request)
    {
        $parameters = array_merge($parameters, $this->card($parameters, $request));

        try {
            $response = $this->gateway()
                ->purchase($parameters)
                ->send();

        } catch (InvalidCreditCardException $e) {
            throw new Exception($e->getMessage());
        }

        return $response;
    }

    /**
     * @param array $parameters
     * @param Request $request
     * @return mixed
     */
    public function complete(array $parameters, Request $request)
    {
        $parameters = array_merge($parameters, $this->card($parameters, $request));

        $response = $this->gateway()
            ->completePurchase($parameters)
            ->send();

        return $response;
    }

    /**
     * @param $amount
     */
    public function formatAmount($amount)
    {
        return number_format($amount, 2, '.', '');
    }

    /**
     * @param $order
     */
    public function getCancelUrl($order)
    {
        return route('checkout.payment.failed', $order->id);
    }

    /**
     * @param $order
     */
    public function getReturnUrl($order)
    {
        return route('checkout.payment.completed', $order->id);
    }
}

To store the API credentials required to connect with API service, you can use config/services.phpwhich ships with the framework out of the box.

    'securepay' => [
        'merchant_id' => env('SECUREPAY_MERCHANTID'),
        'password' => env('SECUREPAY_PASSWORD'),
        'sandbox' => env('SECUREPAY_SANDBOX', true)
    ],

Now, we need a controller class to handle and process all incoming requests into the application. Let's add a class PaymentController.php

<?php

namespace App\Http\Controllers;

use App\Order;
use App\SecurePay;
use Exception;
use Illuminate\Http\Request;

/**
 * Class PaymentController
 * @package App\Http\Controllers
 */
class PaymentController extends Controller
{
    /**
     * @param Request $request
     */
    public function checkout(Request $request)
    {
        $order = Order::findOrFail(mt_rand(1, 140));

        // you application logic goes here
        // the above order is just for example.

        return view('checkout.payment', compact('order'));
    }

    /**
     * @param $order_id
     * @param Request $request
     */
    public function payment($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $gateway = new SecurePay;

        try {
            $response = $gateway->purchase([
                'amount' => $gateway->formatAmount($order->amount),
                'transactionId' => $order->id,
                'currency' => 'USD',
                'cancelUrl' => $gateway->getCancelUrl($order),
                'returnUrl' => $gateway->getReturnUrl($order),
            ], $request);
        } catch (Exception $e) {
            $order->update(['payment_status' => Order::PAYMENT_PENDING]);

            return redirect()
                ->route('checkout.payment.failed', [$order->id])
                ->with('message', sprintf("Your payment failed with error: %s", $e->getMessage()));
        }

        if ($response->isRedirect()) {
            $response->redirect();
        }

        return redirect()->back()->with([
            'message' => "We're unable to process your payment at the moment, please try again !",
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     * @return mixed
     */
    public function completed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $gateway = new SecurePay;

        $response = $gateway->complete([
            'amount' => $gateway->formatAmount($order->amount),
            'transactionId' => $order->id,
            'currency' => 'USD',
            'cancelUrl' => $gateway->getCancelUrl($order),
            'returnUrl' => $gateway->getReturnUrl($order),
        ], $request);

        if ($response->isSuccessful()) {
            $order->update([
                'transaction_id' => $response->getTransactionReference(),
                'payment_status' => Order::PAYMENT_COMPLETED,
            ]);

            return redirect()->route('checkout.payment')->with([
                'message' => 'Payment Successful, Thank you for your order !',
            ]);
        }

        return redirect()->back()->with([
            'message' => $response->getMessage(),
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     * @return mixed
     */
    public function failed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        return view('checkout.payment', compact('order'));
    }
}

To submit the payment information by the customer we need a checkout page, for this example, I have implemented a nice view with the code snippet below.

<div class="card-details">
  <h3 class="title">Credit Card Details</h3>
  <span>@csrf</span>
  <div class="row">
    <div class="form-group col-sm-8">
      <label for="card-holder">Card Holder</label>
      <div class="input-group expiration-date">
          <input type="text" class="form-control" placeholder="Fist Name" name="first_name" aria-label="Fist Name" aria-describedby="basic-addon1">
          <span class="date-separator">/</span>
          <input type="text" class="form-control" placeholder="Last Name" name="last_name" aria-label="Last Name" aria-describedby="basic-addon1">
      </div>
    </div>
    <div class="form-group col-sm-4">
      <label for="">Expiration Date</label>
      <div class="input-group expiration-date">
        <input type="text" class="form-control" placeholder="MM" aria-label="MM" name="expiry_month" aria-describedby="basic-addon1">
        <span class="date-separator">/</span>
        <input type="text" class="form-control" placeholder="YY" aria-label="YY" name="expiry_year" aria-describedby="basic-addon1">
      </div>
    </div>
    <div class="form-group col-sm-8">
      <label for="card-number">Card Number</label>
      <input id="card-number" type="text" class="form-control" placeholder="Card Number" name="card_number" aria-label="Card Holder" aria-describedby="basic-addon1">
    </div>
    <div class="form-group col-sm-4">
      <label for="cvc">CVC</label>
      <input id="cvc" type="text" class="form-control" placeholder="CVC" aria-label="CVC" name="cvc" aria-describedby="basic-addon1">
    </div>
    <div class="form-group col-sm-12">
      <button type="submit" class="btn btn-primary btn-block">
          <i class="fa fa-credit-card" aria-hidden="true"></i>
          Pay with Credit Card
      </button>
    </div>
  </div>
</div>

To view the full HTML code click here.

The preview looks like below.

SecurePay Integration, Checkout

To view the full source code written while crafting this blog post, visit the github repository and grab the code.

Thanks for reading up to the end, I hope you enjoyed reading this article. If you have any feedback on this article feel free to leave your comment below.

Happy Coding!

]]>
<![CDATA[PayPal Integration – Omnipay PayPal PHP Library v3.0 with Laravel]]> https://sujipthapa.co/blog/paypal-integration-omnipay-paypal-php-library-v30-with-laravel e23bd3c6-3ed4-4b6b-993e-4561be3b45c3 2018-05-20T05:12:00+05:45 Mr. Sujip Thapa This is my third blog post about PayPal Integration with Omnipay payment processing library. In this article, I will mainly cover about the recent release v3.0, which is finally available for developers around the world. I'm sure many developers around the world were eagerly waiting for this release.

In my previous article I explained about how to Integrate Omnipay v2.* with Laravel framework.

In the new release v3.0, the team focused mainly on separating the HTTP Client, to be independent with Guzzle. This release now supports symfony 3,4 components to run with all Laravel v5.* versions. This is a most awaited release, developers were not able to use, as it was not fully compatible with latest symfony components.

To know about the breaking changes and new additions to the package, view the full release note from their official doc and github repository.

The original omnipay/omnipay package has been changed as league/omnipay

By using this package (omnipay/paypal) we can implement different gateways listed below.

  • PayPal_Express (PayPal Express Checkout)
  • PayPal_ExpressInContext (PayPal Express In-Context Checkout)
  • PayPal_Pro (PayPal Website Payments Pro)
  • PayPal_Rest (Paypal Rest API)

We're going to integrate PayPal Express Checkout in this article below.

Before starting the coding let's install the package via composer.

composer require league/omnipay omnipay/paypal

Let's add new routes for the PayPal integration under app/routes/web.php

Route::get('/paypal/{order?}', [
    'name' => 'PayPal Express Checkout',
    'as' => 'order.paypal',
    'uses' => 'PayPalController@form',
]);

Route::post('/checkout/payment/{order}/paypal', [
    'name' => 'PayPal Express Checkout',
    'as' => 'checkout.payment.paypal',
    'uses' => 'PayPalController@checkout',
]);

Route::get('/paypal/checkout/{order}/completed', [
    'name' => 'PayPal Express Checkout',
    'as' => 'paypal.checkout.completed',
    'uses' => 'PayPalController@completed',
]);

Route::get('/paypal/checkout/{order}/cancelled', [
    'name' => 'PayPal Express Checkout',
    'as' => 'paypal.checkout.cancelled',
    'uses' => 'PayPalController@cancelled',
]);

Route::post('/webhook/paypal/{order?}/{env?}', [
    'name' => 'PayPal Express IPN',
    'as' => 'webhook.paypal.ipn',
    'uses' => 'PayPalController@webhook',
]);

I mainly write named routes all over my applications, having name routes are easy to create URLs from anywhere within the project with a simple helper route(), I have mentioned before about this mechanism with my previous articles as well.

Adding required models for this implementation.

app/Order.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    const PAYMENT_COMPLETED = 1;
    const PAYMENT_PENDING = 0;

    /**
     * @var string
     */
    protected $table = 'orders';

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['transaction_id', 'amount', 'payment_status'];
}

The order model only has few basic fields implemented just for the simple example, you are free to customize with your required fields in your application.

Adding new database table orders, you need to create a migration file with a console command.

php artisan make:migration create_table_orders --create=orders
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateOrdersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->increments('id');
            $table->string('transaction_id')->nullable();
            $table->float('amount')->unsigned()->nullable();
            $table->integer('payment_status')->unsigned()->default(0);
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('orders');
    }
}

Adding new helper class for generating gateway object and routes to interact with PayPal API.

app/PayPal.php

<?php

namespace App;

use Omnipay\Omnipay;

/**
 * Class PayPal
 * @package App
 */
class PayPal
{
    /**
     * @return mixed
     */
    public function gateway()
    {
        $gateway = Omnipay::create('PayPal_Express');

        $gateway->setUsername(config('services.paypal.username'));
        $gateway->setPassword(config('services.paypal.password'));
        $gateway->setSignature(config('services.paypal.signature'));
        $gateway->setTestMode(config('services.paypal.sandbox'));

        return $gateway;
    }

    /**
     * @param array $parameters
     * @return mixed
     */
    public function purchase(array $parameters)
    {
        $response = $this->gateway()
            ->purchase($parameters)
            ->send();

        return $response;
    }

    /**
     * @param array $parameters
     */
    public function complete(array $parameters)
    {
        $response = $this->gateway()
            ->completePurchase($parameters)
            ->send();

        return $response;
    }

    /**
     * @param $amount
     */
    public function formatAmount($amount)
    {
        return number_format($amount, 2, '.', '');
    }

    /**
     * @param $order
     */
    public function getCancelUrl($order)
    {
        return route('paypal.checkout.cancelled', $order->id);
    }

    /**
     * @param $order
     */
    public function getReturnUrl($order)
    {
        return route('paypal.checkout.completed', $order->id);
    }

    /**
     * @param $order
     */
    public function getNotifyUrl($order)
    {
        $env = config('services.paypal.sandbox') ? "sandbox" : "live";

        return route('webhook.paypal.ipn', [$order->id, $env]);
    }
}

Laravel ships with a config file config/services.php to store credentials to be used with third-party API services. For this implementation add following credentials under the same file like below.

    'paypal' => [
        'username' => env('PAYPAL_USERNAME'),
        'password' => env('PAYPAL_PASSWORD'),
        'signature' => env('PAYPAL_SIGNATURE'),
        'sandbox' => env('PAYPAL_SANDBOX'),
    ],

To handle and maintain the request coming into the application we need to pass information to the controller. We are adding new PayPalController.php

<?php

namespace App\Http\Controllers;

use App\Order;
use App\PayPal;
use Illuminate\Http\Request;

/**
 * Class PayPalController
 * @package App\Http\Controllers
 */
class PayPalController extends Controller
{
    /**
     * @param Request $request
     */
    public function form(Request $request)
    {
        $order = Order::findOrFail(mt_rand(1, 140));

        // the above order is just for example.

        return view('form', compact('order'));
    }

    /**
     * @param $order_id
     * @param Request $request
     */
    public function checkout($order_id, Request $request)
    {
        $order = Order::findOrFail(decrypt($order_id));

        $paypal = new PayPal;

        $response = $paypal->purchase([
            'amount' => $paypal->formatAmount($order->amount),
            'transactionId' => $order->id,
            'currency' => 'USD',
            'cancelUrl' => $paypal->getCancelUrl($order),
            'returnUrl' => $paypal->getReturnUrl($order),
        ]);

        if ($response->isRedirect()) {
            $response->redirect();
        }

        return redirect()->back()->with([
            'message' => $response->getMessage(),
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     * @return mixed
     */
    public function completed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $paypal = new PayPal;

        $response = $paypal->complete([
            'amount' => $paypal->formatAmount($order->amount),
            'transactionId' => $order->id,
            'currency' => 'USD',
            'cancelUrl' => $paypal->getCancelUrl($order),
            'returnUrl' => $paypal->getReturnUrl($order),
            'notifyUrl' => $paypal->getNotifyUrl($order),
        ]);

        if ($response->isSuccessful()) {
            $order->update([
                'transaction_id' => $response->getTransactionReference(),
                'payment_status' => Order::PAYMENT_COMPLETED,
            ]);

            return redirect()->route('order.paypal', encrypt($order_id))->with([
                'message' => 'You recent payment is sucessful with reference code ' . $response->getTransactionReference(),
            ]);
        }

        return redirect()->back()->with([
            'message' => $response->getMessage(),
        ]);
    }

    /**
     * @param $order_id
     */
    public function cancelled($order_id)
    {
        $order = Order::findOrFail($order_id);

        return redirect()->route('order.paypal', encrypt($order_id))->with([
            'message' => 'You have cancelled your recent PayPal payment !',
        ]);
    }

        /**
     * @param $order_id
     * @param $env
     * @param Request $request
     */
    public function webhook($order_id, $env, Request $request)
    {
        // to do with new release of sudiptpa/paypal-ipn v3.0 (under development)
    }
}

Finally, adding a simple view to submitting a form to pay the order amount with PayPal, the form redirects the user to PayPal hosted page to enter the user account details for the security reason.

app/resources/views/form.blade.php

@extends('app')

@section('content')
    <div class="container">
        <div class="gateway--info">
            <div class="gateway--desc">
                @if(session()->has('message'))
                    <p class="message">
                        {{ session('message') }}
                    </p>
                @endif
                <div class="row">
                    <div class="col">
                        <img src="{{ asset('images/paypal.png') }}" class="img-responsive gateway__img">
                    </div>
                    <div class="col">
                        <img src="{{ asset('images/laravel.png') }}" class="img-responsive gateway__img">
                    </div>
                </div>
                <p><strong>Order Overview !</strong></p>
                <hr>
                <p>Item : Yearly Subscription cost !</p>
                <p>Amount : ${{ $order->amount }}</p>
                <hr>
            </div>
            <div class="gateway--paypal">
                <form method="POST" action="{{ route('checkout.payment.paypal', ['order' => encrypt(mt_rand(1, 140))]) }}">
                    {{ csrf_field() }}
                    <button class="btn btn-pay">
                        <i class="fa fa-paypal" aria-hidden="true"></i> Pay with PayPal
                    </button>
                </form>
            </div>
        </div>
    </div>
@stop

The preview of the above view looks like below, I just made for this example.

PayPal Integration with PHP

For the above real-time example, I tried to cover only the mechanism to handle the payment request, failed payment, canceled a payment. You are free to customize it and manage as per your application architecture. The above example was created with Laravel v5.6 version but it works for all v5.* versions, that's the benefit of the new release for omnipay/paypal latest version v3.0.

To view the full source codes, view few commits (65e4b5a, 75a9585, c469e59, ab0b4db b24fa97) on github example repository.

Thanks for reading this article up on the end, please leave your feedback below in the comment section.

Happy Coding!

]]>
<![CDATA[Australia Post API — Location & Postcode Predictive Search]]> https://sujipthapa.co/blog/australia-post-api-location-postcode-predictive-search 7162cd34-5022-4348-8910-990334982898 2018-04-11T21:45:00+05:45 Mr. Sujip Thapa In this article, I'm writing about auto-suggesting postcode, the suburb of Australia with Australia Post API while filling up the address form.

I was recently working with an Australian client, it was an e-commerce platform to sell their products online.

I've also integrated shipping services like Temando, Transdirect, Auspost to calculate the postage cost in real-time. To protect their customer from not making mistake while filling up address form, they were interested in integrating real-time postcode, suburb auto-suggest. The idea was just for making the real-time postage cost calculation more consistent with the valid location.

1. Grab API Key

Before starting to build the feature and to make an API call, firstly we need to grab an API key from the Auspost developers center.Auspost API Key

2. Workflow

  • A customer starts typing at suburb, or postcode field in an HTML input box.
  • A twitter typeahead library detects the text change and fires an AJAX request, the server-side script again makes an API call behind the scene to get data from Auspost API.
  • The API call receives JSON data from Auspost API server, and it will be rendered as an auto-suggest popup.

3. Coding

I'm going to integrate this feature on top of Laravel framework v5.6, you are free to utilize it with any type of PHP project.

Adding new routes to display form, and for AJAX API call to the backend.

routes/web.php

Route::get('/postcode/search', [
    'name' => 'Australia Post Postcode Search',
    'as' => 'app.postcode.search',
    'uses' => 'AustraliaPostController@index',
]);

Route::get('/postcode/api/search', [
    'name' => 'Australia Post Postcode Search',
    'as' => 'app.postcode.api.search',
    'uses' => 'AustraliaPostController@search',
]);

app/Http/Controllers/AustraliaPostController.php

<?php

namespace App\Http\Controllers;

use App\Auspost\Request as Auspost;
use Illuminate\Http\Request;

/**
 * Class AustraliaPostController
 * @package App\Http\Controllers
 */
class AustraliaPostController extends Controller
{
    /**
     * @param Request $request
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    /**
     * Australia Post API search form.
     *
     * @return mixed
     */
    public function index()
    {
        return view('auspost.search');
    }

    /**
     * @return array
     */
    public function search()
    {
        $parameters = [
            'q' => $this->request->get('query'),
        ];

        $response = with(new Auspost(config('auspost.auth_key')))
            ->send($parameters);

        $localities = collect($response['localities']['locality'] ?? []);

        if ($localities->count()) {
            $collection = is_array($localities->first()) ? $localities : [$localities];

            return response()->json($collection);
        }

        return [];
    }
}

config/auspost.php

<?php

// replace xx with your real Auth Key sent by Auspost developers centre.

return [
    'auth_key' => 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
];

app/Auspost/Request.php

<?php

namespace App\Auspost;

use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;

/**
 * Class Request
 * @package App\Auspost
 */
class Request
{
    const END_POINT = 'https://digitalapi.auspost.com.au/postcode/search.json';

    /**
     * The API token.
     *
     * @var string
     */
    protected $token;

    /**
     * The guzzle http client.
     *
     * @var \GuzzleHttp\ClientInterface
     */
    protected $client;

    /**
     * Create a new request instance.
     *
     * @param string $token
     */
    public function __construct($token)
    {
        $this->token = $token;
        $this->client = new Client();
    }

    public function getEndpoint()
    {
        return self::END_POINT;
    }

    /**
     * Retrive data via API call from Auspost server.
     *
     * @param array $parameters
     * @param $method
     *
     * @return App\Auspost\Response
     */
    public function send(array $parameters = [], $method = 'get')
    {
        $url = $this->getEndpoint() . '?' . http_build_query($parameters);

        $parameters = [
            'headers' => [
                'Content-Type' => 'application/json',
                'Auth-Key' => $this->token,
            ],
        ];

        try {
            $response = $this->client->request($method, $url, $parameters);

        } catch (ClientException $exception) {
            return $exception;
        }

        return with(new Response($response))->toArray();
    }
}

app/Auspost/Response.php

<?php

namespace App\Auspost;

use GuzzleHttp\Psr7\Response as GuzzleResponse;

/**
 * Class Response.
 * @package App\Auspost
 */
class Response extends GuzzleResponse
{
    /**
     * The guzzle http client response.
     *
     * @var \GuzzleHttp\Message\Response
     */
    protected $response;

    /**
     * Create a new response instance.
     *
     * @param GuzzleResponse $response
     */
    public function __construct(GuzzleResponse $response)
    {
        $this->response = $response;
    }

    /**
     * @return mixed
     */
    public function toJson()
    {
        return (string) $this->response->getBody();
    }

    /**
     * @return mixed
     */
    public function toArray()
    {
        return json_decode($this->toJson(), true);
    }
}

resources/views/search.blade.php

<div class="card-body">
    <form method="POST" action="/register">
        @csrf

        <div class="form-group row">
            <label for="search" class="col-md-4 col-form-label text-md-right">Postcode</label>
            <div class="col-md-8">
                <input id="search" type="text" data-suggest-postcode="{{ route('app.postcode.api.search') }}" class="form-control" name="email">
            </div>
        </div>
    </form>
</div>

I have only shown a small block of code above for the form, to view the full source code for the above tutorial feel free to visit the repository on github.

4. Preview

Auspost API Search PreviewNote: To run the above example, it requires twitter typeahead javascript library v0.11.1, Laravel 5.6, guzzle 6.*, but you could still use it with lower version of Laravel and guzzle library.

Conclusion

Thanks for reading the full article up to the end, feel free to leave your feedback if you are happy reading it.

Happy Coding!

 

]]>
<![CDATA[PSR-2 Coding Standard – Automatically Format in Sublime Text]]> https://sujipthapa.co/blog/psr-2-coding-standard-automatically-format-in-sublime-text a51b37a5-4b89-44a7-8ce0-c2f8c3bd1b21 2018-04-07T13:57:00+05:45 Mr. Sujip Thapa It is always really nice when a code editor allows fixing your code to PSR2 coding standard by just hitting a single command.

At my work, we heavily use Laravel framework, which follows the PSR-2 coding standard. So, every day we create new packages, add new classes we first format our code to PSR-2 coding standard before submitting a pull request. We always strive to follow the coding standard and modern best practices to keep our code cleaner and elegant.

 

I personally use sublime as my primary code editor all the time, the main reason is its lightness and popularity among the developers around the world, another beneficial part is the huge availability of open-source packages for it.

Below, I'm going to cover how we format our PHP code to PSR-2 coding standard in sublime text. The team at symfony and the contributors have done really great job building a package that can be used with sublime text to format coding standard.

Step 1.

Installing php-cs-fixer via composer to run globally on your machine.

  composer global require friendsofphp/php-cs-fixer

After the installation process is completed open your terminal and simply run php-cs-fixer command just to confirm it's installed properly.

It is always a tedious job when you go and fix the formatting on each file you have in your project. Below, I have an example for you if you want to try to format your files manually.

 //Eg:
 php-cs-fixer fix /path/project --rules=@PSR2

Please refer to the official doc to discover all the available rules and commands you could use for your formatting.

 

Step 2.

To automate the code formatting process, we're going to set up a build process in a sublime text, so whenever you need to format your code instead of running the long command, you could just press the button on a keyword to fix it.

Open your sublime text editor.
Go to Tools Menu > Build System > New Build System

{
    "shell_cmd": "php-cs-fixer fix $file --rules=@PSR2"
}

Now, try to save, a prompt window will open, create a new folder called Builds at that position, save it inside that folder with name PSR-2.sublime-build

The build process setup is now ready to use, before using it we need to make a selection under Tools Menu > Build System > PSR 2 which is a new command we just added with the process above.

Usage

  • If you're Windows, Linux user just hit Ctrl + B
  • If you're Mac user ⌘ + B

Learn more about PSR-2 Coding Standard.

Conclusion

Thanks for reading the article up to the end, if you are happy reading it and was helpful to you, feel free to share and leave your feedback below in the comment section.

Happy Coding!

]]>
<![CDATA[Instagram Feed – Building Gallery with Real-time Data via Instagram API]]> https://sujipthapa.co/blog/instagram-feed-building-gallery-with-real-time-data-via-instagram-api 86275746-c168-4967-990e-acfab63825e2 2018-03-24T18:14:12+05:45 Mr. Sujip Thapa I recently wrote an article about building an RSS feed section to publish Facebook Instant Articles automatically. In this article, I will show how to build a gallery on your website with the picture you have uploaded to your Instagram account. In this tutorial I'll show you an easy way to pull down the data via API, using Vinkla’s Instagram package. I would like to thank him for creating a nice package.

 

API Credentials

Follow up the developer guide via their documentation, generate Client ID, Client Secret and you will need to go to another site to generate an access token.

Step 1

Let’s add new config array under existing file config/services.php

    'instagram' => [
        'access-token' => 'xxxxx.xxx.xxxx',
        //replace xxx with your actual access token
    ],

Step 2

Adding a new route to handle the URL /instagram/feed under routes/web.php

    Route::get('/instagram/feed', [
        'name' => 'Instagram Feed',
        'as' => 'app.instagram.feed',
        'uses' => 'InstagramController@feed',
    ]);

Step 3

Installing package via composer, use command from below or follow the installation guide from the package repository on github.

composer require vinkla/instagram php-http/message php-http/guzzle6-adapter
 

Step 4

Creating a new controller to handle the request and execute the API call.

    <?php

    namespace App\Http\Controllers;

    use Vinkla\Instagram\Instagram;

    /**
     * Class InstagramController
     * @package App\Http\Controllers
     */
    class InstagramController extends Controller
    {
        /**
         * @return mixed
         */
        public function feed()
        {
            $instagram = new Instagram(config('services.instagram.access-token'));

            $images = collect($instagram->get())->map(function ($each) {
                return $each->images->standard_resolution->url;
            });

            return view('gallery', compact('images'));
        }
    }

Now, look at the code above in the controller, I have added the logic to pull down the Instagram feeds via API and utilized the Laravel collection map() method to filter the images with standard resolution only. If you would like to show likes, a caption associated with those images you could customize that array and call them while rendering on the view file.

Step 5

Finally, its time to loop the images on gallery.blade.php file under resources/views.

    <div class="tz-gallery">
        <div class="row">
          @foreach($images as $key => $image)
            <div class="col-sm-12 col-md-4">
                <a class="lightbox" href="{{ $image }}">
                    <img src="{{ $image }}" alt="Instagram Feed" class="img-responsive">
                </a>
            </div>
            @endforeach
        </div>
    </div>
 

I have added a small block of code to just show the process of looping and displaying the images. The full source code I wrote for this tutorial is available on github, feel free to review the full pull request and utilize on your project.

Conclusion

Thanks for reading this article up to the end, if it was helpful to you, feel free share the knowledge with others, also don't forget to leave your feedback below in the comment section.

Happy Coding!

]]>
<![CDATA[How to Create A Trending Articles List using Google Analytics with Laravel]]> https://sujipthapa.co/blog/how-to-create-a-trending-articles-list-using-google-analytics-with-laravel 80c51a2a-845d-4407-8fa3-bf0b8846ba32 2018-03-17T13:45:00+05:45 Mr. Sujip Thapa In this article, I will guide you about building top trending articles using Google Analytics API with Laravel framework.

Recently I built this feature for my own blog, if you would like to view it open up the home page of my blog and see around the right sidebar.

I have used the popular laravel package laravel-analytics built by Spatie.

Prerequisites

Before starting to do the actual coding, first, you need to generate the API credentials required for API connectivity.

 

I am not going to cover the process of generating the API credentials, but I recommend you to follow the full steps explained by Spatie in their package documentation page.

Feature

The trending section I have built has following features, it was based on my own requirement.

If you would like to show the trending posts directly to your view without storing any records to the database or without any implicit logic, that is also provided by the package out of the box.

I will be doing it differently in this article, see below.

  • A console command to fetch the daily top views via Google Analytics API. Also, the command will purge the old records from database pulled before one week, that all depends on you, and you will be able to customize that all.
  • A helper class to fetch and manipulate the weekly page views pulled via API and group them by each blog.
  • The database process will be cached for every single day, so that will reduce queries to load the site faster.

Coding

Installing package via console mode with the composer.

composer require spatie/laravel-analytics

The package will be auto-registered in laravel v5.5 and above. If you are using lower version register the package by following the installation guide.

 

app/Blog.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * Class Blog
 * @package App
 */
class Blog extends Model
{
    /**
     * @var string
     */
    protected $table = 'blog';

    /**
     * @var array
     */
    protected $dates = ['published_at', 'deleted_at'];

    /**
     * @var string
     */
    protected $fillable = ['user_id', 'name', 'slug', 'excerpt', 'content', 'status', 'published_at'];

    /**
     * @param $slug
     */
    public static function findByTitle($slug = null)
    {
        return self::where('slug', $slug)->first();
    }
}

I am assuming that you are running blogs already, my blog model looks like above.

Adding new migration for trendings table.

php artisan make:migration create_trendings_table --create=trendings
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTrendingsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('trendings', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('blog_id')->nullable();
            $table->integer('views')->nullable();
            $table->string('url', 1024)->nullable();
            $table->string('page_title', 1024)->nullable();
            $table->tinyInteger('status')->default(1)->nullable();
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('trendings');
    }
}

app/Trending.php

<?php

namespace App;

use App\Blog;
use Illuminate\Database\Eloquent\Model;
use Spatie\Analytics\Period;

/**
 * Class Trending
 * @package App
 */
class Trending extends Model
{
    /**
     * @var string
     */
    protected $table = 'trendings';

    /**
     * @var boolean
     */
    public $timestamps = true;

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var string
     */
    protected $fillable = ['blog_id', 'views', 'url', 'page_title'];

    /**
     * @return mixed
     */
    public function blog()
    {
        return $this->belongsTo(Blog::class);
    }

    /**
     * @param $query
     * @return mixed
     */
    public function scopeMonthly($query)
    {
        $period = Period::months(1);

        return $query->whereBetween('created_at', [
            $period->startDate,
            $period->endDate->endOfDay(),
        ]);
    }

    /**
     * @param $query
     * @return mixed
     */
    public function scopeWeekly($query)
    {
        $period = Period::days(7);

        return $query->whereBetween('created_at', [
            $period->startDate,
            $period->endDate->endOfDay(),
        ]);
    }
}

Adding new console command for API connectivity.

 

php artisan make:command TrendingCommand

<?php

namespace App\Console\Commands;

use App\Blog;
use App\Trending;
use Illuminate\Console\Command;
use Spatie\Analytics\AnalyticsFacade as Analytics;
use Spatie\Analytics\Period;

/**
 * Class TrendingCommand
 * @package App\Console\Commands
 */
class TrendingCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'analytics:trending';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Sync page view from Google Analytics API';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $pages = Analytics::fetchMostVisitedPages(Period::days(1), 300);

        if ($pages->count()) {
            $this->purge();

            $pages->map(function ($each) {
                $each = (object) $each;

                if (starts_with($each->url, '/blog/')) {
                    $slug = str_replace("/blog/", '', $each->url);
                    $blog = Blog::findByTitle($slug);

                    if (!empty($blog)) {
                        Trending::create([
                            'blog_id' => $blog->id,
                            'views' => $each->pageViews,
                            'status' => $blog->status,
                            'page_title' => $blog->name,
                            'url' => $each->url,
                        ]);

                        $this->info("{$blog->name} - {$each->pageViews} \n");
                    }
                }
            });
        }
    }

    /**
     * @return mixed
     */
    public function purge()
    {
        $period = Period::days(8);

        $this->info("Purging records before : {$period->startDate} \n");

        return Trending::where('created_at', '<', $period->startDate)
            ->forceDelete();
    }
}

Registering console command to run with the application.

app/Console/Kernel.php

    protected $commands = [
        'App\Console\Commands\TrendingCommand',
    ];

app/Helpers/Trending.php

<?php
namespace App\Helpers;

use App\Trending as Popular;
use Illuminate\Support\Facades\Cache;

/**
 * Class Trending
 * @package App\Helpers
 */
class Trending
{
    /**
     * @return mixed
     */
    public static function weekly($take = 15)
    {
        $collection = collect();

        $trendings = Cache::remember('popular', 60 * 24, function () {
            return Popular::with('blog')->weekly()->get();
        });

        $trendings->groupBy('blog_id')->map(function ($each) use ($collection) {
            $object = collect($each);

            $item = $object->first();
            $item->views = $object->sum('views');

            $collection->push($item);
        });

        return $collection->take($take);
    }
}

The job of the above helper class is to implicitly manipulate the seven days pages views and sort them out by the highest page views.

Now, creating new view partial to include it where ever you want to show the trending list.

resources/views/_trending.blade.php

@inject('trending', 'App\Helpers\Trending')

@php
    $trendings = $trending::weekly();
@endphp

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-12">
                <div class="card">
                    <div class="card-header">Trending</div>
                    <div class="card-body">
                        @if($trendings->count())
                            <ul>
                                @foreach($trendings as $key => $each)
                                   <li>
                                        <div class="article">
                                            <a href="{{ asset($each->url) }}" title="{{ $each->views }}">
                                                <span class="text-muted">{{ $each->blog->published_at->format('d M, Y') }}</span><br>
                                                {{ $each->blog->name }}
                                            </a>
                                        </div>
                                    </li>
                                @endforeach
                            </ul>
                        @endif
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Alternative

If you would like to consume the real-time data via API instead of going through the above feature I built for my use case, you could just follow the package documentation and it has enough resources to understand.

 
// pull down real time data via Google Analytics API

Analytics::fetchMostVisitedPages(Period::days(7));

Now after completing the coding work, the most important thing is to set up a cron job that runs mid-night at 12:00 am to pull down the complete page views for that day.

My cron job looks like below.

0 0 * * * /usr/bin/php /home/deployer/project/artisan analytics:trending

Conclusion

Thanks for reading the article up to the end, if you are happy reading it and was helpful to you, feel free to share and leave your feedback below in the comment section. The article was written based on Laravel v5.6, feel free to utilize it with higher or lower versions as per your requirement. The full source code is available on github.

Happy Coding!

]]>
<![CDATA[Publishing Facebook Instant Articles via RSS Feed with Laravel]]> https://sujipthapa.co/blog/publishing-facebook-instant-articles-via-rss-feed-with-laravel b6fb2f6a-59dd-45ec-a530-145d4e1b0706 2018-03-08T15:19:00+05:45 Mr. Sujip Thapa If you want to publish your web articles to Facebook Instant Articles platform and looking for the best way to export your quality articles from your blog to Instant Articles platform without worrying much about configurations, then you're at the right place now.

In this post, I will show you how to automatically export articles whenever you publish a new article on your website. I recently built this feature for my own website, and it was built on top of Laravel framework.

If your website runs other than WordPress and you want to integrate the RSS feed, this post will help you to understand how to do that for your platform. If you would like to know about exporting the Instant Articles from WordPress website, you can read this doc from facebook developers platform.

 

Two different ways to export Instant Articles.

RSS Publishing, API Publishing

The easiest way is setting up RSS feed, which will be automatically ingested periodically by Facebook. So there will be no other configurations required to pull down your fresh articles from your CMS.

Let's begin to build RSS feed, to allow Facebook to read in XML version.

Adding new routes under routes/web.php

Route::get('blog/rss', [
    'as' => 'app.blog.rss',
    'uses' => 'BlogController@rss',
]);

Route::get('blog/{slug}', [
    'as' => 'app.blog.view',
    'uses' => 'BlogController@view',
]);

Adding new Blog model app/Blog.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * Class Blog
 * @package App
 */
class Blog extends Model
{
    const STATUS_ENABLED = 1;
    const STATUS_DISABLED = 0;

    /**
     * @var string
     */
    protected $table = 'blog';

    /**
     * @var array
     */
    protected $dates = ['published_at', 'deleted_at'];

    /**
     * @var string
     */
    protected $fillable = ['user_id', 'name', 'slug', 'guid', 'excerpt', 'content', 'status', 'published_at'];

    /**
     * @param Builder $query
     * @return mixed
     */
    public function scopePublished($query)
    {
        return $query->where('published_at', '<=', now());
    }

    /**
     * @param Builder $query
     * @return mixed
     */
    public function scopeEnabled($query)
    {
        return $query->where('status', self::STATUS_ENABLED);
    }
}

Adding new controller BlogController.php

<?php

namespace App\Http\Controllers;

use App\Blog;
use Illuminate\Support\Str;

/**
 * Class BlogController
 * @package App\Http\Controllers
 */
class BlogController extends Controller
{
    public function rss()
    {
        $blogs = Blog::enabled()
            ->published()
            ->latest('published_at')
            ->get();

        $blogs->map(function ($each) {
            if (empty($each->guid)) {
                $each->guid = Str::uuid();
            }
        });

        return view('rss', [
            'blogs' => $blogs,
            'title' => 'Short title about your blog',
        ]);
    }

    /**
     * @param $slug
     */
    public function view($slug)
    {
        return Blog::where('slug', $slug)->first();
    }
}

Str::uuid() was introduced with Laravel v5.6, with lower versions you could use my simple package for creating guid.

 
composer require sudiptpa/guid

To discover the features, please through the github link.

Adding new view resources/rss.blade.php

<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>{{ $title }}</title>
    <link>{{ url('/') }}</link>
    <description>
        A short description about your blog.
    </description>
    <language>en-us</language>
    <lastBuildDate>{{ date('c') }}</lastBuildDate>
    @forelse($blogs as $key => $blog)
      <item>
        <title><![CDATA[{{ $blog->name }}]]></title>
        <link>{{ route('app.blog.view', ['slug' => $blog->slug]) }}</link>
        <guid>{{ $blog->guid }}</guid>
        <pubDate>{{ date('c', strtotime($blog->published_at)) }}</pubDate>
        <author>Author Name</author>
        <description><![CDATA[{{ $blog->excerpt }}]]></description>
        <content:encoded>
          <![CDATA[
            @include('partials._rss')
          ]]>
        </content:encoded>
      </item>
    @empty
      <item>No feeds found</item>
    @endforelse
  </channel>
</rss>

Adding another partial file included with the above view. resources/partials/_rss.blade.php

<!doctype html>
<html lang="en" prefix="op: http://media.facebook.com/op#">
    <head>
        <meta charset="utf-8">
        <meta property="op:markup_version" content="v1.0">
        <meta property="fb:article_style" content="default"/>
        <link rel="canonical" href="{{ route('app.blog.view', ['slug' => $blog->slug]) }}">
        <title>{{ $blog->name }}</title>
    </head>
    <body>
    <article>
        <header>
            <h1>{{  $blog->name  }}</h1>
            <h2> {{ $blog->excerpt }}</h2>
            <h3 class="op-kicker">
                PHP <!-- Replace with your category name-->
            </h3>
            <address>
                Sujip Thapa <!-- Replace with your author name-->
            </address>
            <time class="op-published" dateTime="$blog->published_at->format('c') }}">{{ $blog->published_at->format('M d Y, h:i a') }}</time>
            <time class="op-modified" dateTime="{{ $blog->updated_at->format('c') }}">{{ $blog->updated_at->format('M d Y, h:i a') }}</time>
        </header>

        {{ $blog->content }}

        <footer>
        <aside>
            A short footer note for your each Instant Articles.
        </aside>
        <small>© Copyright {{ date('Y') }}</small>
        </footer>
    </article>
    </body>
</html>

Note: After exporting articles to Instant Articles platform, you will need to share the post on your facebook page, after sharing on that page article will start appearing as Instant Article.

Finally, to implement the above feature, follow the below steps.

  1. Facebook Page
  2. Publishing Tools
  3. Instant Articles
  4. Configuration
  5. Production RSS Feed

in the input box paste the link ending with blog/rss: (example: https://sujipthapa.co/blog/rss)

 

After saving the RSS feed URL, Facebook will start to retrieve fresh articles periodically.

Conclusion

Thanks for reading the article up to the end, feel free to give your feedback below in the comment section if you like.

Happy Coding!

]]>
<![CDATA[Define Your Google Preferred Domain with Laravel Middleware]]> https://sujipthapa.co/blog/define-your-google-preferred-domain-with-laravel-middleware 4ce2c044-7ed9-4aa1-a3ec-0057612eb5bc 2018-03-04T09:15:00+05:45 Mr. Sujip Thapa This is my second blog post about setting up Google preferred domain from code context for your Laravel application. In my previous post, I have explained a really simple way of doing it with almost no configuration available to set up the preferred version of your choice. In this post, I am going to cover the advanced way of doing it with some configuration available with certain options.

Why is defining preferred domain necessary?

Defining a Google preferred domain is a way of instructing Google to use a final version of your domain to be used forever. Your web pages link should be permanent and never be changing frequently so that Google can start indexing, which is a process of adding pages of your website into Google search. The indexed pages will appear in Google searches around the world with matching keywords.

 

The important part is if your web pages URL are not same when a google crawler runs on your site or the pages start to redirect to 404, Google will start removing them from indexing and they will not appear in search results.

The example of what I wanted to describe above is, your domain should always be like, either www or non-www (i.e. http://www.sujipthapa.co or http://sujipthapa.co).

The primary concern of setting up a google preferred domain is to improve the SEO of your website, that instructs Google to always recognize the final version of your URL to appear in search results.

I would like to recommend you to read this Google support article to understand in detail about the benefits of setting google preferred domain for your website.

Note: Below with the code, it will only focus on forcing your URL to be the preferred version you have selected on Google Search Console. The code will depend on the configuration and will instruct to redirect on the preferred version.

Setup

The first step of the setup is verifying your preferred domain on google end. Read the Google support article carefully and define your preferred version of your domain.

 

Let's start by adding new config file under config/domain.php

<?php

return [
    /*
    |--------------------------------------------------------------------------
    | Preferred Domain / Canonical Domain
    |--------------------------------------------------------------------------
    | Example: https://www.example.com (www), https://example.com (non-www)
    |
    | Available Mode: "//www.", "//"
     */

    'preferred' => '//',

    /*
    |--------------------------------------------------------------------------
    | HTTP Protocol
    |--------------------------------------------------------------------------
    |
    | Available Mode: "http://", "https://"
     */

    'protocol' => 'https://',
];

The above configuration file has two settings available, one is preferred domain, and another is for SSL mode. I highly recommend you to install SSL certificate for your website. If an SSL certificate is not installed, beginning in July 2018 your site visitors will see a “Not Secure” warning, recently Google Security Blog announced with the release of Chrome 68, it will mark all web pages with HTTP as "not secure" version.

Adding new middleware class PreferredDomain.php under app/Http/Middleware directory.

<?php

namespace App\Http\Middleware;

use App\Domain;
use Closure;

/**
 * Class PreferredDomain
 * @package App\Http\Middleware
 */
class PreferredDomain
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $domain = new Domain($request);

        if ($domain->diff()) {
            return redirect()->to($domain->translated(), 301);
        }

        return $next($request);
    }
}

This middleware should monitor every HTTP request on your website. So, register it like below under app/Http/Kernal.php

    /**
     * @var array
     */
    protected $middleware = [
        ...
        \App\Http\Middleware\PreferredDomain::class,
    ];

Again, adding new class Domain.php under app directory to apply a way of translating your non-preferred domain to preferred version automatically, it will be translated to the domain version you have defined in the configuration file. It will monitor each request to verify that each request URL is consistent with your preferred version on Google Search Console.

<?php

namespace App;

use Illuminate\Http\Request;

/**
 * Class Domain
 * @package App
 */
class Domain
{
    const FORCE_HTTP = 'http://';
    const FORCE_HTTPS = 'https://';

    const FORCE_WWW = '//www.';
    const FORCE_NOWWW = '//';

    const REGEX_FILTER_WWW = '/(\/\/www\.|\/\/)/';
    const REGEX_FILTER_HTTPS = '/^(http:\/\/|https:\/\/)/';

    /**
     * @var \Illuminate\Http\Request
     */
    protected $request;

    /**
     * @var string
     */
    protected $translated;

    /**
     * @param \Illuminate\Http\Request $request
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    /**
     * @return bool
     */
    public function isEqual(): bool
    {
        return $this->request->fullUrl() !== $this->translated();
    }

    /**
     * Determines if the original url differs with translated.
     *
     * @return bool
     */
    public function diff(): bool
    {
        $this->translated = $this->translate();

        return $this->isEqual();
    }

    /**
     * @return string
     */
    public function translated(): string
    {
        if (!$this->translated) {
            $this->translated = $this->translate();
        }

        return $this->translated;
    }

    /**
     * @return string
     */
    public function translate(): string
    {
        $url = $this->request->fullUrl();

        $protocol = $this->getProtocol();

        $filtered = preg_replace(self::REGEX_FILTER_HTTPS, $protocol, $url);

        $preferred = $this->getPreferred();

        return preg_replace(self::REGEX_FILTER_WWW, $preferred, $filtered);
    }

    /**
     * Determines if the request supports the https,
     * otherwise, fallback to default (http) protocol.
     *
     * @return string
     */
    public function getProtocol(): string
    {
        if (!$this->request->secure()) {
            return self::FORCE_HTTP;
        }

        return config('domain.protocol') ?? self::FORCE_HTTP;
    }

    /**
     * Determines the preferred domain from config.
     *
     * @return string
     */
    public function getPreferred(): string
    {
        return config('domain.preferred') ?? self::FORCE_NOWWW;
    }
}

Let's say my preferred version is - https://sujipthapa.co but user tries to go to https://www.sujipthapa.co now the middleware will monitor and redirect the non-preferred to preferred version. The job of the middleware is to translate the non-preferred to preferred version and issue a 301 redirect to correct version.

 

This is an implicit process that executes before the HTTP request enters into the application.

I have used the above middleware Laravel v5.5, but it is still compatible with the v5.6 version released in Feb 2018.

Conclusion

Thanks for reading the post up to the end, feel free to share, comment your feedback below in the comment section.

Happy Coding!

]]>
<![CDATA[Easily Switch PHP versions on Linux with Script]]> https://sujipthapa.co/blog/easily-switch-php-versions-on-linux-with-script 76409e7d-0499-4495-82a9-4e00a7719a4a 2018-02-11T01:18:00+05:45 Mr. Sujip Thapa If you are looking for best way to switch multiple PHP versions on your machine, you're at the right place to get the best solution for your requirement.

Let's say on your working computer you have installed multiple PHP versions (for eg: PHP 7.0 and PHP 7.2). As a default version, the PHP 7.0 is set to Nginx or Apache and CLI mode.

Of course, as a modern web developer who works on the different framework, packages need to have multiple PHP versions installed on their working machine. During their work on different projects, the project may not support all latest versions, so the developer may need to switch to a required version of PHP to run that project.

Below I will show you how to use the command line as well as a bash alias function to easily switch your versions.

You may want to go from PHP 7.2 to 7.0, use the commands below.

If you don't know how to check the php version on Linux Ubuntu type below command on terminal.

    php -v
//php 7.2 to php 7.0 switcher

sudo a2dismod php7.2 ; sudo a2enmod php7.0 ; sudo systemctl restart apache2

sudo ln -sfn /usr/bin/php7.0 /etc/alternatives/php

//if you are using nginx
sudo systemctl restart nginx

Similarly, while PHP version 7.0 is active, you can fall back to 7.2 with the following commands.

//php 7.0 to php 7.2 switcher

sudo a2dismod php7.0 ; sudo a2enmod php7.2 ; sudo systemctl restart apache2

sudo ln -sfn /usr/bin/php7.2 /etc/alternatives/php

//if you are using nginx
sudo systemctl restart nginx

Repeating the above process frequently is really irritating, time-wasting, right?

Finally, I decided to create a bash alias as a function to reduce my time to repeat the php switching and easy to remember.

Setup

Open up your terminal and type.

sudo nano ~/.bashrc

Scroll around the last line, where we want to paste the below function.

phpswap () {
    local IS_PHP7=`php --version|grep "PHP 7.2"`

    if [[ -z $IS_PHP7 ]]; then
        echo "Switching to PHP 7.2"
        sudo a2dismod php7.0;
        sudo a2enmod php7.2;
        sudo systemctl restart apache2;
        sudo ln -sfn /usr/bin/php7.2 /etc/alternatives/php
    else
        echo "Switching to PHP 7.0"
        sudo a2dismod php7.2;
        sudo a2enmod php7.0;
        sudo systemctl restart apache2;
        sudo ln -sfn /usr/bin/php7.0 /etc/alternatives/php
    fi
}

Usage

After completing the above process simply hit phpswap on your terminal.

I use multiple PHP versions, 7.2, 7.0 with Ubuntu 16.04.3 LTS. I simply run phpswapover the terminal, it detects the current default PHP version and switches to exactly reverse version which I need to go with.

Easy right?

We're done!

Conclusion

Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.

Happy Coding!

Last Updated: 16th Feb, 2018

]]>
<![CDATA[PayPal Instant Payment Notification (IPN) Handling with Laravel]]> https://sujipthapa.co/blog/paypal-instant-payment-notification-ipn-handling-with-laravel 1ae515cb-abb2-4734-b838-a96f6e6bb295 2018-02-10T09:43:00+05:45 Mr. Sujip Thapa This is a follow-up article from my previous post A guide to Integrate Omnipay PayPal with Laravel, at the end of that article I have mentioned that I will be writing about handling PayPal Instant Payment Notification (IPN) with Laravel. So finally it is published, so you can go through it.

Below, I am going to show in detail about the way of handling the webhook notification that comes from PayPal into the Laravel application.

If you are a beginner level developer I highly recommend you to read this official doc to understand the flow of PayPal Instant Payment Notification.

Why handling IPN is necessary?

Let's say a customer at your site is trying to pay for certain service or goods, your application takes the user to PayPal to complete the payment, the user makes payment, it is completed but due to some technical error the payment completed page from PayPal couldn't return to your application to handle completed orders. In that case, your application may fail to send emails, update the database for recently paid order status and its payment status.

To handle this situation PayPal gives a webhook service that sends POST request with a payload about the payment status.

Let's get started :)

Before diving into the code first let's understand how to set up a notification URL that PayPal actually uses to send a POST request to your web application. There are actually two different ways to setup IPN notification URL.

  • You can set up a static end that never changes and that is always ready to handle the PayPal webhook that may arrive at any time into your application. Follow the below steps from this official doc.
null
  • Another way is that we can send a dynamic notify URL with a payment request implicitly from the code, that way we don't need to worry about the setting up notify URL under PayPal > My Account. Below I will show how to send a notify URL with the payment request.

In the previous article, you can see how to send the notify URL with the payment request, I have made the notify URL to be dynamic with parameters like order_id, environment, so the URL changes for each order.

    $paypal = new PayPal;

    $response = $paypal->complete([
        'amount' => $paypal->formatAmount($order->amount),
        'transactionId' => $order->id,
        'currency' => 'USD',
        'cancelUrl' => $paypal->getCancelUrl($order),
        'returnUrl' => $paypal->getReturnUrl($order),
        'notifyUrl' => $paypal->getNotifyUrl($order),
    ]);

    //route for generating notify URL

    /**
     * @param $order
     */
    public function getNotifyUrl($order)
    {
        $env = config('paypal.credentials.sandbox') ? "sandbox" : "live";

        return route('webhook.paypal.ipn', [$order->id, $env]);
    }

To understand this article, I recommend you to read the previous post about integrating PayPal with Laravel.

Let's start diving into the code.

Installation

composer require sudiptpa/paypal-ipn

If you are using Laravel v4 stick with 1.0.x-dev in your composer.json file.

{
    "require": {
        "sudiptpa/paypal-ipn": "1.0.x-dev",
    }
}

Setup

Defining a route to handle POST request coming from PayPal server.

Route::post('/webhook/paypal/{order?}/{env?}', [
    'name' => 'PayPal Express IPN',
    'as' => 'webhook.paypal.ipn',
    'uses' => 'PayPalController@webhook',
]);

After creating a route to accept incoming POST request via PayPal, you need to keep in mind that Laravel by default filters each HTTP requests entering the application with VerifyCsrfToken middleware. In order to allow the above route to access the application the URL should be excluded from the csrf check.

I have previously covered about it with another article "Disabling CSRF on Specific Route via Middleware", so you can go throgh it to understand how to do that.

Let's create a migration for storing IPN records in the database.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTablePaypalIpnRecords extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('paypal_ipn_records', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('order_id')->nullable();
            $table->string('verified');
            $table->string('transaction_id');
            $table->string('payment_status');
            $table->string('request_method')->nullable();
            $table->string('request_url')->nullable();
            $table->longText('request_headers')->nullable();
            $table->longText('payload')->nullable();
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('paypal_ipn_records');
    }
}

A new model PayPalIPN.php for storing the IPN logs into database.

<?php

namespace App;

use App\Order;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

/**
 * Class PayPalIPN
 * @package App
 */
class PayPalIPN extends Model
{
    use SoftDeletes;

    const COMPLETED = "Completed";
    const IPN_FAILURE = "FALIURE";
    const IPN_INVALID = "INVALID";
    const IPN_VERIFIED = "VERIFIED";

    /**
     * @var boolean
     */
    public $timestamps = true;

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['order_id', 'verified', 'transaction_id', 'payment_status', 'request_method', 'request_url', 'request_headers', 'payload'];

    /**
     * @var string
     */
    protected $table = 'paypal_ipn_records';

    /**
     * @return boolena
     */
    public function isCompleted()
    {
        return in_array($this->payment_status, [self::COMPLETED]);
    }

    /**
     * @return boolena
     */
    public function isVerified()
    {
        return in_array($this->verified, [self::IPN_VERIFIED]);
    }

    /**
     * @return mixed
     */
    public function orders()
    {
        return $this->belongsTo(Order::class);
    }
}

Adding few new methods in Order.php model for easy condition check and model scope to perform database operation.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

/**
 * Class Order
 * @package App
 */
class Order extends Model
{
    use SoftDeletes;

    const COMPLETED = 1;
    const PENDING = 0;

    /**
     * @var string
     */
    protected $table = 'orders';

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['transaction_id', 'amount', 'payment_status'];

    /**
     * @param Builder $query
     * @param string $transaction_id
     * @return mixed
     */
    public function scopeFindByTransactionId($query, $transaction_id)
    {
        return $query->where('transaction_id', $transaction_id);
    }

    /**
     * Payment completed.
     *
     * @return boolean
     */
    public function paid()
    {
        return in_array($this->payment_status, [self::COMPLETED]);
    }

    /**
     * Payment is still pending.
     *
     * @return boolean
     */
    public function unpaid()
    {
        return in_array($this->payment_status, [self::PENDING]);
    }
}

Now creating a webhook() method on PayPalController.php

<?php

namespace App\Http\Controllers;

use App\Order;
use App\PayPal;
use App\Repositories\IPNRepository;
use Illuminate\Http\Request;
use PayPal\IPN\Listener\Http\ArrayListener;

/**
 * Class PayPalController
 * @package App\Http\Controllers
 */
class PayPalController extends Controller
{
    /**
     * @param IPNRepository $repository
     */
    public function __construct(IPNRepository $repository)
    {
        $this->repository = $repository;
    }

    /**
     * @param $order_id
     * @param $env
     * @param Request $request
     */
    public function webhook($order_id, $env, Request $request)
    {
        $listener = new ArrayListener;

        if ($env == 'sandbox') {
            $listener->useSandbox();
        }

        $listener->setData($request->all());

        $listener = $listener->run();

        $listener->onInvalid(function (IPNInvalid $event) use ($order_id) {
            $this->repository->handle($event, PayPalIPN::IPN_INVALID, $order_id);
        });

        $listener->onVerified(function (IPNVerified $event) use ($order_id) {
            $this->repository->handle($event, PayPalIPN::IPN_VERIFIED, $order_id);
        });

        $listener->onVerificationFailure(function (IPNVerificationFailure $event) use ($order_id) {
            $this->repository->handle($event, PayPalIPN::IPN_FAILURE, $order_id);
        });

        $listener->listen();
    }
}

Again, creating another class IPNRepository.php that act as a bridge between model and controller.

<?php
namespace App\Repositories;

use App\Order;
use App\PayPalIPN;
use Illuminate\Http\Request;

/**
 * Class IPNRepository
 * @package App\Repositories
 */
class IPNRepository
{
    /**
     * @param Request $request
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    /**
     * @param $event
     * @param $verified
     * @param $order_id
     */
    public function handle($event, $verified, $order_id)
    {
        $object = $event->getMessage();

        if (is_numeric($order_id)) {
            $order = Order::find($order_id);
        }

        if (empty($order)) {
            $order = Order::findByTransactionId(
                $object->get('txn_id')
            )->first();
        }

        $paypal = PayPalIPN::create([
            'verified' => $verified,
            'transaction_id' => $object->get('txn_id'),
            'order_id' => $order ? $order->id : null,
            'payment_status' => $object->get('payment_status'),
            'request_method' => $this->request->method(),
            'request_url' => $this->request->url(),
            'request_headers' => json_encode($this->request->header()),
            'payload' => json_encode($this->request->all()),
        ]);

        if ($paypal->isVerified() && $paypal->isCompleted()) {
            if ($order && $order->unpaid()) {
                $order->update([
                    'payment_status' => $order::COMPLETED,
                ]);

                // notify customer
                // notify order handling staff
                // update database logic
            }
        }
    }
}

Testing

PayPal provides an Instant Payment Notification (IPN) simulator to test your integration.

If you want to test with real sandbox credentials, use your staging server for your application, as PayPal never comes to your local development environment with IPN request.

I personally used POSTMAN, an API testing client to debug my IPN in my local development environment.

Conclusion

The complete source code is available in the github repository, where I push the real codes I prepare while creating every single tutorial on my blog.

Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.

Happy Coding!

]]>
<![CDATA[Disabling CSRF on Specific Route via Middleware]]> https://sujipthapa.co/blog/disabling-csrf-on-specific-route-via-middleware a0face84-4ebf-4183-93f0-5463b5eb7eae 2018-02-09T15:15:00+05:45 Mr. Sujip Thapa I was lately working with PayPal API on my Laravel project. In the process of coding and testing for Instant Payment Notification (IPN) part, I got an issue with csrf token. The issue was mainly with the POST request to the application via external service, so it threw TokenMismatchException via the VerifyCsrfToken middleware.

One best thing is Laravel ships with CSRF enabled by default for each HTTP request that enters the application, which is made really easy, it handles automatically.

If your application consumes third-party API service, that service may be a webhook to notify about any event and that sends HTTP request to your application. You need to be aware that Laravel filters the request that enters without csrf token, as it monitors all request entering into the application for security reason.

There is a good solution as well, and that also ships with the framework by default. See below how to disable checking csrf token for specific routes in your application, and that fixed my issue as well.

app/Http/Middleware/VerifyCsrfToken.php

    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        '/webhook/paypal/*',
    ];

 

You could specify multiple URLs on that array if you would like to exclude other routes.

Conclusion

Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.

Happy Coding!

]]>
<![CDATA[Eloquent: Query Builder Compare Two Date Columns]]> https://sujipthapa.co/blog/eloquent-query-builder-compare-two-date-columns 84ce2416-f337-4e5c-95bb-471842931352 2018-01-22T06:17:17+05:45 Mr. Sujip Thapa I regularly share my knowledge about Laravel, Web Development tips, and tutorials via my blog.

Now, here in this article, I am going to cover about comparing two different timestamp columns to filter out records from the database.

Recently I was working on filtering some API activity log to push them on notification interface, and the database design was with columns consisted of timestamp datatype as synced_at, optimized_at.

I wanted to filter the activity log based on values in two dates columns synced_at, optimized_at, my logic was to fetch database records if the timestamp in synced_at is greater optimized_at. The API process normally does a remote sync and does some database optimization task for making frontend stuff load faster with a database view.

I want to give you two different ways of comparing the columns.

Activity::where('synced_at', '>', DB::raw('optimized_at'))
    ->latest()
    ->get();

or

Activity::whereRaw('unsynced_at > optimized_at')
    ->latest()
    ->get();

Both of the database queries from the above code snippet give same results.

Conclusion

Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.

Happy Coding!

]]>
<![CDATA[Adding Custom Header with Response in Laravel]]> https://sujipthapa.co/blog/adding-custom-header-with-response-in-laravel 60a07da5-3925-40f1-826a-37a80abdb22f 2018-01-14T17:01:17+05:45 Mr. Sujip Thapa I was working out for the best way to add some custom header to my application for most of the view responses, I wanted the header to be "Cache-Control: no-cache, must-revalidate".

I came up with the best solution with a laravel middleware and decided to share the code snippets with the laravel community people as well. The code snippet will give an idea for you to work on next similar logic and the great solution for setting up the custom header.

Let's get started ;)

Create new middleware with an artisan command, it will be stored in \App\Http\Middleware\NoCache.php

php artisan make:middleware NoCache

<?php

namespace App\Http\Middleware;

use Closure;

/**
 * Class NoCache
 * @package App\Http\Middleware
 */
class NoCache
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        $response->header('Cache-Control', 'no-cache, must-revalidate');

        return $response;
    }
}

Open \App\Http\Kernel.php and register the middleware.

There are different ways you might want to use this middleware, you could either apply it globally on every HTTP request over the application or use only on specific routes.

 

a. Register like below to apply globally on every HTTP request.

    /**
     * @var array
     */
    protected $middleware = [
        ...
        \App\Http\Middleware\NoCache::class,
    ];

b. Another way is, registering with a key and apply it on routes with that key like below.

    /**
     * @var array
     */
    protected $routeMiddleware = [
        ...
        'nocache' => \App\Http\Middleware\NoCache::class,
    ];

Now, applying middleware on routes.

Route::group(['middleware' => 'nocache'], function () {
    Route::get('blog/{slug}', [
        'as' => 'app.blog.view',
        'uses' => 'BlogController@view',
    ]);
});

We are done!

Conclusion

Thanks for reading this post up to the end, if you think this post is worth reading, feel free to share with others, also if you have feedback please post in the comment section below.

Happy Coding!

]]>
<![CDATA[Automatically Posting to Facebook Page via Laravel Notifications]]> https://sujipthapa.co/blog/automatically-posting-to-facebook-page-via-laravel-notifications a25bb0d3-356c-4305-ab86-c583d29a8f8c 2018-01-10T15:40:00+05:45 Mr. Sujip Thapa  A powerful feature that came out with Laravel v5.3 was Laravel Notifications. There are several numbers of notification channels created by the contributors in a github organization called laravel-notification-channels.

The notifications feature is fully available with later versions after it was launched along with the framework version v5.3. After that people started creating different notifications channel that can be used with your laravel application.

Laravel framework ships with support for sending email, SMS(via Nexmo), and Slack. If you wish to store notifications in a database, they may also be stored, so you can display.

I’ve been working on a new update for this website to make auto-posting to the facebook page via API when a new blog post is published on the website. I'm using a package that has already been created by Ahmed Ashraf.

I will give a full overview of available methods and fully functional code snippets of how I implemented it to automatically post them on my Facebook page.

Package Installation

You can easily install this package via composer:

composer require laravel-notification-channels/facebook-poster

If you are below Laravel v5.5, you need to register the service provider class to config/app.php

...
'providers' => [
   ...    
   NotificationChannels\FacebookPoster\FacebookPosterServiceProvider::class,
],
...

Next, you need to go to Facebook to generate some API credentials, to set up this Facebook Poster service.

I recently wrote a detailed blog post to cover the process for generating API credentials, visit this post "Generating Never Expiring Facebook Page Access Token".

After completing the process of generating the API credentials, put them on config/services.php

'facebook_poster' => [
    'app_id' => env('FACEBOOK_APP_ID'),
    'app_secret' => env('FACEBOOK_APP_SECRET'),
    'access_token' => env('FACEBOOK_ACCESS_TOKEN'),
],

Setup Model

In my project setup, I have a Blog model that is responsible for storing and retrieving all blog posts. I'm observing the laravel model events, laravel fires many events in different context. In my case, I am listening to the created event to automate this process.

You can use your own model related to your topic that you already have. Simply use the Notifiable trait to it.

use Illuminate\Notifications\Notifiable;

/**
 * Class Blog
 * @package App
 */
class Blog extends Model
{
    use Notifiable;

Now, create a notification class with a command:

php artisan make:notification ArticlePublished

All you need to do is, adjust via() method, and create a new toFacebookPoster() method, which will be called via FacebookPosterChannel.php class, view the complete class below.

app\Notifications\ArticlePublished.php

<?php

namespace App\Notifications;

use App\Blog;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use NotificationChannels\FacebookPoster\FacebookPosterChannel;
use NotificationChannels\FacebookPoster\FacebookPosterPost;

/**
 * Class ArticlePublished
 * @package App\Notifications
 */
class ArticlePublished extends Notification
{
    use Queueable;

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return [FacebookPosterChannel::class];
    }

    /**
     * @param $blog
     */
    public function toFacebookPoster($blog)
    {
        return with(new FacebookPosterPost($blog->title))
            ->withLink($blog->getLink());
    }
}

Now, most interesting part is making the process automated. I'm creating an event listener to stay ready to fire up a notification when a new blog is stored in the database.

If you want to discover the supported methods, visit the github repository for the package.

You could either fire the notification via the controller after the blog post is created:

/**
 * Create a new blog instance.
 *
 * @param  Request  $request
 * @return Response
 */
public function store(Request $request)
{
    // Validate the request...

    $blog = Blog::create([
        'title' => $request->title,
        'slug' => $request->slug
        ]);

    $blog->notify(new ArticlePublished);
}

Another better way is listening to the model events, the cleaner way I like and the way I follow every single day.

Observing Model

Create a new event subscriber class under, app/Listenerscall it BlogEventSubscriber.php

<?php

namespace App\Listeners;

use App\Notifications\ArticlePublished;

/**
 * Class BlogEventSubscriber
 * @package App\Listeners
 */
class BlogEventSubscriber
{

    /**
     * Handle blog creating events.
     *
     * @param $blog
     */
    public function onCreated($blog)
    {
        $blog->notify(new ArticlePublished());
    }

    /**
     * Register the listeners for the subscriber.
     *
     * @param  Illuminate\Events\Dispatcher  $events
     */
    public function subscribe($events)
    {
        $events->listen(
            'eloquent.created: App\Blog',
            'App\Listeners\BlogEventSubscriber@onCreated'
        );
    }
}

We need to register this class in, app/Providers/EventServiceProvider.php

    /**
     * The subscriber classes to register.
     *
     * @var array
     */
    protected $subscribe = [
        'App\Listeners\BlogEventSubscriber',
    ];

Development Issue

I tried to send the localhost link along with Facebook post but it throws an exception while sending localhost URL, so be aware of that.

    public function toFacebookPoster($blog)
    {
        return with(new FacebookPosterPost($blog->title))
            ->withLink($blog->getLink());
    }

We are done!

Conclusion

Thanks for reading this article up to the end, if you have any feedback regarding the post please feel free to leave your comment below. The code snippets are fully tested, and the full source is available in the github repository.

]]>
<![CDATA[Generating Never Expiring Facebook Page Access Token]]> https://sujipthapa.co/blog/generating-never-expiring-facebook-page-access-token 09805c44-b467-4c39-9d91-4ce855f34d68 2018-01-09T15:17:17+05:45 Mr. Sujip Thapa I’ve been working on the new feature for my own blog to auto-posting the posts from the website to the facebook page via Facebook Graph API. My logic was just to make the process automated when I publish new blog post on my website end.

Below, I will cover the processes that are needed on facebook end to collect the required credentials to make the API connectivity successful.

The main credentials needed are, app_id, app_secrect, access_token for my use case.
We can easily get the app_id, app_secrect after creating the app, but to get the long-lived page access token there are some steps to follow.

Facebook gives short-lived, long-lived access tokens. I prefer to generate long-lived access token just to get rid of the expiry date and monitoring effort required for it.

I will cover all the steps we need to follow to get the never expiring page access token.

Steps

1. Sign up for facebook developers channel.

2. Create a Facebook App or use an existing app if you already have one.

null

3. Copy, App ID, App Secret

4. Now, navigate to Facebook Graph API Explorer, to generate short-lived access token.

a. Firstly, Select an app from Application drop-down.

b. Again, in the next drop-down select "Get user access token".

c. Once you click on “Get user access token in the drop down” you will see a pop-up window like below. There you will be able to select the permission(scopes) for the user access token.

d. Here for my use case I have only selected “publish pages” and “manager pages” permissions which are needed to create never expiring page access token. If would like to understand more about the scopes visit permissions doc.

Generate user access token by clicking on "Get Access Token" button, this will create short-lived user access token.

5. Now we will need to create a long-lived user access token.

Now, navigate to Access Token Tool this page, you will see short-lived user access token, app access token for all the apps you have under your account.

Press debug option at right side for the user access token for the current app we are trying to create a long-lived access token. This will again take you to Access Token Debugger where you will see the full information for the short-lived user access token.

null

Short-lived user access token means that will expire after an hour. So to extend the expiry date, we need to go to the bottom, there is an option to generate long-lived(2 months) user access token. Click on “Extend Access Token” and you will get the long-lived access token.

6. Lastly, creating never expiring Access Token.

Again, go to Graph API Explorer and paste the recently created long-lived user access token in the Access Token field.

Now, we need to change the API endpoint to access “/me/accounts” and click on "Submit" button. This process will show the result with all pages information including page access token i.e never expiring token.

7. Finally, we are at the verification step to make sure the page access token is never expiring.

null

Hurray!

Last Updated - May 24, 2018 : The process I have explained above also works for new version v3.0 of Facebook Graph API

Conclusion

Thanks for reading this article up to the end, if you have any feedback regarding the post please feel free to leave your comment below.

Happy Coding!

]]>
<![CDATA[Speed up your Composer Install or Update Process]]> https://sujipthapa.co/blog/speed-up-your-composer-install-or-update-process 083df214-0532-4eee-8038-18855ea79ab8 2018-01-05T21:31:18+05:45 Mr. Sujip Thapa We all know that, whenever we go for running composer update, we, of course, need to wait until the dependencies are loaded and download. It usually takes up to 5-10 minutes to complete the full process of pulling down from the package hosted server. This waiting time depends on the performance of your machine.

In this article, I am going to give an overview of how to speed up your composer update process. I recently used and experimented the process, so I wanted to share with everyone who wants to utilize this speedy way.

 

There are only two methods, I know and I did experiment with.

Disabling the Xdebug mode

Installing a third-party library prestissimo created by @hirak

If you want to check your xdebug mode, when running composer you can follow this StackOverflow link.

Another method is using a global composer plugin prestissimo, which is blazingly faster, this plugin helps to installs dependencies in parallel mode.

Follow the below command to install the composer plugin globally on your machine.

Install

composer global require hirak/prestissimo

Uninstall

composer global remove hirak/prestissimo

After installing the composer plugin globally you are done, next is to go to your terminal and hit composer update and test the speed.

I am personally using this composer plugin to make my composer process faster. I highly recommend you to try it. I haven't run into any problems using it at all. If you found any issues let people know about it, so feel free to leave your comment below.

 

In my case the composer update used to take around 10 minutes to finish, now after utilizing this package, it turned into 1 min and less, which is blazingly faster for me, a lot of time-saving while waiting for a package to install and move to code works.

Thanks for reading this article, If you like feel free to share with other people in your circle to let them know about the above.

Enjoy !

]]>
<![CDATA[Redirect to Previous URL After Logging in, Laravel v5.5]]> https://sujipthapa.co/blog/redirect-to-previous-url-after-logging-in-laravel-v55 7c402d62-3e69-48af-a7f7-7e61d721a946 2018-01-01T14:52:15+05:45 Mr. Sujip Thapa An end-user visit the website tries to view several products, services by navigating to different pages. If he finally wants to submit an order. Now the website, of course, takes him to login or register page to provide his personal information for shipping or billing purpose. After logging in, he might want the website to take him back again to the previous page where he was viewing the product information.

I was talking about the user experience while browsing the pages, the behavior for the end-user might differ to each other, but the website owner should make a policy for the better user experience. Especially, the e-commerce websites should consider this types of UI for their end-user.

 

Let's say you are running an e-commerce website, where people can view products, create orders. Let's assume you want the website easily navigates back the user to the previous page after logging into the website.

Let's start building the feature for redirecting the user back to the previous page.

Let's add a section in the login file.

resources/views/auth/login.blade.php

<div class="form-group">
    @if (Request::has('previous'))
        <input type="hidden" name="previous" value="{{ Request::get('previous') }}">
    @else
        <input type="hidden" name="previous" value="{{ URL::previous() }}">
    @endif
</div>

View the login.blade.php file on github for the full updated code.

Additionally, you might want to add a login link to your website pages like:

<a href="{{ route('login') . '?previous=' . Request::fullUrl() }}">
    Login
</a>

When a user clicks the login link, the page redirects to the login page, it then sets the previous URL to input that will be sent to login POST request.

    /**
     * @return string
     */
    public function redirectTo()
    {
        if ($this->request->has('previous')) {
            $this->redirectTo = $this->request->get('previous');
        }

        return $this->redirectTo ?? '/home';
    }

The LoginController.php will handle the previous input value to check if there is the previous link to redirect back. A method redirectTo() created in LoginController.php is responsible to determine the next redirect path after the user is successfully authenticated.

 

If you're not familiar with Laravel's auth system and confused that only defining the new redirectTo() method helps in redirecting to our intended URL, then you can have a look at, AuthenticatesUsers.php, RedirectsUsers.php. We cannot touch the core files shipped with Laravel framework, but there is a way we can override those methods to change the behavior as per our requirement.

Conclusion

Thanks for reading this article up to the end. If you have any feedback or the article was really useful to you please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[Integrating Medium Style Image Zoom]]> https://sujipthapa.co/blog/integrating-medium-style-image-zoom 37a2b5f2-588f-4631-9cf6-7988acaf1eec 2017-12-31T16:16:17+05:45 Mr. Sujip Thapa I'm sure you already know about Medium, a widely used and popular publishing platform to share, articles, important stores over the internet.

Medium – Read, write and share stories that matter.

Medium is an online publishing platform developed by Evan Williams, and launched in August 2012. It is owned by A Medium Corporation

Here I'm going to show how I implemented a medium style clean image zoom-in, zoom-out feature for your website. If you would like to see the medium style image zoom-in, out feature on my blog that I implemented recently with the new design published last week, visit some articles to try out the feature. for example: here is a post with few images, have a look at this blog post.

Before diving into the code, I would like to say big thanks to the package creator @francoischalifour, who built this awesome package as open source.

 

The package is available on the npm registry, with no dependencies.

Features

  • Responsive — scale on mobile and desktop.
  • Performant and lightweight — should be able to reach 60 fps.
  • High definition support — load the HD version of your image on zoom.
  • Image selection — apply the zoom to a selection of images.
  • Mouse, keyboard, and gesture friendly — click anywhere, press a key or scroll away to dismiss the zoom.
  • Event handling — trigger events when the zoom enters a new state.
  • Customization — set your own margin, background, and scroll offset.
  • Custom templates — extend the default look to match your UI.
  • Link support — open the link to the image in a new tab when a meta key is held (⌘ or Ctrl)
  • Image opener — when no link, open the image source in a new tab when a meta key is held (⌘ or Ctrl)

Installation

npm install --save medium-zoom

# or

yarn add medium-zoom

Or, If you want to use the CDN version:

<script src="https://unpkg.com/medium-zoom@0/dist/medium-zoom.min.js"></script>

Usage

Import the script:

<script src="node_modules/medium-zoom/dist/medium-zoom.min.js"></script>

Or, using the module syntax or imports:

const mediumZoom = require('medium-zoom')
// or
import mediumZoom from 'medium-zoom'

You don't need to worry about the CSS styling imports.

Integration

mediumZoom(<selector>, <options>)

In my case, I wanted to use medium style zoom, in-out feature for every image I add to the blog post. i.e It is only applied to the post detail page, where the user wants to view the image in zoom, in-out mode. I wanted my readers to enjoy the view of images without having to open it on a new tab to understand it clearly.

 
mediumZoom('.post__container img');

By default, the zoom is applied to all scaled images (with HTML or CSS properties). You can specify the zoomable images with a CSS selector and add options.

Additionally, you can pass an HTML Element, a NodeList, an HTMLCollection or an array of images to the plugin.

// CSS selector
mediumZoom('#cover')

// HTML Element
mediumZoom(document.getElementById('cover'))

// NodeList
mediumZoom(document.querySelectorAll('[data-action="zoom"]'))

// HTMLCollection
mediumZoom(document.images)

// Array
const imagesToZoom = [
  document.querySelector('#cover'),
  ...document.querySelectorAll('[data-action="zoom"]')
]

mediumZoom(imagesToZoom)

API

Options can be passed via a JavaScript object through the mediumZoom call.

Open up the github repo and go through the API options available if you wish to add the customized feature for your web application.

Go through the examples section if you wish to understand the complete possible features provided by the package.

 

Conclusion

Thanks for reading this article up to the end. If you have any feedback or the article was really useful to you please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[Authorizing New Device based on IP Address, with Laravel Middleware]]> https://sujipthapa.co/blog/authorizing-new-device-based-on-ip-address-with-laravel-middleware 6c757df3-5cad-4866-9f51-3eef2813fa3b 2017-12-27T16:17:16+05:45 Mr. Sujip Thapa I recently saw few of the e-commerce & payment gateway sites using device authorization system based on IP address, browser etc. I was also working on a same for my client's web app recently so wanted to share a detailed post with the community people.

In this blog post, I will go in detail to cover following stuff.

  • Allow the user to enter login credentials, if the login credentials are valid, also verify if the user's device is authorized with the current IP address assigned to the user's device.
  • If the user's device is not authorized to access the protected pages, like the dashboard, the application will send an email to the recently logged in user's email to ask for authorizing the device before proceeding.
  • After sending the email, the page will redirect to wait for email authorization, and that will keep refreshing on certain time interval to check if the user is authorized, so it can redirect to the dashboard.
  • If the user is not active and did not authorize the device within next 15 min after email is sent, it will log out the user as a reason for a timeout with a certain message.

 

I will be using Laravel v5.5 to build this feature. Also, for user's device information, IP to location and to generate a unique token for every new request I will be using my own two PHP packages published on packagist as open source, and browser detection library by @cbschuld. So make sure you have a composer, PHP 7.0 >= installed in your environment.

Package Installation

You can install those packages via composer.

composer require sudiptpa/guid

composer require sudiptpa/ipstack


Now let's start with creating the foundation of the feature to implement with Laravel project.

app/routes/web.php

Route::group(['middleware' => ['authorize', 'auth']], function () {
    Route::get('/dashboard', [
        'name' => 'Dashboard',
        'as' => 'dashboard',
        'uses' => 'HomeController@dashboard',
    ]);
});

Route::group(['middleware' => ['auth']], function () {
    Route::get('/authorize/{token}', [
        'name' => 'Authorize Login',
        'as' => 'authorize.device',
        'uses' => 'Auth\AuthorizeController@verify',
    ]);

    Route::post('/authorize/resend', [
        'name' => 'Authorize',
        'as' => 'authorize.resend',
        'uses' => 'Auth\AuthorizeController@resend',
    ]);
});


The first grouped route shows, that the user cannot access the dashboard without logging in and, needs authorization. I will show the authorization middleware very soon below.

Similarly, the second grouped routes, show that only authenticated users can verify the device with associated IP address.

app/database/migrations

 

Now let's create a migration table to store the authorizes for all users.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateAuthorizesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('authorizes', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->nullable();
            $table->boolean('authorized')->nullable();
            $table->string('token')->nullable();
            $table->string('ip_address')->nullable();
            $table->string('browser')->nullable();
            $table->string('os')->nullable();
            $table->string('location')->nullable();
            $table->tinyInteger('attempt')->default(0)->nullable();
            $table->timestamp('authorized_at')->nullable();
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('authorizes');
    }
}


app/Authorize.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Request;

/**
 * Class Authorize
 * @package App
 */
class Authorize extends Model
{
    /**
     * @var string
     */
    protected $table = 'authorizes';

    /**
     * @var boolean
     */
    public $timestamps = true;

    /**
     * @var array
     */
    protected $dates = ['authorized_at', 'deleted_at'];

    /**
     * @var string
     */
    protected $fillable = [
        'user_id', 'authorized', 'token', 'ip_address', 'browser', 'os', 'location', 'attempt', 'authorized_at',
    ];

    /**
     * @param $query
     * @return mixed
     */
    public function scopeCurrentUser($query)
    {
        return $query->where('user_id', Auth::id());
    }

    /**
     * @param $date
     */
    public function setAuthorizedAtAttribute($date)
    {
        $this->attributes['authorized_at'] = Carbon::parse($date);
    }

    /**
     * @return mixed
     */
    public static function active()
    {
        return with(new self)
            ->where('ip_address', Request::ip())
            ->where('authorized', true)
            ->where('authorized_at', '<', Carbon::tomorrow())
            ->first();
    }

    /**
     * @return mixed
     */
    public function resetAttempt()
    {
        $this->update(['attempt' => 0]);

        return $this;
    }

    /**
     * @return mixed
     */
    public function noAttempt()
    {
        return $this->attempt < 1;
    }

    /**
     * @param $token
     */
    public static function validateToken($token = null)
    {
        $query = self::where([
            'token' => $token,
        ])->first();

        if (sizeof($query)) {
            $query->update([
                'authorized' => true,
                'authorized_at' => now(),
            ]);

            return self::active();
        }
    }

    /**
     * @return mixed
     */
    public static function make()
    {
        return self::firstOrCreate([
            'ip_address' => Request::ip(),
            'authorized' => false,
            'user_id' => Auth::id(),
        ]);
    }

    /**
     * @return mixed
     */
    public static function inactive()
    {
        $query = self::active();

        return $query ? null : true;
    }
}


In this model I have written few methods that actually work on authorization, you will see its usage below with middleware, controller.

In this blog post, I am only covering only the dashboard, login routes to be protected from the authorize middleware. I only forced the LoginController.php to apply on login routes only. Make sure you apply this middleware to other routes to be protected from unauthorized access to your application.

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/dashboard';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('authorize')->only('login');
        $this->middleware('guest')->except('logout');
    }


app/Http/Middleware/AuthorizeDevice.php

<?php

namespace App\Http\Middleware;

use App\Authorize;
use App\Mail\AuthorizeDevice as AuthorizeMail;
use Closure;
use Illuminate\Support\Facades\Mail;

/**
 * Class AuthorizeDevice
 * @package App\Http\Middleware
 */
class AuthorizeDevice
{
    /**
     * @var \App\Authorize
     */
    private $authorize;

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (Authorize::inactive() && auth()->check()) {
            $this->authorize = Authorize::make();

            if ($this->authorize->noAttempt()) {
                Mail::to($request->user())
                    ->send(new AuthorizeMail($this->authorize));

                $this->authorize->increment('attempt');
            }

            if ($this->timeout()) {
                auth()->guard()->logout();

                $request->session()->invalidate();

                return redirect('/')->with([
                    'status' => 'You are logged out of system, please follow the link we sent before 15 minutes to authorize your device, the link will be valid with same IP for 24hrs.',
                ]);
            }

            return response()->view('auth.authorize');
        }

        return $next($request);
    }

    /**
     * Determines if the authorize attempt is timed out.
     *
     * @return bool
     */
    private function timeout()
    {
        $waiting = $this->authorize
            ->created_at
            ->addMinutes(15);

        if (now() >= $waiting) {
            return true;
        }

        return false;
    }
}


Now register the middleware within app/Http/Kernel.php

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        ...
        'authorize' => \App\Http\Middleware\AuthorizeDevice::class,
    ];


The most interesting part of this blog is the above middleware class that handles the incoming request into an application that enters the actual routes which are protected before the user's device is authorized.

Open up the Authorize.php model from GitHub to understand the full methods about how it is working.

The middleware is before middleware, follow the laravel official doc if you don't know about it.

Actually, it works with the authenticated user's when the device is not authorized, so it protects the user's trying to enter the application without authorization.

The middleware first, checks, if there is an authorization, exists in the database or creates a new with the current IP address for currently logged in user.

It sends the email requesting authorization to access the application, see below for the simple preview I made while writing this blog post.

The middleware is also handling the session timeout for the authorization to be made within next 15 min after the email has been sent, otherwise, it will log out the user automatically.

If you wish to manage the separate middleware for the timeout feature you could use another class, and register it accordingly like we did above.

Alright, now let's move to email sending code.

app/Mail/AuthorizeDevice.php

<?php

namespace App\Mail;

use App\Browser;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Sujip\Ipstack\Ipstack;

/**
 * Class AuthorizeDevice
 * @package App\Mail
 */
class AuthorizeDevice extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * @var mixed
     */
    protected $authorize;

    /**
     * Create a new message instance.
     *
     * @param $authorize
     *  @return void
     */
    public function __construct($authorize)
    {
        $this->authorize = $authorize;
        $this->browser = new Browser;
    }

    /**
     * @return mixed
     */
    public function setBrowser()
    {
        $this->authorize->browser = $this->browser->getBrowser();

        return $this;
    }

    /**
     * @return mixed
     */
    public function setToken()
    {
        $this->authorize->token = guid();

        return $this;
    }

    /**
     * @return mixed
     */
    public function setLocation()
    {
        $location = with(new Ipstack(
            $this->authorize->ip_address
        ))->formatted();

        $this->authorize->location = $location;

        return $this;
    }

    /**
     * @return mixed
     */
    public function setPlatform()
    {
        $this->authorize->os = $this->browser->getPlatform();

        return $this;
    }

    public function saveAuthorize()
    {
        $this->authorize->save();
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        $this
            ->setBrowser()
            ->setToken()
            ->setLocation()
            ->setPlatform()
            ->saveAuthorize();

        return $this
            ->view('emails.auth.authorize')
            ->with(['authorize' => $this->authorize]);
    }
}


The mail sending class prepares data like location, IP, token, browser, platform(os) and email sending view. In order to collect the user's current device information, I am using a third-party class written by @cbschuld as open source code in GitHub. I would like to say thanks for the nice package to him.

Also, to generate the token, I am using a GUID generator package that I wrote recently for my own use case and published as opensource as well. The guid() helper function will return global unique identifier every time a user requires a token to authorize the device.

The coolest feature in the Laravel 5.5 Illuminate\Mail\Mailable is mail preview to test the email view before sending it. I loved it. :)

See example below how I tested myself.

Route::get('/mailable', function () {
    $authorize = App\Authorize::find(1);

    return new App\Mail\AuthorizeDevice($authorize);
});


After sending the email requesting you to authorize the device, the preview I built was like below, you are free to apply CSS as per your use case.

null

The AuthorizationController.php was written to handle, resend authorize email, and validate the token from the email sent to the current user.

<?php

namespace App\Http\Controllers\Auth;

use App\Authorize;
use App\Http\Controllers\Controller;
use App\Mail\AuthorizeDevice;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Redirect;

/**
 * Class AuthorizeController
 * @package App\Http\Controllers\Auth
 */
class AuthorizeController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     *  Validate the token for the Authorization.
     *
     * @param $token
     * @return \Illuminate\Http\Response
     */
    public function verify($token = null)
    {
        if (Authorize::validateToken($token)) {
            return Redirect::route('dashboard')->with([
                'status' => 'Awesome ! you are now authorized !',
            ]);
        }

        return Redirect::route('login')->with([
            'error' => "Oh snap ! the authorization token is either expired or invalid. Click on Email didn't arraive ? again",
        ]);
    }

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function resend(Request $request)
    {
        if (Authorize::inactive() && auth()->check()) {
            $authorize = Authorize::make()
                ->resetAttempt();

            Mail::to($request->user())
                ->send(new AuthorizeDevice($authorize));

            $authorize->increment('attempt');

            return view('auth.authorize');
        }
    }
}


View the auth/authorize.blade.php from github to know how it was like.

 

View the complete code pushed to github while I was writing this blog post.

Conclusion

Thanks for reading this article up to the end. If you have any feedback or the article was really useful to you please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[A Simple IP to Geo Location solution for PHP]]> https://sujipthapa.co/blog/a-simple-ip-to-geo-location-solution-for-php f3b854bd-24fb-4f7f-a30c-917a4bcff5ed 2017-12-25T12:23:10+05:45 Mr. Sujip Thapa The freegeoip.net service is discontinued, so please refer to the new package to get the same service sudiptpa/ipstack

I've abandoned the legacy package sudiptpa/geoip as the service provider discontinued on July 1st, 2018.

I was working on my own use case to find out IP to Geo Location and finally found a free open source service from http://freegeoip.net. It is a community funded project. The service includes GeoLite2 data created by MaxMind

freegeoip.net provides a public HTTP API for software developers to search the geolocation of IP addresses. It uses a database of IP addresses that are associated with cities along with other relevant information like time zone, latitude, and longitude.

You're allowed up to 15,000 queries per hour by default. Once this limit is reached, all of your requests will result in HTTP 403, forbidden, until your quota is cleared.

The freegeoip web server is free and open source so if the public service limit is a problem for you, download it and run your own instance.

API support.

The HTTP API takes GET requests in XML, JSON, CSV format.

http://freegeoip.net/{format}/{IP_or_hostname}

While making an API call, if no IP or hostname is provided, then your own IP is looked up.

 

Package Implementation

To make this API call simple and organized I decided to create a package for my own use case as well as for the community people to use it as open source. It is available to view it on Github.

Installation

You can install the package via composer.

composer require sudiptpa/geoip


Usage

This package only supports json format for now.

Here are a few examples on how you can use the package:

$geo = new Sujip\GeoIp\GeoIp($ip);

$geo->country();

$geo->city();

$geo->region();

$geo->formatted(); // Jawalakhel, Central Region, Nepal

Also, have a look at the source code of Sujip\GeoIp\GeoIp to discover the methods you can use via GitHub.

Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[A Complete Guide to Integrate Facebook Like and Share]]> https://sujipthapa.co/blog/a-complete-guide-to-integrate-facebook-like-and-share ec3eb66a-4c08-460f-8b0f-c26ca64f4112 2017-12-22T16:10:15+05:45 Mr. Sujip Thapa Nowadays social media platform has grown a lot more and really helpful in marketing, sharing information, communication etc. So here in this post, I am going into detail about how to integrate the Facebook social plugin, with real-time Like, Share button on your products links or blog posts on your web application.

I will be using Laravel framework to demonstrate this integration. So before starting, I will assume you have laravel framework setup, facebook account ready to use in facebook developers mode.

 

At the end of the post, you will get the below output.

Now let's start with creating the basic foundation to show the blog posts and view it.

app/routes/web.php

Route::group(['prefix' => 'blog'], function () {
    Route::get('/', [
        'as' => 'app.blog',
        'uses' => 'BlogController@index',
    ]);
    Route::get('/{slug}', [
        'as' => 'app.blog.view',
        'uses' => 'BlogController@view',
    ]);
});

app/database/migrations

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateBlogsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('blogs', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->string('slug');
            $table->string('excerpt');
            $table->longText('content')->nullable();
            $table->timestamp('published_at')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('blogs');
    }
}

app/Blog.php

<?php

namespace App;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;

/**
 * Class Blog
 * @package App
 */
class Blog extends Model
{
    /**
     * @var string
     */
    protected $table = 'blogs';

    /**
     * @var array
     */
    protected $dates = ['published_at'];

    /**
     * @var array
     */
    protected $fillable = ['title', 'slug', 'excerpt', 'content', 'published_at'];

    /**
     * @param $query
     * @return mixed
     */
    public function scopePublished($query)
    {
        return $query->where('published_at', '<=', Carbon::now());
    }

    /**
     * @param $date
     */
    public function setPublishedAtAttribute($date)
    {
        $this->attributes['published_at'] = Carbon::parse($date);
    }
}

app/Http/Controllers/BlogController.php

<?php

namespace App\Http\Controllers;

use App\Blog;
use Illuminate\Http\Request;

/**
 * Class BlogController
 * @package App\Http\Controllers
 */
class BlogController extends Controller
{
    /**
     * @var int
     */
    protected $paginate = 10;

    /**
     * @param Request $request
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    public function index()
    {
        $blogs = Blog::published()->paginate(
            $this->paginate
        );

        return view('blog.index', [
            'blogs' => $blogs,
            'title' => 'Blogs',
            'open_graph' => [
                'title' => 'Blogs',
                'image' => asset('assets/logo.jpeg'),
                'url' => $this->request->url(),
                'description' => 'A blog website to share tutorials and tips !',
                'keywords' => 'A Laravel Blog, Tips, Tutorials',
            ],
        ]);
    }

    /**
     * @param $slug
     */
    public function view($slug)
    {
        $blog = Blog::published()
            ->where('slug', $slug)
            ->firstOrFail();

        return view('blog.view', [
            'blog' => $blog,
            'open_graph' => [
                'title' => $blog->title,
                'image' => asset('assets/preview.jpeg'),
                'url' => $this->request->url(),
                'description' => $blog->excerpt,
            ],
        ]);
    }
}

Now, I will assume you have following code in your layout blade in your view folder. See how I have created my layout file here for this tutorial.

<!DOCTYPE html>
<html lang="en">
<head>
    ....
    ....
    ....

    @yield('head')

</head>
<body>

Open up the file form Github repository index.blade.php

Similarly, open up view.blade.php

 

The above integration will show the following output.

Blogs

null

 

Blog Preview

null

Now, let's navigate to facebook developers doc.

Scroll down to - Like Button Configurator

You are free to configure the URL to Like, Width, Layout, Action Type, Button Size, Show Friends's Faces, Include Share Button options as per your wish.

After completing the above configuration click on Get Code button. It will show a pop up window like below. Don't forget to read the information shown in the pop up window carefully. The share feature requires open graph meta tags on heads section of your html page, so have a look at the code to know how I implemented it.

There are two different way of implementing the facebook real-time like, share buttons.

 

Javascript SDK

Step 1: Include the JavaScript SDK on your page once, ideally right after the opening body tag.

<div id="fb-root"></div>
<script>(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) return;
  js = d.createElement(s); js.id = id;
  js.src = 'https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.11&appId=180539309049634';
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>

Don't forget to replace the appId=180539309049634 with your own app. Select your facebook app before generating the code. If you don't have any app for your site, create one from facebook developers page under My App.

Step 2: Place this code wherever you want the plugin to appear on your page.

<div class="fb-like" data-href="{{ route('app.blog.view', ['slug' => $post->slug]) }}" data-layout="standard" data-action="like" data-size="small" data-show-faces="true" data-share="true"></div>

IFrame

<iframe src="https://www.facebook.com/plugins/like.php?href={{ route('app.blog.view', ['slug' => $post->slug]) }}&width=51&layout=box_count&action=like&size=small&show_faces=true&share=true&height=65&appId" width="51" height="65" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true"></iframe>

Place this code wherever you want the plugin to appear on your page.

Javascript Code Preview

Now, look at the index.blade.php, view.blade.php on my Github repo about how I implemented the code to show the facebook real-time like, share buttons.

 

View the complete code pushed to github while I was writing this tutorial.

Finally! now we have a complete guide setup for integrating facebook, real-time like, share buttons.

Conclusion

Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[Create unique GUID with PHP]]> https://sujipthapa.co/blog/create-unique-guid-with-php 3520273c-c5e9-4df9-9827-1212d60c75ee 2017-10-06T06:02:15+05:45 Mr. Sujip Thapa In this blog post, I am going to write about my new PHP package for creating globally unique identifiers (GUID).

Begin with installation via Composer:

composer require sudiptpa/guid

I was inspired mainly to create this package while I was searching for GUID information in the php.net official PHP website. I found a good solution by a community member Dave Pearsonview source code here.

I was looking a solution for Laravel, so I created this package as reusable for everyone as an opensource package.

 

 Usage

In order to consume this package from Laravel application, register the package service provider within your config/app.phpfile.

'providers' => [
    Sujip\Guid\GuidServiceProvider::class,
]

'aliases' => [
   'Guid' => 'Sujip\Guid\Guid'
]

If your Laravel application is running with v5.5, this package has been configured for auto-discovery, Laravel will automatically register its service providers and facades when it is installed, creating a convenient installation experience for you.

If you are a Laravel Developer:

Make use of Laravel Facade:

echo "GUID: " . Guid::create(); //example output : 2b23924f-0eaa-4133-848e-7ce1edeca8c9

 or use a helper function guid()

echo "GUID: " . guid(); // example output: 2b23924f-0eaa-4133-848e-7ce1edeca8c9

 If you want to use this package outside of the framework.

use Sujip\Guid\Guid;


$guid = new Guid;

$guid = $guid->create();

Output

//Example: 2b23924f-0eaa-4133-848e-7ce1edeca8c9

Conclusion

This package is framework agnostic, which means it can be used in any type of PHP projects. 

Thanks for reading up to the end. If you have any feedback regarding this blog post feel free to leave your comment below. Also don't forget to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[Set your Preferred Domain on Laravel Application]]> https://sujipthapa.co/blog/set-your-preferred-domain-on-laravel-application d976c361-7a03-47ae-b2d9-b696a3bbb916 2017-09-09T08:45:00+05:45 Mr. Sujip Thapa In this blog post, I would like to show how to set preferred domain from your application context.

Before diving into the code, let's understand why the preferred domain is much important for SEO, web pages indexing on Search Engine.

Follow the link to read in detail from Google support article.

The preferred domain is the one that you would like used to index your website's pages (it is sometimes referred to as the canonical domain). Links may point to your site using both the www and non-www versions of the URL (for instance, http://www.example.com and http://example.com). The preferred domain is the version that you want to be used for your site in the search results.

 

Here, in this guide, I'm going to implement non-www preferred domain for every incoming HTTP request to the application. I have created a custom Laravel Middleware to set the preferred domain.

People usually do this with server configuration, (eg: with .htaccess for apache), but you can also force it from application context. I have used this mechanism with several Laravel applications I have built.

Let's start with creating a middleware with an artisan command.

php artisan make:middleware PreferredDomain

Open up your app/Http/Middleware/PreferredDomin.php and paste code below.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Redirect;

/**
 * Class PreferredDomain
 * @package App\Http\Middleware
 */
class PreferredDomain
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (starts_with($request->header('host'), 'www.')) {
            $host = str_replace('www.', '', $request->header('host'));
            $request->headers->set('host', $host);

            return Redirect::to($request->fullUrl(), 301);
        }

        return $next($request);
    }
}

We want to run this middle over every HTTP request that comes to our application, simply we need to register the middleware class in the $middleware property in app/Http/Kernel.php class.

    protected $middleware = [
        ...

        \App\Http\Middleware\PreferredDomain::class,
    ];

Note: If you're still using v4.2 version of Laravel don't worry I have solution for you too as well ;)

 

Open up your app/filters.php

App::before(function ($request) {
    if (starts_with($request->header('host'), 'www.')) {
        $host = str_replace('www.', '', $request->header('host'));
        $request->headers->set('host', $host);

        return Redirect::to($request->fullUrl(), 301);
    }
});


Conclusion

This tutorial heavily depends on Laravel framework, I primarily use Laravel to build my projects.

Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.

Last Updated: 21st Jan, 2018

Happy Coding!

]]>
<![CDATA[Laravel v5.5: Login, Register with Username or Email]]> https://sujipthapa.co/blog/laravel-v55-login-register-with-username-or-email dbf59aa9-4880-4a63-b33c-271196ec7ad5 2017-09-09T07:03:16+05:45 Mr. Sujip Thapa In this blog post, I would like to share some ideas on how to customize your Laravel v5.5 authentication to support username or email based login. Laravel v5.5 is a second LTS version, that @laravelphp announced on Twitter on 20 Feb 2017. Just simply like the previous LTS version, this will also include 2 years of bug fixes and 3 years of security updates.

Laravel v5.5 release announcement.

If you are new to my blog, view my previous post about Laravel v5.4 authentication with username or email.

 

In my previous blog post, people were requesting to cover register, login. So, here I am going to cover both process with new version v5.5 just released at the end of August 2017.

Let's start the tutorial :)

Before starting to look at the code you should be ready with running v5.5 or fresh installation setup on your machine.

If you need a guide for the installation process see the release note and follow the official guide.

Let's begin with creating authentication scaffolding, migrations for User model.

Generate authentication scaffolding with an artisan command.

php artisan make:auth

As we are going to add username field on the database. 

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('username')->nullable()->unique();
            $table->string('email')->unique();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

Now simply run migration and go ahead.

php artisan migrate

After running this command your database table is now ready with username field support.

 

Now, open up your User model, add the username field to the $fillable array.

    protected $fillable = [
        'name', 'email', 'password', 'username',
    ];

Open up your RegisterController.php, I have added a validation rule for the username in validator() method. Similarly, I have added username field to create() method as well.

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Validator;

class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
     */

    use RegistersUsers;

    /**
     * Where to redirect users after registration.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'username' => 'required|string|max:20|unique:users',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'username' => $data['username'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }
}

Also, I have changed a little bit on resources/views/auth/register.blade.php to support username, email. So, simply add username field on it.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Register</div>
                <div class="panel-body">
                    <form class="form-horizontal" method="POST" action="{{ route('register') }}">
                        {{ csrf_field() }}
                        <div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
                            <label for="name" class="col-md-4 control-label">Name</label>
                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control" name="name" value="{{ old('name') }}" required autofocus>
                                @if ($errors->has('name'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('name') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>
                        <div class="form-group{{ $errors->has('username') ? ' has-error' : '' }}">
                            <label for="username" class="col-md-4 control-label">Username</label>
                            <div class="col-md-6">
                                <input id="username" type="text" class="form-control" name="username" value="{{ old('username') }}" required>
                                @if ($errors->has('username'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('username') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
                            <label for="email" class="col-md-4 control-label">E-Mail Address</label>
                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required>
                                @if ($errors->has('email'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>
                        <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
                            <label for="password" class="col-md-4 control-label">Password</label>
                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control" name="password" required>
                                @if ($errors->has('password'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="password-confirm" class="col-md-4 control-label">Confirm Password</label>
                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <button type="submit" class="btn btn-primary">
                                    Register
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

 Laravel v5.5 Regist, Login Form

You're done with the registration process. Now let's look at the login process.

Open up Illuminate\Foundation\Auth\AuthenticatesUsers it has several methods to handle authentication process. In our customization we only need to override one method credentials() to our App\Http\Controllers\Auth\LoginController.php

 

Also, don't forget to add an import use Illuminate\Http\Request; 

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
     */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(Request $request)
    {
        $field = filter_var($request->get($this->username()), FILTER_VALIDATE_EMAIL)
            ? $this->username()
            : 'username';

        return [
            $field => $request->get($this->username()),
            'password' => $request->password,
        ];
    }
}

 You will require changing the login form to support username, email field, simply open up resources/views/auth/login.blade.php and just change input type to be text from email, just leave other as it is.

<input id="email" type="text" class="form-control" name="email" value="{{ old('email') }}" required autofocus>

 Alright ! now we have a full functionality to support login, register with username, email with Laravel v5.5.

Conclusion

This tutorial heavily depends on Laravel 5.5, so if you are going to implement this on another version of framework please have a look at the traits used properly to override the credentials array. If you need guide about v5.4 view the previous post here.

Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[A Guide to Integrate Omnipay PayPal with Laravel]]> https://sujipthapa.co/blog/a-guide-to-integrate-omnipay-paypal-with-laravel d27ac1da-999f-4212-9539-e4bd04d73e71 2017-07-01T05:59:14+05:45 Mr. Sujip Thapa In this blog post, I would like to share some idea about best way to integrate paypal express checkout using OmniPay with PHP. Omnipay is a framework agnostic, payment processing library for PHP 5.3 and higher.

Omnipay libraries are stable, consistent, and fully united tested by the developers and contributors around the globe. I would like to thank them for their great effort on these awesome open source packages.

Omnipay libraries are really easy to understand and integrate with any framework or non-framework based projects.

 

Also, have a look at their Github repository to discover the supported gateways you want to integrate with your project.

You can simply pull the packages which are hosted freely on packagist by using composer.

In this blog post, I will go step by steps to integrate the popular library for PayPal express checkout with omnipay/omnipay-paypal.

I am going to show the steps with laravel framework v5.4.

Before getting started with the guidelines you will need the following stuff ready for your machine.

  • Composer installed.
  • PHP >= 5.6.4 for Laravel, follow official doc.
  • Web server, the database on your machine. eg: Apache, MySQL

After successfully installing the laravel on your machine.

Important Note

If you are using Symfony 3 or Symfony 3 components, or laravel v5.* which uses Symfony 3 components please note that Omnipay 2.* version still uses guzzle 3.*, which depends on symfony/event-dispatcher 2.*. It conflicts with Symfony 3 in the regular installation. For this fix, we may need to wait until Omnipay 3.* release, which is still under development.

For the alternative fix, go to the command line cd to your project root to force the installation of symfony/event-dispatcher ^2.8 which is fully compatible with both Symfony 3 components and guzzle 3.*.

composer require symfony/event-dispatcher:^2.8

Go to `composer.json` and put the following.

{
    "require": {
        "omnipay/paypal": "~2.0"
    }
}

or

composer require omnipay/paypal

Now let's create some example route to handle the requests and Controller and Model for it.

app/routes/web.php

<?php

Route::get('/{order?}', [
    'name' => 'PayPal Express Checkout',
    'as' => 'app.home',
    'uses' => 'PayPalController@form',
]);

Route::post('/checkout/payment/{order}/paypal', [
    'name' => 'PayPal Express Checkout',
    'as' => 'checkout.payment.paypal',
    'uses' => 'PayPalController@checkout',
]);

Route::get('/paypal/checkout/{order}/completed', [
    'name' => 'PayPal Express Checkout',
    'as' => 'paypal.checkout.completed',
    'uses' => 'PayPalController@completed',
]);

Route::get('/paypal/checkout/{order}/cancelled', [
    'name' => 'PayPal Express Checkout',
    'as' => 'paypal.checkout.cancelled',
    'uses' => 'PayPalController@cancelled',
]);

Route::post('/webhook/paypal/{order?}/{env?}', [
    'name' => 'PayPal Express IPN',
    'as' => 'webhook.paypal.ipn',
    'uses' => 'PayPalController@webhook',
]);

In my projects I prefer writing named routes, we can simply use route() helper from our views. You can change the above web.php routes to your application specific standard URLs.

app/Order.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * Class Order
 * @package App
 */
class Order extends Model
{
    /**
     * @var string
     */
    protected $table = 'orders';

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['transaction_id', 'amount', 'payment_status'];
}

In the Order.php model, I am just covering basic requirements for all type projects. You are free to change and assign them in your application with the relevant type of database fields.

resources/views/form.blade.php

@extends('app')

@section('content')
    <div class="container">
        <div class="gateway--info">
            <div class="gateway--desc">
                @if(session()->has('message'))
                    <p class="message">
                        {{ session('message') }}
                    </p>
                @endif
                <p><strong>Order Overview !</strong></p>
                <hr>
                <p>Item : Yearly Subscription cost !</p>
                <p>Amount : ${{ $order->amount }}</p>
                <hr>
            </div>
            <div class="gateway--paypal">
                <form method="POST" action="{{ route('checkout.payment.paypal', ['order' => encrypt(mt_rand(1, 20))]) }}">
                    {{ csrf_field() }}
                    <button class="btn btn-pay">
                        <i class="fa fa-paypal" aria-hidden="true"></i> Pay with PayPal
                    </button>
                </form>
            </div>
        </div>
    </div>
@stop

In the traditional applications, people used to set values on forms and submit the data. I personally don't prefer that. In the above form.blade.php I have created a form action with a route() helper and encrypted my order id to make it safe.

app/config/paypal.com

<?php

return [
    'credentials' => [
        'username' => env('PAYPAL_USERNAME'),
        'password' => env('PAYPAL_PASSWORD'),
        'signature' => env('PAYPAL_SIGNATURE'),
        'sandbox' => env('PAYPAL_SANDBOX')
    ],
];

If you wish to store the API credentials in the database that is your choice, for the example I have created a config file. So replace the xxx characters with your own credentials.

app/PayPal.php

<?php

namespace App;

use Omnipay\Omnipay;

/**
 * Class PayPal
 * @package App
 */
class PayPal
{
    /**
     * @return mixed
     */
    public function gateway()
    {
        $gateway = Omnipay::create('PayPal_Express');

        $gateway->setUsername(config('paypal.credentials.username'));
        $gateway->setPassword(config('paypal.credentials.password'));
        $gateway->setSignature(config('paypal.credentials.signature'));
        $gateway->setTestMode(config('paypal.credentials.sandbox'));

        return $gateway;
    }

    /**
     * @param array $parameters
     * @return mixed
     */
    public function purchase(array $parameters)
    {
        $response = $this->gateway()
            ->purchase($parameters)
            ->send();

        return $response;
    }

    /**
     * @param array $parameters
     */
    public function complete(array $parameters)
    {
        $response = $this->gateway()
            ->completePurchase($parameters)
            ->send();

        return $response;
    }

    /**
     * @param $amount
     */
    public function formatAmount($amount)
    {
        return number_format($amount, 2, '.', '');
    }

    /**
     * @param $order
     */
    public function getCancelUrl($order)
    {
        return route('paypal.checkout.cancelled', $order->id);
    }

    /**
     * @param $order
     */
    public function getReturnUrl($order)
    {
        return route('paypal.checkout.completed', $order->id);
    }

    /**
     * @param $order
     */
    public function getNotifyUrl($order)
    {
        $env = config('paypal.credentials.sandbox') ? "sandbox" : "live";

        return route('webhook.paypal.ipn', [$order->id, $env]);
    }
}

I prefer separation of concerns, in the example, I have a new class PayPal.php as a helper class for PayPalController.php

I think many people also like the clean code in the controller rather than over-complicating things on Controllers.

app/Http/Controllers/PayPalController.php

<?php

namespace App\Http\Controllers;

use App\Order;
use App\PayPal;
use Illuminate\Http\Request;

/**
 * Class PayPalController
 * @package App\Http\Controllers
 */
class PayPalController extends Controller
{
    /**
     * @param Request $request
     */
    public function form(Request $request, $order_id = null)
    {
        $order_id = $order_id ?: encrypt(1);

        $order = Order::findOrFail(decrypt($order_id));

        return view('form', compact('order'));
    }

    /**
     * @param $order_id
     * @param Request $request
     */
    public function checkout($order_id, Request $request)
    {
        $order = Order::findOrFail(decrypt($order_id));

        $paypal = new PayPal;

        $response = $paypal->purchase([
            'amount' => $paypal->formatAmount($order->amount),
            'transactionId' => $order->id,
            'currency' => 'USD',
            'cancelUrl' => $paypal->getCancelUrl($order),
            'returnUrl' => $paypal->getReturnUrl($order),
        ]);

        if ($response->isRedirect()) {
            $response->redirect();
        }

        return redirect()->back()->with([
            'message' => $response->getMessage(),
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     * @return mixed
     */
    public function completed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $paypal = new PayPal;

        $response = $paypal->complete([
            'amount' => $paypal->formatAmount($order->amount),
            'transactionId' => $order->id,
            'currency' => 'USD',
            'cancelUrl' => $paypal->getCancelUrl($order),
            'returnUrl' => $paypal->getReturnUrl($order),
            'notifyUrl' => $paypal->getNotifyUrl($order),
        ]);

        if ($response->isSuccessful()) {
            $order->update(['transaction_id' => $response->getTransactionReference()]);

            return redirect()->route('app.home', encrypt($order_id))->with([
                'message' => 'You recent payment is sucessful with reference code ' . $response->getTransactionReference(),
            ]);
        }

        return redirect()->back()->with([
            'message' => $response->getMessage(),
        ]);
    }

    /**
     * @param $order_id
     */
    public function cancelled($order_id)
    {
        $order = Order::findOrFail($order_id);

        return redirect()->route('app.home', encrypt($order_id))->with([
            'message' => 'You have cancelled your recent PayPal payment !',
        ]);
    }

    /**
     * @param $order_id
     * @param $env
     */
    public function webhook($order_id, $env)
    {
        // to do with next blog post
    }
}

The above implementation only covers, make a payment, handle failed, canceled the payment. I have only shown basic stuff to get up and running, you are free to update as per your application requirements.

Thanks for reading up on the end!

If you have any feedback, any typo mistake in the post above please feel free to leave your comments below.

If you want to view the overall code on Github follow this link.

For the PayPal Instant Payment Notification support, please follow the link here to read my another blog post.

Note: The team from Omnipay recently released the new version v3.0 with support for symfony 3,4 components. Also, I published a new fresh blog post to explain the new changes in v3.0 with complete integration guide. 

Happy Coding!

]]>
<![CDATA[Laravel 5.4: Login with Username or Email]]> https://sujipthapa.co/blog/laravel-54-login-with-username-or-password cf953127-faa3-4ef6-9781-e460f16b3997 2017-03-12T11:02:00+05:45 Mr. Sujip Thapa I believe that Laravel is now well-known framework among PHP developers. I hope many of you may have already built an application using it. It is really popular due to its powerful features with expressive, beautiful syntax.

From this tutorial, we want to customize the default auth system in our Laravel 5.4 application to allow users to sign in using their username or email address. As of now Laravel framework only supports sign in with email only. After going through this tutorial your application will be able to allow sign in with both username or email, also you can easily extend this concept to any other version of Laravel framework.

 

Before moving to start the tutorial you should be ready with Laravel 5.4 setup on your machine.
Let's begin with creating authentication scaffolding, migrations for User model.

Run below command to generate authentication scaffolding.

php artisan make:auth

If you already have experience with Laravel, then you may know about auth system, which ships with framework out of the box.

If you want to start from scratch, delete the user's table from database/migrations directory and hit the following command to generate new migration file for the user's table.

php artisan make:migration create_users_table --create="users"

Now, we are adding username field to migration file.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('username')->unique();
            $table->string('email')->unique();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

Let's migrate the database and go ahead.

php artisan migrate

Now let's look at LoginController.php which implements AuthenticatesUsers.php trait. which also ships with framework out of the box.

If you look at the trait, which includes several methods only related to authenticating the user into the application. Now its time to deal with our actual requirement.

I am going to override the credentials() methods from AuthenticatesUsers.php trait to LoginController.php

Now, my LoginController.php looks like below.

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
     */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest', ['except' => 'logout']);
    }

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(Request $request)
    {
        $field = filter_var($request->get($this->username()), FILTER_VALIDATE_EMAIL)
            ? $this->username()
            : 'username';

        return [
            $field => $request->get($this->username()),
            'password' => $request->password,
        ];
    }
}

Also, I changed few things in my login.blade.php, the email input field to be the text for username support.

<input type="text" class="form-control" name="email" placeholder="E-Mail / Username" value="{{ old('email') }}" required autofocus>

Okay! now we have a functionality to allow login with username or email.

Last Updated: 10th, September 2017

 

If you would like to view updated new blog post to cover login/register with Laravel v5.5 here.

Conclusion

This tutorial heavily depends on Laravel 5.4, so if you are going to implement this on another version of framework please have a look at the traits used properly to override the credentials array.

Thanks for reading up to the end. If you have any feedback please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[Fixing - PHPUnit Util Deprecated Feature Logger]]> https://sujipthapa.co/blog/fixing-class-phpunit-util-deprecatedfeature-logger 8f954d6b-dddb-4073-9882-e678a564b5b6 2016-11-19T06:03:18+05:45 Mr. Sujip Thapa In this series, i am going to cover a common bug that developers face during running the PHP Unit Tests.

Keep in mind, this error occured while I was using phpunit, version line five.

composer global require "phpunit/phpunit"

Lately, I was working on my open source project on Github, which is an Omnipay based PHP Library for NAB Transact, and stuck with the Class PHPUnit_Util_DeprecatedFeature_Logger PHP Fatal error.

 

This post is to help someone else that tries it – and comes across the same issue like me.

PHP Fatal error: Class PHPUnit_Util_DeprecatedFeature_Logger contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (PHPUnit_Framework_TestListener::addRiskyTest) in …vendor/phpunit/phpunit/PHPUnit/Util/DeprecatedFeature/Logger.php on line 202

My phpunit.xml.dist file was like below.

backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
colors="true"
syntaxCheck="false"> 
./tests/ 
./src

My fix was simple – it took some systematic editing of the phpunit.xml.dist file to figure it out.

The actual fix was simple – the problematical line for me was:

colors = "false"

I’ve said for a long time that “you don’t get paid the big bucks for knowing what to do – it’s for knowing how to fix it when you make the inevitable screw-ups”.

Now, when I run my PHPUnit tests it is working normally.

Thanks for reading up to the end.

Happy Coding!

]]>
<![CDATA[Laravel v4.2.20 , a special release.]]> https://sujipthapa.co/blog/laravel-v4220-a-special-release 8e97311a-f83c-4b1a-8352-befaa7d20d43 2016-10-27T13:45:17+05:45 Mr. Sujip Thapa I new version for Laravel v4.2.20 has been released. Yes!, you are absolutely correct, that new version for the Laravel's back version v4.2 is now released with support for PHP 7.0 and updates to the Symfony components to v2.7.

The v4 line of Laravel is already deprecated, but a lot of developers around the world are still using it and haven’t upgraded to the latest releases. This recent release allows them to move to PHP 7.0 and take new advantage of its speed improvements as well as security fixes without having to upgrade their app's code.

Ideally, those who are still on v4 version have plans to upgrade to v5 but the Laravel team understands that not every business has the luxury of being on the latest and greatest at all times.

 

There will always be an exception in their company to their typical policy of no new features on old versions of applications.

Post Credit: The original post was published on https://laravel-news.com and wanted to write few links to refer my readers.

Conclusion

Thanks for reading this article up to the end. If you have any feedback or the article was really useful to you please leave your comments below. Feel free to share with friends if you like it.

Happy Coding!

]]>
<![CDATA[Hacktoberfest is back, #Hacktoberfest2016]]> https://sujipthapa.co/blog/hacktoberfest-is-back-hacktoberfest2016 df19b099-ce3f-4fea-a8ba-d0ec2c2b9a31 2016-09-29T13:25:00+05:45 Mr. Sujip Thapa Hey Developers !

Its time to celebrate an open source (#Hacktoberfest) in this October by participating with Hacktoberfest 2016. It is a month-long (festival of code) campaign to spread open source resources, which is organized by DigitalOcean and hosted on GitHub.

Rules for developers
To get a Hacktoberfest 2016 t-shirt, you must create four pull requests, starting October 1 - October 31 2016.

Image Courtesy Github

To participate, simply open a pull request and contribute to any open source project. You can fix a bug, add a feature, or even improve some documentation.

 

Don't forget to mention #hacktoberfest hashtag on Twitter, Facebook, or Instagram once you've made your pull requests.

After you create atleast four pull requests by October 31st, you'll get the satisfaction of sharing your code with the world—and a t-shirt, of course.

Last Updated : 26th, Oct 2016

Completed the Hacktoberfest challenge!

Happy Coding !

]]>
<![CDATA[An alternative way for PHP array_column]]> https://sujipthapa.co/blog/an-alternative-way-for-php-array-column f4a76036-1a42-47a5-ab34-766d889a0ac5 2016-09-29T05:45:16+05:45 Mr. Sujip Thapa As a developer i'm pretty much sure, most of the developers use latest version of PHP in their machine, so do I. 

The thing is, sometime with version of PHP on hosting servers, we have to think about the alternative way.

Here with this post I am going to share a pro tip for developers,  I learned when i also got stuck with.

 

Alternative way for to make it working on old version of PHP for method  array_column , this method is available only on (PHP 5 >= 5.5.0, PHP 7) see official doc 

$array = [
   ['developer' => ['name' => 'Sujip Thapa', 'age' => 25, 'expericence' => '+3 years']],
   ['developer' => ['name' => 'Aakash Gurung', 'age' => 25, 'expericence' => '+3 years']],
 ];


In PHP >=5.5 get all developers name only:

$developers = array_column($array, 'name');


In PHP < 5.5 - alternative way to get all developers name only:

$developers = array_map(function ($each) {

 return $each['name'];

 }, $array);


If you are a Laravel developer and using v4.2 you also can do like below, see official doc

$developers = array_fetch($array, 'name');


Output will be same:

$developers = ['Sujip Thapa', 'Aakash Gurung'];


Happy Coding !

]]>
<![CDATA[Git Pro Tip: Show your branch on Linux Ubuntu terminal]]> https://sujipthapa.co/blog/git-pro-tip-show-your-branch-on-linux-ubuntu-terminal 6c4edbe0-ad35-43d8-aeab-1e2e81717f55 2016-09-10T18:15:00+05:45 Mr. Sujip Thapa  If you would like to add branch name to bash prompt then we have to edit the PS1variable(set value of PS1 in~/.bash_profile).

Want to know about PS1 ?

PS1 denotes Prompt String 1. Which is one of the prompt available in Linux/UNIX shell. If we open our terminal, it displays the content defined in PS1 variable in your bash prompt.

Here is an example of how to edit “.bashrc” for Git branch information.

Open up your terminal and type.

nano ~/.bashrc

Note: use sudo command as per you need, otherwise make sure your current user has proper permission to save .bashrc file.

Now, scroll up to last and paste the code snippet provided below.

then press, Ctrl + O to save, Ctrl + X to close the nano edit mode, then restart the terminal.

parse_git_branch() {
     git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
}
export PS1="\u@\h \[\033[32m\]\w\[\033[33m\]\$(parse_git_branch)\[\033[00m\] $ "

Enjoy !

]]>
<![CDATA[Install Atom Editor on Ubunto 16.04]]> https://sujipthapa.co/blog/install-atom-editor-on-ubunto-1604 ecd84524-9e1f-4fcb-b6c0-79c1d8fcc26e 2016-09-10T18:15:00+05:45 Mr. Sujip Thapa Press Ctrl+Alt +T open up your teminal, please type these commands.

sudo add-apt-repository ppa:webupd8team/atom
sudo apt-get update
sudo apt-get install atom

Enjoy!

]]>
<![CDATA[Setup Mailgun with Digital Ocean]]> https://sujipthapa.co/blog/setup-mailgun-with-digital-ocean 0ae0db81-44f8-4a48-99b2-a3cae2cc43ab 2016-09-10T18:15:00+05:45 Mr. Sujip Thapa One big panic thing is managing and having to set up the email server of own. It requires a lot of technical things, resources, pricing, security prerequisites and many more. A lot of modern, secure, reliable mail service providers are up in the tech market these days.

Here, in this blog post, we are going to set up a renowned mail service provider to work your project with low hassle. The mail service provider I'm setting up is Mailgun.

I use Digital Ocean as a primary hosting partner for the Laravel projects I work for my clients. We are going to setup Mailgun with Digital Ocean platform to send emails from the web application. In the previous years, Mailgun used to work without doing any configuration side of things to Digital Ocean end. In the following years, they want us the verify the ownership to use their service with any third party application.

Before following this article, I will assume you have following services available to you if you would like to implement the step by step guide.

Prerequisites

  • Register for Digital Ocean, create a droplet, install the platform you want to run your application.
  • Register your account with Mailgun

Before starting let's understand some benefits of using Mailgun.

  • Exciting thing up to now is, it gives you 10,000 emails free every month, no payment hassle for really small to medium-sized companies.
  • Reliable & Powerful APIs that facilitates to send, receive, track your emails seamlessly.
    Trusted by the tech giants in the industry, so kind of no compromise in security side.
  • If your business needs more than the free emails provided by the service provider, you can choose their simple pricing method as per your requirement. The pricing is like pay as you go, as they give you interface to calculate the price based on your needs for your monthly volume of emails you want.

There are a lot of competitors to this service providing the same quality of email services. I am using Mailgun for long with my projects, so due to its simplicity and reliability, I am considering to use it furthermore for now.

We are not going through any large server setup in this article. We are just going to look at the Networking part under the Digital Ocean account to manage a few DNS configuration to make emails stuff work with Mailgun.

Mailgun
After your successful registration with Mailgun service, the first step is to go to the Domains tab. For the fresh new account, you will see one record saying sandbox domain, which allows testing the development phase emails of your application before going to production.

Now, our concern is to setup emails environment for the production application.

Add Mailgun DomainGo through the steps below to add a new domain.

  • Click on the Add New Domain button and enter your production domain name in which you are going to use this Mailgun email service.
  • After adding your production domain, it will appear in the list under the Domains tab.
  • Click on the domain name from the list to view the email service configurations to be added to Digital Ocean service.
  • After clicking your domain from the list, you will see a page with a lot of details, and configuration that can be changed or managed to make the email service up and running.

Review the following headings on the page based on your need according to your application context.

  • Domain Information
  • Domain Settings
  • Tracking Settings
  • Security Settings For Outgoing Mail
  • IP Management
  • Domain Verification & DNS

In this article, we are going to look more in-depth about Domain Verification & DNS.

Before you add a few DNS configuration to the hosting partner end, the Mailgun shows the status of your domain as Unverified.

Note: I am taking Digital Ocean as my hosting partner, but not only Digital Ocean, this article works for any hosting provider which gives you access to manage DNS settings from their user panel based on what type of plan you choose from them.

After completing the above few things in the Mailgun end, we now have to log in into the hosting partner, for me its Digital Ocean.

Digital Ocean

After logging into digital Ocean dashboard, navigate to the Networking tab. It shows you the list of domains attached with droplets you own for your applications.

If you are going to manage the new domain, add it to the list, or select the existing one from the list.

Digital Ocean DNS PanelWe have to add a few DNS records like TXT, MX, CNAME to verify the Mailgun domain form the Digital Ocean DNS management panel.

Add following records under the Networking > yourdomain.com.

  • A record should always point to your droplet's IP Address, not the IP Address given by previous Mailgun configuration page.
  • While adding TXT record given by Mailgun, wrap them in double quotes before saving in Digital Ocean end.
  • TXT, MX, CNAME should be the correct value from Mailgun.

Mailgin Domain VerificationAfter all setup, you have to lastly go to Mailgun, under Domain Verification & DNS click on Check DNS Record Now, to verify the domain configuration page.

Usage

If you are using Mailgun with Laravel, you have to update your .env file or config/mail.php to use mailgun driver, and config/services.php

'mailgun' => [
    'domain' => 'your-mailgun-domain',
    'secret' => 'your-mailgun-api-key',
],


To get the API Key, select the domain from the Domains list, and you will see it on that page.

Testing

To test your production emails, now perform some email sending functionality, the emails log will appear in the Mailgun dashboard as processed, delivered, dropped and so on.

Conclusion

Thanks for reading this article up to the end. If you have any queries on setting up Mailgun, feel free to ask via comment. I will try to reply or even help for other hosting partners including Digital Ocean.

Last Updated: 7th, October 2018

Happy Coding!

]]>
<![CDATA[Laravel 5.1 LTS Cheat Sheet]]> https://sujipthapa.co/blog/laravel-51-lts-cheat-sheet 3b7a72fd-098f-4c12-ad16-a63f9160cd72 2016-09-10T18:15:00+05:45 Mr. Sujip Thapa Are you a PHP developer, working using the Lararvel framework?

If your answer is yes; here in this article we are writing about the Laravel 5 cheat sheet.

The cheat sheet guide is a complete reference for Laravel 5.1 LTS and higher versions of the framework.

The two members @Summer and @Aufree are the maintainers of the project, and they are from The EST Group.

The cheat sheet guide supports responsive design, so can be viewed in any devices.

Laravel 5.1 Cheat Sheet

Contribute to the project on github - https://github.com/summerblue/laravel5-cheatsheet

Open the cheat sheet - https://summerblue.github.io/laravel5-cheatsheet

Cheat Sheet for Chinese Developers - https://cs.laravel-china.org

Laravel 5.3 Cheat Sheet - https://itviewer.github.io/laravel5-cheatsheet

The cheat sheet for the older Laravel v4.2 version is also available, which is maintained by @JesseObrien.

Laravel v4.2 Cheat Sheet - https://laravel.gen.tr/cheatsheet

Happy Coding!

]]>