$_FILES and $_POST empty in PHP When Uploading Large Files

Posted October 15th, 2013 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 »

Add to or Override the Default WordPress RSS Feed

Posted October 14th, 2013 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 »

Restrict WordPress Admin to Specific User Groups

Posted October 14th, 2013 (Updated 15 Oct 2013) 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 »

How to Regenerate Thumbnails in WordPress

Posted October 14th, 2013 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 »

Include Taxonomy Terms in WordPress get_posts() for Efficient Lookups

Posted October 9th, 2013 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_SIZE;
 
	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 »

Getting Bootstrap 3 NavBar Working with WordPress wp_nav_menu()

Posted October 7th, 2013 (Updated 9 Oct 2013) 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 »

How to Find and Remove @eaDir Directories on Synology NAS

Posted September 26th, 2013 in Linux

If you’ve installed the MediaServer or PhotoStation packages on your Synology NAS you’ve probably noticed @eaDir directories popping up everywhere. These are “hidden” folders equivalent to thumbs.db on Windows where the package stores thumbnail files associated with iTunes support. If you’re not using iTunes you don’t need these directories. You can remove them in two steps:

Disable the Service Creating Them

SSH in as root and run the following:

1
2
cd /usr/syno/etc.defaults/rc.d/
chmod 000 S66fileindexd.sh S66synoindexd.sh S77synomkthumbd.sh S88synomkflvd.sh S99iTunes.sh

Remove the existing directories

Again in SSH use the following to locate them (cd to your volume root first):

1
find . -type d -name "@eaDir"

and if you’re feeling adventurous you can automatically delete them like so:

1
find . -type d -name "@eaDir" -print0 | xargs -0 rm -rf

Read More »

A More Efficient (UTF-8) curl_multi()

Posted August 20th, 2013 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
curl_multi_download(array(
	"http://translink.com.au/travel-information/services-and-timetables/trains/rss",
	"http://translink.com.au/travel-information/services-and-timetables/buses/rss",
	"http://arandombrokenurl.com/should/give/an/error",
	"http://translink.com.au/travel-information/services-and-timetables/ferries/rss",
), 'process_response');
 
function process_response( $info, $response ) 
{
	if ( $info['http_code'] != 200 )
	{
		echo "Error retrieving URL " . $info['url'] . "<br/>";
		return;
	};
	var_dump($info);
	var_dump($response);
}

 

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
function curl_download( $url )
{
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
	$data = self::curl_exec($ch);
	curl_close($ch);
 
	return $data;
}

See inside for the full code.

Read More »

Y Mouse Axis Off in VirtualBox

Posted July 11th, 2013 in Uncategorized

I just installed Ubuntu 13.04 in Virtualbox 4.2.16 and found much to my annoyance that the VM thought my mouse was a little higher than it actually was:

Ubuntu thinks my mouse is a little higher than it actually is

It turns out this is caused by having 3D acceleration turned on in VM Settings – Display window. After doing a bit of sleuthing I came across a forum post on virtualbox.org with a command that did the trick nicely.

Simply open a terminal and run

VBoxManage setextradata global GUI/Customizations noStatusBar

Restart your VM and voila. Perfect mouse working with 3D acceleration!

Mouse Y-Axis working as it should

Read More »

Google Charts ChartRangeFilter Mobile Workaround

Posted June 28th, 2013 (Updated 1 Jul 2013) in Javascript

Despite a global shift towards touch-oriented devices on the internet and the existence of a relatively old bug report, Google has decided not to add support for mobile/touch to their ChartRangeFilter for Google Charts (as of the time of writing). See for yourself:

Demo | Source - Doesn’t work on mobile. Frustrating!

This is really annoying, especially if you are dealing with a significant chunk of data that could take advantage of this functionality by allowing users to ‘zoom in’ on specific areas.

 

A Workaround (For Now)

Although we can’t get the ChartRangeFilter working on mobile, we can display something else instead for mobile devices such as the handy jQRangeSlider. To do so we’ll need to hook into the methods provided by google.visualization.ControlWrapper (which sits around the ChartRangeFilter).

In the source we have:

var controlWrapper = new google.visualization.ControlWrapper({
	controlType: 'ChartRangeFilter',
	...
});

We can update the selection in the ChartRangeFilter by calling the following on its ControlWrapper:

controlWrapper.setState({
	range: {
		start: new Date(2004, 01, 01),
		end: new Date(2006, 01, 01)
	}
});
controlWrapper.draw();

Combine this with a mobile UA check (terrible I know, but you can’t really use browser  feature detection for this) and a DateRangeSlider (I’m using dates on my X axis) and you get the following:

var is_mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
// ChartRangeFilter doesn't work on mobile. Use a dateRangeSlider to manipulate it
if ( is_mobile )
{
	$('#filter').hide();
	$('#filter_mobile').show();
 
	$( "#filter_mobile" ).dateRangeSlider({
		bounds: {
			min: new Date(2001, 1, 1),
			max: new Date(2013, 3, 1)
		},
		defaultValues: {
			min: new Date(2001, 1, 1),
			max: new Date(2013, 3, 1)
		},
		step: {
			months: 1
		},
		arrows: true,
		wheelMode: null
	}).bind('valuesChanged', function(e, data) {
		controlWrapper.setState({range: { start: data.values.min, end: data.values.max }});
		controlWrapper.draw();
	});
}

Demo

ChartRangeFilter workaround for mobile devices

ChartRangeFilter workaround for mobile devices

Try the demo below. View it on a mobile device (or use a mobile UA string). You get the standard Google Charts ChartRangeFilter for desktop and jQRangeSlider for mobile. Everything works nicely!

Demo | Source - Snazzy mobile-specific date range widget

Read More »

Page 2 of 1412345...10...Last »