WP_Query pagination problems? Here’s a hack

It happens every time I use WP_Query. I get a beautiful-looking custom WP_Query. Then I try to add pagination and everything goes to hell.

I’ve spent hours looking up solutions. Every time. Nine times out of ten, they don’t work. And for no discernible reason. Here’s a hack that worked for me, and is mostly for my own benefit so I don’t forget it next time I am in this situation, but maybe it will help you.

This hack requires Lester Chan‘s plugin WP-PageNavi. Go get it and come back.
For me, this applies specifically to using custom post types. If I wasn’t using CPTs, my WP_Query might not have any problems. Feel free to let me know in the comments if you spot an issue with the way I’m doing things. As I said, this is a hack — meaning it’s not the preferred way to do this — but it’s a functional hack at least.

Let’s start with the query:

			rewind_posts();
			global $wp_query, $paged;
			$temp = $wp_query;
			$wp_query = null;
			$wp_query = new WP_Query();

This is pretty basic. I’m running a rewind_posts() here because I have some custom queries going on in the sidebars, so I threw this in there just to be on the safe side. The next bit is where the hack starts coming in…

			$showposts = 12;
			$offset = ( ( $showposts * $page ) - $showposts );

Here we introduce two variables, both of which we will use in the arguments later. $showposts is a hard-coded rule for the posts_per_page argument. Yes, this is required. The reason will become evident by looking at the next line. Our $offset is being multiplied by the $showposts value (for me, this is 12) and then subtracted by that value again. This is how we are going to fix the issue of not being able to go into paged navigation because it only displays the first page worth of posts — by creating an offset that is multiplied by the current page you are looking at ($page is a global variable that is built into WordPress to display the current page).

Now the $args:

			$args = array(
				'post_type' => 'ap_products',
				'posts_per_page' => $showposts,
				'offset' => $offset,
			);
			query_posts($args);

This is pretty straightforward also. All we’re doing here is declaring the post type (which is just for my use, you can omit this if you aren’t using a custom post type, or replace with your own post type), and then feeding the $showposts and $offset values in here. Then we’re running our query. The full loop looks like this:

<?php the_post(); ?>
	<div <?php post_class(); ?> id="post-<?php the_ID(); ?>">	
	<?php 			
			rewind_posts();
			global $wp_query, $paged;
			$temp = $wp_query;
			$wp_query = null;
			$wp_query = new WP_Query();
			$showposts = 12;
			$offset = ( ( $showposts * $page ) - $showposts ); 
			$args = array(
				'post_type' => 'ap_products',
				'posts_per_page' => $showposts,
				'offset' => $offset,
			);
			query_posts($args); 	?>				
			<div class="entry">
				<?php while ( have_posts() ) : the_post(); ?>				
					<!-- do stuff -->
				<?php endwhile; ?>
				<?php if(function_exists('wp_pagenavi')) { echo '<div class="navigation"><span class="pre-navi"><a href="' . home_url() . '" />1</a></span>'; wp_pagenavi() . '</div>'; } ?>
				<?php $wp_query = null; $wp_query = $temp; ?>
			</div>
		</div>

Notice what I’m doing at the end with the wp_pagenavi function. I’ve added this into my style.css:

.current { display: none; }
.pre-navi { float: left; }

This makes it so that the current page doesn’t display at all. I’m doing this because the current page is always 1 with my fantabulous error, and this way I can replace the page 1 with one that actually links (if you’re on any page other than 1).

And that’s it. It’s not elegant, but it works, and the only real issue is that page 1 is always a link. If you like this solution (or if you hate it), let us know in the comments!

Multiple loops, post formats, and content types in a single home page template

Wow. That title is a mouthful. And given the improbable combination of different content types, I’m actually somewhat surprised that I did, in fact, pull off just that. In working on a client site for a Christian ministry in the Democratic Republic of Ghana, I set out today to create this:

ibm homepage Multiple loops, post formats, and content types in a single home page template

What we have here is a Dynamic Content Gallery at the top, a YouTube video, some static content, and then two different areas for recent posts in different formats. The key here is that everything should be dynamic. We’re not looking to slap a YouTube embed in a home page template; I want to be able to use the new WordPress Post Formats to send the most recent video post to that slot whilst juggling all sorts of other dynamic content from everywhere else and — if at all possible — avoid duplicating anything. Here’s to proving it’s not impossible.

So, the first step is to take care of the DCG. This is the easy part.

12
13
14
15
16
17
	/* first, if we have dcg installed, display dcg inside it's own div */ 
if (function_exists ('dynamic_content_gallery')){ ?>
	<div id="dcg">
		<?php dynamic_content_gallery(); ?>	
	</div>
<?php } ?>

