Laravel 5 ile CMS - WYSIWYG Filemanager, Çoklu Dil, Google Analitik API

PHP
Diğer controller ve modeller birbirine benzediği için tekrar tekrar aynı şeyleri paylaşmamak adına onları paylaşmayacağım, ancak bahsetmem gereken hala bazı şeyler var, örneğin herhangi bir WYSIWYG editörünü dosya yükleme özelliğiyle nasıl Laravel'e entegre edebilirsiniz ya da aynı şekilde yabancı kaynaklarda dahi pek bahsedilmeyen, sitenizin dashboardunda çok şık duracak, google analitik verilerini işleyerek güzel grafik ve verileri nasıl oluşturabilirsiniz bunlara cevap arayacağız. Bir sonraki makalede de mail modülünü ekleyip, Authentication'da kalan şifremi unuttum kısmıyla, frontend kısmını ekleyip bitireceğiz.

WYSIWYG tercihini TinyMCE'den yana kullandığım için bower dosyasınıza tinymce'yi eklemek için "tinymce": "~4.1.9" satırını ekleyin, gulp dosyanızda da bower install ile yüklediğiniz bu paketi taşımak için .copy(bowerDir + 'tinymce', 'public/packages/tinymce') satırını ekleyip gulp --production ile de işlemi tamamlayın. Daha sonra composer dosyanızda require kısmına "barryvdh/laravel-elfinder": "0.3.*" ekleyin post-update-cmd kısmına da "php artisan elfinder:publish" kısmını ekleyin ki paket her güncellendiğinde tekrar tekrar paketin assetlerini public klasörüne taşımanıza gerek kalmasın. Tabii ki composer update'inizi gerçekleştirin ve config/app.php dosyanızın providers dizisine de 'Barryvdh\Elfinder\ElfinderServiceProvider' kısmını ekleyin, son olarak da public klasörü içerisinde files klasörünü oluşturun ve routes dosyanıza alttaki satırı ekleyin.
Route::get('elfinder', [ 'as' => 'elfinder', 'middleware' => 'auth', 'uses' => 'Barryvdh\Elfinder\ElfinderController@showTinyMCE4'] );
Views klasöründe de partials/admin/tinymce.blade.php dosyasını oluşturup içeriğini de alttaki gibi yapın.
<script src="{{ url( 'packages/tinymce/tinymce.min.js' ) }}" type="text/javascript"></script>
<script>
    tinymce.init({
        selector: "textarea",
        theme: "modern",
        menubar : false,
        relative_urls: false,
        forced_root_block: false, // Start tinyMCE without any paragraph tag
        plugins: [
            "advlist autolink link image lists charmap print preview hr anchor pagebreak",
            "searchreplace wordcount visualblocks visualchars media nonbreaking",
            "table contextmenu directionality paste textcolor"
        ],
        toolbar1: "bold italic underline hr | link unlink |  image media | forecolor backcolor  | bullist numlist outdent indent | code | preview | styleselect",
        entity_encoding: "raw",
        file_browser_callback : elFinderBrowser
    });
    function elFinderBrowser (field_name, url, type, win) {
        tinymce.activeEditor.windowManager.open({
            file: '{{ action('Barryvdh\Elfinder\ElfinderController@showTinyMCE4') }}', // use an absolute path!
            title: '{{  trans('admin.elfinder') }}',
            width: 900,
            height: 450,
            resizable: 'yes'
        }, {
            setUrl: function (url) {
                win.document.getElementById(field_name).value = url;
            }
        });
        return false;
    }
</script>
İçinde textarea olan herhangi bir yerde de bu partial'ı alttaki gibi yüklemeyi unutmayın.
@extends('layouts.admin')

@section('content')
{!! form($form) !!}
@include('partials.admin.tinymce')
@endsection
Resim işaretine tıkladığınızda, büyüteçli dosya ikonuna tıklayınca bu eklentiyi görmeniz lazım. Son olarak da tahmin edebileceğiniz üzere WYSIWYG editörünüzde, muhtemelen Laravel'in {{ }} şeklindeki blade renderingi sonrasında çıkmayacak içerikler üreteceksiniz, ancak {!! !!} şeklinde de çıktı üretmeniz halinde, güvenilir olmayan bir kişinin editör kullanımı sonucunda XSS avı haline gelebilirsiniz. Bunun için de bir önceki makalede nasıl kuracağınızı belirttiğim HTMLPurifier'ı kullanacağız. Öncelikle projenizin olduğu klasörde alttaki komutu yazın:
sudo chmod 777 vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer 
app/config içerisinde de purifier.php dosyasını oluşturup içeriğini alttaki gibi oluşturun ya da dilediğiniz gibi değiştirebilirsiniz.
return [

'encoding' => 'UTF-8',
'finalize' => true,
'preload' => false,
'cachePath' => storage_path('purifier'),
'settings' => [
'default' => [
'HTML.Doctype' => 'XHTML 1.0 Strict',
'HTML.Allowed' => 'div,b,strong,i,em,a[href|title],ul,ol,li,p[style],br,span[style],
img[width|height|alt|src],pre,code,ul,ol,li,hr',
'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align',
'AutoFormat.AutoParagraph' => true,
'AutoFormat.RemoveEmpty' => true,
],
'test' => [
'Attr.EnableID' => true
],
"youtube" => [
"HTML.SafeIframe" => 'true',
"URI.SafeIframeRegexp" => "%^(http://|https://|//)(www.youtube.com/embed/|player.vimeo.com/video/)%",
],
],

];
Hem bunu nasıl kullanacağınızı göstermek için hem de anlatmadığım birkaç özellik için Article modelini inceleyelim. Belirttiğim gibi controller ya da view veya form hakkında ekstra bilgi vermeyeceğim, aynı şeyleri tekrar etmemize gerek yok.
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;
use Cviebrock\EloquentSluggable\SluggableInterface;
use Cviebrock\EloquentSluggable\SluggableTrait;

