#15: Expanded the hit tracking to include basic information like IP address, user agent, album/photo/user and the album/photo view being accessed. Added a config option to disable the visitor tracking, and a new tab called Analytics in the settings album. Also added links to Cookie Consent.

This commit is contained in:
Andy Heathershaw 2017-04-17 15:45:25 +01:00
parent def4a28b10
commit e93e4d2413
10 changed files with 121 additions and 27 deletions

View File

@ -92,6 +92,7 @@ class ConfigHelper
'app_name' => trans('global.app_name'), 'app_name' => trans('global.app_name'),
'date_format' => $this->allowedDateFormats()[0], 'date_format' => $this->allowedDateFormats()[0],
'default_album_view' => $this->allowedAlbumViews()[0], 'default_album_view' => $this->allowedAlbumViews()[0],
'enable_visitor_hits' => false,
'hotlink_protection' => false, 'hotlink_protection' => false,
'items_per_page' => 12, 'items_per_page' => 12,
'items_per_page_admin' => 10, 'items_per_page_admin' => 10,

View File

@ -62,6 +62,7 @@ class DefaultController extends Controller
]; ];
$checkboxKeys = [ $checkboxKeys = [
'allow_self_registration', 'allow_self_registration',
'enable_visitor_hits',
'hotlink_protection', 'hotlink_protection',
'recaptcha_enabled_registration', 'recaptcha_enabled_registration',
'remove_copyright', 'remove_copyright',

View File

@ -9,6 +9,7 @@ use App\Helpers\ConfigHelper;
use App\Helpers\DbHelper; use App\Helpers\DbHelper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests; use App\Http\Requests;
use App\VisitorHit;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -38,6 +39,18 @@ class AlbumController extends Controller
} }
} }
// Record the visit to the album
if (UserConfig::get('enable_visitor_hits'))
{
DB::transaction(function () use ($album, $request, $requestedView)
{
$album->hits++;
$album->save();
VisitorHit::fromRequest($request, $album->id, null, $requestedView);
});
}
if ($album->photos()->count() == 0) if ($album->photos()->count() == 0)
{ {
$requestedView = 'empty'; $requestedView = 'empty';
@ -57,11 +70,6 @@ class AlbumController extends Controller
->get(); ->get();
} }
DB::transaction(function () use ($album) {
$album->hits++;
$album->save();
});
return Theme::render(sprintf('gallery.album_%s', $requestedView), [ return Theme::render(sprintf('gallery.album_%s', $requestedView), [
'album' => $album, 'album' => $album,
'allowed_views' => $validViews, 'allowed_views' => $validViews,

View File

@ -7,8 +7,10 @@ use App\Facade\Theme;
use App\Facade\UserConfig; use App\Facade\UserConfig;
use App\Helpers\DbHelper; use App\Helpers\DbHelper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\VisitorHit;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class DefaultController extends Controller class DefaultController extends Controller
{ {
@ -17,6 +19,15 @@ class DefaultController extends Controller
$albums = DbHelper::getAlbumsForCurrentUser(); $albums = DbHelper::getAlbumsForCurrentUser();
$resetStatus = $request->session()->get('status'); $resetStatus = $request->session()->get('status');
// Record the visit to the index (no album or photo to record a hit against though)
if (UserConfig::get('enable_visitor_hits'))
{
DB::transaction(function () use ($request)
{
VisitorHit::fromRequest($request);
});
}
return Theme::render('gallery.index', [ return Theme::render('gallery.index', [
'albums' => $albums, 'albums' => $albums,
'info' => $request->session()->get('info'), 'info' => $request->session()->get('info'),

View File

@ -11,6 +11,7 @@ use app\Http\Controllers\Admin\AlbumController;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Middleware\VerifyCsrfToken; use App\Http\Middleware\VerifyCsrfToken;
use App\Photo; use App\Photo;
use App\VisitorHit;
use Guzzle\Http\Mimetypes; use Guzzle\Http\Mimetypes;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -58,13 +59,20 @@ class PhotoController extends Controller
$this->authorizeForUser($this->getUser(), 'photo.download_original', $photo); $this->authorizeForUser($this->getUser(), 'photo.download_original', $photo);
} }
$photoStream = $album->getAlbumSource()->fetchPhotoContent($photo, $thumbnail); // Record the visit to the photo
$mimeType = Mimetypes::getInstance()->fromFilename($photo->storage_file_name); if (UserConfig::get('enable_visitor_hits'))
{
DB::transaction(function () use ($photo) { DB::transaction(function () use ($album, $photo, $request, $thumbnail)
{
$photo->hits_download++; $photo->hits_download++;
$photo->save(); $photo->save();
VisitorHit::fromRequest($request, $album->id, $photo->id, (is_null($thumbnail) ? 'original' : $thumbnail));
}); });
}
$photoStream = $album->getAlbumSource()->fetchPhotoContent($photo, $thumbnail);
$mimeType = Mimetypes::getInstance()->fromFilename($photo->storage_file_name);
return response()->stream( return response()->stream(
function() use ($photoStream) function() use ($photoStream)
@ -101,10 +109,17 @@ class PhotoController extends Controller
$returnAlbumUrl = $referer; $returnAlbumUrl = $referer;
} }
DB::transaction(function () use ($photo) { // Record the visit to the photo
if (UserConfig::get('enable_visitor_hits'))
{
DB::transaction(function () use ($album, $photo, $request)
{
$photo->hits++; $photo->hits++;
$photo->save(); $photo->save();
VisitorHit::fromRequest($request, $album->id, $photo->id);
}); });
}
return Theme::render('gallery.photo', [ return Theme::render('gallery.photo', [
'album' => $album, 'album' => $album,

View File

@ -3,8 +3,38 @@
namespace App; namespace App;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class VisitorHit extends Model class VisitorHit extends Model
{ {
// /**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'album_id', 'photo_id', 'user_id', 'view_style', 'hit_at', 'ip_address', 'user_agent', 'session_identifier', 'uri'
];
public static function fromRequest(Request $request, $albumID = null, $photoID = null, $viewStyle = null)
{
$hit = new VisitorHit();
$hit->album_id = $albumID;
$hit->photo_id = $photoID;
$hit->view_style = $viewStyle;
$hit->hit_at = new \DateTime();
$hit->uri = $request->getRequestUri();
$hit->ip_address = substr($request->getClientIp(), 0, 255);
$hit->user_agent = substr($request->headers->get('User-Agent'), 0, 255);
$hit->session_identifier = $request->getSession()->getId();
if (!is_null(Auth::user()))
{
$hit->user_id = Auth::user()->id;
}
$hit->save();
return $hit;
}
} }

View File

@ -18,10 +18,12 @@ class CreateVisitorHitsTable extends Migration
$table->unsignedInteger('album_id')->nullable(); $table->unsignedInteger('album_id')->nullable();
$table->unsignedBigInteger('photo_id')->nullable(); $table->unsignedBigInteger('photo_id')->nullable();
$table->unsignedInteger('user_id')->nullable(); $table->unsignedInteger('user_id')->nullable();
$table->string('album_view')->nullable(); $table->string('view_style')->nullable();
$table->timestamp('hit_at'); $table->timestamp('hit_at');
$table->string('ip_address'); $table->string('ip_address');
$table->string('user_agent'); $table->string('user_agent');
$table->string('session_identifier');
$table->string('uri', 500);
$table->foreign('album_id') $table->foreign('album_id')
->references('id')->on('albums') ->references('id')->on('albums')

View File

@ -31,7 +31,7 @@ class AttachHitColumns extends Migration
public function down() public function down()
{ {
Schema::table('photos', function (Blueprint $table) { Schema::table('photos', function (Blueprint $table) {
$table->bigInteger('hits_download'); $table->dropColumn('hits_download');
$table->dropColumn('hits'); $table->dropColumn('hits');
}); });

View File

@ -123,6 +123,13 @@ return [
'security_text' => 'You can assign permissions on this album to either groups (recommended) or directly to users.', 'security_text' => 'You can assign permissions on this album to either groups (recommended) or directly to users.',
'security_users_heading' => 'User Permissions', 'security_users_heading' => 'User Permissions',
'settings' => [ 'settings' => [
'analytics_cookie_link_1' => 'Information about the EU Cookie Law directive',
'analytics_cookie_link_2' => 'Cookie Consent by Insites',
'analytics_cookie_warning_1' => 'If you are based in Europe and you enable visitor tracking, you will need to comply with the EU Cookie Law. The easiest way to do so is using a script like Cookie Consent by Insites.',
'analytics_cookie_warning_2' => 'You can add Javascript like that provided by Cookie Consent in the code box below.',
'analytics_enable_visitor_hits' => 'Enable built-in visitor hit tracking',
'analytics_enable_visitor_hits_description' => 'Visitor hits to the public gallery will be recorded in the Blue Twilight database, allowing for analysis such as the most popular album/photo.',
'analytics_tab' => 'Analytics',
'security_allow_self_registration' => 'Allow self-registration', 'security_allow_self_registration' => 'Allow self-registration',
'security_allow_self_registration_description' => 'With this option enabled, users can sign up for their own accounts. You can grant permissions to accounts to allow users to upload their own photos or manage yours.' 'security_allow_self_registration_description' => 'With this option enabled, users can sign up for their own accounts. You can grant permissions to accounts to allow users to upload their own photos or manage yours.'
], ],
@ -189,7 +196,7 @@ return [
'user_groups_tab' => 'Groups', 'user_groups_tab' => 'Groups',
'user_pending' => 'Pending activation', 'user_pending' => 'Pending activation',
'users_title' => 'User accounts', 'users_title' => 'User accounts',
'visitor_analytics_heading' => 'Visitor analytics', 'visitor_analytics_heading' => 'External analytics',
'visitor_analytics_p' => 'If you would like to analyse your visitor\'s activity using Google Analytics, Piwik or other real-time user monitoring services, please copy and paste the tracking code in the box below.', 'visitor_analytics_p' => 'If you would like to analyse your visitor\'s activity using Google Analytics, Piwik or other real-time user monitoring services, please copy and paste the tracking code in the box below.',
'visitor_analytics_p2' => 'This code will appear at the end of your site\'s body tag. Remember to include the "script" tags.' 'visitor_analytics_p2' => 'This code will appear at the end of your site\'s body tag. Remember to include the "script" tags.'
]; ];

View File

@ -22,6 +22,7 @@
@include(Theme::viewName('partials.tab'), ['active_tab' => 'general', 'tab_name' => 'general', 'tab_icon' => 'info-circle', 'tab_text' => trans('admin.settings_general_tab')]) @include(Theme::viewName('partials.tab'), ['active_tab' => 'general', 'tab_name' => 'general', 'tab_icon' => 'info-circle', 'tab_text' => trans('admin.settings_general_tab')])
@include(Theme::viewName('partials.tab'), ['active_tab' => 'general', 'tab_name' => 'email', 'tab_icon' => 'envelope', 'tab_text' => trans('admin.settings_email_tab')]) @include(Theme::viewName('partials.tab'), ['active_tab' => 'general', 'tab_name' => 'email', 'tab_icon' => 'envelope', 'tab_text' => trans('admin.settings_email_tab')])
@include(Theme::viewName('partials.tab'), ['active_tab' => 'general', 'tab_name' => 'security', 'tab_icon' => 'lock', 'tab_text' => trans('admin.settings_security_tab')]) @include(Theme::viewName('partials.tab'), ['active_tab' => 'general', 'tab_name' => 'security', 'tab_icon' => 'lock', 'tab_text' => trans('admin.settings_security_tab')])
@include(Theme::viewName('partials.tab'), ['active_tab' => 'general', 'tab_name' => 'analytics', 'tab_icon' => 'line-chart', 'tab_text' => trans('admin.settings.analytics_tab')])
</ul> </ul>
{{-- Tab panes --}} {{-- Tab panes --}}
@ -90,15 +91,6 @@
</label> </label>
</div> </div>
</fieldset> </fieldset>
<hr/>
<fieldset>
<legend>@lang('admin.visitor_analytics_heading')</legend>
<p>@lang('admin.visitor_analytics_p')</p>
<p>@lang('admin.visitor_analytics_p2')</p>
<textarea class="form-control" rows="10" name="analytics_code">{{ old('analytics_code', $config['analytics_code']) }}</textarea>
</fieldset>
</div> </div>
{{-- E-mail --}} {{-- E-mail --}}
@ -269,6 +261,33 @@
</div> </div>
</fieldset> </fieldset>
</div> </div>
{{-- Analytics --}}
<div role="tabpanel" class="tab-pane" id="analytics-tab">
<div class="alert alert-warning">
<p>@lang('admin.settings.analytics_cookie_warning_1')</p>
<p>@lang('admin.settings.analytics_cookie_warning_2')</p>
<p class="mb-0">
<a href="https://www.cookielaw.org/the-cookie-law/" target="_blank">@lang('admin.settings.analytics_cookie_link_1')</a><br/>
<a href="https://cookieconsent.insites.com/" target="_blank">@lang('admin.settings.analytics_cookie_link_2')</a>
</p>
</div>
<div class="checkbox mt-4">
<label>
<input type="checkbox" name="enable_visitor_hits" @if (old('enable_visitor_hits', UserConfig::get('enable_visitor_hits')))checked="checked"@endif>
<strong>@lang('admin.settings.analytics_enable_visitor_hits')</strong><br/>
@lang('admin.settings.analytics_enable_visitor_hits_description')
</label>
</div>
<hr/>
<fieldset>
<legend>@lang('admin.visitor_analytics_heading')</legend>
<p>@lang('admin.visitor_analytics_p')</p>
<p>@lang('admin.visitor_analytics_p2')</p>
<textarea class="form-control" rows="10" name="analytics_code">{{ old('analytics_code', $config['analytics_code']) }}</textarea>
</fieldset>
</div>
</div> </div>
<div class="pull-right" style="margin-top: 15px;"> <div class="pull-right" style="margin-top: 15px;">