jesse_orange_newable

jesse_orange_newable

Member Since 11 Months Ago

Experience Points
5,010
Total
Experience

4,990 experience to go until the next level!

In case you were wondering, you earn Laracasts experience when you:

  • Complete a lesson — 100pts
  • Create a forum thread — 50pts
  • Reply to a thread — 10pts
  • Leave a reply that is liked — 50pts
  • Receive a "Best Reply" award — 500pts
Lessons Completed
37
Lessons
Completed
Best Reply Awards
0
Best Reply
Awards
  • start-engines Created with Sketch.

    Start Your Engines

    Earned once you have completed your first Laracasts lesson.

  • first-thousand Created with Sketch.

    First Thousand

    Earned once you have earned your first 1000 experience points.

  • 1-year Created with Sketch.

    One Year Member

    Earned when you have been with Laracasts for 1 year.

  • 2-years Created with Sketch.

    Two Year Member

    Earned when you have been with Laracasts for 2 years.

  • 3-years Created with Sketch.

    Three Year Member

    Earned when you have been with Laracasts for 3 years.

  • 4-years Created with Sketch.

    Four Year Member

    Earned when you have been with Laracasts for 4 years.

  • 5-years Created with Sketch.

    Five Year Member

    Earned when you have been with Laracasts for 5 years.

  • school-session Created with Sketch.

    School In Session

    Earned when at least one Laracasts series has been fully completed.

  • welcome-newcomer Created with Sketch.

    Welcome To The Community

    Earned after your first post on the Laracasts forum.

  • full-time-student Created with Sketch.

    Full Time Learner

    Earned once 100 Laracasts lessons have been completed.

  • pay-it-forward Created with Sketch.

    Pay It Forward

    Earned once you receive your first "Best Reply" award on the Laracasts forum.

  • subscriber-token Created with Sketch.

    Subscriber

    Earned if you are a paying Laracasts subscriber.

  • lifer-token Created with Sketch.

    Lifer

    Earned if you have a lifetime subscription to Laracasts.

  • lara-evanghelist Created with Sketch.

    Laracasts Evangelist

    Earned if you share a link to Laracasts on social media. Please email [email protected] with your username and post URL to be awarded this badge.

  • chatty-cathy Created with Sketch.

    Chatty Cathy

    Earned once you have achieved 500 forum replies.

  • lara-veteran Created with Sketch.

    Laracasts Veteran

    Earned once your experience points passes 100,000.

  • 10k-strong Created with Sketch.

    Ten Thousand Strong

    Earned once your experience points hits 10,000.

  • lara-master Created with Sketch.

    Laracasts Master

    Earned once 1000 Laracasts lessons have been completed.

  • laracasts-tutor Created with Sketch.

    Laracasts Tutor

    Earned once your "Best Reply" award count is 100 or more.

  • laracasts-sensei Created with Sketch.

    Laracasts Sensei

    Earned once your experience points passes 1 million.

  • top-50 Created with Sketch.

    Top 50

    Earned once your experience points ranks in the top 50 of all Laracasts users.

Level 2
5,010 XP
Jan
27
12 hours ago
Activity icon

Replied to Creating A Simple Activity Logger

My only thought is that I may not always have a model instance.

Jan
26
1 day ago
Activity icon

Replied to Creating A Simple Activity Logger

So in that sense I'd have a pivot table between user and activity?

Jan
24
3 days ago
Activity icon

Replied to Creating A Simple Activity Logger

Also, because the model can be null, should I do this?


    public function handle($event)
    {
        $event->user->activities()->create([
            'type' => $event->type,
            'description' => $event->description,
            'model_name' => get_class($event->model),
            'model_id' => $event->model ? $event->model->id : null,
        ]);
    }

Activity icon

Replied to Creating A Simple Activity Logger

Hi,

Do you think this approach is a little rudimentary? I just don't like over complicating for the sake of it.

Activity icon

Started a new Conversation Creating A Simple Activity Logger

In my application I need to be able to log a user activity in a table, so these can be viewed later.

Starting off, I made a migration:


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUserActivitiesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('user_activities', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->uuid('user_id')->index();
            $table->string('type');
            $table->longText('description');
            $table->unsignedBigInteger('model_id')->nullable();
            $table->string('model_type')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('user_activities');
    }
}

Then I made a model:


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class UserActivity extends Model
{
    /**
     * The table this model is related to
     *
     * @var string
     */
    protected $table = 'user_activities';

    /**
     * The attributes that are mass assignable
     */
    protected $fillable = [
        'user_id', 'type', 'description', 'model_type', 'model_id',
    ];

    /**
     * Get the user associated with this activity
     */
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id');
    }
}

Then I made an event:


<?php

namespace App\Events;

use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class UserActivityOccurred
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * The user performing the activity
     *
     * @var User
     */
    public $user;

    /**
     * The type of activity that was performed
     *
     * @var string
     */
    public $type;

    /**
     * The description off the activity
     *
     * @var string
     */
    public $description;

    /**
     * The model involved
     *
     * @var string
     */
    public $model;

    /**
     * Creates a new event instance
     *
     * @param User   $user
     * @param string $type
     * @param string $description
     * @param mixed  $model
     */
    public function __construct($user, $type, $description, $model = null)
    {
        $this->user = $user;
        $this->type = $type;
        $this->description = $description;
        $this->model = $model;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

Then a listener:


<?php

namespace App\Listeners;

class LogUserActivityOccurred
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
    }

    /**
     * Handle the event.
     *
     * @param  object $event
     * @return void
     */
    public function handle($event)
    {
        $event->user->activities()->create([
            'type' => $event->type,
            'description' => $event->description,
            'model_name' => '',
            'model_id' => '',
        ]);
    }
}

