Alternate version of “The Loop”
September 1, 2010
OK So, most theme developers are aware of what the loop is… it displays your WordPress posts.
The one interesting thing about the loop is that it bounces in and out of the php and straight html output. So, most of the functions that are called do the echoes themselves. But what if need the output in a variable, or want to code it so that the call is part of its own echo function? You simply cannot use the same functions.
Fortunately WordPress provides two versions of each of these core functions. For example there is the_content and get_the_content() also the_permalink() and get_permalink() (yes, that inconsistency has ALWAYS bothered me..)
Here is a version of ”The Loop” which calls all of the alternate versions of the functions:
if (have_posts()) {
while (have_posts()) {
the_post();
echo ‘<div id=”post-’ . the_ID() . ‘”>’;
echo ‘ <div></div>’ . “\n”;
echo ‘ <div>’ . “\n”;
echo ‘ <div>’ . “\n”;
echo ‘ <h2><a href=”‘ . get_permalink() . ‘” rel=”bookmark” title=”Permanent Link to ‘ . the_title_attribute(‘echo=0′) . ‘”>’ . the_title(‘echo=0′) . ‘</a></h2>’ . “\n”;
echo ‘ <small> <!– by ‘ . get_the_author() . ‘ –></small>’ . “\n”;
echo ‘ <div>’ . “\n”;
$content = get_the_content();
$content = apply_filters(‘the_content’, $content);
$content = str_replace(‘]]>’, ‘]]>’, $content);
echo $content . “\n”;
echo ‘ </div>’ . “\n”;
echo ‘ </div>’ . “\n”;
echo ‘ </div>’ . “\n”;
echo ‘ <div></div>’ . “\n”;
echo ‘</div>’ . “\n”;}
echo ‘<div>’ . “\n”;
echo ‘ <div>’ . next_posts_link(‘« Older Entries’) . ‘</div>’ . “\n”;
echo ‘ <div>’ . previous_posts_link(‘Newer Entries »’) . ‘</div>’ . “\n”;
echo ‘</div>’ . “\n”;} else {
echo ‘<h2>Not Found</h2>’ . “\n”;
echo ‘<p>’ . “Sorry, but you are looking for something that isn’t here.” . ‘</p>’ . “\n”;
get_search_form();}
Determining what versions of WordPress you are hosting
August 10, 2010
If you host lots of different sites for people, one of the things you might want to know is what versions of WordPress each site is running.
WordPress stores the version number in a variable named $wp_version which is set in the file version.php.
With that information in hand, you can write a bash command that you run from your /home directory to display all of the WordPress versions you have on your server:
find . -name version.php -type f|xargs grep ^\$wp_version
This is one of the aliases I have in my .bashrc file.
WordPress Security – a plugin done right.
June 22, 2010
It’s rare that I open the source code for a random plugin and see every recommended security measure taken. When looking at Chris Boyd‘s plugin GeoLocation Plugin, I kept digging deeper and deeper and found he’d consistently covered everything. This plugin is a text book example of how to write a secure plugin.
Since he has so many techniques in here, this post wrote itself in my head. It was irresistible.
I strongly recommend you download the plugin and open geolocation.php to follow along
So, here are the security methods I saw him use, there may be more I didn’t catch, if so feel free to add them to the comments:
Use nonces on all input forms
Chances are your plugin will ask for information. A nonce is a security token that helps guarantee that you are getting real data from a real user. In function geolocation_inner_custom_box() Chris calls
echo '<input type="hidden" id="geolocation_nonce" name="geolocation_nonce" value="' . wp_create_nonce(plugin_basename(__FILE__) ) . '" />';
to add the nonce to his input form and then to check this on the processing side in function geolocation_save_postdata he calls:
// Check authorization, permissions, autosave, etc if (!wp_verify_nonce($_POST['geolocation_nonce'], plugin_basename(__FILE__))) return $post_id;
This is very simple code. There’s almost no cost to adding nonces to form processing and you can use that exact code, just changing the name of the field. This will block most bots from abusing your plugin.
Verify the user’s permissions
The user_can functions go hand in hand with the nonces and they work together to block the same kind of attacks. If your plugin does something that just anyone off the street shouldn’t be able to do, verify that the user is allowed to do it. This check is just a little bit further into function geolocation_save_postdata:
if('page' == $_POST['post_type'] ) { if(!current_user_can('edit_page', $post_id)) return $post_id; } else { if(!current_user_can('edit_post', $post_id)) return $post_id; }
This simple check to confirm that the user is allowed to edit a post before the geotagging information is added and ties the plugin directly into all of the security already built into WordPress. It is tremendously powerful.
Configure default values
In programming 101, you learn never to trust the computer to give you a clean value for an uninitialized variable. In web development, there are even more implications to this. Caching is one of those. If you call get_option and WordPress does not have a value, it will access the database to see if there is a value set. Until a value is stored in the database, WordPress has to keep looking for it on every page load. Anything you can do that avoids accessing the database files and quite possibly the hard drives where the information is stored, will make your site run faster. If a value is found, that value will be loaded from the cache automatically. Additionally, if you set your default values, you know what you are getting back. Chris handles this in function default_settings():
function default_settings() { if(get_option('geolocation_map_width') == '0') update_option('geolocation_map_width', '450'); if(get_option('geolocation_map_height') == '0') update_option('geolocation_map_height', '200'); if(get_option('geolocation_default_zoom') == '0') update_option('geolocation_default_zoom', '16'); if(get_option('geolocation_map_position') == '0') update_option('geolocation_map_position', 'after'); }
Use the Settings API and let WordPress do all the work
WordPress 2.7 added a wonderful set of tools called the Settings API. Most plugins do not take advantage of this complex tool set when they could. Chris uses the register_setting API call in function register_settings to enumerate the type of data he wants in each field.
function register_settings() { register_setting( 'geolocation-settings-group', 'geolocation_map_width', 'intval' ); register_setting( 'geolocation-settings-group', 'geolocation_map_height', 'intval' ); register_setting( 'geolocation-settings-group', 'geolocation_default_zoom', 'intval' ); register_setting( 'geolocation-settings-group', 'geolocation_map_position' ); register_setting( 'geolocation-settings-group', 'geolocation_wp_pin'); }
As you can see he specifies that certain values must be integers. Chris doesn’t take full advantage of the API in this plugin. In fact, I’m not convinced that simply calling register_setting on its own does anything. It is meant to be used with other calls. Had he used them all WordPress could have handled all sorts of things for him automatically, including the nonce field and additional checks.
The settings API is POWERFUL but has a steep learning curve. Once you pass the learning curve your input form printing just becomes five lines:
<form action="options.php" method="post"> <?php settings_fields('plugin_options'); ?> <?php do_settings_sections(__FILE__); ?> <input name="Submit" type="submit" value="<?php esc_attr_e('Save Changes'); ?>" /> </form>
This topic is WAAAAAAY to big to cover here and I freely admit, I’m no expert on it. I recommend that you read more: A quick & dirty example or The Whole Shebang
Verify your data
The last two topics are related as the come from the same rule: ALL data that you get from ANY source must be verified. This is where many plugins fail. The assumption is that “if it is in the database, it must be clean” or “it’s from an RSS feed, I know the data is good” or “The variable has INT in the name so it obviously contains an integer” or any of a thousand other gotyas. The way to keep yourself safe from an assumption is to VERIFY.
I want to highlight just two examples of this in Chris’s code.
First, in function display_location, he uses a cast to ensure the data he gets is what he wants from post meta:
$public = (bool)get_post_meta($post->ID, 'geo_public', true);
Second a function clean_coordinates uses regex to ensure the value he receives is what he wants:
$latitude = clean_coordinate($_POST['geolocation-latitude']); [..] function clean_coordinate($coordinate) { $pattern = '/^(\-)?(\d{1,3})\.(\d{1,15})/'; preg_match($pattern, $coordinate, $matches); return $matches[0]; }
Escape all variable data you display
I won’t show specific examples of this as they are everywhere throughout the whole plugin, but this is one of the most important steps you can take to make your plugin secure. It is also one of the areas that has has the most focus in the WordPress core over the years.
If you are displaying a value that isn’t hard coded in your source, you need to first feed it through one of these functions (or something similar):
esc_attr() Cleans HTML attributes for use on screen esc_html() Prepares complete blocks of HTML code esc_js() Special javascript handling of quotes and EOL characters esc_sql() Escapes data for use in a query esc_url() Returns a valid URL if possible esc_url_raw() Prepares an URL to be inserted in a database
This applies to values you are putting into javascript, urls you are displaying on the screen or using for includes, and data used to build image references. All this information must be sanitized. Visit the codex for more information on data validation.
Bonus Tip
If you’ve made it this far in the post, you get one more tip for free. DON’T DO IT ALL YOURSELF. I’d be willing to wager that this plugin was not ‘born’ with all of these security techniques in place. Though this plugin is attributed to Chris in the repository, he lists multiple authors for the plugin. You don’t HAVE to do all of this by yourself. Once your plugin is done, ask people to review it. I can pretty much guarantee that someone will find something. That’s just how it works. But won’t you feel much better if your friend catches the problem before it is discovered because your plugin added an avenue of attack to every site it’s been installed on? Someone will be glad to review it for you it. That’s part of what is great about the WordPress community: people love to help.
Summation
Website security will forever be an on going battle. It is a big, complicated subject with nuances you can miss easily. However, WordPress has been around long enough to have build up quite a collection of tools you have at your disposal as a plugin author. If use the tools WordPress provides, following Chris’s example outlined here, you can rest easy at night knowing you’ve done your job well.
Three helpful additions to your .bashrc
June 21, 2010
I just made a change to my .bashrc file and I thought I would share the tip. All of this is pretty basic stuff, but if you don’t customize your linux logins, this would be a good place to start.
For Microsoft people who don’t know, .bashrc is in some ways like a combined config.sys and autoexec.bat file. If you don’t know what an autoexec.bat file is, you totally missed the 80s dudes…
In a *nix environment, the rc at the end of a file name typically means that it is a “run control” file. Run Control files execute when a program starts. In this case, the program is bash – the command line interpreter/shell. Other programs look for rc files too. Because of this, you could have bunches of them in your home directory. The . at the front of the file name indicates that they are hidden from a normal directory listing. This way they don’t clutter up your home.
I have lots of neat things in my .bashrc file that add functionality to my default CLI. I’ll be sharing just three of those with you now.
The first is an alias: ebrc. When I type ebrc and press enter, I’m taken immediately into an editor with my .bashrc file open. You can think of an alias as a single line shortcut. It looks like this:
alias ebrc='vi ~/.bashrc'
As you can see, it just says “when I type ‘ebrc’, treat it like I really typed ‘vi ~/.bashrc’”.
The second thing I used tonight was the alias brc:
alias brc='. ~/.bashrc'
That executes the .bashrc file again, so that all of the changes I’d just made are loaded.
You might ask “Can’t you just type all that out? You’re not saving much time.” Go ahead.. ask. I’ll wait…
OK. The answer is Yes. So, it is important that you don’t go overboard on this stuff. If you use aliases too much, you’ll lose your familiarity with *nix and the skills to do your work on any other server. So proceed with caution. This stuff can be addictive and detrimental to your guru health.
Now with those two helper aliases in hand, I added the function I really wanted to include: ‘upskel’. It takes a task I might otherwise put off and allows it to be completed in 7 keypresses. This is the perfect use case for a .bashrc function.
‘upskel’ takes the latest version of WordPress and places it into the cpanel skeleton directory that is used as the base for every new account created on my hosting service eHermits, Inc.. So, every time an update comes out for WordPress, I can spend 5 seconds to grab the latest and all new accounts I create will be safe and updated.
Unlike an alias, this is done through a function call. Functions allow the use of multiple lines and variables. Here is the call I just added:
function upskel() { cd /root/cpanel3-skel rm -R public_html rm latest.zip* wget http://wordpress.org/latest.zip unzip latest.zip mv wordpress public_html }
Technically I probably could have done that as an alias but a function is much easier to read with multiple lines involved.
As a bonus, here is a function that takes a variable:
function ewpc() { cd /home/$1*/ pwd sudo vi ./public_html/wp-config.php }
Can you tell me what it does?
WordPress DB Hacks: Determining the ID of a category parents
June 3, 2010
When manipulating WordPress databases for exports and merges, sometimes it is helpful to get a list of all of the parents for the categories your posts are in.
For the import I am working on right now. The category list is being flattened from 30 categories down to 5 categories. The blog has just over 75 thousand posts in it and doing the conversion manually would be a rather arduous process to say the least.
So, I came up with this little query to give me the parent category for each of the child categories on that site.
It’s fairly simple but I’ve had to write it several times now and thought maybe someone else might need it sometime too:
SELECT `tt1`.`term_taxonomy_id` as 'Child Tax ID', `t1`.`term_id` as 'Child Term ID', `t1`.`name` as 'Child Name', `tt2`.`term_taxonomy_id` as 'Parent Tax ID', t2.`term_id` as 'Parent Term ID', `t2`.`name` as 'Parent Name' FROM `wp_term_taxonomy` `tt1`, `wp_term_taxonomy` `tt2`, `wp_terms` `t1`, `wp_terms` `t2` where `tt1`.`term_id` = `t1`.`term_id` and `tt1`.`parent` = `t2`.`term_id` and `tt1`.`taxonomy` = 'category' and `tt2`.`term_id` = `t2`.`term_id` and `tt2`.taxonomy = 'category';
Now I can just grab it instead of rewriting it again next time. After all, this is The Code Cave…
PS – If you just want to see the top level categories, here’s how:
SELECT `tt1`.`term_taxonomy_id`, `t1`.`term_id`, `t1`.`name` FROM `wp_term_taxonomy` `tt1`, `wp_terms` `t1`where `tt1`.`term_id` = `t1`.`term_id` and `tt1`.`taxonomy` = 'category' and `tt1`.`parent`=0
Why does Facebook keep telling me my email address is broken
June 3, 2010
Periodically I would get messages from Twitter and Facebook telling me that my email address is invalid. I would just hit reconfirm and it would work fine for a while.
When a client came to me and said he was getting the same messages, it became important to dig into it. What did I find? The Spam Cop is to blame.
Why is AVG blocking legitimate sites?
October 15, 2009
AVG is a “free” antivirus software package that has become fairly popular lately. The b5media tech team has been asked many times in the last 24 hours about why AVG is blocking legitimate sites. There is a FAQ on AVG’s site about this, but it is incomplete and, in my opinion, inaccurate*. People are asking the questions in the AVG forum, but responses have just been long paragraphs explaining how they’ve asked the question in the wrong forum and links to the FAQ. I’ll attempt to better address the question here.
The FAQ says:
There are several possibilities how a clean and legitimate website may become infected:
- Website was exploited by some hacktoolkit which searches for vulnerable websites, and automatically infects them.
- Infection was inserted on the machine that is used to create/upload websites, which means that the author’s/administrator’s computer is infected.
- An attacker gained direct access to the website administration thanks to a weak or stolen access password.
We recommend to contact the administrator of such website.
This may have largely been true 5 years ago, but it isn’t now. Yes, breaches in security happen and always will. However, the most common vector for malware to get on today’s sites is through ads. These ads aren’t even ever seen by the webserver you are visiting**.
The FAQ would be accurate if they added:
- The website is currently displaying an ad/image hosted on a site that AVG has deemed dangerous.
The unprinted bullet point is:
- We mess up. We are humans. We are not perfect and neither are the tools we use. Sometimes we will say a site is infected when it isn’t. Sometimes we will say something is malware, that really is, but it is so common place that blocking it will make so many sites unusable that we will have to back down. When this happens, we will try to “fix” the issue as quickly as possible.
This ad vector is something that all websites are fighting right now. It’s difficult because servers can be spotless and tight, but tools like AVG and the Google toolbar will see one of these ads (that we have nothing to do with) and will list that site as infected. If a bad ad is served when Google scans for infection, then the site is completely dropped from the Google index. NOT GOOD. Then the admin has to go and figure out which ad is bad and from that which ad manager let something slip through that they should have blocked.***
So, what is the current issue? Well, on October 14, 2009 AVG reclassified an ad/cookie used on a large majority of websites out there as dangerous. That’s why AVG is blocking familiar sites such as http://nytimes.com, http://imageshack.us, http://yahoo.co.uk, http://babelfish.yahoo.com, http://problogger.com as well as other b5media entities like http://everyjoe.com http://splendicity.com and http://blisstree.com
Everything seems to point to ads.YieldManager.com as what is being blocked. Yieldmanager.com has an Alexa rating of 198. Think what you want of Alexa, 198 means they are BIG. You probably know the name as they have been on most sites you’ve visit for years. YieldManager is run by Right Media, which, since 2007, has been controlled by Yahoo. This is why the ip addresses, that come up in the AVG LinkScanner alerts, all point to Yahoo.
In short, AVG will either change this decision or lose market share. It won’t take long for them to make up their mind.
UPDATE: Around 9am the forum included a post stating “UPDATE…. It’s now been ascertained that it was actually a false positive. Please update the AVG on your system.”
Forum users report that AVG has changed their statement from:
Let us inform you that this is not a false detection. Our LinkScanner
technology detects a real threat, which is Geo-IP and also browser
specific targeted, therefore it is detected only at www.yahoo.co.uk
(IP: ) in Mozilla Firefox web browser only.
Over to:
Unfortunately, the previous AVG Link scanner database might have
detected the mentioned web page as threat. However, after thorough
analysis we can confirm that it was a false alarm. We have released a
new Link scanner update that removes the false positive detection on
this web page. Please update your AVG and check if your are able to
open the web page properly.
As you can also see from that thread, some people received an update this morning, but still are having problems on popular sites. My only suggestion is that you update AVG a few more times and hope that they allow you to surf your favorite sites.
* Yes, I’ve submitted feedback about this.
** The way most ads work is a webpage will include instructions for your computer to say “Hey, you big sexy ad server, give me an ad to display.” when the page is loaded. At that point, the big sexy ad server says “Very well then. Go to ads.example.com and get the ad.”. Your computer then visits ads.example.com which can return any number of different ads. Some will be perfect nice ads. Some may be nasty infectious ads. Others will work on one browser, and throw errors on another. The site has no control over this. Really, neither does the big, sexy ad server (though it should run its own periodic checks) since the ad that appears at ads.example.com was probably legit when the ad was purchased. The ads that appear are only as reliably legit as the checks in place at that third party ad server.
*** That said, it is kinda interesting to look at all the sites out there listed as infected. For example go here to see an infected site and then bounce up from there into any of the 3 networks that site is hosted on. That will lead you to thousands of other infected listings.
How do you get the current directory name in PHP?
July 1, 2009
I ran into a situation where I wanted to get the name of the directory I was in, in PHP. To be clear, I didn’t want the full path, just the directory/folder name
To be clear, if I was working in the directory:
/home/username/public_html/addonname
I wanted to have addonname returned.
I worked out two solutions.
The first solution used the getcwd() function which returns the full directory path as shown above but to use the basename function to get the part I needed. It worked so… “echo basename(getcwd());” returns “addonname” in this scenario.
That would meet my needs perfectly. I believe that matches my requirements in spirit, but was not literally correct. That statement returned the current working directory, but not the directory where the file was located. In fact, PHP 4 and PHP 5 differ when getcwd() is called from the command line. If you are in the ‘/’ directory and execute “php /test/talktome.php“, a php 4 getcwd() in that file will return the path “/test” while PHP 5 will correctly return ‘/’.
To resolve this, this next call works even better:
“echo basename(dirname(__FILE__));”
Does anyone want to do some speed tests to see which is faster after 10,000 calls? Let us know your results.
WordPress 2.8 Beta 1 Released
May 17, 2009
While I was rolling around near comatose yesterday WordPress 2.8 beta 1 hit the streets. We plan to do a thorough review of this project on Thursday at the Ohio WordPress meetup here in Akron, Ohio. So, I figured it would would be a good time to run it officially here in The Code Cave.
The upgrade process is as simple as always. Unzip the file, copy it over the existing files, go to wp-admin upgrade and click continue. When the official release comes out, I plan upgrading my wp-upgrade script as I still think it is useful. Even though WordPress itself has upgrade abilities within it, the full file backup and database backup that my script does, still provides added benefit. So, I’ll keep it around a while longer.
As for WordPress 2.8, you don’t need to fear about learning a totally new system from scratch. There are a number of nice changes and tweaks, but the basic interface remains the same. There are a lot of changes for plugin developers and the like, but the everyday users will see some things like the new widget drop zone that makes it easy to make a widget inactive without loosing its settings. I’ll be testing over the next few weeks to see what I’d consider note worthy for a release post. I used to even do a line by line comparison, but I don’t know if I’ll don’t be returning to that. For now you can read about the changes here.
If there is any part of this upgrade that you definitely think we should cover at the Ohio WordPress Meetup, please let me know so that I don’t miss it!
MySQL Founder Resigns from Sun over Quality of MySQL 5.1
February 5, 2009
I first laid eyes on Michael Widenius, the original and principle author of the MySQL database software at the 2008 MySQL Conference in San Jose. Michael, who is more commonly known simply as “Monty”, had recently had his pride and joy, the MySQL AB company purchased by Sun Microsystems. I’d say that just about every attendee was extremely nervous about the future of MySQL, and every (new) Sun employee was eager to say “Oh, the purchase was great thing!”. There certainly was a Sun head hunter at every corner ready to hand out an application form (and a pair of boxer shorts or two).
I left the conference having learned a lot of the techniques Lee Newton would be soon applying to the b5media database architecture. But far as the Sun purchase was concerned… I felt a little less safe. It was worrying that something that important was not quite as secure as it once was. There was no indication something bad was about to happen, but the way things were, it was sure to be painful if something did.
So now not a year later, Monty announced today that he has quit because Sun released MySQL 5.1 without first resolving significant flaws despite Monty’s strenuous objections. Monty previously released a detailed list describing some of the “many known and unknown fatal bugs in the new features that are still not addressed.” My take from the article is that we should consider MySQL 5.1 should be considered a 5.0 maintenance release with pre-release beta features included.
Obviously Monty had spoken up to the higher ups at Sun prior to the release, but as he explained this had little affect. I think that the open source world collided heavily with the corporate reality of “Cost, Schedule, Features, or Quality – Choose 3”. In the open source communities, the choice is simple, the schedule rarely if ever enters the mix. In this corporate battle, it obviously was one of the three chosen. Monty had been seen this coming early on and had been very vocal even back in April 2008 (see page 19) calling for Sun to “Create a release policy and independent release policy board that can’t be manipulated by people in charge of server development (to not allow anyone to sacrifice quality to reach personal goals)” Whoa… “To reach Personal Goals” – even then it sounded to me like he had someone in particular in mind. Additionally, from another comment later in the keynote: “Sun is more opensource/free software friendly than MySQL AB has been lately and is driving MySQL in the right direction” it seems obvious that there was a power struggle going on. After all MySQL AB was co-founded by Michael and he should have had significant influence over the company’s philosophies. I don’t know the rest of this particular sub-plot, but I’m certain there is more to be told.
In any case, in light of MySQL 5.1’s quality issues at time of general availability Michael tells us he immediately quit but was talked into giving three months months to Sun for reconciliation and putting things right. That stretched into seven months, but the end result was the same. Michael announced today that he’s resigned and will be creating his own version of MySQL called MySQL-Maria which will will incorporate all MySQL updates but include rewrites and additional code to improve stability. It will be primarily developed by a new company he is forming named Monty Program Ab which will be “a true open source company”. I’m still not sure what that means, but I guess I could read up on it in more detail, if I wanted to.
So, what does this mean? Is it a good thing? I guess it is good that someone is out there fixing known bugs in MySQL, but won’t that happen anyway with an open source project? It’s great to see another company formed to further the open source movement, but can a MySQL standards war be beneficial? Given the adoption rate of new MySQL releases, does it even matter that 5.1 was released? It’s not as if ISPs will install it anytime before 2010 by which time there will be patches.
In the end, from the clues I’ve seen I suspect this episode occurred due to a personal, philosophical dispute that Monty didn’t win. Regardless, I wish him success with his new project and thank him for providing a tool that I use ever day: MySQL.
