<?php
/*
Plugin Name: WP-o-Matic
Plugin URI: http://themeskult.com/wp-o-matic/
Description: Automated posts via RSS feed aggregation.
Version: 2.3.7
Author: Themes Kult
Author URI: http://themeskult.com/
*/

# WP-o-Matic paths. With trailing slash.
define('WPODIR', dirname(__FILE__) . '/');                
define('WPOINC', WPODIR . 'inc/');   
define('WPOTPL', WPOINC . 'admin/');
    
# Dependencies                            
require_once( WPOINC . 'tools.class.php' );               
            
class WPOMatic {               
             
  # Internal
  var $version = '2.3.7';   
                        
  var $newsetup = false;  # true if this version introduces setup changes
  
  var $sections = array('home', 'setup', 'list', 'add', 'edit', 'options', 'import', 'export',
                        'reset', 'delete', 'logs', 'testfeed', 'forcefetch');  
                        
  var $campaign_structure = array('main' => array(), 'rewrites' => array(), 
                                  'categories' => array(), 'feeds' => array());
  
  # __construct()
  function WPOMatic()
  {              
    global $wpdb, $wp_version;
                                     
    # Table names init
    $this->db = array(
      'campaign'            => $wpdb->prefix . 'wpo_campaign',
      'campaign_category'   => $wpdb->prefix . 'wpo_campaign_category',
      'campaign_feed'       => $wpdb->prefix . 'wpo_campaign_feed',     
      'campaign_word'       => $wpdb->prefix . 'wpo_campaign_word',   
      'campaign_post'       => $wpdb->prefix . 'wpo_campaign_post',
      'log'                 => $wpdb->prefix . 'wpo_log'
    );                                    
    
    # Are we running the new admin panel (2.5+) ?
    $this->newadmin = version_compare($wp_version, '2.5.0', '>=');
    
    # Is installed ?
    $this->installed = get_option('wpo_version');
    $this->setup = get_option('wpo_setup');
    
    # Actions
    add_action('activate_wp-o-matic/wpomatic.php', array(&$this, 'activate'));                # Plugin activated
    add_action('deactivate_wp-o-matic/wpomatic.php', array(&$this, 'deactivate'));            # Plugin deactivated
    add_action('init', array(&$this, 'init'));                                                # Wordpress init      
    add_action('admin_head', array(&$this, 'adminHead'));                                     # Admin head
    add_action('admin_footer', array(&$this, 'adminWarning'));                                # Admin footer
    add_action('admin_menu', array(&$this, 'adminMenu'));                                     # Admin menu creation            

   
    # Ajax actions
    add_action('wp_ajax_delete-campaign', array(&$this, 'adminDelete'));
    add_action('wp_ajax_test-feed', array(&$this, 'adminTestfeed'));
    
    # Filters
    add_action('the_permalink', array(&$this, 'filterPermalink'));
   
    # WP-o-Matic URIs. Without trailing slash               
    $this->optionsurl = get_option('siteurl') . '/wp-admin/options-general.php';                                           
    $this->adminurl = $this->optionsurl . '?page=wpomatic.php';
    $this->pluginpath = get_option('siteurl') . '/wp-content/plugins/wp-o-matic';           
    $this->helpurl = $this->pluginpath . '/help.php?item=';
    $this->tplpath = $this->pluginpath . '/inc/admin';
    $this->cachepath = WPODIR . get_option('wpo_cachepath');
    
    # Cron command / url
    $this->cron_url = $this->pluginpath . '/cron.php?code=' . get_option('wpo_croncode');
    $this->cron_command = attribute_escape('*/20 * * * * '. $this->getCommand() . ' ' . $this->cron_url);
  }
  
  /**
   * Called when plugin is activated 
   *
   *
   */ 
  function activate($force_install = false)
  {
    global $wpdb;
    
    if(file_exists(ABSPATH . '/wp-admin/upgrade-functions.php'))
      require_once(ABSPATH . '/wp-admin/upgrade-functions.php');
    else
      require_once(ABSPATH . '/wp-admin/includes/upgrade.php');
                                                  
    # Options   
    WPOTools::addMissingOptions(array(
     'wpo_log'          => array(1, 'Log WP-o-Matic actions'),
     'wpo_log_stdout'   => array(0, 'Output logs to browser while a campaign is being processed'),
     'wpo_unixcron'     => array(WPOTools::isUnix(), 'Use unix-style cron'),
     'wpo_croncode'     => array(substr(md5(time()), 0, 8), 'Cron job password.'),
     'wpo_cacheimages'  => array(0, 'Cache all images. Overrides campaign options'),
     'wpo_cachepath'    => array('cache', 'Cache path relative to wpomatic directory'),
		 'wpo_duplicates'    => array(0, 'Allows duplicate posts')	
    ));
    
    // only re-install if new version or uninstalled
    if($force_install || ! $this->installed || $this->installed != $this->version) 
    {			
			# wpo_campaign
			dbDelta( "CREATE TABLE {$this->db['campaign']} (
							    id int(11) unsigned NOT NULL auto_increment,
							    title varchar(255) NOT NULL default '', 
							    active tinyint(1) default '1', 
							    slug varchar(250) default '',         
							    template MEDIUMTEXT default '',         
  							  frequency int(5) default '180',
							    feeddate tinyint(1) default '0', 
							    cacheimages tinyint(1) default '1',
							    posttype enum('publish','draft','private') NOT NULL default 'publish',
							    authorid int(11) default NULL,                  
							    comment_status enum('open','closed','registered_only') NOT NULL default 'open',
							    allowpings tinyint(1) default '1',
							    dopingbacks tinyint(1) default '1',
							    max smallint(3) default '10',
							    linktosource tinyint(1) default '0',
							    count int(11) default '0',
							    lastactive datetime NOT NULL default '0000-00-00 00:00:00',	
							    created_on datetime NOT NULL default '0000-00-00 00:00:00',  							  
							    PRIMARY KEY (id)
						   );" ); 
		 
		 # wpo_campaign_category 			               
     dbDelta(  "CREATE TABLE {$this->db['campaign_category']} (
  						    id int(11) unsigned NOT NULL auto_increment,
  							  category_id int(11) NOT NULL,
  							  campaign_id int(11) NOT NULL,
  							  PRIMARY KEY  (id)
  						 );" );              
  	 
  	 # wpo_campaign_feed 				 
     dbDelta(  "CREATE TABLE {$this->db['campaign_feed']} (
  						    id int(11) unsigned NOT NULL auto_increment,
  							  campaign_id int(11) NOT NULL default '0',   
  							  url varchar(255) NOT NULL default '',  
  							  type varchar(255) NOT NULL default '',    
  							  title varchar(255) NOT NULL default '',   
  							  description varchar(255) NOT NULL default '',
  							  logo varchar(255) default '',                         
  							  count int(11) default '0',
  							  hash varchar(255) default '',
  							  lastactive datetime NOT NULL default '0000-00-00 00:00:00',							    
  							  PRIMARY KEY  (id)
  						 );" );  
  						 
    # wpo_campaign_post				 
    dbDelta(  "CREATE TABLE {$this->db['campaign_post']} (
    				    id int(11) unsigned NOT NULL auto_increment,
    					  campaign_id int(11) NOT NULL,
    					  feed_id int(11) NOT NULL,
    					  post_id int(11) NOT NULL,					
						    hash varchar(255) default '',	    
    					  PRIMARY KEY  (id)
    				 );" ); 
  						 
  	 # wpo_campaign_word 				 
     dbDelta(  "CREATE TABLE {$this->db['campaign_word']} (
  						    id int(11) unsigned NOT NULL auto_increment,
  							  campaign_id int(11) NOT NULL,
  							  word varchar(255) NOT NULL default '',
							    regex tinyint(1) default '0',
  							  rewrite tinyint(1) default '1',
  							  rewrite_to varchar(255) default '',
  							  relink varchar(255) default '',
  							  PRIMARY KEY  (id)
  						 );" );  						 
		                      
		 # wpo_log 			
     dbDelta(  "CREATE TABLE {$this->db['log']} (
  						    id int(11) unsigned NOT NULL auto_increment,
  							  message mediumtext NOT NULL default '',
  							  created_on datetime NOT NULL default '0000-00-00 00:00:00',
  							  PRIMARY KEY  (id)
  						 );" ); 			      
      
      
      add_option('wpo_version', $this->version, 'Installed version log');
      
   	  $this->installed = true;
    }
  }                                                                                      
  