So, I then like to trigger an event wherever I want like so:


event(new UserActivityOccurred(auth->user()->id, 'Activity type',  'Description of activity', $model));

The part that baffles me, is the $model part. If I pass in $post, how could I get the namespace and ID?

This is because in future I may need to link activities to models.

I have seen many a package for this but I was curios.

Dec
02
1 month ago
Activity icon

Started a new Conversation Testing Within Model Observers

In my recent projects I've been experimenting with Observers and they seem like a great way to do things tied to model events.

In one model called KnowledgeBaseArticle I send an email to a particular group of users if the status is changed.


/**
 * Handle the knowledge base article "updated" event.
 *
 * @param  \App\KnowledgeBaseArticle $knowledgeBaseArticle
 * @return void
 */
public function updated(KnowledgeBaseArticle $knowledgeBaseArticle)
{
    Log::info("A knowledge base article was updated {$knowledgeBaseArticle->title} in knowledge base {$knowledgeBaseArticle->knowledge_base->name}");

    if ($knowledgeBaseArticle->isDirty('status') && $knowledgeBaseArticle->is_published) {
        // Send a mailable to all DIT members about the new DIT article
        if ($knowledgeBaseArticle->knowledge_base_slug === 'dit') {
            User::role('dit member')->get()
                ->each(function ($user) use ($knowledgeBaseArticle) {
                    Mail::to($user)
                        ->queue(new KnowledgeBaseArticleCreatedNotification($knowledgeBaseArticle));
                });
        }
    }
}

Another example if if an Article needs approval


/**
 * Handle the article "created" event.
 *
 * @param  \App\Article  $article
 * @return void
 */
public function created(Article $article)
{
    Log::notice("A new article was created: {$article->title}");

    // If the article needs publishing, send a notification
    if ($article->needs_approval) {
        // Get users with appropriate roles
        User::role(['admin', 'news administrator'])->get()
            ->each(function ($user) use ($article) {
                $user->notify((new ArticleCreated($article))->delay(now()->addSeconds(10)));
            });
    }
}

Another is generating a slug


    /**
     * Handle the article "creating" event.
     *
     * @param  \App\Article $article
     * @return void
     */
    public function creating(Article $article)
    {
        $article->slug = Str::slug($article->title);

    }

So, there are three examples where I've used observers to do something.

My question is: how would I test this?

I'd want to have something like so:


    /** @test */
    public function a_published_knowledge_base_article_sends_an_email()
    {
        // put request to knowledge-base.article.update
        // Data - status -> Published
        // Criteria met in updating event
        // Email is sent to queue
        // Assert test passes
    }


/** @test */
public function a_published_knowledge_base_article_sends_an_email()
{
    $this->withoutExceptionHandling();

    Mail::fake();

    $article = factory(KnowledgeBaseArticle::class)->create(['status' => 'Unpublished']);

    $attributes = [
        'title' => 'An update on Brexit',
        'status' => 'Published'
    ];

    $this->actingAs($this->admin)
            ->put(route('knowledge-base.article.update', $article), $attributes)
            ->assertStatus(302);

    Mail::assertQueued(KnowledgeBaseArticleCreatedNotification::class);
}

Nov
14
2 months ago
Activity icon

Replied to Working With Email Aliases

Hi again


"select * from `users` where (`email` in (?, ?, ?, ?, ?, ?, ?) and `email` = ? or exists (select * from `user_email_aliases` where `users`.`username` = `user_email_aliases`.`user_username` and `email` = ?)) and `users`.`deleted_at` is null"


        $user = User::whereIn('email', $this->email_white_list)
            ->where('email', $request->get('email'))
            ->orWhereHas('aliases', function ($query) use ($email) {
                $query->where('email', $email);
            })->toSql();

Activity icon

Replied to Working With Email Aliases

Hi,

That's what I was thinking, I just wondered whether you could search for an email in multiple tables using Eloquent? Rather than the two seperate queries I currently have.

Nov
12
2 months ago
Activity icon

Started a new Conversation Working With Email Aliases

In my application I have been experimenting with email aliases, so a user could be recognised by their main email, or, any number of aliases.

I have a model called UserEmailAlias and a model called User, there is a one to many between the two.

I have also made some helper functions:


    /**
     * A wrapper function to assign email aliases
     *
     * @param string $email
     * @return void
     */
    public function assignEmailAlias(string $email)
    {
        $this->aliases()->create(['email' => $email]);

        return $this;
    }

    /**
     * A wrapper function to remove an email alias from a user
     *
     * @param string $email
     * @return void
     */
    public function removeEmailAlias(string $email)
    {
        $this->aliases()->where('email', $email)->delete();

        return $this;
    }

Below is the source code for a controller that manages simplistic login tokens.


<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\User;
use App\UserEmailAlias;
use App\VerificationToken;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Log;
use Mail;
use App\Mail\VerifyEmail;
use App\Observers\UserEmailAliasObserver;

class TokenVerificationController extends Controller
{
    /**
     * Instantiate a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->email_white_list = [
            '[email protected]',
            '[email protected]',
            '[email protected]',
            '[email protected]',
            '[email protected]',
            '[email protected]',
            '[email protected]',
        ];
    }

    /**
     * Display the starting form for the token authentication process
     */
    public function showVerificationRequestForm()
    {
        return view('auth.verify');
    }