class Article extends Model implements SluggableInterface{

use SluggableTrait;

protected $fillable = ['title', 'description', 'content', 'category_id', 'published_at'];

protected $dates = ['published_at'];

protected $sluggable = array(
'build_from' => 'title',
'save_to' => 'slug',
'on_update' => true
);

public function category()
{
return $this->belongsTo('App\Category');
}

public function getPublishedAtAttribute($date)
{
return Carbon::parse($date)->format('Y-m-d');
}

public function setPublishedAtAttribute($date)
{
$this->attributes['published_at'] = Carbon::parse($date);
}

public function setContentAttribute($content)
{
$this->attributes['content'] = clean($content, 'youtube');
}

public function scopePublished($query)
{
$query->where('published_at', '<=', Carbon::now());
}

}
Burada farkettiğiniz üzere set*Attribute ve get*Attribute olarak iki farklı fonksiyon bildirim şekli var, * da değiştirmek ya da elde etmek istediğiniz alanın adı. Örneğin setContentAttribute ile contentin içeriğini XSS'ten temizliyoruz, ikinci parametre ile de youtube videolarına izin veriyoruz ya da get* methoduyla da ile nesnenin ulaşacağımız alanını render etmeden önce rötuşlayabiliyoruz. Onun dışında da scope* ile de Eloquent modelinde herhangi bir query'i ekleyebiliyoruz, örneğin scopePublished ile Article->published()->get() şeklinde her seferinde bu ufak kısmı eklemek zorunda kalmıyoruz ve kısaltabiliyoruz. Yukarıdaki ayarlar ile XSS filtresi sonucunda alttaki çıktıyı herhangi bir problem yaşamadan ve XSS derdiyle uğraşmadan elde edebilirsiniz.

Tekrardan dil kısmına geri dönebilir ve bunu entegre edebiliriz. Hatırladığınız üzere, diller kısmını tamamiyle bir önceki makalede entegre etmiştik, oradan dil ekledikten sonra admin panelinde de bu dillerin bayrakları çıkacak ve dileyen kullanıcı kendi dilinde bunu görebilecek. Bunun için yeni bir middleware ekleyebilir ve ona bu işi yaptırabiliriz. Middleware klasörü içerisinde Locale middlewareini oluşturun ve içeriğini de alttaki gibi yapın.
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'));
App::setLocale($language);
Carbon::setLocale($language);
return $next($request);
}

}
Ve tabii ki bu middleware'i global olarak kullanacağımız için Kernel.php dosyasında $middleware dizisinin içerisine bu middleware'i 'App\Http\Middleware\Locale' yazarak ekleyin. Middleware'imiz language session parametresini arayacak ve eğer bulamazsa da config içerisinde tanımlı olan locale'yi baz alacak. Dikkat ederseniz de tüm dilleri bir config parametresinde dizi olarak tutuyoruz, böylece uygulamanın her yerinde bu veriye ulaşabileceğiz.

Bir de daha önceki makaleler menüyü service provider içerisinde oluşturmuştuk ancak, service providerlar çalışırken session henüz başlamadığı için ve biz de menüleri dile göre oluşturduğumuzdan, bunu middleware içine taşımaya karar verdim. Dolayısıyla siz de MakeMenu diye bir middleware oluşturup, bu middleware'i kernel içerisinden register edebilir, içeriğini de menu service providerından handle kısmına taşıyabilirsiniz. Yani MenuServiceProvider'ı sileceksiniz ve middleware klasöründe MakeMenu.php dosyasını oluşturacaksınız, içeriği de alttaki gibi olacak.
use App\Language;
use Closure;
use Menu;