  /**
   * Called when plugin is deactivated 
   *
   *
   */
  function deactivate()
  {    
  }       
  
  /**
   * Uninstalls
   *
   *
   */
  function uninstall()
  {   
    global $wpdb;
       
    foreach($this->db as $table) 
      $wpdb->query("DROP TABLE {$table} ");
    
    WPOTools::deleteOptions(array('wpo_log', 'wpo_log_stdout', 'wpo_unixcron', 'wpo_croncode', 'wpo_cacheimages', 'wpo_cachepath', 'wpo_duplicates'));
  }                                
  
  /**
   * Checks that WP-o-Matic tables exist
   *
   *
   */                                   
  function tablesExist()
  {
    global $wpdb;
    
    foreach($this->db as $table)
    {
      if(! $wpdb->query("SELECT * FROM {$table}"))
        return false;
    }
    
    return true;
  } 
   
   
  /**
   * Called when blog is initialized 
   *
   *
   */
  function init() 
  {
    global $wpdb;
    
    if($this->installed)
    {
      if(! get_option('wpo_unixcron'))
        $this->processAll();   

			if(isset($_REQUEST['page']) && $_REQUEST['page'] == 'wpomatic.php')
      {
        if(isset($_REQUEST['campaign_add']) || isset($_REQUEST['campaign_edit']))
          $this->adminCampaignRequest();

        $this->adminExportProcess();
        $this->adminInit();  
      }  
    }
  } 
    
  /** 
   * Saves a log message to database
   *
   *
   * @param string  $message  Message to save  
   */
  function log($message)
  {
    global $wpdb;
    
    if(get_option('wpo_log_stdout'))
      echo $message;    
    
    if(get_option('wpo_log'))
    {
      $message = $wpdb->escape($message);
      $time = current_time('mysql', true);
      $wpdb->query("INSERT INTO {$this->db['log']} (message, created_on) VALUES ('{$message}', '{$time}') "); 
    }
  }
    
  /**
   * Called by cron.php to update the site
   *
   *
   */     
  function runCron($log = true)
  {
    $this->log('Running cron job');   
    $this->processAll();
  }   
  
  /**
   * Finds a suitable command to run cron
   *
   * @return string command
   **/
  function getCommand()
  {
    $commands = array(
      @WPOTools::getBinaryPath('curl'),
      @WPOTools::getBinaryPath('wget'),
      @WPOTools::getBinaryPath('lynx', ' -dump'),
      @WPOTools::getBinaryPath('ftp')
    );
    
    return WPOTools::pick($commands[0], $commands[1], $commands[2], $commands[3], '<em>{wget or similar command here}</em>');
  }
  
  /**
   * Determines what the title has to link to
   *
   * @return string new text
   **/
  function filterPermalink($url)
  {
    // if from admin panel
    if($this->admin)
      return $url;
      
    if(get_the_ID())
    {
    	$campaignid = (int) get_post_meta(get_the_ID(), 'wpo_campaignid', true);

    	if($campaignid)
    	{
    	  $campaign = $this->getCampaignById($campaignid);
    	  if($campaign->linktosource)
    	    return get_post_meta(get_the_ID(), 'wpo_sourcepermalink', true);
    	}  	  

    	return $url;      
    }
  }
  
  /**
   * Processes all campaigns
   *
   */
  function processAll()
  {
    @set_time_limit(0);
    
    $campaigns = $this->getCampaigns('unparsed=1');

    foreach($campaigns as $campaign) 
    {
      $this->processCampaign($campaign);
    }
  }
  
  /**
   * Processes a campaign
   *
   * @param   object    $campaign   Campaign database object
   * @return  integer   Number of processed items
   */  
  function processCampaign(&$campaign)
  {
    global $wpdb;
    
    @set_time_limit(0);
    ob_implicit_flush();

    // Get campaign
    $campaign = is_numeric($campaign) ? $this->getCampaignById($campaign) : $campaign;

    // Log 
    $this->log('Processing campaign ' . $campaign->title . ' (ID: ' . $campaign->id . ')');
        
    // Get feeds
    $count = 0;
    $feeds = $this->getCampaignFeeds($campaign->id);    
    
    foreach($feeds as $feed)
      $count += $this->processFeed($campaign, $feed);

    $wpdb->query(WPOTools::updateQuery($this->db['campaign'], array(
      'count' => $campaign->count + $count,
      'lastactive' => current_time('mysql', true)
    ), "id = {$campaign->id}"));
    
    return $count;
  } 
  
  /**
   * Processes a feed
   *
   * @param   $campaign   object    Campaign database object   
   * @param   $feed       object    Feed database object
   * @return  The number of items added to database
   */
  function processFeed(&$campaign, &$feed)
  {
    global $wpdb;

    @set_time_limit(0);

    // Log
    $this->log('Processing feed ' . $feed->title . ' (ID: ' . $feed->id . ')');
    
    // Access the feed
    $simplepie = $this->fetchFeed($feed->url, false, $campaign->max);
    
    // Get posts (last is first)
    $items = array();
    $count = 0;
    
    foreach($simplepie->get_items() as $item)
    {
      if($feed->hash == $this->getItemHash($item))
      {
        if($count == 0) $this->log('No new posts');
        break;
      }    

			if (in_array($item, $items)) {
				break;
			}
      
      if($this->isDuplicate($campaign, $feed, $item))
      {
        $this->log('Filtering duplicate post');
        break;
      }
      
      $count++;
      array_unshift($items, $item);
      
      if($count == $campaign->max)
      {
        $this->log('Campaign fetch limit reached at ' . $campaign->max);
        break;
      }
    }
    
    // Processes post stack
    foreach($items as $item)
    {
      $this->processItem($campaign, $feed, $item);
      $lasthash = $this->getItemHash($item);
    }
    
    // If we have added items, let's update the hash
    if($count)
    {
      $wpdb->query(WPOTools::updateQuery($this->db['campaign_feed'], array(
        'count' => $count,
        'lastactive' => current_time('mysql', true),
        'hash' => $lasthash
      ), "id = {$feed->id}"));    
    
      $this->log( $count . ' posts added' );
    }
    
    return $count;
  }              
  
  /**
   * Processes an item
   *
   * @param   $item       object    SimplePie_Item object
   */
  function getItemHash($item)
  {
    return sha1($item->get_title() . $item->get_permalink());
  }  
   