    /**
     * Send the verification email to a valid user
     *
     * @return void
     */
    public function sendVerificationEmail(Request $request)
    {
        $email = $request->get('email');

        $user = User::whereIn('email', $this->email_white_list)->where('email', $request->get('email'))->first();

        if ($user) {
            Mail::to($user->email)->queue(new VerifyEmail($this->generateVerificationToken($user->email), $user));
        } else if ($this->isAlias($email)) {
            Mail::to($email)->queue(new VerifyEmail($this->generateVerificationToken($this->getUserFromAlias($email)->email), $this->getUserFromAlias($email)));
        }

        return back()->withSuccess('If everything worked, you\'ll recieve an email');
    }

    /**
     * Verify the token sent back in the response
     *
     * @param string $token
     * @return void
     */
    public function verify(string $token)
    {
        $token = VerificationToken::where('token', $token)->first();

        if ($token) {
            Auth::login(User::where('email', $token->email)->first(), true);

            $token->destroy();
        } else {
            abort(403, "There was an issue verifying your email");
        }
    }



    /**
     * Determine whether an alias exists for this email address 
     *
     * @param string $email
     * @return void
     */
    private function isAlias(string $email)
    {
        return count(UserEmailAlias::where('email', $email)->get()) > 0 ? true : false;
    }

    /**
     * Get the user from the alias
     *
     * @param string $email
     * @return User 
     */
    private function getUserFromAlias(string $email)
    {
        return UserEmailAlias::where('email', $email)->first()->user;
    }

    /**
     * Generate a token to use for authentication
     *
     * @param string $email
     * @return string $token
     */
    private function generateVerificationToken(string $email)
    {
        $verification_token = VerificationToken::create([
            'email' => $email,
            'token' => bin2hex(openssl_random_pseudo_bytes(30)),
        ]);

        return $verification_token->token;
    }
}

Is there a better way to determine whether the email entered is an alias?

I'm changing the $to address because the email either needs to go to the alias given or the actual email address.

Is it possible just to perform and join and locate the entered email address?

Nov
07
2 months ago
Activity icon

Replied to Differentiating Between Notifiable And User Instance

In the UserDeleted notification UserDeletedEmail is defined as:


use App\Mail\UserDeleted as UserDeletedEmail;

Activity icon

Started a new Conversation Differentiating Between Notifiable And User Instance

In my Laravel application I have a Notification that sends a Mailable when a User is deleted.

The Notification class:

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use App\User;
use App\Mail\UserDeleted as UserDeletedEmail;

class UserDeleted extends Notification implements ShouldQueue
{
    use Queueable;

    /**
     * The user instance being passed to the notification
     *
     * @var User $user
     */
    protected $user;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

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

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        return (new UserDeletedEmail($notifiable, $this->user));
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            'user_id' => $this->user['id'],
            'user_username' => $this->user['username'],
            'user_email' => $this->user['email'],
            'user_full_name' => $this->user['full_name'],

        ];
    }
}

In this case $notifiable is an instance of User but soo is $user as this is the user that has been deleted.

The Mailable looks like this:

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Spatie\Permission\Models\Role;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\User;


class UserDeleted extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * The order instance.
     *
     * @var User
     */
    public $user;

    /** 
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this
            ->to($this->user->email)
            ->subject("{$this->user->full_name} been deleted from the Citibase Intranet")
            ->markdown('mail.user-deleted');
    }
}

The issue is, as they're both instances of User I'm effectively passing the wrong instance in the subject line.

Everything is triggered through the UserObserver.

/**
 * Handle the user "deleted" event.
 *
 * @param  \App\User $user
 * @return void
 */
public function deleted(User $user)
{
    Log::notice("A user has been deleted: {$user->full_name} by " . optional(auth()->user())->full_name ?? "System");

    User::role(['admin'])->get()
        ->each->notify(
            (new UserDeleted($user))->delay(now()->addSeconds(10))
        );
}
Nov
05
2 months ago
Activity icon

Replied to Search With Location Radius

@tisuchi did you figure out the maths for this before writing the query or is there a universal formula?

Nov
01
2 months ago
Activity icon

Replied to Query Returning Unexpected Results

Is that when the field we're checking is on the left?

Activity icon

Replied to Query Returning Unexpected Results

*Facepalm - is there an easy way to remember older and newer with dates, I always get these confused.

Activity icon

Started a new Conversation Query Returning Unexpected Results

In my application I have a Spotlight model.

I run this query:


Spotlight::newerThan(1)->published()->inRandomOrder()->take(2)->get();

Here are the query scopes.


/**
 * Scope an article by whether or not it's published
 * 
 */
public function scopePublished($query)
{
    return $query->where('status', 'published');
}

/**
 * Only retrieve Spotlights newer than the given increment
 *
 * @param [type] $query
 * @param [type] $interval
 * @return void
 */
public function scopeNewerThan($query, $interval)
{
    return $query->where('created_at', '<=', Carbon::now()->subMonths($interval)->toDateTimeString());
}

Dumping the actual query gives this:


"select * from `spotlights` where `created_at` <= 2019-10-01 16:14:27 and `status` = published order by RAND() limit 2"


However, I have no spotlights that are newer than one month, and yet it still returns something.

Activity icon