I’ve actually got featured images set up on my other pages in this theme, so if you wanted to do an else for the case where DCG didn’t exist, you could do something like this:

	/* first, if we have dcg installed, display dcg inside it's own div */ 
if (function_exists ('dynamic_content_gallery')){ ?>
	<div id="dcg">
		<?php dynamic_content_gallery(); ?>	
	</div>
<?php } else {	
// if you don't have dynamic content gallery, maybe a featured image would work?
// this is borrowing the code pretty much straight from Twentyten, so you'll need to add this to your functions.php, too
// so check with Twentyten and get that sorted first
	if ( is_singular() && current_theme_supports( 'post-thumbnails' ) &&
			has_post_thumbnail( $post->ID ) &&
			( /* $src, $width, $height */ $image = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'post-thumbnail' ) ) &&
			$image[1] >= HEADER_IMAGE_WIDTH ) :
		// Houston, we have a new header image!
		echo get_the_post_thumbnail( $post->ID );
	elseif ( get_header_image() ) : ?>
		<img src="<?php header_image(); ?>" width="<?php echo HEADER_IMAGE_WIDTH; ?>" height="<?php echo HEADER_IMAGE_HEIGHT; ?>" alt="" />
<?php } ?>

(Note: this part isn’t tested, per se…I’m just combining two different things I have going on in this theme. It should work, but if if borks your theme let me know, and I’ll fix my code.)

Okay, back to my code.

After DCG, I’ve got a video and static content. The static content is going to be whatever they have on their Home page. The video is the most recent video post. We’re using Post Formats so — like the featured image snippet above — you’ll need to enable that in your functions.php to get that working before you try this.

19
20
21
22
23
24
25
26
<div class="content home">
<?php /* this is the main home page stuff...there's a lot going on here 
	     first of all, we're displaying the most recent VIDEO post */ 
	query_posts( 'showposts=1' );
		if (have_posts()) : while (have_posts()) : the_post(); 
			if (has_post_format( 'video' )) { ?>	
				<div <?php post_class(); ?> id="post-<?php the_ID(); ?>"><?php the_content(); ?></div>
        <?php } endwhile; endif; rewind_posts();

So far, so good, and nothing too complex yet, right? All we’re doing here is running a loop that’s limited to 1 post, and making sure that post is a video post. Here’s where it gets fun.

27
28
29
30
31
32
33
34
35
		/* next, we're displaying the regular home page content.  That's right, you heard me, we're adding page content in between a bunch of dynamic post stuff */
		$ibm_home_page = new WP_Query('post_type=page&name=home'); 
		while ($ibm_home_page->have_posts()) : $ibm_home_page->the_post(); ?>
			<div <?php post_class('home-content'); ?>>
				<?php the_content(); ?>
			</div>
		<?php endwhile; rewind_posts(); ?>
	<div class="clear"></div>
	<hr />

I’ve completely switched over to WP_Query to be able to grab a Page with the slug ‘home’. But since I did rewind_posts, the Loop isn’t completely freaking out. Underneath this, we’ve got even more fun times.

36
37
38
39
40
41
42
43
	<?php /* down here, we're doing two more things.  First order of business is to display the next post making sure that the next post ISN'T the video post above. we're also excluding sticky posts here to get a more linear order going on. */ 
	query_posts( 'caller_get_posts=1&showposts=1' );
		if (have_posts()) : while (have_posts()) : the_post(); if (has_post_format( 'video' )) { query_posts('offset=1&caller_get_posts=1&showposts=1'); } else { ?>
			<div <?php post_class('home-excerpt'); ?> id="post-<?php the_ID(); ?>">
				<a href="<?php the_permalink() ?>" rel="bookmark" title="Permanent Link to <?php the_title_attribute(); ?>"><h2 class="the_title"><?php the_title(); ?></h2></a>
				<?php the_excerpt(); ?>
			</div>
		<?php } endwhile; endif; rewind_posts(); ?>

Woah there. It was at this point that I started running into a couple snags. First of all, I kept getting any sticky posts popping up at the top of the list, above my single post I was trying to display. This is only supposed to be a single post excerpt, so that was not going to work. Second, the next post, under my sticky post, just happened to be my video post which I created to test the video Post Format. Which is ridiculous, right? You don’t want your video and then a big empty space under your video (because I’m using the_excerpt rather than the_content) with the title of the post for…your video. And that would be bound to happen anytime you posted a video. Ninja coding skills needed to be summoned. Or, barring that, ninja Googling skills…

The first half of the query is straightforward. caller_get_posts gets the next posts in the loop sans sticky posts. Golden. And we only want one, so that part’s obvious. Next, after we do our normal loop stuff, I throw in this if (has_post_format( 'video' )) stuff that looks like it does pretty much the same thing. Well…it does. With one exception: if the next post is a video post, we’re going to offset the query by one and grab the one after that, effectively skipping your video. It’s ugly but it works, and that’s all I wanted.

This last bit is just recent updates. With all the querying and offsetting, I know we’re going to have some issues here. But I don’t care too much about this section because all I’m looking to do here is display some links from some recent posts. On another site, I might do random posts or popular posts or most commented posts here, and none of the stuff we’ve done up to now would even matter for these. Instead, what I’m doing is adding an extra offset, and if I skip a post, no big deal — chances are, that post will be featured in the DCG above, anyway.

44
45
46
47
48
49
50
51
52
53
	<div class="home-updates">
		<h2>Recent Updates</h2>
		<?php /* finally, this last section is just a recent posts area.  we care a bit less here about sticky posts or video posts, so really the only thing I'm concerned about is the offset, which i've incremented by 1 extra just in case.  this may result in some duplicates, or, alternately, some posts missing, but, again, they're just links and they're not taking up much real estate and I'm not too concerned about it */
		query_posts( 'showposts=5&offset=2' );		
			if (have_posts()) : while (have_posts()) : the_post(); ?>			
				<a href="<?php the_permalink() ?>" rel="bookmark" title="Permanent Link to <?php the_title_attribute(); ?>"><strong><?php the_title(); ?></strong></a><br />
			<?php endwhile; endif; rewind_posts(); ?>
	</div>
</div>    
<div class="clear"></div>

And there you have it. The real star of the show here is rewind_posts. If you’ve ever done multiple loops before rewind_posts came along, you’ll know that all sorts of wacky stuff can come up that throws your loop(s) off. rewind_posts allows you to use multiple loops in the same template, and then go on and do some more loops somewhere else, and nothing goes horribly awry.

Hopefully this gives someone out there some ideas of what you can do with multiple loops using query_posts. There isn’t a lot of stuff out there yet on Post Formats — particularly excluding post formats, so that was one area I sort of had to wing it. If anyone has some awesome ideas, I’d love to hear them.

(By the way, all the example text used in the mockup above comes courtesy of F*** Lorem Ipsum, which I’ve been using as an alternative to the Lorem Ipsum Generator I’ve used for years. F*** Lorem Ipsum pulls text from TV shows and commercials and is able to throw in links and line and paragraph breaks, too.)

How to add customizable header images and customizable backgrounds to your WordPress theme

One of the coolest new features of WordPress 3.0 is the ability to customize the site header image and background color or image. The custom header actually goes one step further and lets you set a unique header for each of your pages. This means that you can create unique headers that tie into the content of the page without complex if (is_page()) statements or hard coding them into unique page templates. The only problem is that these features need to be added into the theme for them to show up as options in your wp-admin panel and not every free (or even commercial) theme has those options set up and ready to go.

Not all themes are made for big, changeable headers, but for those that are or would adapt well, this gives users a powerful resource far beyond the custom header features that Kubrick, the previous default WordPress theme, offered with its’ ability to change the header color and gradient. Likewise, I’m tentative about giving users the ability to pick a color at random from the 16 million colors at their disposal – it’s too easy to create clashes worthy of the most gaudy GeoCities or MySpace creations. However, used with discretion and a keen eye, these features add a powerful tool to those WordPress users’ arsenal who are just itching to add a little bit of personality to their site without breaking everything.

Our newest free theme, Color Garden includes both custom headers and custom backgrounds, making it our first theme – free or otherwise – to offer both. I also played with custom headers on my recent redesign of jazzsequence.com. For the headers, it’s not the most intuitive thing to add by hand at all, and few of the resources I have found go far beyond quoting the code from TwentyTen’s functions.php file, so we wanted to give our readers just a little bit more to go on if you want to add these features to your blog theme.

Custom backgrounds are dead simple. A single line of code and you’re done:

// This theme allows users to set a custom background
add_custom_background();

Once this is included somewhere in your functions.php file, you will have a Background link under Appearance that gives you options to set a solid color for the background (by hex code or picking from a color map) or upload an file to use as a repeating background image.

Headers are a bit more complex.

To really understand how custom headers work, you need to know how post thumbnails work. Post thumbnails were added to WordPress core in 2.9 and use the built-in resizing process that happens when you upload images to create the thumbnails that will appear next to your post or post excerpt. This is presumably to replace other third-party scripts like the ubiquitous TimThumb which creates its own thumbnails and stores it in a cache folder usually located in the theme folder (although it could really be anywhere). Since WordPress 3.0, what was previously referred to as Post Thumbnail on the Add New Post page is now called Featured Image. This change is reflects the fact that the Featured Image option now controls the post thumbnail or the custom header, and the way it knows the difference is by the size of the image you are using.

