Friday, March 27, 2009

Perl Search and Replace an Entire Directory Tree

I recently had to do a search/replace for a couple of directories here at work. I know you're probably thinking why didn't I just do a find | xargs sed, well good point, though there is actually more to the code that is not displayed that worked better in perl. So ha! ;)

Anyhow, my code utilizes File::Find. If you've never used it, check it out. It's very useful. The process is easy; use find for a directory root and write a sub to process against the files.

Nothing too fancy here, but if you don't understand anything feel free to shoot me a message.

Cheers!


use strict;
use File::Find;

my $dir = "/mnt/www";

find(\&doFile, $dir);

sub doFile {
my $file = $File::Find::name;
my $fdir = $File::Find::dir;
open (FILE, $file) or die "Couldn't open file: $file $!\n";
my @foundfiles;
while () {
if ($_ =~ /your_search_regex/)
{
print "found: $file\n";
push(@foundfiles, $file);
}
}
close(FILE);

foreach my $f (@foundfiles) {
my $replace = qq~/usr/bin/perl -pi -e 's,your_search_regex,your_replacement,g' $file~;
print "$replace\n";
system($replace);
}
}

Friday, March 20, 2009

WordPress Plugin: Change Admin Pagination

This plugin has been tested on WordPress Version 2.7.

Are you seeing enough posts in your WordPress admin screen? Or are you seeing too much?

Well, either way I've written a plugin to accomodate for the lack of choice for posts-per-page. First, please note this is unfortunately not a standard install for a WordPress plugin. Because WordPress hardcoded a static value (15) for posts-per-page, I needed to modify some of WordPress' core-code. So, continue at your own risk, copy/paste with caution, make backups often!

We will be editing the following files: wp-admin/edit.php and wp-admin/includes/post.php

Let's start with wp-admin/includes/post.php. Look around line 806, you should see this line: wp("post_type=post&what_to_show=posts$post_status_q&posts_per_page=15&order=$order&orderby=$orderby"); Aha! This is where they hardcode the posts_per_page to 15.

We're going to add a global variable line and replace the 15 with that global variable.

global $ppp;
wp("post_type=post&what_to_show=posts$post_status_q&posts_per_page=$ppp&order=$order&orderby=$orderby");

Now, let's go and edit the wp-admin/edit.php page. Look around line 250, you should see this code block:

__( 'Displaying %s–%s of %s' ) . '%s',
number_format_i18n( ( $_GET['paged'] - 1 ) * $wp_query->query_vars['posts_per_page'] + 1 ),
number_format_i18n( min( $_GET['paged'] * $wp_query->query_vars['posts_per_page'], $wp_query->found_posts ) ),
number_format_i18n( $wp_query->found_posts ),
$page_links
); echo $page_links_text; ?><\/div>
<\ ?php } ?>


We're going to add the following to make it look like this:
); echo $page_links_text;
if (function_exists(writePostsPerPage)) {
echo ' Posts per page';
echo writePostsPerPage(15);
echo writePostsPerPage(25);
echo writePostsPerPage(35);
}
?>
<\/div>
<\ ?php } ?>


That's all of the core code you'll be editing. Nothing too major! Now, all you need to do is add this following plugin code to a file and activate the plugin. Now, you should see a "Posts per page" option in your Edit posts screen.



/*
Plugin Name: Change Admin Pagination
Description: Change the pagination values in the Admin section for Posts and Pages
Version: 1.0
Plugin URI:
Author: Adrian J. Cruz
Author URI: http://drincruz.blogspot.com/
License:
Copyright 2009 Adrian J. Cruz (email : drincruz at gmail.com)

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

Changelog:
2009-03-20: didn't want as many core code changes, so moving as much as i can
to this plugin.

*/

global $ppp;
$ppp = $_GET['posts_per_page'];

/**
* This changes the posts_per_page option.
*/
function updatePostsPerPage($ppp) {
if (!$ppp)
return;
$old_ppp = get_option('posts_per_page');
if ($ppp == $old_ppp)
return;
else
update_option('posts_per_page', $ppp);
}

/**
* This will return the proper URL for the posts_per_page links.
*/
function writePostsPerPage ($n) {
if (!$n) { return; }
$ppp_url = "<\a href=\"?paged=" . $_GET['paged'] . "&posts_per_page=" . $n . "\">$n<\/a>";
return $ppp_url;
}

/**
* We want to update_option before the admin page is loaded so the page
* will take the changes immediately.
*/
add_filter('admin_head', updatePostsPerPage($ppp));


*Note: I know blogger doesn't handle source code very well within its 'code' tags, sorry about that! I think I'll try and find a free fileshare where I can keep source code and you can just download. I'll keep you guys posted.

Cheers!

Monday, March 16, 2009

