jueves, 29 de diciembre de 2022

Dockerize Moodle application

Create the docker-compose.yml file to deploy a Moodle application, using Nginx as a webserver, latest PHP, and MySQL as a database engine:

version: '3.7'
services:
  nginx:
    image: nginx:latest
    ports: 
      - 80:80
    volumes:
      - ./moodle:/var/www/html
  php:
    image: php:latest
    depends_on:
      - nginx
    volumes:
      - ./moodle:/var/www/html
  mysql:
    image: mysql:latest
    environment:
      MYSQL_ROOT_PASSWORD: my-secret-pw
    volumes:
      - db:/var/lib/mysql
volumes:
  db:
    external:

Do I need to install Moodle in docker from an image or from a moodle downloaded directory files?

You can install Moodle from a downloaded directory of Moodle files, or you can use an existing image to quickly deploy a Moodle application in Docker.



lunes, 29 de marzo de 2021

Laravel 8 Jetstream How to queue Email Verification

Laravel 8 Email verification is not queued by the default. To queue it, you need to override the /vendor/ functions (not modify them directly).

Photo by Picography from Pexels

Basically we need to add both the ShouldQueue interface and the Queueable trait to the "SendEmailVerificationNotification" listener. 


The problem is that we can't go ahead and edit it, since it's located right in the framework source code (/vendor/laravel/framework/src/Illuminate/Auth/Listeners/). 


The reason is that when you execute the "composer update", your changes will simply be overriden should the "laravel/framework" be updated.

The steps are

1.- Open and take a look at the "/vendor/laravel/framework/src/Illuminate/Contracts/Auth/MustVerifyEmail.php" interface file. 


It has several methods that you can use basically inside the User.php model file to customize (override) them. 


That's why those methods are empty and are part of an Interface class.


2.- Create our own "VerifyEmail" notification.

$ php artisan make:notification Auth/VerifyEmail


Now open the "/vendor/laravel/framework/src/Illuminate/Auth/Notifications/VerifyEmail.php", copy all the contents and paste them inside your new "app/Notifications/Auth/VerifyEmail"

It should look like:

<?php

namespace App\Notifications\Auth;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\URL;

class VerifyEmail extends Notification implements ShouldQueue
{
    use Queueable;


    /**
     * The callback that should be used to create the verify email URL.
     *
     * @var \Closure|null
     */
    public static $createUrlCallback;

    /**
     * The callback that should be used to build the mail message.
     *
     * @var \Closure|null
     */
    public static $toMailCallback;

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

    /**
     * Build the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        $verificationUrl = $this->verificationUrl($notifiable);

        if (static::$toMailCallback) {
            return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl);
        }

        return $this->buildMailMessage($verificationUrl);
    }

    /**
     * Get the verify email notification mail message for the given URL.
     *
     * @param  string  $url
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    protected function buildMailMessage($url)
    {
        return (new MailMessage)
            ->subject(Lang::get('Verify Email Address'))
            ->line(Lang::get('Please click the button below to verify your email address.'))
            ->action(Lang::get('Verify Email Address'), $url)
            ->line(Lang::get('If you did not create an account, no further action is required.'));
    }

    /**
     * Get the verification URL for the given notifiable.
     *
     * @param  mixed  $notifiable
     * @return string
     */
    protected function verificationUrl($notifiable)
    {
        if (static::$createUrlCallback) {
            return call_user_func(static::$createUrlCallback, $notifiable);
        }

        return URL::temporarySignedRoute(
            'verification.verify',
            Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
            [
                'id' => $notifiable->getKey(),
                'hash' => sha1($notifiable->getEmailForVerification()),
            ]
        );
    }

    /**
     * Set a callback that should be used when creating the email verification URL.
     *
     * @param  \Closure  $callback
     * @return void
     */
    public static function createUrlUsing($callback)
    {
        static::$createUrlCallback = $callback;
    }

    /**
     * Set a callback that should be used when building the notification mail message.
     *
     * @param  \Closure  $callback
     * @return void
     */
    public static function toMailUsing($callback)
    {
        static::$toMailCallback = $callback;
    }
}


3.- Copy the following from that "MustVerifyEmail.php  interface function:


<?php
//...

/**
     * Send the email verification notification.
     *
     * @return void
     */
    public function sendEmailVerificationNotification();


4.- Paste it somewhere, maybe at the end, inside the app/Models/User.php Model file and set the following:


<?php

//...
use App\Notifications\Auth\VerifyEmail;
//...

    /** .......
     * Send the email verification notification.
     *
     * @return void
     */
    public function sendEmailVerificationNotification()
    {
        try {
            $this->notify(new VerifyEmail);
        }
        catch (\Swift_TransportException $exception){
            return redirect()->route('home')->with('error','馃槦 Could not send the email: '.$exception->getMessage().' ❌')->send();
        }
    }


5. If you want to store the queued jobs in the database, like I do. Then you need to configure additional settings.

Create the `jobs` table for Laravel 8 Jetstream

$ php artisan queue:table

which will create a new migration file called "create job batches table": 

Run the migration

$ php artisan migrate

6.- Test it now.

Go ahead and register a new user. You will get a message that a verification email has been sent. Open your mailhog client at http://localhost:8025/ .


You will find your email box empty. This is because that task has been queued. 

7.- To run the pending queues execute the command:

$ php artisan queue:work

It will display the jobs pending and it will start processing them.


$ php artisan queue:work

[2021-03-30 03:48:09][1] Processing: App\Notifications\Auth\VerifyEmail

[2021-03-30 03:48:09][1] Processed:  App\Notifications\Auth\VerifyEmail

