Laravel 5 ile CMS - Events, Email ve Frontend

Admin panelimizi bitirmeden önce ufak bir ekleme daha yapacağız, yapacağımız şey ise kullanıcı admin paneline giriş ve çıkış yaptığında, o anı timestamp olarak veritabanınında tutacağız, yine aynı şekilde giriş yaptığında da ip adresini de tutacağız.

Öncelikle bu sütunları eklememiz lazım:
php artisan make:migration:schema add_columns_to_users_table --schema="logged_in_at:timestamp,logged_out_at:timestamp,ip_address:binary"
php artisan migrate
Bu sütunları ekledikten sonra iki adet event handler oluşturacağız.
php artisan handler:event LoginEventHandler
php artisan handler:event LogoutEventHandler
Event oluşturmamıza gerek yok, çünkü Authentication, login ve logout durumunda eventlerini fire(Burada Türkçe hangi kelimeyi kullanabilirim fikrim yok, kusura bakmayın) ediyor. app/Handlers/Events klasörü içerisinde iki yeni dosya göreceksiniz. LoginEventHandler.php dosyasını alttaki gibi düzenleyin. İlerledikçe hepsini açıklayacağım.

use Illuminate\Http\Request;
use Carbon\Carbon;
use App\User;

class LoginEventHandler {

protected $request;

public function __construct(Request $request)
{
$this->request = $request;
}

public function handle(User $user)
{
$user->logged_in_at = Carbon::now();
$user->ip_address = $this->request->getClientIp();
$user->save();
}

}
Şimdi bu event handler oluşturulurken, Laravel Request nesnesini paslıyor, biz de bu nesneyi alıp kendi değişkenimizde tutuyoruz, Handle kısmında da yine auth.login eventi kullanıcıyı ve varsa remember_me kısmını paslıyor, biz de sadece paslanılan kullanıcıyla ilgileneceğimizden, onu kullanıyorum. O anki anı kullanıcının giriş yaptığı an olarak kaydederken, Request modelinin içerisindeki yöntemle de kullanıcının ip adresini kaydediyoruz. LogoutEventHandler.php dosyası da alttaki gibi olacak.

use Illuminate\Http\Request;
use Carbon\Carbon;
use App\User;

class LogoutEventHandler {

protected $request;

public function __construct(Request $request)
{
$this->request = $request;
}

public function handle(User $user)
{
$user->logged_out_at = Carbon::now();
$user->save();
}

}
Son olarak da bunları kullandığımızı belirtmemiz lazım ki en kritik kısım burada, app/Providers içerisinde, EventServiceProvider.php dosyanızda Laravel'in dinleyeceği eventleri belirtmeniz lazım ki biz burada login ve logout kısımlarında olduğunu belirteceğiz. Bunun için de listen dizisini alttaki gibi düzenlememiz lazım.
protected $listen = [
'auth.login' => [
'App\Handlers\Events\LoginEventHandler',
],
'auth.logout' => [
'App\Handlers\Events\LogoutEventHandler',
],
];
Burada auth.login eventin adı, dizi içerisindeki kısım da event handlerlarının adı, neden bu kısım işime yarasın ki diye düşünecek olursanız, örneğin sitenizde satış yaptığınızı düşünün, satış yapıldığında satış yapıldı diye kendi eventinizi fire ettiniz, bu event sonucunda da hem kullanıcıya mail atmak istiyorsunuz, bunun yanında kendinize mail atmak istiyorsunuz, ayrıca da bu durumla ilgili bir rapor oluşturmak istiyorsunuz, işte bu üç işlemi de üç farklı event handler oluşturarak halledebilir, her bir bileşeni birbirinden ayırabilirsiniz. Ufak bir not daha, bu handlerları ekledikten sonra dinlenebilmeleri için php artisan optimize komutunu da uygumanız lazım. Son olarak ip adresini binary tutuyorsanız, işlem yaparken ona göre get ve set yöntemlerini düzenlemeniz lazım. Bunun için de alttaki gibi accessor ve mutator düzenlemesi yapmanız lazım.
public function setIpAddressAttribute($ip)
{
$this->attributes['ip_address'] = inet_pton($ip);
}