Adding Widgets to Your WordPress Dashboard

Well, first off, I take no credit for most of this information you're about to read. I just tweaked the code I got from wpengineer.com, so go check out that site for the original details.

Anyhow, we're recently in the process of beta'ing WordPress 2.7 (from 2.0.1). We wrote a lot of custom code on top of WordPress already and most of it worked with little or no help. One of the recent complaints from one of our test users was regarding the new dashboard and the lack of "recent posts" and "scheduled posts".

If you haven't used WordPress 2.0.1, well there used to be a list of recent published posts and scheduled posts right on the dashboard. Honestly, in 2.7 the data is still there, just a few more clicks away. But anyway, to please our users, I wrote this plugin to add two widgets, "My Recent Posts" and "My Scheduled Posts".

It's loosely called "AddDashboard" for now. I'll try and come up with something snazzier later.

Enjoy. Cheers!

post script: I've "escaped" some of the code such as: <\?php


<\?php
/*
Plugin Name: AddDashboard
Plugin URI:
Description: This plugin adds on to the WP Dashboard
Version: 0.1
Author: Adrian J. Cruz
Author URI:
License:
Changelog:
*/


/**
* Content of Dashboard-Widget
*/
function my_wp_dashboard_recent() {
$result = get_recent_posts(10, publish);
if (!$result) { echo 'No recent posts.'; }
else {
foreach($result as $key => $value) {
print "<\a href=\"post.php?action=edit&post=" . $value['ID'] . "\">"
. $value['ID'] . ": " . $value['post_title'] . "<\/a>
";
}
}
}

function my_scheduled_posts() {
$result = get_recent_posts(10, future);
if (!$result) { echo 'No posts scheduled to be published.'; }
else {
foreach($result as $key => $value) {
print "<\a href=\"post.php?action=edit&post=" . $value['ID'] . "\">"
. $value['ID'] . ": " . $value['post_title'] . "<\/a>
";
}
}
}

/**
* add Dashboard Widget via function wp_add_dashboard_widget()
*/
function my_wp_dashboard_setup() {
wp_add_dashboard_widget( 'my_wp_dashboard_recent', __( 'My Recent Posts' ), 'my_wp_dashboard_recent' );
}

function my_scheduled_posts_setup() {
wp_add_dashboard_widget( 'my_scheduled_posts', __( 'My Scheduled Posts' ), 'my_scheduled_posts' );
}

/**
* use hook, to integrate new widget
*/
add_action('wp_dashboard_setup', 'my_wp_dashboard_setup');
add_action('wp_dashboard_setup', 'my_scheduled_posts_setup');

function get_recent_posts($num = 10, $status) {
global $wpdb;

// Set the limit clause, if we got a limit
$num = (int) $num;
if ($num) {
$limit = "LIMIT $num";
}
// if no $status default to publish
if (!status) {
$status = "publish";
}

$sql = "SELECT * FROM $wpdb->posts WHERE post_type = 'post' AND post_status='$status' ORDER BY post_date DESC $limit";
$result = $wpdb->get_results($sql,ARRAY_A);

return $result ? $result : array();
}
?>

Friday, March 13, 2009

Command Line Upgrade Wordpress Databases

Oh yay, we're going to upgrade WordPress here at work! Now we have a couple hundred WordPress databases at work, so I don't want to have to manually load up a web browser and click the "WordPress Upgrade" button a hundred or so times. In technology, if you have to do the same process over and over, you should always look for a way to automate it, and that is what I've done here.

The PHP script is rather simple. If you look at it, it basically defines which database, db user, db password, db server, etc. and then it runs WordPress' wp_upgrade() function. That simple! Honestly, the hardest part (for me at least) was figuring which WordPress files I needed to include and what order they go in! (In the beginning, I traversed the order of my require statements. Doh!)



\<\?php //this gets hosed by blogger so i'm trying to "escape" it

if ($argc != 2) {
print "Usage: php $argv[0] [wpdb]\n";
exit;
}
$db = $argv[1];
define( 'ABSPATH', dirname(__FILE__) . '/' );
define('WP_ADMIN', true);
define('DB_NAME', $db);
define('DB_USER', 'wpuser'); // Your MySQL username
define('DB_PASSWORD', 'wppass'); // ...and password
define('DB_HOST', 'mysql.server.com'); // 99% chance you won't need to change this value
require('wp-load.php');
require('wp-admin/includes/upgrade.php');
print "Upgrading " . DB_NAME . "...\n";
wp_upgrade();
print "\n\nUpgrade for " . DB_NAME . " complete!\n";
?>


Now as you can see, I set up my PHP script to handle the database name as an argument. So now, all I need to do is write a bash wrapper script! I haven't done it yet, but it'll basically be similar to this:


