Media management is, unfortunately, one of the weaker points of WordPress. There are some exciting changes coming for 3.3, but they don’t address some of the core deficiencies, such as the inability to attach a photo to multiple posts.
We had a fun time killing our site in an unusual manner last night and again this morning.
Version 0.3-beta of our Docs to WordPress plugin has been released. It’s a fairly minor release, and only affects people using the cleaner extension, but we just rolled it out internally this morning at the BDN and it’s exciting for us, so I wanted to pass it on to the public.
The newest version of the cleaner extension passes correctly parses bold and italic text and headings, such as <h4>. It also strips out span tags to avoid a few instances where they would cause an extra line break.
In an earlier version of Docs, bold and italic text were surrounded by <em> and <strong> tags, as would be expected. It made it very easy to handle stylized text.
In the newest version of Docs, however, text formatting is done completely via CSS. So the header of each of the HTML versions of the Doc contains a stylesheet, where bold and italic text is given an arbitrary class name.
To carry that through to WordPress, I use preg_match to find the correct class name from the stylesheet and then preg_replace to convert span tags to <em> and <strong>. Thanks much to Andrew Nacin, Rob Flaherty Bill, who replied to my cry for help.
As always, the Docs to WordPress plugin can be found in the WordPress Plugin Repository.
One of the most annoying things about using Google Docs is that none of the styles are inline. It used to be that bold text was wrapped in a <strong> tag and italic text was wrapped in am <em> tag. No longer. Now each style of text is wrapped in a span with a number of different classes applied to it. Those styles don’t carry through when we bring the text into WordPress and the names of the classes vary from article to article. This can be very annoying for columnists who bold names of subjects, for example.
So, what I’m looking for is a regex expression to turn <span class=”c0 c3″>My text</span> into <span class=”c0 c3″><strong>My text</strong></span> where class c3 is the bold class, for example.
Here at the BDN, we’re all a bit relieved to finally have completed the relaunch of bangordailynews.com. And one of our tasks now is to clean up all the mistakes we’ve made and all the sloppy code we’ve written while rushing toward the finish line.
A great talk by Frederick Townes, CTO of Mashable.com and the man behind the must-use W3 Total Cache plugin, gave a great talk at WordCamp Boston this weekend about performance enhancements, and I thought it’d be good to repeat some of his points and add a few of my own.
Continue reading OK, so you’ve built your site. Now what? Focus on performance. #wcbos
Just released: A very simple plugin that allows you to require SSL for certain pages on your site. For example, at the BDN we moved the login form from /wp-login.php to /login/, and wanted to require SSL for that page. So, using this plugin, we can force anyone who visits http://bangordailynews.com/login/ to https://bangordailynews.com/login/.
It’s in the WordPress plugin repository: http://wordpress.org/extend/plugins/require-ssl-for-pages/
We’ve released a quick update to the Docs to WordPress plugin that allows the plugin to integrate with WP Cron rather than requiring you to create your own file to run cron against (though you can still do that, too). To take advantage of it, you’ll have to upgrade to the latest version and edit your wp-config file to define at least three variables:
DOCSTOWP_USER: Your Google Docs username
DOCSTOWP_PASS: Your Google Docs password
DOCSTOWP_ORIGIN: The ID of the origin folder you want to draw the docs from
DOCSTOWP_DESTINATION: This one’s optional, but if defined it will put the docs in this folder when everything is finished processing.
To define a variable:
define( 'DOCSTOWP_USER', 'firstname.lastname@example.org' );
To get the ID of the folder, look to the URL. It will look something like this:
https://docs.google.com/#folders/folder.0.!–ID STARTS HERE, AFTER THE 0 and the period –!
A quick note: I decided to hardcode the variables in wp-config instead of using a setting because I’d rather store a plaintext password on the server than in the database. Obviously, neither are ideal, so if anybody has a better solution I’m all ears.
At the BDN, we love WordPress, but we didn’t feel it was the right place to have our reporters working. Even with distraction-free writing, there’s too much cruft that would confuse reporters or that we don’t want reporters touching. We wanted something lightweight, fast, simple to grasp and that would make editing easier.
Enter Google Docs. It’s easy to use, lightweight and a large number of people are already familiar with it. And, it’s got beautiful tools that enable real-time collaboration with reporters and editors.
That’s where we decided to start. Reporters write stories in Google Docs, and the docs are sorted in a series of collections, which are shared out to everybody. We have collections for each desk, such as metro, state, sports, etc.; collections for workflow, such as Needs Copy Editing, On Hold, etc.; and collections for actions, such as Send to Publish and Published.
The action collections are important — they’re how docs actually get from Google to WordPress. When we first started using Docs with our sports department in August or September of last year, Docs was much different, more neanderthal. But, that was nice in some ways. The docs came through with inline markup, and Docs supported XML-RPC, which made it easy to connect the two systems.
Then came an upgrade, which was on the whole a good one. It combined what they’d learned building Wave to bring real-time collaboration to a new level. But it also eliminated XML-RPC support, and docs aren’t marked up as nicely as they used to be.
So, we delved into the API, and I think what we have now is an even nicer system than XML-RPC could provide. You can find a version of what we built in the WordPress Plugin Repository.
On the whole, the process is fairly simple. When we’re all done copy editing an article, the doc goes into a collection called Send to Publish. We have a script running every two minutes that will grab docs from that collection, process them, and move them to WordPress. Then the script takes the doc out of Send to Publish and puts the doc in a collection called Published.
Here are a few features the plugin provides:
- If the doc is a new post, it will go into WordPress as a draft. If the doc has already been put into WordPress, it will update the previous post.
- Usernames in Docs must correspond with usernames in WordPress.
- If a doc is in a collection and there is a corresponding category in WordPress, it will automatically put that the post in that collection. Else it will put it in the default category.
There are a few filters and actions in the plugin that allow you to extend it, and the plugin actually currently comes with one extender, which will strip out comments into a custom field and normalize the content. It’s a slightly stripped-down version of the extender we use.
We actually go one step further by fielding data using delimiters. We name the doc with a slug instead of a headline, and then the first line of the doc becomes a headline, followed by a pipe (|). After that comes the body copy, and at the very end we can add another pipe. Anything after that last pipe acts as a comment. We do this because we also use the API to put things, such as AP stories, into Google Docs, and we couldn’t figure out a way to add comments via the API.
To install the plugin, you’ll need to upload it to your /wp-content/plugins/ folder. Right now there isn’t an extender for wp-cron (there will be soon), so you’ll have to put the action on a page and point a cron job to it. I do this like so:
<?php include('./wp-load.php'); $docs_to_wp = new Docs_To_WP(); $gdClient = $docs_to_wp->docs_to_wp_init( 'email@example.com', 'mypassword' ); //We're just going to call one function: $docs_to_wp->retrieve_docs_for_web( $gdClient, ID of origin folder, ID of destination folder );
As always, the code is on Github.
I’m starting with the import process not because it is an exceptionally good place to start when preparing to move a site to WordPress but because it’s one of the few things I got right from the get-go when I transferred my first news site to WordPress.
The best way to import content into WordPress, in my experience, is using WordPress’s XML import.
WordPress’s XML files follow an easy-to-grasp but powerful structure that, in general, goes something like this:
<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0" xmlns:excerpt="http://wordpress.org/export/1.0/excerpt/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:wp="http://wordpress.org/export/1.0/" > <channel> <title>My example site</title> <link>http://example.com/</link> <description></description> <pubDate>Thu, 28 May 2009 16:06:40 +0000</pubDate> <generator>http://wordpress.org/?v=2.7.1</generator> <language>en</language> <wp:wxr_version>1.0</wp:wxr_version> <wp:base_site_url>http://example.com/</wp:base_site_url> <wp:base_blog_url>http://example.com/</wp:base_blog_url> <item> <category domain="category" nicename="my-category"><![CDATA[My Category]]></category> <category domain="tag" nicename="my-tag"><![CDATA[My Tag]]></category> <title><![CDATA[My Post Title]]></title> <dc:creator><![CDATA[My Name]]></dc:creator> <link>http://example.com/2010/07/06/my-post-title/</link> <pubDate>Tue, 06 Jul 2010 10:51:32 +0000</pubDate><dc:creator><![CDATA[bdnoutdoors]]></dc:creator> <guid isPermaLink="false">http://example.com/?p=12345</guid> <description></description> <content:encoded><![CDATA[My post content.]]></content:encoded> <excerpt:encoded><![CDATA[My post excerpt.]]></excerpt:encoded> <wp:post_id>12345</wp:post_id> <wp:post_date>2010-07-06 10:51:32</wp:post_date> <wp:post_date_gmt>2010-07-06 10:51:32</wp:post_date_gmt> <wp:comment_status>open</wp:comment_status> <wp:ping_status>closed</wp:ping_status> <wp:post_name>my-post-title</wp:post_name> <wp:status>publish</wp:status> <wp:post_parent>0</wp:post_parent> <wp:menu_order>0</wp:menu_order> <wp:post_type>post</wp:post_type> <wp:post_password></wp:post_password> <wp:postmeta> <wp:meta_key>my_post_meta_key</wp:meta_key> <wp:meta_value>My Post Meta Value</wp:meta_value> </wp:postmeta> </item> </channel> </rss>
That’s a fairly simple usage of WordPress XML, and when we imported our content from the Bangor Daily News we did a lot more.
For example, we imported all our posts with a hidden post meta (_old_id) value of the article’s ID in our old CMS. Then, we used the CP Redirect plugin as a template for a new plugin to redirect people clicking on old links to the new URL.
We also found that using the <dc:creator> tag quickly overwhelmed us. As with any newspaper, there are thousands of people who have written just one or a few articles for us, and we didn’t want to create accounts for all of them. Instead, we created a whitelist of authors we wanted to come in as users — basically just BDN staff and frequent contributors and freelancers — and the rest of the posts came in with a default username and with the author’s name in a most meta field name _byline.
We don’t embed images in posts. Rather, we query for all images attached to the post and display them at the top of the post and in the sidebar (more about this in a later post). So we natively imported the images so they would become attachments. WordPress automatically copies all attachments onto the server, so we didn’t have to worry about getting all the images off our old server. Importing the images is just as easy. <wp:post_parent> is set to the ID of the post, <wp:status> is set to inherit and <wp:post_type> is set to attachment. The image path goes in <wp:attachment_url>, and the caption goes in <excerpt:encoded>.
We broke the XML files up by 1,000 posts at a time. All in all, we had more than 100 XML files. We also imported everything onto a local machine and then pushed the database back up to our webserver. All told, importing everything took several solid days of work.
If you’re working on a site much larger than ours, you might consider importing posts directly into WordPress using the API, but to be honest I’m not sure how much overhead that would save.
The script, which you’ll have to modify a bit but hopefully not too much, is on github.
Lauren Rabaino asked on a previous post for a video of our entire workflow. The whole process is actually pretty simple, so it wasn’t hard to record it.
Everything starts in Google Docs — that’s where the reporters write their stories, the AEs read them and the copy editors edit them. We interface with Google Docs via its API and WordPress via XML-RPC to move stories out of a folder and into the CMS. It requires a bit of cleanup, but for the most part everything goes smoothly.
All the stories are then saved on a local server as Indesign Tagged Text files, and prepared for print. Styles are applied, the byline and headline is added to the top of the story and a few other changes are made. We try to keep as much formatting from the web to print as possible, including bolding, italics, and a whole host of styles, particularly for sports.
We custom-built a plugin for InDesign that allows us to easily search WordPress and import the files from the server.