  /**
   * Processes an item
   *
   * @param   $campaign   object    Campaign database object   
   * @param   $feed       object    Feed database object
   * @param   $item       object    SimplePie_Item object
   */
  function processItem(&$campaign, &$feed, &$item)
  {
    global $wpdb;
    
    $this->log('Processing item');
    
    // Item content
    $content = $this->parseItemContent($campaign, $feed, $item);
    
    // Item date
    // if($campaign->feeddate && ($item->get_date('U') > (current_time('timestamp', 1) - $campaign->frequency) && $item->get_date('U') < current_time('timestamp', 1)))
    if($item->get_date('U') < current_time('timestamp', 1))
      $date = $item->get_date('U');
    else
      $date = null;
      
    // Categories
    $categories = $this->getCampaignData($campaign->id, 'categories');
      
    // Meta
    $meta = array(
      'wpo_campaignid' => $campaign->id,
      'wpo_feedid' => $feed->id,
      'wpo_sourcepermalink' => $item->get_permalink()
    );  
    
    // Create post
    $postid = $this->insertPost($wpdb->escape($item->get_title()), $wpdb->escape($content), $date, $categories, $campaign->posttype, $campaign->authorid, $campaign->allowpings, $campaign->comment_status, $meta);
    
    // If pingback/trackbacks
    if($campaign->dopingbacks)
    {
      $this->log('Processing item pingbacks');
      
      require_once(ABSPATH . WPINC . '/comment.php');
    	pingback($content, $postid);      
    }      
    
    // Save post to log database
    $wpdb->query(WPOTools::insertQuery($this->db['campaign_post'], array(
      'campaign_id' => $campaign->id,
      'feed_id' => $feed->id,
      'post_id' => $postid,
      'hash' => $this->getItemHash($item)
    )));
  }
  
  /**
   * Checks datebase for a duplicate item
   *
   * @param   $campaign   object    Campaign database object   
   * @param   $feed       object    Feed database object
   * @param   $item       object    SimplePie_Item object
   */


  function isDuplicate(&$campaign, &$feed, &$item)
  {
    global $wpdb;
    $hash = $this->getItemHash($item);
		if (get_option('wpo_duplicates')) {
			$row = $wpdb->get_row("SELECT * FROM {$this->db['campaign_post']} WHERE campaign_id = {$campaign->id} AND feed_id = {$feed->id} AND hash = '$hash' ");
		} else {
			$row = $wpdb->get_row("SELECT * FROM {$this->db['campaign_post']} WHERE hash = '$hash' ");
		}
    return !! $row;
  }
  
  /**
   * Writes a post to blog
   *
   *  
   * @param   string    $title            Post title
   * @param   string    $content          Post content
   * @param   integer   $timestamp        Post timestamp
   * @param   array     $category         Array of categories
   * @param   string    $status           'draft', 'published' or 'private'
   * @param   integer   $authorid         ID of author.
   * @param   boolean   $allowpings       Allow pings
   * @param   boolean   $comment_status   'open', 'closed', 'registered_only'
   * @param   array     $meta             Meta key / values
   * @return  integer   Created post id
   */
  function insertPost($title, $content, $timestamp = null, $category = null, $status = 'draft', $authorid = null, $allowpings = true, $comment_status = 'open', $meta = array())
  {
    $date = ($timestamp) ? gmdate('Y-m-d H:i:s', $timestamp + (get_option('gmt_offset') * 3600)) : null;
    $postid = wp_insert_post(array(
    	'post_title' 	            => $title,
  		'post_content'  	        => $content,
  		'post_content_filtered'  	=> $content,
  		'post_category'           => $category,
  		'post_status' 	          => $status,
  		'post_author'             => $authorid,
  		'post_date'               => $date,
  		'comment_status'          => $comment_status,
  		'ping_status'             => $allowpings
    ));
    	
		foreach($meta as $key => $value) 
			$this->insertPostMeta($postid, $key, $value);			
		
		return $postid;
  }
  
  /**
   * insertPostMeta
   *
   *
   */
	function insertPostMeta($postid, $key, $value) {
		global $wpdb;
		
		$result = $wpdb->query( "INSERT INTO $wpdb->postmeta (post_id,meta_key,meta_value ) " 
					                . " VALUES ('$postid','$key','$value') ");
					
		return $wpdb->insert_id;		
	}
  
  /**
   * Parses an item content
   *
   * @param   $campaign       object    Campaign database object   
   * @param   $feed           object    Feed database object
   * @param   $item           object    SimplePie_Item object
   */
  function parseItemContent(&$campaign, &$feed, &$item)
  {  
    $content = $item->get_content();
    
    // Caching
    if(get_option('wpo_cacheimages') || $campaign->cacheimages)
    {
      $images = WPOTools::parseImages($content);
      $urls = $images[2];
      
      if(sizeof($urls))
      {
        $this->log('Caching images');
        
        foreach($urls as $url)
        {
          $newurl = $this->cacheRemoteImage($url);
          if($newurl)
            $content = str_replace($url, $newurl, $content);
        } 
      }
    }
    
    // Template parse
    $vars = array(
      '{content}',
      '{title}',
      '{permalink}',
      '{feedurl}',
      '{feedtitle}',
      '{feedlogo}',
      '{campaigntitle}',
      '{campaignid}',
      '{campaignslug}'
    );
    
    $replace = array(
      $content,
      $item->get_title(),
      $item->get_link(),
      $feed->url,
      $feed->title,
      $feed->logo,
      $campaign->title,
      $campaign->id,
      $campaign->slug
    );
    
    $content = str_ireplace($vars, $replace, ($campaign->template) ? $campaign->template : '{content}');
    
    // Rewrite
    $rewrites = $this->getCampaignData($campaign->id, 'rewrites');
    foreach($rewrites as $rewrite)
    {
      $origin = $rewrite['origin']['search'];
      
      if(isset($rewrite['rewrite']))
      {
        $reword = isset($rewrite['relink']) 
                    ? '<a href="'. $rewrite['relink'] .'">' . $rewrite['rewrite'] . '</a>' 
                    : $rewrite['rewrite'];
        
        if($rewrite['origin']['regex'])
        {
          $content = preg_replace($origin, $reword, $content);
        } else
          $content = str_ireplace($origin, $reword, $content);
      } else if(isset($rewrite['relink'])) 
        $content = str_ireplace($origin, '<a href="'. $rewrite['relink'] .'">' . $origin . '</a>', $content);
    }
    
    return $content;
  }
  
  /**
   * Cache remote image
   *
   * @return string New url
   */
  function cacheRemoteImage($url)
  { 

    $contents = @file_get_contents($url);

    $info = pathinfo($url);
    $ext = explode('?', $info['extension']);
    if ($ext[0]) {
      $ext = $ext[0];
    }else{
      $ext = $info['extension'];
    }
    $file_name =  preg_replace("/[^A-Za-z0-9-]/", '-',basename($url,'.'.$info['extension'])).'.'.$ext;

    $filename = substr(md5(time()), 0, 10) . '_' .  $file_name;
    
    $cachepath = $this->cachepath;
    
    if(is_writable($cachepath) && $contents && getimagesize($url))
    { 
      file_put_contents($cachepath . '/' . $filename, $contents);
      return $this->pluginpath . '/' . get_option('wpo_cachepath') . '/' . $filename;
    }
    
    return false;
  }
   
  /**
   * Parses a feed with SimplePie
   *
   * @param   boolean     $stupidly_fast    Set fast mode. Best for checks
   * @param   integer     $max              Limit of items to fetch
   * @return  SimplePie_Item    Feed object
   **/
  function fetchFeed($url, $stupidly_fast = false, $max = 0)
  {
    # SimplePie
    if(! class_exists('SimplePie'))
      require_once( WPOINC . 'simplepie/simplepie.class.php' );
    
    $feed = new SimplePie();
    $feed->enable_order_by_date(false); // thanks Julian Popov
    $feed->set_feed_url($url);
    $feed->set_item_limit($max);
    $feed->set_stupidly_fast($stupidly_fast);
    $feed->enable_cache(false);    
    $feed->init();
    $feed->handle_content_type(); 
    
    return $feed;
  }
  