for db in `cat listofdbs.txt`; do php ajc-wpupgrade.php $db; done;


And Bob's your uncle!

Thursday, March 5, 2009

Reset FreeBSD root Password

I have various different VMs for testing purposes. I needed to test something on FreeBSD and luckily I already had a VM setup. But, hmmm...I forgot the root password!

So, if you happened to ever forget the root password for one of your FreeBSD servers, fear not, you can reset it! Well, of course if you have access to boot the server into single user mode of course! ;)

The process is quick and painless; select the default shell once in single user mode (/bin/sh). After that just enter these commands to change the root password:


# mount -u /
# mount -a
# passwd


Obviously, if you've used passwd before it'll prompt you for the new password and to confirm. After that, all you need to do is exit or sync;sync; reboot.

That's it. Cheers!

Tuesday, March 3, 2009

Check MySQL Tables Across Multiple Databases

If you manage multiple databases on a MySQL server that are identical in architecture, but unique in data well I'm sure you may come across this issue at some point in time. This issue being that, the MySQL err log shows an error on a table, but it doesn't specify which database!

(Error looks like this: # 090217 18:38:22 [ERROR] /usr/local/bin/mysqld: Can't open file: 'wp_comments.MYI' (errno: 144)
)

Now sure, you can just run a mysqlcheck on all-databases' tables, but obviously that may take a while, especially if you manage a lot of databases.

So, remember just a few blog posts before, I mentioned a way of monitoring MySQL's err log. Well, to be honest, that was actually a product of my original idea; that idea being:
- monitor the MySQL err log for table errors
- if there is a table error, run a mysqlcheck for that specific table against all databases
- email the results

Well, why only search for one specific error if you can search for them all? So, I broke my script down into two; an err log monitor and a mysqlcheck. Same as always, tailor the code to fit your environment. For example in this line: if ($dbdata->[0] =~ m/^wp_.+/) { I'm only going to search through our Wordpress databases and skip everything else. You can change the regex so that you will only check databases that begin with the letter 'a' if that fits your situation (/^a.+/).

I'm sure you get the point! Eventually (when I'm less busy), I'll modify some of my code so that this will run more of an automated process. --All you need to do is have the err log script write to a file with the tables giving errors and then write a wrapper to process the err log script, then process the mysqlcheck script. But, that's for another day kids!

Cheers!


#!/usr/bin/perl -w
#
# chkmysqltables.pl v1.0
# 2009-02-17
# Adrian J. Cruz
#

use strict;
use DBI;

if (!$ARGV[0]) {
print "Usage: $0 \" [db_table]\"\n";
print "Note: Use quotes if you are checking multiple tables.\n";
exit;
}

my $db_table = $ARGV[0];

my $mysqlu = "dbuser";
my $mysqlp = qw(dbpass);
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
localtime(time);
my $yy = sprintf("%02d", $year % 100);
$year += 1900;
$mon += 1;
if ($mon < 10) { $mon = 0 . $mon; }
if ($mday < 10) { $mday = 0 . $mday; }
my $todaylog = $year . $mon . $mday . $hour . $min;
my $runlog = "/root/log/chkmysqltables.$todaylog.log";

my @tables;
push @tables, $db_table;
# we only want the unique tables
my %sorthash;
@sorthash{@tables} = ();
my @error_tables = keys %sorthash;

my @dbs;
my $dbh = DBI->connect("DBI:mysql:mysql",$mysqlu,$mysqlp)
|| die "Unable to connect: $!\n";
my $query = "SHOW DATABASES";
my $sth = $dbh->prepare($query)
|| die "Could not prepare: $!\n";
$sth->execute || die "Could not execute: $!\n";
while (my $dbdata = $sth->fetchrow_arrayref) {
if ($dbdata->[0] =~ m/^wp_.+/) {
push(@dbs,$dbdata->[0]);
}
}
$sth->finish;

open(LOG, ">>$runlog");
print LOG "$todaylog\n";
for my $t (@error_tables) {
for my $db (@dbs) {
system "/usr/local/bin/mysqlcheck --password=$mysqlp $db $t >> $runlog\n";
}
}
print LOG "--END LOG--\n";
close(LOG);

my $email_message;
open(MSG, "<$runlog");
$email_message = do {local $/; };
close(MSG);

sendMail( "you\@email.com", "mysqlerr\@email.com",
"mysqlerr: $db_table", "$email_message" );

sub sendMail {
my ($to, $from, $subject, $message) = @_;
my $sendmail = "/usr/sbin/sendmail";
open(MAIL, "|$sendmail -oi -t");
print MAIL "From: $from\n";
print MAIL "To: $to\n";
print MAIL "Subject: $subject\n\n";
print MAIL "$message\n";
close(MAIL);
}