Let’s take a look at how to add it to your theme and then we can revisit how to use it once it’s there. The code I will be showing comes from Color Garden although all of it has been adapted from TwentyTen (including much of the comments). Since most other posts I’ve found don’t discuss each piece of the header image function, I will go over each so you understand just what it is that you’re adding and what needs to be changed or left alone.

The first part is here:

// Your changeable header business starts here
// No CSS, just IMG call. The %s is a placeholder for the theme template directory URI.
define( 'HEADER_IMAGE', '%s/images/headers/leaves.jpg' );

The key thing to pay attention to in this snippet is the HEADER_IMAGE definition. This is the default header image. If this is left blank or set to an invalid path, no image will show up, and depending on your CSS, the layout might break. In TwentyTen this is set to %s/images/headers/path.jpg. You’ll probably want to put something here even if you plan on changing it later, and you’ll want to make sure the image actually exists where you say it exists.

The next part sets the height and width of your header image:

// The height and width of your custom header. You can hook into the theme's own filters to change these values.
// Add a filter to twentyten_header_image_width and twentyten_header_image_height to change these values.
define( 'HEADER_IMAGE_WIDTH', apply_filters( 'cg_header_image_width', 1000 ) );
define( 'HEADER_IMAGE_HEIGHT', apply_filters( 'cg_header_image_height', 343 ) );
 
// We'll be using post thumbnails for custom header images on posts and pages.
// We want them to be 1000 pixels wide by 343 pixels tall.
// Larger images will be auto-cropped to fit, smaller ones will be ignored. See header.php.
set_post_thumbnail_size( HEADER_IMAGE_WIDTH, HEADER_IMAGE_HEIGHT, true );

Notice that we’re using set_post_thumbnail_size with the header image’s dimensions. This is because the custom header function uses the existing post thumbnails function from 2.9 and builds onto it. As the comments indicate, images that are equal to or larger than the height and width definitions will be resized to fit and set as the header image when you add it to your post or page. Anything smaller will be ignored and set as the post thumbnail (if post thumbnails are enabled).

The last part of the custom header function is this:

// Don't support text inside the header image.
define( 'HEADER_TEXTCOLOR', '' );
define( 'NO_HEADER_TEXT', true );
// ... and thus ends the changeable header business.

The NO_HEADER_TEXT and HEADER_TEXTCOLOR add the ability to change the color of the text that appears in the header. The alternative (pulled from the WordPress Codex) would be to use:

define('HEADER_TEXTCOLOR', 'ffffff');

and omitting the NO_HEADER_TEXT line.

Now this part is done, you’ve enabled custom headers and defined a default. But what if you want to have multiple defaults like TwentyTen? In that case, we go back to TwentyTen’s functions and keep digging:

// Default custom headers packaged with the theme. %s is a placeholder for the theme template directory URI.
register_default_headers( array(
 
'leaves' =&gt; array(
'url' =&gt; '%s/images/headers/leaves.jpg',
'thumbnail_url' =&gt; '%s/images/headers/leaves-thumbnail.jpg',
/* translators: header image description */
'description' =&gt; __( 'Leaves', 'cg' )
),
 
'sunset' =&gt; array(
'url' =&gt; '%s/images/headers/sunset.jpg',
'thumbnail_url' =&gt; '%s/images/headers/sunset-thumbnail.jpg',
/* translators: header image description */
'description' =&gt; __( 'Sunset', 'cg' )
),
 
'beach' =&gt; array(
'url' =&gt; '%s/images/headers/beach.jpg',
'thumbnail_url' =&gt; '%s/images/headers/beach-thumbnail.jpg',
/* translators: header image description */
'description' =&gt; __( 'Beach', 'cg' )
),
 
'blueberries' =&gt; array(
'url' =&gt; '%s/images/headers/blueberries.jpg',
'thumbnail_url' =&gt; '%s/images/headers/blueberries-thumbnail.jpg',
/* translators: header image description */
'description' =&gt; __( 'Blueberries', 'cg' )
)
) );

Again, we’re looking at the code from Color Garden but it’s adapted from TwentyTen’s functions.php. Each item in the array above defines one of the default header image options. Color Garden has four default headers to choose from to go with the four color schemes built into the theme. All we’re really doing here is adding the path to the image, the path to the thumbnail of the image that appears on the Headers page in the WordPress backend, and the description which – as far as I have been able to tell – really only affects the alt text of the image in the backend.

All said, what you should be looking at in your functions.php file when you’re done is this (if you’re including multiple default header images):

