Posted (Updated ) in PHP

Laravel may be one of the younger frameworks out there but it’s making ripples in the PHP world. The following post teaches how to build a basic to-do application in Laravel 4. It covers a wide range of concepts, links to relevant learning material where possible and should make for a great introduction to the framework.

This tutorial is relatively long so I’ve broken it up into multiple posts.

  1. Part 1 – Installation, Database and Routes
  2. Part 2 – Listing Projects and Tasks
  3. Part 3 – Create/Edit/Delete
  4. Part 4 – Validation and Slugs

Today will cover installation, configuration, artisan, migration, seeding and routes.

 

Before you Begin

Before you begin there are a few great resources you should check out.

You won’t get far in the Laravel world without hearing about Jeffrey Way. Jeffrey has perhaps done more for the Laravel community than any other non-core developer. He has produced high-quality, comprehensive video tutorials on almost every aspect of L4, many of which are free to view and aimed at beginners. I would highly recommend you check out the following pieces of his work:

  1. Laravel 4 Mastery – although older (from the L4 beta days), it’s mostly still relevant. Aimed at beginners and quite comprehensive
  2. Laravel From Scratch – in many ways an updated version of Laravel 4 Mastery. Not as comprehensive but a great compliment to the above
  3. Laracasts – Mostly paid for videos of very high quality. New videos are regularly created and one a week is made free.

There are a few other places to find news and information:

  1. The Laravel Twitter feed – for the latest breaking news on L4 development
  2. Laravel.io – Weekly roundups  that gather the latest news and tutorials from around the web. They also do a weekly podcast that often includes the framework author Taylor Otwell
  3. Laravel Packages Registry – good place to go to find some of the best L4 packages
  4. Code Bright – An e-book provided free of charge by framework author Dayle Rees

 

Project Aim

Our to-do application will consist of one or more projects, each with its own list of tasks. You will be able to create, list, modify and delete both tasks and projects.