Started a new Conversation Any Benefit To Using HasOneThrough Over Relationship Chaining?

I've been reading through the Eloquent documentation and I can see many use cases for hasManyThrough, however, there's also a hasOneThrough.

In my example I have a Leaver, a User and a manager. The manager attribute is a self referencing relationship on User.


    /**
     * Retrieve this users line manager by retrieving the user from the managed by column
     * 
     * @return void
     */
    public function manager()
    {
        return $this->belongsTo(User::class, 'managed_by', 'username');
    }

When accessing a Leaver if I wwanted the manager's username I could do the following:


$leaver->user->manager->username

My question is in two parts:

  • Can you even use hasOneThrough with self referencing relationships?
  • Is there any benefit to it's use other than easier access to the manager?

According to the documentation a hasOneThrough looks like this:


        return $this->hasOneThrough(
            'App\History',
            'App\User',
            'supplier_id', // Foreign key on users table...
            'user_id', // Foreign key on history table...
            'id', // Local key on suppliers table...
            'id' // Local key on users table...
        );

Oct
28
2 months ago
Activity icon

Replied to Using A Policy With Scout

I rescind my last statement... I just can't type. Thanks.

Activity icon

Replied to Using A Policy With Scout

I don't think you can use the Scouit::search method on a query builder instance though...

Activity icon

Started a new Conversation Using A Policy With Scout

In a project I am using Scout. Something as simple as:

Article::search($request->get('q'))->get()

