How to Bind to Eloquent Model Event in Laravel 5

Whenever a Laravel model is modified there are a number of events that fire that allow you to trigger your own action(s). For example, in my PoliticsEQ application I needed to calculate some statistics about keywords and sentiment scores and store them in a separate table to improve performance for some of the front in graphs. The challenge was making sure the keyword statistics table was updated whenever a keyword was added or updated.

Laravel gives you the following events right out of the box: creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored. You can get the particulars here. Most of them are pretty intuitive.

In my case it made sense to use the “saved” event. To make this happen was remarkably. All I had to do was create a new “Service Provider” and use the Event::saved pattern in the provider’s boot method.

So first I created a KeywordStats provider.


namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Keyword, App\KeywordStat;
use Log;

class KeywordStats extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        Keyword::saved(function($keyword) {
          $stat = KeywordStat::where('keyword_name', $keyword->name)->first();
          if (!$stat) {
            $stat = new KeywordStat(['keyword_name'=>$keyword->name]);
          }
          $stat->sentiment_avg = Keyword::where('name', $keyword->name)->avg('sentiment_score');
          $stat->total_usages = Keyword::where('name',$keyword->name)->get()->count();
          $stat->save();
          Log::info("Updated {$keyword->name} / avg: {$stat->sentiment_avg} / count: {$stat->total_usages}");
        });
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        
    }
}

Then I simply registered the provider in the config/app.php providers array:


    App\Providers\KeywordStats::class,

Hide WordPress Post from All Queries

Problem: you want to create a variation of a page but you don’t want it to show up on the home page or in any archives or anything. You just need a direct link so you can share it with someone.

Solution:

add_action('pre_get_posts', 'hide_hidden_posts');
function hide_hidden_posts($query) {
  if ( is_admin() ) {
    return $query;
  }

  if ( is_single() AND $query->is_main_query() ) {
    return $query;
  }
  $ids = wp_cache_get('hidden_posts', 'posts');
  if ( !$ids ) {
    global $wpdb;
    $ids = $wpdb->get_col("SELECT post_id FROM {$wpdb->prefix}postmeta WHERE meta_key = 'hide_post'");
    wp_cache_set('hidden_posts', $ids, 'posts');
  }
  $query->set('post__not_in', $ids);
  return $query;
}

This function will modify all WordPress’ frontend queries to exclude any posts with a custom field “hide_post”, except in the case that the query is the main query on a single post page.

Caveat: This will only be functional for plugins and themes using the WP_Query api. Custom queries will not be modified.

Block hacker attacks on WordPress’ xmlrpc.php

It you’re getting a ton of POST requests to your WordPress xmlrpc.php file, here’s a quick way to block all the bad ips via iptables. In my case I’m using nginx and php-fpm, but something very similar would also work for apache.

First, recognize the signature. Your access logs will look something like this:

5.135.68.51 - - [13/May/2015:12:14:59 -0400] "POST /xmlrpc.php HTTP/1.0" 499 0 "-" "Mozilla/5.0 (compatible; Googlebot/2.1;  https://www.google.com/bot.html)"
185.61.138.72 - - [13/May/2015:12:14:59 -0400] "POST /xmlrpc.php HTTP/1.0" 499 0 "-" "Mozilla/5.0 (compatible; Googlebot/2.1;  https://www.google.com/bot.html)"
185.11.147.17 - - [13/May/2015:12:14:59 -0400] "POST /xmlrpc.php HTTP/1.0" 404 168 "-" "Mozilla/5.0 (compatible; Googlebot/2.1;  https://www.google.com/bot.html)"
185.11.147.17 - - [13/May/2015:12:14:59 -0400] "POST /xmlrpc.php HTTP/1.0" 499 0 "-" "Mozilla/5.0 (compatible; Googlebot/2.1;  https://www.google.com/bot.html)"
185.62.188.76 - - [13/May/2015:12:15:01 -0400] "POST /xmlrpc.php HTTP/1.0" 499 0 "-" "Mozilla/5.0 (compatible; Googlebot/2.1;  https://www.google.com/bot.html)"
185.62.188.76 - - [13/May/2015:12:15:01 -0400] "POST /xmlrpc.php HTTP/1.0" 499 0 "-" "Mozilla/5.0 (compatible; Googlebot/2.1;  https://www.google.com/bot.html)"
185.62.188.76 - - [13/May/2015:12:15:01 -0400] "POST /xmlrpc.php HTTP/1.0" 499 0 "-" "Mozilla/5.0 (compatible; Googlebot/2.1;  https://www.google.com/bot.html)"
185.61.138.72 - - [13/May/2015:12:15:01 -0400] "POST /xmlrpc.php HTTP/1.0" 404 168 "-" "Mozilla/5.0 (compatible; Googlebot/2.1;  https://www.google.com/bot.html)"
5.135.68.51 - - [13/May/2015:12:15:02 -0400] "POST /xmlrpc.php HTTP/1.0" 499 0 "-" "Mozilla/5.0 (compatible; Googlebot/2.1;  https://www.google.com/bot.html)"
5.135.68.51 - - [13/May/2015:12:15:02 -0400] "POST /xmlrpc.php HTTP/1.0" 404 168 "-" "Mozilla/5.0 (compatible; Googlebot/2.1;  https://www.google.com/bot.html)"

Googlebot will NOT be POSTing your xmlrpc.php like that. Next the trick is to figure out which IP addresses are harassing you. Run this in your terminal:

$> grep xmlrpc /var/log/nginx/access.log | cut -d' ' -f1 | sort | uniq -c | sort -rn | head
  29200 185.11.147.17
  17182 185.62.188.76
  10657 185.61.138.72
   8183 5.135.68.51
   1914 192.227.175.122
   1738 195.154.185.116
   1198 43.252.228.132
    501 205.234.152.218
    155 86.105.212.68
    103 141.138.157.95

Most likely all of these are hackers since it would be unlikely even Jetpack or some other WordPress service would hit your xmlrpc.php that frequently. But you can decide where the cut off should be by adding -n# the the head request above. In my case I chose head -n8 like so:

$> grep xmlrpc /var/log/nginx/access.log | cut -d' ' -f1 | sort | uniq -c | sort -rn | head -n8
  29200 185.11.147.17
  17182 185.62.188.76
  10657 185.61.138.72
   8183 5.135.68.51
   1914 192.227.175.122
   1738 195.154.185.116
   1198 43.252.228.132
    501 205.234.152.218

Sooo …. now you just need to wrap that in a loop that will create the iptable rules to block traffic from the ips:

$> for ip in $(grep xmlrpc /var/log/nginx/access.log | cut -d' ' -f1 | sort | uniq -c | sort -rn | head -n8 | awk '{print $2}'); do iptables -A INPUT -s $ip -j DROP; done

No more hackers.

Meta Tag Titles and Descriptions for Your WordPress site.

There are many good plugins out there the help you easily add meta tags to your site. All-in-one-SEO is probably the most popular of all the options. But there several reason why you may want to manually add this code to your template.

For one, you may be a template designer and want your templates to come pre-installed with this SEO friend feature.

Second, you may be having issues with Facebook link sharing not working. WordPress plugins use the wp_head() hook to access your template. But you have no control over the order. If you are using more than a dozen plugins your meta tags can get buried under tons of other code. This can prevent some bots from finding them. Indeed on several of my site Facebook’s bot was not finding them.

Third, it is just always good to do things without plugins if possible. Call it Occum’s Razor of web site building, the less code you use, the better.

Luck for us, it is extremely simple to add code for meta tags and descriptions without knowing much about wordpress or PHP.

Open the header.php file in your theme and paste in the following code:

<?php if(is_singular()) { ?>
<?php
global $post;
?>
<?php $recent = new WP_query('p='.$post->ID);
while($recent->have_posts()) : $recent->the_post(); ?>
<meta name="title" content="<?php the_title(); ?>">
<meta name="description" content="<?php the_content_rss('', TRUE, '', 50); ?>">
<?php endwhile; ?>
<?php } else { ?>
<meta name="title" content="<?php bloginfo('title'); ?>">
<meta name="description" content="<?php bloginfo('description'); ?>">
<?php } ?>

So what’s going on here? First, we’re checking to see if the post is a single post using the WordPress Conditional tag <?php if(is_singular()); ?> because we’ll to pull the title and the description of the individual post for our meta tags. But to do this, we need to get some information about the post, which is why we use the call global $post. This will give us information about the current post. Particularly it allows us access to $post->ID to query information about the post using WP_query.

The query we us looks like this:

$recent = new WP_query('p='.$post->ID); 

Once we have the query we put it into the standard wordpress loop:

while($recent->have_posts()) : $recent->the_post();

Then we use the standard WordPress tag <?php the_title(); ?> to pull in the META TITLE and <? the_content_rss() ?> to get the description. Notice, we are not using <?php the_excerpt(); ?>. This is because this WordPress tag prints the excerpt with a “read more” link in it. This will seriously screw up your theme. Using <?php the_content_rss(); ?> allows us to specify how many words of the content to pull in. But the second parameter has to be set to TRUE to avoid pulling in a “read more” link.