settings page. Version: 1.2.1 Author: Matt Jacob Author URI: http://mattjacob.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 */ // Let's make sure we're running a compatible version of PHP before we go any further // TODO: Actually, let's hold off until I can get a PHP 4 test server up and running. /* if (version_compare(PHP_VERSION, '5.1.0', '<')) { add_action('admin_notices', 'wp_pingfm_php_version'); return; } */ /** * Drives the Ping.fm Custom URL plugin * * @author Matt Jacob * @version 1.2.1 */ class WP_PingFmCustomUrl { // Some constants to keep track of version numbers const PLUGIN_VERSION = '1.2.1'; const DB_VERSION = '1.0'; const DB_TABLE_NAME = 'pingfm'; // Error messages (should be straight HTML) const ERROR_TABLE_MISSING = " It looks like the required database table doesn't exist yet! It's not really a big deal, but you'll have to go and create it manually in order for status updates to work properly. Check out the installation instructions for some help on how to do that. "; const ERROR_TABLE_EMPTY = " It looks like the owner of this blog hasn't posted any status updates yet. This message is only temporary and will disappear after the first update is posted. If you are the owner of this blog and need help with the plugin or the widget, contact the developer. "; const ERROR_PERMALINK_404 = "No status updates found for the ID you provided."; const ERROR_DIRECT_ACCESS = "No direct access allowed. Post something from Ping.fm instead."; /** * Status update text for the single item permalink page * * @access private * @var string */ private $mSinglePing; /** * Status update date and time for the single item permalink page * * @access private * @var string */ private $mSinglePingDate; /** * Derived DB table name where status updates are kept. Global WordPress * prefix is taken into account. * * @access private * @var string */ private $mDbTableName; /** * Absolute HTTP path to the plugin directory. Useful for including images * and other client-side goodies. * * @access private * @var string */ private $mAbsHttpPath; /** * User-defined options that are stored in the DB * * @access private * @var array */ private $mOptions; // Getters public function GetSinglePing() { return $this->mSinglePing; } public function GetSinglePingDate() { return $this->mSinglePingDate; } /** * Constructor */ public function __construct() { global $wpdb; $this->mDbTableName = $wpdb->prefix . self::DB_TABLE_NAME; $this->mOptions = get_option('pingfm_options'); $this->mAbsHttpPath = get_bloginfo('wpurl') . stristr(dirname(__FILE__), '/wp-content'); } /** * Does some basic housekeeping and is run when the plugin is first * installed and also when it's re-activated. */ public function ActivatePlugin() { if (get_option('pingfm_db_version') != self::DB_VERSION) { $sql = " CREATE TABLE `{$this->mDbTableName}` ( `id` int(10) unsigned NOT NULL auto_increment, `status` text NOT NULL, `timestamp` timestamp NOT NULL, PRIMARY KEY (`id`) ); "; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta($sql); update_option('pingfm_db_version', self::DB_VERSION); } // Clean up after the old way of storing options // As of 1.1.0, everything is separated out and we're not using the array if (FALSE !== get_option('pingfm_options')) { delete_option('pingfm_options'); } $user_css = << 'Status Updates', 'pingfm_widget_prefix' => 'From Ping.fm:', 'pingfm_widget_limit' => '10', 'pingfm_default_author' => '1', 'pingfm_default_status' => 'publish', 'pingfm_default_category' => wp_create_category('Ping.fm'), 'pingfm_default_tags' => 'pingfm, social, networking, alpacas', 'pingfm_generate_token' => 'N', 'pingfm_token' => $this->GenerateUniqueToken(), 'pingfm_user_css' => $user_css, 'pingfm_widget_links' => 'N', 'pingfm_force_posts' => 'N', 'pingfm_title_structure' => 'Status update on %date% at %time%', ); foreach ($options as $option => $value) { if (FALSE === get_option($option)) { update_option($option, $value); } } } /** * Sets up the widget in the system. Also inserts some custom CSS into the * of pages if the user specified some styles on the settings page. */ public function InitializeWidget() { global $wp_pingfm; $id = 'wp-pingfm'; $name = 'Ping.fm'; $widget_ops = array( 'classname' => 'wp-pingfm-widget', 'description' => 'Your most recent Ping.fm status updates', ); wp_register_sidebar_widget($id, $name, array($wp_pingfm, 'RegisterWidget'), $widget_ops); wp_register_widget_control($id, $name, array($wp_pingfm, 'RegisterWidgetControl')); if (is_active_widget(array($wp_pingfm, 'RegisterWidget'))) { if (get_option('pingfm_user_css')) { add_action('wp_head', array($wp_pingfm, 'InsertWidgetCss')); } } } /** * Creates the widget and causes it to appear as a draggable box in the * admin interface. * * @param array $args the bits surrounding the widget box */ public function RegisterWidget($args) { $widget_title = get_option('pingfm_widget_title'); $widget_prefix = get_option('pingfm_widget_prefix'); $widget_limit = get_option('pingfm_widget_limit'); $widget_links = get_option('pingfm_widget_links'); if ('N' == $widget_links) { $widget_links = false; } extract($args); $html = ''; $html .= $before_widget; $html .= $before_title . $widget_title . $after_title; $html .= $this->GetWidgetContent($widget_limit, $widget_prefix, $widget_links); $html .= $after_widget; echo $html; } /** * Registers some form controls for changing widget settings via the admin * interface. The output is included directly inside the main
, whose * action POSTs back to itself. */ public function RegisterWidgetControl() { $options = array( 'pingfm_widget_title' => '', 'pingfm_widget_prefix' => '', 'pingfm_widget_limit' => '', 'pingfm_widget_links' => '', ); if (isset($_POST['pingfm_is_submitted']) && $_POST['pingfm_is_submitted'] == 'Y') { // Prep the form data for validation foreach (array_keys($options) as $o) { $options[$o] = $this->CleanFormInput($o); } if (!$options['pingfm_widget_title']) { $options['pingfm_widget_title'] = 'Status Updates'; } if (!preg_match('/^[0-9]+$/', $options['pingfm_widget_limit'])) { $options['pingfm_widget_limit'] = '10'; } // Update the options in the DB foreach ($options as $option => $value) { update_option($option, $value); } } foreach (array_keys($options) as $o) { $options[$o] = wp_specialchars(get_option($o)); } $widget_links = ($options['pingfm_widget_links'] == 'Y') ? 'checked="checked"' : ''; echo <<


Between 5 and 10 seems to work well

EOF; } /** * Sets up fields that WordPress should look for when the settings page is * submitted. This is the preferred way to do things as of WordPress 2.7, * and it should be forward-compatible with future versions. */ public function InitializeAdmin() { $settings = array( 'pingfm_generate_token', 'pingfm_default_status', 'pingfm_default_author', 'pingfm_default_category', 'pingfm_default_tags', 'pingfm_user_css', 'pingfm_force_posts', 'pingfm_title_structure', ); foreach ($settings as $s) { register_setting('wp-pingfm-settings', $s); } } /** * Adds a 'settings' link into the action column of the plugin on the * plugins listing page in the administrative area. This helps users find * the settings page. * * @param array $links a reference to the current array of links * @return array an array reference w/new element prepended */ public function InsertAdminActionLink($links) { // Add a link to this plugin's settings page $settings_link = 'Settings'; array_unshift($links, $settings_link); return $links; } /** * Adds a 'settings' link to the navigation panel in the administrative * area. The link will show up under the 'plugins' heading. */ public function InsertAdminMenuLink() { global $wp_pingfm; add_submenu_page('plugins.php', 'Ping.fm Settings', 'Ping.fm Settings', 'manage_options', 'wp-pingfm-settings', array($wp_pingfm, 'GenerateAdminPage')); } /** * Inserts some plugin-specific CSS into the of administrative area * pages. It's important to target just the stuff we want to change, * because this CSS gets inserted into every single admin page. */ public function InsertAdminCss() { echo << #wp-pingfm-settings code { font-size: 12px; } #wp-pingfm-settings textarea { font-family: Consolas, Monaco, Courier, monospace; font-size: 12px; width: 100%; } #icon-pingfm { background: transparent url({$this->mAbsHttpPath}/pingfm-logo.png) no-repeat scroll -11px -5px; } dl { margin: 1em 0; font-size: 12px; } dl dt { color: #000; float: left; width: 80px; } dl dd { color: #666; } EOF; } /** * Outputs the user-defined widget CSS that gets inserted into every blog * page, assuming that the user actually specified some styles. */ public function InsertWidgetCss() { echo "\n"; } /** * Forces the WordPress mod_rewrite rules to be re-generated. */ public function FlushRewriteRules() { global $wp_rewrite; $wp_rewrite->flush_rules(); } /** * Creates the mod_rewrite rules to use with the plugin and adds them onto * the front of the current set of rules. * * @param object $wp_rewrite a reference to an instantiated WP_Rewrite object */ public function GenerateRewriteRules($wp_rewrite) { $new_rules = array( 'pingfm/post/([a-zA-Z0-9]{32})/?$' => 'index.php?pa=post&pk=' . $wp_rewrite->preg_index(1), 'pingfm/status/([0-9]+)/?$' => 'index.php?pa=status&pi=' . $wp_rewrite->preg_index(1), 'pingfm/feed/?$' => 'index.php?pa=feed', ); $wp_rewrite->rules = $new_rules + $wp_rewrite->rules; } /** * Adds some query string variables to the list of allowed query string * parameters. The plugin depends on these for proper page routing and * operation. * * @param array $vars the current query string vars * @return array the updated query string vars */ public function AddQueryVars($vars) { // PA = ping action: post|status|feed $vars[] = 'pa'; // PK = ping key: unique token $vars[] = 'pk'; // PI = ping ID: PK from *_pingfm.id table $vars[] = 'pi'; return $vars; } /** * Redirects to proper page based on query string. Redirect is used loosely * here, because we're actually just outputting some content (HTML/XML) and * then killing script execution. * * @param object $wp_query a reference to an instantiated WP_Query object */ public function DoPageRedirect($wp_query) { global $wp_pingfm; if (isset($wp_query->query_vars['pa'])) { switch ($wp_query->query_vars['pa']) { case 'post': $wp_pingfm->HandleIncomingPing($wp_query->query_vars['pk']); break; case 'status': $wp_pingfm->DisplaySinglePing($wp_query->query_vars['pi']); break; case 'feed': $wp_pingfm->GenerateRssFeed(); break; } exit; } } /** * Builds the core HTML of the widget as a
    . Also used for the * non-widgetized "template tag" function. * * @param int $limit the number of recent status updates to get * @param string $prefix something to put before each update * @param bool $permalink whether or not to link to archived statuses * @return string the HTML for an unordered list */ public function GetWidgetContent($limit, $prefix, $permalink = FALSE) { global $wpdb, $wp_rewrite; if ($permalink) { $blog_url = get_bloginfo('url'); $permalink_url = "$blog_url/?pa=status&pi="; if ($wp_rewrite->using_permalinks()) { $permalink_url = "$blog_url/pingfm/status/"; } } $prefix = trim($prefix); $html = ''; if ($wpdb->get_var("SHOW TABLES LIKE '{$this->mDbTableName}'") != $this->mDbTableName) { $html .= '

    ' . self::ERROR_TABLE_MISSING . '

    '; } else { if ($wpdb->get_var("SELECT COUNT(id) FROM {$this->mDbTableName}") > 0) { $html .= "
      \n"; $recent = $this->GetRecentPings($limit); foreach ($recent as $r) { $html .= "
    • "; if ($prefix) { $html .= "$prefix "; } $html .= make_clickable(wp_specialchars($r->status)) . '
      '; $timestamp = "" . human_time_diff(strtotime($r->timestamp)) . " ago
    • \n"; if ($permalink) { $html .= sprintf("%s", $timestamp); } else { $html .= $timestamp; } } $html .= "
    \n"; } else { $html .= '

    ' . self::ERROR_TABLE_EMPTY . '

    '; } } return $html; } /** * Gets n most recent status updates from the DB. This is useful in a lot * of places, including the widget, the non-widget sidebar block, and the * RSS feed. * * @param int $limit the number of pings to get * @return array an array of objects (one element per row) */ private function GetRecentPings($limit = 20) { global $wpdb; $sql = "SELECT `id`, `status`, `timestamp` FROM {$this->mDbTableName} ORDER BY `timestamp` DESC LIMIT %d"; $sth = $wpdb->prepare($sql, $limit); $res = $wpdb->get_results($sth); return $res; } /** * Outputs a permalink archive page to display a single status update. * * @param int $id the ID of the ping to show from DB table */ private function DisplaySinglePing($id) { global $wpdb; $sql = "SELECT id, status, timestamp FROM {$this->mDbTableName} WHERE id = %d"; $sth = $wpdb->prepare($sql, $id); $row = $wpdb->get_row($sth); if ($row) { $unix_time = strtotime($row->timestamp); $post_date = date_i18n(get_option('date_format'), $unix_time); $post_time = date_i18n(get_option('time_format'), $unix_time); $this->mSinglePing = make_clickable(wp_specialchars($row->status)); $this->mSinglePingDate = $post_date . ' ' . $post_time; include dirname(__FILE__) . '/tmpl-single.php'; } else { echo 'ERROR: ' . self::ERROR_PERMALINK_404; } } /** * Receives an incoming ping from Ping.fm and inserts it into the DB. * * @param string $token the secret authentication token */ private function HandleIncomingPing($token) { global $wpdb; if (!empty($token)) { if (isset($_POST['method'])) { $title = stripslashes($_POST['title']); $message = stripslashes($_POST['message']); // Method can be: microblog|blog|status $method = stripslashes($_POST['method']); // We'll try to handle status updates differently unless the option // is set to force everything to behave like normal posts. $full_post = false; if ($method == 'status') { if (get_option('pingfm_force_posts') == 'Y') { // Force a full-blown blog entry $full_post = true; $title_structure = get_option('pingfm_title_structure'); if (!empty($title_structure)) { $excerpt = $message; if (strlen($excerpt) > 40) { $excerpt = explode('|', wordwrap($message, 40, '|')); $excerpt = $excerpt[0] . '...'; } $search = array('%excerpt%', '%date%', '%time%'); $replace = array($excerpt, date_i18n(get_option('date_format')), date_i18n(get_option('time_format'))); $title = str_replace($search, $replace, $title_structure); } } else { // Post an update to the sidebar and display via the widget if ($token == get_option('pingfm_token')) { $sql = "INSERT INTO {$this->mDbTableName} (status, timestamp) VALUES (%s, NOW())"; $sth = $wpdb->prepare($sql, $message); $wpdb->query($sth); } } } else { $full_post = true; } if ($full_post) { wp_insert_post(array( 'post_type' => 'post', 'post_status' => get_option('pingfm_default_status'), 'post_author' => get_option('pingfm_default_author'), 'post_category' => array(get_option('pingfm_default_category')), 'tags_input' => get_option('pingfm_default_tags'), 'post_title' => $title, 'post_content' => $message, 'comment_status' => get_option('default_comment_status'), 'ping_status' => get_option('default_ping_status'), )); } } else { echo 'ERROR: ' . self::ERROR_DIRECT_ACCESS; } } } /** * Generates an RSS feed containing the n most recent status updates. */ private function GenerateRssFeed() { global $wpdb, $wp_rewrite; // Send out the proper header for RSS/XML header('Content-Type: text/xml'); // Grab some config settings that we'll need later $blog_title = get_bloginfo_rss('name'); $blog_description = get_bloginfo_rss('description'); $blog_url = get_bloginfo_rss('url'); $blog_admin = get_bloginfo_rss('admin_email'); $blog_charset = get_bloginfo_rss('charset'); $blog_version = get_bloginfo_rss('version'); $plugin_version = self::PLUGIN_VERSION; $xml = << $blog_title $blog_description $blog_url Ping.fm WordPress Plugin $plugin_version (WordPress $blog_version) $blog_admin $blog_admin EOF; $recent = $this->GetRecentPings(20); foreach ($recent as $r) { // Convert the timestamp to RFC 2822 format for pubDate $pub_date = date('r', strtotime($r->timestamp)); $guid = "$blog_url/?pa=status&pi={$r->id}"; // Make nice GUIDs/permalinks if we're using mod_rewrite if ($wp_rewrite->using_permalinks()) { $guid = "$blog_url/pingfm/status/{$r->id}"; } $xml .= << <![CDATA[$r->status]]> status]]> $pub_date $guid $guid EOF; } $xml .= << EOF; echo $xml; } /** * Builds the HTML for the mighty admin area settings page */ public function GenerateAdminPage() { global $wpdb, $wp_rewrite; // Generate a new unique token if the user selected that option if (get_option('pingfm_generate_token') == 'Y') { update_option('pingfm_token', $this->GenerateUniqueToken()); // Be sure to reset the flash variable when we're done with it update_option('pingfm_generate_token', 'N'); } // Grab the WP-centric options we'll be using $site_url = get_bloginfo('url'); $wp_url = get_bloginfo('wpurl'); $date_format = date_i18n(get_option('date_format')); $time_format = date_i18n(get_option('time_format')); // Grab the plugin-specific options we'll be using $default_status = get_option('pingfm_default_status'); $default_author = get_option('pingfm_default_author'); $default_category = get_option('pingfm_default_category'); $default_tags = get_option('pingfm_default_tags'); $user_css = wp_specialchars(get_option('pingfm_user_css')); $token = get_option('pingfm_token'); $title_structure = get_option('pingfm_title_structure'); // Handle the radio buttons/checkboxes because they're a little unique $default_status_publish = ($default_status == 'publish') ? 'checked="checked"' : ''; $default_status_draft = ($default_status == 'draft') ? 'checked="checked"' : ''; $force_posts = (get_option('pingfm_force_posts') == 'Y') ? 'checked="checked"' : ''; // Build the URLs for POSTing to and for the RSS feed $endpoint = "$site_url/?pa=post&pk=$token"; $rss_url = "$site_url/?pa=feed"; // We can use pretty URLs if they're using mod_rewrite permalinks if ($wp_rewrite->using_permalinks()) { $endpoint = "$site_url/pingfm/post/$token"; $rss_url = "$site_url/pingfm/feed"; } // Build the list of available users $user_args = array( 'name' => 'pingfm_default_author', 'selected' => $default_author, 'echo' => 0, ); $dropdown_users = wp_dropdown_users($user_args); // Build the list of available categories $cat_args = array( 'name' => 'pingfm_default_category', 'selected' => $default_category, 'orderby' => 'name', 'hide_empty' => 0, 'echo' => 0, ); $dropdown_categories = wp_dropdown_categories($cat_args); // Show a message if the page has been saved or if we have something // important to say. Default is no message. $message = ''; if (isset($_GET['updated']) && $_GET['updated'] == 'true') { $message = '

    Changes saved.

    '; } // This option didn't exist prior to 1.1.0 // We use it to see if the user has upgraded successfully if (!get_option('pingfm_db_version')) { $message = '

    Important! ' . 'Deactivate and re-activate this plugin to take advantage of new ' . 'features in version 1.1.0.

    '; } echo <<

    Ping.fm Custom URL

    $message EOF; // The forward-compatible method (>= 2.7) // Wish there was a way to capture the output of this function... settings_fields('wp-pingfm-settings'); echo <<Your Super Secret URL
    1. Log in to your Ping.fm account and head over to the Custom URL Settings.
    2. Check Update custom URL in order to activate the text box.
    3. Copy and paste the URL below into the Custom URL box.
    4. Hit Submit.

    Be sure not to give out this URL to anyone, because if someone else were to get ahold of it, they would be able to post updates to your site. And they'd probably write all kinds of embarrassing things about you, so that'd be no good at all. However, if an adversary forces you to divulge your secret URL, fear not, because help is on the way! Keep reading below...

    A Fresh-Baked Plugin Key

    So, you screwed up and now you need a new key, huh? No problem! Check the box below and hit the submit button at the bottom of the page, and you'll be on your way. For reference, your current key is: $token

    RSS Feeding Frenzy

    An RSS feed of your 20 most recent status updates is generated for you automatically. People can subscribe to this feed directly to see your latest status updates in their feed reader. You can also use this feed on social networking sites that let you import your own content. If none of that means anything to you, just ignore this section.

    RSS icon Your feed URL: $rss_url

    Note: If you force status updates into regular posts (see below), they'll appear in your main feed and not in this feed. Obviously.

    High Style, Low Class

    The styles below will be inserted into your blog's pages when this plugin's widget is active. You may modify the rules below or copy and paste them into one of your other style sheets and delete them here—it's up to you! For reference, you can style the ID #wp-pingfm and the class .wp-pingfm-widget.

    Some themes will require different selectors in order to style the widget properly (especially when dealing with CSS specificity issues), but the default styles provided here should work well with the default WordPress theme. For themes with multiple sidebars or any other variations, you'll just have to experiment on your own.

    Status Update Behavior

    Normally, status updates are handled differently from blog or micro-blog pings. Instead of appearing as full-blown posts, statuses are relegated to a lowly sidebar widget where they can't be searched or archived. To circumvent that behavior, check the box below to force all pings to become posts. A list of available title structure tags is below.

    %date%
    Same as Date Format from General Settings, e.g. $date_format
    %time%
    Same as Time Format from General Settings, e.g. $time_format
    %excerpt%
    The first 40 characters of your status update, e.g. Heading to the Auld Dubliner for a pint...
    Force Posts
    Available tags listed above

    Default Values for Posts

    The options below apply to incoming blog and micro-blog pings. Incoming status pings won't be affected since they operate completely outside of the WordPress posts system.

    Default Status   
    $dropdown_users
    $dropdown_categories
    Separated by commas

    EOF; } /** * Takes a string (most likely a field from a form submission) and applies * some basic cleanup filtering to it. This function can be used safely * without getting warnings and will return FALSE if the field doesn't * exist. * * @param string $str the form field from GET or POST * @return string the filtered string */ private function CleanFormInput($str) { return isset($_POST[$str]) ? trim(strip_tags(stripslashes($_POST[$str]))) : FALSE; } /** * Creates a 32-character unique token that's hard to guess. This is the * basis of the plugin's authentication scheme to make sure only authorized * users are pinging the blog. * * @return string the MD5 hash of a random, unique string */ private function GenerateUniqueToken() { return md5(uniqid(rand(), TRUE)); } } /** * Displays an error message if the user's PHP version isn't up to spec. This * function is only meant to be used as a callback, but I need to get PHP 4 * installed on a test server before we can enforce anything reliably. */ function wp_pingfm_php_version() { echo '

    Oh noes! The ' . 'Ping.fm Custom URL plugin requires PHP version 5.1.0 or greater, ' . 'but the installed version is ' . PHP_VERSION . '.

    '; } // Template tags /** * Shows the ping text in the single ping template page */ function wp_pingfm_single() { global $wp_pingfm; echo $wp_pingfm->GetSinglePing(); } /** * Shows the date/time in the single ping template page */ function wp_pingfm_single_date() { global $wp_pingfm; echo $wp_pingfm->GetSinglePingDate(); } /** * Shows an unordered list of the most recent status updates. This template tag * is provided for people who are using non-widgetized templates. * * @param string $title the header for the list (appears inside

    ) * @param int $limit the number of updates to show * @param string $prefix some text to display before every ping * @param bool $permalink whether or not to link to archived statuses * @param string $id the ID of the containing
    */ function wp_pingfm_status($title = 'Status Updates', $limit = 10, $prefix = '', $permalink = FALSE, $id = 'wp-pingfm') { global $wp_pingfm; $content = $wp_pingfm->GetWidgetContent($limit, $prefix, $permalink); echo <<

    $title

    $content
    EOF; } // We need this for the plugin_action_links_* filter below $plugin_action_links = 'plugin_action_links_' . plugin_basename(__FILE__); // Instantiate the object and let's get rolling! $wp_pingfm = new WP_PingFmCustomUrl(); // Install the plugin register_activation_hook(__FILE__, array($wp_pingfm, 'ActivatePlugin')); // Add some stuff to the admin area add_filter($plugin_action_links, array($wp_pingfm, 'InsertAdminActionLink')); add_action('admin_menu', array($wp_pingfm, 'InsertAdminMenuLink')); add_action('admin_head', array($wp_pingfm, 'InsertAdminCss')); add_action('admin_init', array($wp_pingfm, 'InitializeAdmin')); // Handle custom query string vars and mod_rewrite stuff add_action('init', array($wp_pingfm, 'FlushRewriteRules')); add_action('generate_rewrite_rules', array($wp_pingfm, 'GenerateRewriteRules')); add_filter('query_vars', array($wp_pingfm, 'AddQueryVars')); add_action('parse_query', array($wp_pingfm, 'DoPageRedirect')); // Add the widget and associated controls for it add_action('plugins_loaded', array($wp_pingfm, 'InitializeWidget')); ?>