  /**
   * Returns all blog usernames (in form [user_login => display_name (user_login)] )
   *
   * @return array $usernames
   **/
  function getBlogUsernames()
  {
    $return = array();
    $users = get_users_of_blog();
    
    foreach($users as $user)
    {
      if($user->display_name == $user->user_login)
        $return[$user->user_login] = "{$user->display_name}";      
      else
        $return[$user->user_login] = "{$user->display_name} ({$user->user_login})";
    }
    
    return $return;
  }
  
  /**
   * Returns all data for a campaign
   *
   *
   */
  function getCampaignData($id, $section = null)
  {
    global $wpdb;
    $campaign = (array) $this->getCampaignById($id);
    
    if($campaign)
    {
      $campaign_data = $this->campaign_structure;
      
      // Main
      if(!$section || $section == 'main')
      {
        $campaign_data['main'] = array_merge($campaign_data['main'], $campaign);
        $userdata = get_userdata($campaign_data['main']['authorid']);
        $campaign_data['main']['author'] = $userdata->user_login; 
      }
      
      // Categories
      if(!$section || $section == 'categories')
      {
        $categories = $wpdb->get_results("SELECT * FROM {$this->db['campaign_category']} WHERE campaign_id = $id");
        foreach($categories as $category)
          $campaign_data['categories'][] = $category->category_id;
      }
      
      // Feeds
      if(!$section || $section == 'feeds')
      {
        $campaign_data['feeds']['edit'] = array();
        
        $feeds = $this->getCampaignFeeds($id);
        foreach($feeds as $feed)
          $campaign_data['feeds']['edit'][$feed->id] = $feed->url;
      }
      
      // Rewrites      
      if(!$section || $section == 'rewrites')
      {
        $rewrites = $wpdb->get_results("SELECT * FROM {$this->db['campaign_word']} WHERE campaign_id = $id");
        foreach($rewrites as $rewrite)
        {
          $word = array('origin' => array('search' => $rewrite->word, 'regex' => $rewrite->regex), 'rewrite' => $rewrite->rewrite_to, 'relink' => $rewrite->relink);
        
          if(! $rewrite->rewrite) unset($word['rewrite']);
          if(empty($rewrite->relink)) unset($word['relink']);
        
          $campaign_data['rewrites'][] = $word;
        }
      }

      if($section)
        return $campaign_data[$section];
        
      return $campaign_data; 
    }
    
    return false;
  }
  
  /**
   * Retrieves logs from database
   *
   *
   */       
  function getLogs($args = '') 
  {   
    global $wpdb;
    extract(WPOTools::getQueryArgs($args, array('orderby' => 'created_on', 
                                                'ordertype' => 'DESC', 
                                                'limit' => null,
                                                'page' => null,
                                                'perpage' => null))); 
    if(!is_null($page))
    {
      if($page == 0) $page = 1;
      $page--;
      
      $start = $page * $perpage;
      $end = $start + $perpage;
      $limit = "LIMIT {$start}, {$end}";
    }
  	
  	return $wpdb->get_results("SELECT * FROM {$this->db['log']} ORDER BY $orderby $ordertype $limit");
  }
           
  /**
   * Retrieves a campaign by its id
   *
   *
   */  
  function getCampaignById($id)
  {
    global $wpdb;
    
    $id = intval($id);
    return $wpdb->get_row("SELECT * FROM {$this->db['campaign']} WHERE id = $id");
  }
  
  /**
   * Retrieves a feed by its id
   *
   *
   */  
  function getFeedById($id)
  {
    global $wpdb;
    
    $id = intval($id);
    return $wpdb->get_row("SELECT * FROM {$this->db['campaign_feed']} WHERE id = $id");
  }
         
  /**
   * Retrieves campaigns from database
   *
   *
   */       
  function getCampaigns($args = '') 
  {   
    global $wpdb;
    extract(WPOTools::getQueryArgs($args, array('fields' => '*',      
                                                'search' => '',
                                                'orderby' => 'created_on', 
                                                'ordertype' => 'DESC', 
                                                'where' => '',
                                                'unparsed' => false,
                                                'limit' => null)));
  
  	if(! empty($search))
  	  $where .= " AND title LIKE '%{$search}%' ";
  	  
  	if($unparsed)
  	  $where .= " AND active = 1 AND (frequency + UNIX_TIMESTAMP(lastactive)) < ". (current_time('timestamp', true) - get_option('gmt_offset') * 3600) . " ";
  	                              
  	$sql = "SELECT $fields FROM {$this->db['campaign']} WHERE 1 = 1 $where "
         . "ORDER BY $orderby $ordertype $limit";
         
  	return $wpdb->get_results($sql);
  }            
  
  /**
   * Retrieves feeds for a certain campaign
   *
   * @param   integer   $id     Campaign id
   */
  function getCampaignFeeds($id)
  {    
    global $wpdb;
    return $wpdb->get_results("SELECT * FROM {$this->db['campaign_feed']} WHERE campaign_id = $id");
  }
  
  /**
   * Retrieves all WP posts for a certain campaign
   *
   * @param   integer   $id     Campaign id
   */
  function getCampaignPosts($id)
  {
    global $wpdb;
    return $wpdb->get_results("SELECT post_id FROM {$this->db['campaign_post']} WHERE campaign_id = $id ");          
  }
  
  /**
   * Adds a feed by url and campaign id
   *
   *
   */
  function addCampaignFeed($id, $feed)
  {
    global $wpdb;
    
    $simplepie = $this->fetchFeed($feed, true);
    $url = $wpdb->escape($simplepie->subscribe_url());
    
    // If it already exists, ignore it
    if(! $wpdb->get_var("SELECT id FROM {$this->db['campaign_feed']} WHERE campaign_id = $id AND url = '$url' "))
    {
      $wpdb->query(WPOTools::insertQuery($this->db['campaign_feed'], 
        array('url' => $url, 
              'title' => $wpdb->escape($simplepie->get_title()),
              'description' => $wpdb->escape($simplepie->get_description()),
              'logo' => $wpdb->escape($simplepie->get_image_url()),
              'campaign_id' => $id)
      ));  
      
      return $wpdb->insert_id;
    }
    
    return false;
  }
  
  
  /**
   * Retrieves feeds from database
   *         
   * @param   mixed   $args
   */  
  function getFeeds($args = '') 
  {
    global $wpdb;
    extract(WPOTools::getQueryArgs($args, array('fields' => '*',  
                                                'campid' => '',    
                                                'join' => false,
                                                'orderby' => 'created_on', 
                                                'ordertype' => 'DESC', 
                                                'limit' => null)));
  	
  	$sql = "SELECT $fields FROM {$this->db['campaign_feed']} cf ";
  	
  	if(!empty($join))
  	  $sql .= "INNER JOIN {$this->db['campaign']} camp ON camp.id = cf.campaign_id ";
  	     
  	if(!empty($campid))
  	  $sql .= "WHERE cf.campaign_id = $campid";
  	
  	return $wpdb->get_results($sql);  
  }        
  
  /**
   * Returns how many seconds left till reprocessing
   *
   * @return seconds
   **/
  function getCampaignRemaining(&$campaign, $gmt = 0)
  {    
    return mysql2date('U', $campaign->lastactive) + $campaign->frequency - current_time('timestamp', true) + ($gmt ? 0 : (get_option('gmt_offset') * 3600));
  }
  
  /**
   * Called when WP-o-Matic admin pages initialize.
   * 
   * 
   */
  function adminInit() 
  {                 
    auth_redirect();
    
    // force display of a certain section    
    $this->section = ($this->setup) ? ((isset($_REQUEST['s']) && $_REQUEST['s']) ? $_REQUEST['s'] : $this->sections[0]) : 'setup';
    
    if (isset($_SERVER['HTTP_USER_AGENT']) && (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false))
      die('Please switch to Firefox / Safari');
    
    wp_enqueue_script('prototype');
    wp_enqueue_script('wpoadmin', $this->tplpath . '/admin.js', array('prototype'), $this->version);
    
    if($this->section == 'list')
      wp_enqueue_script('listman');
          
    if(WPOTools::isAjax())
    {              
      $this->admin();
      exit;
    }      
  }
  
  /**
   * Called by admin-header.php
   *
   * @return void
   **/
  function adminHead()
  {
    wp_enqueue_style('admin-css', $this->tplpath . '/css/admin.css');
    wp_enqueue_style('admin-css-new', $this->tplpath . '/css/admin-new.css');
    $this->admin = true;

  }
  
  /**
   * Shows a warning box for setup
   *
   *
   */
  function adminWarning()
  {
    if(! $this->setup && $this->section != 'setup')
    {
      echo "<div id='wpo-warning' class='updated fade-ff0000'><p>" . sprintf(__('Please <a href="%s">click here</a> to setup and configure WP-o-Matic.', 'wpomatic'), $this->adminurl . '&amp;s=setup') . "</p></div>

  		  <style type='text/css'>
  		    " . ($this->newadmin
  		    ?
  		    "
  		    #wpo-warning {top: 4em; right: 0 }
  		    "
  		    :
  		    "
  		    #adminmenu { margin-bottom: 5em; }
  		    #wpo-warning {top: 6.8em; }
  		    "
  		    )
  		    .
  		    "
  		  </style>
  	  "; 
    }
  }
  
  /**
   * Executes the current section method.
   * 
   *
   */
  function admin()
  {                       
    if(in_array($this->section, $this->sections))
    {
      $method = 'admin' . ucfirst($this->section);
      $this->$method();        
    }  
  }
    
  /**
   * Adds the WP-o-Matic item to menu 
   *           
   * 
   */
  function adminMenu()
  {
    add_submenu_page('options-general.php', 'WP-o-Matic', 'WP-o-Matic', 10, basename(__FILE__), array(&$this, 'admin'));
  }  


  /**
   * Outputs the admin header in a template 
   *            
   * 
   */
  function adminHeader()
  {              
    $current = array();
                    
    foreach($this->sections as $s)
      $current[$s] = ($s == $this->section) ? 'class="nav-tab nav-tab-active "' : 'class="nav-tab"';
    
    include(WPOTPL . 'header.php');
  }                                                                  
                                
  /**
   * Outputs the admin footer in a template
   *            
   * 
   */
  function adminFooter()
  {
    include(WPOTPL . 'footer.php');
  }
    
  /**
   * Home section
   *
   *
   */
  function adminHome()
  {                                   
    $logging = get_option('wpo_log');
    $logs = $this->getLogs('limit=7');    
    $nextcampaigns = $this->getCampaigns('fields=id,title,lastactive,frequency&limit=5' .
                                          '&where=active=1&orderby=UNIX_TIMESTAMP(lastactive)%2Bfrequency&ordertype=ASC');
    $lastcampaigns = $this->getCampaigns('fields=id,title,lastactive,frequency&limit=5&where=UNIX_TIMESTAMP(lastactive)>0&orderby=lastactive');
    $campaigns = $this->getCampaigns('fields=id,title,count&limit=5&orderby=count');
    


    

    include(WPOTPL . 'home.php');
  }      
    
  
  /** 
   * Setup admin
   *
   *
   */
  function adminSetup()
  {
    if(isset($_POST['dosetup']))
    {
      update_option('wpo_unixcron', isset($_REQUEST['option_unixcron']));
      update_option('wpo_setup', 1);
      
      $this->adminHome();
      exit;
    }
    
    # Commands
    $prefix = $this->getCommand();
    $nocommand = ! file_exists($prefix);
                
    $safe_mode = ini_get('safe_mode');
        
    $command = $this->cron_command;
    $url = $this->cron_url;
        
    include(WPOTPL . 'setup.php');
  }     

  /**
   * List campaigns section
   *
   *
   */
  function adminList()
  {                                                                                
    global $wpdb;
    
    if(isset($_REQUEST['q']))
    {
      $q = $_REQUEST['q'];
      $campaigns = $this->getCampaigns('search=' . $q);
    } else
      $campaigns = $this->getCampaigns('orderby=CREATED_ON');
  
    include(WPOTPL . 'list.php');
  }
            
  /**
   * Add campaign section
   *
   *
   */
  function adminAdd()
  {                
    $data = $this->campaign_structure;
  
    if(isset($_REQUEST['campaign_add']))
    {
      check_admin_referer('wpomatic-edit-campaign');
      
      if($this->errno)
        $data = $this->campaign_data;
      else
        $addedid = $this->adminProcessAdd();   
    }
    
    $author_usernames = $this->getBlogUsernames();   
    $campaign_add = true;
    include(WPOTPL . 'edit.php');
  }     
  
  /**
   * Edit campaign section
   *
   *
   */
  function adminEdit()
  {    
    $id = intval($_REQUEST['id']);
    if(!$id) die("Can't be called directly");
    
    if(isset($_REQUEST['campaign_edit']))
    {
      check_admin_referer('wpomatic-edit-campaign');
            
      $data = $this->campaign_data;
      $submitted = true;
      
      if(! $this->errno) 
      {
        $this->adminProcessEdit($id);
        $edited = true;
        $data = $this->getCampaignData($id);
      }
    } else      
      $data = $this->getCampaignData($id);    
    
    $author_usernames = $this->getBlogUsernames();
    $campaign_edit = true;
    
    include(WPOTPL . 'edit.php');
  }
  
  function adminEditCategories(&$data, $parent = 0, $level = 0, $categories = 0)
  {    
  	if ( !$categories )
  		$categories = get_categories(array('hide_empty' => 0));

    if(function_exists('_get_category_hierarchy'))
      $children = _get_category_hierarchy();
    elseif(function_exists('_get_term_hierarchy'))
      $children = _get_term_hierarchy('category');
    else
      $children = array();

  	if ( $categories ) {
  		ob_start();
  		foreach ( $categories as $category ) {
  			if ( $category->parent == $parent) {
  				echo "\t" . _wpo_edit_cat_row($category, $level, $data);
  				if ( isset($children[$category->term_id]) )
  					$this->adminEditCategories($data, $category->term_id, $level + 1, $categories );
  			}
  		}
  		$output = ob_get_contents();
  		ob_end_clean();

  		echo $output;
  	} else {
  		return false;
  	}
    	
  }
  
  /**
   * Resets a campaign (sets post count to 0, forgets last parsed post)
   *
   *
   * @todo Make it ajax-compatible here and add javascript code
   */
  function adminReset()
  {
    global $wpdb;
    
    $id = intval($_REQUEST['id']);

    if(! defined('DOING_AJAX'))
      check_admin_referer('reset-campaign_'.$id);    
      
    // Reset count and lasactive
    $wpdb->query(WPOTools::updateQuery($this->db['campaign'], array(
      'count' => 0,
      'lastactive' => 0
    ), "id = $id"));
      
    // Reset feeds hashes, count, and lasactive
    foreach($this->getCampaignFeeds($id) as $feed)
    {
      $wpdb->query(WPOTools::updateQuery($this->db['campaign_feed'], array(
        'count' => 0,
        'lastactive' => 0,
        'hash' => ''
      ), "id = {$feed->id}"));
    }
      
    if(defined('DOING_AJAX'))
      die('1');
    else
      $this->adminList();
  }
  
  /**
   * Deletes a campaign
   *
   *
   */
  function adminDelete()
  {
    global $wpdb;
    
    $id = intval($_REQUEST['id']);

    // If not called through admin-ajax.php
    if(! defined('DOING_AJAX'))
      check_admin_referer('delete-campaign_'.$id);    
      
    $wpdb->query("DELETE FROM {$this->db['campaign']} WHERE id = $id");
    $wpdb->query("DELETE FROM {$this->db['campaign_feed']} WHERE campaign_id = $id");
    $wpdb->query("DELETE FROM {$this->db['campaign_word']} WHERE campaign_id = $id");
    $wpdb->query("DELETE FROM {$this->db['campaign_category']} WHERE campaign_id = $id");
    
    if(defined('DOING_AJAX'))
      die('1');
    else
      $this->adminList();
  }
  
  /**
   * Options section 
   *
   *
   */
  function adminOptions()
  {  
              
    if(isset($_REQUEST['update']))
    {              
      update_option('wpo_unixcron',     isset($_REQUEST['option_unixcron']));
      update_option('wpo_log',          isset($_REQUEST['option_logging']));
      update_option('wpo_log_stdout',   isset($_REQUEST['option_logging_stdout']));      
      update_option('wpo_cacheimages',  isset($_REQUEST['option_caching']));
      update_option('wpo_cachepath',    rtrim($_REQUEST['option_cachepath'], '/'));
      update_option('wpo_duplicates',   isset($_REQUEST['option_duplicates']));		
      
      $updated = 1;
    }
    
    if(!is_writable($this->cachepath))
      $not_writable = true;
    
    include(WPOTPL . 'options.php');
  }
   
  /**
   * Import section
   *
   *
   */
  function adminImport()
  {  
    global $wpdb;
    
    @session_start();
    
    if(!$_POST) unset($_SESSION['opmlimport']);
    
    if(isset($_FILES['importfile']) || $_POST)
      check_admin_referer('import-campaign');   
           
    if(!isset($_SESSION['opmlimport']))
    {
      if(isset($_FILES['importfile']))      
      {
        if(is_uploaded_file($_FILES['importfile']['tmp_name']))
          $file = $_FILES['importfile']['tmp_name'];
        else
          $file = false;
      } else if(isset($_REQUEST['importurl']))
      {
        $fromurl = true;
        $file = $_REQUEST['importurl'];
      }  
    }
    
    if(isset($file) || ($_POST && isset($_SESSION['opmlimport'])) )
    {               
      require_once( WPOINC . 'xmlparser.class.php' );
    
      $contents = (isset($file) ? @file_get_contents($file) : $_SESSION['opmlimport']);
      $_SESSION['opmlimport'] = $contents;    
    
      # Get OPML data
      $opml = new XMLParser($contents);
      $opml = $opml->getTree();                                             
              
      # Check that it is indeed opml      
      if(is_array($opml) && isset($opml['OPML'])) 
      {                            
        $opml = $opml['OPML'][0];
        
        $title = isset($opml['HEAD'][0]['TITLE'][0]['VALUE']) 
                  ? $opml['HEAD'][0]['TITLE'][0]['VALUE'] 
                  : null;          
                  
        $opml = $opml['BODY'][0];
                   
        $success = 1;
        
        # Campaigns dropdown
        $campaigns = array();
        foreach($this->getCampaigns() as $campaign)
          $campaigns[$campaign->id] = $campaign->title;
      }          
      else 
        $import_error = 1;
    }    
    
    $this->adminImportProcess();
      
    include(WPOTPL . 'import.php');
  }      
  
  /**
   * Import process
   *
   *
   */
  function adminImportProcess()
  {
    global $wpdb;
    
    if(isset($_REQUEST['add']))
    {
      if(!isset($_REQUEST['feed']))
        $add_error = __('You must select at least one feed', 'wpomatic');
      else 
      {
        switch($_REQUEST['import_mode'])
        {
          // Several campaigns
          case '1':
            $created_campaigns = array();

            foreach($_REQUEST['feed'] as $campaignid => $feeds)
            {
              if(!in_array($campaignid, $created_campaigns))
              {
                // Create campaign
                $title = $_REQUEST['campaign'][$campaignid];
                if(!$title) continue;
                
                $slug = WPOTools::stripText($title);
                $wpdb->query("INSERT INTO {$this->db['campaign']} (title, active, slug, lastactive, count) VALUES ('$title', 0, '$slug', 0, 0) ");
                $created_campaigns[] = $wpdb->insert_id;  
              
                // Add feeds
                foreach($feeds as $feedurl => $yes)
                  $this->addCampaignFeed($campaignid, urldecode($feedurl));
                  
              }            
            }
            
            $this->add_success = __('Campaigns added successfully. Feel free to edit them', 'wpomatic');
            
            break;
          
          // All feeds into an existing campaign
          case '2':
            $campaignid = $_REQUEST['import_custom_campaign'];
            
            foreach($_REQUEST['feed'] as $cid => $feeds)
            {
              // Add feeds              
              foreach($feeds as $feedurl => $yes)
                $this->addCampaignFeed($campaignid, urldecode($feedurl));
            }  
            
            $this->add_success = sprintf(__('Feeds added successfully. <a href="%s">Edit campaign</a>', 'wpomatic'), $this->adminurl . '&s=edit&id=' . $campaignid);
            
            break;
            
          // All feeds into new campaign
          case '3':
            $title = $_REQUEST['import_new_campaign'];
            $slug = WPOTools::stripText($title);
            $wpdb->query("INSERT INTO {$this->db['campaign']} (title, active, slug, lastactive, count) VALUES ('$title', 0, '$slug', 0, 0) ");
            $campaignid = $wpdb->insert_id;
            
            // Add feeds
            foreach($_REQUEST['feed'] as $cid => $feeds)
            {
              // Add feeds              
              foreach($feeds as $feedurl => $yes)
                $this->addCampaignFeed($campaignid, urldecode($feedurl));
            }
            
            $this->add_success = sprintf(__('Feeds added successfully. <a href="%s">Edit campaign</a>', 'wpomatic'), $this->adminurl . '&s=edit&id=' . $campaignid);
            
            break;
        }
      }
    }
  }
  
  /**
   * Export
   *
   *
   */
  function adminExport()
  {    
    if(isset($this->export_error))
      $error = $this->export_error;
    
    $campaigns = $this->getCampaigns();
    
    include(WPOTPL . 'export.php');    
  }
  
  /** 
   * Export process
   *
   *
   */
  function adminExportProcess()
  {
    if($_POST)
    {
      if(!isset($_REQUEST['export_campaign']))
      {
        $this->export_error = __('Please select at least one campaign', 'wpomatic');
      } else 
      {
        $campaigns = array();
        foreach($_REQUEST['export_campaign'] as $cid)
        {
          $campaign = $this->getCampaignById($cid);
          $campaign->feeds = (array) $this->getCampaignFeeds($cid);
          $campaigns[] = $campaign;
        }
        
        header("Content-type: text/x-opml");
        header('Content-Disposition: attachment; filename="wpomatic.opml"');
        
        include(WPOTPL . 'export.opml.php');
        exit;
      }
    }
  }
  
  /**
   * Tests a feed
   *
   *
   */
  function adminTestfeed()
  {
    if(!isset($_REQUEST['url'])) return false;
    
    $url = $_REQUEST['url'];
    $feed = $this->fetchFeed($url, true);
    $works = ! $feed->error(); // if no error returned
    
    if(defined('DOING_AJAX')){
      echo intval($works);
      die();
    } else
      include(WPOTPL . 'testfeed.php');
  }
  
  /**
   * Forcedfully processes a campaign
   *
   *
   */
  function adminForcefetch()
  {
    $cid = intval($_REQUEST['id']);
    
    if(! defined('DOING_AJAX'))
      check_admin_referer('forcefetch-campaign_'.$cid);    
    
    $this->forcefetched = $this->processCampaign($cid);    
    
    if(defined('DOING_AJAX'))
      die('1');
    else
      $this->adminList();
  }
  
  /**
   * Checks submitted campaign edit form for errors
   * 
   *
   * @return array  errors 
   */
  function adminCampaignRequest()
  {  
    global $wpdb;
    
    # Main data
    $this->campaign_data = $this->campaign_structure;
    $this->campaign_data['main'] = array(      
        'title'         => $_REQUEST['campaign_title'],
        'active'        => isset($_REQUEST['campaign_active']),
        'slug'          => $_REQUEST['campaign_slug'],
        'template'      => (isset($_REQUEST['campaign_templatechk'])) 
                            ? $_REQUEST['campaign_template'] : null,
        'frequency'     => intval($_REQUEST['campaign_frequency_d']) * 86400 
                          + intval($_REQUEST['campaign_frequency_h']) * 3600 
                          + intval($_REQUEST['campaign_frequency_m']) * 60,
        'cacheimages'   => (int) isset($_REQUEST['campaign_cacheimages']),
        'feeddate'      => (int) isset($_REQUEST['campaign_feeddate']),
        'posttype'      => $_REQUEST['campaign_posttype'],
        'author'        => sanitize_user($_REQUEST['campaign_author']),
        'comment_status' => $_REQUEST['campaign_commentstatus'],
        'allowpings'    => (int) isset($_REQUEST['campaign_allowpings']),
        'dopingbacks'   => (int) isset($_REQUEST['campaign_dopingbacks']),
        'max'           => intval($_REQUEST['campaign_max']),
        'linktosource'  => (int) isset($_REQUEST['campaign_linktosource'])
    );
    
    // New feeds     
    foreach($_REQUEST['campaign_feed']['new'] as $i => $feed) 
    {
      $feed = trim($feed);
      
      if(!empty($feed))
      {        
        if(!isset($this->campaign_data['feeds']['new']))
          $this->campaign_data['feeds']['new'] = array();
          
        $this->campaign_data['feeds']['new'][$i] = $feed;
      }
    } 
    
    // Existing feeds to delete
    if(isset($_REQUEST['campaign_feed']['delete']))
    {
      $this->campaign_data['feeds']['delete'] = array();
      
      foreach($_REQUEST['campaign_feed']['delete'] as $feedid => $yes)
        $this->campaign_data['feeds']['delete'][] = intval($feedid);
    }
    
    // Existing feeds.
    if(isset($_REQUEST['id']))
    {
      $this->campaign_data['feeds']['edit'] = array();
      foreach($this->getCampaignFeeds(intval($_REQUEST['id'])) as $feed)
        $this->campaign_data['feeds']['edit'][$feed->id] = $feed->url;
    }
    
    // Categories
    if(isset($_REQUEST['campaign_categories']))
    {
      foreach($_REQUEST['campaign_categories'] as $category)
      {
        $id = intval($category);
        $this->campaign_data['categories'][] = $category;
      }
    }
    
    # New categories
    if(isset($_REQUEST['campaign_newcat']))
    {
      foreach($_REQUEST['campaign_newcat'] as $k => $on)
      {
        $catname = $_REQUEST['campaign_newcatname'][$k];
        if(!empty($catname))
        {
          if(!isset($this->campaign_data['categories']['new']))
            $this->campaign_data['categories']['new'] = array();
          
          $this->campaign_data['categories']['new'][] = $catname;
        }
      }
    }
    
    // Rewrites
    if(isset($_REQUEST['campaign_word_origin']))
    {
      foreach($_REQUEST['campaign_word_origin'] as $id => $origin_data)
      {
        $rewrite = isset($_REQUEST['campaign_word_option_rewrite']) 
                && isset($_REQUEST['campaign_word_option_rewrite'][$id]); 
        $relink = isset($_REQUEST['campaign_word_option_relink']) 
                && isset($_REQUEST['campaign_word_option_relink'][$id]);  

        if($rewrite || $relink)
        {
          $rewrite_data = trim($_REQUEST['campaign_word_rewrite'][$id]);
          $relink_data = trim($_REQUEST['campaign_word_relink'][$id]);
        
          // Relink data field can't be empty
          if(($relink && !empty($relink_data)) || !$relink) 
          {
            $regex = isset($_REQUEST['campaign_word_option_regex']) 
                  && isset($_REQUEST['campaign_word_option_regex'][$id]);

            $data = array();        
            $data['origin'] = array('search' => $origin_data, 'regex' => $regex);
            
            if($rewrite)
              $data['rewrite'] = $rewrite_data;
              
            if($relink)
              $data['relink'] = $relink_data;
              
            $this->campaign_data['rewrites'][] = $data; 
          }  
        }
      }
    }
    
    $errors = array('basic' => array(), 'feeds' => array(), 'categories' => array(), 
                    'rewrite' => array(), 'options' => array());
    
    # Main    
    if(empty($this->campaign_data['main']['title']))
    {
      $errors['basic'][] = __('You have to enter a campaign title', 'wpomatic');
      $this->errno++;
    }
    
    # Feeds
    $feedscount = 0;
    
    if(isset($this->campaign_data['feeds']['new'])) $feedscount += count($this->campaign_data['feeds']['new']);
    if(isset($this->campaign_data['feeds']['edit'])) $feedscount += count($this->campaign_data['feeds']['edit']);
    if(isset($this->campaign_data['feeds']['delete'])) $feedscount -= count($this->campaign_data['feeds']['delete']);
    
    if(!$feedscount)
    {
      $errors['feeds'][] = __('You have to enter at least one feed', 'wpomatic');
      $this->errno++;
    } else {  
      if(isset($this->campaign_data['feeds']['new']))    
      {
        foreach($this->campaign_data['feeds']['new'] as $feed)
        {
          $simplepie = $this->fetchFeed($feed, true);
          if($simplepie->error())
          {
            $errors['feeds'][] = sprintf(__('Feed <strong>%s</strong> could not be parsed (SimplePie said: %s)', 'wpomatic'), $feed, $simplepie->error());
            $this->errno++;
          }          
        }  
      }
    }
    
    # Categories
    if(! sizeof($this->campaign_data['categories']))
    {
      $errors['categories'][] = __('Select at least one category', 'wpomatic');
      $this->errno++;
    }
    
    # Rewrite
    if(sizeof($this->campaign_data['rewrites']))
    {
      foreach($this->campaign_data['rewrites'] as $rewrite)
      {
        if($rewrite['origin']['regex'])
        {
          if(false === @preg_match($rewrite['origin']['search'], ''))
          {
            $errors['rewrites'][] = __('There\'s an error with the supplied RegEx expression', 'wpomatic');         
            $this->errno++;
          }
        }
      }
    }
    
    # Options    
    if(! get_userdatabylogin($this->campaign_data['main']['author']))
    {
      $errors['options'][] = __('Author username not found', 'wpomatic');
      $this->errno++;
    }
    
    if(! $this->campaign_data['main']['frequency'])
    {
      $errors['options'][] = __('Selected frequency is not valid', 'wpomatic');
      $this->errno++;
    }
    
    if(! ($this->campaign_data['main']['max'] === 0 || $this->campaign_data['main']['max'] > 0))
    {
      $errors['options'][] = __('Max items should be a valid number (greater than zero)', 'wpomatic');
      $this->errno++;
    }
    
    if($this->campaign_data['main']['cacheimages'] && !is_writable($this->cachepath))
    {
      $errors['options'][] = sprintf(__('Cache path (in <a href="%s">Options</a>) must be writable before enabling image caching.', 'wpomatic'), $this->adminurl . '&s=options' );
      $this->errno++;
    }
    
    $this->errors = $errors;
  }
  
  /**
   * Creates a campaign, and runs processEdit. If processEdit fails, campaign is removed
   *
   * @return campaign id if created successfully, errors if not
   */
  function adminProcessAdd()
  {
    global $wpdb;
    
    // Insert a campaign with dumb data
    $wpdb->query(WPOTools::insertQuery($this->db['campaign'], array('lastactive' => 0, 'count' => 0)));
    $cid = $wpdb->insert_id;
    
    // Process the edit
    $this->campaign_data['main']['lastactive'] = 0;
    $this->adminProcessEdit($cid);    
    return $cid;
  }
 
  /**
   * Cleans everything for the given id, then redoes everything
   *
   * @param integer $id           The id to edit
   */
  function adminProcessEdit($id)
  {
    global $wpdb;
    
    // If we need to execute a tool action we stop here
    if($this->adminProcessTools()) return;    
    
    // Delete all to recreate
    $wpdb->query("DELETE FROM {$this->db['campaign_word']} WHERE campaign_id = $id");
    $wpdb->query("DELETE FROM {$this->db['campaign_category']} WHERE campaign_id = $id");    
    
    // Process categories    
    # New
    if(isset($this->campaign_data['categories']['new']))
    {
      foreach($this->campaign_data['categories']['new'] as $category)
        $this->campaign_data['categories'][] = wp_insert_category(array('cat_name' => $category));
      
      unset($this->campaign_data['categories']['new']);
    }
    
    # All
    foreach($this->campaign_data['categories'] as $category)
    {
      // Insert
      $wpdb->query(WPOTools::insertQuery($this->db['campaign_category'], 
        array('category_id' => $category, 
              'campaign_id' => $id)
      ));
    }
    
    // Process feeds
    # New
    if(isset($this->campaign_data['feeds']['new']))
    {
      foreach($this->campaign_data['feeds']['new'] as $feed)
        $this->addCampaignFeed($id, $feed);        
    }
    
    # Delete
    if(isset($this->campaign_data['feeds']['delete']))
    {
      foreach($this->campaign_data['feeds']['delete'] as $feed)
        $wpdb->query("DELETE FROM {$this->db['campaign_feed']} WHERE id = $feed ");
    }
    
    // Process words
    foreach($this->campaign_data['rewrites'] as $rewrite)
    {
      $wpdb->query(WPOTools::insertQuery($this->db['campaign_word'], 
        array('word' => $rewrite['origin']['search'], 
              'regex' => $rewrite['origin']['regex'],
              'rewrite' => isset($rewrite['rewrite']),
              'rewrite_to' => isset($rewrite['rewrite']) ? $rewrite['rewrite'] : '',
              'relink' => isset($rewrite['relink']) ? $rewrite['relink'] : null,
              'campaign_id' => $id)
      ));
    }
    
    // Main 
    $main = $this->campaign_data['main'];

    // Fetch author id
    $author = get_userdatabylogin($this->campaign_data['main']['author']);
    $main['authorid'] = $author->ID;
    unset($main['author']);
    
    // Query
    $query = WPOTools::updateQuery($this->db['campaign'], $main, 'id = ' . intval($id));    
    $wpdb->query($query);
  }
  
  /**
   * Processes edit campaign tools actions
   *
   *
   */
  function adminProcessTools()
  {
    global $wpdb;
        
    $id = intval($_REQUEST['id']);
    
    if(isset($_REQUEST['tool_removeall']))
    {      
      $posts = $this->getCampaignPosts($id);
      
      foreach($posts as $post)
      {
        $wpdb->query("DELETE FROM {$wpdb->posts} WHERE ID = {$post->post_id} ");
      }
            
      // Delete log
      $wpdb->query("DELETE FROM {$this->db['campaign_post']} WHERE campaign_id = {$id} ");
      
      // Update feed and campaign posts count
      $wpdb->query(WPOTools::updateQuery($this->db['campaign'], array('count' => 0), "id = {$id}"));
      $wpdb->query(WPOTools::updateQuery($this->db['campaign_feed'], array('hash' => 0, 'count' => 0), "campaign_id = {$id}"));
      
      $this->tool_success = __('All posts removed', 'wpomatic');
      return true;
    }
    
    if(isset($_REQUEST['tool_changetype']))
    {
      $this->adminUpdateCampaignPosts($id, array(
        'post_status' => $wpdb->escape($_REQUEST['campaign_tool_changetype'])
      ));
      
      $this->tool_success = __('Posts status updated', 'wpomatic');
      return true;
    }
    
    if(isset($_REQUEST['tool_changeauthor']))
    {
      $author = get_userdatabylogin($_REQUEST['campaign_tool_changeauthor']);

      if($author)
      {
        $authorid = $author->ID;      
        $this->adminUpdateCampaignPosts($id, array('post_author' => $authorid)); 
      } else {
        $this->errno = 1;
        $this->errors = array('tools' => array(sprintf(__('Author %s not found', 'wpomatic'), attribute_escape($_REQUEST['campaign_tool_changeauthor']))));
      }
      
      $this->tool_success = __('Posts status updated', 'wpomatic');
      return true;
    }
    
    return false;
  }
  
  function adminUpdateCampaignPosts($id, $properties)
  {
    global $wpdb;
    
    $posts = $this->getCampaignPosts($id);
    
    foreach($posts as $post)
      $wpdb->query(WPOTools::updateQuery($wpdb->posts, $properties, "ID = {$post->id}"));
  }
  
  
  /** 
   * Show logs
   *
   *
   */  
  function adminLogs()
  {
    global $wpdb;
    
    // Clean logs?
    if(isset($_REQUEST['clean_logs']))
    {
      check_admin_referer('clean-logs');
      $wpdb->query("DELETE FROM {$this->db['log']} WHERE 1=1 ");
    }
    
    // Logs to show per page
    $logs_per_page = 20;
        
    $page = isset($_REQUEST['p']) ? intval($_REQUEST['p']) : 0;
    $total = $wpdb->get_var("SELECT COUNT(*) as cnt FROM {$this->db['log']} ");
    $logs = $this->getLogs("page={$page}&perpage={$logs_per_page}");
    
    $paging = paginate_links(array(
      'base' => $this->adminurl . '&s=logs&%_%',
      'format' => 'p=%#%',
      'total' => ceil($total / $logs_per_page),
      'current' => $page,
      'end_size' => 3
    ));
    
    include(WPOTPL . 'logs.php');
  }
}        

$wpomatic = & new WPOMatic();