In this lesson we will go through:

  1. Installing and setting up Laravel 4
  2. Installing extra packages that will make development easier
  3. Using migrations and seeds
  4. Learning how to use resourceful controllers
  5. Learning how to use views (including the blade templating language and content layouts
  6. Handling model relations

Read More »

Posted in PHP

We’ve been having no end to issues with image scaling in WordPress where an administrator uploads a ridiculously large image and it appears stretched vertically in Internet Explorer.

I looked into this and discovered WordPress was automatically adding width and height attachments onto the inserted image in the admin. This is generally fine as all modern browsers will scale accordingly for smaller windows…but then we have Internet Explorer which will just retardedly scale width but not height resulting in massively stretched images.

The approach I ended up taking to fix it was to scale the automatically generated width and height attributes on the image down before the HTML is inserted into the post in admin. This can be accomplished with the following snippet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
add_filter( 'post_thumbnail_html', 'flyn_apply_max_image_dimenions', 10 );
add_filter( 'image_send_to_editor', 'flyn_apply_max_image_dimenions', 10 );
function flyn_apply_max_image_dimenions( $html ) {
	ini_set('display_errors', true);
	preg_match('/width="(\d+)"\s/', $html, $wmatches);
	preg_match('/height="(\d+)"\s/', $html, $hmatches);
 
	if ( !empty($wmatches[1]) && !empty($hmatches[1]) )
	{
		list($width, $height) = boilerplate_scale_inside($wmatches[1], $hmatches[1], 630, 9999);
		$html = str_replace('width="'.$wmatches[1].'"', 'width="'.$width.'"', $html);
		$html = str_replace('height="'.$hmatches[1].'"', 'height="'.$height.'"', $html);
	}
 
	return $html;
}
 
function flyn_scale_inside( $width, $height, $into_width, $into_height )
{
	$ratio = $into_width / $width;
 
	$width = $into_width;
	$height *= $ratio;
 
	if ( $height > $into_height )
	{
		$ratio = $into_height / $height;
		$height = $into_height;
		$width *= $ratio;
	}
 
	return array($width, $height);
}

Set your maximum width however you please (I’m using 630px in the example above). I chose a height of 9999 so the image would always be scaled nicely to my themes maximum width.

Read More »

Posted in PHP

Today I came across an issue where when uploading large files my $_FILES and $_POST values returned empty arrays. Turns out this is expected (albeit stupid IMO) behavior.

You can test this by uploading a small then a huge file to this relatively simple PHP script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<title>Upload test</title>
</head>
<body>
	<form action="?submit=true" role="form" method="POST" enctype="multipart/form-data">
		<input name="images" type="file">
		<input type="hidden" name="foo" value="bar">
		<input type="submit">
	</form>
 
	<pre>$_FILES:
<?php print_r($_FILES); ?>
	</pre>
	<pre>$_POST:
<?php print_r($_POST); ?>
	</pre>
</body>
</html>

After uploading a small file your $_FILES variable will contain the file details but with a huge file (one larger than your POST_MAX_SIZE ini setting) it will be empty.

To check if a user has uploaded a file larger than your POST_MAX_SIZE value you need to use the following if statement:

1
2
if ( !empty($_SERVER['CONTENT_LENGTH']) && empty($_FILES) && empty($_POST) )
	echo 'The uploaded zip was too large. You must upload a file smaller than ' . ini_get("upload_max_filesize");

Here is an updated file that will notify the user if the file they uploaded is too large:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html>
<head>
<title>Upload test</title>
</head>
<body>
	<form action="?submit=true" role="form" method="POST" enctype="multipart/form-data">
		<input name="images" type="file">
		<input type="hidden" name="foo" value="bar">
		<input type="submit">
	</form>
 
	<?php		if ( !empty($_SERVER['CONTENT_LENGTH']) && empty($_FILES) && empty($_POST) )			echo 'The uploaded zip was too large. You must upload a file smaller than ' . ini_get("upload_max_filesize");	?> 
	<pre>$_FILES:
<?php print_r($_FILES); ?>
	</pre>
	<pre>$_POST:
<?php print_r($_POST); ?>
	</pre>
</body>
</html>

Read More »

Posted in PHP

Customising your RSS feed in WordPress is relatively easy and comes in two methods: adding rows to the existing feed, or complete customisation. Each is detailed below along with example code snippets.

 

Adding Rows

If you want to add rows to the default RSS feed for each entry, the best way to do this is with the rss2_item action. In the example below I add an <image> row if my posts have featured images.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
add_action('rss2_item', function() {
	global $post;
	if ( $post->post_type != 'cctv_image' )
		return;
 
	// Only add rows to feed with post images
	if ( !has_post_thumbnail($post->ID) )
		return;
 
	// Get the featured image URL and dimensions
	$thumbnail_id = get_post_thumbnail_id( $post->ID );
	$result = wp_get_attachment_image_src( $thumbnail_id, 'full' );
 
	// Did an error occur?
	if ( !$result )
		return;
 
	$url = $result[0];
	$width = $result[1];
	$height = $result[2];
	echo "<image width='$width' height='$height'>$url</image>";
});

Now if a post has a featured image, it’s RSS entry will contain the following extra line:

<image width=”640″ height=”480″>http://mysite.com/wp-content/uploads/2013/10/my_image.jpg</image>

 

Complete Customisation

The above not enough for you? Do you want complete control over what is and isn’t in your feed? Add the following to your functions.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
remove_all_actions( 'do_feed_rss2' );
add_action( 'do_feed_rss2', function( $for_comments ) {
	if ( $for_comments )
		load_template( ABSPATH . WPINC . '/feed-rss2-comments.php' );
	else
	{
		if ( $rss_template = locate_template( 'feed-rss2.php' ) )
			// locate_template() returns path to file
			// if either the child theme or the parent theme have overridden the template
			load_template( $rss_template );
		else
			load_template( ABSPATH . WPINC . '/feed-rss2.php' );
	}
}, 10, 1 );

The above allows you to drop a feed-rss2.php file into your theme folder which will be loaded in place of the default RSS template when a visitor hits your RSS feed URL. Put whatever you like in there!

Read More »

Posted (Updated ) in PHP

Another quick set of utility functions. If you wish to restrict WordPress admin to only users with specifics sets of permissions (such as only those higher than subscriber) use the following two actions in your functions.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * Remove admin bar for users without adequate permissions
 */
add_action('after_setup_theme', function() { 
	if ( !current_user_can('edit_posts') )
		add_filter('show_admin_bar', '__return_false');	
});
 
/**
 * Redirect users without adequate permissions back to home page
 */
add_action('admin_init', function(){
	if ( !current_user_can('edit_posts') )
	{
		// Only redirect if not an AJAX request
		if ( empty($_SERVER['PHP_SELF']) || basename($_SERVER['PHP_SELF']) != 'admin-ajax.php' )
		{
			wp_redirect( site_url() );
			exit;
		}
	}
});

You can use any permission you like from the Roles and Capabilities section of the documentation to make admin accessible to just the user groups of your choice. There’s even a handy table showing which roles are available to which default user levels.

Read More »

Posted in PHP

You may occasionally want to regenerate image thumbnails in WordPress (such as after changing image quality settings). Here is a handy function that regenerates all thumbnails for a given attachment post

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function regenerate_attachment_images( $attachment_id )
{
	// We only want to look at image attachments
	if ( !wp_attachment_is_image($attachment_id) )
		return;
 
	$filepath = get_attached_file( $attachment_id, true );
	$metadata = wp_generate_attachment_metadata( $attachment_id, $filepath );
 
	// Was there an error?
	if ( is_wp_error( $metadata ) )
		$this->die_json_error_msg( $attachment_id, $metadata->get_error_message() );
	if ( empty( $metadata ) )
		$this->die_json_error_msg( $attachment_id, 'Unknown failure reason.' );
 
	// If this fails, then it just means that nothing was changed (old value == new value)
	wp_update_attachment_metadata( $attachment_id, $metadata );
}

The above was taken mostly from the Regenerate Thumbnails plugin with a few modifications of my own.

Example Usage

To regenerate all thumbnails for all images use the following (Warning: This could take a while if you have alot of images and/or image sizes):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$images = new WP_Query(array(
	'post_type' => 'attachment',
	'posts_per_page' => -1,
	'post_mime_type' => 'image',
	'suppress_filters' => false,
 
	'offset' => 0,
	'post_status' => 'inherit',
	'ignore_sticky_posts' => true,
	'no_found_rows' => true,
));
 
while ( $images->have_posts() ): 
	$images->next_post();
	regenerate_attachment_images( $images->post->ID );
endwhile;

Read More »

Posted (Updated ) in Database, PHP

Sometimes in WordPress you want to include associated taxonomy terms with your get_posts() or WP_Query lookups. Doing so can have a noticeable impact on performance. Not to mention it’s much cleaner code-wise.

Here’s an example. I wanted to group image attachments into genre – action, adventure etc. and display that information on my sites frontend. Firstly I added my genre taxonomy to the attachment post type:

1
2
3
4
5
6
7
8
9
register_taxonomy('genre', 'attachment', array(
	'label' => 'Genres',
	'rewrite' => array( 'slug' => 'genre' ),
	'hierarchical' => true,
	'capabilities' => array(
		'assign_terms' => 'edit_posts',
		'edit_terms' => 'publish_posts'
	)
));

I now needed to display that information on the images associated post page (single.php) on the frontend.

 

The dumb way

On my first attempt I looped through the images, grabbing the associated genres and displaying them:

1
2
3
4
5
6
7
8
9
10
$images = get_posts(array(
	'post_parent' => get_the_ID(),
	'post_type' => 'attachment',
	'numberposts' => -1,
	'orderby'        => 'title',
	'order'           => 'ASC',
	'post_mime_type' => 'image',
));
foreach ( $images as $image )
	echo $image->post_title . ': ' . strip_tags(get_the_term_list($image->ID, 'genre', '', ', ', ''));

My image: Action, Adventure

This resulted in one unnecessary database call per image which could add up quickly. I needed a better way.

 

A smarter approach

WP_Query (which get_posts() uses to retrieve its results) supports a filter posts_clauses that lets you modify various parts of the SQL query it is about to perform. I used this to JOIN the taxonomy tables on and include the genre name(s) in the result array.

Firstly the filter (only works if you drop it in functions.php):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * Include 'size' name in image attachment lookups. This only applies if
 * INCLUDE_GENRES global variable flag is set - otherwise it will affect
 * the_loop
 *
 * @param array $pieces Includes where, groupby, join, orderby, distinct, fields, limits
 *
 * @return array $pieces
 */
add_filter( 'posts_clauses', function( $pieces )
{
	global $wpdb, $INCLUDE_GENRES;
 
	if ( empty($INCLUDE_GENRES) )
		return $pieces;
 
	$pieces['join'] .= " LEFT JOIN $wpdb->term_relationships iqctr ON iqctr.object_id=$wpdb->posts.ID
						 LEFT JOIN $wpdb->term_taxonomy iqctt ON iqctt.term_taxonomy_id=iqctr.term_taxonomy_id AND iqctt.taxonomy='genre'
						 LEFT JOIN $wpdb->terms iqct ON iqct.term_id=iqctt.term_id";
	$pieces['fields'] .= ",GROUP_CONCAT(iqct.name SEPARATOR ', ') AS genres";
 
	return $pieces;
}, 10, 1 );

You’ll notice the $INCLUDE_GENRES variable. This is required because without it the filter will apply to all the_loop and other queries. We only want it to apply for one specific query. Now how to use it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$INCLUDE_GENRES = true;
$images = get_posts(array(
	'post_parent' => get_the_ID(),
	'post_type' => 'attachment',
	'numberposts' => -1,
	'orderby'        => 'title',
	'order'           => 'ASC',
	'post_mime_type' => 'image',
	'suppress_filters' => false,
));
$INCLUDE_GENRES = false;
 
foreach ( $images as $image )
	echo $image->post_title . ': ' . $image->genres;

My image: Action, Adventure

Perfect!

Read More »

Posted (Updated ) in PHP

Bootstrap 3’s NavBar component has a convoluted markup that makes it difficult to integrate into WordPress’s wp_nav_menu() function but with the help of a custom Walker and a filter it’s quite possible to get happening.

 

Target Markup

We’re aiming to replicate the NavBar markup from the BS3 documentation. Here it is in full. Specifically wp_nav_menu() will cover the highlighted section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<nav class="navbar navbar-default" role="navigation">
	<!-- Brand and toggle get grouped for better mobile display -->
	<div class="navbar-header">
		<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
			<span class="sr-only">Toggle navigation</span>
			<span class="icon-bar"></span>
			<span class="icon-bar"></span>
			<span class="icon-bar"></span>
		</button>
		<a class="navbar-brand" href="#">Brand</a>
	</div>
 
	<!-- Collect the nav links, forms, and other content for toggling -->
	<div class="collapse navbar-collapse navbar-ex1-collapse">
		<ul class="nav navbar-nav">			<li class="active"><a href="#">Link</a></li>			<li><a href="#">Link</a></li>			<li class="dropdown">				<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown <b class="caret"></b></a>				<ul class="dropdown-menu">					<li><a href="#">Action</a></li>					<li><a href="#">Another action</a></li>					<li><a href="#">Something else here</a></li>					<li><a href="#">Separated link</a></li>					<li><a href="#">One more separated link</a></li>				</ul>			</li>		</ul>	</div><!-- /.navbar-collapse -->
</nav>

Read on for full details!

Read More »

Posted in PHP

If you need to do multiple CURL requests with PHP, curl_multi is a great way to do it. The problem with curl_multi is that it waits for all requests to complete before it begins processing any of the responses – for example if you’re making 100 requests and just one is slow, the other 99 will be held off for processing until the one has come back. This is wasteful so I’ve whipped up a script originally written by Josh Fraser with my own modifications and improvements that will begin processing a response immediately before dispatching the next request.
 

Usage:

The $callback (second) argument of rolling_url() can take any callback supported by PHP.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
function curl_multi_download(array $urls, callable $callback, array $custom_options = array())
{
	// make sure the rolling window isn't greater than the # of urls
	$rolling_window = 5;
	$rolling_window = (sizeof($urls) < $rolling_window) ? sizeof($urls) : $rolling_window;
 
	$master = curl_multi_init();
	$curl_arr = array();
	$options = array(
		CURLOPT_RETURNTRANSFER => true,
		CURLOPT_FOLLOWLOCATION => true,
		CURLOPT_MAXREDIRS => 5,
	) + $custom_options;
 
	// start the first batch of requests
	for ( $i = 0; $i < $rolling_window; $i++ )
	{
		$ch = curl_init();
		$options[CURLOPT_URL] = $urls[$i];
		curl_setopt_array($ch, $options);
		curl_multi_add_handle($master, $ch);
	}
 
	do
	{
		while(($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM);
		if($execrun != CURLM_OK)
			break;
		// a request was just completed -- find out which one
		while( $done = curl_multi_info_read($master) )
		{
			$info = curl_getinfo($done['handle']);
 
			// request successful.  process output using the callback function.
			$output = curl_multi_getcontent($done['handle']);
			call_user_func_array($callback, array($info, $output));
 
			if ( isset($urls[$i+1]) )
			{
				// start a new request (it's important to do this before removing the old one)
				$ch = curl_init();
				$options[CURLOPT_URL] = $urls[$i++];  // increment i
				curl_setopt_array($ch, $options);
				curl_multi_add_handle($master, $ch);
			}
 
			// remove the curl handle that just completed
			curl_multi_remove_handle($master, $done['handle']);
		}
	} while ($running);
 
	curl_multi_close($master);
	return true;
}

 

Bonus Round

Here’s a curl_download() function, used to grab the response from a single URL (For extra points switch out curl_exec() with curl_exec_utf8() using the same method as above):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function curl_multi_getcontent_utf8( $ch )
{
	$data = curl_multi_getcontent( $ch );
	if ( !is_string($data) )
		return $data;
 
	unset($charset);
	$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
 
	/* 1: HTTP Content-Type: header */
	preg_match( '@([\w/+]+)(;\s*charset=(\S+))?@i', $content_type, $matches );
	if ( isset( $matches[3] ) )
		$charset = $matches[3];
 
	/* 2: <meta> element in the page */
	if ( !isset($charset) )
	{
		preg_match( '@<meta\s+http-equiv="Content-Type"\s+content="([\w/]+)(;\s*charset=([^\s"]+))?@i', $data, $matches );
		if ( isset( $matches[3] ) )
			$charset = $matches[3];
	}
 
	/* 3: <xml> element in the page */
	if ( !isset($charset) )
	{
		preg_match( '@<\?xml.+encoding="([^\s"]+)@si', $data, $matches );
		if ( isset( $matches[1] ) )
			$charset = $matches[1];
	}
 
	/* 4: PHP's heuristic detection */
	if ( !isset($charset) )
	{
		$encoding = mb_detect_encoding($data);
		if ($encoding)
			$charset = $encoding;
	}
 
	/* 5: Default for HTML */
	if ( !isset($charset) )
	{
		if (strstr($content_type, "text/html") === 0)
			$charset = "ISO 8859-1";
	}
 
	/* Convert it if it is anything but UTF-8 */
	/* You can change "UTF-8"  to "UTF-8//IGNORE" to
	   ignore conversion errors and still output something reasonable */
	if ( isset($charset) && strtoupper($charset) != "UTF-8" )
		$data = iconv($charset, 'UTF-8', $data);
 
	return $data;
}

See inside for the full code.

Read More »

Posted (Updated ) in PHP

As I’m sure is the case with just about every WordPress user, my site is constantly hit with failed login attempts from bots – usually originating from other countries. I had the idea last night to implement country-based login restrictions using CloudFlare’s IP Geolocation server variable.

My goal was to redirect redirect all users visiting the login page from countries other than Australia to the home page. This occurs before a login attempt can even be made.

Here’s how it’s done:

  • Go into CloudFlare settings and make sure the IP Geolocation option is turned onMake sure CloudFlare IP Geolocation is turned on
  • Add the following to your active themes functions.php file
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    // Limit login countries - requires cloudflare
    add_filter( 'authenticate', 'login_failed', 1, 3);
    function login_failed( $user, $username, $password )
    {
        if ( !isset( $_SERVER['HTTP_CF_IPCOUNTRY'] ) )
            return $user;
     
        if ( !in_array( $_SERVER['HTTP_CF_IPCOUNTRY'], array('AU') ) )    {
            wp_redirect( home_url() );
            exit;
        }
     
        return $user;
    }

To test simply use a country other than your own. Once it’s confirmed working switch to your country and you’re good to go!

Read More »