0) { exit(__("In Series requires at least WordPress 1.5 to operate.", "in_series")); } // 1.5 to 2.0 else if(version_compare("2.0", $wp_version) > 0) { // 1.5.x does not have a plugin activation action. Emulate it. $active_plugins = get_option("active_plugins"); if($_GET['activate'] == "true" && in_array("in-series/in-series.php", $active_plugins)) { // Not perfect; runs whenever any plugin is activated, and // In Series is active. Still fairly infrequent, though. :) InSeriesInternal::activate(); } add_action('edit_form_advanced', array('InSeriesInternal','add_write_post_sidebar')); // 1.5.x fires save_post only when creating a new post, not when // saving an existing post -- so we have to hook edit post. add_action('edit_post', array('InSeriesInternal','process_save')); } // 2.0 or later else { add_action('activate_in-series/in-series.php', array('InSeriesInternal','activate')); add_action('dbx_post_sidebar', array('InSeriesInternal','add_write_post_sidebar')); } } else { exit(__("Could not get WordPress version.", "in_series")); } } /**************************************************************************\ * General Helper Functions * \**************************************************************************/ /** * @param string $table the table name to add a prefix to * @return string $table with the table prefix prepended */ function get_table_name($table) { global $wpdb; global $table_prefix; $prefix = ""; if(!empty($wpdb->prefix)) { $prefix = $wpdb->prefix; } else { $prefix = $table_prefix; } return $prefix . $table; } /** * @return string The name of the series table */ function get_series_table_name() { return InSeriesInternal::get_table_name("in_series_3_0_series"); } /** * @return string The name of the authorization table */ function get_auth_table_name() { return InSeriesInternal::get_table_name("in_series_3_0_auth"); } /** * @return string The name of the entries table */ function get_entry_table_name() { return InSeriesInternal::get_table_name("in_series_3_0_entries"); } /** * @param string $table The complete table name to look up * @return bool Whether $table exists in the database */ function has_table($table) { global $wpdb; return $wpdb->get_var("SHOW TABLES LIKE '{$table}'") == $table; } /** * @return int|NULL The current series ID hint, or NULL if there is none. */ function get_series_hint() { return InSeriesInternal::make_int($_GET['sid']); } /** * @return int|NULL The current series ID, or NULL * @see adv_CurrentSeries */ function get_current_series_id($pid = false) { global $wpdb; $hint = InSeriesInternal::get_series_hint(); if($pid !== false) { $id = InSeriesInternal::make_int($pid); } else { $id = InSeriesInternal::make_int(InSeriesInternal::get_the_ID()); } if (is_null($id)) { return NULL; } $entry_table = InSeriesInternal::get_entry_table_name(); $series_set = $wpdb->get_results(" SELECT post_id,series_id FROM {$entry_table} WHERE post_id='{$id}'"); if(empty($series_set)) { return NULL; } else if(count($series_set) == 1) { return $series_set[0]->series_id; } else if(!empty($hint)) { foreach($series_set as $series) { if($series->series_id == $hint) { return $series->series_id; } } // Fall through on no match } return NULL; } /** * @param mixed $value Int or a string with an int value * @return int|NULL A proper int, or (if no sane conversion could be made) * NULL * * "Sane conversion" means that either $value is already an integer (in * which case, it is returned), or $value is a string representing an * integer value (not floating point, or an integer with any extra stuff at * the end). */ function make_int($value) { if(is_int($value)) { return $value; } // Sift out strings with non-numeric parts if(!is_numeric($value)) { return NULL; } // Do a loose cast; this sifts out strings that are floats. $value += 0; // If we don't have an int at this point, we never will. if(!is_int($value)) { return NULL; } return $value; } /** * @return int|NULL a value indicating the current post ID. * * Leave wiggle-room, just in case things change in the future. * */ function get_the_ID() { global $post; if(function_exists("get_the_ID")) { return get_the_ID(); } if(!empty($post) && !empty($post->ID)) { return $post->ID; } return NULL; } /** * @return bool whether the given author is allowed to edit the given * series. */ function can_alter_series($sid, $aid) { $sid = InSeriesInternal::make_int($sid); $aid = InSeriesInternal::make_int($aid); if(is_null($sid) || is_null($aid)) { return false; } $series = InSeries::adv_SidToSeries($sid); if(empty($series)) { return false; } // TODO: work off the auth table, too. return $series->owner_id == $aid; } /**************************************************************************\ * Basic API back-end * \**************************************************************************/ function NextHtml() { $options = get_option("in_series"); $series = InSeries::adv_CurrentSeries(); $pid = InSeries::adv_NextInSeries($series->series_id); if(is_null($series) || is_null($pid)) { return NULL; } return InSeriesInternal::FormatHtml($options['format_next'], $pid); } function PrevHtml() { $options = get_option("in_series"); $series = InSeries::adv_CurrentSeries(); $pid = InSeries::adv_PrevInSeries($series->series_id); if(is_null($series) || is_null($pid)) { return NULL; } return InSeriesInternal::FormatHtml($options['format_prev'], $pid); } function ToCHtml() { $options = get_option('in_series'); $sid = InSeries::adv_CurrentSeries(); if(is_null($sid)) { return NULL; } $sid = $sid->series_id; $pid = InSeries::adv_FirstInSeries($sid); $current_pid = InSeriesInternal::get_the_ID(); if(is_null($sid) || is_null($pid) || is_null($current_pid)) { return NULL; } $entries = ""; while(!is_null($pid)) { if($pid == $current_pid) { $entries .= InSeriesInternal::FormatHtml($options['format_toc_active_entry'], $pid); } else { $entries .= InSeriesInternal::FormatHtml($options['format_toc_entry'], $pid); } $pid = InSeries::adv_NextInSeries($sid, $pid); } $output = InSeriesInternal::FormatHtml($options['format_toc_block'], $current_pid); $output = str_replace('%entries', $entries, $output); return $output; } function SeriesListHtml() { $options = get_option('in_series'); $series = InSeries::adv_SeriesList_BySeriesCreation(); $entries = ""; foreach($series as $a_series) { $pid = InSeries::adv_FirstInSeries($a_series->series_id); $entries .= InSeriesInternal::FormatHtml($options['format_series_list_entry'], $pid); } $output = InSeriesInternal::FormatHtml($options['format_series_list_block'], InSeriesInternal::get_the_ID()); $output = str_replace("%entries", $entries, $output); return $output; } function FormatHtml($format, $pid) { $title = get_the_title($pid); $series = InSeries::adv_CurrentSeries($pid); $series = stripslashes($series->series_name); $url = get_permalink($pid); $format = str_replace("%title", $title, $format); $format = str_replace("%series", $series, $format); $format = str_replace("%url", $url, $format); return $format; } /**************************************************************************\ * Series Manipulation Functions * \**************************************************************************/ function AddToSeries($sid, $pid, $start) { global $wpdb; $sid = InSeriesInternal::make_int($sid); $pid = InSeriesInternal::make_int($pid); if(is_null($sid) || is_null($pid)) { return; } $entry_table = InSeriesInternal::get_entry_table_name(); if($start) { $start = "prev"; } else { $start = "next"; } $check = $wpdb->get_var(" SELECT post_id FROM {$entry_table} WHERE series_id='{$sid}' AND post_id='{$pid}'"); // Don't allow double-adds. if(!empty($check)) { return; } $opid = $wpdb->get_var(" SELECT post_id FROM {$entry_table} WHERE series_id='{$sid}' AND {$start}_post_id IS NULL"); // Should never happen (adding to empty series) if(empty($opid)) { return; } // Link the old head/tail to the new head/tail $wpdb->query(" UPDATE {$entry_table} SET {$start}_post_id='{$pid}' WHERE series_id='{$sid}' AND {$start}_post_id IS NULL"); if($start == "next") { $ins = "'{$opid}',NULL"; } else { $ins = "NULL,'{$opid}'"; } // Do the actual insert. // ($ins is properly quoted, and contains SQL syntax) $wpdb->query(" INSERT INTO {$entry_table} (series_id,post_id,prev_post_id,next_post_id) VALUES ('{$sid}','{$pid}',{$ins})"); } function RemoveFromSeries($sid, $pid) { global $wpdb; $sid = InSeriesInternal::make_int($sid); $pid = InSeriesInternal::make_int($pid); if(is_null($sid) || is_null($pid)) { return; } $entry_table = InSeriesInternal::get_entry_table_name(); // Get the entries before and after the entry being removed $need_update = $wpdb->get_results(" SELECT post_id,prev_post_id FROM {$entry_table} WHERE series_id='{$sid}' AND (prev_post_id='{$pid}' OR next_post_id='{$pid}')"); // Something's unusual... if(empty($need_update)) { $post = $wpdb->get_var(" SELECT post_id FROM {$entry_table} WHERE series_id='{$sid}' AND post_id='{$pid}'"); // Post is not in the series if(empty($post)) { return; } // Post is the only post in the series $series_table = InSeriesInternal::get_series_table_name(); $wpdb->query(" DELETE FROM {$entry_table} WHERE series_id='{$sid}'"); $wpdb->query(" DELETE FROM {$series_table} WHERE series_id='{$sid}'"); return; } // Should never happen. if(count($need_update) > 2) { return; } $ppid = NULL; $npid = NULL; // For each entry, figure out if it came before or after the entry being // removed. foreach($need_update as $entry) { if($entry->prev_post_id == $pid) { $npid = InSeriesInternal::make_int($entry->post_id); } else { $ppid = InSeriesInternal::make_int($entry->post_id); } } if(is_null($ppid)) { $ppid = "NULL"; } if(is_null($npid)) { $npid = "NULL"; } // If there was a prior entry, point it at the next entry (or NULL) if($ppid != "NULL") { // ($npid can be NULL) $wpdb->query(" UPDATE {$entry_table} SET next_post_id={$npid} WHERE series_id='{$sid}' AND post_id='{$ppid}'"); } // If there was a next entry, point it at the prior entry (or NULL) if($npid != "NULL") { // ($ppid can be NULL) $wpdb->query(" UPDATE {$entry_table} SET prev_post_id={$ppid} WHERE series_id='{$sid}' AND post_id='{$npid}'"); } // Do the actual removal now. $wpdb->query(" DELETE FROM {$entry_table} WHERE series_id='{$sid}' AND post_id='{$pid}'"); } function InsertIntoSeries($sid, $pid, $ppid) { global $wpdb; $sid = InSeriesInternal::make_int($sid); $pid = InSeriesInternal::make_int($pid); $ppid = InSeriesInternal::make_int($ppid); if(is_null($sid) || is_null($pid) || is_null($ppid)) { return; } $entry_table = InSeriesInternal::get_entry_table_name(); $check = $wpdb->get_var(" SELECT post_id FROM {$entry_table} WHERE series_id='{$sid}' AND post_id='{$pid}'"); // Don't allow double-adds. if(!is_null($check)) { return; } // Get the series we're inserting AFTER $check = $wpdb->get_results(" SELECT post_id,next_post_id FROM {$entry_table} WHERE series_id='{$sid}' AND post_id='{$ppid}'"); // The post to insert after does not exist. if(empty($check)) { return; } // Should never happen. if(count($check) > 1) { return; } $npid = $check[0]->next_post_id; if(empty($npid)) { $npid = "NULL"; } // Do the forward link $wpdb->query(" UPDATE {$entry_table} SET next_post_id='{$pid}' WHERE post_id='{$ppid}'"); // And the backward link // (this is a no-op if no backward link needs to be updated) $wpdb->query(" UPDATE {$entry_table} SET prev_post_id='{$pid}' WHERE prev_post_id='{$ppid}'"); // Do the actual insertion now // ($npid can be NULL) $wpdb->query(" INSERT INTO {$entry_table} (series_id,post_id,prev_post_id,next_post_id) VALUES ('{$sid}','{$pid}','{$ppid}',{$npid})"); } /**************************************************************************\ * UI for Series Data * \**************************************************************************/ function display_manage_posts_column($colname, $id) { if($colname != 'in_series') { return; } $in_series = InSeries::adv_PostToSeries($id); $sep = ""; foreach($in_series as $series) { $series_name = stripslashes($series->series_name); echo "{$sep}{$series_name}"; $sep = ", "; } } function add_manage_posts_column($posts_columns) { $posts_columns['in_series'] = __('Series', 'in_series'); return $posts_columns; } function add_write_post_sidebar() { require_once('in-series-edit.php'); } function process_save($epid) { global $wpdb; $series_table = InSeriesInternal::get_series_table_name(); $entry_table = InSeriesInternal::get_entry_table_name(); $epid = InSeriesInternal::make_int($epid); if(is_null($epid)) { return; } $post = get_post($epid); if(empty($post)) { return; } $series_delta = $_POST['in_series']; if(isset($series_delta['add_to_series'])) { if(empty($series_delta['add_to_series']['series']) && !empty($series_delta['add_to_series']['new_series'])) { $series_name = $series_delta['add_to_series']['new_series']; $series_name = $wpdb->escape($series_name); $aid = $post->post_author; $exists = $wpdb->get_var(" SELECT series_id FROM {$series_table} WHERE series_name='{$series_name}' AND owner_id='{$aid}'"); // TODO: If does match an existing series, add to it if(empty($exists)) { $wpdb->query(" INSERT INTO {$series_table} (series_id,owner_id,series_name) VALUES (DEFAULT,'{$aid}','{$series_name}')"); $sid = $wpdb->get_var(" SELECT series_id FROM {$series_table} WHERE owner_id='{$aid}' AND series_name='{$series_name}'"); $wpdb->query(" INSERT INTO {$entry_table} (series_id,post_id,next_post_id,prev_post_id) VALUES ('{$sid}','{$epid}',NULL,NULL)"); } } else if(!empty($series_delta['add_to_series']['series'])) { $sid = InSeriesInternal::make_int($series_delta['add_to_series']['series']); if(!is_null($sid)) { if(InSeriesInternal::can_alter_series($sid, $post->post_author)) { $at_start = $series_delta['add_to_series']['position'] == "start"; InSeriesInternal::AddToSeries($sid, $epid, $at_start); } } } } if(isset($series_delta['existing'])) { foreach($series_delta['existing'] as $sid => $pid) { // No change; don't bother doing anything. if(empty($pid)) { continue; } if($pid == "NULL") { InSeriesInternal::RemoveFromSeries($sid, $epid); InSeriesInternal::AddToSeries($sid, $epid, true); continue; } if($pid == "delete") { InSeriesInternal::RemoveFromSeries($sid, $epid); continue; } $pid = InSeriesInternal::make_int($pid); // Should never happen if(is_null($pid)) { continue; } InSeriesInternal::RemoveFromSeries($sid, $epid); InSeriesInternal::InsertIntoSeries($sid, $epid, $pid); } } } function process_delete($pid) { $pid = InSeriesInternal::make_int($pid); if(is_null($pid)) { return; } $in_series = InSeries::adv_PostToSeries($pid); if(empty($in_series)) { return; } foreach($in_series as $series) { InSeriesInternal::RemoveFromSeries($series->series_id, $pid); } } /******************************************************************************\ * Hooks & UI for automatic series hyperlink insertion * \******************************************************************************/ function insert_doc_rel_links() { if(!is_single()) { return; } $options = get_option("in_series"); if(!$options['meta_links']) { return; } $series = InSeries::adv_CurrentSeries(); if(is_null($series)) { return; } $sid = $series->series_id; $npid = InSeries::adv_NextInSeries($sid); $ppid = InSeries::adv_PrevInSeries($sid); if(!is_null($npid)) { $next_url = get_permalink($npid); $output .= ""; } if(!is_null($ppid)) { $prev_url = get_permalink($ppid); $output .= ""; } echo $output; } function in_series_linker_content_filter($content) { $options = get_option('in_series'); $output = InSeriesInternal::FormatHtml($options['format_post'], InSeriesInternal::get_the_ID()); $output = str_replace("%prev", InSeriesInternal::PrevHtml(), $output); $output = str_replace("%next", InSeriesInternal::NextHtml(), $output); $output = str_replace("%toc", InSeriesInternal::ToCHtml(), $output); $output = str_replace("%content", $content, $output); return $output; } function prepend_title($title) { $options = get_option('in_series'); if($options['title_prefix']) { $series = InSeries::adv_CurrentSeries(); if(!is_null($series)) { $series_name = stripslashes($series->series_name); $title = "[{$series_name}] {$title}"; } } return $title; } /**************************************************************************\ * In Series Configuration Hook * \**************************************************************************/ function add_admin_panels() { global $wp_version; $level = "manage_options"; // Use a level number if running in pre-2.0 if(version_compare("2.0",$wp_version) > 0) { $level = 8; } add_options_page(__('In Series Configuration', 'in_series'), __('Series', 'in_series'), $level, 'in-series-config', array('InSeriesInternal', 'display_admin_panels')); } function display_admin_panels() { require_once('in-series-config.php'); } } add_filter('manage_posts_columns', array('InSeriesInternal','add_manage_posts_column')); add_action('manage_posts_custom_column', array('InSeriesInternal','display_manage_posts_column'), 10, 2); add_action('save_post', array('InSeriesInternal','process_save')); add_action('delete_post', array('InSeriesInternal', 'process_delete')); add_action('wp_head', array('InSeriesInternal','insert_doc_rel_links')); add_filter('the_content', array('InSeriesInternal','in_series_linker_content_filter')); add_filter('the_title', array('InSeriesInternal','prepend_title')); add_action('admin_menu', array('InSeriesInternal','add_admin_panels')); InSeriesInternal::compat_hooks(); ?>