public function getIpAddressAttribute($ip)
{
return inet_ntop($ip);
}
Email kısmını da şifremi unuttum arayüzünde kullanacağız, aslında çok büyük bir yapılandırma yapmaya gerek yok. Öncelikle config içerisinde yer alan mail.php dosyasını açın ve from satırını bulup alttaki gibi değiştirin, böyle kısımları direkt config'ten belirtmek bana saçma geldiği için .env dosyasında tutacağız.
'from' => ['address' => env('MAIL_FROM_ADDRESS'), 'name' => env('MAIL_FROM_NAME')],
Emailleri gmail ile yollayacağımız için, alttaki gibi değişkenlerinizi oluşturun. Kullanıcı adınız gmail kullanıcı adınız, şifreniz gmail'e o adresle girerken kullandığınız şifre, from adresi de yine aynı kullanıcı adı ve name değişkeni de o mailin üzerine kayıtlı olduğu kişinin adı.
MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=******
MAIL_PASSWORD=******
MAIL_FROM_ADDRESS=******@gmail.com
MAIL_FROM_NAME=******
Bu düzenlemelerden sonra resources/views/auth/password.blade.php dosyanızı alttaki gibi düzenleyebilirsiniz.
@extends('layouts.auth')

@section('title')
    {{ trans('auth.password.title') }}
@stop

@section('content')

    <div class="login-box" id="login-box">

        <div class="header">
            {{ trans('auth.password.title') }}
        </div>

        {!! Form::open(['method' => 'POST', 'url' => '/password/email']) !!}

        <div class="body bg-gray-50">

            @include('errors.validation')
            @if (session('status'))
                <div class="alert alert-success">
                    {{ session('status') }}
                </div>
            @endif

            <div class="form-group has-feedback">
                {!! Form::label('email', trans('auth.login.email')) !!}
                {!! Form::text('email', null, ['class' => 'form-control']) !!}
                <i class="fa fa-envelope form-control-feedback"></i>
            </div>

        </div>

        <div class="footer">
            {!! Form::submit(trans('auth.password.submit'), ['class' => 'btn bg-olive btn-block btn-flat']) !!}
        </div>

        {!!  Form::close() !!}

    </div>

@endsection
Eğer test ederseniz, artık şifreyi yenileyebildiğinizi göreceksiniz. Şifre yenileme sonucunda giden emailin içeriğini değiştirmek için de resources/views/emails/password.blade.php dosyasında değişiklik yapabilirsiniz. Şahsen ben onu da dil dosyalarında tutuyorum. Son olarak da şifreyi yeniledikten sonra kullanıcının yönleneceği kısmı belirtmek için de app/Http/Controllers/Auth içerisindeki PasswordController.php dosyanıza alttaki satırı ekleyebilirsiniz.
protected $redirectTo = '/admin';
Şifre yenileme kısmı da bittiğine göre yönetici panelimizin tamamiyle bittiğini belirtebilirim.

Arayüz kısmında her şeyi tek tek anlatmayacağım, sadece anasayfa nasıl yapılır, makaleleri dile göre nasıl getirebiliriz bunlara biraz bakacağız. Gulp dosyasında neler olduğunu nasıl derlediğimize tekrar tekrar geri dönmektense application layout dosyamıza bakalım.

resources/views/layouts/application.blade.php
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>@yield('title')</title>
    <meta name="_token" content="{{ csrf_token() }}" />
    <meta property="og:type" content="website">
    <meta property="og:url" content="{{ Request::url() }}">
    <meta name="twitter:url" content="{{ Request::url() }}">
    <meta name="og:title" content="@yield('title')" >
    <meta name="twitter:title" content="@yield('title')">
    <meta name="description" property="og:description" content="@yield('description')">
    <meta name="twitter:description" content="@yield('description')">
    <link rel="stylesheet" type="text/css" href="{{ url( elixir('css/application.css') ) }}">
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
    <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
    <script src="{{ url( elixir('js/application.js') ) }}" type="text/javascript"></script>
    <script src="{{ url( 'js/application-custom.js' ) }}" type="text/javascript"></script>
</head>
<body>
@include('partials.application.top')
<div class="container">
    <main class="content">
        @yield('content')
    </main>
</div>
@include('partials.application.footer')
</body>
</html>
Bu dosyayı şu yüzden tekrar gösteriyorum, title description gibi SEO için önemli kısımları da yield ile genişleterek, her sayfanın, kendine has Title ve Description'ı olmasını sağlayabiliriz. Daha önceden Locale diye bir middleware oluşturmuştuk hatırlarsanız, o dosyada ufak değişiklik yapmamız lazım.
use Session;
use App\Language;
use Closure;
use App;
use Carbon\Carbon;
use Config;