So far so good.


Now you may find useful queueing the password reset notification as well.

1.- Create a new notification:

$ php artisan make:notification Auth/ResetPassword


2.- Open that file and paste the code found in "vendor/laravel/framework/src/Illuminate/Auth/Notifications/ResetPassword.php" into this newly created file "app/Auth/ResetPassword.php" where you basically want to add the ShouldQueue implementation and the Queueable bus:


<?php

namespace App\Notifications\Auth;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Lang;

class ResetPassword extends Notification implements ShouldQueue
{
    use Queueable;

    /**
     * The password reset token.
     *
     * @var string
     */
    public $token;

    /**
     * The callback that should be used to create the reset password URL.
     *
     * @var \Closure|null
     */
    public static $createUrlCallback;

    /**
     * The callback that should be used to build the mail message.
     *
     * @var \Closure|null
     */
    public static $toMailCallback;

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

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

    /**
     * Build the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        if (static::$toMailCallback) {
            return call_user_func(static::$toMailCallback, $notifiable, $this->token);
        }

        if (static::$createUrlCallback) {
            $url = call_user_func(static::$createUrlCallback, $notifiable, $this->token);
        } else {
            $url = url(route('password.reset', [
                'token' => $this->token,
                'email' => $notifiable->getEmailForPasswordReset(),
            ], false));
        }

        return $this->buildMailMessage($url);
    }

    /**
     * Get the reset password notification mail message for the given URL.
     *
     * @param  string  $url
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    protected function buildMailMessage($url)
    {
        return (new MailMessage)
            ->subject(Lang::get('Reset Password Notification'))
            ->line(Lang::get('You are receiving this email because we received a password reset request for your account.'))
            ->action(Lang::get('Reset Password'), $url)
            ->line(Lang::get('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.'.config('auth.defaults.passwords').'.expire')]))
            ->line(Lang::get('If you did not request a password reset, no further action is required.'));
    }

    /**
     * Set a callback that should be used when creating the reset password button URL.
     *
     * @param  \Closure  $callback
     * @return void
     */
    public static function createUrlUsing($callback)
    {
        static::$createUrlCallback = $callback;
    }

    /**
     * Set a callback that should be used when building the notification mail message.
     *
     * @param  \Closure  $callback
     * @return void
     */
    public static function toMailUsing($callback)
    {
        static::$toMailCallback = $callback;
    }
}


3.- Go to the User.php model and add the following method, which is found in the "vendor/laravel/framework/src/Illuminate/Contracts/Auth/CanResetPassword.php" interface:


<?php
//..
use App\Notifications\Auth\ResetPassword;
//..


    /**
     * Send the password reset notification.
     *
     * @param  string  $token
     * @return void
     */
    public function sendPasswordResetNotification($token)
    {
        try{
            $this->notify(new ResetPassword($token));
        }catch (\Swift_TransportException $exception){
            return redirect()->route('home')->with('error','馃槦 Could not send the email: '.$exception->getMessage().' ❌')->send();
        }
    }

4.- Try it now by clicking on "forgot password?", and then execute

$ php artisan queue:work

[2021-03-30 04:25:37][2] Processing: App\Notifications\Auth\ResetPassword

[2021-03-30 04:25:38][2] Processed:  App\Notifications\Auth\ResetPassword

That is pretty much it


domingo, 28 de marzo de 2021

Laravel 8 Mailhog in Homestead: connection could not be established with host mailhog

Laravel 8 Mailhog in Homestead: connection could not be established with host mailhog - Photo by Ivan Samkov from Pexels

After registering a user account in Laravel 8 Jetstream Homestead, I got the following error:

Connection could not be established with host mailhog :stream_socket_client(): php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution.

This error happened to me because in the .env file I had the following:

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=mail@net.com
MAIL_FROM_NAME="${APP_NAME}"

So, I set the MAIL_HOST value to "localhost":

MAIL_MAILER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=mail@net.com

And the problem got solved.

Laravel 8 Jetstream How to enable email verification

 Laravel's Jetstream email verification is not activated by default.


Notice I am using Laravel Homestead. Should you use any other development environment, you should be good to go. 


Follow these steps

1.- Open the /config/fortify.php file and comment out the email verification feature, so that:

    'features' => [
        Features::registration(),
        Features::resetPasswords(),
        Features::emailVerification(),
        Features::updateProfileInformation(),
        Features::updatePasswords(),
        Features::twoFactorAuthentication([
            'confirmPassword' => true,
        ]),
    ],

2.- Open the app/models/User.php file and enable the Email verification functionality by adding:

use Illuminate\Contracts\Auth\MustVerifyEmail;
...

class User extends Authenticatable implements MustVerifyEmail
{
   ...
}


3.- Enable your email service. For example, in Laravel homestead you already have mailhog installed. Inside your .env set your email settings:

MAIL_MAILER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=hello@yourapp.com
MAIL_FROM_NAME="${APP_NAME}"

4.- Now try to register a new user and make sure your email settings are correct. 

Once I've registered I get the following view:

Laravel 8 Jetstream How to enable email verification

If I open my mailhog client:

Laravel 8 Jetstream How to enable email verification

I get indeed the Laravel Jestream email verification message.


References

https://jetstream.laravel.com/2.x/features/registration.html#email-verification

https://stackoverflow.com/q/64127670/1883256

https://www.youtube.com/watch?v=vgf3rjvAzEY


Dockerize Moodle application

Create the docker - com pose . y ml file to deploy a Mood le application , using N ginx as a webs erver , latest PHP , and ...