I also have an `ArticlePolicy'.


<?php

namespace App\Policies;

use App\User;
use App\Article;
use Illuminate\Auth\Access\HandlesAuthorization;

class ArticlePolicy
{
    use HandlesAuthorization;

    /**
     * Determine whether the user can view the article.
     *
     * @param  \App\User  $user
     * @param  \App\Article  $article
     * @return mixed
     */
    public function view(User $user, Article $article)
    {
        // If the article is published
        if ($article->published) {
            return true;
        }

        // A user with permission can view unpublished articles
        if ($user->can('view unpublished articles')) {
            return true;
        }

        // Authors can view their own unpublished posts
        if ($user->username === optional($article->author)->username) {
            return true;
        }

        $this->deny('Sorry, but this article cannot be viewed.');
    }

    /**
     * Determine whether the user can create articles.
     *
     * @param  \App\User  $user
     * @return mixed
     */
    public function create(User $user)
    {
        if ($user->can('create articles')) {
            return true;
        }

        $this->deny('Sorry, but you can\'t create articles.');
    }

    /**
     * Determine whether the user can update the article.
     *
     * @param  \App\User  $user
     * @param  \App\Article  $article
     * @return mixed
     */
    public function update(User $user, Article $article)
    {
        if ($user->can('edit own articles')) {
            return $user->username === optional($article->author)->username;
        }

        if ($user->can('edit any articles')) {
            return true;
        }

        $this->deny('Sorry, but you can\'t update this article.');
    }

    /**
     * Determine whether the user can delete the article.
     *
     * @param  \App\User  $user
     * @param  \App\Article  $article
     * @return mixed
     */
    public function delete(User $user, Article $article)
    {
        // A user can delete their own articles
        if ($user->can('delete own articles')) {
            return $user->username === optional($article->author)->username;
        }

        // A user with permission can delete any article
        if ($user->can('delete any articles')) {
            return true;
        }

        $this->deny('Sorry, but you can\'t delete this article.');
    }

    /**
     * Determine whether the user can restore the article.
     *
     * @param  \App\User  $user
     * @param  \App\Article  $article
     * @return mixed
     */
    public function restore(User $user, Article $article)
    {
        return true;
    }

    /**
     * Determine whether the user can permanently delete the article.
     *
     * @param  \App\User  $user
     * @param  \App\Article  $article
     * @return mixed
     */
    public function forceDelete(User $user, Article $article)
    {
        //
    }
}


Is there a way to directly integrate the Policy with Scout or would I just have to filter the collection that is returned by Scout's search function?

E.g.

Articles that are not published shouldn't really be searchable so you could just do:

$articles = Article::search($query_string)->where('status', 'published')->orderBy('created_at', 'desc')->get();

However, could I have a filter on a collection like so:


$articles= $articles->filter(function ($articles) {
    return $articles->author->username == auth()->user->username
});

Oct
25
3 months ago
Activity icon

Replied to Rendering Model Attributes In Markdown

I've marked your answer, but that is a very interesting element because I'd never considered what we've talked about. I'll give your suggest a try.

Oct
24
3 months ago
Activity icon

Replied to Rendering Model Attributes In Markdown

My only reservation is that I was always able to access the title property on the article. Is this because it was a genuine database field and not an accessor set on the model?

Essentially would the response treat something it wasn't fully aware of as a collection? This would make sense because then you'd essentially have $object->title but the url attribute is specific to the KnowledgeBaseArticle.

My response is written awfully but I hope it kinda makes sense?

Activity icon

Replied to Rendering Model Attributes In Markdown

It seemed to, I don't know whether this is because I also have an Article model and it got a bit confused? Is that possible?

Activity icon

Replied to Rendering Model Attributes In Markdown

However, previously I was not type casting the $article attribute...

Activity icon

Replied to Rendering Model Attributes In Markdown


<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\KnowledgeBaseArticle;

class KnowledgeBaseArticleCreatedNotification extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * The Knowledge Base article instance
     *
     * @var KnowledgeBaseArticle $article
     */
    public $article;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(KnowledgeBaseArticle $article)
    {
        $this->article = $article;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this
            ->subject("New DIT Knowledge Base Article - {$this->article->title}")
            ->markdown('emails.knowledge-base-article-created');
    }
}

This looks correct?

Activity icon

Started a new Conversation Rendering Model Attributes In Markdown

I have a type of article whose direct path as a route looks like this:


route('knowledge-base.article.show', [$this->knowledge_base->slug, $this->id, $this->slug])

I got bored of typing this over and over so I made it an attribute on my model with an accessor:


    /**
     * Generate a URL to the given article
     */
    public function getUrlAttribute()
    {
        return route('knowledge-base.article.show', [$this->knowledge_base->slug, $this->id, $this->slug]);
    }

So I can now do $knowledgeBaseArticle->url where the variable is an instance of KnowledgeBaseArticle.

In a Markdown template the url attribute is not rendered.

See here:


@component('mail::message')

# Hi,

A new article has been created on the DIT Knowledge Base called {{ $article->title }}.

@component('mail::button', ['url' => $article->url])
Read the article
@endcomponent

If the button above does not work please copy the following link into your browser's address bar: {{ $article->url }}.

@component('mail::panel')
This notification was sent from an unattended mailbox.
@endcomponent

@endcomponent


Both instances of {{ $article->url }} don't render anything whatsoever, despite me using the url attribute successfully in multiple other places.

E.g.


<div class="col-12 mb-4">
    <small>Link: <a target="_blank" href="{{ $article->url }}">{{ $article->url }}</a></small>
</div>

Oct
18
3 months ago
Activity icon

Replied to Understanding The HasManyThrough Relation

The toSql() method has made my day.

Activity icon

Replied to Understanding The HasManyThrough Relation

Exactly, however the only difference is that I was testing in Tinker like so:


$team = Team::where('name', 'Digital')->get();

$team->articles;

Surely this would yield the same result?

Activity icon

Replied to Understanding The HasManyThrough Relation

On my local machine this:

select * from `articles` inner join `users` on `users`.`username` = `articles`.`user_username` where `users`.`deleted_at` is null and `users`.`department` = 'Digital'

Returns as expected, which is very peculiar indeed.

Activity icon

Replied to Understanding The HasManyThrough Relation

I never realized you could convert any Eloquent chain to SQL...

It output the following:

"select * from `articles` inner join `users` on `users`.`username` = `articles`.`user_username` where `users`.`deleted_at` is null and `users`.`department` = ?"
Activity icon

Started a new Conversation Understanding The HasManyThrough Relation

Hi everyone, I'm trying to understand a relation I haven't paid much attention to, until recently.

In my setup I have 3 models: Team, User and Article with relations between User and Article, andTeam and User


/**
 * A user belongs to a team
 *
 * @return void
 */
public function team()
{
    return $this->belongsTo(Team::class, 'department', 'name');
}


/**
 * Get all of the articles associated with this user
 */
public function articles()
{
    return $this->hasMany(Article::class, 'user_username', 'username');
}

I've also added the inverse of the given relations, so at the moment I can do

  • $team->users
  • $user->team
  • $article->author
  • $user->articles

I've been trying to get the articles written per department so this was my thought process:

  • Get the users in the department
  • Get the articles from the user

I feel like this is a perfect place to use a hasManyThrough so I tried this in the Team model:


/**
 * Get all of the articles for this department by going through users who authored articles
 * hasManyThrough: [farthest model]|[intermediate model]
 */
public function articles()
{
    return $this->hasManyThrough(
        'App\Article',
        'App\User',
        'department', // Foreign key on users table...
        'user_username', // Foreign key on articles table...
        'id', // Local key on teams table...
        'username' // Local key on users table...
    );
}

However, this returns null.

Essentially this query:


SELECT * FROM articles
LEFT JOIN users ON articles.user_username = users.username
WHERE user_username IS NOT NULL
AND users.department = 'Digital'

Or something similar to this?

"select * fromarticleswhere exists (select * fromuserswherearticles.user_username=users.usernameanddepartment= ?)"

Oct
17
3 months ago
Activity icon

Replied to Dot Dot Slash Attacks

It'd be both. If you're using Laravel Filemanager by Unisharp it allows a user to perform a Dot Dot Slash attack if you have no protection in place, but only because the download function accepts a user supplied path.

Is this preventable?

Activity icon

Started a new Conversation Dot Dot Slash Attacks

In one of my applications there was recently a security audit and directory traversal came up. In one package I'm using, one method allows you to download a file from a specified path, however this path is exposed in a GET request.

So, you can append ../../../../../ until you reach the root of the server, which is awful.

A code example

return response()->download("../.env");

As there is no protection in place, this will break out of the public folder and actually download the .env file.

I've read this could be avoided by using realpath() and doing some comparison but my attempts have been unsuccessful.


        $query_string = $validated_data['q'];

        $laravel_root = base_path();

        $user_real_path = realpath($query_string);

        dd($user_real_path);

        if ($user_real_path === false || strpos($user_real_path, $laravel_root) !== 0) {
            dd('traversal');
        } else {
            dd('!traversal');
        }

Activity icon

Replied to Testing A Fork Of A Composer Installed Package In A Local Project

It turns out I also had to remove the package from the vendor folder.

Oct
15
3 months ago
Activity icon

Started a new Conversation Testing A Fork Of A Composer Installed Package In A Local Project

Hi everyone,

I'm looking to add a patch to a package I use in my Laravel projects - arcanedev/log-viewer so I thought I'd make a pull request to get it added. I hit a snag however, because I have no idea how to test my changes within a local Laravel project.

Steps I've taken:

  • Fork the repo I'm looking to make a pull request for. The fork is here: https://github.com/blorange2/LogViewer
  • Clone this forked repo onto my local machine and switch to the branch that's compatible with my current version of Laravel (which is v4.5 for Laravel 5.6)
  • Update the composer.json in my local project to have a repositories array

  "repositories": [
    {
      "type": "path",
      "url": "../forks/LogViewer"
    }
  ],

With the whole thing looking like this:


{
  "name": "laravel/laravel",
  "description": "The Laravel Framework.",
  "keywords": [
    "framework",
    "laravel"
  ],
  "license": "MIT",
  "type": "project",
  "repositories": [
    {
      "type": "path",
      "url": "../forks/LogViewer"
    }
  ],
  "require": {
    "php": "^7.1.3",
    "alexusmai/laravel-purifier": "^0.5.0",
    "arcanedev/log-viewer": "^4.5",
    "artesaos/laravel-linkedin": "^1.3",
    "barryvdh/laravel-dompdf": "^0.8.4",
    "cartalyst/tags": "6.0.*",
    "cornford/googlmapper": "^2.33",
    "doctrine/dbal": "^2.9",
    "fideloper/proxy": "^4.0",
    "guzzlehttp/guzzle": "^6.3",
    "guzzlehttp/psr7": "^1.4",
    "happyr/linkedin-api-client": "^1.0",
    "intervention/image": "^2.5",
    "ixudra/curl": "^6.16",
    "jdavidbakr/mail-tracker": "~2.1",
    "laravel/framework": "5.6.*",
    "laravel/scout": "^5.0",
    "laravel/socialite": "^3.0",
    "laravel/tinker": "^1.0",
    "laravelcollective/html": "^5.6",
    "laravolt/avatar": "^3.0",
    "league/flysystem-sftp": "~1.0",
    "maatwebsite/excel": "^3.1",
    "maddhatter/laravel-fullcalendar": "^1.3",
    "mews/purifier": "^2.1",
    "php-http/curl-client": "^1.7",
    "php-http/message": "^1.6",
    "pusher/pusher-http-laravel": "^4.2",
    "socialiteproviders/microsoft-graph": "^2.0",
    "spatie/calendar-links": "^1.0",
    "spatie/flysystem-dropbox": "^1.2",
    "spatie/laravel-analytics": "^3.6",
    "spatie/laravel-backup": "^5.9",
    "spatie/laravel-medialibrary": "7.6.3",
    "spatie/laravel-permission": "^2.12",
    "teamtnt/laravel-scout-tntsearch-driver": "^3.0",
    "thujohn/twitter": "^2.2",
    "unisharp/laravel-filemanager": "~1.8",
    "vimeo/laravel": "^5.0"
  },
  "require-dev": {
    "barryvdh/laravel-debugbar": "^3.2",
    "filp/whoops": "^2.0",
    "fzaninotto/faker": "^1.4",
    "mockery/mockery": "^1.0",
    "nunomaduro/collision": "^2.0",
    "phpunit/phpunit": "^7.0"
  },
  "autoload": {
    "files": [
      "app/Helpers/Helper.php"
    ],
    "classmap": [
      "database/seeds",
      "database/factories"
    ],
    "psr-4": {
      "App\": "app/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "Tests\": "tests/"
    }
  },
  "extra": {
    "laravel": {
      "dont-discover": []
    }
  },
  "scripts": {
    "post-root-package-install": [
      "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
    ],
    "post-create-project-cmd": [
      "@php artisan key:generate"
    ],
    "post-autoload-dump": [
      "Illuminate\Foundation\ComposerScripts::postAutoloadDump",
      "@php artisan package:discover"
    ]
  },
  "config": {
    "preferred-install": "dist",
    "sort-packages": true,
    "optimize-autoloader": true
  },
  "minimum-stability": "dev",
  "prefer-stable": true
}

My main project is located at the following path (from running pwd on Windows) C:\xampp\htdocs\projects\newable\newable-intranet

The cloned, forked project is located here: C:\xampp\htdocs\projects\forks\LogViewer.

However, running composer update does not use the local version, it just uses: "arcanedev/log-viewer": "^4.5",

Sep
27
4 months ago
Activity icon

Replied to Creating A File Library In Laravel

It was mainly because I want people to be able to rename the file but I wanted the actual filename to stay the same if that makes sense.

Activity icon

Started a new Conversation Creating A File Library In Laravel

In my application I have a section that's literally full of uploaded files which acts as a library of sorts. Originally the user would select a category and a number of files and just upload them. These would be stored in the public folder with the original file name.

I felt this was rather primitive so I changed my controller.


<?php

namespace App\Http\Controllers\Editable;

use App\File;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreLibrary;

class LibraryController extends Controller
{
    /**
     * Instantiate a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware(['role:admin|templates manager']);

        $this->disk = "library";
    }

    /**
     * Set the categories a file can go into so we can standardize the views
     * Add to this list as necessary
     *
     * @var array
     */
    private $categories = [
        'Bid Information',
        'Cyber Security',
        'Department of International Trade (DIT)',
        'Finance',
        'Funding',
        'GDPR',
        'Health and Safety (H&S)',
        'Help and How To',
        'Human Resources (HR)',
        'London Trade and Innovation ISO',
        'Marketing',
        'Post Event Documents'
    ];

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $files = File::orderBy('category', 'DESC')->get();

        return view('pages.thanos.library.index', compact('files'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        $categories = $this->categories;

        return view('pages.thanos.library.create', compact('categories'));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(StoreLibrary $request)
    {
        $data = $request->validated();

        $category = $data['category'];

        $files = $data['files'];

        foreach ($files as $file) {
            // File details 
            $original_name = $file->getClientOriginalName();
            $mime_type = $file->getClientOriginalExtension();
            $size = $file->getSize();

            // A generated name
            $system_generated_name = sha1(date('YmdHis') . str_random(30)) . '.' . $file->getClientOriginalExtension();

            // Store the file and asertain the path
            $path = Storage::disk('library')->putFileAs(null, $file, $system_generated_name);

            // Create a new file instance in the database
            File::create([
                'display_name' => $original_name,
                'file_name' => $system_generated_name,
                'mime_type' => $mime_type,
                'size' => $size,
                'disk' => $this->disk,
                'storage_location' => $path,
                'category' => $category,
            ]);
        }

        return redirect()->route('thanos.library.index')->with('success', 'Files uploaded successfully.');
    }

    /**
     * Display a form to edit this file
     *
     * @param File $file
     * @return void
     */
    public function edit(File $file)
    {
        //
    }

    /**
     * Update this file in storage
     *
     * @param Request $request
     * @param File $file
     * @return void
     */
    public function update(Request $request, File $file)
    {
        $file->fill([
            'display_name' => $request->get('display_name') . "." . $file->mime_type,
        ])->save();

        return redirect()->route('thanos.library.index')->with('success', 'File updated successfully.');
    }

    /**
     * Remove the given file from storage and then remove the corresponding database record
     *
     * @param Library $library
     * @return void
     */
    public function destroy(File $file)
    {
        if (Storage::disk($file->disk)->exists($file->file_name)) {
            Storage::disk($file->disk)->delete($file->file_name);
        }

        $file->delete();

        return redirect()->route('thanos.library.index')->with('success', "File deleted {$file->display_name}");
    }
}

Is having a display name and a system generated name overkill? I didn't want to create seperate directories per category as they felt unnecessary. The only downside is that when accessed via FTP the file names tend to look like this:

0d343332794a383bef629f6099d2343db279bfa3.docx

So, if the database was to go you'd have no idea what anything is.

Sep
26
4 months ago
Activity icon

Replied to When To Use Jobs?

So, like everything the answer is basically 'there is no right answer'. To be fair I didn't even realize a listener could be queueable.

In terms of performance I could just queue the notification itself right?

This question came about because I saw a whole bunch of tutorials on Welcome Emails however I've always just used events and listeners.

Do you yourself lean more towards dedicated jobs or queueable classes?

Activity icon

Started a new Conversation When To Use Jobs?

I have been looking at Queues and Jobs recently and I have a question:

In an application I have you can create articles but if you can't publish it, a notification is sent from the SuggestionObserver


<?php

namespace App\Observers;

use App\Suggestion;
use App\User;
use Notification;
use App\Notifications\SuggestionMade;
use Log;

class SuggestionObserver
{
   /**
    * Handle the suggestion "created" event.
    *
    * @param  \App\Suggestion  $suggestion
    * @return void
    */
   public function created(Suggestion $suggestion)
   {
       Log::notice("A new suggestion has been submitted by {$suggestion->user->display_name}");

       $users = User::whereIn('username', ['jesseo', 'houdam', 'graemem'])->get();

       Notification::send($users, ((new SuggestionMade($suggestion))->delay(now()->addSeconds(10))));
   }

   /**
    * Handle the suggestion "updated" event.
    *
    * @param  \App\Suggestion  $suggestion
    * @return void
    */
   public function updated(Suggestion $suggestion)
   {
       Log::notice("A suggestion has been updated by {$suggestion->user->display_name}");
   }

   /**
    * Handle the suggestion "deleted" event.
    *
    * @param  \App\Suggestion  $suggestion
    * @return void
    */
   public function deleted(Suggestion $suggestion)
   {
       Log::notice("A suggestion has been deleted by {$suggestion->user->display_name}");
   }

   /**
    * Handle the suggestion "restored" event.
    *
    * @param  \App\Suggestion  $suggestion
    * @return void
    */
   public function restored(Suggestion $suggestion)
   {
       //
   }

   /**
    * Handle the suggestion "force deleted" event.
    *
    * @param  \App\Suggestion  $suggestion
    * @return void
    */
   public function forceDeleted(Suggestion $suggestion)
   {
       //
   }
}


As you can see I have used delay() which adds this to the queue and then sends it after the elapsed time period.

Is it preferable to specifically use a Job class?

Another example in in my EventServiceProvider


    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
            LogRegistered::class,
            SendWelcomeEmail::class,
        ],
     ];

When a user registers a Listener called SendWelcomeEmail is triggered that looks like this:


<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Registered;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Http\Request;
use Mail;
use App\Mail\User\Welcome;
use Log;

class SendWelcomeEmail
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    /**
     * Handle the event.
     *
     * @param  Registered  $event
     * @return void
     */
    public function handle(Registered $event)
    {
        Mail::send(new Welcome($event->user));

        Log::notice("{$event->user->log_reference} recieved their welcome email.");
    }
}

In this case would you queue the email sending or specifically have a job called SendWelcomeEmail? How do you guys make the decision?

Sep
20
4 months ago
Activity icon

Commented on Commentable Workshop

three levels

Activity icon

Commented on Commentable Workshop

Two levels...

Activity icon

Commented on Commentable Workshop

Here's one level deep...

Activity icon

Commented on Commentable Workshop

I'm interested in seeing how this works with multiple replies...

Sep
09
4 months ago
Activity icon

Replied to All Policies Return A 403

Using your advice about breakpoints I was able to see that my AuthServiceProvider was actually missing use App\Article;, so fundamentally the application didn't know how to resolve the class. Instead of dying it just failed every Gate check.

Thanks for all your advice on the subject.

Activity icon

Replied to All Policies Return A 403

I have a secondary gate, which returns this:


success
array:4 [▼
  "ability" => "create articles"
  "result" => true
  "user" => "jesseo"
  "arguments" => "[]"
]

Then in my Policy I have this:


    /**
     * Determine whether the user can create articles.
     *
     * @param  \App\User  $user
     * @return mixed
     */
    public function create(User $user)
    {
        if ($user->can('create articles')) {
            return true;
        }
    }

I know that the user can create articles, so how is it resolving to false?

Activity icon

Replied to All Policies Return A 403

In my User model I am defining the following:


 /**
     * Table to use
     */
    protected $table = 'users';

    /**
     * Set the Primary Key
     *
     * @var string
     */
    protected $primaryKey = 'username';

Activity icon

Replied to All Policies Return A 403

Sorry but this made no difference.

Activity icon

Replied to All Policies Return A 403

Yes, and my policy looks like this:


<?php

namespace App\Policies;

use App\User;
use App\Article;
use Illuminate\Auth\Access\HandlesAuthorization;

class ArticlePolicy
{
    use HandlesAuthorization;

    /**
     * Determine whether the user can view the article.
     *
     * @param  \App\User  $user
     * @param  \App\Article  $article
     * @return mixed
     */
    public function view(User $user, Article $article)
    {
        //
    }

    /**
     * Determine whether the user can create articles.
     *
     * @param  \App\User  $user
     * @return mixed
     */
    public function create(User $user)
    {
        return true;
    }

    /**
     * Determine whether the user can update the article.
     *
     * @param  \App\User  $user
     * @param  \App\Article  $article
     * @return mixed
     */
    public function update(User $user, Article $article)
    {
        return true;
    }

    /**
     * Determine whether the user can delete the article.
     *
     * @param  \App\User  $user
     * @param  \App\Article  $article
     * @return mixed
     */
    public function delete(User $user, Article $article)
    {
        return true;
    }

    /**
     * Determine whether the user can restore the article.
     *
     * @param  \App\User  $user
     * @param  \App\Article  $article
     * @return mixed
     */
    public function restore(User $user, Article $article)
    {
        return true;
    }

    /**
     * Determine whether the user can permanently delete the article.
     *
     * @param  \App\User  $user
     * @param  \App\Article  $article
     * @return mixed
     */
    public function forceDelete(User $user, Article $article)
    {
        return true;
    }
}

Then in my ArticleController I have methods like so:


 /**
     * Show the form for creating a new resource.
     *
     * @param  Request  $request
     * @return Response
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function create()
    {
        $this->authorize('create', Article::class);

        $categories = $this->categories;

        return view('editable.news.create', compact('categories'));
    }

Then in my AuthServiceProvider I have this:


<?php

namespace App\Providers;

use App\Joiner;
use App\ExpensePersonal;
use App\ExpenseMileage;
use App\CreditStatement;
use App\CreditTransaction;
use App\ExpenseFile;
use App\Policies\ArticlePolicy;
use App\Policies\ExpensePersonalPolicy;
use App\Policies\ExpenseMileagePolicy;
use App\Policies\CreditStatementPolicy;
use App\Policies\CreditTransactionPolicy;
use App\Policies\JoinerPolicy;
use App\Policies\ExpenseFilePolicy;

use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        Article::class => ArticlePolicy::class,
        Joiner::class => JoinerPolicy::class,
        ExpensePersonal::class => ExpensePersonalPolicy::class,
        ExpenseMileage::class => ExpenseMileagePolicy::class,
        CreditStatement::class => CreditStatementPolicy::class,
        CreditTransaction::class => CreditTransactionPolicy::class,
        ExpenseFile::class => ExpenseFilePolicy::class
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        // Ensure admin users bypass gate checksv
        Gate::before(function ($user) {
            return $user->hasRole('admin') ? true : null;
        });
    }
}


The actual Gate returns the following:


array:4 [▼
  "ability" => "update"
  "result" => false
  "user" => "jesseo"
  "arguments" => "[0 => Object(App\Article)]"
]

Activity icon

Replied to All Policies Return A 403

I'll try to scale down and then I'll post again.

Sep
07
4 months ago
Activity icon

Replied to All Policies Return A 403

In the AuthServiceProvider I have Article::class => ArticlePolicy::class in the policies array, and have included the appropriate use statements.

It's very peculiar because it doesn't feel like the policy is triggering as even if you abort with a custom message you just get the standard "This action is unauthorized" .

In my ArticleController I use the helper in this way $this->authorize('create', Article::class); in a method with a stub of public function create()

Activity icon

Started a new Conversation All Policies Return A 403

I went back to a Laravel 5.6 application to add Policies and it always returns a 403 response, even when explicitly returning true. Has anyone else experienced this?