web/wp-content/plugins/twitter-tools/twitter-tools.php
changeset 136 bde1974c263b
child 194 32102edaa81b
equal deleted inserted replaced
135:53cff4b4a802 136:bde1974c263b
       
     1 <?php
       
     2 /*
       
     3 Plugin Name: Twitter Tools
       
     4 Plugin URI: http://alexking.org/projects/wordpress
       
     5 Description: A complete integration between your WordPress blog and <a href="http://twitter.com">Twitter</a>. Bring your tweets into your blog and pass your blog posts to Twitter. Show your tweets in your sidebar, and post tweets from your WordPress admin.
       
     6 Version: 2.0
       
     7 Author: Alex King
       
     8 Author URI: http://alexking.org
       
     9 */
       
    10 
       
    11 // Copyright (c) 2007-2009 Crowd Favorite, Ltd., Alex King. All rights reserved.
       
    12 //
       
    13 // Released under the GPL license
       
    14 // http://www.opensource.org/licenses/gpl-license.php
       
    15 //
       
    16 // This is an add-on for WordPress
       
    17 // http://wordpress.org/
       
    18 //
       
    19 // Thanks to John Ford ( http://www.aldenta.com ) for his contributions.
       
    20 // Thanks to Dougal Campbell ( http://dougal.gunters.org ) for his contributions.
       
    21 // Thanks to Silas Sewell ( http://silas.sewell.ch ) for his contributions.
       
    22 // Thanks to Greg Grubbs for his contributions.
       
    23 //
       
    24 // **********************************************************************
       
    25 // This program is distributed in the hope that it will be useful, but
       
    26 // WITHOUT ANY WARRANTY; without even the implied warranty of
       
    27 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
       
    28 // **********************************************************************
       
    29 
       
    30 load_plugin_textdomain('twitter-tools');
       
    31 
       
    32 if (!defined('PLUGINDIR')) {
       
    33 	define('PLUGINDIR','wp-content/plugins');
       
    34 }
       
    35 
       
    36 if (is_file(trailingslashit(ABSPATH.PLUGINDIR).'twitter-tools.php')) {
       
    37 	define('AKTT_FILE', trailingslashit(ABSPATH.PLUGINDIR).'twitter-tools.php');
       
    38 }
       
    39 else if (is_file(trailingslashit(ABSPATH.PLUGINDIR).'twitter-tools/twitter-tools.php')) {
       
    40 	define('AKTT_FILE', trailingslashit(ABSPATH.PLUGINDIR).'twitter-tools/twitter-tools.php');
       
    41 }
       
    42 
       
    43 if (!function_exists('wp_prototype_before_jquery')) {
       
    44 	function wp_prototype_before_jquery( $js_array ) {
       
    45 		if ( false === $jquery = array_search( 'jquery', $js_array ) )
       
    46 			return $js_array;
       
    47 	
       
    48 		if ( false === $prototype = array_search( 'prototype', $js_array ) )
       
    49 			return $js_array;
       
    50 	
       
    51 		if ( $prototype < $jquery )
       
    52 			return $js_array;
       
    53 	
       
    54 		unset($js_array[$prototype]);
       
    55 	
       
    56 		array_splice( $js_array, $jquery, 0, 'prototype' );
       
    57 	
       
    58 		return $js_array;
       
    59 	}
       
    60 	
       
    61 	add_filter( 'print_scripts_array', 'wp_prototype_before_jquery' );
       
    62 }
       
    63 
       
    64 define('AKTT_API_POST_STATUS', 'http://twitter.com/statuses/update.json');
       
    65 define('AKTT_API_USER_TIMELINE', 'http://twitter.com/statuses/user_timeline.json');
       
    66 define('AKTT_API_STATUS_SHOW', 'http://twitter.com/statuses/show/###ID###.json');
       
    67 define('AKTT_PROFILE_URL', 'http://twitter.com/###USERNAME###');
       
    68 define('AKTT_STATUS_URL', 'http://twitter.com/###USERNAME###/statuses/###STATUS###');
       
    69 define('AKTT_HASHTAG_URL', 'http://search.twitter.com/search?q=###HASHTAG###');
       
    70 
       
    71 function aktt_install() {
       
    72 	global $wpdb;
       
    73 
       
    74 	$aktt_install = new twitter_tools;
       
    75 	$wpdb->aktt = $wpdb->prefix.'ak_twitter';
       
    76 	$charset_collate = '';
       
    77 	if ( version_compare(mysql_get_server_info(), '4.1.0', '>=') ) {
       
    78 		if (!empty($wpdb->charset)) {
       
    79 			$charset_collate .= " DEFAULT CHARACTER SET $wpdb->charset";
       
    80 		}
       
    81 		if (!empty($wpdb->collate)) {
       
    82 			$charset_collate .= " COLLATE $wpdb->collate";
       
    83 		}
       
    84 	}
       
    85 	$result = $wpdb->query("
       
    86 		CREATE TABLE `$wpdb->aktt` (
       
    87 		`id` INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
       
    88 		`tw_id` VARCHAR( 255 ) NOT NULL ,
       
    89 		`tw_text` VARCHAR( 255 ) NOT NULL ,
       
    90 		`tw_reply_username` VARCHAR( 255 ) DEFAULT NULL ,
       
    91 		`tw_reply_tweet` VARCHAR( 255 ) DEFAULT NULL ,
       
    92 		`tw_created_at` DATETIME NOT NULL ,
       
    93 		`modified` DATETIME NOT NULL ,
       
    94 		INDEX ( `tw_id` )
       
    95 		) $charset_collate
       
    96 	");
       
    97 	foreach ($aktt_install->options as $option) {
       
    98 		add_option('aktt_'.$option, $aktt_install->$option);
       
    99 	}
       
   100 	add_option('aktt_update_hash', '');
       
   101 }
       
   102 register_activation_hook(AKTT_FILE, 'aktt_install');
       
   103 
       
   104 class twitter_tools {
       
   105 	function twitter_tools() {
       
   106 		$this->options = array(
       
   107 			'twitter_username'
       
   108 			, 'twitter_password'
       
   109 			, 'create_blog_posts'
       
   110 			, 'create_digest'
       
   111 			, 'create_digest_weekly'
       
   112 			, 'digest_daily_time'
       
   113 			, 'digest_weekly_time'
       
   114 			, 'digest_weekly_day'
       
   115 			, 'digest_title'
       
   116 			, 'digest_title_weekly'
       
   117 			, 'blog_post_author'
       
   118 			, 'blog_post_category'
       
   119 			, 'blog_post_tags'
       
   120 			, 'notify_twitter'
       
   121 			, 'sidebar_tweet_count'
       
   122 			, 'tweet_from_sidebar'
       
   123 			, 'give_tt_credit'
       
   124 			, 'exclude_reply_tweets'
       
   125 			, 'tweet_prefix'
       
   126 			, 'last_tweet_download'
       
   127 			, 'doing_tweet_download'
       
   128 			, 'doing_digest_post'
       
   129 			, 'install_date'
       
   130 			, 'js_lib'
       
   131 			, 'digest_tweet_order'
       
   132 			, 'notify_twitter_default'
       
   133 		);
       
   134 		$this->twitter_username = '';
       
   135 		$this->twitter_password = '';
       
   136 		$this->create_blog_posts = '0';
       
   137 		$this->create_digest = '0';
       
   138 		$this->create_digest_weekly = '0';
       
   139 		$this->digest_daily_time = null;
       
   140 		$this->digest_weekly_time = null;
       
   141 		$this->digest_weekly_day = null;
       
   142 		$this->digest_title = __("Twitter Updates for %s", 'twitter-tools');
       
   143 		$this->digest_title_weekly = __("Twitter Weekly Updates for %s", 'twitter-tools');
       
   144 		$this->blog_post_author = '1';
       
   145 		$this->blog_post_category = '1';
       
   146 		$this->blog_post_tags = '';
       
   147 		$this->notify_twitter = '0';
       
   148 		$this->notify_twitter_default = '0';
       
   149 		$this->sidebar_tweet_count = '3';
       
   150 		$this->tweet_from_sidebar = '1';
       
   151 		$this->give_tt_credit = '1';
       
   152 		$this->exclude_reply_tweets = '0';
       
   153 		$this->install_date = '';
       
   154 		$this->js_lib = 'jquery';
       
   155 		$this->digest_tweet_order = 'ASC';
       
   156 		$this->tweet_prefix = 'New blog post';
       
   157 		// not included in options
       
   158 		$this->update_hash = '';
       
   159 		$this->tweet_format = $this->tweet_prefix.': %s %s';
       
   160 		$this->last_digest_post = '';
       
   161 		$this->last_tweet_download = '';
       
   162 		$this->doing_tweet_download = '0';
       
   163 		$this->doing_digest_post = '0';
       
   164 		$this->version = '1.6';
       
   165 	}
       
   166 	
       
   167 	function upgrade() {
       
   168 		global $wpdb;
       
   169 		$wpdb->aktt = $wpdb->prefix.'ak_twitter';
       
   170 
       
   171 		$col_data = $wpdb->get_results("
       
   172 			SHOW COLUMNS FROM $wpdb->aktt
       
   173 		");
       
   174 		$cols = array();
       
   175 		foreach ($col_data as $col) {
       
   176 			$cols[] = $col->Field;
       
   177 		}
       
   178 		// 1.2 schema upgrade
       
   179 		if (!in_array('tw_reply_username', $cols)) {
       
   180 			$wpdb->query("
       
   181 				ALTER TABLE `$wpdb->aktt`
       
   182 				ADD `tw_reply_username` VARCHAR( 255 ) DEFAULT NULL
       
   183 				AFTER `tw_text`
       
   184 			");
       
   185 		}
       
   186 		if (!in_array('tw_reply_tweet', $cols)) {
       
   187 			$wpdb->query("
       
   188 				ALTER TABLE `$wpdb->aktt`
       
   189 				ADD `tw_reply_tweet` VARCHAR( 255 ) DEFAULT NULL
       
   190 				AFTER `tw_reply_username`
       
   191 			");
       
   192 		}
       
   193 		$this->upgrade_default_tweet_prefix();
       
   194 	}
       
   195 	
       
   196 	function upgrade_default_tweet_prefix() {
       
   197 		$prefix = get_option('aktt_tweet_prefix');
       
   198 		if (empty($prefix)) {
       
   199 			$aktt_defaults = new twitter_tools;
       
   200 			update_option('aktt_tweet_prefix', $aktt_defaults->tweet_prefix);
       
   201 		}
       
   202 	}
       
   203 
       
   204 	function get_settings() {
       
   205 		foreach ($this->options as $option) {
       
   206 			$value = get_option('aktt_'.$option);
       
   207 			if ($option != 'tweet_prefix' || !empty($value)) {
       
   208 				$this->$option = $value;
       
   209 			}
       
   210 		}
       
   211 		$this->tweet_format = $this->tweet_prefix.': %s %s';
       
   212 	}
       
   213 	
       
   214 	// puts post fields into object propps
       
   215 	function populate_settings() {
       
   216 		foreach ($this->options as $option) {
       
   217 			$value = stripslashes($_POST['aktt_'.$option]);
       
   218 			if (isset($_POST['aktt_'.$option]) && ($option != 'tweet_prefix' || !empty($value))) {
       
   219 				$this->$option = $value;
       
   220 			}
       
   221 		}
       
   222 	}
       
   223 	
       
   224 	// puts object props into wp option storage
       
   225 	function update_settings() {
       
   226 		if (current_user_can('manage_options')) {
       
   227 			$this->sidebar_tweet_count = intval($this->sidebar_tweet_count);
       
   228 			if ($this->sidebar_tweet_count == 0) {
       
   229 				$this->sidebar_tweet_count = '3';
       
   230 			}
       
   231 			foreach ($this->options as $option) {
       
   232 				update_option('aktt_'.$option, $this->$option);
       
   233 			}
       
   234 			if (empty($this->install_date)) {
       
   235 				update_option('aktt_install_date', current_time('mysql'));
       
   236 			}
       
   237 			$this->initiate_digests();
       
   238 			$this->upgrade();
       
   239 			$this->upgrade_default_tweet_prefix();
       
   240 		}
       
   241 	}
       
   242 	
       
   243 	// figure out when the next weekly and daily digests will be
       
   244 	function initiate_digests() {
       
   245 		$next = ($this->create_digest) ? $this->calculate_next_daily_digest() : null;
       
   246 		$this->next_daily_digest = $next;
       
   247 		update_option('aktt_next_daily_digest', $next);
       
   248 		
       
   249 		$next = ($this->create_digest_weekly) ? $this->calculate_next_weekly_digest() : null;
       
   250 		$this->next_weekly_digest = $next;
       
   251 		update_option('aktt_next_weekly_digest', $next);
       
   252 	}
       
   253 	
       
   254 	function calculate_next_daily_digest() {
       
   255 		$optionDate = strtotime($this->digest_daily_time);
       
   256 		$hour_offset = date("G", $optionDate);
       
   257 		$minute_offset = date("i", $optionDate);
       
   258 		$next = mktime($hour_offset, $minute_offset, 0);
       
   259 		
       
   260 		// may have to move to next day
       
   261 		$now = time();
       
   262 		while($next < $now) {
       
   263 			$next += 60 * 60 * 24;
       
   264 		}
       
   265 		return $next;
       
   266 	}
       
   267 	
       
   268 	function calculate_next_weekly_digest() {
       
   269 		$optionDate = strtotime($this->digest_weekly_time);
       
   270 		$hour_offset = date("G", $optionDate);
       
   271 		$minute_offset = date("i", $optionDate);
       
   272 		
       
   273 		$current_day_of_month = date("j");
       
   274 		$current_day_of_week = date("w");
       
   275 		$current_month = date("n");
       
   276 		
       
   277 		// if this week's day is less than today, go for next week
       
   278 		$nextDay = $current_day_of_month - $current_day_of_week + $this->digest_weekly_day;
       
   279 		$next = mktime($hour_offset, $minute_offset, 0, $current_month, $nextDay);
       
   280 		if ($this->digest_weekly_day <= $current_day_of_week) {
       
   281 			$next = strtotime('+1 week', $next);
       
   282 		}
       
   283 		return $next;
       
   284 	}
       
   285 	
       
   286 	function ping_digests() {
       
   287 		// still busy
       
   288 		if (get_option('aktt_doing_digest_post') == '1') {
       
   289 			return;
       
   290 		}
       
   291 		// check all the digest schedules
       
   292 		if ($this->create_digest == 1) {
       
   293 			$this->ping_digest('aktt_next_daily_digest', 'aktt_last_digest_post', $this->digest_title, 60 * 60 * 24 * 1);
       
   294 		}
       
   295 		if ($this->create_digest_weekly == 1) {
       
   296 			$this->ping_digest('aktt_next_weekly_digest', 'aktt_last_digest_post_weekly', $this->digest_title_weekly, 60 * 60 * 24 * 7);
       
   297 		}
       
   298 		return;
       
   299 	}
       
   300 	
       
   301 	function ping_digest($nextDateField, $lastDateField, $title, $defaultDuration) {
       
   302 
       
   303 		$next = get_option($nextDateField);
       
   304 		
       
   305 		if ($next) {		
       
   306 			$next = $this->validateDate($next);
       
   307 			$rightNow = time();
       
   308 			if ($rightNow >= $next) {
       
   309 				$start = get_option($lastDateField);
       
   310 				$start = $this->validateDate($start, $rightNow - $defaultDuration);
       
   311 				if ($this->do_digest_post($start, $next, $title)) {
       
   312 					update_option($lastDateField, $rightNow);
       
   313 					update_option($nextDateField, $next + $defaultDuration);
       
   314 				} else {
       
   315 					update_option($lastDateField, null);
       
   316 				}
       
   317 			}
       
   318 		}
       
   319 	}
       
   320 	
       
   321 	function validateDate($in, $default = 0) {
       
   322 		if (!is_numeric($in)) {
       
   323 			// try to convert what they gave us into a date
       
   324 			$out = strtotime($in);
       
   325 			// if that doesn't work, return the default
       
   326 			if (!is_numeric($out)) {
       
   327 				return $default;
       
   328 			}
       
   329 			return $out;	
       
   330 		}
       
   331 		return $in;
       
   332 	}
       
   333 
       
   334 	function do_digest_post($start, $end, $title) {
       
   335 		
       
   336 		if (!$start || !$end) return false;
       
   337 
       
   338 		// flag us as busy
       
   339 		update_option('aktt_doing_digest_post', '1');
       
   340 		remove_action('publish_post', 'aktt_notify_twitter', 99);
       
   341 		remove_action('publish_post', 'aktt_store_post_options', 1, 2);
       
   342 		remove_action('save_post', 'aktt_store_post_options', 1, 2);
       
   343 		// see if there's any tweets in the time range
       
   344 		global $wpdb;
       
   345 		
       
   346 		$startGMT = gmdate("Y-m-d H:i:s", $start);
       
   347 		$endGMT = gmdate("Y-m-d H:i:s", $end);
       
   348 		
       
   349 		// build sql
       
   350 		$conditions = array();
       
   351 		$conditions[] = "tw_created_at >= '{$startGMT}'";
       
   352 		$conditions[] = "tw_created_at <= '{$endGMT}'";
       
   353 		$conditions[] = "tw_text NOT LIKE '$this->tweet_prefix%'";
       
   354 		if ($this->exclude_reply_tweets) {
       
   355 			$conditions[] = "tw_text NOT LIKE '@%'";
       
   356 		}
       
   357 		$where = implode(' AND ', $conditions);
       
   358 		
       
   359 		$sql = "
       
   360 			SELECT * FROM {$wpdb->aktt}
       
   361 			WHERE {$where}
       
   362 			GROUP BY tw_id
       
   363 			ORDER BY tw_created_at {$this->digest_tweet_order}
       
   364 		";
       
   365 
       
   366 		$tweets = $wpdb->get_results($sql);
       
   367 
       
   368 		if (count($tweets) > 0) {
       
   369 		
       
   370 			$tweets_to_post = array();
       
   371 			foreach ($tweets as $data) {
       
   372 				$tweet = new aktt_tweet;
       
   373 				$tweet->tw_text = $data->tw_text;
       
   374 				$tweet->tw_reply_tweet = $data->tw_reply_tweet;
       
   375 				if (!$tweet->tweet_is_post_notification() || ($tweet->tweet_is_reply() && $this->exclude_reply_tweets)) {
       
   376 					$tweets_to_post[] = $data;
       
   377 				}
       
   378 			}
       
   379 			
       
   380 			$tweets_to_post = apply_filters('aktt_tweets_to_digest_post', $tweets_to_post); // here's your chance to alter the tweet list that will be posted as the digest
       
   381 
       
   382 			if (count($tweets_to_post) > 0) {
       
   383 				$content = '<ul class="aktt_tweet_digest">'."\n";
       
   384 				foreach ($tweets_to_post as $tweet) {
       
   385 					$content .= '	<li>'.aktt_tweet_display($tweet, 'absolute').'</li>'."\n";
       
   386 				}
       
   387 				$content .= '</ul>'."\n";
       
   388 				if ($this->give_tt_credit == '1') {
       
   389 					$content .= '<p class="aktt_credit">'.__('Powered by <a href="http://alexking.org/projects/wordpress">Twitter Tools</a>', 'twitter-tools').'</p>';
       
   390 				}
       
   391 				$post_data = array(
       
   392 					'post_content' => $wpdb->escape($content),
       
   393 					'post_title' => $wpdb->escape(sprintf($title, date('Y-m-d'))),
       
   394 					'post_date' => date('Y-m-d H:i:s', $end),
       
   395 					'post_category' => array($this->blog_post_category),
       
   396 					'post_status' => 'publish',
       
   397 					'post_author' => $wpdb->escape($this->blog_post_author)
       
   398 				);
       
   399 				$post_data = apply_filters('aktt_digest_post_data', $post_data); // last chance to alter the digest content
       
   400 
       
   401 				$post_id = wp_insert_post($post_data);
       
   402 
       
   403 				add_post_meta($post_id, 'aktt_tweeted', '1', true);
       
   404 				wp_set_post_tags($post_id, $this->blog_post_tags);
       
   405 			}
       
   406 
       
   407 		}
       
   408 		add_action('publish_post', 'aktt_notify_twitter', 99);
       
   409 		add_action('publish_post', 'aktt_store_post_options', 1, 2);
       
   410 		add_action('save_post', 'aktt_store_post_options', 1, 2);
       
   411 		update_option('aktt_doing_digest_post', '0');
       
   412 		return true;
       
   413 	}
       
   414 	
       
   415 	function tweet_download_interval() {
       
   416 		return 600;
       
   417 	}
       
   418 	
       
   419 	function do_tweet($tweet = '') {
       
   420 		if (empty($this->twitter_username) 
       
   421 			|| empty($this->twitter_password) 
       
   422 			|| empty($tweet)
       
   423 			|| empty($tweet->tw_text)
       
   424 		) {
       
   425 			return;
       
   426 		}
       
   427 		$tweet = apply_filters('aktt_do_tweet', $tweet); // return false here to not tweet
       
   428 		if (!$tweet) {
       
   429 			return;
       
   430 		}
       
   431 		require_once(ABSPATH.WPINC.'/class-snoopy.php');
       
   432 		$snoop = new Snoopy;
       
   433 		$snoop->agent = 'Twitter Tools http://alexking.org/projects/wordpress';
       
   434 		$snoop->rawheaders = array(
       
   435 			'X-Twitter-Client' => 'Twitter Tools'
       
   436 			, 'X-Twitter-Client-Version' => $this->version
       
   437 			, 'X-Twitter-Client-URL' => 'http://alexking.org/projects/wordpress/twitter-tools.xml'
       
   438 		);
       
   439 		$snoop->user = $this->twitter_username;
       
   440 		$snoop->pass = $this->twitter_password;
       
   441 		$snoop->submit(
       
   442 			AKTT_API_POST_STATUS
       
   443 			, array(
       
   444 				'status' => $tweet->tw_text
       
   445 				, 'source' => 'twittertools'
       
   446 			)
       
   447 		);
       
   448 		if (strpos($snoop->response_code, '200')) {
       
   449 			update_option('aktt_last_tweet_download', strtotime('-28 minutes'));
       
   450 			return true;
       
   451 		}
       
   452 		return false;
       
   453 	}
       
   454 	
       
   455 	function do_blog_post_tweet($post_id = 0) {
       
   456 // this is only called on the publish_post hook
       
   457 		if ($this->notify_twitter == '0'
       
   458 			|| $post_id == 0
       
   459 			|| get_post_meta($post_id, 'aktt_tweeted', true) == '1'
       
   460 			|| get_post_meta($post_id, 'aktt_notify_twitter', true) == 'no'
       
   461 		) {
       
   462 			return;
       
   463 		}
       
   464 		$post = get_post($post_id);
       
   465 		// check for an edited post before TT was installed
       
   466 		if ($post->post_date <= $this->install_date) {
       
   467 			return;
       
   468 		}
       
   469 		// check for private posts
       
   470 		if ($post->post_status == 'private') {
       
   471 			return;
       
   472 		}
       
   473 		$tweet = new aktt_tweet;
       
   474 		$url = apply_filters('tweet_blog_post_url', get_permalink($post_id));
       
   475 		$tweet->tw_text = sprintf(__($this->tweet_format, 'twitter-tools'), @html_entity_decode($post->post_title, ENT_COMPAT, 'UTF-8'), $url);
       
   476 		$tweet = apply_filters('aktt_do_blog_post_tweet', $tweet, $post); // return false here to not tweet
       
   477 		if (!$tweet) {
       
   478 			return;
       
   479 		}
       
   480 		$this->do_tweet($tweet);
       
   481 		add_post_meta($post_id, 'aktt_tweeted', '1', true);
       
   482 	}
       
   483 	
       
   484 	function do_tweet_post($tweet) {
       
   485 		global $wpdb;
       
   486 		remove_action('publish_post', 'aktt_notify_twitter', 99);
       
   487 		$data = array(
       
   488 			'post_content' => $wpdb->escape(aktt_make_clickable($tweet->tw_text))
       
   489 			, 'post_title' => $wpdb->escape(trim_add_elipsis($tweet->tw_text, 30))
       
   490 			, 'post_date' => get_date_from_gmt(date('Y-m-d H:i:s', $tweet->tw_created_at))
       
   491 			, 'post_category' => array($this->blog_post_category)
       
   492 			, 'post_status' => 'publish'
       
   493 			, 'post_author' => $wpdb->escape($this->blog_post_author)
       
   494 		);
       
   495 		$data = apply_filters('aktt_do_tweet_post', $data, $tweet); // return false here to not make a blog post
       
   496 		if (!$data) {
       
   497 			return;
       
   498 		}
       
   499 		$post_id = wp_insert_post($data);
       
   500 		add_post_meta($post_id, 'aktt_twitter_id', $tweet->tw_id, true);
       
   501 		wp_set_post_tags($post_id, $this->blog_post_tags);
       
   502 		add_action('publish_post', 'aktt_notify_twitter', 99);
       
   503 	}
       
   504 }
       
   505 
       
   506 class aktt_tweet {
       
   507 	function aktt_tweet(
       
   508 		$tw_id = ''
       
   509 		, $tw_text = ''
       
   510 		, $tw_created_at = ''
       
   511 		, $tw_reply_username = null
       
   512 		, $tw_reply_tweet = null
       
   513 	) {
       
   514 		$this->id = '';
       
   515 		$this->modified = '';
       
   516 		$this->tw_created_at = $tw_created_at;
       
   517 		$this->tw_text = $tw_text;
       
   518 		$this->tw_reply_username = $tw_reply_username;
       
   519 		$this->tw_reply_tweet = $tw_reply_tweet;
       
   520 		$this->tw_id = $tw_id;
       
   521 	}
       
   522 	
       
   523 	function twdate_to_time($date) {
       
   524 		$parts = explode(' ', $date);
       
   525 		$date = strtotime($parts[1].' '.$parts[2].', '.$parts[5].' '.$parts[3]);
       
   526 		return $date;
       
   527 	}
       
   528 	
       
   529 	function tweet_post_exists() {
       
   530 		global $wpdb;
       
   531 		$test = $wpdb->get_results("
       
   532 			SELECT *
       
   533 			FROM $wpdb->postmeta
       
   534 			WHERE meta_key = 'aktt_twitter_id'
       
   535 			AND meta_value = '".$wpdb->escape($this->tw_id)."'
       
   536 		");
       
   537 		if (count($test) > 0) {
       
   538 			return true;
       
   539 		}
       
   540 		return false;
       
   541 	}
       
   542 	
       
   543 	function tweet_is_post_notification() {
       
   544 		global $aktt;
       
   545 		if (substr($this->tw_text, 0, strlen($aktt->tweet_prefix)) == $aktt->tweet_prefix) {
       
   546 			return true;
       
   547 		}
       
   548 		return false;
       
   549 	}
       
   550 	
       
   551 	function tweet_is_reply() {
       
   552 // Twitter data changed - users still expect anything starting with @ is a reply
       
   553 //		return !empty($this->tw_reply_tweet);
       
   554 		return (substr($this->tw_text, 0, 1) == '@');
       
   555 	}
       
   556 	
       
   557 	function add() {
       
   558 		global $wpdb, $aktt;
       
   559 		$wpdb->query("
       
   560 			INSERT
       
   561 			INTO $wpdb->aktt
       
   562 			( tw_id
       
   563 			, tw_text
       
   564 			, tw_reply_username
       
   565 			, tw_reply_tweet
       
   566 			, tw_created_at
       
   567 			, modified
       
   568 			)
       
   569 			VALUES
       
   570 			( '".$wpdb->escape($this->tw_id)."'
       
   571 			, '".$wpdb->escape($this->tw_text)."'
       
   572 			, '".$wpdb->escape($this->tw_reply_username)."'
       
   573 			, '".$wpdb->escape($this->tw_reply_tweet)."'
       
   574 			, '".date('Y-m-d H:i:s', $this->tw_created_at)."'
       
   575 			, NOW()
       
   576 			)
       
   577 		");
       
   578 		do_action('aktt_add_tweet', $this);
       
   579 		if ($aktt->create_blog_posts == '1' && !$this->tweet_post_exists() && !$this->tweet_is_post_notification() && (!$aktt->exclude_reply_tweets || !$this->tweet_is_reply())) {
       
   580 			$aktt->do_tweet_post($this);
       
   581 		}
       
   582 	}
       
   583 }
       
   584 
       
   585 function aktt_api_status_show_url($id) {
       
   586 	return str_replace('###ID###', $id, AKTT_API_STATUS_SHOW);
       
   587 }
       
   588 
       
   589 function aktt_profile_url($username) {
       
   590 	return str_replace('###USERNAME###', $username, AKTT_PROFILE_URL);
       
   591 }
       
   592 
       
   593 function aktt_profile_link($username, $prefix = '', $suffix = '') {
       
   594 	return $prefix.'<a href="'.aktt_profile_url($username).'" class="aktt_username">'.$username.'</a>'.$suffix;
       
   595 }
       
   596 
       
   597 function aktt_hashtag_url($hashtag) {
       
   598 	$hashtag = urlencode('#'.$hashtag);
       
   599 	return str_replace('###HASHTAG###', $hashtag, AKTT_HASHTAG_URL);
       
   600 }
       
   601 
       
   602 function aktt_hashtag_link($hashtag, $prefix = '', $suffix = '') {
       
   603 	return $prefix.'<a href="'.aktt_hashtag_url($hashtag).'" class="aktt_hashtag">'.htmlspecialchars($hashtag).'</a>'.$suffix;
       
   604 }
       
   605 
       
   606 function aktt_status_url($username, $status) {
       
   607 	return str_replace(
       
   608 		array(
       
   609 			'###USERNAME###'
       
   610 			, '###STATUS###'
       
   611 		)
       
   612 		, array(
       
   613 			$username
       
   614 			, $status
       
   615 		)
       
   616 		, AKTT_STATUS_URL
       
   617 	);
       
   618 }
       
   619 
       
   620 function aktt_login_test($username, $password) {
       
   621 	require_once(ABSPATH.WPINC.'/class-snoopy.php');
       
   622 	$snoop = new Snoopy;
       
   623 	$snoop->agent = 'Twitter Tools http://alexking.org/projects/wordpress';
       
   624 	$snoop->user = $username;
       
   625 	$snoop->pass = $password;
       
   626 	$snoop->fetch(AKTT_API_USER_TIMELINE);
       
   627 	if (strpos($snoop->response_code, '200')) {
       
   628 		return __("Login succeeded, you're good to go.", 'twitter-tools');
       
   629 	} else {
       
   630 		$json = new Services_JSON();
       
   631 		$results = $json->decode($snoop->results);
       
   632 		return sprintf(__('Sorry, login failed. Error message from Twitter: %s', 'twitter-tools'), $results->error);
       
   633 	}
       
   634 }
       
   635 
       
   636 
       
   637 function aktt_ping_digests() {
       
   638 	global $aktt;
       
   639 	$aktt->ping_digests();
       
   640 }
       
   641 
       
   642 function aktt_update_tweets() {
       
   643 	global $aktt;
       
   644 	// let the last update run for 10 minutes
       
   645 	if (time() - intval(get_option('aktt_doing_tweet_download')) < $aktt->tweet_download_interval()) {
       
   646 		return;
       
   647 	}
       
   648 	// wait 10 min between downloads
       
   649 	if (time() - intval(get_option('aktt_last_tweet_download')) < $aktt->tweet_download_interval()) {
       
   650 		return;
       
   651 	}
       
   652 	update_option('aktt_doing_tweet_download', time());
       
   653 	global $wpdb, $aktt;
       
   654 	if (empty($aktt->twitter_username) || empty($aktt->twitter_password)) {
       
   655 		update_option('aktt_doing_tweet_download', '0');
       
   656 		return;
       
   657 	}
       
   658 	require_once(ABSPATH.WPINC.'/class-snoopy.php');
       
   659 	$snoop = new Snoopy;
       
   660 	$snoop->agent = 'Twitter Tools http://alexking.org/projects/wordpress';
       
   661 	$snoop->user = $aktt->twitter_username;
       
   662 	$snoop->pass = $aktt->twitter_password;
       
   663 	$snoop->fetch(AKTT_API_USER_TIMELINE);
       
   664 
       
   665 	if (!strpos($snoop->response_code, '200')) {
       
   666 		update_option('aktt_doing_tweet_download', '0');
       
   667 		return;
       
   668 	}
       
   669 
       
   670 	$data = $snoop->results;
       
   671 	// hash results to see if they're any different than the last update, if so, return
       
   672 	$hash = md5($data);
       
   673 	if ($hash == get_option('aktt_update_hash')) {
       
   674 		update_option('aktt_last_tweet_download', time());
       
   675 		update_option('aktt_doing_tweet_download', '0');
       
   676 		return;
       
   677 	}
       
   678 	$json = new Services_JSON();
       
   679 	$tweets = $json->decode($data);
       
   680 
       
   681 	if (is_array($tweets) && count($tweets) > 0) {
       
   682 		$tweet_ids = array();
       
   683 		foreach ($tweets as $tweet) {
       
   684 			$tweet_ids[] = $wpdb->escape($tweet->id);
       
   685 		}
       
   686 		$existing_ids = $wpdb->get_col("
       
   687 			SELECT tw_id
       
   688 			FROM $wpdb->aktt
       
   689 			WHERE tw_id
       
   690 			IN ('".implode("', '", $tweet_ids)."')
       
   691 		");
       
   692 		$new_tweets = array();
       
   693 		foreach ($tweets as $tw_data) {
       
   694 			if (!$existing_ids || !in_array($tw_data->id, $existing_ids)) {
       
   695 				$tweet = new aktt_tweet(
       
   696 					$tw_data->id
       
   697 					, $tw_data->text
       
   698 				);
       
   699 				$tweet->tw_created_at = $tweet->twdate_to_time($tw_data->created_at);
       
   700 				if (!empty($tw_data->in_reply_to_status_id)) {
       
   701 					$tweet->tw_reply_tweet = $tw_data->in_reply_to_status_id;
       
   702 					$url = aktt_api_status_show_url($tw_data->in_reply_to_status_id);
       
   703 					$snoop->fetch($url);
       
   704 					if (strpos($snoop->response_code, '200') !== false) {
       
   705 						$data = $snoop->results;
       
   706 						$status = $json->decode($data);
       
   707 						$tweet->tw_reply_username = $status->user->screen_name;
       
   708 					}
       
   709 				}
       
   710 				// make sure we haven't downloaded someone else's tweets - happens sometimes due to Twitter hiccups
       
   711 				if (strtolower($tw_data->user->screen_name) == strtolower($aktt->twitter_username)) {
       
   712 					$new_tweets[] = $tweet;
       
   713 					$tweet->add();
       
   714 				}
       
   715 			}
       
   716 		}
       
   717 	}
       
   718 	aktt_reset_tweet_checking($hash, time());
       
   719 }
       
   720 
       
   721 
       
   722 function aktt_reset_tweet_checking($hash = '', $time = 0) {
       
   723 	if (!current_user_can('manage_options')) {
       
   724 		return;
       
   725 	}
       
   726 	update_option('aktt_update_hash', $hash);
       
   727 	update_option('aktt_last_tweet_download', $time);
       
   728 	update_option('aktt_doing_tweet_download', '0');
       
   729 }
       
   730 
       
   731 function aktt_reset_digests() {
       
   732 	if (!current_user_can('manage_options')) {
       
   733 		return;
       
   734 	}
       
   735 	update_option('aktt_doing_digest_post', '0');
       
   736 }
       
   737 
       
   738 function aktt_notify_twitter($post_id) {
       
   739 	global $aktt;
       
   740 	$aktt->do_blog_post_tweet($post_id);
       
   741 }
       
   742 add_action('publish_post', 'aktt_notify_twitter', 99);
       
   743 
       
   744 function aktt_sidebar_tweets() {
       
   745 	global $wpdb, $aktt;
       
   746 	if ($aktt->exclude_reply_tweets) {
       
   747 		$where = "AND tw_text NOT LIKE '@%' ";
       
   748 	}
       
   749 	else {
       
   750 		$where = '';
       
   751 	}
       
   752 	$tweets = $wpdb->get_results("
       
   753 		SELECT *
       
   754 		FROM $wpdb->aktt
       
   755 		WHERE tw_text NOT LIKE '$aktt->tweet_prefix%'
       
   756 		$where
       
   757 		GROUP BY tw_id
       
   758 		ORDER BY tw_created_at DESC
       
   759 		LIMIT $aktt->sidebar_tweet_count
       
   760 	");
       
   761 	$output = '<div class="aktt_tweets">'."\n"
       
   762 		.'	<ul>'."\n";
       
   763 	if (count($tweets) > 0) {
       
   764 		foreach ($tweets as $tweet) {
       
   765 			$output .= '		<li>'.aktt_tweet_display($tweet).'</li>'."\n";
       
   766 		}
       
   767 	}
       
   768 	else {
       
   769 		$output .= '		<li>'.__('No tweets available at the moment.', 'twitter-tools').'</li>'."\n";
       
   770 	}
       
   771 	if (!empty($aktt->twitter_username)) {
       
   772   		$output .= '		<li class="aktt_more_updates"><a href="'.aktt_profile_url($aktt->twitter_username).'">'.__('More updates...', 'twitter-tools').'</a></li>'."\n";
       
   773 	}
       
   774 	$output .= '</ul>';
       
   775 	if ($aktt->tweet_from_sidebar == '1' && !empty($aktt->twitter_username) && !empty($aktt->twitter_password)) {
       
   776   		$output .= aktt_tweet_form('input', 'onsubmit="akttPostTweet(); return false;"');
       
   777 		  $output .= '	<p id="aktt_tweet_posted_msg">'.__('Posting tweet...', 'twitter-tools').'</p>';
       
   778 	}
       
   779 	if ($aktt->give_tt_credit == '1') {
       
   780 		$output .= '<p class="aktt_credit">'.__('Powered by <a href="http://alexking.org/projects/wordpress">Twitter Tools</a>', 'twitter-tools').'</p>';
       
   781 	}
       
   782 	$output .= '</div>';
       
   783 	print($output);
       
   784 }
       
   785 
       
   786 function aktt_latest_tweet() {
       
   787 	global $wpdb, $aktt;
       
   788 	$tweets = $wpdb->get_results("
       
   789 		SELECT *
       
   790 		FROM $wpdb->aktt
       
   791 		WHERE tw_text NOT LIKE '$aktt->tweet_prefix%'
       
   792 		GROUP BY tw_id
       
   793 		ORDER BY tw_created_at DESC
       
   794 		LIMIT 1
       
   795 	");
       
   796 	if (count($tweets) == 1) {
       
   797 		foreach ($tweets as $tweet) {
       
   798 			$output = aktt_tweet_display($tweet);
       
   799 		}
       
   800 	}
       
   801 	else {
       
   802 		$output = __('No tweets available at the moment.', 'twitter-tools');
       
   803 	}
       
   804 	print($output);
       
   805 }
       
   806 
       
   807 function aktt_tweet_display($tweet, $time = 'relative') {
       
   808 	global $aktt;
       
   809 	$output = aktt_make_clickable(wp_specialchars($tweet->tw_text));
       
   810 	if (!empty($tweet->tw_reply_username)) {
       
   811 		$output .= 	' <a href="'.aktt_status_url($tweet->tw_reply_username, $tweet->tw_reply_tweet).'" class="aktt_tweet_reply">'.sprintf(__('in reply to %s', 'twitter-tools'), $tweet->tw_reply_username).'</a>';
       
   812 	}
       
   813 	switch ($time) {
       
   814 		case 'relative':
       
   815 			$time_display = aktt_relativeTime($tweet->tw_created_at, 3);
       
   816 			break;
       
   817 		case 'absolute':
       
   818 			$time_display = '#';
       
   819 			break;
       
   820 	}
       
   821 	$output .= ' <a href="'.aktt_status_url($aktt->twitter_username, $tweet->tw_id).'" class="aktt_tweet_time">'.$time_display.'</a>';
       
   822 	$output = apply_filters('aktt_tweet_display', $output, $tweet); // allows you to alter the tweet display output
       
   823 	return $output;
       
   824 }
       
   825 
       
   826 function aktt_make_clickable($tweet) {
       
   827 	$tweet .= ' ';
       
   828 	$tweet = preg_replace_callback(
       
   829 			'/@([a-zA-Z0-9_]{1,15})([) ])/'
       
   830 			, create_function(
       
   831 				'$matches'
       
   832 				, 'return aktt_profile_link($matches[1], \'@\', $matches[2]);'
       
   833 			)
       
   834 			, $tweet
       
   835 	);
       
   836 	$tweet = preg_replace_callback(
       
   837 		'/\ #([a-zA-Z0-9_]{1,15})/'
       
   838 		, create_function(
       
   839 			'$matches'
       
   840 			, 'return aktt_hashtag_link($matches[1], \' #\', \'\');'
       
   841 		)
       
   842 		, $tweet
       
   843 	);
       
   844 	
       
   845 	if (function_exists('make_chunky')) {
       
   846 		return make_chunky($tweet);
       
   847 	}
       
   848 	else {
       
   849 		return make_clickable($tweet);
       
   850 	}
       
   851 }
       
   852 
       
   853 function aktt_tweet_form($type = 'input', $extra = '') {
       
   854 	$output = '';
       
   855 	if (current_user_can('publish_posts')) {
       
   856 		$output .= '
       
   857 <form action="'.get_bloginfo('wpurl').'/index.php" method="post" id="aktt_tweet_form" '.$extra.'>
       
   858 	<fieldset>
       
   859 		';
       
   860 		switch ($type) {
       
   861 			case 'input':
       
   862 				$output .= '
       
   863 		<p><input type="text" size="20" maxlength="140" id="aktt_tweet_text" name="aktt_tweet_text" onkeyup="akttCharCount();" /></p>
       
   864 		<input type="hidden" name="ak_action" value="aktt_post_tweet_sidebar" />
       
   865 		<script type="text/javascript">
       
   866 		//<![CDATA[
       
   867 		function akttCharCount() {
       
   868 			var count = document.getElementById("aktt_tweet_text").value.length;
       
   869 			if (count > 0) {
       
   870 				document.getElementById("aktt_char_count").innerHTML = 140 - count;
       
   871 			}
       
   872 			else {
       
   873 				document.getElementById("aktt_char_count").innerHTML = "";
       
   874 			}
       
   875 		}
       
   876 		setTimeout("akttCharCount();", 500);
       
   877 		document.getElementById("aktt_tweet_form").setAttribute("autocomplete", "off");
       
   878 		//]]>
       
   879 		</script>
       
   880 				';
       
   881 				break;
       
   882 			case 'textarea':
       
   883 				$output .= '
       
   884 		<p><textarea type="text" cols="60" rows="5" maxlength="140" id="aktt_tweet_text" name="aktt_tweet_text" onkeyup="akttCharCount();"></textarea></p>
       
   885 		<input type="hidden" name="ak_action" value="aktt_post_tweet_admin" />
       
   886 		<script type="text/javascript">
       
   887 		//<![CDATA[
       
   888 		function akttCharCount() {
       
   889 			var count = document.getElementById("aktt_tweet_text").value.length;
       
   890 			if (count > 0) {
       
   891 				document.getElementById("aktt_char_count").innerHTML = (140 - count) + "'.__(' characters remaining', 'twitter-tools').'";
       
   892 			}
       
   893 			else {
       
   894 				document.getElementById("aktt_char_count").innerHTML = "";
       
   895 			}
       
   896 		}
       
   897 		setTimeout("akttCharCount();", 500);
       
   898 		document.getElementById("aktt_tweet_form").setAttribute("autocomplete", "off");
       
   899 		//]]>
       
   900 		</script>
       
   901 				';
       
   902 				break;
       
   903 		}
       
   904 		$output .= '
       
   905 		<p>
       
   906 			<input type="submit" id="aktt_tweet_submit" name="aktt_tweet_submit" value="'.__('Post Tweet!', 'twitter-tools').'" class="button-primary" />
       
   907 			<span id="aktt_char_count"></span>
       
   908 		</p>
       
   909 		<div class="clear"></div>
       
   910 	</fieldset>
       
   911 </form>
       
   912 		';
       
   913 	}
       
   914 	return $output;
       
   915 }
       
   916 
       
   917 function aktt_widget_init() {
       
   918 	if (!function_exists('register_sidebar_widget')) {
       
   919 		return;
       
   920 	}
       
   921 	function aktt_widget($args) {
       
   922 		extract($args);
       
   923 		$options = get_option('aktt_widget');
       
   924 		$title = $options['title'];
       
   925 		if (empty($title)) {
       
   926 		}
       
   927 		echo $before_widget . $before_title . $title . $after_title;
       
   928 		aktt_sidebar_tweets();
       
   929 		echo $after_widget;
       
   930 	}
       
   931 	register_sidebar_widget(array(__('Twitter Tools', 'twitter-tools'), 'widgets'), 'aktt_widget');
       
   932 	
       
   933 	function aktt_widget_control() {
       
   934 		$options = get_option('aktt_widget');
       
   935 		if (!is_array($options)) {
       
   936 			$options = array(
       
   937 				'title' => __("What I'm Doing...", 'twitter-tools')
       
   938 			);
       
   939 		}
       
   940 		if (isset($_POST['ak_action']) && $_POST['ak_action'] == 'aktt_update_widget_options') {
       
   941 			$options['title'] = strip_tags(stripslashes($_POST['aktt_widget_title']));
       
   942 			update_option('aktt_widget', $options);
       
   943 			// reset checking so that sidebar isn't blank if this is the first time activating
       
   944 			aktt_reset_tweet_checking();
       
   945 			aktt_update_tweets();
       
   946 		}
       
   947 
       
   948 		// Be sure you format your options to be valid HTML attributes.
       
   949 		$title = htmlspecialchars($options['title'], ENT_QUOTES);
       
   950 		
       
   951 		// Here is our little form segment. Notice that we don't need a
       
   952 		// complete form. This will be embedded into the existing form.
       
   953 		print('
       
   954 			<p style="text-align:right;"><label for="aktt_widget_title">' . __('Title:') . ' <input style="width: 200px;" id="aktt_widget_title" name="aktt_widget_title" type="text" value="'.$title.'" /></label></p>
       
   955 			<p>'.__('Find additional Twitter Tools options on the <a href="options-general.php?page=twitter-tools.php">Twitter Tools Options page</a>.', 'twitter-tools').'
       
   956 			<input type="hidden" id="ak_action" name="ak_action" value="aktt_update_widget_options" />
       
   957 		');
       
   958 	}
       
   959 	register_widget_control(array(__('Twitter Tools', 'twitter-tools'), 'widgets'), 'aktt_widget_control', 300, 100);
       
   960 
       
   961 }
       
   962 add_action('widgets_init', 'aktt_widget_init');
       
   963 
       
   964 function aktt_init() {
       
   965 	global $wpdb, $aktt;
       
   966 	$aktt = new twitter_tools;
       
   967 
       
   968 	$wpdb->aktt = $wpdb->prefix.'ak_twitter';
       
   969 
       
   970 	$aktt->get_settings();
       
   971 	if (($aktt->last_tweet_download + $aktt->tweet_download_interval()) < time()) {
       
   972 		add_action('shutdown', 'aktt_update_tweets');
       
   973 		add_action('shutdown', 'aktt_ping_digests');
       
   974 	}
       
   975 	if (is_admin() || ($aktt->tweet_from_sidebar && current_user_can('publish_posts'))) {
       
   976 		switch ($aktt->js_lib) {
       
   977 			case 'jquery':
       
   978 				wp_enqueue_script('jquery');
       
   979 				break;
       
   980 			case 'prototype':
       
   981 				wp_enqueue_script('prototype');
       
   982 				break;
       
   983 		}
       
   984 	}
       
   985 	global $wp_version;
       
   986 	if (isset($wp_version) && version_compare($wp_version, '2.5', '>=') && empty ($aktt->install_date)) {
       
   987 		add_action('admin_notices', create_function( '', "echo '<div class=\"error\"><p>".sprintf(__('Please update your <a href="%s">Twitter Tools settings</a>', 'twitter-tools'), get_bloginfo('wpurl')."/wp-admin/options-general.php?page=twitter-tools.php")."</p></div>';" ) );
       
   988 	}
       
   989 	if (!get_option('aktt_tweet_prefix')) {
       
   990 		update_option('aktt_tweet_prefix', $aktt->tweet_prefix);
       
   991 		add_action('admin_notices', create_function( '', "echo '<div class=\"error\"><p>".sprintf(__('Please update your <a href="%s">Twitter Tools settings</a>', 'twitter-tools'), get_bloginfo('wpurl')."/wp-admin/options-general.php?page=twitter-tools.php")."</p></div>';" ) );
       
   992 	}
       
   993 }
       
   994 add_action('init', 'aktt_init');
       
   995 
       
   996 function aktt_head() {
       
   997 	global $aktt;
       
   998 	if ($aktt->tweet_from_sidebar) {
       
   999 		print('
       
  1000 			<link rel="stylesheet" type="text/css" href="'.get_bloginfo('wpurl').'/index.php?ak_action=aktt_css" />
       
  1001 			<script type="text/javascript" src="'.get_bloginfo('wpurl').'/index.php?ak_action=aktt_js"></script>
       
  1002 		');
       
  1003 	}
       
  1004 }
       
  1005 add_action('wp_head', 'aktt_head');
       
  1006 
       
  1007 function aktt_head_admin() {
       
  1008 	print('
       
  1009 		<link rel="stylesheet" type="text/css" href="'.get_bloginfo('wpurl').'/index.php?ak_action=aktt_css_admin" />
       
  1010 		<script type="text/javascript" src="'.get_bloginfo('wpurl').'/index.php?ak_action=aktt_js_admin"></script>
       
  1011 	');
       
  1012 }
       
  1013 add_action('admin_head', 'aktt_head_admin');
       
  1014 
       
  1015 function aktt_request_handler() {
       
  1016 	global $wpdb, $aktt;
       
  1017 	if (!empty($_GET['ak_action'])) {
       
  1018 		switch($_GET['ak_action']) {
       
  1019 			case 'aktt_update_tweets':
       
  1020 				aktt_update_tweets();
       
  1021 				wp_redirect(get_bloginfo('wpurl').'/wp-admin/options-general.php?page=twitter-tools.php&tweets-updated=true');
       
  1022 				die();
       
  1023 				break;
       
  1024 			case 'aktt_reset_tweet_checking':
       
  1025 				aktt_reset_tweet_checking();
       
  1026 				wp_redirect(get_bloginfo('wpurl').'/wp-admin/options-general.php?page=twitter-tools.php&tweet-checking-reset=true');
       
  1027 				die();
       
  1028 				break;
       
  1029 			case 'aktt_reset_tweet_checking':
       
  1030 				aktt_reset_digests();
       
  1031 				wp_redirect(get_bloginfo('wpurl').'/wp-admin/options-general.php?page=twitter-tools.php&digest-reset=true');
       
  1032 				die();
       
  1033 				break;
       
  1034 			case 'aktt_js':
       
  1035 				remove_action('shutdown', 'aktt_ping_digests');
       
  1036 				header("Content-type: text/javascript");
       
  1037 				switch ($aktt->js_lib) {
       
  1038 					case 'jquery':
       
  1039 ?>
       
  1040 function akttPostTweet() {
       
  1041 	var tweet_field = jQuery('#aktt_tweet_text');
       
  1042 	var tweet_text = tweet_field.val();
       
  1043 	if (tweet_text == '') {
       
  1044 		return;
       
  1045 	}
       
  1046 	var tweet_msg = jQuery("#aktt_tweet_posted_msg");
       
  1047 	jQuery.post(
       
  1048 		"<?php bloginfo('wpurl'); ?>/index.php"
       
  1049 		, {
       
  1050 			ak_action: "aktt_post_tweet_sidebar"
       
  1051 			, aktt_tweet_text: tweet_text
       
  1052 		}
       
  1053 		, function(data) {
       
  1054 			tweet_msg.html(data);
       
  1055 			akttSetReset();
       
  1056 		}
       
  1057 	);
       
  1058 	tweet_field.val('').focus();
       
  1059 	jQuery('#aktt_char_count').html('');
       
  1060 	jQuery("#aktt_tweet_posted_msg").show();
       
  1061 }
       
  1062 function akttSetReset() {
       
  1063 	setTimeout('akttReset();', 2000);
       
  1064 }
       
  1065 function akttReset() {
       
  1066 	jQuery('#aktt_tweet_posted_msg').hide();
       
  1067 }
       
  1068 <?php
       
  1069 						break;
       
  1070 					case 'prototype':
       
  1071 ?>
       
  1072 function akttPostTweet() {
       
  1073 	var tweet_field = $('aktt_tweet_text');
       
  1074 	var tweet_text = tweet_field.value;
       
  1075 	if (tweet_text == '') {
       
  1076 		return;
       
  1077 	}
       
  1078 	var tweet_msg = $("aktt_tweet_posted_msg");
       
  1079 	var akttAjax = new Ajax.Updater(
       
  1080 		tweet_msg,
       
  1081 		"<?php bloginfo('wpurl'); ?>/index.php",
       
  1082 		{
       
  1083 			method: "post",
       
  1084 			parameters: "ak_action=aktt_post_tweet_sidebar&aktt_tweet_text=" + tweet_text,
       
  1085 			onComplete: akttSetReset
       
  1086 		}
       
  1087 	);
       
  1088 	tweet_field.value = '';
       
  1089 	tweet_field.focus();
       
  1090 	$('aktt_char_count').innerHTML = '';
       
  1091 	tweet_msg.style.display = 'block';
       
  1092 }
       
  1093 function akttSetReset() {
       
  1094 	setTimeout('akttReset();', 2000);
       
  1095 }
       
  1096 function akttReset() {
       
  1097 	$('aktt_tweet_posted_msg').style.display = 'none';
       
  1098 }
       
  1099 <?php
       
  1100 						break;
       
  1101 				}
       
  1102 				die();
       
  1103 				break;
       
  1104 			case 'aktt_css':
       
  1105 				remove_action('shutdown', 'aktt_ping_digests');
       
  1106 				header("Content-Type: text/css");
       
  1107 ?>
       
  1108 #aktt_tweet_form {
       
  1109 	margin: 0;
       
  1110 	padding: 5px 0;
       
  1111 }
       
  1112 #aktt_tweet_form fieldset {
       
  1113 	border: 0;
       
  1114 }
       
  1115 #aktt_tweet_form fieldset #aktt_tweet_submit {
       
  1116 	float: right;
       
  1117 	margin-right: 10px;
       
  1118 }
       
  1119 #aktt_tweet_form fieldset #aktt_char_count {
       
  1120 	color: #666;
       
  1121 }
       
  1122 #aktt_tweet_posted_msg {
       
  1123 	background: #ffc;
       
  1124 	display: none;
       
  1125 	margin: 0 0 5px 0;
       
  1126 	padding: 5px;
       
  1127 }
       
  1128 #aktt_tweet_form div.clear {
       
  1129 	clear: both;
       
  1130 	float: none;
       
  1131 }
       
  1132 <?php
       
  1133 				die();
       
  1134 				break;
       
  1135 			case 'aktt_js_admin':
       
  1136 				remove_action('shutdown', 'aktt_ping_digests');
       
  1137 				header("Content-Type: text/javascript");
       
  1138 				switch ($aktt->js_lib) {
       
  1139 					case 'jquery':
       
  1140 ?>
       
  1141 function akttTestLogin() {
       
  1142 	var result = jQuery('#aktt_login_test_result');
       
  1143 	result.show().addClass('aktt_login_result_wait').html('<?php _e('Testing...', 'twitter-tools'); ?>');
       
  1144 	jQuery.post(
       
  1145 		"<?php bloginfo('wpurl'); ?>/index.php"
       
  1146 		, {
       
  1147 			ak_action: "aktt_login_test"
       
  1148 			, aktt_twitter_username: jQuery('#aktt_twitter_username').val()
       
  1149 			, aktt_twitter_password: jQuery('#aktt_twitter_password').val()
       
  1150 		}
       
  1151 		, function(data) {
       
  1152 			result.html(data).removeClass('aktt_login_result_wait');
       
  1153 			setTimeout('akttTestLoginResult();', 5000);
       
  1154 		}
       
  1155 	);
       
  1156 };
       
  1157 
       
  1158 function akttTestLoginResult() {
       
  1159 	jQuery('#aktt_login_test_result').fadeOut('slow');
       
  1160 };
       
  1161 
       
  1162 (function($){
       
  1163 
       
  1164 	jQuery.fn.timepicker = function(){
       
  1165 	
       
  1166 		var hrs = new Array();
       
  1167 		for(var h = 1; h <= 12; hrs.push(h++));
       
  1168 
       
  1169 		var mins = new Array();
       
  1170 		for(var m = 0; m < 60; mins.push(m++));
       
  1171 
       
  1172 		var ap = new Array('am', 'pm');
       
  1173 
       
  1174 		function pad(n) {
       
  1175 			n = n.toString();
       
  1176 			return n.length == 1 ? '0' + n : n;
       
  1177 		}
       
  1178 	
       
  1179 		this.each(function() {
       
  1180 
       
  1181 			var v = $(this).val();
       
  1182 			if (!v) v = new Date();
       
  1183 
       
  1184 			var d = new Date(v);
       
  1185 			var h = d.getHours();
       
  1186 			var m = d.getMinutes();
       
  1187 			var p = (h >= 12) ? "pm" : "am";
       
  1188 			h = (h > 12) ? h - 12 : h;
       
  1189 
       
  1190 			var output = '';
       
  1191 
       
  1192 			output += '<select id="h_' + this.id + '" class="timepicker">';				
       
  1193 			for (var hr in hrs){
       
  1194 				output += '<option value="' + pad(hrs[hr]) + '"';
       
  1195 				if(parseInt(hrs[hr], 10) == h || (parseInt(hrs[hr], 10) == 12 && h == 0)) output += ' selected';
       
  1196 				output += '>' + pad(hrs[hr]) + '</option>';
       
  1197 			}
       
  1198 			output += '</select>';
       
  1199 	
       
  1200 			output += '<select id="m_' + this.id + '" class="timepicker">';				
       
  1201 			for (var mn in mins){
       
  1202 				output += '<option value="' + pad(mins[mn]) + '"';
       
  1203 				if(parseInt(mins[mn], 10) == m) output += ' selected';
       
  1204 				output += '>' + pad(mins[mn]) + '</option>';
       
  1205 			}
       
  1206 			output += '</select>';				
       
  1207 	
       
  1208 			output += '<select id="p_' + this.id + '" class="timepicker">';				
       
  1209 			for(var pp in ap){
       
  1210 				output += '<option value="' + ap[pp] + '"';
       
  1211 				if(ap[pp] == p) output += ' selected';
       
  1212 				output += '>' + ap[pp] + '</option>';
       
  1213 			}
       
  1214 			output += '</select>';
       
  1215 			
       
  1216 			$(this).after(output);
       
  1217 			
       
  1218 			var field = this;
       
  1219 			$(this).siblings('select.timepicker').change(function() {
       
  1220 				var h = parseInt($('#h_' + field.id).val(), 10);
       
  1221 				var m = parseInt($('#m_' + field.id).val(), 10);
       
  1222 				var p = $('#p_' + field.id).val();
       
  1223 	
       
  1224 				if (p == "am") {
       
  1225 					if (h == 12) {
       
  1226 						h = 0;
       
  1227 					}
       
  1228 				} else if (p == "pm") {
       
  1229 					if (h < 12) {
       
  1230 						h += 12;
       
  1231 					}
       
  1232 				}
       
  1233 				
       
  1234 				var d = new Date();
       
  1235 				d.setHours(h);
       
  1236 				d.setMinutes(m);
       
  1237 				
       
  1238 				$(field).val(d.toUTCString());
       
  1239 			}).change();
       
  1240 
       
  1241 		});
       
  1242 
       
  1243 		return this;
       
  1244 	};
       
  1245 	
       
  1246 	jQuery.fn.daypicker = function() {
       
  1247 		
       
  1248 		var days = new Array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
       
  1249 		
       
  1250 		this.each(function() {
       
  1251 			
       
  1252 			var v = $(this).val();
       
  1253 			if (!v) v = 0;
       
  1254 			v = parseInt(v, 10);
       
  1255 			
       
  1256 			var output = "";
       
  1257 			output += '<select id="d_' + this.id + '" class="daypicker">';				
       
  1258 			for (var i = 0; i < days.length; i++) {
       
  1259 				output += '<option value="' + i + '"';
       
  1260 				if (v == i) output += ' selected';
       
  1261 				output += '>' + days[i] + '</option>';
       
  1262 			}
       
  1263 			output += '</select>';
       
  1264 			
       
  1265 			$(this).after(output);
       
  1266 			
       
  1267 			var field = this;
       
  1268 			$(this).siblings('select.daypicker').change(function() {
       
  1269 				$(field).val( $(this).val() );
       
  1270 			}).change();
       
  1271 		
       
  1272 		});
       
  1273 		
       
  1274 	};
       
  1275 	
       
  1276 	jQuery.fn.forceToggleClass = function(classNames, bOn) {
       
  1277 		return this.each(function() {
       
  1278 			jQuery(this)[ bOn ? "addClass" : "removeClass" ](classNames);
       
  1279 		});
       
  1280 	};
       
  1281 	
       
  1282 })(jQuery);
       
  1283 
       
  1284 jQuery(function() {
       
  1285 
       
  1286 	// add in the time and day selects
       
  1287 	jQuery('form#ak_twittertools input.time').timepicker();
       
  1288 	jQuery('form#ak_twittertools input.day').daypicker();
       
  1289 	
       
  1290 	// togglers
       
  1291 	jQuery('.time_toggle .toggler').change(function() {
       
  1292 		var theSelect = jQuery(this);
       
  1293 		theSelect.parent('.time_toggle').forceToggleClass('active', theSelect.val() === "1");
       
  1294 	}).change();
       
  1295 	
       
  1296 });
       
  1297 <?php
       
  1298 						break;
       
  1299 					case 'prototype':
       
  1300 ?>
       
  1301 function akttTestLogin() {
       
  1302 	var username = $('aktt_twitter_username').value;
       
  1303 	var password = $('aktt_twitter_password').value;
       
  1304 	var result = $('aktt_login_test_result');
       
  1305 	result.className = 'aktt_login_result_wait';
       
  1306 	result.innerHTML = '<?php _e('Testing...', 'twitter-tools'); ?>';
       
  1307 	var akttAjax = new Ajax.Updater(
       
  1308 		result,
       
  1309 		"<?php bloginfo('wpurl'); ?>/index.php",
       
  1310 		{
       
  1311 			method: "post",
       
  1312 			parameters: "ak_action=aktt_login_test&aktt_twitter_username=" + username + "&aktt_twitter_password=" + password,
       
  1313 			onComplete: akttTestLoginResult
       
  1314 		}
       
  1315 	);
       
  1316 }
       
  1317 function akttTestLoginResult() {
       
  1318 	$('aktt_login_test_result').className = 'aktt_login_result';
       
  1319 	Fat.fade_element('aktt_login_test_result');
       
  1320 }
       
  1321 <?php
       
  1322 						break;
       
  1323 				}
       
  1324 				die();
       
  1325 				break;
       
  1326 			case 'aktt_css_admin':
       
  1327 				remove_action('shutdown', 'aktt_ping_digests');
       
  1328 				header("Content-Type: text/css");
       
  1329 ?>
       
  1330 #aktt_tweet_form {
       
  1331 	margin: 0;
       
  1332 	padding: 5px 0;
       
  1333 }
       
  1334 #aktt_tweet_form fieldset {
       
  1335 	border: 0;
       
  1336 }
       
  1337 #aktt_tweet_form fieldset textarea {
       
  1338 	width: 95%;
       
  1339 }
       
  1340 #aktt_tweet_form fieldset #aktt_tweet_submit {
       
  1341 	float: right;
       
  1342 	margin-right: 50px;
       
  1343 }
       
  1344 #aktt_tweet_form fieldset #aktt_char_count {
       
  1345 	color: #666;
       
  1346 }
       
  1347 #ak_readme {
       
  1348 	height: 300px;
       
  1349 	width: 95%;
       
  1350 }
       
  1351 #ak_twittertools .options {
       
  1352 	overflow: hidden;
       
  1353 	border: none;
       
  1354 }
       
  1355 #ak_twittertools .option {
       
  1356 	overflow: hidden;
       
  1357 	padding-bottom: 9px;
       
  1358 	padding-top: 9px;
       
  1359 }
       
  1360 #ak_twittertools .option label {
       
  1361 	display: block;
       
  1362 	float: left;
       
  1363 	width: 200px;
       
  1364 	margin-right: 24px;
       
  1365 	text-align: right;
       
  1366 }
       
  1367 #ak_twittertools .option span {
       
  1368 	display: block;
       
  1369 	float: left;
       
  1370 	margin-left: 230px;
       
  1371 	margin-top: 6px;
       
  1372 	clear: left;
       
  1373 }
       
  1374 #ak_twittertools select,
       
  1375 #ak_twittertools input {
       
  1376 	float: left;
       
  1377 	display: block;
       
  1378 	margin-right: 6px;
       
  1379 }
       
  1380 #ak_twittertools p.submit {
       
  1381 	overflow: hidden;
       
  1382 }
       
  1383 #ak_twittertools .option span {
       
  1384 	color: #666;
       
  1385 	display: block;
       
  1386 }
       
  1387 #ak_twittertools #aktt_login_test_result {
       
  1388 	display: inline;
       
  1389 	padding: 3px;
       
  1390 }
       
  1391 #ak_twittertools fieldset.options .option span.aktt_login_result_wait {
       
  1392 	background: #ffc;
       
  1393 }
       
  1394 #ak_twittertools fieldset.options .option span.aktt_login_result {
       
  1395 	background: #CFEBF7;
       
  1396 	color: #000;
       
  1397 }
       
  1398 #ak_twittertools .timepicker,
       
  1399 #ak_twittertools .daypicker {
       
  1400 	display: none;
       
  1401 }
       
  1402 #ak_twittertools .active .timepicker,
       
  1403 #ak_twittertools .active .daypicker {
       
  1404 	display: block
       
  1405 }
       
  1406 <?php
       
  1407 				die();
       
  1408 				break;
       
  1409 		}
       
  1410 	}
       
  1411 	if (!empty($_POST['ak_action'])) {
       
  1412 		switch($_POST['ak_action']) {
       
  1413 			case 'aktt_update_settings':
       
  1414 				$aktt->populate_settings();
       
  1415 				$aktt->update_settings();
       
  1416 				wp_redirect(get_bloginfo('wpurl').'/wp-admin/options-general.php?page=twitter-tools.php&updated=true');
       
  1417 				die();
       
  1418 				break;
       
  1419 			case 'aktt_post_tweet_sidebar':
       
  1420 				if (!empty($_POST['aktt_tweet_text']) && current_user_can('publish_posts')) {
       
  1421 					$tweet = new aktt_tweet();
       
  1422 					$tweet->tw_text = stripslashes($_POST['aktt_tweet_text']);
       
  1423 					if ($aktt->do_tweet($tweet)) {
       
  1424 						die(__('Tweet posted.', 'twitter-tools'));
       
  1425 					}
       
  1426 					else {
       
  1427 						die(__('Tweet post failed.', 'twitter-tools'));
       
  1428 					}
       
  1429 				}
       
  1430 				break;
       
  1431 			case 'aktt_post_tweet_admin':
       
  1432 				if (!empty($_POST['aktt_tweet_text']) && current_user_can('publish_posts')) {
       
  1433 					$tweet = new aktt_tweet();
       
  1434 					$tweet->tw_text = stripslashes($_POST['aktt_tweet_text']);
       
  1435 					if ($aktt->do_tweet($tweet)) {
       
  1436 						wp_redirect(get_bloginfo('wpurl').'/wp-admin/post-new.php?page=twitter-tools.php&tweet-posted=true');
       
  1437 					}
       
  1438 					else {
       
  1439 						wp_die(__('Oops, your tweet was not posted. Please check your username and password and that Twitter is up and running happily.', 'twitter-tools'));
       
  1440 					}
       
  1441 					die();
       
  1442 				}
       
  1443 				break;
       
  1444 			case 'aktt_login_test':
       
  1445 				$test = @aktt_login_test(
       
  1446 					@stripslashes($_POST['aktt_twitter_username'])
       
  1447 					, @stripslashes($_POST['aktt_twitter_password'])
       
  1448 				);
       
  1449 				die(__($test, 'twitter-tools'));
       
  1450 				break;
       
  1451 		}
       
  1452 	}
       
  1453 }
       
  1454 add_action('init', 'aktt_request_handler', 10);
       
  1455 
       
  1456 function aktt_admin_tweet_form() {
       
  1457 	global $aktt;
       
  1458 	if ( $_GET['tweet-posted'] ) {
       
  1459 		print('
       
  1460 			<div id="message" class="updated fade">
       
  1461 				<p>'.__('Tweet posted.', 'twitter-tools').'</p>
       
  1462 			</div>
       
  1463 		');
       
  1464 	}
       
  1465 	print('
       
  1466 		<div class="wrap" id="aktt_write_tweet">
       
  1467 	');
       
  1468 	if (empty($aktt->twitter_username) || empty($aktt->twitter_password)) {
       
  1469 		print('
       
  1470 			<p>'.__('Please enter your <a href="http://twitter.com">Twitter</a> account information in your <a href="options-general.php?page=twitter-tools.php">Twitter Tools Options</a>.', 'twitter-tools').'</p>		
       
  1471 		');
       
  1472 	}
       
  1473 	else {
       
  1474 		print('
       
  1475 			<h2>'.__('Write Tweet', 'twitter-tools').'</h2>
       
  1476 			<p>'.__('This will create a new \'tweet\' in <a href="http://twitter.com">Twitter</a> using the account information in your <a href="options-general.php?page=twitter-tools.php">Twitter Tools Options</a>.', 'twitter-tools').'</p>
       
  1477 			'.aktt_tweet_form('textarea').'
       
  1478 		');
       
  1479 	}
       
  1480 	print('
       
  1481 		</div>
       
  1482 	');
       
  1483 }
       
  1484 
       
  1485 function aktt_options_form() {
       
  1486 	global $wpdb, $aktt;
       
  1487 
       
  1488 	$categories = get_categories('hide_empty=0');
       
  1489 	$cat_options = '';
       
  1490 	foreach ($categories as $category) {
       
  1491 // WP < 2.3 compatibility
       
  1492 		!empty($category->term_id) ? $cat_id = $category->term_id : $cat_id = $category->cat_ID;
       
  1493 		!empty($category->name) ? $cat_name = $category->name : $cat_name = $category->cat_name;
       
  1494 		if ($cat_id == $aktt->blog_post_category) {
       
  1495 			$selected = 'selected="selected"';
       
  1496 		}
       
  1497 		else {
       
  1498 			$selected = '';
       
  1499 		}
       
  1500 		$cat_options .= "\n\t<option value='$cat_id' $selected>$cat_name</option>";
       
  1501 	}
       
  1502 
       
  1503 	$authors = get_users_of_blog();
       
  1504 	$author_options = '';
       
  1505 	foreach ($authors as $user) {
       
  1506 		$usero = new WP_User($user->user_id);
       
  1507 		$author = $usero->data;
       
  1508 		// Only list users who are allowed to publish
       
  1509 		if (! $usero->has_cap('publish_posts')) {
       
  1510 			continue;
       
  1511 		}
       
  1512 		if ($author->ID == $aktt->blog_post_author) {
       
  1513 			$selected = 'selected="selected"';
       
  1514 		}
       
  1515 		else {
       
  1516 			$selected = '';
       
  1517 		}
       
  1518 		$author_options .= "\n\t<option value='$author->ID' $selected>$author->user_nicename</option>";
       
  1519 	}
       
  1520 	
       
  1521 	$js_libs = array(
       
  1522 		'jquery' => 'jQuery'
       
  1523 		, 'prototype' => 'Prototype'
       
  1524 	);
       
  1525 	$js_lib_options = '';
       
  1526 	foreach ($js_libs as $js_lib => $js_lib_display) {
       
  1527 		if ($js_lib == $aktt->js_lib) {
       
  1528 			$selected = 'selected="selected"';
       
  1529 		}
       
  1530 		else {
       
  1531 			$selected = '';
       
  1532 		}
       
  1533 		$js_lib_options .= "\n\t<option value='$js_lib' $selected>$js_lib_display</option>";
       
  1534 	}
       
  1535 	$digest_tweet_orders = array(
       
  1536 		'ASC' => __('Oldest first (Chronological order)', 'twitter-tools'),
       
  1537 		'DESC' => __('Newest first (Reverse-chronological order)', 'twitter-tools')
       
  1538 	);
       
  1539 	$digest_tweet_order_options = '';
       
  1540 	foreach ($digest_tweet_orders as $digest_tweet_order => $digest_tweet_order_display) {
       
  1541 		if ($digest_tweet_order == $aktt->digest_tweet_order) {
       
  1542 			$selected = 'selected="selected"';
       
  1543 		}
       
  1544 		else {
       
  1545 			$selected = '';
       
  1546 		}
       
  1547 		$digest_tweet_order_options .= "\n\t<option value='$digest_tweet_order' $selected>$digest_tweet_order_display</option>";
       
  1548 	}	
       
  1549 	$yes_no = array(
       
  1550 		'create_blog_posts'
       
  1551 		, 'create_digest'
       
  1552 		, 'create_digest_weekly'
       
  1553 		, 'notify_twitter'
       
  1554 		, 'notify_twitter_default'
       
  1555 		, 'tweet_from_sidebar'
       
  1556 		, 'give_tt_credit'
       
  1557 		, 'exclude_reply_tweets'
       
  1558 	);
       
  1559 	foreach ($yes_no as $key) {
       
  1560 		$var = $key.'_options';
       
  1561 		if ($aktt->$key == '0') {
       
  1562 			$$var = '
       
  1563 				<option value="0" selected="selected">'.__('No', 'twitter-tools').'</option>
       
  1564 				<option value="1">'.__('Yes', 'twitter-tools').'</option>
       
  1565 			';
       
  1566 		}
       
  1567 		else {
       
  1568 			$$var = '
       
  1569 				<option value="0">'.__('No', 'twitter-tools').'</option>
       
  1570 				<option value="1" selected="selected">'.__('Yes', 'twitter-tools').'</option>
       
  1571 			';
       
  1572 		}
       
  1573 	}
       
  1574 	if ( $_GET['tweets-updated'] ) {
       
  1575 		print('
       
  1576 			<div id="message" class="updated fade">
       
  1577 				<p>'.__('Tweets updated.', 'twitter-tools').'</p>
       
  1578 			</div>
       
  1579 		');
       
  1580 	}
       
  1581 	if ( $_GET['tweet-checking-reset'] ) {
       
  1582 		print('
       
  1583 			<div id="message" class="updated fade">
       
  1584 				<p>'.__('Tweet checking has been reset.', 'twitter-tools').'</p>
       
  1585 			</div>
       
  1586 		');
       
  1587 	}
       
  1588 	print('
       
  1589 			<div class="wrap" id="aktt_options_page">
       
  1590 				<h2>'.__('Twitter Tools Options', 'twitter-tools').'</h2>
       
  1591 				<form id="ak_twittertools" name="ak_twittertools" action="'.get_bloginfo('wpurl').'/wp-admin/options-general.php" method="post">
       
  1592 					<fieldset class="options">
       
  1593 						<div class="option">
       
  1594 							<label for="aktt_twitter_username">'.__('Twitter Username', 'twitter-tools').'/'.__('Password', 'twitter-tools').'</label>
       
  1595 							<input type="text" size="25" name="aktt_twitter_username" id="aktt_twitter_username" value="'.$aktt->twitter_username.'" autocomplete="off" />
       
  1596 							<input type="password" size="25" name="aktt_twitter_password" id="aktt_twitter_password" value="'.$aktt->twitter_password.'" autocomplete="off" />
       
  1597 							<input type="button" class="button" name="aktt_login_test" id="aktt_login_test" value="'.__('Test Login Info', 'twitter-tools').'" onclick="akttTestLogin(); return false;" />
       
  1598 							<span id="aktt_login_test_result"></span>
       
  1599 						</div>
       
  1600 						<div class="option">
       
  1601 							<label for="aktt_notify_twitter">'.__('Enable option to create a tweet when you post in your blog?', 'twitter-tools').'</label>
       
  1602 							<select name="aktt_notify_twitter" id="aktt_notify_twitter">'.$notify_twitter_options.'</select>
       
  1603 						</div>
       
  1604 						<div class="option">
       
  1605 							<label for="aktt_tweet_prefix">'.__('Tweet prefix for new blog posts:', 'twitter-tools').'</label>
       
  1606 							<input type="text" size="30" name="aktt_tweet_prefix" id="aktt_tweet_prefix" value="'.$aktt->tweet_prefix.'" /><span>'.__('Cannot be left blank. Will result in <b>{Your prefix}: Title URL</b>', 'twitter-tools').'</span>
       
  1607 						</div>
       
  1608 						<div class="option">
       
  1609 							<label for="aktt_notify_twitter_default">'.__('Set this on by default?', 'twitter-tools').'</label>
       
  1610 							<select name="aktt_notify_twitter_default" id="aktt_notify_twitter_default">'.$notify_twitter_default_options.'</select><span>'							.__('Also determines tweeting for posting via XML-RPC', 'twitter-tools').'</span>
       
  1611 						</div>
       
  1612 						<div class="option">
       
  1613 							<label for="aktt_create_blog_posts">'.__('Create a blog post from each of your tweets?', 'twitter-tools').'</label>
       
  1614 							<select name="aktt_create_blog_posts" id="aktt_create_blog_posts">'.$create_blog_posts_options.'</select>
       
  1615 						</div>
       
  1616 						<div class="option time_toggle">
       
  1617 							<label>'.__('Create a daily digest blog post from your tweets?', 'twitter-tools').'</label>
       
  1618 							<select name="aktt_create_digest" class="toggler">'.$create_digest_options.'</select>
       
  1619 							<input type="hidden" class="time" id="aktt_digest_daily_time" name="aktt_digest_daily_time" value="'.$aktt->digest_daily_time.'" />
       
  1620 						</div>
       
  1621 						<div class="option">
       
  1622 							<label for="aktt_digest_title">'.__('Title for daily digest posts:', 'twitter-tools').'</label>
       
  1623 							<input type="text" size="30" name="aktt_digest_title" id="aktt_digest_title" value="'.$aktt->digest_title.'" />
       
  1624 							<span>'.__('Include %s where you want the date. Example: Tweets on %s', 'twitter-tools').'</span>
       
  1625 						</div>
       
  1626 						<div class="option time_toggle">
       
  1627 							<label>'.__('Create a weekly digest blog post from your tweets?', 'twitter-tools').'</label>
       
  1628 							<select name="aktt_create_digest_weekly" class="toggler">'.$create_digest_weekly_options.'</select>
       
  1629 							<input type="hidden" class="time" name="aktt_digest_weekly_time" id="aktt_digest_weekly_time" value="'.$aktt->digest_weekly_time.'" />
       
  1630 							<input type="hidden" class="day" name="aktt_digest_weekly_day" value="'.$aktt->digest_weekly_day.'" />
       
  1631 						</div>
       
  1632 						<div class="option">
       
  1633 							<label for="aktt_digest_title_weekly">'.__('Title for weekly digest posts:', 'twitter-tools').'</label>
       
  1634 							<input type="text" size="30" name="aktt_digest_title_weekly" id="aktt_digest_title_weekly" value="'.$aktt->digest_title_weekly.'" />
       
  1635 							<span>'.__('Include %s where you want the date. Example: Tweets on %s', 'twitter-tools').'</span>
       
  1636 						</div>
       
  1637 						<div class="option">
       
  1638 							<label for="aktt_digest_tweet_order">'.__('Order of tweets in digest?', 'twitter-tools').'</label>
       
  1639 							<select name="aktt_digest_tweet_order" id="aktt_digest_tweet_order">'.$digest_tweet_order_options.'</select>
       
  1640 						</div>
       
  1641 						<div class="option">
       
  1642 							<label for="aktt_blog_post_category">'.__('Category for tweet posts:', 'twitter-tools').'</label>
       
  1643 							<select name="aktt_blog_post_category" id="aktt_blog_post_category">'.$cat_options.'</select>
       
  1644 						</div>
       
  1645 						<div class="option">
       
  1646 							<label for="aktt_blog_post_tags">'.__('Tag(s) for your tweet posts:', 'twitter-tools').'</label>
       
  1647 							<input name="aktt_blog_post_tags" id="aktt_blog_post_tags" value="'.$aktt->blog_post_tags.'">
       
  1648 							<span>'.__('Separate multiple tags with commas. Example: tweets, twitter', 'twitter-tools').'</span>
       
  1649 						</div>
       
  1650 						<div class="option">
       
  1651 							<label for="aktt_blog_post_author">'.__('Author for tweet posts:', 'twitter-tools').'</label>
       
  1652 							<select name="aktt_blog_post_author" id="aktt_blog_post_author">'.$author_options.'</select>
       
  1653 						</div>
       
  1654 						<div class="option">
       
  1655 							<label for="aktt_exclude_reply_tweets">'.__('Exclude @reply tweets in your sidebar, digests and created blog posts?', 'twitter-tools').'</label>
       
  1656 							<select name="aktt_exclude_reply_tweets" id="aktt_exclude_reply_tweets">'.$exclude_reply_tweets_options.'</select>
       
  1657 						</div>
       
  1658 						<div class="option">
       
  1659 							<label for="aktt_sidebar_tweet_count">'.__('Tweets to show in sidebar:', 'twitter-tools').'</label>
       
  1660 							<input type="text" size="3" name="aktt_sidebar_tweet_count" id="aktt_sidebar_tweet_count" value="'.$aktt->sidebar_tweet_count.'" />
       
  1661 							<span>'.__('Numbers only please.', 'twitter-tools').'</span>
       
  1662 						</div>
       
  1663 						<div class="option">
       
  1664 							<label for="aktt_tweet_from_sidebar">'.__('Create tweets from your sidebar?', 'twitter-tools').'</label>
       
  1665 							<select name="aktt_tweet_from_sidebar" id="aktt_tweet_from_sidebar">'.$tweet_from_sidebar_options.'</select>
       
  1666 						</div>
       
  1667 						<div class="option">
       
  1668 							<label for="aktt_js_lib">'.__('JS Library to use?', 'twitter-tools').'</label>
       
  1669 							<select name="aktt_js_lib" id="aktt_js_lib">'.$js_lib_options.'</select>
       
  1670 						</div>
       
  1671 						<div class="option">
       
  1672 							<label for="aktt_give_tt_credit">'.__('Give Twitter Tools credit?', 'twitter-tools').'</label>
       
  1673 							<select name="aktt_give_tt_credit" id="aktt_give_tt_credit">'.$give_tt_credit_options.'</select>
       
  1674 						</div>
       
  1675 					</fieldset>
       
  1676 					<p class="submit">
       
  1677 						<input type="submit" name="submit" class="button-primary" value="'.__('Update Twitter Tools Options', 'twitter-tools').'" />
       
  1678 					</p>
       
  1679 					<input type="hidden" name="ak_action" value="aktt_update_settings" class="hidden" style="display: none;" />
       
  1680 				</form>
       
  1681 				<h2>'.__('Update Tweets / Reset Checking and Digests', 'twitter-tools').'</h2>
       
  1682 				<form name="ak_twittertools_updatetweets" action="'.get_bloginfo('wpurl').'/wp-admin/options-general.php" method="get">
       
  1683 					<p>'.__('Use these buttons to manually update your tweets or reset the checking settings.', 'twitter-tools').'</p>
       
  1684 					<p class="submit">
       
  1685 						<input type="submit" name="submit-button" value="'.__('Update Tweets', 'twitter-tools').'" />
       
  1686 						<input type="submit" name="reset-button-1" value="'.__('Reset Tweet Checking', 'twitter-tools').'" onclick="document.getElementById(\'ak_action_2\').value = \'aktt_reset_tweet_checking\';" />
       
  1687 						<input type="submit" name="reset-button-2" value="'.__('Reset Digests', 'twitter-tools').'" onclick="document.getElementById(\'ak_action_2\').value = \'aktt_reset_digests\';" />
       
  1688 						<input type="hidden" name="ak_action" id="ak_action_2" value="aktt_update_tweets" />
       
  1689 					</p>
       
  1690 				</form>
       
  1691 	');
       
  1692 	do_action('aktt_options_form');
       
  1693 	print('
       
  1694 				
       
  1695 				<h2>'.__('README', 'twitter-tools').'</h2>
       
  1696 				<p>'.__('Find answers to common questions here.', 'twitter-tools').'</p>
       
  1697 				<iframe id="ak_readme" src="http://alexking.org/projects/wordpress/readme?project=twitter-tools"></iframe>
       
  1698 			</div>
       
  1699 	');
       
  1700 }
       
  1701 
       
  1702 function aktt_post_options() {
       
  1703 	global $aktt, $post;
       
  1704 	if ($aktt->notify_twitter) {
       
  1705 		echo '<div class="postbox">
       
  1706 			<h3>'.__('Twitter Tools', 'twitter-tools').'</h3>
       
  1707 			<div class="inside">
       
  1708 			<p>'.__('Notify Twitter about this post?', 'twitter-tools');
       
  1709 		$notify = get_post_meta($post->ID, 'aktt_notify_twitter', true);
       
  1710 		if ($notify == '') {
       
  1711 			switch ($aktt->notify_twitter_default) {
       
  1712 				case '1':
       
  1713 					$notify = 'yes';
       
  1714 					break;
       
  1715 				case '0':
       
  1716 					$notify = 'no';
       
  1717 					break;
       
  1718 			}
       
  1719 		}
       
  1720 		if ($notify == 'no') {
       
  1721 			$yes = '';
       
  1722 			$no = 'checked="checked"';
       
  1723 		}
       
  1724 		else {
       
  1725 			$yes = 'checked="checked"';
       
  1726 			$no = '';
       
  1727 		}
       
  1728 		echo '
       
  1729 		<input type="radio" name="aktt_notify_twitter" id="aktt_notify_twitter_yes" value="yes" '.$yes.' /> <label for="aktt_notify_twitter_yes">'.__('Yes', 'twitter-tools').'</label> &nbsp;&nbsp;
       
  1730 		<input type="radio" name="aktt_notify_twitter" id="aktt_notify_twitter_no" value="no" '.$no.' /> <label for="aktt_notify_twitter_no">'.__('No', 'twitter-tools').'</label>
       
  1731 		';
       
  1732 		echo '
       
  1733 			</p>
       
  1734 		';
       
  1735 		do_action('aktt_post_options');
       
  1736 		echo '
       
  1737 			</div><!--.inside-->
       
  1738 			</div><!--.postbox-->
       
  1739 		';
       
  1740 	}
       
  1741 }
       
  1742 add_action('edit_form_advanced', 'aktt_post_options');
       
  1743 
       
  1744 function aktt_store_post_options($post_id, $post = false) {
       
  1745 	global $aktt;
       
  1746 	$post = get_post($post_id);
       
  1747 	if (!$post || $post->post_type == 'revision') {
       
  1748 		return;
       
  1749 	}
       
  1750 
       
  1751 	$notify_meta = get_post_meta($post_id, 'aktt_notify_twitter', true);
       
  1752 	$posted_meta = $_POST['aktt_notify_twitter'];
       
  1753 
       
  1754 	$save = false;
       
  1755 	if (!empty($posted_meta)) {
       
  1756 		$posted_meta == 'yes' ? $meta = 'yes' : $meta = 'no';
       
  1757 		$save = true;
       
  1758 	}
       
  1759 	else if (empty($notify_meta)) {
       
  1760 		$aktt->notify_twitter_default ? $meta = 'yes' : $meta = 'no';
       
  1761 		$save = true;
       
  1762 	}
       
  1763 	else {
       
  1764 		$save = false;
       
  1765 	}
       
  1766 	
       
  1767 	if ($save) {
       
  1768 		if (!update_post_meta($post_id, 'aktt_notify_twitter', $meta)) {
       
  1769 			add_post_meta($post_id, 'aktt_notify_twitter', $meta);
       
  1770 		}
       
  1771 	}
       
  1772 }
       
  1773 add_action('draft_post', 'aktt_store_post_options', 1, 2);
       
  1774 add_action('publish_post', 'aktt_store_post_options', 1, 2);
       
  1775 add_action('save_post', 'aktt_store_post_options', 1, 2);
       
  1776 
       
  1777 function aktt_menu_items() {
       
  1778 	if (current_user_can('manage_options')) {
       
  1779 		add_options_page(
       
  1780 			__('Twitter Tools Options', 'twitter-tools')
       
  1781 			, __('Twitter Tools', 'twitter-tools')
       
  1782 			, 10
       
  1783 			, basename(__FILE__)
       
  1784 			, 'aktt_options_form'
       
  1785 		);
       
  1786 	}
       
  1787 	if (current_user_can('publish_posts')) {
       
  1788 		add_submenu_page(
       
  1789 			'post-new.php'
       
  1790 			, __('New Tweet', 'twitter-tools')
       
  1791 			, __('Tweet', 'twitter-tools')
       
  1792 			, 2
       
  1793 			, basename(__FILE__)
       
  1794 			, 'aktt_admin_tweet_form'
       
  1795 		);
       
  1796 	}
       
  1797 }
       
  1798 add_action('admin_menu', 'aktt_menu_items');
       
  1799 
       
  1800 function aktt_plugin_action_links($links, $file) {
       
  1801 	$plugin_file = basename(__FILE__);
       
  1802 	if (basename($file) == $plugin_file) {
       
  1803 		$settings_link = '<a href="options-general.php?page='.$plugin_file.'">'.__('Settings', 'twitter-tools').'</a>';
       
  1804 		array_unshift($links, $settings_link);
       
  1805 	}
       
  1806 	return $links;
       
  1807 }
       
  1808 add_filter('plugin_action_links', 'aktt_plugin_action_links', 10, 2);
       
  1809 
       
  1810 if (!function_exists('trim_add_elipsis')) {
       
  1811 	function trim_add_elipsis($string, $limit = 100) {
       
  1812 		if (strlen($string) > $limit) {
       
  1813 			$string = substr($string, 0, $limit)."...";
       
  1814 		}
       
  1815 		return $string;
       
  1816 	}
       
  1817 }
       
  1818 
       
  1819 if (!function_exists('ak_gmmktime')) {
       
  1820 	function ak_gmmktime() {
       
  1821 		return gmmktime() - get_option('gmt_offset') * 3600;
       
  1822 	}
       
  1823 }
       
  1824 
       
  1825 /**
       
  1826 
       
  1827 based on: http://www.gyford.com/phil/writing/2006/12/02/quick_twitter.php
       
  1828 
       
  1829 	 * Returns a relative date, eg "4 hrs ago".
       
  1830 	 *
       
  1831 	 * Assumes the passed-in can be parsed by strtotime.
       
  1832 	 * Precision could be one of:
       
  1833 	 * 	1	5 hours, 3 minutes, 2 seconds ago (not yet implemented).
       
  1834 	 * 	2	5 hours, 3 minutes
       
  1835 	 * 	3	5 hours
       
  1836 	 *
       
  1837 	 * This is all a little overkill, but copied from other places I've used it.
       
  1838 	 * Also superfluous, now I've noticed that the Twitter API includes something
       
  1839 	 * similar, but this version is more accurate and less verbose.
       
  1840 	 *
       
  1841 	 * @access private.
       
  1842 	 * @param string date In a format parseable by strtotime().
       
  1843 	 * @param integer precision
       
  1844 	 * @return string
       
  1845 	 */
       
  1846 function aktt_relativeTime ($date, $precision=2)
       
  1847 {
       
  1848 
       
  1849 	$now = time();
       
  1850 
       
  1851 	$time = gmmktime(
       
  1852 		substr($date, 11, 2)
       
  1853 		, substr($date, 14, 2)
       
  1854 		, substr($date, 17, 2)
       
  1855 		, substr($date, 5, 2)
       
  1856 		, substr($date, 8, 2)
       
  1857 		, substr($date, 0, 4)
       
  1858 	);
       
  1859 
       
  1860 	$time = strtotime(date('Y-m-d H:i:s', $time));
       
  1861 
       
  1862 	$diff 	=  $now - $time;
       
  1863 
       
  1864 	$months	=  floor($diff/2419200);
       
  1865 	$diff 	-= $months * 2419200;
       
  1866 	$weeks 	=  floor($diff/604800);
       
  1867 	$diff	-= $weeks*604800;
       
  1868 	$days 	=  floor($diff/86400);
       
  1869 	$diff 	-= $days * 86400;
       
  1870 	$hours 	=  floor($diff/3600);
       
  1871 	$diff 	-= $hours * 3600;
       
  1872 	$minutes = floor($diff/60);
       
  1873 	$diff 	-= $minutes * 60;
       
  1874 	$seconds = $diff;
       
  1875 
       
  1876 	if ($months > 0) {
       
  1877 		return date_i18n( __('Y-m-d', 'twitter-tools'), $time);
       
  1878 	} else {
       
  1879 		$relative_date = '';
       
  1880 		if ($weeks > 0) {
       
  1881 			// Weeks and days
       
  1882 			$relative_date .= ($relative_date?', ':'').$weeks.' '.__ngettext('week', 'weeks', $weeks, 'twitter-tools');
       
  1883 			if ($precision <= 2) {
       
  1884 				$relative_date .= $days>0? ($relative_date?', ':'').$days.' '.__ngettext('day', 'days', $days, 'twitter-tools'):'';
       
  1885 				if ($precision == 1) {
       
  1886 					$relative_date .= $hours>0?($relative_date?', ':'').$hours.' '.__ngettext('hr', 'hrs', $hours, 'twitter-tools'):'';
       
  1887 				}
       
  1888 			}
       
  1889 		} elseif ($days > 0) {
       
  1890 			// days and hours
       
  1891 			$relative_date .= ($relative_date?', ':'').$days.' '.__ngettext('day', 'days', $days, 'twitter-tools');
       
  1892 			if ($precision <= 2) {
       
  1893 				$relative_date .= $hours>0?($relative_date?', ':'').$hours.' '.__ngettext('hr', 'hrs', $hours, 'twitter-tools'):'';
       
  1894 				if ($precision == 1) {
       
  1895 					$relative_date .= $minutes>0?($relative_date?', ':'').$minutes.' '.__ngettext('min', 'mins', $minutes, 'twitter-tools'):'';
       
  1896 				}
       
  1897 			}
       
  1898 		} elseif ($hours > 0) {
       
  1899 			// hours and minutes
       
  1900 			$relative_date .= ($relative_date?', ':'').$hours.' '.__ngettext('hr', 'hrs', $hours, 'twitter-tools');
       
  1901 			if ($precision <= 2) {
       
  1902 				$relative_date .= $minutes>0?($relative_date?', ':'').$minutes.' '.__ngettext('min', 'mins', $minutes, 'twitter-tools'):'';
       
  1903 				if ($precision == 1) {
       
  1904 					$relative_date .= $seconds>0?($relative_date?', ':'').$seconds.' '.__ngettext('sec', 'secs', $seconds, 'twitter-tools'):'';
       
  1905 				}
       
  1906 			}
       
  1907 		} elseif ($minutes > 0) {
       
  1908 			// minutes only
       
  1909 			$relative_date .= ($relative_date?', ':'').$minutes.' '.__ngettext('min', 'mins', $minutes, 'twitter-tools');
       
  1910 			if ($precision == 1) {
       
  1911 				$relative_date .= $seconds>0?($relative_date?', ':'').$seconds.' '.__ngettext('sec', 'secs', $seconds, 'twitter-tools'):'';
       
  1912 			}
       
  1913 		} else {
       
  1914 			// seconds only
       
  1915 			$relative_date .= ($relative_date?', ':'').$seconds.' '.__ngettext('sec', 'secs', $seconds, 'twitter-tools');
       
  1916 		}
       
  1917 	}
       
  1918 
       
  1919 	// Return relative date and add proper verbiage
       
  1920 	return sprintf(__('%s ago', 'twitter-tools'), $relative_date);
       
  1921 }
       
  1922 if (!class_exists('Services_JSON')) {
       
  1923 
       
  1924 // PEAR JSON class
       
  1925 
       
  1926 /**
       
  1927 * Converts to and from JSON format.
       
  1928 *
       
  1929 * JSON (JavaScript Object Notation) is a lightweight data-interchange
       
  1930 * format. It is easy for humans to read and write. It is easy for machines
       
  1931 * to parse and generate. It is based on a subset of the JavaScript
       
  1932 * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
       
  1933 * This feature can also be found in  Python. JSON is a text format that is
       
  1934 * completely language independent but uses conventions that are familiar
       
  1935 * to programmers of the C-family of languages, including C, C++, C#, Java,
       
  1936 * JavaScript, Perl, TCL, and many others. These properties make JSON an
       
  1937 * ideal data-interchange language.
       
  1938 *
       
  1939 * This package provides a simple encoder and decoder for JSON notation. It
       
  1940 * is intended for use with client-side Javascript applications that make
       
  1941 * use of HTTPRequest to perform server communication functions - data can
       
  1942 * be encoded into JSON notation for use in a client-side javascript, or
       
  1943 * decoded from incoming Javascript requests. JSON format is native to
       
  1944 * Javascript, and can be directly eval()'ed with no further parsing
       
  1945 * overhead
       
  1946 *
       
  1947 * All strings should be in ASCII or UTF-8 format!
       
  1948 *
       
  1949 * LICENSE: Redistribution and use in source and binary forms, with or
       
  1950 * without modification, are permitted provided that the following
       
  1951 * conditions are met: Redistributions of source code must retain the
       
  1952 * above copyright notice, this list of conditions and the following
       
  1953 * disclaimer. Redistributions in binary form must reproduce the above
       
  1954 * copyright notice, this list of conditions and the following disclaimer
       
  1955 * in the documentation and/or other materials provided with the
       
  1956 * distribution.
       
  1957 *
       
  1958 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
       
  1959 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
       
  1960 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
       
  1961 * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
       
  1962 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
       
  1963 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
       
  1964 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
       
  1965 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
       
  1966 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
       
  1967 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
       
  1968 * DAMAGE.
       
  1969 *
       
  1970 * @category
       
  1971 * @package     Services_JSON
       
  1972 * @author      Michal Migurski <mike-json@teczno.com>
       
  1973 * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
       
  1974 * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
       
  1975 * @copyright   2005 Michal Migurski
       
  1976 * @version     CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
       
  1977 * @license     http://www.opensource.org/licenses/bsd-license.php
       
  1978 * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
       
  1979 */
       
  1980 
       
  1981 /**
       
  1982 * Marker constant for Services_JSON::decode(), used to flag stack state
       
  1983 */
       
  1984 define('SERVICES_JSON_SLICE',   1);
       
  1985 
       
  1986 /**
       
  1987 * Marker constant for Services_JSON::decode(), used to flag stack state
       
  1988 */
       
  1989 define('SERVICES_JSON_IN_STR',  2);
       
  1990 
       
  1991 /**
       
  1992 * Marker constant for Services_JSON::decode(), used to flag stack state
       
  1993 */
       
  1994 define('SERVICES_JSON_IN_ARR',  3);
       
  1995 
       
  1996 /**
       
  1997 * Marker constant for Services_JSON::decode(), used to flag stack state
       
  1998 */
       
  1999 define('SERVICES_JSON_IN_OBJ',  4);
       
  2000 
       
  2001 /**
       
  2002 * Marker constant for Services_JSON::decode(), used to flag stack state
       
  2003 */
       
  2004 define('SERVICES_JSON_IN_CMT', 5);
       
  2005 
       
  2006 /**
       
  2007 * Behavior switch for Services_JSON::decode()
       
  2008 */
       
  2009 define('SERVICES_JSON_LOOSE_TYPE', 16);
       
  2010 
       
  2011 /**
       
  2012 * Behavior switch for Services_JSON::decode()
       
  2013 */
       
  2014 define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
       
  2015 
       
  2016 /**
       
  2017 * Converts to and from JSON format.
       
  2018 *
       
  2019 * Brief example of use:
       
  2020 *
       
  2021 * <code>
       
  2022 * // create a new instance of Services_JSON
       
  2023 * $json = new Services_JSON();
       
  2024 *
       
  2025 * // convert a complexe value to JSON notation, and send it to the browser
       
  2026 * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
       
  2027 * $output = $json->encode($value);
       
  2028 *
       
  2029 * print($output);
       
  2030 * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
       
  2031 *
       
  2032 * // accept incoming POST data, assumed to be in JSON notation
       
  2033 * $input = file_get_contents('php://input', 1000000);
       
  2034 * $value = $json->decode($input);
       
  2035 * </code>
       
  2036 */
       
  2037 class Services_JSON
       
  2038 {
       
  2039    /**
       
  2040     * constructs a new JSON instance
       
  2041     *
       
  2042     * @param    int     $use    object behavior flags; combine with boolean-OR
       
  2043     *
       
  2044     *                           possible values:
       
  2045     *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
       
  2046     *                                   "{...}" syntax creates associative arrays
       
  2047     *                                   instead of objects in decode().
       
  2048     *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
       
  2049     *                                   Values which can't be encoded (e.g. resources)
       
  2050     *                                   appear as NULL instead of throwing errors.
       
  2051     *                                   By default, a deeply-nested resource will
       
  2052     *                                   bubble up with an error, so all return values
       
  2053     *                                   from encode() should be checked with isError()
       
  2054     */
       
  2055     function Services_JSON($use = 0)
       
  2056     {
       
  2057         $this->use = $use;
       
  2058     }
       
  2059 
       
  2060    /**
       
  2061     * convert a string from one UTF-16 char to one UTF-8 char
       
  2062     *
       
  2063     * Normally should be handled by mb_convert_encoding, but
       
  2064     * provides a slower PHP-only method for installations
       
  2065     * that lack the multibye string extension.
       
  2066     *
       
  2067     * @param    string  $utf16  UTF-16 character
       
  2068     * @return   string  UTF-8 character
       
  2069     * @access   private
       
  2070     */
       
  2071     function utf162utf8($utf16)
       
  2072     {
       
  2073         // oh please oh please oh please oh please oh please
       
  2074         if(function_exists('mb_convert_encoding')) {
       
  2075             return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
       
  2076         }
       
  2077 
       
  2078         $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
       
  2079 
       
  2080         switch(true) {
       
  2081             case ((0x7F & $bytes) == $bytes):
       
  2082                 // this case should never be reached, because we are in ASCII range
       
  2083                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2084                 return chr(0x7F & $bytes);
       
  2085 
       
  2086             case (0x07FF & $bytes) == $bytes:
       
  2087                 // return a 2-byte UTF-8 character
       
  2088                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2089                 return chr(0xC0 | (($bytes >> 6) & 0x1F))
       
  2090                      . chr(0x80 | ($bytes & 0x3F));
       
  2091 
       
  2092             case (0xFFFF & $bytes) == $bytes:
       
  2093                 // return a 3-byte UTF-8 character
       
  2094                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2095                 return chr(0xE0 | (($bytes >> 12) & 0x0F))
       
  2096                      . chr(0x80 | (($bytes >> 6) & 0x3F))
       
  2097                      . chr(0x80 | ($bytes & 0x3F));
       
  2098         }
       
  2099 
       
  2100         // ignoring UTF-32 for now, sorry
       
  2101         return '';
       
  2102     }
       
  2103 
       
  2104    /**
       
  2105     * convert a string from one UTF-8 char to one UTF-16 char
       
  2106     *
       
  2107     * Normally should be handled by mb_convert_encoding, but
       
  2108     * provides a slower PHP-only method for installations
       
  2109     * that lack the multibye string extension.
       
  2110     *
       
  2111     * @param    string  $utf8   UTF-8 character
       
  2112     * @return   string  UTF-16 character
       
  2113     * @access   private
       
  2114     */
       
  2115     function utf82utf16($utf8)
       
  2116     {
       
  2117         // oh please oh please oh please oh please oh please
       
  2118         if(function_exists('mb_convert_encoding')) {
       
  2119             return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
       
  2120         }
       
  2121 
       
  2122         switch(strlen($utf8)) {
       
  2123             case 1:
       
  2124                 // this case should never be reached, because we are in ASCII range
       
  2125                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2126                 return $utf8;
       
  2127 
       
  2128             case 2:
       
  2129                 // return a UTF-16 character from a 2-byte UTF-8 char
       
  2130                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2131                 return chr(0x07 & (ord($utf8{0}) >> 2))
       
  2132                      . chr((0xC0 & (ord($utf8{0}) << 6))
       
  2133                          | (0x3F & ord($utf8{1})));
       
  2134 
       
  2135             case 3:
       
  2136                 // return a UTF-16 character from a 3-byte UTF-8 char
       
  2137                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2138                 return chr((0xF0 & (ord($utf8{0}) << 4))
       
  2139                          | (0x0F & (ord($utf8{1}) >> 2)))
       
  2140                      . chr((0xC0 & (ord($utf8{1}) << 6))
       
  2141                          | (0x7F & ord($utf8{2})));
       
  2142         }
       
  2143 
       
  2144         // ignoring UTF-32 for now, sorry
       
  2145         return '';
       
  2146     }
       
  2147 
       
  2148    /**
       
  2149     * encodes an arbitrary variable into JSON format
       
  2150     *
       
  2151     * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
       
  2152     *                           see argument 1 to Services_JSON() above for array-parsing behavior.
       
  2153     *                           if var is a strng, note that encode() always expects it
       
  2154     *                           to be in ASCII or UTF-8 format!
       
  2155     *
       
  2156     * @return   mixed   JSON string representation of input var or an error if a problem occurs
       
  2157     * @access   public
       
  2158     */
       
  2159     function encode($var)
       
  2160     {
       
  2161         switch (gettype($var)) {
       
  2162             case 'boolean':
       
  2163                 return $var ? 'true' : 'false';
       
  2164 
       
  2165             case 'NULL':
       
  2166                 return 'null';
       
  2167 
       
  2168             case 'integer':
       
  2169                 return (int) $var;
       
  2170 
       
  2171             case 'double':
       
  2172             case 'float':
       
  2173                 return (float) $var;
       
  2174 
       
  2175             case 'string':
       
  2176                 // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
       
  2177                 $ascii = '';
       
  2178                 $strlen_var = strlen($var);
       
  2179 
       
  2180                /*
       
  2181                 * Iterate over every character in the string,
       
  2182                 * escaping with a slash or encoding to UTF-8 where necessary
       
  2183                 */
       
  2184                 for ($c = 0; $c < $strlen_var; ++$c) {
       
  2185 
       
  2186                     $ord_var_c = ord($var{$c});
       
  2187 
       
  2188                     switch (true) {
       
  2189                         case $ord_var_c == 0x08:
       
  2190                             $ascii .= '\b';
       
  2191                             break;
       
  2192                         case $ord_var_c == 0x09:
       
  2193                             $ascii .= '\t';
       
  2194                             break;
       
  2195                         case $ord_var_c == 0x0A:
       
  2196                             $ascii .= '\n';
       
  2197                             break;
       
  2198                         case $ord_var_c == 0x0C:
       
  2199                             $ascii .= '\f';
       
  2200                             break;
       
  2201                         case $ord_var_c == 0x0D:
       
  2202                             $ascii .= '\r';
       
  2203                             break;
       
  2204 
       
  2205                         case $ord_var_c == 0x22:
       
  2206                         case $ord_var_c == 0x2F:
       
  2207                         case $ord_var_c == 0x5C:
       
  2208                             // double quote, slash, slosh
       
  2209                             $ascii .= '\\'.$var{$c};
       
  2210                             break;
       
  2211 
       
  2212                         case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
       
  2213                             // characters U-00000000 - U-0000007F (same as ASCII)
       
  2214                             $ascii .= $var{$c};
       
  2215                             break;
       
  2216 
       
  2217                         case (($ord_var_c & 0xE0) == 0xC0):
       
  2218                             // characters U-00000080 - U-000007FF, mask 110XXXXX
       
  2219                             // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2220                             $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
       
  2221                             $c += 1;
       
  2222                             $utf16 = $this->utf82utf16($char);
       
  2223                             $ascii .= sprintf('\u%04s', bin2hex($utf16));
       
  2224                             break;
       
  2225 
       
  2226                         case (($ord_var_c & 0xF0) == 0xE0):
       
  2227                             // characters U-00000800 - U-0000FFFF, mask 1110XXXX
       
  2228                             // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2229                             $char = pack('C*', $ord_var_c,
       
  2230                                          ord($var{$c + 1}),
       
  2231                                          ord($var{$c + 2}));
       
  2232                             $c += 2;
       
  2233                             $utf16 = $this->utf82utf16($char);
       
  2234                             $ascii .= sprintf('\u%04s', bin2hex($utf16));
       
  2235                             break;
       
  2236 
       
  2237                         case (($ord_var_c & 0xF8) == 0xF0):
       
  2238                             // characters U-00010000 - U-001FFFFF, mask 11110XXX
       
  2239                             // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2240                             $char = pack('C*', $ord_var_c,
       
  2241                                          ord($var{$c + 1}),
       
  2242                                          ord($var{$c + 2}),
       
  2243                                          ord($var{$c + 3}));
       
  2244                             $c += 3;
       
  2245                             $utf16 = $this->utf82utf16($char);
       
  2246                             $ascii .= sprintf('\u%04s', bin2hex($utf16));
       
  2247                             break;
       
  2248 
       
  2249                         case (($ord_var_c & 0xFC) == 0xF8):
       
  2250                             // characters U-00200000 - U-03FFFFFF, mask 111110XX
       
  2251                             // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2252                             $char = pack('C*', $ord_var_c,
       
  2253                                          ord($var{$c + 1}),
       
  2254                                          ord($var{$c + 2}),
       
  2255                                          ord($var{$c + 3}),
       
  2256                                          ord($var{$c + 4}));
       
  2257                             $c += 4;
       
  2258                             $utf16 = $this->utf82utf16($char);
       
  2259                             $ascii .= sprintf('\u%04s', bin2hex($utf16));
       
  2260                             break;
       
  2261 
       
  2262                         case (($ord_var_c & 0xFE) == 0xFC):
       
  2263                             // characters U-04000000 - U-7FFFFFFF, mask 1111110X
       
  2264                             // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2265                             $char = pack('C*', $ord_var_c,
       
  2266                                          ord($var{$c + 1}),
       
  2267                                          ord($var{$c + 2}),
       
  2268                                          ord($var{$c + 3}),
       
  2269                                          ord($var{$c + 4}),
       
  2270                                          ord($var{$c + 5}));
       
  2271                             $c += 5;
       
  2272                             $utf16 = $this->utf82utf16($char);
       
  2273                             $ascii .= sprintf('\u%04s', bin2hex($utf16));
       
  2274                             break;
       
  2275                     }
       
  2276                 }
       
  2277 
       
  2278                 return '"'.$ascii.'"';
       
  2279 
       
  2280             case 'array':
       
  2281                /*
       
  2282                 * As per JSON spec if any array key is not an integer
       
  2283                 * we must treat the the whole array as an object. We
       
  2284                 * also try to catch a sparsely populated associative
       
  2285                 * array with numeric keys here because some JS engines
       
  2286                 * will create an array with empty indexes up to
       
  2287                 * max_index which can cause memory issues and because
       
  2288                 * the keys, which may be relevant, will be remapped
       
  2289                 * otherwise.
       
  2290                 *
       
  2291                 * As per the ECMA and JSON specification an object may
       
  2292                 * have any string as a property. Unfortunately due to
       
  2293                 * a hole in the ECMA specification if the key is a
       
  2294                 * ECMA reserved word or starts with a digit the
       
  2295                 * parameter is only accessible using ECMAScript's
       
  2296                 * bracket notation.
       
  2297                 */
       
  2298 
       
  2299                 // treat as a JSON object
       
  2300                 if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
       
  2301                     $properties = array_map(array($this, 'name_value'),
       
  2302                                             array_keys($var),
       
  2303                                             array_values($var));
       
  2304 
       
  2305                     foreach($properties as $property) {
       
  2306                         if(Services_JSON::isError($property)) {
       
  2307                             return $property;
       
  2308                         }
       
  2309                     }
       
  2310 
       
  2311                     return '{' . join(',', $properties) . '}';
       
  2312                 }
       
  2313 
       
  2314                 // treat it like a regular array
       
  2315                 $elements = array_map(array($this, 'encode'), $var);
       
  2316 
       
  2317                 foreach($elements as $element) {
       
  2318                     if(Services_JSON::isError($element)) {
       
  2319                         return $element;
       
  2320                     }
       
  2321                 }
       
  2322 
       
  2323                 return '[' . join(',', $elements) . ']';
       
  2324 
       
  2325             case 'object':
       
  2326                 $vars = get_object_vars($var);
       
  2327 
       
  2328                 $properties = array_map(array($this, 'name_value'),
       
  2329                                         array_keys($vars),
       
  2330                                         array_values($vars));
       
  2331 
       
  2332                 foreach($properties as $property) {
       
  2333                     if(Services_JSON::isError($property)) {
       
  2334                         return $property;
       
  2335                     }
       
  2336                 }
       
  2337 
       
  2338                 return '{' . join(',', $properties) . '}';
       
  2339 
       
  2340             default:
       
  2341                 return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
       
  2342                     ? 'null'
       
  2343                     : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
       
  2344         }
       
  2345     }
       
  2346 
       
  2347    /**
       
  2348     * array-walking function for use in generating JSON-formatted name-value pairs
       
  2349     *
       
  2350     * @param    string  $name   name of key to use
       
  2351     * @param    mixed   $value  reference to an array element to be encoded
       
  2352     *
       
  2353     * @return   string  JSON-formatted name-value pair, like '"name":value'
       
  2354     * @access   private
       
  2355     */
       
  2356     function name_value($name, $value)
       
  2357     {
       
  2358         $encoded_value = $this->encode($value);
       
  2359 
       
  2360         if(Services_JSON::isError($encoded_value)) {
       
  2361             return $encoded_value;
       
  2362         }
       
  2363 
       
  2364         return $this->encode(strval($name)) . ':' . $encoded_value;
       
  2365     }
       
  2366 
       
  2367    /**
       
  2368     * reduce a string by removing leading and trailing comments and whitespace
       
  2369     *
       
  2370     * @param    $str    string      string value to strip of comments and whitespace
       
  2371     *
       
  2372     * @return   string  string value stripped of comments and whitespace
       
  2373     * @access   private
       
  2374     */
       
  2375     function reduce_string($str)
       
  2376     {
       
  2377         $str = preg_replace(array(
       
  2378 
       
  2379                 // eliminate single line comments in '// ...' form
       
  2380                 '#^\s*//(.+)$#m',
       
  2381 
       
  2382                 // eliminate multi-line comments in '/* ... */' form, at start of string
       
  2383                 '#^\s*/\*(.+)\*/#Us',
       
  2384 
       
  2385                 // eliminate multi-line comments in '/* ... */' form, at end of string
       
  2386                 '#/\*(.+)\*/\s*$#Us'
       
  2387 
       
  2388             ), '', $str);
       
  2389 
       
  2390         // eliminate extraneous space
       
  2391         return trim($str);
       
  2392     }
       
  2393 
       
  2394    /**
       
  2395     * decodes a JSON string into appropriate variable
       
  2396     *
       
  2397     * @param    string  $str    JSON-formatted string
       
  2398     *
       
  2399     * @return   mixed   number, boolean, string, array, or object
       
  2400     *                   corresponding to given JSON input string.
       
  2401     *                   See argument 1 to Services_JSON() above for object-output behavior.
       
  2402     *                   Note that decode() always returns strings
       
  2403     *                   in ASCII or UTF-8 format!
       
  2404     * @access   public
       
  2405     */
       
  2406     function decode($str)
       
  2407     {
       
  2408         $str = $this->reduce_string($str);
       
  2409 
       
  2410         switch (strtolower($str)) {
       
  2411             case 'true':
       
  2412                 return true;
       
  2413 
       
  2414             case 'false':
       
  2415                 return false;
       
  2416 
       
  2417             case 'null':
       
  2418                 return null;
       
  2419 
       
  2420             default:
       
  2421                 $m = array();
       
  2422 
       
  2423                 if (is_numeric($str)) {
       
  2424                     // Lookie-loo, it's a number
       
  2425 
       
  2426                     // This would work on its own, but I'm trying to be
       
  2427                     // good about returning integers where appropriate:
       
  2428                     // return (float)$str;
       
  2429 
       
  2430                     // Return float or int, as appropriate
       
  2431                     return ((float)$str == (integer)$str)
       
  2432                         ? (integer)$str
       
  2433                         : (float)$str;
       
  2434 
       
  2435                 } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
       
  2436                     // STRINGS RETURNED IN UTF-8 FORMAT
       
  2437                     $delim = substr($str, 0, 1);
       
  2438                     $chrs = substr($str, 1, -1);
       
  2439                     $utf8 = '';
       
  2440                     $strlen_chrs = strlen($chrs);
       
  2441 
       
  2442                     for ($c = 0; $c < $strlen_chrs; ++$c) {
       
  2443 
       
  2444                         $substr_chrs_c_2 = substr($chrs, $c, 2);
       
  2445                         $ord_chrs_c = ord($chrs{$c});
       
  2446 
       
  2447                         switch (true) {
       
  2448                             case $substr_chrs_c_2 == '\b':
       
  2449                                 $utf8 .= chr(0x08);
       
  2450                                 ++$c;
       
  2451                                 break;
       
  2452                             case $substr_chrs_c_2 == '\t':
       
  2453                                 $utf8 .= chr(0x09);
       
  2454                                 ++$c;
       
  2455                                 break;
       
  2456                             case $substr_chrs_c_2 == '\n':
       
  2457                                 $utf8 .= chr(0x0A);
       
  2458                                 ++$c;
       
  2459                                 break;
       
  2460                             case $substr_chrs_c_2 == '\f':
       
  2461                                 $utf8 .= chr(0x0C);
       
  2462                                 ++$c;
       
  2463                                 break;
       
  2464                             case $substr_chrs_c_2 == '\r':
       
  2465                                 $utf8 .= chr(0x0D);
       
  2466                                 ++$c;
       
  2467                                 break;
       
  2468 
       
  2469                             case $substr_chrs_c_2 == '\\"':
       
  2470                             case $substr_chrs_c_2 == '\\\'':
       
  2471                             case $substr_chrs_c_2 == '\\\\':
       
  2472                             case $substr_chrs_c_2 == '\\/':
       
  2473                                 if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
       
  2474                                    ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
       
  2475                                     $utf8 .= $chrs{++$c};
       
  2476                                 }
       
  2477                                 break;
       
  2478 
       
  2479                             case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
       
  2480                                 // single, escaped unicode character
       
  2481                                 $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
       
  2482                                        . chr(hexdec(substr($chrs, ($c + 4), 2)));
       
  2483                                 $utf8 .= $this->utf162utf8($utf16);
       
  2484                                 $c += 5;
       
  2485                                 break;
       
  2486 
       
  2487                             case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
       
  2488                                 $utf8 .= $chrs{$c};
       
  2489                                 break;
       
  2490 
       
  2491                             case ($ord_chrs_c & 0xE0) == 0xC0:
       
  2492                                 // characters U-00000080 - U-000007FF, mask 110XXXXX
       
  2493                                 //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2494                                 $utf8 .= substr($chrs, $c, 2);
       
  2495                                 ++$c;
       
  2496                                 break;
       
  2497 
       
  2498                             case ($ord_chrs_c & 0xF0) == 0xE0:
       
  2499                                 // characters U-00000800 - U-0000FFFF, mask 1110XXXX
       
  2500                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2501                                 $utf8 .= substr($chrs, $c, 3);
       
  2502                                 $c += 2;
       
  2503                                 break;
       
  2504 
       
  2505                             case ($ord_chrs_c & 0xF8) == 0xF0:
       
  2506                                 // characters U-00010000 - U-001FFFFF, mask 11110XXX
       
  2507                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2508                                 $utf8 .= substr($chrs, $c, 4);
       
  2509                                 $c += 3;
       
  2510                                 break;
       
  2511 
       
  2512                             case ($ord_chrs_c & 0xFC) == 0xF8:
       
  2513                                 // characters U-00200000 - U-03FFFFFF, mask 111110XX
       
  2514                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2515                                 $utf8 .= substr($chrs, $c, 5);
       
  2516                                 $c += 4;
       
  2517                                 break;
       
  2518 
       
  2519                             case ($ord_chrs_c & 0xFE) == 0xFC:
       
  2520                                 // characters U-04000000 - U-7FFFFFFF, mask 1111110X
       
  2521                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
       
  2522                                 $utf8 .= substr($chrs, $c, 6);
       
  2523                                 $c += 5;
       
  2524                                 break;
       
  2525 
       
  2526                         }
       
  2527 
       
  2528                     }
       
  2529 
       
  2530                     return $utf8;
       
  2531 
       
  2532                 } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
       
  2533                     // array, or object notation
       
  2534 
       
  2535                     if ($str{0} == '[') {
       
  2536                         $stk = array(SERVICES_JSON_IN_ARR);
       
  2537                         $arr = array();
       
  2538                     } else {
       
  2539                         if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
       
  2540                             $stk = array(SERVICES_JSON_IN_OBJ);
       
  2541                             $obj = array();
       
  2542                         } else {
       
  2543                             $stk = array(SERVICES_JSON_IN_OBJ);
       
  2544                             $obj = new stdClass();
       
  2545                         }
       
  2546                     }
       
  2547 
       
  2548                     array_push($stk, array('what'  => SERVICES_JSON_SLICE,
       
  2549                                            'where' => 0,
       
  2550                                            'delim' => false));
       
  2551 
       
  2552                     $chrs = substr($str, 1, -1);
       
  2553                     $chrs = $this->reduce_string($chrs);
       
  2554 
       
  2555                     if ($chrs == '') {
       
  2556                         if (reset($stk) == SERVICES_JSON_IN_ARR) {
       
  2557                             return $arr;
       
  2558 
       
  2559                         } else {
       
  2560                             return $obj;
       
  2561 
       
  2562                         }
       
  2563                     }
       
  2564 
       
  2565                     //print("\nparsing {$chrs}\n");
       
  2566 
       
  2567                     $strlen_chrs = strlen($chrs);
       
  2568 
       
  2569                     for ($c = 0; $c <= $strlen_chrs; ++$c) {
       
  2570 
       
  2571                         $top = end($stk);
       
  2572                         $substr_chrs_c_2 = substr($chrs, $c, 2);
       
  2573 
       
  2574                         if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
       
  2575                             // found a comma that is not inside a string, array, etc.,
       
  2576                             // OR we've reached the end of the character list
       
  2577                             $slice = substr($chrs, $top['where'], ($c - $top['where']));
       
  2578                             array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
       
  2579                             //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
       
  2580 
       
  2581                             if (reset($stk) == SERVICES_JSON_IN_ARR) {
       
  2582                                 // we are in an array, so just push an element onto the stack
       
  2583                                 array_push($arr, $this->decode($slice));
       
  2584 
       
  2585                             } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
       
  2586                                 // we are in an object, so figure
       
  2587                                 // out the property name and set an
       
  2588                                 // element in an associative array,
       
  2589                                 // for now
       
  2590                                 $parts = array();
       
  2591                                 
       
  2592                                 if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
       
  2593                                     // "name":value pair
       
  2594                                     $key = $this->decode($parts[1]);
       
  2595                                     $val = $this->decode($parts[2]);
       
  2596 
       
  2597                                     if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
       
  2598                                         $obj[$key] = $val;
       
  2599                                     } else {
       
  2600                                         $obj->$key = $val;
       
  2601                                     }
       
  2602                                 } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
       
  2603                                     // name:value pair, where name is unquoted
       
  2604                                     $key = $parts[1];
       
  2605                                     $val = $this->decode($parts[2]);
       
  2606 
       
  2607                                     if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
       
  2608                                         $obj[$key] = $val;
       
  2609                                     } else {
       
  2610                                         $obj->$key = $val;
       
  2611                                     }
       
  2612                                 }
       
  2613 
       
  2614                             }
       
  2615 
       
  2616                         } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
       
  2617                             // found a quote, and we are not inside a string
       
  2618                             array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
       
  2619                             //print("Found start of string at {$c}\n");
       
  2620 
       
  2621                         } elseif (($chrs{$c} == $top['delim']) &&
       
  2622                                  ($top['what'] == SERVICES_JSON_IN_STR) &&
       
  2623                                  ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
       
  2624                             // found a quote, we're in a string, and it's not escaped
       
  2625                             // we know that it's not escaped becase there is _not_ an
       
  2626                             // odd number of backslashes at the end of the string so far
       
  2627                             array_pop($stk);
       
  2628                             //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
       
  2629 
       
  2630                         } elseif (($chrs{$c} == '[') &&
       
  2631                                  in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
       
  2632                             // found a left-bracket, and we are in an array, object, or slice
       
  2633                             array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
       
  2634                             //print("Found start of array at {$c}\n");
       
  2635 
       
  2636                         } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
       
  2637                             // found a right-bracket, and we're in an array
       
  2638                             array_pop($stk);
       
  2639                             //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
       
  2640 
       
  2641                         } elseif (($chrs{$c} == '{') &&
       
  2642                                  in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
       
  2643                             // found a left-brace, and we are in an array, object, or slice
       
  2644                             array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
       
  2645                             //print("Found start of object at {$c}\n");
       
  2646 
       
  2647                         } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
       
  2648                             // found a right-brace, and we're in an object
       
  2649                             array_pop($stk);
       
  2650                             //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
       
  2651 
       
  2652                         } elseif (($substr_chrs_c_2 == '/*') &&
       
  2653                                  in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
       
  2654                             // found a comment start, and we are in an array, object, or slice
       
  2655                             array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
       
  2656                             $c++;
       
  2657                             //print("Found start of comment at {$c}\n");
       
  2658 
       
  2659                         } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
       
  2660                             // found a comment end, and we're in one now
       
  2661                             array_pop($stk);
       
  2662                             $c++;
       
  2663 
       
  2664                             for ($i = $top['where']; $i <= $c; ++$i)
       
  2665                                 $chrs = substr_replace($chrs, ' ', $i, 1);
       
  2666 
       
  2667                             //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
       
  2668 
       
  2669                         }
       
  2670 
       
  2671                     }
       
  2672 
       
  2673                     if (reset($stk) == SERVICES_JSON_IN_ARR) {
       
  2674                         return $arr;
       
  2675 
       
  2676                     } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
       
  2677                         return $obj;
       
  2678 
       
  2679                     }
       
  2680 
       
  2681                 }
       
  2682         }
       
  2683     }
       
  2684 
       
  2685     /**
       
  2686      * @todo Ultimately, this should just call PEAR::isError()
       
  2687      */
       
  2688     function isError($data, $code = null)
       
  2689     {
       
  2690         if (class_exists('pear')) {
       
  2691             return PEAR::isError($data, $code);
       
  2692         } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
       
  2693                                  is_subclass_of($data, 'services_json_error'))) {
       
  2694             return true;
       
  2695         }
       
  2696 
       
  2697         return false;
       
  2698     }
       
  2699 }
       
  2700 
       
  2701 if (class_exists('PEAR_Error')) {
       
  2702 
       
  2703     class Services_JSON_Error extends PEAR_Error
       
  2704     {
       
  2705         function Services_JSON_Error($message = 'unknown error', $code = null,
       
  2706                                      $mode = null, $options = null, $userinfo = null)
       
  2707         {
       
  2708             parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
       
  2709         }
       
  2710     }
       
  2711 
       
  2712 } else {
       
  2713 
       
  2714     /**
       
  2715      * @todo Ultimately, this class shall be descended from PEAR_Error
       
  2716      */
       
  2717     class Services_JSON_Error
       
  2718     {
       
  2719         function Services_JSON_Error($message = 'unknown error', $code = null,
       
  2720                                      $mode = null, $options = null, $userinfo = null)
       
  2721         {
       
  2722 
       
  2723         }
       
  2724     }
       
  2725 
       
  2726 }
       
  2727 
       
  2728 }
       
  2729 
       
  2730 ?>