// Your changeable header business starts here
// No CSS, just IMG call. The %s is a placeholder for the theme template directory URI.
define( 'HEADER_IMAGE', '%s/images/headers/leaves.jpg' );
 
// The height and width of your custom header. You can hook into the theme's own filters to change these values.
// Add a filter to twentyten_header_image_width and twentyten_header_image_height to change these values.
define( 'HEADER_IMAGE_WIDTH', apply_filters( 'cg_header_image_width', 1000 ) );
define( 'HEADER_IMAGE_HEIGHT', apply_filters( 'cg_header_image_height', 343 ) );
 
// We'll be using post thumbnails for custom header images on posts and pages.
// We want them to be 1000 pixels wide by 343 pixels tall.
// Larger images will be auto-cropped to fit, smaller ones will be ignored. See header.php.
set_post_thumbnail_size( HEADER_IMAGE_WIDTH, HEADER_IMAGE_HEIGHT, true );
 
// Don't support text inside the header image.
define( 'HEADER_TEXTCOLOR', '' );
define( 'NO_HEADER_TEXT', true );
 
// ... and thus ends the changeable header business.
 
// Default custom headers packaged with the theme. %s is a placeholder for the theme template directory URI.
register_default_headers( array(
 
'leaves' =&gt; array(
'url' =&gt; '%s/images/headers/leaves.jpg',
'thumbnail_url' =&gt; '%s/images/headers/leaves-thumbnail.jpg',
/* translators: header image description */
'description' =&gt; __( 'Leaves', 'cg' )
),
 
'sunset' =&gt; array(
'url' =&gt; '%s/images/headers/sunset.jpg',
'thumbnail_url' =&gt; '%s/images/headers/sunset-thumbnail.jpg',
/* translators: header image description */
'description' =&gt; __( 'Sunset', 'cg' )
),
 
'beach' =&gt; array(
'url' =&gt; '%s/images/headers/beach.jpg',
'thumbnail_url' =&gt; '%s/images/headers/beach-thumbnail.jpg',
/* translators: header image description */
'description' =&gt; __( 'Beach', 'cg' )
),
 
'blueberries' =&gt; array(
'url' =&gt; '%s/images/headers/blueberries.jpg',
'thumbnail_url' =&gt; '%s/images/headers/blueberries-thumbnail.jpg',
/* translators: header image description */
'description' =&gt; __( 'Blueberries', 'cg' )
)
) );

What I haven’t said yet is that in every instance that cg appears in the code, this specifically references Color Garden. In TwentyTen it’s twentyten. You can put whatever you want here so long as it remains internally consistent.

After you’ve got your functions.php set up, you still need to add in some code in your header.php to actually display the custom header image. What we have in Color Garden is taken almost verbatim out of TwentyTen.

	  <?php
          // Check if this is a post or page, if it has a thumbnail, and if it's a big one
          if ( is_singular() &&
                  has_post_thumbnail( $post->ID ) &&
                  ( /* $src, $width, $height */ $image = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'post-thumbnail' ) ) &&
                  $image[1] >= HEADER_IMAGE_WIDTH ) :
              // Houston, we have a new header image!
              echo get_the_post_thumbnail( $post->ID, 'post-thumbnail' );
          else : ?>
              <img src="<?php header_image(); ?>" width="<?php echo HEADER_IMAGE_WIDTH; ?>" height="<?php echo HEADER_IMAGE_HEIGHT; ?>" alt="" class="masthead" />
          <?php endif; ?>

This is where the theme asks “what is the size of the featured image?” and, based on the size, determines whether it is a header image or a post thumbnail.

Now, obviously there needs to be some CSS styling involved and you may or may not want your header image to be 1000 pixels wide by 343 high like ours is in Color Garden. We added a “masthead” class to the image so we could isolate styles to our header image. But, if you’ve made it this far, you’ve got everything you need to tweak the code to suit your theme. The custom headers function is exciting because it doesn’t need to be limited to just header images. You can use it wherever you might want a customizable image in your theme, and there’s more than a few creative uses I can think of beyond headers for something like that.

You might also be interested in these:

  1. How I fixed the “cannot redeclare _checkactive_widgets()” error in WordPress child themes
  2. How to upgrade your Museum Theme to the most current version after you’ve made changes to the files
  3. 12 Free WordPress Themes I Like (and you should, too)

web site security

can we really be that surprised by a hosting company that uses sex to sell its services?

i was listening to the replay of the teleconference hosted by WPSecurityLock (with special guests from GoDaddy) in light of the recent wave of website hacks that affected hundreds of sites not once, but twice.  it was actually when they were talking to a customer (one who had been hit twice) that a concern was raised in my mind.  the exchange went something like this:

a GoDaddy representative was on the line talking about ways to protect your site against attack, emphasizing the importance of keeping your software (be it WordPress, Joomla!, or whatever) updated.  — note: for the record, this seems like a lame cop-out.  yes, it’s great to keep your software updated, but when the attack is indiscriminately affecting php files — whether they belong to a known open source software or are completely custom-coded — i don’t see how this has any relevance on the situation at hand.  it should be noted that neither GoDaddy nor WPSecurityLock have been able to identify how intruders were able to access users’ sites and change the file permissions which allowed them to inject malicious code into the php files.  software version doesn’t really have any bearing whatsoever on that. —

after he said his piece, Regina from WPSecurityLock spoke with a customer who suffered in the first wave of attacks — actually the first client that they (WPSecurityLock) fixed, and then fixed again when the second wave hit.  she started talking about how, after the second intrusion, she noticed that all the files were left completely open in terms of file permissions (i.e. 777) and that she didn’t think he would have installed it that way.  he expressed gratitude for having them on his team because he admitted that he had absolutely no idea what she was talking about.

and that’s the problem isn’t it?

GoDaddy and other web hosts are saying you are responsible for your files.  you should know what goes into a WordPress installation so you can identify anything weird that’s not part of it.  you are expected to be familiar with FTP and changing file permissions.  but i think that most people hear “file permissions” and it’s like you’re suddenly speaking like the teachers in Charlie Brown: wah wah, wah-wah wah-wah wah.

godaddy: drunk on the job or just intoxicated by mon--er, success?you need to speak to the lowest common denominator here.  if you’re going to provide 1-click installations for any software at all, you have to make sure that when your auto-installer does its’ job it’s not leaving customers open to attack.  because no one that’s going to use a 1-click installer is going to know anything about FTP or chmod, that’s why they used the installer.  and even some people clever enough to know their way around FTP and WordPress’ patented 5-minute install might not know the proper file permissions for their site and just use 777 because it works.  we are lazy.  we use the same single password for everything we do online.  we can’t be expected by our service providers to be educated on proper security practices and safety procedures.  that’s what the geeks with the smelly t-shirts and glasses that make them look bug-eyed are for (although i wrote about some ways to help make your website more secure on arcane palette on tuesday).  no, it shouldn’t be the webhost’s responsibility to wipe their customers’ butt for them when it comes to securing their site, but neither do i think it’s fair that hosts honestly expect otherwise.  especially if one infected site on a server can spread to any or all the other sites hosted on the same shared server which seems like it was the case for both GoDaddy and Network Solutions.

wait, websites?  i just came for the pornit would be great if everyone remembered to change permissions on their files after installing software like WordPress.  it would be great if everyone knew and used the special extra security tricks WPSecurityLock mentions on the call, on their blog and in their free e-book.  but, i’m looking at you guys here, webhosts: the files may belong to your customers but they’re on your servers.  they, apparently, affect all the other sites on your servers (or have the potential to, anyway).  and you can point the finger everywhere except yourself as much as you like — you can say it’s the customer’s responsibility to keep proper permissions, you can say that old software has known exploits that can be used by hackers and that upgrading your software can even leave artifacts behind from older versions (so that, even if you are upgrading your software, you still aren’t safe) — but none of those things are going to make you any friends.  none of those things are going to make you into the good guy.  you know what is going to make you the good guy?  thinking for your customers.  taking care of the situation before it becomes a situation.  taking the role of assigning and/or correcting the file and directory permissions on a website out of the customer’s hands and taking responsibility for that yourself.  surely, by now, webhosts, you’ve figured out that people aren’t going to do something just because they’re told to do it.  surely you don’t really expect people to walk away from these widespread hacks and say “gee, i guess i should be more careful next time.”  unless that’s really part of the plan: give the user the responsibility, then when the shit hits the fan you can say “well, it wasn’t really our fault, but we can have one of our security analysts fix it for you for $150.”

wait.  nevermind.  i see the business model now.

i wish i could say that i made these pictures up, but this is actually how godaddy markets themselves

GoDaddy-hosted sites at risk — WordPress, Joomla!, Pligg, ZenCart, others…

Recently, there was a malicious attack on GoDaddy-hosted sites. It’s tough to track down the exact date of the original attack — I was first able to find a mention of it on Slashdot from 4/26, and I found a topic in the WordPress support forums that supported that approximate time. However, I also found another post elsewhere with a Unix/Linux shell script that would fix what appears to be the same issue which was dated March 2, so this may have been around much longer.  At any rate, there seems to have been another major wave this past weekend.

Last month, Network Solutions was targeted by what appeared to be a malicious user who was able to gain access to sites’ databases by accessing the database server, username and password from the wp-config.php file common to all WordPress installations.  The attack took advantage of the fact that most users don’t change their file permissions, and left the wp-config.php file — with all the above information that it contained — in a public-readable state.  Once the database username and password were obtained, the hacker added a redirect script in the Site URL setting in the options stored in the database which redirected visitors to a site of his choosing.

This taught us the lesson to make sure your files — especially the important files with sensitive data contained within them, like passwords — are properly secured by correct file permissions.

This particular hack on GoDaddy sites did not attack the database, the upside of which means your data, stored passwords, posts, and everything else are still safe. However, according to the reports I found, what it did was embed a small javascript at the bottom of every php file.  The embedded code would ultimately cause visitors to be redirected to a malicious site that would install malware on their PC.  Based on the domain names that were grabbed by an independent security analyst, it looks like the end-goal was to infect a visitor’s computer with malicious software or viruses and then sell them antivirus software (the virus-scanning effectiveness of which would, presumably, be fairly suspect, given the circumstances) — the domains were things like safelinkhere.net, cleanupantivirus.com, letme-guardyourzone.com, systemmdefender.com, etc.  That post also points out that the person or people behind this attack have done this before.

So you have a GoDaddy site running WordPress, how do you know your site is infected?  If your site was infected, you would see this when you logged into your dashboard:

At first glance it just looks like the CSS is messed up, not anything that might suggest a virus.  However, when I tried to access the admin panel on two GoDaddy-hosted WordPress sites that we support, my antivirus software gave me a virus warning.  This happened, too, when the Network Solutions hack went down, so I became instantly suspicious.  (My virus software, by the way, is avast! which can be downloaded free for personal use.)

GoDaddy’s official initial response to the issue — which has been posted in a couple different places — was this:

A few of our customers were affected. Here’s what our CISO had to say about it:

“WordPress is a-ok. Go Daddy is rock solid. Neither were ‘hacked,’ as some have speculated.

After an extensive investigation, we can report there was a small group of customers negatively impacted. What happened? Those users had outdated versions of the popular blogging software, set up in a particular way.

This underscores the importance of installing the latest Web applications, no matter where you are on the Internet. If you use Hosting Connection, automatically update WordPress to version 2.9.2 using the simple 3-step update offered when you log-in.

And, while we’re on the topic of Web security and Best Practices – be sure all your online passwords are unique, secure, and in a safe place.”

If you have questions or you’d like someone to take a look at your WordPress site, please get in touch with our 24/7 support team at http://fwd4.me/MBI

Alicia

Here’s the problem with that statement: it’s wrong.  Both the sites that I saw directly were running the 2.9.2 version of WordPress.  And I’m not alone.  I’ve read reports in forums and blog comments from many users who were not only running up-to-date versions of WordPress, but also had secure and unique passwords for their WordPress backend, FTP and database, and many had countermeasures in place to prevent attacks on their WordPress site.  None of which protected them from being infected.  Later, GoDaddy’s Chief Information Security Officer sent out the following message which is currently appearing on their support page:

If you are experiencing difficulties with your site, you may be using outdated software and unknowingly hosting malware…And, while we’re on the topic of Web security and Best Practices – be sure all your online passwords are unique, secure and in a safe place.

Calls to GoDaddy support about the issue right now are apparently being met with “this is a WordPress issue”-type responses.  But careful viewers of how this particular infection works will see that there isn’t anything specific to WordPress about it — it’s targeting php files on GoDaddy-hosted sites.  Whereas the Network Solutions hack was looking for a specific file — wp-config.php — to gain access to the database, php files can be in any software written in that language, which includes Joomla!, Drupal, ZenCart, Magento, and on and on.  Blaming the issue on bad security practices is a gross misrepresentation of the facts. As of this writing, GoDaddy hasn’t yet tracked down the source of the infiltration or taken much more responsibility for the issue than the above statements.

The good news is that, as far as I can tell, there isn’t anything you did wrong to make your site more of a target. The bad news is no one is sure how the hacker was able to access a huge number of sites on GoDaddy and append a javascript code at the bottom of every php file on those sites. The fact that GoDaddy’s canned response inaccurately suggests that the affected installations were outdated isn’t very reassuring.

Fear not, for there is a fix.  One thing the infected sites seem to have in common is that they are all hosted on Linux servers.  If your GoDaddy site is hosted on a Linux server, you can use the History feature to restore an earlier backup of the files. Since the database was untouched, this won’t affect any posts you may have published, however it may (or may not) affect any media files you uploaded between the time you restore back to and now — so it’s probably a good idea to check those posts for broken image links and the like after you do it to make sure everything is okay.  Check out this help file for full instructions on how to restore your site to an older backup.

