Nov
13
(2009)
Modifying the BuddyPress AdminBar
Filed under: general. Tags: buddypress, code, howto, wordpress, wpmu. | 7 Comments
On UCalgaryBlogs, I’d modified the adminbar to include a link to the current site’s dashboard if a person was logged in, making it easy to get to the members-only side of WordPress without having to go through My Blogs and finding the right blog, then mousing over the pop-out “Dashboard” link. Most people never found that, and it’s not very intuitive.
So, I hacked in a hard-coded link to Dashboard in bp-core-adminbar.php. This worked, but meant I had to remember to re-hack the file after running a BuddyPress update. I forgot to do that right after I ran the last upgrade, and got emails from users asking WTF?
I decided to figure out the best way to add in the Dashboard link without hacking the actual plugin files. Turns out, it’s drop-dead simple. Yay, WordPress.
In your /wp-content/plugins/ directory, create a file called bp-custom.php (if it’s not there already), and drop this code into it:
<?php
// custom functions for tweaking BuddyPress
function custom_adminbar_dashboard_button() {
// adds a "Dashboard" link to the BuddyPress admin bar if a user is logged in.
if (is_user_logged_in()) {
echo '<li><a href="/wp-admin/">Dashboard</a></li>';
}
}
add_action('bp_adminbar_menus', 'custom_adminbar_dashboard_button', 1);
?>
When in place, your BuddyPress adminbar will look something like this:
Yes, I know I should do something to properly detect user levels and privileges, rather than just providing the Dashboard link all willie-nillie to anyone that’s logged in, but the link itself just provides access to whatever Dashboard features the user is allowed to see, so there’s no security risk. Better to just say that a user can see the Dashboard for any site they’re logged into, and let WordPress deal with restricting access properly.
I should also deal with the possibility of WPMU being configured as a subdirectory vs. subdomain (the /wp-admin/ link will bork if you’re using subdirectories – better to use the real code to sniff out the base url of the current site…)
Sep
30
(2009)
Fixing WPMU 2.8.4 and the ignored Banned Email Domains option
Filed under: general. Tags: bug, code, php, wordpress, wpmu. | 8 Comments
I’ve been having a heck of a time battling sploggers at UCalgaryBlogs.ca – roaches that create accounts and blogs so they can foist their spam links to game Google (thanks for providing spammers with such a powerful incentive, Google).
There’s an option in WordPress Multiuser to ban email domains – provide the domains, one per line, into a text box, and it will reject any roaches trying to create accounts from those domains.
The biggest offenders have been myspace.info and myspacee.info – and although they’ve been in my Banned Email Domains list for months, they just keep getting through. I figured there was some exploit they were using, but couldn’t find a thing.
So, today, I took a look through the code of WPMU 2.8.4, to see if I could find what was going on. Turns out, it’s a really simple fix. There’s a function in wp-includes/wpmu-functions.php, called is_email_address_unsafe() – it’s supposed to check the contents of the Banned Email Domains option field, and reject addresses from the flagged domains.
Except it wasn’t. Rejecting, I mean. It was letting everyone through, because of a simple bug in the code. It was written to treat the value of the option as an array and to directly walk through each item of the array. But, the option is stored as a string, so it needs to be converted to an array first. Easy peasy. Here’s my updated is_email_address_unsafe() function, which goes around line 880 of wpmu-functions.php:
function is_email_address_unsafe( $user_email ) {
$banned_names_text = get_site_option( "banned_email_domains" ); // grab the string first
$banned_names = explode("\n", $banned_names_text); // convert the raw text string to an array with an item per line
if ( is_array( $banned_names ) && empty( $banned_names ) == false ) {
$email_domain = strtolower( substr( $user_email, 1 + strpos( $user_email, '@' ) ) );
foreach( (array) $banned_names as $banned_domain ) {
if( $banned_domain == '' )
continue;
if (
strstr( $email_domain, $banned_domain ) ||
(
strstr( $banned_domain, '/' ) &&
preg_match( $banned_domain, $email_domain )
)
)
return true;
}
}
return false;
}
The fix is in the first 2 lines of the function – getting the value of the string, and then exploding that into the array which is then used by the rest of the function. I’ve tested the updated function out on UCalgaryBlogs.ca and it seems to work just fine. Hopefully the fix will get pulled into the next update of WPMU so everyone with Banned Email Domains can breathe a bit more easily.
Jun
25
(2009)
BuddyPress and MultiDB
Filed under: work. Tags: buddypress, howto, multidb, wordpress, wpmu. | 10 Comments
I’ve been trying to get BuddyPress working on my WPMU installation that uses MultiDB for database partitioning. It’s been cranky, but I just realized I’m a complete idiot because I was overlooking the obvious (and drop dead simple) fix.
BuddyPress was acting up because it was creating tables in each blog’s database tableset. But MultiDB makes it easy to declare tables as belonging to a shared global database, so they don’t get recreated for each blog and are common across the entire service.
Thanks to a reminder by Andrew on the premium.wpmudev.org forum!
I edited my db-config.php file to declare the BuddyPress tables as being global, and copied the tables from the database where they had been collecting, into the global database.
// BuddyPress
add_global_table('bp_activity_sitewide');
add_global_table('bp_activity_user_activity');
add_global_table('bp_activity_user_activity_cached');
add_global_table('bp_friends');
add_global_table('bp_groups');
add_global_table('bp_groups_groupmeta');
add_global_table('bp_groups_members');
add_global_table('bp_groups_wire');
add_global_table('bp_messages_messages');
add_global_table('bp_messages_notices');
add_global_table('bp_messages_recipients');
add_global_table('bp_messages_threads');
add_global_table('bp_notifications');
add_global_table('bp_user_blogs');
add_global_table('bp_user_blogs_blogmeta');
add_global_table('bp_user_blogs_comments');
add_global_table('bp_user_blogs_posts');
add_global_table('bp_xprofile_data');
add_global_table('bp_xprofile_fields');
add_global_table('bp_xprofile_groups');
add_global_table('bp_xprofile_wire');
It seems to be working fine. I’ll do some more testing, but it’s looking promising. If it’s really working, I’ll be spending some time to BuddyPress-enable the main theme for the WPMU service, and roll it out properly.
May
20
(2009)
Stopping Spamblog Registration in WordPress MultiUser
Filed under: work. Tags: apache, htaccess, spam, wordpress, wpmu. | 39 Comments
I’ve been running a copy of WordPress MultiUser for over a year now. Comment spam hasn’t been much of a problem, thanks to Akismet, but if I leave site registration open (so students and faculty can create new accounts and blogs), the evil spammers find it and start sending their bots en masse.
I tried a few plugins, with varying levels of success. There’s an interesting one that attempts to limit registrations to a specific country, but it falsely reported some valid users as not being in Canada. Captchas work, but also block some valid users (and the signup captcha plugin I’d been using is possibly abandoned).
So, I did some quick googling, and came across the page on the WordPress Codex about using .htaccess files to stop comment spam. I made some modifications to the technique, and am now running it on UCalgaryBlogs.ca with apparent success. The apache logs show the bot attacks are still in full force, but not a single one has gotten through in order to register. And valid users have been able to get through. That’s pretty promising.
Apr
27
(2009)
Trying WP-SpamFree
Filed under: general. Tags: plugin, spam, wordpress, wp-spamfree. | 8 Comments
Thanks to a tip from David Esrati (who I’m not going to link to from this post because I’m taunting spammers and don’t want to inflict collateral damage on him), I’m testing out WP-SpamFree which is a really interesting antispam plugin for WordPress. I’ve used Akismet and Mollom before, and I’ve always been uncomfortable with externally hosted antispam systems. For some reason, I’m just not completely comfortable with relying on another server for this. I’d used Spam Karma 2 with great success, but since that went defunct I abandoned it as well.
Now, WP-SpamFree seems to offer an intelligent antispam system without relying on external servers or blacklists. I’m giving it a shot. So far, it’s been pretty successful.
Let’s see how well it does. Bring it.
Apr
1
(2009)
Cleaning up after Microsoft
Filed under: general. Tags: html, plugin, wordpress, wpmu. | 3 Comments
I spend a depressing amount of time cleaning up after Microsoft. Specifically, cleaning up the “helpful” HTML code generated by MS Word and/or Internet Exploder on Windows when people copy content from MS Word and paste it into a WYSIWYG editor in Internet Explorer. Helpful, in that it tries (and fails so spectacularly that it boggles my mind how such a “feature” was designed) and more often than not completely borks whatever website is the unsuspecting recipient of the control-V-of-death.
I’m not going to tell people not to use MS Word. It’s what people use. Trying to get them to switch to anything else would be tilting at windmills. People use Word.
I’m not going to tell people not to use Internet Explorer. I don’t use it. Nobody I work with uses it. But people do – most often, it’s people who don’t really know what a “browser” is, or that there are options, or that IE is a dangerous beast. They use IE. Fine.
But… I just found a plugin for WordPress that should at least mitigate the damage of the Word/IE duopoly.
Here’s an example. I just worked up a simple document in Word. It’s pretty fantastic. I’m proud of it. Teacher will give me an A+, for sure. It looks like this:
It’s a work of art. Now, I copy the contents of that fantastic piece of literature, and hit control-C to copy it. I switch over to Internet Explorer, and paste it into the Visual editor on a WordPress site. And it looks kinda like hell. The source code of the pasted content looks like this:
WTF? MsoNormal? margins? font-size and font-family? For the love of Xenu, why do you bork my content like this? Now, most people just see the result and say “Man, does WordPress suck. I’m not going to use THAT again.” – they don’t realize that it’s Word/IE that’s borking their content, and that it would be equally borked on any web-based content management system that offers a visual wysiwyg editor.
So, after activating the plugin, pasting the same content from my most awesome Word Document into the Visual editor of a WordPress site generates code like this:
It’s not perfect, but it’s cleaner. Some of the formatting won’t be exactly what was in the MS Word document, but that’s probably for the better. Apparently, if I used proper styles to define Headings in my document, it would convert them to h1/h2/etc… in the pasted markup. Ahhh… much better.
If you’re using WordPress with people that are using MS Word and/or Internet Explorer, get the plugin. You’ll be doing them a favour, and saving yourself some grief.
Mar
31
(2009)
WPMU Post and Comment Growth
Filed under: work. Tags: activity, community, plugin, ucalgaryblogs.ca, wordpress, wpmu. | 2 Comments
The group of WPMU rockstars at UBC’s OLT just whipped up a fantastic new plugin for administrators of a WPMU site to get a feel for the growth of the community. It generates a graph to display growth in numbers of blog posts and comments over time, and uses the Google Data Visualization API to let you interactively define data ranges to be graphed.
Here’s the growth of UCalgaryBlogs.ca graphed for the last 2 semesters:
Another fantastic job by the OLT blogging platform crew. Now, to just add users and pages, and it’ll be perfect…
Mar
26
(2009)
WordPress, draft/private pages, and the parent hierarchy structure
Filed under: general, work. Tags: code, howto, wordpress, wp-sentry, wpmu. | 4 Comments
I’m working with a class of 250+ geology undergrads, split up into 53 groups. They’re using a WordPress site to publish online presentations as the product of a semester-long group project. I’m using the great WP-Sentry plugin to let them collaboratively author the pages without worrying about other students in the class being able to edit their work (I know – but it makes them more comfortable so it’s a good thing to add).
The premise is this – I created a Page called, creatively enough, “Winter 2009″ – and each of the groups is to create a page (or set of pages) and add them to the site – and selecting “Winter 2009″ as the parent page for the main page of their presentation. They are free to create as many other pages as they like, and can set those to use their first page as the parent, thereby generating a table of contents.
Works great. Except that the WP-Sentry plugin hijacks the “Private” state of pages, and the tree of Pages available in the Parent selector is based on “Published” pages.
Conflict. Confusion. Frustration.
The students could either collaborate on the pages, or organize them in the tree structure.
Of course they could create the pages and add them to the tree structure and THEN enable the WP-Sentry-managed group editing controls, but YOU try explaining that process to 250 undergrads, all stressed out about building web pages as part of a geology course.
So… I dug into the code to see what was yanking “Private” pages from the Parent list. Turns out, it’s in wp-includes/post.php, waaaay down on line 2618 (as of WPMU 2.7). All I did was remove the " AND post_status = 'publish'" bit, and it now appears to be listing all pages.
I’m quite sure I borked something else, but for now I’m leaving the Parent list wide open until the students are done publishing their presentations.
Update: Unintended consequence #242: Looks like with the tweak, Private pages show up where they’re not expected. I’m disabling the tweak for now until I can find a better way (if that’s even possible).
Mar
5
(2009)
notes on converting ucalgaryblogs.ca to use multi-db
Filed under: work. Tags: documentation, howto, wordpress, wpmu. | 6 Comments
I followed Jim’s instructions to get UCalgaryBlogs.ca converted from using a single database (as is the default) to using multiple databases (17 separate databases now) via the premium.wpmudev.org Multi-DB code to prevent growing pains. The single database config is good for getting up and running, but with 300 blogs in the system, table explosion was causing grief on the shared MySQL database server – there were almost 3000 tables, which was making the automated backup script complain a bit.
While reading the documentation, I was rather confused by the term “global” – which appeared to be used in slightly different ways. Eventually, I plugged through, and got it working. The key is to test it all on a local copy of the database before running the migration script on the production server. Thankfully, the script doesn’t delete anything, so I was confident that if anything borked I could just back out the multi-db files and revert to single database config without losing anything.
“Global Tables” are tables that will be stored in a shared, common database rather than in each blog’s database in one of the 16 databases used by the multi-db code. These are things that are accessed by all blogs on the WPMU install, and include administrative stuff.
In the db-config-sample-16.php file that ships with multi-db, it also mentions “global-db”, “globaluser”, and “globalpassword” – those are just the database server address, username, and password to use when connecting to the “Global” database containing the “global tables”. They used “global-” in these parameters because it’s possible to configure each of the 17 databases to use different database servers, different usernames, and different passwords. For simplicity, I used the same database server and account for all 17 databases.
My db-config.php file was edited as follows:
<?php
// Plugin Name: Multi-DB
// Plugin URI: http://premium.wpmudev.org/project/Multiple-Databases
// Author: Andrew Billits (Incsub)
// Version: 2.7.0
//------------------------------------------------------------------------//
//---DB Scaling-----------------------------------------------------------//
//------------------------------------------------------------------------//
// 16,256,4096
define ('DB_SCALING', '16'); // use 16 databases for the blogs
//------------------------------------------------------------------------//
//---DC IPs---------------------------------------------------------------//
//------------------------------------------------------------------------//
// Usage: add_dc_ip(IP, DC)
// EX: add_dc_ip('123.123.123.', 'dc1');
add_dc_ip('127.0.0.1', 'dc1'); // DN: change this to the IP address of your WEB SERVER
//------------------------------------------------------------------------//
//---Global Tables--------------------------------------------------------//
//------------------------------------------------------------------------//
// Do not include default global tables
// Leave off base prefix (eg: wp_)
//
// Usage: add_global_table(TABLE_NAME)
// EX: add_global_table('something');
// DN: These are tables that will be stored in the global database configured below (wpmu_global)
// rather than in the 16 blog databases.
add_global_table('mass_mailer');
add_global_table('registration_log');
add_global_table('reports_comment_activity');
add_global_table('reports_post_activity');
add_global_table('reports_user_activity');
add_global_table('signups');
add_global_table('support_faq');
add_global_table('support_faq_cats');
add_global_table('support_tickets');
add_global_table('support_tickets_cats');
add_global_table('support_tickets_messages');
add_global_table('domain_mapping');
add_global_table('comment_activity');
add_global_table('blog_activity');
add_global_table('user_activity');
add_global_table('post_activity');
//------------------------------------------------------------------------//
//---DB Servers-----------------------------------------------------------//
//------------------------------------------------------------------------//
// Database servers grouped by dataset.
// R can be 0 (no reads) or a positive integer indicating the order
// in which to attempt communication (all locals, then all remotes)
//
// Usage: add_db_server(DS, DC, READ, WRITE, HOST, LAN_HOST, NAME, USER, PASS)
// EX: add_db_server('global', 'dc1', 1, 1,'global.mysql.example.com:3509','global.mysql.example.lan:3509', 'global-db', 'globaluser', 'globalpassword');
// DN: NOTE: change 'dbserver.com' to the address of the mysql server,
// 'username' to your mysql username,
// 'password' to the appropriate password.
add_db_server('global', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_global', 'username', 'password');
add_db_server('0', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_0', 'username', 'password');
add_db_server('1', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_1', 'username', 'password');
add_db_server('2', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_2', 'username', 'password');
add_db_server('3', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_3', 'username', 'password');
add_db_server('4', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_4', 'username', 'password');
add_db_server('5', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_5', 'username', 'password');
add_db_server('6', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_6', 'username', 'password');
add_db_server('7', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_7', 'username', 'password');
add_db_server('8', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_8', 'username', 'password');
add_db_server('9', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_9', 'username', 'password');
add_db_server('a', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_a', 'username', 'password');
add_db_server('b', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_b', 'username', 'password');
add_db_server('c', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_c', 'username', 'password');
add_db_server('d', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_d', 'username', 'password');
add_db_server('e', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_e', 'username', 'password');
add_db_server('f', 'dc1', 1, 1, 'dbserver.com', 'dbserver.com', 'wpmu_f', 'username', 'password');
//
// Note: you can also place this section in a file called db-list.php in wp-content
// EX: add_db_server('global', 'dc1', 1, 1,'global.mysql.example.com:3509','global.mysql.example.lan:3509', 'global-db', 'globaluser', 'globalpassword');
//------------------------------------------------------------------------//
//---VIP Blogs------------------------------------------------------------//
//------------------------------------------------------------------------//
// Usage: add_vip_blog(BLOG_ID, DS)
// EX: add_vip_blog(1, 'vip1');
// DN: I didn't add any VIP blogs.
?>
To create the databases, I used the script at http://db-tools.wpmudev.org/db.php and it generated the code below, which I ran on the MySQL server to create the databases:
CREATE DATABASE `wpmu_global` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_0` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_1` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_2` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_3` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_4` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_5` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_6` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_7` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_8` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_9` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_a` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_b` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_c` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_d` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_e` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE DATABASE `wpmu_f` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
After copying the db.php and db-config.php files into place as per Jim’s instructions, it all Just Worked™. New content was being stored in the 16 blog databases, and sites were behaving as expected, but with slightly less table explosion bloat as before.
One thing that makes me a little nervous is that the multi-db code isn’t core to WordPress, and is part of the premium.wpmudev.org subscription. This means that it can break in the future – there is no obligation for WordPress to continue to work with it, and if for some reason premium.wpmudev.org decides to abandon the plugin or stop updating it, I’m locked into WordPress 2.7. Neither of these made me lose too much sleep. Worst case scenario, I can always recombine the tables from all 17 databases back into a single ĂĽberdatabase, assuming we haven’t outgrown the physical limits of a single MySQL database by then.
Feb
23
(2009)
security hole in wordpress-admin-bar under WPMU?
Filed under: work. Tags: code, wordpress, wpmu. | 3 Comments
I just tried logging into ucalgaryblogs.ca using a test user account, and was surprised to see a strange item in the admin bar at the top of the page:
I was curious, so I clicked it.
mwah? Those are site-admin items, being displayed to a non-admin user. I was actually able to click the “Admin Message” item to set that, even though the logged in user wasn’t an admin. Scary. Luckily, nobody’s noticed the extra menu yet – or if they have, they’ve behaved.
I poked around in the wordpress-admin-bar.php file to see if I could plug the hole. I have no idea if this is the right way, but I’ve added this bit:
} else {
if ($menu[0]['title'] === null) continue; // this is the line I added
echo ' <li class="wpabar-menu_';
if ( TRUE === $menu[0]['custom'] ) echo 'admin-php_';
It’s down around line 320 or so. It’s probably not the correct or most reliable way to strip that menu from non-admin-users’ version of the admin bar, but it worked. Here’s the result:
Has anyone else seen the extra menu? Could it have just been a freak thing only on my WPMU install, or is this a wide open potential security problem in the shipping wordpress-admin-bar.php file? It was written for non-WPMU WordPress, so it’s quite possible it just doesn’t grok the different types of users in WPMU.