class MakeMenu {


/**
* Set menus in middleware as sessions are not stored already in service providers instead
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$this->makeAdminMenu();
return $next($request);
}

protected function makeAdminMenu()
{
Menu::make('admin', function($menu) {
$dashboard = $menu->add(trans('admin.menu.dashboard'), ['route' => 'admin.root'])
->icon('dashboard')
->prependIcon();

$language = $menu->add(trans('admin.menu.language.root'), '#')
->icon('flag')
->prependIcon();

$language->add(trans('admin.menu.language.add'), ['route' => 'admin.language.create'])
->icon('circle-o')
->prependIcon();

$language->add(trans('admin.menu.language.all'), ['route' => 'admin.language.index'])
->icon('circle-o')
->prependIcon();

$pages = $menu->add(trans('admin.menu.page.root'), '#')
->icon('folder')
->prependIcon();

$pages->add(trans('admin.menu.page.add'), ['route' => 'admin.page.create'])
->icon('circle-o')
->prependIcon();

$pages->add(trans('admin.menu.page.all'), ['route' => 'admin.page.index'])
->icon('circle-o')
->prependIcon();

$categories = $menu->add(trans('admin.menu.category.root'), '#')
->icon('book')
->prependIcon();

$categories->add(trans('admin.menu.category.add'), ['route' => 'admin.category.create'])
->icon('circle-o')
->prependIcon();

$categories->add(trans('admin.menu.category.all'), ['route' => 'admin.category.index'])
->icon('circle-o')
->prependIcon();

$articles = $menu->add(trans('admin.menu.article.root'), '#')
->icon('edit')
->prependIcon();

$articles->add(trans('admin.menu.article.add'), ['route' => 'admin.article.create'])
->icon('circle-o')
->prependIcon();

$articles->add(trans('admin.menu.article.all'), ['route' => 'admin.article.index'])
->icon('circle-o')
->prependIcon();

$users = $menu->add(trans('admin.menu.user.root'), '#')
->icon('users')
->prependIcon();

$users->add(trans('admin.menu.user.add'), ['route' => 'admin.user.create'])
->icon('circle-o')
->prependIcon();

$users->add(trans('admin.menu.user.all'), ['route' => 'admin.user.index'])
->icon('circle-o')
->prependIcon();

$settings = $menu->add(trans('admin.menu.settings'), ['route' => 'admin.settings'])
->icon('gears')
->prependIcon();
});
}

}
Kernelin middleware dizisinin son hali de alttaki gibi olacak, menüyü locale kısmından sonra eklemeyi unutmayın.
protected $middleware = [
'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode',
'Illuminate\Cookie\Middleware\EncryptCookies',
'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
'Illuminate\Session\Middleware\StartSession',
'Illuminate\View\Middleware\ShareErrorsFromSession',
'App\Http\Middleware\VerifyCsrfToken',
'App\Http\Middleware\Locale',
'App\Http\Middleware\MakeMenu',
];
Dili artık nasıl değiştireceğinize gelirsek de partials içerisinde admin/languages.blade.php dosyasını oluşturun ve içeriğini alttaki gibi oluşturun.
@if(count($languages))
    <ul class="languages text-center">
        @foreach($languages as $lang)
            <li>
                <img class="img-circle chosen-one" title="{{ $lang->title }}"
                     src="{{ url($lang->flag) }}" alt="{{ $lang->code }}"  value="{{ $lang->code }}" />
            </li>
        @endforeach
        {!! Form::open(['method' => 'POST', 'route' => 'admin.language.change', 'id' => 'anakin-skywalker']) !!}
        {!! Form::hidden('language') !!}
        {!! Form::close() !!}
    </ul>

    <script>
        $('img.chosen-one').click(function(){
            $('input[name="language"]').val($(this).attr("alt"));
            $('#anakin-skywalker').submit();
        });
    </script>
@endif
Burada yaptığımız şey, kullanıcı bayraklardan birine tıkladığında o veriyi formun hidden değerine atayacak ve bu formu o şekilde submitleyecek, admin language controllerının içinde de alttaki ufak fonksiyonla dili session değişkeninde tutacağız.
public function postChange()
{
Session::put('language', Input::get('language'));
return Redirect::back();
}
Son olarak da bu route'u kaydetmemiz lazım, admin route'unun son hali alttaki gibi olacak.
Route::group(['prefix' => 'admin', 'namespace' => 'Admin'], function()
{
Route::get('user/table', ['as'=>'admin.user.table', 'uses'=>'UserController@getDatatable']);
Route::get('article/table', ['as'=>'admin.article.table', 'uses'=>'ArticleController@getDatatable']);
Route::get('category/table', ['as'=>'admin.category.table', 'uses'=>'CategoryController@getDatatable']);
Route::get('language/table', ['as'=>'admin.language.table', 'uses'=>'LanguageController@getDatatable']);
Route::group(['middleware' => 'auth'], function(){
Route::get('/', ['as' => 'admin.root', 'uses' => 'DashboardController@index']);
Route::post('change', ['as' => 'admin.language.change' , 'uses' => 'LanguageController@postChange']);
Route::resource('language', 'LanguageController');
Route::resource('user', 'UserController');
Route::resource('article', 'ArticleController');
Route::resource('category', 'CategoryController');
Route::resource('page', 'PageController');
Route::get('settings', ['as' => 'admin.settings', 'uses' => 'SettingController@index']);
});
});
admin/top.blade.php dosyasının son hali de alttaki gibi olacak.
<header class="main-header">
    <a href="{{ route('admin.root') }}" class="logo"> {{ trans('admin.title')  }}</a>
    <nav class="navbar navbar-static-top" role="navigation">
        <a href="#" class="sidebar-toggle" data-toggle="offcanvas" role="button">
            <span class="sr-only">Toggle</span>
        </a>
        <div class="navbar-custom-menu">
            <ul class="nav navbar-nav">
                <li class="dropdown user user-menu">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                        <img class="user-image img-circle" src="{{ !empty($user->picture) ? $user->picture : 'https://ssl.gstatic.com/accounts/ui/avatar_2x.png' }}" alt="{{ Auth::user()->name  }}" />
                        <span class="hidden-xs">{{ Auth::user()->name  }}</span>
                    </a>
                    <ul class="dropdown-menu">
                        <li class="user-header">
                            <img class="img-circle" src="{{ !empty($user->picture) ? $user->picture : 'https://ssl.gstatic.com/accounts/ui/avatar_2x.png' }}" alt="{{ Auth::user()->name  }}" />
                            <p>{{ Auth::user()->name  }}</p>
                            @include('partials.admin.languages', ['languages' => Config::get('languages') ])
                        </li>
                        <li class="user-footer">
                            <div class="pull-left">
                                <a href="{{ route('admin.user.edit', ['id' => Auth::user()->id])  }}" class="btn btn-default btn-flat">{{ trans('admin.profile') }}</a>
                            </div>
                            <div class="pull-right">
                                <a href="{{ route('auth.logout') }}" class="btn btn-default btn-flat">{{ trans('auth.logout') }}</a>
                            </div>
                        </li>
                    </ul>
                </li>
            </ul>
        </div>
    </nav>
</header>
Artık kullanıcılar dili rahatlıkla değiştirebilir. Her eklediğiniz dil için resources lang içerisinde dil dosyası oluşturmanız lazım, ya da bunu da başka bir paketle çözebilirsiniz, ancak şimdilik ona pek değinmeyeceğim.
Laravel Dil Değiştir
Analitik kısmı için öncellikle gerekli dosyayı composer dosyasına dahil edip yüklememiz lazım.
composer require spatie/laravel-analytics
Sonrasında da config/app.php dosyasında providers dizisine bu eklentiyi eklemeniz lazım.
'Spatie\LaravelAnalytics\LaravelAnalyticsServiceProvider',
Aliases dizisine de alttaki kısmı ekleyebilirsiniz.
'LaravelAnalytics' => 'Spatie\LaravelAnalytics\LaravelAnalyticsFacade',
Son olarak da bu eklentinin config dosyasını da düzenleyebilmek için alttaki komut ile config dosyalarını oluşturuyoruz.
php artisan vendor:publish --provider="Spatie\LaravelAnalytics\LaravelAnalyticsServiceProvider"
config/laravel-analytics.php dosyasında bir tek şu kısmı değiştirin:
'certificatePath' => storage_path('analytics/'.env('ANALYTICS_FILENAME').'.p12'),
Bunu değiştirdiğiniz için, storage içerisinde analytics diye bir klasör oluşturun ve birazdan indireceğiniz dosyayı bu klasörde muhafaza edin, diğer değişkenleri env dosyası içerisinde değiştireceğiz. env dosyanızda alttaki değişkenleri tanımlayıp, değerlerini belirtmemiz lazım.
ANALYTICS_SITE_ID=ga:********
ANALYTICS_CLIENT_ID=**************************.apps.googleusercontent.com
ANALYTICS_SERVICE_EMAIL=**************************@developer.gserviceaccount.com
ANALYTICS_FILENAME=**************************
ANALYTICS_COUNTRY=Turkey
ANALYTICS_COUNTRY_CODE=TR
Yıldız ile belirttiğim kısımlar değişecek kısımlar, şimdi bunları nasıl elde edeceğinize bakalım. Öncelikle https://code.google.com/apis/console/ adresinden, yeni bir proje oluşturup, ona yeni bir isim verin, yeni proje oluşturmak için üstteki select a project kısmına basıp, create new project demelisiniz, daha sonra da soldaki menüdeki APIs & auth kısmından APIs'e tıklayıp, karşınıza gelen listeden Analytics API'ye tıklayıp, Enable API'ye basmanız lazım. Daha sonra tekrar soldaki menüden, Credentials'a tıklayıp, Create new Client ID'ye basıp karşınıza gelecek olan şeyde Server seçeneğini seçmeniz lazım. Bunun sonucunda size Client ID ve email adresini oluşturacak, işte bunlar, yukarıda da belirttiğim env dosyanızdaki kısımlardan client id ve service email yerine yazmanız gerekenler. Son olarak da p12 dosyasını da indirmek için yine aynı ekranda Generate new P12 key'e basmanız lazım, bu dosya da filename kısmı için gerekli olan dosya, onu storage/analytics içerisine taşıyıp, .p12 uzantısı olmadan, dosyanın adını env dosyanızda filename değişkenine atayın. Son olarak da hangi hesap ile ilgili işlem yapmak istiyorsanız, Google Analitik hesabınızda, istatistikleri oluşturmak istediğiniz profile ait Admin(yönetici) kısmında kullanıcı yönetiminden, okuma ve analiz izniyle biraz önce oluşturduğumuz credentials'a ait olan email adresini ekleyin. Son olarak da eklemiş olduğunuz bu siteye ait olan ga:******** şeklindeki profil kodunu env dosyanızda belirtin, bu kısım hem profile girdiğinizde yazıyor, hem de sitenizi takip ederken kullandığınız kodda da yazıyor, ama yine de bunu bulamadıysanız, kullanıcı eklediğiniz kısımda, tarayıcınızın linkinde p'den sonra gelen kısım olduğunu belirtebilirim. Country ve Country Code kısımları ise analikten veri çekerken kullanacağımız değişkenler, örneğin Türkiye illerinin herbiri için veri çekeceğiz, bunun için ülke adı gerekiyor, bu veriyi de grafik oluştururken kullanırken de ülke kodu gerekiyor, onun için de bunu da belirtmemiz lazım.

Bu kısımda problem yaşayacağınızı zannetmiyorum, dolayısıyla ilerlemeye devam edelim. Dashboard controllerımızı alttaki gibi oluşturalım. Neyin ne olduğunu açıklayacağım, ancak ufaktan bir göz gezdirmeniz açıklamayı okumadan önce yararlı olacaktır.
use Illuminate\Routing\Controller;
use Illuminate\Support\Collection;
use Carbon\Carbon;
use LaravelAnalytics;

class DashboardController extends Controller {

private $period;
private $limit;
private $start;
private $end;
private $country;

public function __construct()
{
$this->period = 30;
$this->limit = 16;
$this->end = Carbon::today();
$this->start = Carbon::today()->subDays($this->period);
$this->country = env('ANALYTICS_COUNTRY');
}

public function index()
{
$statistics = [
'keywords' => LaravelAnalytics::getTopKeywords($this->period, $this->limit),
'referrers' => LaravelAnalytics::getTopReferrers($this->period, $this->limit),
'browsers' => LaravelAnalytics::getTopBrowsers($this->period, $this->limit),
'pages' => LaravelAnalytics::getMostVisitedPages($this->period, $this->limit),
'users' => LaravelAnalytics::getActiveUsers(),
'total_visits' => $this->getTotalVisits(),
'landings' => $this->getLandings(),
'exits' => $this->getExits(),
'times' => $this->getTimeOnPages(),
'sources' => $this->getSources(),
'ops' => $this->getOperatingSystems(),
'browsers' => $this->getBrowsers(),
'countries' => $this->getCountries(),
'visits' => $this->getDailyVisits(),
'regions' => $this->getRegions(),
'averages' => $this->getAverages()
];
return view('admin.dashboard.index', compact('statistics'));
}

private function query($options=[],$metrics='ga:visits')
{
return LaravelAnalytics::performQuery($this->start,$this->end,$metrics,$options)->rows;
}

private function makeCollection($data,$fields,$offset=1)
{
if (is_null($data))
{
return new Collection([]);
}
else
{
foreach ($data as $pageRow)
{
$keywordData[] = [$fields[0] => $pageRow[0], $fields[1] => $pageRow[$offset]];
}
return new Collection($keywordData);
}
}

private function getTotalVisits()
{
$options = [
'dimensions' => 'ga:year',
];
return $this->query($options)[0][1];
}

private function getLandings()
{
$options = [
'dimensions' => 'ga:landingPagePath',
'sort' => '-ga:entrances',
'max-results' => $this->limit
];
$data = $this->query($options,'ga:entrances');
return $this->makeCollection($data, ['0' => 'path', '1' => 'visits']);
}

private function getExits()
{
$options = [
'dimensions' => 'ga:exitPagePath',
'sort' => '-ga:exits',
'max-results' => $this->limit
];
$data = $this->query($options,'ga:exits');
return $this->makeCollection($data, ['0' => 'path', '1' => 'visits']);
}

public function getTimeOnPages()
{
$options = [
'dimensions' => 'ga:pagePath',
'sort' => '-ga:timeOnPage',
'max-results' => $this->limit
];
$data = $this->query($options, 'ga:timeOnPage');
return $this->makeCollection($data, ['0' => 'path', '1' => 'time']);
}

private function getSources()
{
$options = [
'dimensions' => 'ga:source, ga:medium',
'sort' => '-ga:visits',
'max-results' => $this->limit
];
$data = $this->query($options);
return $this->makeCollection($data, ['0' => 'path', '1' => 'visits'],2);
}

public function getOperatingSystems()
{
$options = [
'dimensions' => 'ga:operatingSystem',
'sort' => '-ga:visits',
'max-results' => $this->limit
];
$data = $this->query($options);
return $this->makeCollection($data, ['0' => 'os', '1' => 'visits']);
}

public function getBrowsers()
{
$options = [
'dimensions' => 'ga:browser',
'sort' => '-ga:visits',
'max-results' => $this->limit
];
$data = $this->query($options);
return $this->makeCollection($data, ['0' => 'browser', '1' => 'visits']);
}

private function getCountries()
{
$options = [
'dimensions' => 'ga:country',
'sort' => '-ga:visits'
];
$array = $this->query($options);
$visits = [];
if(count($array))
{
foreach($array as $k => $v)
{
$visits[$k] = [$v[0], (int) $v[1]];
}
}
return json_encode($visits);
}

private function getDailyVisits()
{
$options = [
'dimensions' => 'ga:date'
];
$array = $this->query($options);
$visits = [];
foreach($array as $k => $v)
{
$visits[$k]['date'] = Carbon::parse($v['0'])->format('Y-m-d');
$visits[$k]['visits'] = $v['1'];
}
return json_encode($visits);
}

private function getRegions()
{
$options = [
'dimensions' => 'ga:country, ga:region',
'sort' => '-ga:visits',
'filters' => 'ga:country==' . $this->country . ''
];
$array = $this->query($options);
$visits = [];
if(count($array))
{
foreach($array as $k => $v)
{
$visits[$k] = [str_replace(" Province", "", $v[1]), (int) $v[2]];
}
}
return json_encode($visits);
}

private function getAverages()
{
$options = [
'dimensions' => 'ga:pagePath'
];
$array = $this->query($options,'ga:avgTimeOnPage , ga:entranceBounceRate, ga:pageviewsPerVisit');
$count = count($array);
$average = ['time' => 0, 'bounce' => 0, 'visit' => 0];
if(count($array))
{
foreach($array as $v)
{
$average['time'] += $v['1'];
$average['bounce'] += $v['2'];
$average['visit'] += $v['3'];
}
$average['time'] = ($average['time'] ? floor($average['time'] / $count) : 0);
$average['bounce'] = ($average['bounce'] ? round($average['bounce'] / $count, 2) : 0);
$average['visit'] = ($average['visit'] ? round($average['visit'] / $count, 2) : 0);
}
return $average;
}

}
Öncelikle değişkenlerden bahsedeyim, period dediğimiz şey, kaç güne ait verileri elde edeceğimize dair olan kısım, ben 30 gün yani bir ay olarak default atama yaptım, limit kısmı ise gelen veriler arasından ilk kaçını alacağımız iken, start ve end tarih başlangıcı ile sonu oluyor, country kısmını yukarıda açıklamıştım, env dosyasından okuyoruz zaten. ga şeklindeki şeyler de google analytic api değişkenleri, ben eklentinin getirdiklerinin yanında sık kullanılan diğerlerini de oluşturdum, eğer hala ihtiyacınız olan başka bir şey varsa, https://developers.google.com/analytics/devguides/reporting/core/v3/common-queries adresinden diğerlerine bakabilir, kendinize göre benim hazırladığım kısımlara bakarak düzenleme yapabilirsiniz. Burada Collection kısmını ilk defa görmüş olabilirsiniz, çok kabaca açıklayacak olursam, genellikle büyük veriler için kullanılan bir model, kendi içerisinde de sağladığı birkaç güzel şey var, örneğin bir collection içerisinde birden çok dizinin olduğunu ve her birinde belli bir anahtar olduğunu düşünün, bunları toplamak istiyorsanız, collectionın sum('key') fonksiyonunu kullanabilirsiniz ya da sıralama yapmak istiyorsunuz, yine burada da collection'ın kendi içerisindeki SortBy yöntemi gibi belirli yöntemleri kullanabilir ya da bazı filtrelemeleri kolaylıkla gerçekleştirebilirsiniz, açıkçası buna girip daha fazla detaya boğmak istemiyorum, burada neden collection döndürdüğüme gelirsek, aslında özel bir sebebi yok, eklentinin kendisi collection kullanmış ben de buna sadık kalayım istedim, belki siz de Collection'ı kurcalayıp, bununla ilgili bir şey yaparsanız.

Grafikleri oluşturmak için morris.js kütüphanesini kullanacağız, bunu bower dosyanıza eklemeniz lazım, daha sonrasında da gulp dosyanıza dahil etmeniz lazım. bower.json dosyasına "morris.js" : "~0.5.1" ekleyip Gulp dosyasında da css dizisinde 'morris.js/morris.css' satırını, javascript dizisinde 'raphael/raphael.js', 'morris.js/morris.js' bu iki satırı eklemeyi unutmayın, raphael'i unutursanız Morris grafiği çıkmaz ki onu kullanarak 30 günlük ziyaretçi grafiği çıkarıyoruz.

Tüm bu verileri nasıl kullanacağımıza da tekrardan dönersek, view dosyanızı alttaki gibi oluşturursanız benimle paralel gitmiş olursunuz, yalnız helper, css ya da dil kısımlarını yine paylaşmayacağım, Github'tan app/Helpers/AdminHelper.php, resources/assets/less/admin.less dosyaları ve resources/lang kısımlarına bakarsınız.
@extends('layouts.admin', ['no_boxes' => true])

@section('content')
    <section class="content">
        <div class="row">
            {!! dashboard_box("bg-aqua", "user-plus",
                trans('admin.fields.dashboard.total_visits'), $statistics['total_visits']) !!}
            {!! dashboard_box("bg-green", "user-times",
                trans('admin.fields.dashboard.bounce_rate'), $statistics['averages']['bounce'] . "%") !!}
            {!! dashboard_box("bg-yellow", "clock-o",
                trans('admin.fields.dashboard.average_time'), formatMilliseconds($statistics['averages']['time'])) !!}
            {!! dashboard_box("bg-red", "exchange",
                trans('admin.fields.dashboard.page_visits'),  $statistics['averages']['visit']) !!}
        </div>
        <div class="row">
            <div class="col-md-8">
                <div class="nav-tabs-custom">
                    <ul class="nav nav-tabs">
                        <li class="active">
                            <a href="#pages" data-toggle="tab">
                                <i class="fa fa-file"></i> {{ trans('admin.fields.dashboard.pages') }}
                            </a>
                        </li>
                        <li>
                            <a href="#keywords" data-toggle="tab">
                                <i class="fa fa-key"></i> {{ trans('admin.fields.dashboard.keywords') }}
                            </a>
                        </li>
                        <li>
                            <a href="#entrance-pages" data-toggle="tab">
                                <i class="fa fa-building-o"></i> {{  trans('admin.fields.dashboard.entrance_pages') }}
                            </a>
                        </li>
                        <li>
                            <a href="#exit-pages" data-toggle="tab">
                                <i class="fa fa-power-off"></i> {{ trans('admin.fields.dashboard.exit_pages') }}
                            </a>
                        </li>
                        <li>
                            <a href="#time-pages" data-toggle="tab">
                                <i class="fa fa-clock-o"></i> {{ trans('admin.fields.dashboard.time_pages') }}
                            </a>
                        </li>
                        <li>
                            <a href="#traffic-sources" data-toggle="tab">
                                <i class="fa fa-lightbulb-o"></i> {{ trans('admin.fields.dashboard.traffic_sources') }}
                            </a>
                        </li>
                        <li>
                            <a href="#browsers" data-toggle="tab">
                                <i class="fa fa-android"></i> {{ trans('admin.fields.dashboard.browsers') }}
                            </a>
                        </li>
                        <li>
                            <a href="#os" data-toggle="tab">
                                <i class="fa fa-linux"></i> {{ trans('admin.fields.dashboard.os') }}
                            </a>
                        </li>
                    </ul>
                    <div class="tab-content no-padding">
                        <div class="tab-pane statistic-tabs active" id="pages">
                            <ul class="item-list">
                                @foreach($statistics['pages'] as $p)
                                <li>
                                    {{ $p['url'] }}<span class="pull-right"> {{ $p['pageViews'] }}</span>
                                </li>
                                @endforeach
                            </ul>
                        </div>
                        <div class="tab-pane statistic-tabs" id="keywords">
                            <ul class="item-list">
                                @foreach($statistics['keywords'] as $p)
                                <li>
                                    {{ $p['keyword'] }}<span class="pull-right"> {{ $p['sessions'] }}</span>
                                </li>
                                @endforeach
                            </ul>
                        </div>
                        <div class="tab-pane statistic-tabs" id="entrance-pages">
                            <ul class="item-list">
                                @foreach($statistics['landings'] as $p)
                                <li>
                                    {{ $p['path'] }}<span class="pull-right"> {{ $p['visits'] }}</span>
                                </li>
                                @endforeach
                            </ul>
                        </div>
                        <div class="tab-pane statistic-tabs" id="exit-pages">
                            <ul class="item-list">
                                @foreach($statistics['exits'] as $p)
                                <li>
                                    {{ $p['path'] }}<span class="pull-right"> {{ $p['visits'] }}</span>
                                </li>
                                @endforeach
                            </ul>
                        </div>
                        <div class="tab-pane statistic-tabs" id="time-pages">
                            <ul class="item-list">
                                @foreach($statistics['times'] as $p)
                                <li>
                                    {{ $p['path'] }}<span class="pull-right"> {{ formatMilliseconds($p['time']) }}</span>
                                </li>
                                @endforeach
                            </ul>
                        </div>
                        <div class="tab-pane statistic-tabs" id="traffic-sources">
                            <ul class="item-list">
                                @foreach($statistics['sources'] as $p)
                                <li>
                                    {{ $p['path'] }}<span class="pull-right"> {{ $p['visits'] }}</span>
                                </li>
                                @endforeach
                            </ul>
                        </div>
                        <div class="tab-pane statistic-tabs" id="browsers">
                            <ul class="item-list">
                                @foreach($statistics['browsers'] as $p)
                                <li>
                                    {{ $p['browser'] }}<span class="pull-right"> {{ $p['visits'] }}</span>
                                </li>
                                @endforeach
                            </ul>
                        </div>
                        <div class="tab-pane statistic-tabs" id="os">
                            <ul class="item-list">
                                @foreach($statistics['ops'] as $p)
                                <li>
                                    {{ $p['os'] }}<span class="pull-right"> {{ $p['visits'] }}</span>
                                </li>
                                @endforeach
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
            <div class="col-md-4">
                <div class="box box-solid bg-dark-blue-gradient">
                    <div class="box-header">
                        <i class="fa fa-th"></i>
                        <h3 class="box-title">{{ trans('admin.fields.dashboard.visits') }}</h3>
                    </div>
                    <div class="box-body border-radius-none">
                        <div class="chart right-charts" id="visitor-chart"></div>
                    </div>
                </div>
                <div class="box box-solid bg-blue-special">
                    <div class="box-header">
                        <i class="fa fa-location-arrow"></i>
                        <h3 class="box-title">{{ trans('admin.fields.dashboard.region_visitors') }}</h3>
                    </div>
                    <div class="box-body">
                        <div id="region-map"></div>
                    </div>
                </div>
            </div>
        </div>

        <div class="row">
            <div class="col-xs-12">
                <div class="box bg-gray-white">
                    <div class="box-header">
                        <i class="fa fa-globe"></i>
                        <h3 class="box-title">{{ trans('admin.fields.dashboard.world_visitors') }}</h3>
                    </div>
                    <div class="box-body">
                        <div id="world-map"></div>
                    </div>
                </div>
            </div>
        </div>

    </section>


    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script>

        $(function() {
            Morris.Line({
                element: 'visitor-chart',
                data: {!! $statistics['visits'] !!},
                xkey: 'date',
                ykeys: ['visits'],
                labels: ['{{ trans('admin.fields.dashboard.visits') }}'],
                lineColors: ['#3B525E'],
                gridTextColor: ['#ebf4f9'],
                hideHover: 'auto',
                resize: true,
                redraw: true
            });
        });

        google.load("visualization", "1", {packages:["geochart"]});
        google.setOnLoadCallback(drawRegionsMap);
        google.setOnLoadCallback(drawLocalRegionsMap);

        function drawRegionsMap() {
            var data = new google.visualization.DataTable();
            data.addColumn('string', '{{ trans('admin.fields.dashboard.chart_country') }}');
            data.addColumn('number', '{{ trans('admin.fields.dashboard.chart_visitors') }}');
            data.addRows({!! $statistics['countries'] !!});
            var options = {
                colors:['#c8e0ed','#24536e'],
                backgroundColor: '#f9f9f9',
                datalessRegionColor: '#e5e5e5',
                legend:  {textStyle: {fontName: 'Source Sans Pro'}}
            };
            var chart = new google.visualization.GeoChart(document.getElementById('world-map'));
            chart.draw(data, options);
        }

        function drawLocalRegionsMap(){
            var data = new google.visualization.DataTable();
            data.addColumn('string', '{{ trans('admin.fields.dashboard.chart_region') }}');
            data.addColumn('number', '{{ trans('admin.fields.dashboard.chart_visitors') }}');
            data.addRows({!! $statistics['regions'] !!});
            var options = {
                colorAxis: {colors: ['#92c1dc', '#2d688a']},
                backgroundColor: '#55a9bc',
                legend:  {textStyle: {color: '#000', fontName: 'Source Sans Pro'}},
                displayMode: 'markers',
                region: '{{  env('ANALYTICS_COUNTRY_CODE') }}'
            };
            var chart = new google.visualization.GeoChart(document.getElementById('region-map'));
            chart.draw(data, options);
        }

    </script>
@endsection
Sonuç ise alttaki gibi oluyor.
Laravel Google Analitik API

Dünya ziyaretçi dağılımı da alttaki gibi:
Laravel Google Analitik Dünya Ziyaretçi Dağılımı
Tüm bunlara ve önceden yaptıklarımızın kopyası olduğu için tekrardan bahsetmediğim CMS'in diğer kısımlarına Github'tan ulaşmak için: https://github.com/ozdemirburak/laravel-5-simple-cms/tree/3821a25abaa7d1276a86963ce8b74ba0474828be

Bir sonraki kısımda seriyi bitiriyoruz.

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