class Locale {

public function __construct()
{
Config::set(['languages' => Language::all()]);
}

public function handle($request, Closure $next)
{
$language = Session::get('language', Config::get('app.locale'));
$current_lang = Language::whereCode($language)->firstOrFail();
App::setLocale($language);
Carbon::setLocale($language);
Session::set('current_lang', $current_lang);
return $next($request);
}

}
Fark ettiyseniz Session içerisinde şu anki dili tutacağız, bu çok işimize yarayacak. Şimdi anasayfa Controller'ını oluşturalım.

use App\Http\Controllers\Controller;
use Session;
use App;

class HomeController extends Controller {

public function index()
{
$language = Session::get('current_lang');
$articles = $language->articles()->published()->orderBy('published_at','desc')->paginate(5);
return view('application.home.index', compact('articles'));
}

}
Evet pagination yapmak bu kadar basit, hiç ekstra bir şey yapmamıza gerek yok. Makaleleri de dilin sahip olduğu makalelerden ulaşıyoruz. Language modelindeki alttaki kısım buraya ulaşabilmemizi sağlıyor.
public function articles()
{
return $this->hasManyThrough('App\Article', 'App\Category');
}
Ve son olarak anasayfa viewımızı oluşturalım.
@extends('layouts.application')

@section('title'){{ Session::get('current_lang')->site_title }}@endsection
@section('description'){{ Session::get('current_lang')->site_description }}@endsection

@section('content')
    @if(count($articles))
        @foreach($articles as $article)
            <article class="post">
                <header class="post-header">
                    <div class="post-category">
                        <a style="background-color: {{ $article->category->color }}" href="{{ route('category', ['id' => $article->category->id])  }}">{{ $article->category->title }}</a>
                    </div>
                    <div class="post-title">
                        <h2>
                            <a href="{{ route('article', ['id' => $article->id])  }}">{{ $article->title }}</a>
                        </h2>
                    </div>
                </header>
                <div class="post-excerpt">
                    {{ limit_to_numwords($article->content, 50)  }}
                </div>
                <footer class="post-footer">
                    <div class="post-meta-date pull-left">
                        <i class="fa fa-clock-o"></i>
                        {{ $article->published_at }}
                    </div>
                    <div class="pull-right">
                        <a class="btn post-btn btn-sm" href="{{ route('article', ['id' => $article->id])  }}">{{ trans('application.read_more') }}</a>
                    </div>
                </footer>
            </article>
        @endforeach
        {!! $articles->render() !!}
    @endif
@endsection
Render fonksiyonu sayesinde paginationı tamamiyle entegre etmiş oluyoruz. Route kısmını kendiniz oluşturabiliyor olmalısınız, işlemler aynı, ama yine de bu makaleye bakınca kafanız karışmasın diye paylaşıyorum.
Route::group(['namespace' => 'Application', 'middleware' => 'app'], function()
{
Route::get('/', ['as' => 'root', 'uses' => '[email protected]']);
Route::get('article/{article}', ['as' => 'article', 'uses' => '[email protected]']);
Route::get('page/{page}', ['as' => 'page', 'uses' => '[email protected]']);
Route::get('category/{category}', ['as' => 'category', 'uses' => '[email protected]']);
Route::post('language/change', ['as' => 'app.language.change' , 'uses' => '[email protected]']);
});
Örnek anasayfa çıktısı da alttaki gibi:
Laravel Makaleler Anasayfa

Diğer kısımları da incelemek isterseniz Github'tan bakabilirsiniz: https://github.com/ozdemirburak/laravel-5-simple-cms/tree/2c3913e96af5f7d68ad255ca42a81d44dfb86e86

Bir sonraki makalede, FTP ile sürükle bırak yöntemi dışında nasıl uygulamanızı sunucuya aktarabilirsiniz buna bakacağız ve seriyi bitireceğiz.

Seriye ait tüm makaleler alttaki gibidir.
  1. Laravel 5 ile CMS - Kurulum
  2. Laravel 5 ile CMS - Migration, Seed, Middleware, Elixir, Bower, Gulp, Blade
  3. Laravel 5 ile CMS - Controller, Model, Request, Provider, Form
  4. Laravel 5 ile CMS - WYSIWYG Filemanager, Çoklu Dil, Google Analitik API
  5. Laravel 5 ile CMS - Events, Email ve Frontend
  6. Laravel 5 ile CMS - FTP veya SSH ile Aktarım (Deployment)
Github üzerinden projenin son haline ulaşmak için: https://github.com/ozdemirburak/laravel-5-simple-cms