Using middleware in laravel to determine page visits - laravel

Public function handle($request, Closure $next)
{
$count = 0;
$time = now();
$visitor = "Someone";
if (request("visitor") == "Someone" && $count == 0) {
$count++;
\Mail::raw('You have a new page visit at ' . $time ' ' . $count . '', function ($note) {
$note->to('manager#mail.com');
});
return $next($request);
} else {
return abort (404);
}
This code is just a simple middleware to check a page visit in laravel. It‘a working fine and I’m receiving the email via log. However, the count is just 1 on every page hit. It doesn’t increase to 2, 3......
I really need help to be able to count the page hits regardless of who or what visits, even regardless of IP. Plus how do I fetch the counts to a view and also save it in the database. I really need help as I’m still a learner. Please help!

I did something similar once to this, but for the system's issues encountered by users. Since you want to measure traffic (using the database), the scenario is kinda similar. Let's say you have a table called traffic, so the logic in regards that traffic would be placed in the handle function at the middleware as you have but measuring traffic like this:
public function handle($request, Closure $next)
{
$time = now(); // #Todo: check timezone in the config/app.php
$visitor = $request->ip();
$traffic = Traffic::where('visitor', $visitor)->first();
if ($traffic) {
// Resident visitor
$traffic->visits++;
$traffic->update();
} else {
// New visitor
$traffic = new Traffic(['visitor' => $visitor]);
$traffic->save();
$totalTraffic = Traffic::all()->sum('visits');
$totalVisitors = Traffic::all()->count();
// Email notification
\Mail::raw("There is a new visitor at {$time} from ip {$visitor}.\nIn total there are {$totalVisitors} visitors, and over all you have {$totalTraffic} visits.", function ($note) {
$note->to('manager#mail.com');
});
}
return $next($request);
}
Keep in mind you can improve this solution by moving the traffic logic into the Traffic model by implementing a boot function inside the model, that would look cleaner. More info https://laravel.com/docs/7.x/eloquent#observers
Sorry I got late, hope it helps though...
Solution 2:
You could also create a migration or run the following statement in your database, setting a default column for visits as 1, since it won't change every time a new one is created.
ALTER TABLE traffic ALTER visits SET DEFAULT 1;
Traffic Model:
class Traffic extends Model
{
protected $fillable = ['visitor' , 'visits'];
protected static function boot()
{
parent::boot();
static::saving(function ($traffic) {
if ($traffic->visits) {
$traffic->visits++;
}
});
}
}
TrafficMiddleware:
public function handle($request, Closure $next)
{
$time = now();
$visitor = $request->ip();
$traffic = Traffic::firstOrCreate(['visitor' => $visitor]);
$traffic->save();
//Email notification
$totalTraffic = Traffic::all()->sum('visits');
$totalVisitors = Traffic::all()->count();
\Mail::raw("New visit at {$time} from {$visitor}. You have over all {$totalTraffic} visits from {$totalVisitors} visitors.", function ($note) {
$note->to('manager#mail.com');
});
return $next($request);
}

First, if you redeclare $count every time, it will never increase.
Second, you're checking if $count is equal to 0 before sending the message, so even if it did increase, you would not send the mail next time.
One way to persist the count value would be using cache.
public function handle($request, Closure $next)
{
// retrieve cached value for 'page_visits'. If it doesn't exist, return 0 instead
$count = cache('page_visits', 1);
$visitor = request()->ip();
$time = now();
\Mail::raw("You have a new page visit at {$time->isoFormat('LLLL')} from {$visitor}. Total: {$count}", function ($note) {
$note->to('manager#mail.com');
});
cache()->put('page_visits', ++$count);
return $next($request);
}

Related

how to build a Laravel command that loops through users is sends unique emails to each user

I have a command that I'll run nightly using the Forge scheduler. The command simply loops through and sends emails to each user who qualifies for one.
COMMAND:
public function handle()
{
//Get all users
$users = User::all();
$data = [];
$renewalEmail = '';
foreach($users as $user)
{
//Check each users next_biling_date and see if is less than 72 hours from now, if so send reminder email
$nextBillingDate = ($user->hasSubscription())? $user->getSubscriptionData()->current_period_end : false;
$now = strtotime(now());
$threeDaysFromNow = 60*60*24*3;
//($user->hasSubscription())? $this->updateNextBillingDate($user) : false;//TODO: remove after working: follow up
if($user->hasSubscription() && $nextBillingDate-$now<=$threeDaysFromNow)
{
$data = [
'name' => $user->name,
'billingdate' => date('n/j/Y',strtotime($user->next_billing_date)),
];
// Log::info(print_r($data,true));
$renewalEmail = Mail::to('my#email.com')->send(new SubscriptionRenewalReminder($data));
// Log::info(print_r($renewalEmail,true));
}
}
return true;
}
My Mailable is pretty straight forward:
public function __construct($data)
{
$this->data = $data;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
Log::info('SubscriptionRenewalReminder Email build() called: ');
$firstName = explode(' ',$this->data['name'])[0];
$billingDate = $this->data['billingdate'];
Log::info('firstname: '.$firstName);
Log::info('billingDate: '.$billingDate);
return $this->view('emails.subscription-renewal-reminder')
->from('my#email.com', 'Project')
->subject('Project Subscription Is About To Renew')
->withName($firstName)
->withBillingdate($billingDate);
}
All of my Log::info's dump out the right information. I have 3 test users who all qualify to get the email.
In my testing, all three emails show the first user's name and billing date. Instead of unique emails, they are all identical.
I may move this into a queue but on a small set of users this should work fine. TIA

How do I loop over json data

I am using FlutterWave as my payment gateway, I want to loop through a transaction whenever a customer finish making payment and check for the chargeamount which the user paid. the return data is in json format. Here is my code.
public function callback(Request $request)
{
$resp = $request->resp;
$body = json_decode($resp, true);
$txRef = $body['data']['data']['txRef'];
$data = Rave::verifyTransaction($txRef);
dd($data);
return redirect()->route('success');
}
I want to loop and check for the chargedamount but I couldn't. Thanks for your help
Seems like you are dumping an object. I don't think you need a loop to access the data you need. According to your image dump, to access the chargedamount attribute:
dd($data->data->chargedamount);
The data name here repeats, perhaps $payload in this case, its a better name.
public function callback(Request $request)
{
$resp = $request->resp;
$body = json_decode($resp, true);
$txRef = $body['data']['data']['txRef'];
$payload = Rave::verifyTransaction($txRef);
dd($payload->data->chargedamount);
return redirect()->route('success');
}
Some clean up:
public function callback(Request $request)
{
$json = json_decode($request->resp);
$payload = Rave::verifyTransaction($json->data->data->txRef);
dd($payload->data->chargedamount);
return redirect()->route('success');
}
Again that data->data repeating stuff, but in this case, nothing we can do, its in the API.

What's the solution for adding unique slug to prevent duplicate entry in creating?

i'm working on a project for my homework which i'm trying to make it work as a spa, but I've got some problem in making slug for posts.
in the tutorial which i've followed, instructor used this to make a slug from title :
protected static function boot()
{
parent::boot();
static::creating(function ($course){
$course->slug = str_slug($course->name);
});
}
now, if i make this table unique, which this is what i wanna do. how should i prevent app from giving me duplicate entry? or how can i add something to slug, like a number, Every time i get duplicate entry?
if i make a post with This Post name twice, second time, i get duplicate Error.
In my opinion your selected answer is not getting close to something efficient. In large applications the 2 random strings can be overwritten in a short time and then you will have huge issues (code and DB).
A safer approach is to build a service and use that when you save the slug in the DB. Or course this is not 100% perfect but definitely is better then to increment 2 random strings. That, by the way, can also affect the SEO part of the app.
Below you can find my example:
The Model
public static function boot()
{
parent::boot();
static::saving(function ($model) {
$slug = new Slug();
$model->slug = $slug->createSlug($model->title);
});
}
The Service
<?php
namespace App\Services;
use App\Job;
class Slug
{
/**
* #param $title
* #param int $id
* #return string
* #throws \Exception
*/
public function createSlug($title, $id = 0)
{
// Normalize the title
$slug = str_slug($title);
// Get any that could possibly be related.
// This cuts the queries down by doing it once.
$allSlugs = $this->getRelatedSlugs($slug, $id);
// If we haven't used it before then we are all good.
if (!$allSlugs->contains('slug', $slug)) {
return $slug;
}
// Just append numbers like a savage until we find not used.
for ($i = 1; $i <= 100; $i++) {
$newSlug = $slug . '-' . $i;
if (!$allSlugs->contains('slug', $newSlug)) {
return $newSlug;
}
}
throw new \Exception('Can not create a unique slug');
}
protected function getRelatedSlugs($slug, $id = 0)
{
return Model::select('slug')->where('slug', 'like', $slug . '%')
->where('id', '<>', $id)
->get();
}
}
You could use inbuilt Str class, and create some random strings in your Post slug. Example:
static::creating(function ($course){
$course->slug = str_slug($course->name . Str::random( 2 ));
});
This will add 2 random strings on each slug you create, which will ensure there are no duplicates. You can find more about Str class here.
you can use laravel Inbuilt Helper, follow below link.
https://laravel.com/docs/5.8/helpers#method-str-slug
$data = 'My Data'
$slug = Str::slug($data, '-');
dd($slug);
add this in your controller
use Illuminate\Support\Str;

Filtering one-to-many connections in entity

I have an entity User which has a one-to-many connection to questions. Questions has a one to many connection to answers. The question and answer entity each has a property called state.
In generally after execute a GET request API Platform returns all users, with all questions including all answers. That works fine!
Now I would like to implement a get request that returns the user with all questions that has a specific state (e.g. "X"). The questions should only include the answers with the same state ("X").
I used the filter function (to filter the whole not necessary data)
Therefore I generated a controller called GetUserObjectAction which the following function
public function __invoke(PaginatorInterface $data, Request $request): PaginatorInterface
{
$repo = $this->managerRegistry->getRepository(Question::class);
foreach ($data as $value) {
$q = $value->getQuestions()->filter(function($q1) {
if($q1->getState() === 'a') {
$q1->values = $q1->values->filter(function($a) {
return $a->getState() === 'a';
});
return true;
} else {
return false;
}
return ;
});
$value->setQuestions($int);
}
return $data;
}
Is there a better way to implement it?
Thanks
You chose the right way because you did not tell us how are your data managed (Doctrine ORM/ODM, custom data providers) we cannot tell you more. But I suggest you are using API Platform defaults, so you can filter your data before fetching them in your QuestionRepository and omit to iterate over data.
Here is example:
QuestionRepository
...
public function findWithAnswersByState(string $state): array
{
$qb = $this->createQueryBuilder('q')
->join('q.answers', 'a')
->andWhere('q.state = :state')
->andWhere('a.state = :state')
->setParameter('state', $state);
return $qb->getQuery()->getResult();
}
...
Controller:
...
public function __invoke(PaginatorInterface $data, Request $request): PaginatorInterface
{
$repo = $this->managerRegistry->getRepository(Question::class);
return $repo->findWithAnswersByState('a');
}
...

Laravel dynamic scope only works first time

I'm missing something with how the global scopes work in Laravel 5.5.
In my controller, index , I am passing filters into a getter:
public function index(SaleFilters $filters)
{
return new SaleCollection($this->getSales($filters));
}
getSales:
protected function getSales(SaleFilters $filters)
{
$sales = Sale::with('office')->filter($filters);
return $sales->paginate(50);
}
protected function range($range)
{
$dates = explode(" ", $range);
if (count($dates) == 2) {
$this->builder = Sale::with(['office', 'staff'])
->where('sale_date', '>=', $dates[0])
->where('sale_date', '<', $dates[1])
->orderBy('sale_date', 'desc');
return $this->builder;
}
return false;
}
I have a scope setup in the sale model as such, which I would have thought would apply to the above filter automatically ? If not, do I have to reapply the same scope, duplicating the scope code in the filter ?
protected static function boot()
{
parent::boot();
$user = Auth::user();
if (($user) && ($user['office_id'])) {
return Sale::ofOffice($user['office_id'])->get();
}
}
public function scopeOfOffice($query, $office)
{
return $query->where('office_id', $office);
}
So basically, IF the user has an office_id applied, it should apply the ofOffice scope, therefore it should only ever return the sales that apply to that office_id.
Basically it works on page load via axios GET request
Route::get('/sales', 'SalesController#index')->middleware('auth:api');
axios
.get('api/sales/?range=" + this.rangeFilter)
rangeFilter is basically a start and end date passed into the above filter query.
Can anyone shed some light on how the scopes really work or if anything is obvious as to why its not always working? As I said, it works on page load where I provide default values for the rangeFilter, however when I change those days and it refetches via the same axios call, it seems to not be applying the scope, and I get ALL results instead of where office_id = 'x'
As far as i'm concerned, the range filter above would be executing on the first page load as well, so not sure why it would apply there, and not afterwards.
You should not mix the use of dynamic scope with global one. Also, static boot function does not expect a return. In order to use dynamic scope, you need to call it every time you need it. Hence, the name is dynamic. Query applied is not always executed by default. There so,
protected function getSales(SaleFilters $filters)
{
$sales = Sale::ofOffice($anyOfficeHere)->with('office')->filter($filters);
return $sales->paginate(50);
}
To suit your existing code, you may want to add an if statement in your model. Then call the scope function without argument.
public function scopeOfOffice($q)
{
if (($user = \Auth::user()) && ($office = $user->office_id)) {
$q->where('office_id', $office);
}
}
// Your controller
protected function getSales(SaleFilters $filters)
{
$sales = Sale::ofOffice()->with('office')->filter($filters);
return $sales->paginate(50);
}
If you feel so much cumbersome to type ofOffice repeatedly. A global scope is the way to go. Within your model static boot function, you can also apply anonymous function if you feel creating a separated class kinda bloat your apps.
protected static function boot()
{
parent::boot();
static::addGlobalScope('officeOrWhatNot', function ($q) {
if (($user = \Auth::user()) && ($office = $user->office_id)) {
$q->where('office_id', $office);
}
});
}
// Your controller. No more `ofOffice`, it's automatically applied.
protected function getSales(SaleFilters $filters)
{
$sales = Sale::with('office')->filter($filters);
return $sales->paginate(50);
}

Resources