Over the weekend, GoDaddy posted a fix to their forums that they referred to as a “permanent” fix. The updated fix involves backing up your database, saving any files you may have uploaded to your site, deleting everything, then reinstalling and restoring your database backup. While this would resolve the issue, until they announce that they know where these attacks are coming from and why they are so widespread, I can’t see this as a “permanent” fix — it’s not any more permanent than restoring from a backup (presuming that the virus wasn’t present at the time the restore point you are using was created).  And evidence suggests that security best practices wouldn’t safeguard your site against attack either.  That said, changing your passwords is probably not a bad idea.

I would like to reiterate that this is not a WordPress compromise, but a GoDaddy compromise. In this case, the sites that were reportedly vulnerable (those that were using a version of WordPress prior to 2.9.2) was inaccurate and given the nature of the attack, I don’t see what the WordPress version really has to do with anything anyway.  Even if we assume the attack only targeted WordPress, 2.9.2 addressed an issue in which users who were logged into the admin panel could look at Trashed posts from other authors. 2.9.1 resolved a number of minor bugs but no security holes. Neither of these would have prevented someone injecting malicious code into the php files of your WordPress installation. Until someone is able to expose the breach, I’m not convinced that any of the posted resolutions and prevention methods are “permanent” nor do I believe that this is the last we’ll see of this attack — from what I’ve been able to gather, many bloggers who fixed the problem last week were hit again this past weekend. (Not surprisingly, domain transfers from GoDaddy to another registrar quadrupled in the last week of April. This doesn’t necessarily mean hosting went with it, but I’d be surprised if the two things weren’t related.)

I don’t like to tell people that you shouldn’t use a certain webhost because of x, y and z. I will make recommendations based on my personal experience and suggest hosts that I’ve had good experiences with, but I’m not one to badmouth other companies to our clients or their decision to go (or stay) with that company. So I’m not going to tell you to switch to something else if you are using GoDaddy. However, if I was using GoDaddy, I’d be very concerned about my site’s security if it was running any kind of web application (blog or forum or CMS, WordPress or otherwise) until their Security team says something other than “make sure your passwords are secure.” What’s more, while Network Solutions was able to respond to, and secure the attack on their hosted WordPress sites within 24 hours (at least the initial attack, if not the subsequent attacks that came later), GoDaddy was hit with this, initially, sometime around April 21 or even much earlier.  I’ve seen no indication that they resolved the initial breach, let alone these new modifications.

If you have been compromised, or you are concerned about being compromised, I urge you to contact GoDaddy and express your concerns and try to learn what they are doing to prevent this attack from happening again. The more concerned customers they hear from, the more urgent the issue becomes and, hopefully, the faster they will work to resolve it.  However, if you would rather not be at risk just waiting for this weekend and the next wave of attacks, we highly recommend 1and1 for web hosting, and if you have questions or concerns about your site, you can contact us for a quote.  We can fix your infected WordPress site, help with your webhost transfer, or clean your infected php site that uses a different platform.

Tomorrow I will be posting an article about what you can do to keep your website as safe as possible.

If you would like to learn more about the issue, here are some of my sources:

Massive Number of GoDaddy WordPress Blogs Hacked [slashdot]
Warning! Massive Number of GoDaddy WordPress Blogs Hacked This Weekend [BlogcastFM]
GoDaddy/WordPress ninoplas Base64Virus and the Fix [inspirated]
GoDaddy WordPress blog hacked [WordPress Support Forums]
What people are saying about “GoDaddy WordPress” [Twitter Search]
GoDaddy Hacked WordPress Hosting Accounts [Smooth Blog]
WordPress on GoDaddy.com Hacked [NeoWin]
WordPress Compromised? How to Fix It! [GoDaddy]
GoDaddy Support [GoDaddy]
WordPress 2.9.2 [WordPress Development Blog]
WordPress 2.9.1 [WordPress Development Blog]
Restoring a Linux Hosting Account [GoDaddy]
GoDaddy Domains Lost by Transfer QUADRUPLE!! [NoDaddy]
Registrar Report for GoDaddy [WebHosting.Info]
Ninoplas Base64 WordPress Hacked on GoDaddy [WPSecurityLock]
Cechriecom.com.js.php – WordPress Hacked [WPSecurityLock]
GoDaddy’s Mass WordPress Blogs Compromise Serving Scareware [Dancho Danchev’s Blog]
Breaking News!  Dangerous Malware Alert — Self-Hosted Sites on Major Hosting Service Hacked Again! [WPSecurityLock]
Details on the Network Solutions/WordPress Mass Hack [Sucuri Security]
The Kneber botnet – FAQ [ZDNet]

[byline]