Advertisement
masonjames

bbAggregate for MarketPress

Sep 27th, 2011
158
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 57.73 KB | None | 0 0
  1. <?php
  2. /*******************************************************************************
  3. Plugin Name: bbAggregate
  4. Plugin URI: http://www.burobjorn.nl
  5. Description: bbAggregate allows you to aggregate content from multiple blogs into one stream
  6. Author: Bjorn Wijers <burobjorn at burobjorn dot nl>
  7. Version: 1.0
  8. Author URI: http://www.burobjorn.nl
  9. *******************************************************************************/  
  10.  
  11. /*  Copyright 2010
  12.  
  13. bbAggregate is free software; you can redistribute it and/or modify
  14. it under the terms of the GNU General Public License as published by
  15. the Free Software Foundation; either version 2 of the License, or
  16. (at your option) any later version.
  17.  
  18. bbAggregate is distributed in the hope that it will be useful,
  19. but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21. GNU General Public License for more details.
  22.  
  23. You should have received a copy of the GNU General Public License
  24. along with this program; if not, write to the Free Software
  25. Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  26. */
  27.  
  28. if ( ! class_exists('bbAggregate')) {
  29.   class bbAggregate {
  30.  
  31.     /**
  32.      * @var string The options string name for this plugin
  33.     */
  34.     var $options_name = 'bbagg_var_options';
  35.  
  36.     /**
  37.      * @var string $localization_domain Domain used for localization
  38.     */
  39.     var $localization_domain = "bbagg";
  40.  
  41.     /**
  42.      * @var float current version number
  43.      * @todo do not forget to change the version number upon release
  44.      */
  45.     var $bbagg_version = 1;
  46.  
  47.     /**
  48.      * @var string $plugin_url The path to this plugin
  49.     */
  50.     var $plugin_url = '';
  51.  
  52.     /**
  53.      * @var string $plugin_path The path to this plugin
  54.     */
  55.     var $plugin_path = '';
  56.  
  57.     /**
  58.      * @var array $options Stores the options for this plugin
  59.     */
  60.     var $options = array();
  61.  
  62.     /**
  63.      * @var wpdb database object
  64.      */
  65.     var $wpdb = null;
  66.  
  67.     /**
  68.      * @var database table names
  69.      */
  70.     var $db = array();
  71.  
  72.     /**
  73.      * @var bbAggregateStream object
  74.      */
  75.     var $stream;
  76.  
  77.     /**
  78.      * @var holds the lasts aggregated stream's pagination info
  79.      */
  80.     var $last_aggregated_pagination_data = array();  
  81.  
  82.     /**
  83.      * PHP 4 Compatible Constructor
  84.     */
  85.     function bbAggregate(){ $this->__construct(); }
  86.  
  87.     /**
  88.      * PHP 5 Constructor
  89.     */
  90.     function __construct()
  91.     {
  92.       // language setup
  93.       $locale = get_locale();
  94.       $mo     = dirname(__FILE__) . "/lang/" . $this->localization_domain . "-".$locale.".mo";
  95.       load_textdomain($this->localization_domain, $mo);
  96.  
  97.       // 'constants' setup
  98.       $this->plugin_url  = WP_PLUGIN_URL  . '/' . dirname(plugin_basename(__FILE__) ) .'/';
  99.       $this->plugin_path = WP_PLUGIN_DIR . '/' . dirname(plugin_basename(__FILE__) ) .'/';
  100.  
  101.       // database connection setup
  102.       global $wpdb;
  103.       $this->wpdb = $wpdb;
  104.  
  105.       // setup stream
  106.       require_once('lib/bbAggregateStream.class.php');
  107.       $this->stream = new bbAggregateStream($this->wpdb);
  108.  
  109.       // setup stream
  110.       require_once('lib/bbAggregateStreamItem.class.php');
  111.       $this->stream_item = new bbAggregateStreamItem($this->wpdb);
  112.  
  113.       // database table names
  114.       $this->db['table_stream'] = $this->wpdb->base_prefix . 'bbAggregate_stream';
  115.       $this->db['table_stream_item'] = $this->wpdb->base_prefix . 'bbAggregate_item';
  116.  
  117.       // initialize the options
  118.       //This is REQUIRED to initialize the options when the plugin is loaded!
  119.       $this->get_sitewide_options();
  120.  
  121.       // Wordpress actions
  122.       //add_action('init', array(&$this, "init"), 10, 0);
  123.       register_activation_hook(__FILE__, array(&$this, "activate") );
  124.       add_action("admin_menu", array(&$this,"admin_menu_link") );
  125.       add_action('admin_enqueue_scripts', array(&$this, 'admin_js') );
  126.       add_action('save_post', array(&$this, 'save_post_stream_data') );
  127.       add_action('delete_post', array(&$this, 'remove_post_stream_data') );
  128.       add_action('wpmu_options', array(&$this, 'network_options_gui'), 10, 0);
  129.       add_action('update_wpmu_options', array(&$this, 'network_options_save'), 10, 0 );
  130.  
  131.       // actions on which the blog_list cache needs an update
  132.       add_action('wpmu_update_blog_options', array(&$this, '_clear_blogs_transient'), 99, 0);
  133.       add_action('wpmu_new_blog', array(&$this, '_clear_blogs_transient'), 99, 0);
  134.       add_action('delete_blog', array(&$this, '_clear_blogs_transient'), 99, 0);
  135.  
  136.       // add uninstall
  137.       register_uninstall_hook(__FILE__, array(&$this, 'uninstall') );
  138.  
  139.     }
  140.  
  141.     /**
  142.      * Upon plugin activation check if WordPress is in multisite
  143.      * mode.
  144.      *
  145.      * @access public
  146.      * @return void
  147.      **/
  148.     function activate()
  149.     {
  150.       if( is_multisite() ) {
  151.         // setup the database if needed
  152.         $this->setup_database();
  153.       } else {
  154.         // explain the plugin won't work
  155.         // unless multisite is enabled
  156.         wp_die(__('The bbAggregate plugin will only work in a multisite enabled WordPress.', $this->localization_domain) );
  157.       }
  158.     }
  159.  
  160.     /**
  161.      * Upon initialization add a metabox to the WordPress admin post interface
  162.      * this allows a user to add their post to one or more streams
  163.      *
  164.      * @access public
  165.      * @return void
  166.      */
  167.     function setup_metabox()
  168.     {
  169.       add_meta_box($id = 'bbagg-streams', $title = 'Streams', $callback = array(&$this, 'render_post_stream_gui'),
  170.         $page = 'product', $context = 'normal', $priority = 'high', $callback_args=null
  171.       );
  172.     }
  173.  
  174.     /**
  175.      * Renders (echo) a metabox with all streams allowed for this blog
  176.      * to which a post may be added. Callback for add_meta_box
  177.      *
  178.      * @access public
  179.      * @return string html
  180.      */
  181.     function render_post_stream_gui()
  182.     {
  183.       global $blog_id;
  184.       global $post_id;
  185.       $excluded_streams = $this->get_excluded_streams($blog_id);
  186.       $selected_streams = $this->get_item_streams($post_id, $blog_id);
  187.  
  188.       $html  = '';
  189.       $html .= '<p>';
  190.       $html .= __('Add the post to one or more streams: <br />', $this->localization_domain);
  191.       $html .= $this->show_checkboxes_streams($selected_streams, $excluded_streams, $echo = false );
  192.       $html .= wp_nonce_field('bbagg-streams', $name = 'bbagg_wpnonce', true, false);
  193.       $html .= '</p>';
  194.       echo $html;
  195.     }
  196.  
  197.     /**
  198.      * Saves stream data upon the WordPress hook save_post
  199.      *
  200.      * @param int post_id
  201.      * @return int post_id
  202.      */
  203.     function save_post_stream_data( $post_id )
  204.     {
  205.       // only continue if we're not dealing with a revision or auto save
  206.       if( wp_is_post_revision($post_id) || wp_is_post_autosave($post_id) ) {
  207.         return $post_id;
  208.       }
  209.       // check nonce and make sure we're dealing with a post
  210.       if ( isset($_POST['bbagg_wpnonce']) && (wp_verify_nonce($_POST['bbagg_wpnonce'], 'bbagg-streams') ) && ('product' === $_POST['post_type']) ) {
  211.         global $blog_id;
  212.         global $user_ID;
  213.         // First check if the post is already part of one or more streams
  214.         // if it was part of any stream, remove the post from the streams
  215.         // and then add it again with the new data if needed. This makes
  216.         // it easy for a user to deselect a post from a stream
  217.         if( $this->stream_item->does_item_exist($post_id, $blog_id) ) {
  218.           $this->remove_item($post_id, $blog_id);
  219.         }
  220.         if( isset($_POST['bbagg_stream_ids']) && ! empty($_POST['bbagg_stream_ids']) ) {
  221.           $stream_ids = $_POST['bbagg_stream_ids'];
  222.           if( is_array($stream_ids) ) {
  223.             foreach($stream_ids as $stream_id) {
  224.               $this->save_item($post_id, $blog_id, $stream_id, $user_ID);
  225.             }
  226.           }
  227.         }
  228.       }
  229.       // always return the post_id for further use
  230.       return $post_id;
  231.     }
  232.  
  233.     /*
  234.      * If a post gets removed and it is part of a stream
  235.      * we need to remove the post from the streams it was part of as well.
  236.      * Posts placed into the trash will only be removed if they are literally removed
  237.      * from the database. Called by delete_post hook.
  238.      *
  239.      * @access public
  240.      * @param int post_id
  241.      * @return int post_id
  242.      */
  243.     function remove_post_stream_data($post_id)
  244.     {
  245.       // only continue if we're not dealing with a revision or auto save
  246.       if( wp_is_post_revision($post_id) || wp_is_post_autosave($post_id) ) {
  247.         return $post_id;
  248.       }
  249.  
  250.       global $blog_id;
  251.       // First check if the post is already part of one or more streams
  252.       // if it was part of any stream, remove the post from the streams
  253.       // and then add it again with the new data if needed. This makes
  254.       // it easy for a user to deselect a post from a stream
  255.       if( $this->stream_item->does_item_exist($post_id, $blog_id) ) {
  256.         $this->remove_item($post_id, $blog_id);
  257.       }
  258.       return $post_id;
  259.     }
  260.  
  261.     /**
  262.      * Retrieve all streams which should not be
  263.      * shown in this particular blog
  264.      */
  265.     function get_excluded_streams($blog_id)
  266.     {
  267.       $excluded_streams = array();
  268.       $all_streams = $this->get_all_streams();
  269.       if( is_array($all_streams) ) {
  270.         foreach($all_streams as $s) {
  271.           if( is_object($s) ) {
  272.             if( is_array($s->stream_options['bbagg_site_blogs']) ) {
  273.               if(in_array($blog_id, $s->stream_options['bbagg_site_blogs']) ) {
  274.                 $excluded_streams[] = $s->stream_id;
  275.               }
  276.             }
  277.           }
  278.         }
  279.       }
  280.       return $excluded_streams;
  281.     }
  282.  
  283.     /**
  284.      * Checks if a given table is already installed
  285.      *
  286.      * @access private
  287.      * @param string table name
  288.      * @return bool true on database tables installed
  289.      */
  290.     function _is_installed($table_name)
  291.     {
  292.       $sql = $this->wpdb->prepare("SHOW TABLES LIKE %s", $table_name);
  293.       $result = $this->wpdb->get_var($sql) == $table_name;
  294.       if( $result == $table_name ) {
  295.         return true;
  296.       }
  297.       // fall thru
  298.       return false;
  299.     }  
  300.  
  301.     /**
  302.      * Creates the necessary database tables for the plugin. Also deals with
  303.      * plugin specific database upgrades if needed
  304.      *
  305.      * @access public
  306.      * @return void
  307.      */
  308.     function setup_database()
  309.     {
  310.       $queries = array();
  311.  
  312.       // dealing with colation and character sets. Derived from /wp-admin/wp-includes/schema.php
  313.       $charset_collate = '';
  314.       if ( ! empty($this->wpdb->charset) ) {
  315.         $charset_collate = "DEFAULT CHARACTER SET " . $this->wpdb->charset;
  316.       }
  317.       if ( ! empty($this->wpdb->collate) ) {
  318.         $charset_collate .= " COLLATE " . $this->wpdb->collate;
  319.       }
  320.  
  321.       // check if the tables are already installed
  322.       if( $this->_is_installed( $this->db['table_stream'] ) &&
  323.         $this->_is_installed( $this->db['table_stream_item'] ) ) {
  324.           // if there are already tables installed, check the version
  325.           //  to see if and what we need to update
  326.  
  327.       } else {
  328.         // no tables so we need to install using the latest sql
  329.         $queries[] = "CREATE TABLE IF NOT EXISTS " . $this->db['table_stream'] . " (
  330.          stream_id bigint(20) NOT NULL auto_increment,
  331.          stream_name varchar(255) NOT NULL,
  332.          stream_slug varchar(255) NOT NULL,
  333.          stream_description text NOT NULL,
  334.          stream_options text NOT NULL,
  335.          stream_nr_items bigint(20) NOT NULL,
  336.          PRIMARY KEY  (stream_id)
  337.        ) $charset_collate;";
  338.  
  339.         $queries[] = "CREATE TABLE IF NOT EXISTS " . $this->db['table_stream_item'] . " (
  340.          stream_id bigint(20) NOT NULL,
  341.          post_id bigint(20) not null,
  342.          user_id bigint(20) not null,
  343.          blog_id bigint(20) NOT NULL,
  344.          modify_date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  345.          KEY stream_id (stream_id),
  346.          KEY post_id (post_id,blog_id)
  347.        ) $charset_collate;";
  348.  
  349.       }
  350.       // include dbDelta function
  351.       require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
  352.  
  353.       // for some unknown reason dbDelta refuses to work correctly with multiple queries at once
  354.       // so for now I use a for loop to loop through the necessary queries
  355.       foreach($queries as $q) {
  356.         dbDelta($q);
  357.       }
  358.     }
  359.  
  360.     /**
  361.      * Add custom javascript scripts from our plugin
  362.      * to the WordPress admin interface
  363.      *
  364.      * @TODO Play nice: add check so the js will only
  365.      * be included on ou plugin's page
  366.      */
  367.     function admin_js($hook_suffix)
  368.     {
  369.       wp_enqueue_script('bbagg-admin-js', $this->plugin_url . 'js/bbagg-admin.js');
  370.     }
  371.  
  372.     /**
  373.      * Retrieves aggregated posts, including author name, blog name, blog url
  374.      * and nummer of comments made on the post in one array sorted by date/time/title
  375.      * limited by total number of posts allowed
  376.      *
  377.      * @access public
  378.      * @param string stream name
  379.      * @param int start default 0
  380.      * @param int end default uses the setting made in the plugin options
  381.      * @return array posts
  382.      */
  383.     function aggregate($stream_name, $start = 0, $end = null)
  384.     {
  385.       // retrieve the current page permalink from which the aggregate function
  386.       // is called from. We use this for the paginate functionality
  387.       $absolute_url =  get_permalink();
  388.  
  389.       // retrieve the posts and sort them.
  390.       $posts = $this->_sort_posts( $this->_get_posts($stream_name) );
  391.  
  392.       if( is_array($posts) ) {
  393.         $total_nr_posts = sizeof($posts);
  394.  
  395.         if( is_null($end) ) {
  396.         // retrieve the stream's max number of posts
  397.         $max = $this->stream->get_option( $this->stream->get_stream_id_by_name($stream_name), 'bbagg_option_max_nr_posts');
  398.         $max_nr_posts = is_numeric($max) ? $max : $total_nr_posts; // default to all posts if no limit was given
  399.         }    
  400.  
  401.         $posts_per_page = $this->stream->get_option( $this->stream->get_stream_id_by_name($stream_name), 'bbagg_option_posts_per_page');
  402.         $posts_per_page = is_numeric($posts_per_page) ? $posts_per_page : $total_nr_posts; // defaults to all posts
  403.  
  404.         $args = array(
  405.           'max_nr_posts'   => $max_nr_posts,
  406.           'total_nr_posts' => $total_nr_posts,
  407.           'posts_per_page' => $posts_per_page,
  408.           'posts'          => $posts,
  409.           'absolute_url'   => $absolute_url
  410.         );
  411.  
  412.         return $this->_calculate_start_offset($args);
  413.       } else {
  414.         return -1;
  415.       }
  416.     }
  417.  
  418.     /**
  419.      * _calculate_start_offset
  420.      * based on the wp_query object calculates start and end of posts query
  421.      *
  422.      * @param array $args
  423.      * @access protected
  424.      * @return array of posts
  425.      */
  426.     function _calculate_start_offset($args = array() )
  427.     {
  428.       $defaults = array(
  429.         'max_nr_posts'   => 0,
  430.         'total_nr_posts' => 0,
  431.         'posts_per_page' => 0,
  432.         'posts'          => -1,
  433.         'absolute_url'   => ''
  434.       );
  435.  
  436.       $r        = wp_parse_args( $args, $defaults );
  437.       extract( $r, EXTR_SKIP ); // extract the parameters into variables
  438.  
  439.       // get the current wp_query object
  440.       global $wp_query;
  441.       // check if the page query parameter is set
  442.       // and retrieve the current page_nr if it has been set
  443.       // this will overwrite the default page_nr which is 1
  444.       $current_page_nr = 1;
  445.       if( is_object($wp_query) ) {
  446.         if( is_array($wp_query->query_vars) && array_key_exists('page', $wp_query->query_vars) ) {
  447.           if( is_numeric($wp_query->query_vars['page']) ) {
  448.             $current_page_nr = (int) $wp_query->query_vars['page'];
  449.             $current_page_nr = ($current_page_nr < 0) ? 1 : $current_page_nr; // make sure the page nr is always 1 or bigger
  450.           }
  451.         }
  452.       }
  453.       // calculate the max nr of pages in total
  454.       $total_nr_pages = ceil($total_nr_posts / $posts_per_page);
  455.  
  456.       // make sure the current_page_nr is smaller than the total nr pages
  457.       $current_page_nr = ($current_page_nr < $total_nr_pages) ? $current_page_nr : $total_nr_pages;
  458.  
  459.       $offset = ($current_page_nr * $posts_per_page) - $posts_per_page;
  460.       $offset = ($offset < 0) ? 0 : $offset; // make sure offset is not smaller than zero
  461.       $length = $posts_per_page;
  462.  
  463.       $this->last_aggregated_pagination_data = array(
  464.         'total_nr_pages'  => $total_nr_pages,
  465.         'current_page_nr' => $current_page_nr,
  466.         'absolute_url'    => $absolute_url
  467.       );
  468.  
  469.       /* Help with debugging */
  470.       //echo "Total nr pages: $total_nr_pages<br />\n";
  471.       //echo "Current page nr: $current_page_nr<br />\n";
  472.       //echo "Offset: $offset<br />\n";
  473.       //echo "Length: $length<br />\n";
  474.  
  475.       if( is_array($posts) ) {
  476.         return array_slice($posts, $offset, $length);
  477.       } else {
  478.         return array();
  479.       }
  480.     }
  481.  
  482.     /**
  483.      * paginate creates the paginate html
  484.      *
  485.      * @param array
  486.      * @access public
  487.      * @return string html
  488.      */
  489.     function paginate($args = array())
  490.     {
  491.       $defaults = array(
  492.         'class'         => 'bbagg_paginate_link',
  493.         'class_current' => 'selected',
  494.         'html_before'   => '<li>',
  495.         'html_after'    => '</li>'
  496.       );
  497.  
  498.       $r        = wp_parse_args( $args, $defaults );
  499.       extract( $r, EXTR_SKIP ); // extract the parameters into variables
  500.       extract( $this->last_aggregated_pagination_data);
  501.       $link ='';
  502.       for ($i = 1; $i <= $total_nr_pages; $i++) {
  503.         $class_link = ($i == $current_page_nr) ? "class=\"$class $class_current\"" : "class=\"$class\"";
  504.         $url   = $absolute_url . $i;
  505.         $link .= $html_before . "<a href=\"$url\" $class_link>$i</a>" . $html_after;
  506.       }
  507.       echo $link;
  508.     }  
  509.  
  510.     /**
  511.      * Adds the options subpanel for the plugin
  512.      * Called by WP action 'admin_menu'
  513.      *
  514.      * @access public
  515.      * @return void
  516.     */
  517.     function admin_menu_link()
  518.     {
  519.       // If you change this from add_options_page, MAKE SURE you change the filter_plugin_actions function (below) to
  520.       // reflect the page filename (ie - options-general.php) of the page your plugin is under!
  521.       add_options_page('bbAggregate', 'bbAggregate', 'manage_options', basename(__FILE__), array(&$this,'admin_options_page'));
  522.       add_filter( 'plugin_action_links_' . plugin_basename(__FILE__), array(&$this, 'filter_plugin_actions'), 10, 2 );
  523.       $this->setup_metabox();
  524.     }
  525.  
  526.     /**
  527.      * Adds the Settings link to the plugins activate/deactivate page
  528.      * Called by WP filter 'plugin_action_links'
  529.      *
  530.      * @param array links
  531.      * @param file
  532.      * @return array links
  533.     */
  534.     function filter_plugin_actions($links, $file)
  535.     {
  536.       // If your plugin is under a different top-level menu than
  537.       // Settiongs (IE - you changed the function above to something other than add_options_page)
  538.       // Then you're going to want to change options-general.php below to the name of your top-level page
  539.       $settings_link = '<a href="options-general.php?page=' . basename(__FILE__) . '">' . __('Settings') . '</a>';
  540.       array_unshift( $links, $settings_link ); // before other links
  541.       return $links;
  542.     }
  543.  
  544.     /**
  545.      * Show the available streams as a dropdown
  546.      *
  547.      * @access public
  548.      * @param int stream id Selected stream (optional)
  549.      * @param bool echo TRUE echo result, FALSE return the result
  550.      * @return string html
  551.      **/
  552.     function show_dropdown_streams($chosen_stream_id = null, $echo = false)
  553.     {
  554.       $streams = $this->get_all_streams();
  555.       if( is_array($streams) ) {
  556.         $html = "<select name=\"bbagg_edit_stream_id\" id=\"bbagg_edit_stream_id\">\n";
  557.         $selected = ( is_null($chosen_stream_id) ) ? "selected='selected'" : '';
  558.         $html .= "<option value=\"-1\">" .__('Select a stream to edit') . "</option>";
  559.         foreach ($streams as $s) {
  560.           if( is_object($s) ) {
  561.           $selected = ($chosen_stream_id == $s->stream_id) ? "selected='selected'" : '';
  562.           $html .= "<option $selected value=\"$s->stream_id\">$s->stream_name</option>\n";
  563.           }
  564.         }
  565.         $html .= "</select>\n";
  566.         $html .= "<input type=\"submit\" name =\"bbagg_edit_stream_gui\" value=\"" . __('Edit selected stream', $this->localization_domain) . "\">";
  567.       }
  568.       if($echo) {
  569.         echo $html;
  570.       } else {
  571.         return $html;
  572.       }
  573.     }
  574.  
  575.     /**
  576.      * Show all availabe streams as checkboxes
  577.      *
  578.      * @param array stream ids of streams which need to be selected
  579.      * @param array stream ids of streams not be rendered
  580.      * @param bool echo or return the html defaults to returning
  581.      * @return string html
  582.      */
  583.     function show_checkboxes_streams($selected_streams = array(), $excluded_streams = array(), $echo = false)
  584.     {
  585.       $streams = $this->get_all_streams();
  586.       if( is_array($streams) ) {
  587.         $html = '';
  588.         foreach ($streams as $s){
  589.           if( ! in_array($s->stream_id, $excluded_streams) ) {
  590.             $checked = ( in_array($s->stream_id, $selected_streams) ) ? 'checked="checked"' : '';
  591.             $html .= "<input type=\"checkbox\" value=\"$s->stream_id\" name=\"bbagg_stream_ids[]\" $checked id=\"bbagg_stream_id_$s->stream_id\" />";
  592.             $html .= " <label for=\"bbagg_stream_id_$s->stream_id\">" . esc_html($s->stream_name) .' (' . esc_html($s->stream_nr_items) .')' . "</label><br />\n";
  593.           }
  594.         }
  595.       }
  596.       if($echo) {
  597.         echo $html;
  598.       } else {
  599.         return $html;
  600.       }
  601.     }
  602.  
  603.     /**
  604.      * Check if there are streams available
  605.      *
  606.      * @access public
  607.      * @return bool true on streams available, false on none
  608.      */
  609.     function has_streams()
  610.     {
  611.       $nr_streams = $this->stream->nr_streams_available();
  612.       if( is_numeric($nr_streams) && $nr_streams > 0) {
  613.         return true;
  614.       }
  615.       // fall thru
  616.       return false;
  617.     }
  618.  
  619.     /**
  620.      * _get_site_blogs implements a custom get_blog_list which is deprecated in
  621.      * WordPress 3.
  622.      *
  623.      * @param int $start defaults to zero
  624.      * @param string|int $num defaults to 'all'
  625.      * @access protected
  626.      * @return array with per blog an array with the blogs id, domain and path
  627.      */
  628.     function _get_site_blogs($start = 0, $num = 'all')
  629.     {
  630.       // sets the time to live for the cache / transients
  631.       $cache_time = 60*60*24; // sets the cache for 24 hrs
  632.  
  633.       // check if we have a cached result otherwise retrieve the results from the database
  634.       if(false === ($blogs = get_site_transient('bbagg_blogs') ) ) {
  635.         // prepare query
  636.         $query = $this->wpdb->prepare("SELECT blog_id, domain, path FROM {$this->wpdb->blogs}
  637.          WHERE site_id = %d
  638.          AND public = '1'
  639.          AND archived = '0'
  640.          AND mature = '0'
  641.          AND spam = '0'
  642.          AND deleted = '0'
  643.          ORDER BY registered DESC", $this->wpdb->siteid
  644.         );
  645.         // retrieve the results from the database
  646.         $blogs = $this->wpdb->get_results($query, ARRAY_A );
  647.         // if we have some results cache them so we can use the cache
  648.         if(is_array($blogs) && sizeof($blogs) > 0) {
  649.           set_site_transient('bbagg_blogs', $blogs, $cache_time);
  650.         }
  651.       }
  652.  
  653.       // setup the array similar to the deprecated get_blog_list function
  654.       foreach ( (array) $blogs as $details ) {
  655.         $blog_list[ $details['blog_id'] ] = $details;
  656.       }
  657.       unset( $blogs );
  658.       $blogs = $blog_list;
  659.  
  660.       // make sure we always return an array
  661.       if ( false == is_array( $blogs ) ) {
  662.         return array();
  663.       } elseif ( $num == 'all' ) {
  664.         return array_slice( $blogs, $start, count( $blogs ) );
  665.       } else {
  666.         return array_slice( $blogs, $start, $num );
  667.       }
  668.     }
  669.  
  670.     /**
  671.      * _clear_blogs_transient removes the bbagg_blogs transient
  672.      * upon a blog options change, blog addition or blog removal
  673.      *
  674.      * @access protected
  675.      * @return void
  676.      */
  677.     function _clear_blogs_transient()
  678.     {
  679.       if(false !== get_site_transient('bbagg_blogs') ) {
  680.         delete_site_transient('bbagg_blogs');
  681.       }
  682.     }
  683.  
  684.     /**
  685.      * Show all public blogs as checkboxes
  686.      *
  687.      * @access public
  688.      * @param array blog ids OPTIONAL
  689.      * @param bool echo on true or return value on false (default)
  690.      * @return string html checkboxes
  691.      */
  692.     function show_checkboxes_site_blogs($selected_blogs = array(), $echo = false)
  693.     {
  694.       $site_blogs = $this->_get_site_blogs();
  695.       if( is_array($site_blogs) ) {
  696.         $html = '';
  697.         foreach ($site_blogs as $blog){
  698.           $checked = '';
  699.           if( is_array($blog) && is_array($selected_blogs) ) {
  700.             $checked = ( in_array($blog['blog_id'], $selected_blogs) ) ? 'checked="checked"' : '';
  701.           }
  702.           $html .= "<input type=\"checkbox\" value=\"{$blog['blog_id']}\" name=\"bbagg_site_blogs[]\" $checked id=\"bbagg_site_blogs\" />
  703.            <span> {$blog['domain']}{$blog['path']}</span><br />\n";
  704.         }
  705.       }
  706.       if($echo) {
  707.         echo $html;
  708.       } else {
  709.         return $html;
  710.       }
  711.     }
  712.  
  713.     /**
  714.      * Adds settings/options page for the plugin. This is
  715.      * the main dispatcher dealing with streams and their settings
  716.      * called by WordPress add_options_page function
  717.      *
  718.      * @access public
  719.      */
  720.     function admin_options_page()
  721.     {
  722.  
  723.       // continue with a dispatcher like control structure.
  724.       // this takes care of routing requests to the correct
  725.       // function and shows the correct gui
  726.       if( isset($_POST['bbagg_new_stream_gui']) ) {
  727.  
  728.         // using nonce to make sure the forms originate from our
  729.         // plugin. AFAIK there's no need to use different nonces per
  730.         // action, so I'll use only one for all operations.
  731.         if ( ! wp_verify_nonce($_POST['bbagg_wpnonce'], 'bbagg-actions') ) {
  732.             die( __('Whoops! There was a problem with the data you posted. Please go back and try again.', $this->localization_domain) ) ;
  733.         }
  734.  
  735.         // show the 'add new stream' gui
  736.         // make sure the entered stream name is unique and not empty
  737.         // or else show an error message
  738.         if( ($stream_ok = $this->is_stream_name_ok($_POST['bbagg_stream_name']) ) === 1 ) {
  739.           $this->_render_stream_gui($_POST['bbagg_stream_name']);
  740.         } else {
  741.           echo $this->_retrieve_error($stream_ok);
  742.         }
  743.  
  744.       } elseif( isset($_POST['bbagg_edit_stream_gui']) ) {
  745.  
  746.         // using nonce to make sure the forms originate from our
  747.         // plugin. AFAIK there's no need to use different nonces per
  748.         // action, so I'll use only one for all operations.
  749.         if ( ! wp_verify_nonce($_POST['bbagg_wpnonce'], 'bbagg-actions') ) {
  750.             die( __('Whoops! There was a problem with the data you posted. Please go back and try again.', $this->localization_domain) ) ;
  751.         }
  752.  
  753.         // show the 'edit stream' gui
  754.         // use the stream_id to get the requested stream data
  755.         // make sure the id is a number higher than zero
  756.         if( isset($_POST['bbagg_edit_stream_id']) && is_numeric($_POST['bbagg_edit_stream_id']) && $_POST['bbagg_edit_stream_id'] > 0 ) {
  757.           $this->_render_stream_gui($_POST['bbagg_edit_stream_id'], 'edit');
  758.         } else {
  759.           $msg = __('<div class="error fade"><p>Not a valid stream. Perhaps you have no streams to edit yet?</p></div>', $this->localization_domain);
  760.           $this->_render_default_gui($msg, true);
  761.         }
  762.       } elseif( isset($_POST['bbagg_remove_stream']) ) {
  763.  
  764.         // using nonce to make sure the forms originate from our
  765.         // plugin. AFAIK there's no need to use different nonces per
  766.         // action, so I'll use only one for all operations.
  767.         if ( ! wp_verify_nonce($_POST['bbagg_wpnonce'], 'bbagg-actions') ) {
  768.             die( __('Whoops! There was a problem with the data you posted. Please go back and try again.', $this->localization_domain) ) ;
  769.         }
  770.  
  771.         // remove one or more streams based on their id
  772.         if( $this->remove_stream($_POST['bbagg_stream_ids']) ) {
  773.           $msg = __('<div class="updated fade"><p>Successfully removed stream(s)</p></div>', $this->localization_domain);
  774.         } else {
  775.           $msg = __('<div class="error fade"><p>Could not remove stream(s)</p></div>', $this->localization_domain);
  776.         }
  777.         // return to the default gui and notify the user
  778.         $this->_render_default_gui($msg, true);
  779.       } elseif( isset($_POST['bbagg_save_stream']) ) {
  780.  
  781.         // process and save the new stream
  782.         // check if the name is unique and not empty
  783.         if( ($stream_ok = $this->is_stream_name_ok($_POST['bbagg_stream_name']) ) === 1 ) {
  784.  
  785.           // process options into an array, will be un/serialized in Stream class
  786.           $options = array (
  787.             'bbagg_option_posts_per_page' => (int) $_POST['bbagg_option_posts_per_page'],
  788.             'bbagg_option_max_nr_posts_per_blog' => (int) $_POST['bbagg_option_max_nr_posts_per_blog'],
  789.             'bbagg_option_max_nr_posts'   => (int) $_POST['bbagg_option_max_nr_posts'],
  790.             'bbagg_site_blogs'  => (array) isset($_POST['bbagg_site_blogs']) ? $_POST['bbagg_site_blogs'] : array() // @todo BjornW: fix this, not it will result in an undefined index
  791.           );
  792.  
  793.           $new_stream = array (
  794.             'stream_name' => $_POST['bbagg_stream_name'],
  795.             'stream_slug'        => $this->sanitize( $_POST['bbagg_stream_name'] ),
  796.             'stream_description' => $_POST['bbagg_stream_description'],
  797.             'stream_options'     => $options);
  798.  
  799.           if($this->add_stream($new_stream) === true) {
  800.             $msg = sprintf( __('<div class="updated fade" id="bbagg-message"><p>The stream <em>%s</em> was successfully created!</p></div>',
  801.               $this->localization_domain), $_POST['bbagg_stream_name'] );
  802.           }
  803.  
  804.         } else {
  805.             $msg = $this->_retrieve_error($stream_ok);
  806.         }
  807.         // return to the default menu and notify the user on their actions
  808.         $this->_render_default_gui($msg, true);
  809.       } elseif( isset($_POST['bbagg_update_stream']) ) {
  810.         // process and update an existing stream
  811.         $stream_ok = 1;
  812.         // check if the name has changed, if it did make sure
  813.         // the new name is unique, if not the value will be a negative int
  814.         if($this->has_stream_name_changed( (int) $_POST['bbagg_update_stream_id'], $_POST['bbagg_stream_name'] ) ) {
  815.           $stream_ok = $this->is_stream_name_ok($_POST['bbagg_stream_name']);
  816.         }
  817.  
  818.         if($stream_ok === 1) {
  819.           // process options into an array, will be un/serialized in Stream class
  820.           $options = array (
  821.             'bbagg_option_posts_per_page'        => (int) $_POST['bbagg_option_posts_per_page'],
  822.             'bbagg_option_max_nr_posts_per_blog' => (int) $_POST['bbagg_option_max_nr_posts_per_blog'],
  823.             'bbagg_option_max_nr_posts'          => (int) $_POST['bbagg_option_max_nr_posts'],
  824.             'bbagg_site_blogs'                   => (array) $_POST['bbagg_site_blogs']
  825.           );
  826.  
  827.           $new_stream = array (
  828.             'stream_id'          => (int) $_POST['bbagg_update_stream_id'],
  829.             'stream_name'        => $_POST['bbagg_stream_name'],
  830.             'stream_slug'        => $this->sanitize( $_POST['bbagg_stream_name'] ),
  831.             'stream_description' => $_POST['bbagg_stream_description'],
  832.             'stream_options'     => $options);
  833.  
  834.           if($this->update_stream($new_stream) === true) {
  835.             $msg = sprintf( __('<div class="updated fade" id="bbagg-message"><p>The stream <em>%s</em> was successfully updated!</p></div>',
  836.               $this->localization_domain), $_POST['bbagg_stream_name'] );
  837.           }
  838.  
  839.         } else {
  840.           $msg = $this->_retrieve_error($stream_ok);
  841.         }
  842.         // return to the default menu and notify the user on their actions
  843.         $this->_render_default_gui($msg, true);
  844.       } else {
  845.         // show the default options gui
  846.         $this->_render_default_gui($msg = '', true);
  847.       }
  848.  
  849.     }
  850.  
  851.     /**
  852.      * Renders the default gui for the plugin
  853.      *
  854.      * @access public
  855.      * @param string optional message defaults to empty
  856.      * @param bool echo or return html defaults to returning
  857.      */
  858.     function _render_default_gui($msg = '', $echo = false)
  859.     {
  860.       // build the options page
  861.       $html = $msg;
  862.       $html .= "<div class=\"wrap\">\n";
  863.       $html .= "<h2>bbAggregate</h2>\n";
  864.       $html .= "<form method=\"post\" id=\"bbagg_options\">";
  865.       $html .= wp_nonce_field('bbagg-actions', $name = 'bbagg_wpnonce', true, false);
  866.       $html .= "<table width=\"100%\" cellspacing=\"2\" cellpadding=\"5\" class=\"form-table\">\n";
  867.  
  868.       $html .= "<tr valign=\"top\">\n";
  869.       $html .= "\t<th width=\"33%\" scope=\"row\">" .  __('Create a new stream:', $this->localization_domain) . "</th>\n";
  870.       $html .= "\t<td><input type=\"text\" size=\"45\" maxlength=\"255\" name=\"bbagg_stream_name\" id=\"bbagg_stream_name\" /> ";
  871.       $html .= "\t<input name=\"bbagg_new_stream_gui\" type=\"submit\" id=\"bbagg_new_stream_gui\" value=\"" . __('Add a new stream') . "\" /></td>\n";
  872.       $html .= "</tr>\n";
  873.  
  874.       $html .= "<tr valign=\"top\">\n";
  875.       $html .= "\t<th width=\"33%\" scope=\"row\">" .  __('Edit an existing stream:', $this->localization_domain) . "</th>\n";
  876.       $html .= "\t<td>" . $this->show_dropdown_streams() ."</td>\n";
  877.       $html .= "</tr>\n";
  878.  
  879.       // only show if we have some streams
  880.       if( $this->has_streams() ) {
  881.         $html .= "<tr valign=\"top\">\n";
  882.         $html .= "\t<th width=\"33%\" scope=\"row\">" .  __('Remove one or more existing streams:', $this->localization_domain) . "</th>\n";
  883.         $html .= "\t<td>" . $this->show_checkboxes_streams();
  884.         $html .= "\t<input name=\"bbagg_remove_stream\" type=\"submit\" id=\"bbagg_remove_stream\" value=\"" . __('Remove selected streams') . "\" />\n";
  885.         $html .= "</td>\n";
  886.       }
  887.  
  888.       $html .= "</tr>\n";
  889.       $html .= "</table>\n";
  890.       $html .= "</form>\n";
  891.  
  892.       if($echo) {
  893.         echo $html;
  894.       } else {
  895.         return $html;
  896.       }
  897.     }  
  898.  
  899.     /**
  900.      * Renders (echo) the new/edit stream interface.
  901.      *
  902.      * @param string|array stream name or stream as array
  903.      * @param string required action defaults to new
  904.      *
  905.      */
  906.     function _render_stream_gui($stream, $action = 'new')
  907.     {
  908.       if( is_numeric($stream) && $action == 'edit' ) {
  909.         if($stream < 0) { return; } // do nothing on values lower than zero
  910.         $stream_data = $this->stream->get($stream);
  911.         if( is_array($stream_data) && sizeof($stream_data) == 1 ) {
  912.           if( is_object($stream_data[0]) ) {
  913.             $stream_name        = $stream_data[0]->stream_name;
  914.             $stream_description = $stream_data[0]->stream_description;
  915.             $stream_options     = $stream_data[0]->stream_options;
  916.             extract($stream_options);
  917.           }
  918.         }
  919.         $btn = "\t<th colspan=2><input type=\"hidden\" name=\"bbagg_update_stream_id\" value=\"" . (int) $stream . " \" />";
  920.         $btn .="<input type=\"submit\" name=\"bbagg_update_stream\" value=\"" . __('Update stream', $this->localization_domain) . "\"/></th>\n";
  921.       } else {
  922.         $stream_name = $stream;
  923.         $stream_description = '';
  924.         // get the sitwide defaults
  925.         extract($this->options);
  926.         $btn = "\t<th colspan=2><input type=\"submit\" name=\"bbagg_save_stream\" value=\"" . __('Save new stream', $this->localization_domain) . "\"/></th>\n";
  927.       }
  928.  
  929.       $html = '';
  930.       $html .= "<div class=\"wrap\">\n";
  931.       $html .= "<h2>bbAggregate</h2>\n";
  932.       $html .= "<form method=\"post\" id=\"bbagg_options\">";
  933.       $html .= wp_nonce_field('bbagg-add-stream', $name = 'bbagg_wpnonce', $referer = true, $echo = false);
  934.       $html .= "<table width=\"100%\" cellspacing=\"2\" cellpadding=\"5\" class=\"form-table\">\n";
  935.  
  936.       $html .= "<tr valign=\"top\">\n";
  937.       $html .= "\t<th width=\"33%\" scope=\"row\">" .  __('Stream name:', $this->localization_domain) . "</th>\n";
  938.       $html .= "\t<td><input name=\"bbagg_stream_name\" type=\"text\" maxlength=\"255\" size=\"45\" id=\"bbagg_stream_name\" value=\"$stream_name\" /></td>\n";
  939.       $html .= "</tr>\n";
  940.  
  941.       $html .= "<tr valign=\"top\">\n";
  942.       $html .= "\t<th width=\"33%\" scope=\"row\">" .  __('Stream description:', $this->localization_domain) . "</th>\n";
  943.       $html .= "\t<td><textarea name=\"bbagg_stream_description\" rows=\"10\" cols=\"42\" id=\"bbagg_stream_description\">$stream_description</textarea></td>\n";
  944.       $html .= "</tr>\n";
  945.  
  946.       $html .= "<tr valign=\"top\">\n";
  947.       $html .= "\t<th width=\"33%\" colspan=\"2\" scope=\"row\"><strong>" .  __('Stream Options', $this->localization_domain) . "</strong></th>\n";
  948.       $html .= "</tr>\n";
  949.  
  950.       $html .= "<tr valign=\"top\">\n";
  951.       $html .= "\t<th width=\"33%\" scope=\"row\">" .  __('Maximum number of posts in this stream:', $this->localization_domain) . "</th>\n";
  952.       $html .= "\t<td><input name=\"bbagg_option_max_nr_posts\" type=\"text\" id=\"bbagg_option_max_nr_posts\" size=\"45\" value=\"$bbagg_option_max_nr_posts\" /></td>\n";
  953.       $html .= "</tr>\n";
  954.  
  955.       $html .= "<tr valign=\"top\">\n";
  956.       $html .= "\t<th width=\"33%\" scope=\"row\">" .  __('Maximum number of posts per blog in this stream:', $this->localization_domain) . "</th>\n";
  957.       $html .= "\t<td><input name=\"bbagg_option_max_nr_posts_per_blog\" type=\"text\" id=\"bbagg_option_max_nr_posts_per_blog\" size=\"45\" value=\"$bbagg_option_max_nr_posts_per_blog\" /></td>\n";
  958.       $html .= "</tr>\n";
  959.  
  960.       $html .= "<tr valign=\"top\">\n";
  961.       $html .= "\t<th width=\"33%\" scope=\"row\">" .  __('Number of posts per page:', $this->localization_domain) . "</th>\n";
  962.       $html .= "\t<td><input name=\"bbagg_option_posts_per_page\" type=\"text\" id=\"bbagg_option_posts_per_page\" size=\"45\" value=\"$bbagg_option_posts_per_page\" /></td>\n";
  963.       $html .= "</tr>\n";
  964.  
  965.       $html .= "<tr valign=\"top\">\n";
  966.       $html .= "\t<th width=\"33%\" scope=\"row\">" . __('Exclude these blogs from aggregating in this stream:', $this->localization_domain) . "</th>\n";
  967.       $html .= "\t<td>" . $this->show_checkboxes_site_blogs($bbagg_site_blogs) . "</td>\n";
  968.       $html .= "</tr>\n";
  969.  
  970.       $html .= "<tr>\n";
  971.       $html .= $btn;
  972.       $html .= "</tr>\n";
  973.  
  974.       $html .= "</tr>\n";
  975.       $html .= "</table>\n";
  976.       $html .= "</form>\n";
  977.       echo $html;
  978.     }
  979.  
  980.     /**
  981.      * Renders a gui for the sitewide default options of the plugin.
  982.      * Displayed at /wp-admin/ms-options.php
  983.      * Called by wpmu_options action
  984.      *
  985.      * @access public
  986.      * @return string html
  987.      */
  988.     function network_options_gui()
  989.     {
  990.       // get the defaults from an array
  991.       extract($this->options);
  992.  
  993.      // build the options page
  994.       $html  = '';
  995.       $html .= "<h3>bbAggregate sitewide defaults</h3>\n";
  996.       $html .= wp_nonce_field('bbagg-update-options', $name = 'bbagg_wpnonce', $referer = true, $echo = false);
  997.       $html .= "<table width=\"100%\" cellspacing=\"2\" cellpadding=\"5\" class=\"form-table\">\n";
  998.  
  999.       $html .= "<tr valign=\"top\">\n";
  1000.       $html .= "\t<th width=\"33%\" scope=\"row\">" .  __('Maximum total of posts to aggregate:', $this->localization_domain) . "</th>\n";
  1001.       $html .= "\t<td><input name=\"bbagg_option_max_nr_posts\" type=\"text\" id=\"bbagg_option_max_nr_posts\" size=\"45\" value=\"$bbagg_option_max_nr_posts\" /></td>\n";
  1002.       $html .= "</tr>\n";
  1003.  
  1004.       $html .= "<tr valign=\"top\">\n";
  1005.       $html .= "\t<th width=\"33%\" scope=\"row\">" .  __('Maximum number of posts per blog aggregated:', $this->localization_domain) . "</th>\n";
  1006.       $html .= "\t<td><input name=\"bbagg_option_max_nr_posts_per_blog\" type=\"text\" id=\"bbagg_option_max_nr_posts_per_blog\" size=\"45\" value=\"$bbagg_option_max_nr_posts_per_blog\" /></td>\n";
  1007.       $html .= "</tr>\n";
  1008.  
  1009.       $html .= "<tr valign=\"top\">\n";
  1010.       $html .= "\t<th width=\"33%\" scope=\"row\">" .  __('Show number of posts at a time (used for pagination) :', $this->localization_domain) . "</th>\n";
  1011.       $html .= "\t<td><input name=\"bbagg_option_posts_per_page\" type=\"text\" id=\"bbagg_option_posts_per_page\" size=\"45\" value=\"$bbagg_option_posts_per_page\" /></td>\n";
  1012.       $html .= "</tr>\n";
  1013.  
  1014.       $html .= "<tr valign=\"top\">\n";
  1015.       $html .= "\t<th width=\"33%\" scope=\"row\">" . __('Exclude blogs from aggregating:', $this->localization_domain) . "</th>\n";
  1016.       $html .= "\t<td>" . $this->show_checkboxes_site_blogs($bbagg_site_blogs) . "</td>\n";
  1017.       $html .= "</tr>\n";
  1018.  
  1019.       $html .= "</table>\n";
  1020.       // show the built page
  1021.       echo $html;
  1022.     }
  1023.  
  1024.     /**
  1025.      * Retrieves the plugin options from the database.
  1026.      *
  1027.      * @access public
  1028.      * @return options array
  1029.     */
  1030.     function get_sitewide_options()
  1031.     {
  1032.       // don't forget to set up the default options
  1033.       if ( ! $the_options = get_site_option( $this->options_name) ) {
  1034.         // defaults
  1035.         $the_options = array(
  1036.           'bbagg_version'                      => $this->bbagg_version,
  1037.           'bbagg_option_max_nr_posts_per_blog' => 20,
  1038.           'bbagg_option_max_nr_posts'          => 250,
  1039.           'bbagg_option_posts_per_page'        => 10,
  1040.           'bbagg_site_blogs'                   => array()
  1041.         );
  1042.  
  1043.         update_site_option($this->options_name, $the_options);
  1044.       }
  1045.       $this->options = $the_options;
  1046.     }
  1047.  
  1048.     /**
  1049.      * Saves/updates the sitewide options
  1050.      *
  1051.      * @param mixed data
  1052.      * @return void
  1053.      */
  1054.     function network_options_save()
  1055.     {
  1056.       $options = array (
  1057.         'bbagg_option_posts_per_page'        => (int) $_POST['bbagg_option_posts_per_page'],
  1058.         'bbagg_option_max_nr_posts_per_blog' => (int) $_POST['bbagg_option_max_nr_posts_per_blog'],
  1059.         'bbagg_option_max_nr_posts'          => (int) $_POST['bbagg_option_max_nr_posts'],
  1060.         'bbagg_site_blogs'                   => (array) $_POST['bbagg_site_blogs']
  1061.       );
  1062.       update_site_option($this->options_name, $options);
  1063.     }
  1064.  
  1065.     /**
  1066.      * Wraper for Stream object to add a new stream
  1067.      * to the database
  1068.      *
  1069.      * @access public
  1070.      * @param array stream data as an associative array
  1071.      * @return int|false Number of rows affected/selected or false on error
  1072.      */
  1073.     function add_stream($stream)
  1074.     {
  1075.       return $this->stream->save($stream);
  1076.     }
  1077.  
  1078.     /**
  1079.      * Wrapper for Stream object.
  1080.      * Updates an existing stream, needs a stream id otherwise
  1081.      * a new stream will be created
  1082.      *
  1083.      * @access public
  1084.      * @param array stream data as an associative array
  1085.      *
  1086.      */
  1087.     function update_stream($stream)
  1088.     {
  1089.       return $this->stream->save($stream);
  1090.     }
  1091.  
  1092.     /**
  1093.      * Removes a stream and all its items
  1094.      * from the database.
  1095.      *
  1096.      * @access
  1097.      * @param int|array one stream id or multiple stream ids in an array
  1098.      * @return
  1099.      */
  1100.     function remove_stream($stream)
  1101.     {
  1102.       if( is_numeric($stream) ) {
  1103.         $stream = array( (int) $stream);
  1104.       }
  1105.  
  1106.       if( is_array($stream) ) {
  1107.         foreach($stream as $stream_id) {
  1108.           $this->stream_item->remove_all_items($stream_id);
  1109.           $this->stream->remove($stream);
  1110.         }
  1111.       }
  1112.       return true;
  1113.     }
  1114.  
  1115.     function get_all_streams()
  1116.     {
  1117.       return $this->stream->get('all');
  1118.  
  1119.     }
  1120.  
  1121.     /**
  1122.      * Check if a stream name has changed
  1123.      * based on it's stream id and the name
  1124.      *
  1125.      * @access public
  1126.      * @param int stream id
  1127.      * @param string stream name
  1128.      * @return bool true on the name has changed false on the not changed
  1129.      */
  1130.     function has_stream_name_changed($stream_id, $stream_name)
  1131.     {
  1132.       $stream = $this->stream->get($stream_id);
  1133.       if(is_array($stream) && sizeof($stream) == 1) {
  1134.         if($stream[0]->stream_name == $stream_name) {
  1135.           return false;
  1136.         }
  1137.       }
  1138.       // fall thru
  1139.       return true;
  1140.     }
  1141.  
  1142.     /**
  1143.      * Checks if the given stream name is not empty
  1144.      * and unique.
  1145.      *
  1146.      * @acccess public
  1147.      * @param string stream name
  1148.      * @result
  1149.      */
  1150.     function is_stream_name_ok($stream_name)
  1151.     {
  1152.       $result = 1;
  1153.       if( empty($stream_name) ) {
  1154.         $result = -1;
  1155.       } elseif ( ! $this->stream->is_unique_stream( $this->sanitize($stream_name) ) ) {
  1156.         $result = -2;
  1157.       }
  1158.       return $result;
  1159.     }
  1160.  
  1161.     /**
  1162.      * Removes the item from one or more streams and lowers the item count
  1163.      * on the streams of which the item was removed.
  1164.      *
  1165.      * @access public
  1166.      * @param int post_id
  1167.      * @param int blog_id
  1168.      *
  1169.      *
  1170.      */
  1171.     function remove_item($post_id, $blog_id)
  1172.     {
  1173.       // first get all stream ids where the item is being used
  1174.       $stream_ids = $this->get_item_streams($post_id, $blog_id);
  1175.  
  1176.       // if the stream ids array is bigger than zero the item is in use
  1177.       // with at least one stream and thus can be removed, otherwise the item
  1178.       // is not used by any streams and thus does not exist
  1179.       if( is_array($stream_ids) && sizeof($stream_ids) > 0 ){
  1180.         // then remove the item
  1181.         $removed = $this->stream_item->remove($post_id, $blog_id);
  1182.         // now lower the item count of the streams the item was part of
  1183.         foreach($stream_ids as $stream_id) {
  1184.           $this->stream->set_item_count($stream_id, '-');
  1185.         }
  1186.       }
  1187.     }
  1188.  
  1189.     /**
  1190.      * Adds an item to a stream
  1191.      *
  1192.      * @param int post_id
  1193.      * @param int blog_id
  1194.      * @param int stream_id
  1195.      * @return bool true on success, false on failure
  1196.      */
  1197.     function save_item($post_id, $blog_id, $stream_id, $user_id)
  1198.     {
  1199.       // save the item
  1200.       if( $this->stream_item->save($post_id, $blog_id, $stream_id, $user_id) !== FALSE) {
  1201.         // item saved successfully, add another count to the nr of items
  1202.         // of the stream in question
  1203.         if( $this->stream->set_item_count($stream_id, '+') !== FALSE) {
  1204.           return true;
  1205.         }
  1206.       }
  1207.       return false;
  1208.     }
  1209.  
  1210.     /**
  1211.      * Retrieve the stream ids from a specific item based on
  1212.      * item's post_id and blog_id
  1213.      *
  1214.      * @access public
  1215.      * @param int post_id
  1216.      * @param int blog_id
  1217.      * @return array with stream ids or empty array
  1218.      */
  1219.     function get_item_streams($post_id, $blog_id)
  1220.     {
  1221.       $stream_ids = array();
  1222.       $results = $this->stream_item->get($post_id, $blog_id);
  1223.       if( is_array($results) ) {
  1224.         foreach($results as $item) {
  1225.           if( is_object($item) ) {
  1226.             $stream_ids[] = $item->stream_id;
  1227.           }
  1228.         }
  1229.       }
  1230.       return $stream_ids;
  1231.     }
  1232.  
  1233.     /**
  1234.      * Checks if a given blog id is part
  1235.      * of the excluded list of blogs from a stream
  1236.      *
  1237.      * @param int $blog_id
  1238.      * @access protected
  1239.      * @return bool true on blog is excluded or false if blog is not excluded
  1240.      */
  1241.     function _is_blog_excluded($blog_id, $stream_id)
  1242.     {
  1243.       // get all the blogs we want to exclude
  1244.       $excluded_blogs = $this->stream->get_option($stream_id, 'bbagg_site_blogs');
  1245.  
  1246.       if( is_array($excluded_blogs) && sizeof($excluded_blogs) > 0 ) {
  1247.         if( in_array($blog_id, $excluded_blogs) ) {
  1248.           return true;
  1249.         }
  1250.       }
  1251.       // fall thru
  1252.       return false;
  1253.     }
  1254.  
  1255.     /**
  1256.      * Checks if a given blog_id is still valid
  1257.      *
  1258.      * @param int $blog_id
  1259.      * @access protected
  1260.      * @return bool true if the blog_id is still active otherwise false
  1261.      */
  1262.     function _is_blog_active($blog_id)
  1263.     {
  1264.       $q = $this->wpdb->prepare("SELECT blog_id FROM {$this->wpdb->blogs}
  1265.        WHERE site_id = %d
  1266.        AND blog_id = %d
  1267.        AND public = '1'
  1268.        AND archived = '0'
  1269.        AND mature = '0'
  1270.        AND spam = '0'
  1271.        AND deleted = '0'
  1272.        LIMIT 1", $this->wpdb->siteid, $blog_id
  1273.       );
  1274.  
  1275.       $result = $this->wpdb->get_var($q);
  1276.       if($result == $blog_id) {
  1277.         return true;
  1278.       }
  1279.       // fall thru
  1280.       return false;
  1281.     }
  1282.  
  1283.     /**
  1284.      * Retrieves the posts from all public and active blogs
  1285.      * in the category set to be aggregated using the options
  1286.      * set in the settings of the plugin.
  1287.      *
  1288.      * @access private
  1289.      * @param string stream name
  1290.      * @param bool true if post content needs to be processed else false, defaults true
  1291.      * @return array posts or -1 on error
  1292.      */
  1293.     function _get_posts($stream_name, $process_content = true)
  1294.     {
  1295.       // 1. get stream by name
  1296.       // 2. get all post ids per blog
  1297.       $stream_id = $this->stream->get_stream_id_by_name($stream_name);
  1298.       $aggregated_posts = array();
  1299.       if( is_numeric($stream_id) ) {
  1300.         $posts = $this->stream_item->get_all($stream_id);
  1301.         if( is_array($posts) ) {
  1302.           foreach($posts as $post) {
  1303.             if( is_object($post) ) {
  1304.               // check if the excluded blogs list
  1305.               if( ! $this->_is_blog_excluded($post->blog_id, $stream_id) && $this->_is_blog_active($post->blog_id) ) {
  1306.                 switch_to_blog($post->blog_id);
  1307.                 $blog_name = get_bloginfo('name');
  1308.                 $blog_url  = get_bloginfo('url');
  1309.                 $post_data = get_post($post->post_id);
  1310.                 if( is_object($post_data) ) {
  1311.                   $post_data->blog_name   = $blog_name;
  1312.                   $post_data->blog_url    = $blog_url;
  1313.                   $post_data->nr_comments = $this->_get_nr_comments($post->blog_id, $post->post_id);
  1314.                   $post_data->author_name = $this->_get_author_name($post_data->post_author);
  1315.                   if($process_content) {
  1316.                    $post_data->post_content = $this->process_post_content($post_data->post_content, $post->post_id, $post->blog_id);
  1317.                   }
  1318.                   $key = $post_data->post_date . ' ' . $post_data->post_title;
  1319.                   $aggregated_posts[ $key ] = $post_data;
  1320.                 }
  1321.                 restore_current_blog();
  1322.               }
  1323.             }
  1324.           }
  1325.         }
  1326.         return $this->_sort_posts($aggregated_posts);
  1327.       }
  1328.  
  1329.       // fall thru
  1330.       return -1;
  1331.     }
  1332.  
  1333.     /**
  1334.      * Sort posts. For now we use a reversed natural sort on the
  1335.      * array keys, so the last created post is first. Might change in
  1336.      * the future
  1337.      *
  1338.      * @access private
  1339.      * @param array post objects.
  1340.      */
  1341.     function _sort_posts( $posts = array() )
  1342.     {
  1343.       // sort the posts so the oldest post is last
  1344.       return $this->natkrsort($posts);
  1345.     }
  1346.  
  1347.     /**
  1348.      * Retrieve author name from wp_users based on post_author
  1349.      *
  1350.      * @access private
  1351.      * @param int user id
  1352.      * @return string user displayname or int -1 on error
  1353.      */
  1354.     function _get_author_name($author_id)
  1355.     {
  1356.       if( ! is_numeric($author_id) ) { return -1; }
  1357.  
  1358.       $q = sprintf("SELECT display_name FROM %s WHERE ID = '%s' LIMIT 1", $this->wpdb->users, $author_id);
  1359.       $post_author = $this->wpdb->get_var($q);
  1360.       return $post_author;
  1361.     }
  1362.  
  1363.     /**
  1364.      * Retrieve number of comments based in the blog and post id
  1365.      *
  1366.      * @access private
  1367.      * @param int blog id
  1368.      * @param int post id
  1369.      * @return int number of comments or -1 on error
  1370.      */
  1371.     function _get_nr_comments($blog_id, $post_id)
  1372.     {
  1373.       if( ! is_numeric($blog_id) || ! is_numeric($post_id) ) { return -1; }
  1374.       switch_to_blog($blog_id);
  1375.       $q = sprintf("SELECT COUNT(*) FROM %s WHERE comment_post_ID = '%s'", $this->wpdb->comments, $post_id);
  1376.       $nr_post_comments = $this->wpdb->get_var($q);
  1377.       restore_current_blog();
  1378.       return $nr_post_comments;
  1379.     }
  1380.  
  1381.     /**
  1382.      * Sort an array with case insensitive natural key sorting
  1383.      * thanks to http://nl3.php.net/manual/en/function.krsort.php#55577
  1384.      *
  1385.      * @access public
  1386.      * @param array
  1387.      * @return array sorted or empty array on error
  1388.      */
  1389.     function natkrsort($array)
  1390.     {
  1391.       // prevent errors,
  1392.       if( ! is_array($array) ) {
  1393.         return array();
  1394.       }
  1395.  
  1396.       $keys = array_keys($array);
  1397.       natcasesort($keys);
  1398.  
  1399.       $new_array = array();
  1400.       foreach ($keys as $k) {
  1401.         $new_array[$k] = $array[$k];
  1402.       }
  1403.  
  1404.       $new_array = array_reverse($new_array, true);
  1405.  
  1406.       return $new_array;
  1407.     }
  1408.  
  1409.     /**
  1410.      * Wrapper for WordPress sanitize_title_with_dashes function
  1411.      * Sanitizes a string replacing whitespace with dashes.
  1412.      * Limits the output to alphanumeric characters, underscore (_) and dash (-).
  1413.      * Whitespace becomes a dash.
  1414.      *
  1415.      * @access public
  1416.      * @param string
  1417.      * @return string
  1418.      */
  1419.     function sanitize($string)
  1420.     {
  1421.       return sanitize_title_with_dashes($string);
  1422.     }
  1423.  
  1424.     /**
  1425.      * this is a hack to make the post content and more tag behave.
  1426.      * The reason for having this function is to have more control over
  1427.      * the output of the more url and more text.
  1428.      * It is modeled after the IMHO not to so useful (due to the use of globals!) Wordpress
  1429.      * the_content function in wp-includes/post-template.php
  1430.      *
  1431.      * @access public
  1432.      * @param string content
  1433.      * @param numeric post id
  1434.      * @param numeric blog id
  1435.      * @param array optional
  1436.      * @return string transformed content
  1437.      */
  1438.     function process_post_content($content, $post_id, $blog_id, $args = '')
  1439.     {
  1440.       $defaults = array('more_link_text'     => __('Lees verder', $this->localization_domain),
  1441.                         'more_link_title'    =>  __('Lees verder', $this->localization_domain),
  1442.                         'class'              => 'more-link',
  1443.                         'override_permalink' => null,
  1444.                         'post_object'        => null);
  1445.  
  1446.       $r        = wp_parse_args( $args, $defaults );
  1447.       extract( $r, EXTR_SKIP ); // extract the parameters into variables
  1448.  
  1449.       // set initial values
  1450.       // make sure we at least get some output back
  1451.       // it will be overwritten if needed..
  1452.       $output   = $content;
  1453.  
  1454.       // switch to the blog needed, useful for permalinks and such
  1455.       switch_to_blog($blog_id);
  1456.  
  1457.       // get the more text if there is any..
  1458.       if ( preg_match('/<!--more(.*?)?-->/', $content, $matches) ) {
  1459.         $content = explode($matches[0], $content, 2);
  1460.         if ( ! empty($matches[1]) && ! empty($more_link_text) ) {
  1461.           $more_link_text = strip_tags(wp_kses_no_null(trim($matches[1]) ) );
  1462.         }
  1463.       } else {
  1464.         $content = array($content); // make sure we have an array
  1465.       }
  1466.  
  1467.       // unique id for css
  1468.       $css_id = "bid-$blog_id-pid-$post_id";
  1469.  
  1470.       if ( count($content) > 1 ) {
  1471.         $output  = $content[0]; // clear the output and only add the first part (content before the more tag)
  1472.         if ( $more ) {
  1473.           $output .= '<span id="more-' . $css_id . '"></span>' . $content[1];
  1474.         } else {
  1475.           if ( ! empty($more_link_text) ) {
  1476.             if( ! is_null($override_permalink) ) {
  1477.               $output .= apply_filters( 'the_content_more_link', ' <a href="' . $override_permalink . "#more-$css_id\" title=\"$more_link_title\" class=\"$class\">$more_link_text</a>", $more_link_text );
  1478.             } else {
  1479.               $output .= apply_filters( 'the_content_more_link', ' <a href="' . get_permalink($post_id) . "#more-$css_id\" title=\"$more_link_title\" class=\"$class\">$more_link_text</a>", $more_link_text );
  1480.             }
  1481.             $output = force_balance_tags($output);
  1482.           }
  1483.         }
  1484.       }
  1485.       // clean up and get ready to output the content, we even allow other plugins to mess with the content
  1486.       // by using the the_content filter
  1487.       $output = apply_filters('the_content', $output);
  1488.       $output = str_replace(']]>', ']]>', $output);
  1489.  
  1490.       // restore to the previous blog
  1491.       restore_current_blog();
  1492.       return $output;
  1493.     }
  1494.  
  1495.     /**
  1496.      * Returns error specific messages for the user
  1497.      * based on an error code
  1498.      *
  1499.      * @access private
  1500.      * @param int (negative) error code
  1501.      * @return string html message
  1502.      */
  1503.     function _retrieve_error($error_code)
  1504.     {
  1505.       switch ($error_code) {
  1506.         case -1:
  1507.           $result = '<div class="error"><p>' . __('You forgot to enter a stream name!', $this->localization_domain) . '</p></div>';
  1508.           break;
  1509.  
  1510.         case -2:
  1511.           $result = '<div class="error"><p>' . __('Sorry, the stream name you chose already exists. Please use a different name.', $this->localization_domain) . '</p></div>';
  1512.           break;
  1513.  
  1514.         case -3:
  1515.           //
  1516.           break;
  1517.  
  1518.         default:
  1519.           $result = __('Whoops something weird happened. Sorry about that', $this->localization_domain);  
  1520.  
  1521.       }
  1522.       return $result;
  1523.     }
  1524.  
  1525.     /**
  1526.      * uninstall removes the sitewide options and the database tables
  1527.      * used by this plugin
  1528.      *
  1529.      * @access public
  1530.      * @return void
  1531.      */
  1532.     function uninstall()
  1533.     {
  1534.       // remove sitewide options
  1535.       remove_site_options($this->options_name);  
  1536.  
  1537.       // remove the database tables
  1538.       if(is_array($this->db) && sizeof($this->db) > 0 ) {
  1539.         foreach($this->db as $table) {
  1540.           $this->wpdb->query( $this->wpdb->prepare("DROP TABLE IF EXISTS '%s'", $table) );
  1541.         }
  1542.       }
  1543.     }
  1544.  
  1545.   }
  1546. }
  1547.  
  1548. // instantiate the class
  1549. if ( class_exists('bbAggregate') ) {
  1550.   $bbagg_var = new bbAggregate();
  1551.  
  1552.   /**
  1553.    * Makes live easier for themers
  1554.    */
  1555.   if( ! function_exists ('bbagg_aggregate') ) {
  1556.     function bbagg_aggregate($stream_name, $start = 0, $end = null)
  1557.     {
  1558.       global $bbagg_var;
  1559.       return $bbagg_var->aggregate($stream_name, $start, $end);
  1560.     }
  1561.   }
  1562.  
  1563.   /**
  1564.    * Makes live easier for themers
  1565.    */
  1566.   if( ! function_exists ('bbagg_paginate') ) {
  1567.     function bbagg_paginate( $args = array() )
  1568.     {
  1569.       global $bbagg_var;
  1570.       return $bbagg_var->paginate($args);
  1571.     }
  1572.   }
  1573.  
  1574. }
  1575. ?>
  1576.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement