Twitter. Bring your tweets into your blog and pass your blog posts to Twitter. Configure your settings here. Version: 1.0 Author: Alex King Author URI: http://alexking.org Updated: Category problem fixed for Wordpress 2.5 and above Dug Stokes - http://www.monkeyboi.com/ */ // Copyright (c) 2007 Alex King. All rights reserved. // // Released under the GPL license // http://www.opensource.org/licenses/gpl-license.php // // This is an add-on for WordPress // http://wordpress.org/ // // Thanks to John Ford ( http://www.aldenta.com ) for his contributions. // // ********************************************************************** // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // ********************************************************************** // ini_set('display_errors', '1'); // ini_set('error_reporting', E_ALL); load_plugin_textdomain('twitter-tools'); if (!function_exists('is_admin_page')) { function is_admin_page() { if (function_exists('is_admin')) { return is_admin(); } if (function_exists('check_admin_referer')) { return true; } else { return false; } } } class twitter_tools { function twitter_tools() { $this->options = array( 'twitter_username' , 'twitter_password' , 'create_blog_posts' , 'create_digest' , 'digest_title' , 'blog_post_category' , 'notify_twitter' , 'sidebar_tweet_count' , 'tweet_from_sidebar' , 'give_tt_credit' , 'last_tweet_download' , 'doing_tweet_download' , 'doing_digest_post' ); $this->twitter_username = ''; $this->twitter_password = ''; $this->create_blog_posts = '0'; $this->create_digest = '0'; $this->digest_title = __("Twitter Updates for %s", 'twitter-tools'); $this->blog_post_category = '1'; $this->notify_twitter = '0'; $this->sidebar_tweet_count = '3'; $this->tweet_from_sidebar = '1'; $this->give_tt_credit = '1'; // not included in options $this->update_hash = ''; $this->tweet_prefix = 'New blog post'; $this->tweet_format = $this->tweet_prefix.': %s %s'; $this->last_digest_post = ''; $this->last_tweet_download = ''; $this->doing_tweet_download = '0'; $this->doing_digest_post = '0'; $this->version = '1.0'; } function install() { global $wpdb; $result = $wpdb->query(" CREATE TABLE `$wpdb->aktt` ( `id` INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , `tw_id` VARCHAR( 255 ) NOT NULL , `tw_text` VARCHAR( 255 ) NOT NULL , `tw_created_at` DATETIME NOT NULL , `modified` DATETIME NOT NULL , INDEX ( `tw_id` ) ) "); foreach ($this->options as $option) { add_option('aktt_'.$option, $this->$option); } add_option('aktt_update_hash', ''); } function get_settings() { foreach ($this->options as $option) { $this->$option = get_option('aktt_'.$option); } } function update_settings() { if (current_user_can('manage_options')) { if (get_option('aktt_create_digest') != '1' && $this->create_digest == 1) { $this->initiate_digests(); } $this->sidebar_tweet_count = intval($this->sidebar_tweet_count); if ($this->sidebar_tweet_count == 0) { $this->sidebar_tweet_count = '3'; } foreach ($this->options as $option) { update_option('aktt_'.$option, $this->$option); } } } function populate_settings() { foreach ($this->options as $option) { if (isset($_POST['aktt_'.$option])) { $this->$option = stripslashes($_POST['aktt_'.$option]); } } } function initiate_digests() { $this->last_digest_post = date('Y-m-d 00:00:00', strtotime('-1 day')); if (get_option('aktt_last_digest_post') == '') { add_option('aktt_last_digest_post', $this->last_digest_post); } else { update_option('aktt_last_digest_post', $this->last_digest_post); } } function tweet_download_interval() { return 1800; } function do_tweet($tweet = '') { if (empty($this->twitter_username) || empty($this->twitter_password) || empty($tweet) || empty($tweet->tw_text) ) { return; } require_once(ABSPATH.WPINC.'/class-snoopy.php'); $snoop = new Snoopy; $snoop->agent = 'Twitter Tools http://alexking.org/projects/wordpress'; $snoop->rawheaders = array( 'X-Twitter-Client' => 'Twitter Tools' , 'X-Twitter-Client-Version' => $this->version , 'X-Twitter-Client-URL' => 'http://alexking.org/projects/wordpress/twitter-tools.xml' ); $snoop->user = $this->twitter_username; $snoop->pass = $this->twitter_password; $snoop->submit( 'http://twitter.com/statuses/update.json' , array( 'status' => $tweet->tw_text , 'source' => 'twittertools' ) ); if (strpos($snoop->response_code, '200')) { update_option('aktt_last_tweet_download', strtotime('-28 minutes')); return true; } return false; } function do_blog_post_tweet($post_id = 0) { if ($this->notify_twitter == '0' || $post_id == 0 || get_post_meta($post_id, 'aktt_tweeted', true) == '1' ) { return; } $post = get_post($post_id); $tweet = new aktt_tweet; $tweet->tw_text = sprintf(__($this->tweet_format, 'twitter-tools'), $post->post_title, get_permalink($post_id)); $this->do_tweet($tweet); add_post_meta($post_id, 'aktt_tweeted', '1', true); } function do_tweet_post($tweet) { global $wpdb; remove_action('publish_post', 'aktt_notify_twitter'); $data = array( 'post_content' => $wpdb->escape($tweet->tw_text) , 'post_title' => $wpdb->escape(trim_add_elipsis($tweet->tw_text, 30)) , 'post_date' => get_date_from_gmt(date('Y-m-d H:i:s', $tweet->tw_created_at)) , 'post_category' => array($this->blog_post_category) , 'post_status' => 'publish' ); $post_id = wp_insert_post($data); add_post_meta($post_id, 'aktt_twitter_id', $tweet->tw_id, true); add_action('publish_post', 'aktt_notify_twitter'); } function do_digest_post() { global $wpdb; if ($this->create_digest != '1' || get_option('aktt_doing_digest_post') == '1') { return; } update_option('aktt_doing_digest_post', '1'); remove_action('publish_post', 'aktt_notify_twitter'); $now = ak_gmmktime(); $yesterday = strtotime('-1 day', $now); $last_post = get_option('aktt_last_digest_post'); if ($last_post != date('Y-m-d 00:00:00', $yesterday)) { $days = ceil((strtotime(date('Y-m-d 00:00:00', $yesterday)) - strtotime($last_post)) / (3600 * 24)); } else { $days = 1; } for ($i = 0; $i < $days; $i++) { $n = $days - $i; $digest_day = strtotime('-'.$n.' days', $now); $tweets = $wpdb->get_results(" SELECT * FROM $wpdb->aktt WHERE tw_created_at >= '".date('Y-m-d 00:00:00', $digest_day)."' AND tw_created_at <= '".date('Y-m-d 23:59:59', $digest_day)."' GROUP BY tw_id ORDER BY tw_created_at "); if (count($tweets) > 0) { $tweets_to_post = array(); foreach ($tweets as $data) { $tweet = new aktt_tweet; $tweet->tw_text = $data->tw_text; if (!$tweet->tweet_is_post_notification()) { $tweets_to_post[] = $data; } } if (count($tweets_to_post) > 0) { $content = ''."\n"; if ($this->give_tt_credit == '1') { $content .= '

Powered by Twitter Tools.

'; } $data = array( 'post_content' => $wpdb->escape($content) , 'post_title' => $wpdb->escape(sprintf($this->digest_title, date('Y-m-d', $digest_day))) , 'post_date' => date('Y-m-d 23:59:59', $digest_day) , 'post_category' => array($this->blog_post_category) , 'post_status' => 'publish' ); $post_id = wp_insert_post($data); add_post_meta($post_id, 'aktt_tweeted', '1', true); } } } $this->last_digest_post = date('Y-m-d 00:00:00', $now); update_option('aktt_last_digest_post', $this->last_digest_post); add_action('publish_post', 'aktt_notify_twitter'); update_option('aktt_doing_digest_post', '0'); } } class aktt_tweet { function aktt_tweet( $tw_id = '' , $tw_text = '' , $tw_created_at = '' ) { $this->id = ''; $this->modified = ''; $this->tw_created_at = $tw_created_at; $this->tw_text = $tw_text; $this->tw_id = $tw_id; } function twdate_to_time($date) { $parts = explode(' ', $date); $date = strtotime($parts[1].' '.$parts[2].', '.$parts[5].' '.$parts[3]); return $date; } function tweet_post_exists() { global $wpdb; $test = $wpdb->get_results(" SELECT * FROM $wpdb->postmeta WHERE meta_key = 'aktt_twitter_id' AND meta_value = '$this->tw_id' "); if (count($test) > 0) { return true; } return false; } function tweet_is_post_notification() { global $aktt; if (substr($this->tw_text, 0, strlen($aktt->tweet_prefix)) == $aktt->tweet_prefix) { return true; } return false; } function add() { global $wpdb, $aktt; $wpdb->query(" INSERT INTO $wpdb->aktt ( tw_id , tw_text , tw_created_at , modified ) VALUES ( '".$wpdb->escape($this->tw_id)."' , '".$wpdb->escape($this->tw_text)."' , '".date('Y-m-d H:i:s', $this->tw_created_at)."' , NOW() ) "); do_action('aktt_add_tweet', $this); if ($aktt->create_blog_posts == '1' && !$this->tweet_post_exists() && !$this->tweet_is_post_notification()) { $aktt->do_tweet_post($this); } } } function aktt_login_test($username, $password) { require_once(ABSPATH.WPINC.'/class-snoopy.php'); $snoop = new Snoopy; $snoop->agent = 'Twitter Tools http://alexking.org/projects/wordpress'; $snoop->user = $username; $snoop->pass = $password; $snoop->fetch('http://twitter.com/statuses/user_timeline.json'); if (strpos($snoop->response_code, '200')) { return true; } else { return false; } } function aktt_update_tweets() { // let the last update run for 10 minutes if (time() - intval(get_option('aktt_doing_tweet_download')) < 600) { return; } update_option('aktt_doing_tweet_download', time()); global $wpdb, $aktt; if (empty($aktt->twitter_username) || empty($aktt->twitter_password)) { update_option('aktt_doing_tweet_download', '0'); die(); } require_once(ABSPATH.WPINC.'/class-snoopy.php'); $snoop = new Snoopy; $snoop->agent = 'Twitter Tools http://alexking.org/projects/wordpress'; $snoop->user = $aktt->twitter_username; $snoop->pass = $aktt->twitter_password; $snoop->fetch('http://twitter.com/statuses/user_timeline.json'); if (!strpos($snoop->response_code, '200')) { update_option('aktt_doing_tweet_download', '0'); return; } $data = $snoop->results; $hash = md5($data); if ($hash == get_option('aktt_update_hash')) { update_option('aktt_doing_tweet_download', '0'); return; } $json = new Services_JSON(); $tweets = $json->decode($data); if (is_array($tweets) && count($tweets) > 0) { $tweet_ids = array(); foreach ($tweets as $tweet) { $tweet_ids[] = $wpdb->escape($tweet->id); } $existing_ids = $wpdb->get_col(" SELECT tw_id FROM $wpdb->aktt WHERE tw_id IN ('".implode("', '", $tweet_ids)."') "); $new_tweets = array(); foreach ($tweets as $tw_data) { if (!$existing_ids || !in_array($tw_data->id, $existing_ids)) { $tweet = new aktt_tweet( $tw_data->id , $tw_data->text ); $tweet->tw_created_at = $tweet->twdate_to_time($tw_data->created_at); $new_tweets[] = $tweet; } } foreach ($new_tweets as $tweet) { $tweet->add(); } } update_option('aktt_update_hash', $hash); update_option('aktt_last_tweet_download', time()); update_option('aktt_doing_tweet_download', '0'); if ($aktt->create_digest == '1' && strtotime(get_option('aktt_last_digest_post')) < strtotime(date('Y-m-d 00:00:00', ak_gmmktime()))) { $aktt->do_digest_post(); } } function aktt_notify_twitter($post_id) { global $aktt; $aktt->do_blog_post_tweet($post_id); } add_action('publish_post', 'aktt_notify_twitter'); function aktt_sidebar_tweets() { global $wpdb, $aktt; $tweets = $wpdb->get_results(" SELECT * FROM $wpdb->aktt GROUP BY tw_id ORDER BY tw_created_at DESC LIMIT $aktt->sidebar_tweet_count "); $output = '
'."\n" .' '; if ($aktt->tweet_from_sidebar == '1') { $output .= aktt_tweet_form('input', 'onsubmit="akttPostTweet(); return false;"'); $output .= '

'.__('Posting tweet...', 'twitter-tools').'

'; } if ($aktt->give_tt_credit == '1') { $output .= '

Powered by Twitter Tools.

'; } $output .= '
'; print($output); } function aktt_latest_tweet() { global $wpdb, $aktt; $tweets = $wpdb->get_results(" SELECT * FROM $wpdb->aktt GROUP BY tw_id ORDER BY tw_created_at DESC LIMIT 1 "); if (count($tweets) == 1) { foreach ($tweets as $tweet) { $output = aktt_make_clickable(wp_specialchars($tweet->tw_text)).' '.aktt_relativeTime($tweet->tw_created_at, 3).''; } } else { $output = __('No tweets available at the moment.', 'twitter-tools'); } print($output); } function aktt_make_clickable($tweet) { if (substr($tweet, 0, 1) == '@' && substr($tweet, 1, 1) != ' ') { $space = strpos($tweet, ' '); $username = substr($tweet, 1, $space - 1); $tweet = '@'.$username.''.substr($tweet, $space); } return make_clickable($tweet); } function aktt_tweet_form($type = 'input', $extra = '') { $output = ''; if (current_user_can('publish_posts')) { $output .= '
'; switch ($type) { case 'input': $output .= '

'; break; case 'textarea': $output .= '

'; break; } $output .= '

'; } return $output; } function aktt_widget_init() { if (!function_exists('register_sidebar_widget')) { return; } function aktt_widget($args) { extract($args); $options = get_option('aktt_widget'); $title = $options['title']; if (empty($title)) { } echo $before_widget . $before_title . $title . $after_title; aktt_sidebar_tweets(); echo $after_widget; } register_sidebar_widget(array(__('Twitter Tools', 'twitter-tools'), 'widgets'), 'aktt_widget'); function aktt_widget_control() { $options = get_option('aktt_widget'); if (!is_array($options)) { $options = array( 'title' => __("What I'm Doing...", 'twitter-tools') ); } if (isset($_POST['ak_action']) && $_POST['ak_action'] == 'aktt_update_widget_options') { $options['title'] = strip_tags(stripslashes($_POST['aktt_widget_title'])); update_option('aktt_widget', $options); } // Be sure you format your options to be valid HTML attributes. $title = htmlspecialchars($options['title'], ENT_QUOTES); // Here is our little form segment. Notice that we don't need a // complete form. This will be embedded into the existing form. print('

'.__('Find additional Twitter Tools options on the Twitter Tools Options page.', 'twitter-tools').' '); } register_widget_control(array(__('Twitter Tools', 'twitter-tools'), 'widgets'), 'aktt_widget_control', 300, 100); } add_action('widgets_init', 'aktt_widget_init'); function aktt_init() { global $wpdb, $aktt; $aktt = new twitter_tools; $wpdb->aktt = $wpdb->prefix.'ak_twitter'; if (isset($_GET['activate']) && $_GET['activate'] == 'true') { $tables = $wpdb->get_col(" SHOW TABLES "); if (!in_array($wpdb->aktt, $tables)) { $aktt->install(); } } $aktt->get_settings(); if (($aktt->last_tweet_download + $aktt->tweet_download_interval()) < time()) { add_action('shutdown', 'aktt_update_tweets'); } if (is_admin() || $aktt->tweet_from_sidebar) { wp_enqueue_script('prototype'); } } add_action('init', 'aktt_init'); function aktt_head() { global $aktt; if ($aktt->tweet_from_sidebar) { print(' '); } } add_action('wp_head', 'aktt_head'); function aktt_head_admin() { print(' '); } add_action('admin_head', 'aktt_head_admin'); function aktt_request_handler() { global $wpdb, $aktt; if (!empty($_GET['ak_action'])) { switch($_GET['ak_action']) { case 'aktt_update_tweets': aktt_update_tweets(); header('Location: '.get_bloginfo('wpurl').'/wp-admin/options-general.php?page=twitter-tools.php&tweets-updated=true'); die(); break; case 'aktt_js': header("Content-type: text/javascript"); ?> function akttPostTweet() { var tweet_field = $('aktt_tweet_text'); var tweet_text = tweet_field.value; if (tweet_text == '') { return; } var tweet_msg = $("aktt_tweet_posted_msg"); var akttAjax = new Ajax.Updater( tweet_msg, "/index.php", { method: "post", parameters: "ak_action=aktt_post_tweet_sidebar&aktt_tweet_text=" + tweet_text, onComplete: akttSetReset } ); tweet_field.value = ''; tweet_field.focus(); $('aktt_char_count').innerHTML = ''; tweet_msg.style.display = 'block'; } function akttSetReset() { setTimeout('akttReset();', 2000); } function akttReset() { $('aktt_tweet_posted_msg').style.display = 'none'; } #aktt_tweet_form { margin: 0; padding: 5px 0; } #aktt_tweet_form fieldset { border: 0; } #aktt_tweet_form fieldset #aktt_tweet_submit { float: right; margin-right: 10px; } #aktt_tweet_form fieldset #aktt_char_count { color: #666; } #aktt_tweet_posted_msg { background: #ffc; display: none; margin: 0 0 5px 0; padding: 5px; } #aktt_tweet_form div.clear { clear: both; float: none; } function akttTestLogin() { var username = encodeURIComponent($('aktt_twitter_username').value); var password = encodeURIComponent($('aktt_twitter_password').value); var result = $('aktt_login_test_result'); result.className = 'aktt_login_result_wait'; result.innerHTML = ''; var akttAjax = new Ajax.Updater( result, "/index.php", { method: "post", parameters: "ak_action=aktt_login_test&aktt_twitter_username=" + username + "&aktt_twitter_password=" + password, onComplete: akttTestLoginResult } ); } function akttTestLoginResult() { $('aktt_login_test_result').className = 'aktt_login_result'; Fat.fade_element('aktt_login_test_result'); } #aktt_tweet_form { margin: 0; padding: 5px 0; } #aktt_tweet_form fieldset { border: 0; } #aktt_tweet_form fieldset textarea { width: 95%; } #aktt_tweet_form fieldset #aktt_tweet_submit { float: right; margin-right: 50px; } #aktt_tweet_form fieldset #aktt_char_count { color: #666; } #ak_twittertools fieldset.options p span { color: #666; display: block; } #ak_readme { height: 300px; width: 95%; } #ak_twittertools #aktt_login_test_result { display: inline; padding: 3px; } #ak_twittertools fieldset.options p span.aktt_login_result_wait { background: #ffc; } #ak_twittertools fieldset.options p span.aktt_login_result { background: #CFEBF7; color: #000; } populate_settings(); $aktt->update_settings(); header('Location: '.get_bloginfo('wpurl').'/wp-admin/options-general.php?page=twitter-tools.php&updated=true'); die(); break; case 'aktt_post_tweet_sidebar': if (!empty($_POST['aktt_tweet_text'])) { $tweet = new aktt_tweet(); $tweet->tw_text = stripslashes($_POST['aktt_tweet_text']); if ($aktt->do_tweet($tweet)) { die(__('Tweet posted.', 'twitter-tools')); } else { die(__('Tweet post failed.', 'twitter-tools')); } } break; case 'aktt_post_tweet_admin': if (!empty($_POST['aktt_tweet_text'])) { $tweet = new aktt_tweet(); $tweet->tw_text = stripslashes($_POST['aktt_tweet_text']); if ($aktt->do_tweet($tweet)) { header('Location: '.get_bloginfo('wpurl').'/wp-admin/post-new.php?page=twitter-tools.php&tweet-posted=true'); } else { wp_die(__('Oops, your tweet was not posted. Please check your username and password and that Twitter is up and running happily.', 'twitter-tools')); } die(); } break; case 'aktt_login_test': $test = @aktt_login_test( @stripslashes($_POST['aktt_twitter_username']) , @stripslashes($_POST['aktt_twitter_password']) ); if ($test) { die(__("Login succeeded, you're good to go.", 'twitter-tools')); } else { die(__("Login failed, double-check that username and password.", 'twitter-tools')); } break; } } } add_action('init', 'aktt_request_handler', 10); function aktt_admin_tweet_form() { global $aktt; if ( $_GET['tweet-posted'] ) { print('

'.__('Tweet posted.', 'twitter-tools').'

'); } if (empty($aktt->twitter_username) || empty($aktt->twitter_password)) { print('

Please enter your Twitter account information in your Twitter Tools Options.

'); } print('

'.__('Write Tweet', 'twitter-tools').'

This will create a new \'tweet\' in Twitter using the account information in your Twitter Tools Options.

'.aktt_tweet_form('textarea').'
'); } function aktt_options_form() { global $wpdb, $aktt; $terms = $wpdb->get_results("SELECT * FROM $wpdb->terms ORDER BY name "); $cat_options = ''; foreach ($terms as $category) { if ($category->term_id == $aktt->blog_post_category) { $selected = 'selected="selected"'; } else { $selected = ''; } $cat_options .= "\n\t"; } $yes_no = array( 'create_blog_posts' , 'create_digest' , 'notify_twitter' , 'tweet_from_sidebar' , 'give_tt_credit' ); foreach ($yes_no as $key) { $var = $key.'_options'; if ($aktt->$key == '0') { $$var = ' '; } else { $$var = ' '; } } if ( $_GET['tweets-updated'] ) { print('

'.__('Tweets updated.', 'twitter-tools').'

'); } print('

'.__('Twitter Tools Options', 'twitter-tools').'

'.__('Include %s where you want the date. Example: Tweets on %s', 'twitter-tools').'

'.__('Numbers only please.', 'twitter-tools').'

'.__('Update Tweets', 'twitter-tools').'

'.__('Use this button to manually update your tweets.', 'twitter-tools').'

'.__('README', 'twitter-tools').'

'.__('Find answers to common questions here.', 'twitter-tools').'

'); } function aktt_menu_items() { if (current_user_can('manage_options')) { add_options_page( __('Twitter Tools Options', 'twitter-tools') , __('Twitter Tools', 'twitter-tools') , 10 , basename(__FILE__) , 'aktt_options_form' ); } if (current_user_can('publish_posts')) { add_submenu_page( 'post-new.php' , __('Write Tweet', 'twitter-tools') , __('Write Tweet', 'twitter-tools') , 10 , basename(__FILE__) , 'aktt_admin_tweet_form' ); } } add_action('admin_menu', 'aktt_menu_items'); if (!function_exists('trim_add_elipsis')) { function trim_add_elipsis($string, $limit = 100) { if (strlen($string) > $limit) { $string = substr($string, 0, $limit)."..."; } return $string; } } if (!function_exists('ak_gmmktime')) { function ak_gmmktime() { return gmmktime() - get_option('gmt_offset') * 3600; } } /** based on: http://www.gyford.com/phil/writing/2006/12/02/quick_twitter.php * Returns a relative date, eg "4 hrs ago". * * Assumes the passed-in can be parsed by strtotime. * Precision could be one of: * 1 5 hours, 3 minutes, 2 seconds ago (not yet implemented). * 2 5 hours, 3 minutes * 3 5 hours * * This is all a little overkill, but copied from other places I've used it. * Also superfluous, now I've noticed that the Twitter API includes something * similar, but this version is more accurate and less verbose. * * @access private. * @param string date In a format parseable by strtotime(). * @param integer precision * @return string */ function aktt_relativeTime ($date, $precision=2) { $now = time(); $time = gmmktime( substr($date, 11, 2) , substr($date, 14, 2) , substr($date, 17, 2) , substr($date, 5, 2) , substr($date, 8, 2) , substr($date, 0, 4) ); $time = strtotime(date('Y-m-d H:i:s', $time)); $diff = $now - $time; $months = floor($diff/2419200); $diff -= $months * 2419200; $weeks = floor($diff/604800); $diff -= $weeks*604800; $days = floor($diff/86400); $diff -= $days * 86400; $hours = floor($diff/3600); $diff -= $hours * 3600; $minutes = floor($diff/60); $diff -= $minutes * 60; $seconds = $diff; if ($days > 30) { return date('Y-m-d', $time); } else { $relative_date = ''; if ($weeks > 0) { // Weeks and days $relative_date .= ($relative_date?', ':'').$weeks.' week'.($weeks>1?'s':''); if ($precision <= 2) { $relative_date .= $days>0?($relative_date?', ':'').$days.' day'.($days>1?'s':''):''; if ($precision == 1) { $relative_date .= $hours>0?($relative_date?', ':'').$hours.' hr'.($hours>1?'s':''):''; } } } elseif ($days > 0) { // days and hours $relative_date .= ($relative_date?', ':'').$days.' day'.($days>1?'s':''); if ($precision <= 2) { $relative_date .= $hours>0?($relative_date?', ':'').$hours.' hr'.($hours>1?'s':''):''; if ($precision == 1) { $relative_date .= $minutes>0?($relative_date?', ':'').$minutes.' min'.($minutes>1?'s':''):''; } } } elseif ($hours > 0) { // hours and minutes $relative_date .= ($relative_date?', ':'').$hours.' hr'.($hours>1?'s':''); if ($precision <= 2) { $relative_date .= $minutes>0?($relative_date?', ':'').$minutes.' min'.($minutes>1?'s':''):''; if ($precision == 1) { $relative_date .= $seconds>0?($relative_date?', ':'').$seconds.' sec'.($seconds>1?'s':''):''; } } } elseif ($minutes > 0) { // minutes only $relative_date .= ($relative_date?', ':'').$minutes.' min'.($minutes>1?'s':''); if ($precision == 1) { $relative_date .= $seconds>0?($relative_date?', ':'').$seconds.' sec'.($seconds>1?'s':''):''; } } else { // seconds only $relative_date .= ($relative_date?', ':'').$seconds.' sec'.($seconds>1?'s':''); } } // Return relative date and add proper verbiage return sprintf(__('%s ago', 'twitter-tools'), $relative_date); } if (!class_exists('Services_JSON')) { // PEAR JSON class /** * Converts to and from JSON format. * * JSON (JavaScript Object Notation) is a lightweight data-interchange * format. It is easy for humans to read and write. It is easy for machines * to parse and generate. It is based on a subset of the JavaScript * Programming Language, Standard ECMA-262 3rd Edition - December 1999. * This feature can also be found in Python. JSON is a text format that is * completely language independent but uses conventions that are familiar * to programmers of the C-family of languages, including C, C++, C#, Java, * JavaScript, Perl, TCL, and many others. These properties make JSON an * ideal data-interchange language. * * This package provides a simple encoder and decoder for JSON notation. It * is intended for use with client-side Javascript applications that make * use of HTTPRequest to perform server communication functions - data can * be encoded into JSON notation for use in a client-side javascript, or * decoded from incoming Javascript requests. JSON format is native to * Javascript, and can be directly eval()'ed with no further parsing * overhead * * All strings should be in ASCII or UTF-8 format! * * LICENSE: Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: Redistributions of source code must retain the * above copyright notice, this list of conditions and the following * disclaimer. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * @category * @package Services_JSON * @author Michal Migurski * @author Matt Knapp * @author Brett Stimmerman * @copyright 2005 Michal Migurski * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ * @license http://www.opensource.org/licenses/bsd-license.php * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 */ /** * Marker constant for Services_JSON::decode(), used to flag stack state */ define('SERVICES_JSON_SLICE', 1); /** * Marker constant for Services_JSON::decode(), used to flag stack state */ define('SERVICES_JSON_IN_STR', 2); /** * Marker constant for Services_JSON::decode(), used to flag stack state */ define('SERVICES_JSON_IN_ARR', 3); /** * Marker constant for Services_JSON::decode(), used to flag stack state */ define('SERVICES_JSON_IN_OBJ', 4); /** * Marker constant for Services_JSON::decode(), used to flag stack state */ define('SERVICES_JSON_IN_CMT', 5); /** * Behavior switch for Services_JSON::decode() */ define('SERVICES_JSON_LOOSE_TYPE', 16); /** * Behavior switch for Services_JSON::decode() */ define('SERVICES_JSON_SUPPRESS_ERRORS', 32); /** * Converts to and from JSON format. * * Brief example of use: * * * // create a new instance of Services_JSON * $json = new Services_JSON(); * * // convert a complexe value to JSON notation, and send it to the browser * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); * $output = $json->encode($value); * * print($output); * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] * * // accept incoming POST data, assumed to be in JSON notation * $input = file_get_contents('php://input', 1000000); * $value = $json->decode($input); * */ class Services_JSON { /** * constructs a new JSON instance * * @param int $use object behavior flags; combine with boolean-OR * * possible values: * - SERVICES_JSON_LOOSE_TYPE: loose typing. * "{...}" syntax creates associative arrays * instead of objects in decode(). * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. * Values which can't be encoded (e.g. resources) * appear as NULL instead of throwing errors. * By default, a deeply-nested resource will * bubble up with an error, so all return values * from encode() should be checked with isError() */ function Services_JSON($use = 0) { $this->use = $use; } /** * convert a string from one UTF-16 char to one UTF-8 char * * Normally should be handled by mb_convert_encoding, but * provides a slower PHP-only method for installations * that lack the multibye string extension. * * @param string $utf16 UTF-16 character * @return string UTF-8 character * @access private */ function utf162utf8($utf16) { // oh please oh please oh please oh please oh please if(function_exists('mb_convert_encoding')) { return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); } $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); switch(true) { case ((0x7F & $bytes) == $bytes): // this case should never be reached, because we are in ASCII range // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 return chr(0x7F & $bytes); case (0x07FF & $bytes) == $bytes: // return a 2-byte UTF-8 character // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 return chr(0xC0 | (($bytes >> 6) & 0x1F)) . chr(0x80 | ($bytes & 0x3F)); case (0xFFFF & $bytes) == $bytes: // return a 3-byte UTF-8 character // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 return chr(0xE0 | (($bytes >> 12) & 0x0F)) . chr(0x80 | (($bytes >> 6) & 0x3F)) . chr(0x80 | ($bytes & 0x3F)); } // ignoring UTF-32 for now, sorry return ''; } /** * convert a string from one UTF-8 char to one UTF-16 char * * Normally should be handled by mb_convert_encoding, but * provides a slower PHP-only method for installations * that lack the multibye string extension. * * @param string $utf8 UTF-8 character * @return string UTF-16 character * @access private */ function utf82utf16($utf8) { // oh please oh please oh please oh please oh please if(function_exists('mb_convert_encoding')) { return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); } switch(strlen($utf8)) { case 1: // this case should never be reached, because we are in ASCII range // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 return $utf8; case 2: // return a UTF-16 character from a 2-byte UTF-8 char // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 return chr(0x07 & (ord($utf8{0}) >> 2)) . chr((0xC0 & (ord($utf8{0}) << 6)) | (0x3F & ord($utf8{1}))); case 3: // return a UTF-16 character from a 3-byte UTF-8 char // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 return chr((0xF0 & (ord($utf8{0}) << 4)) | (0x0F & (ord($utf8{1}) >> 2))) . chr((0xC0 & (ord($utf8{1}) << 6)) | (0x7F & ord($utf8{2}))); } // ignoring UTF-32 for now, sorry return ''; } /** * encodes an arbitrary variable into JSON format * * @param mixed $var any number, boolean, string, array, or object to be encoded. * see argument 1 to Services_JSON() above for array-parsing behavior. * if var is a strng, note that encode() always expects it * to be in ASCII or UTF-8 format! * * @return mixed JSON string representation of input var or an error if a problem occurs * @access public */ function encode($var) { switch (gettype($var)) { case 'boolean': return $var ? 'true' : 'false'; case 'NULL': return 'null'; case 'integer': return (int) $var; case 'double': case 'float': return (float) $var; case 'string': // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT $ascii = ''; $strlen_var = strlen($var); /* * Iterate over every character in the string, * escaping with a slash or encoding to UTF-8 where necessary */ for ($c = 0; $c < $strlen_var; ++$c) { $ord_var_c = ord($var{$c}); switch (true) { case $ord_var_c == 0x08: $ascii .= '\b'; break; case $ord_var_c == 0x09: $ascii .= '\t'; break; case $ord_var_c == 0x0A: $ascii .= '\n'; break; case $ord_var_c == 0x0C: $ascii .= '\f'; break; case $ord_var_c == 0x0D: $ascii .= '\r'; break; case $ord_var_c == 0x22: case $ord_var_c == 0x2F: case $ord_var_c == 0x5C: // double quote, slash, slosh $ascii .= '\\'.$var{$c}; break; case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): // characters U-00000000 - U-0000007F (same as ASCII) $ascii .= $var{$c}; break; case (($ord_var_c & 0xE0) == 0xC0): // characters U-00000080 - U-000007FF, mask 110XXXXX // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 $char = pack('C*', $ord_var_c, ord($var{$c + 1})); $c += 1; $utf16 = $this->utf82utf16($char); $ascii .= sprintf('\u%04s', bin2hex($utf16)); break; case (($ord_var_c & 0xF0) == 0xE0): // characters U-00000800 - U-0000FFFF, mask 1110XXXX // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 $char = pack('C*', $ord_var_c, ord($var{$c + 1}), ord($var{$c + 2})); $c += 2; $utf16 = $this->utf82utf16($char); $ascii .= sprintf('\u%04s', bin2hex($utf16)); break; case (($ord_var_c & 0xF8) == 0xF0): // characters U-00010000 - U-001FFFFF, mask 11110XXX // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 $char = pack('C*', $ord_var_c, ord($var{$c + 1}), ord($var{$c + 2}), ord($var{$c + 3})); $c += 3; $utf16 = $this->utf82utf16($char); $ascii .= sprintf('\u%04s', bin2hex($utf16)); break; case (($ord_var_c & 0xFC) == 0xF8): // characters U-00200000 - U-03FFFFFF, mask 111110XX // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 $char = pack('C*', $ord_var_c, ord($var{$c + 1}), ord($var{$c + 2}), ord($var{$c + 3}), ord($var{$c + 4})); $c += 4; $utf16 = $this->utf82utf16($char); $ascii .= sprintf('\u%04s', bin2hex($utf16)); break; case (($ord_var_c & 0xFE) == 0xFC): // characters U-04000000 - U-7FFFFFFF, mask 1111110X // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 $char = pack('C*', $ord_var_c, ord($var{$c + 1}), ord($var{$c + 2}), ord($var{$c + 3}), ord($var{$c + 4}), ord($var{$c + 5})); $c += 5; $utf16 = $this->utf82utf16($char); $ascii .= sprintf('\u%04s', bin2hex($utf16)); break; } } return '"'.$ascii.'"'; case 'array': /* * As per JSON spec if any array key is not an integer * we must treat the the whole array as an object. We * also try to catch a sparsely populated associative * array with numeric keys here because some JS engines * will create an array with empty indexes up to * max_index which can cause memory issues and because * the keys, which may be relevant, will be remapped * otherwise. * * As per the ECMA and JSON specification an object may * have any string as a property. Unfortunately due to * a hole in the ECMA specification if the key is a * ECMA reserved word or starts with a digit the * parameter is only accessible using ECMAScript's * bracket notation. */ // treat as a JSON object if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { $properties = array_map(array($this, 'name_value'), array_keys($var), array_values($var)); foreach($properties as $property) { if(Services_JSON::isError($property)) { return $property; } } return '{' . join(',', $properties) . '}'; } // treat it like a regular array $elements = array_map(array($this, 'encode'), $var); foreach($elements as $element) { if(Services_JSON::isError($element)) { return $element; } } return '[' . join(',', $elements) . ']'; case 'object': $vars = get_object_vars($var); $properties = array_map(array($this, 'name_value'), array_keys($vars), array_values($vars)); foreach($properties as $property) { if(Services_JSON::isError($property)) { return $property; } } return '{' . join(',', $properties) . '}'; default: return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) ? 'null' : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); } } /** * array-walking function for use in generating JSON-formatted name-value pairs * * @param string $name name of key to use * @param mixed $value reference to an array element to be encoded * * @return string JSON-formatted name-value pair, like '"name":value' * @access private */ function name_value($name, $value) { $encoded_value = $this->encode($value); if(Services_JSON::isError($encoded_value)) { return $encoded_value; } return $this->encode(strval($name)) . ':' . $encoded_value; } /** * reduce a string by removing leading and trailing comments and whitespace * * @param $str string string value to strip of comments and whitespace * * @return string string value stripped of comments and whitespace * @access private */ function reduce_string($str) { $str = preg_replace(array( // eliminate single line comments in '// ...' form '#^\s*//(.+)$#m', // eliminate multi-line comments in '/* ... */' form, at start of string '#^\s*/\*(.+)\*/#Us', // eliminate multi-line comments in '/* ... */' form, at end of string '#/\*(.+)\*/\s*$#Us' ), '', $str); // eliminate extraneous space return trim($str); } /** * decodes a JSON string into appropriate variable * * @param string $str JSON-formatted string * * @return mixed number, boolean, string, array, or object * corresponding to given JSON input string. * See argument 1 to Services_JSON() above for object-output behavior. * Note that decode() always returns strings * in ASCII or UTF-8 format! * @access public */ function decode($str) { $str = $this->reduce_string($str); switch (strtolower($str)) { case 'true': return true; case 'false': return false; case 'null': return null; default: $m = array(); if (is_numeric($str)) { // Lookie-loo, it's a number // This would work on its own, but I'm trying to be // good about returning integers where appropriate: // return (float)$str; // Return float or int, as appropriate return ((float)$str == (integer)$str) ? (integer)$str : (float)$str; } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { // STRINGS RETURNED IN UTF-8 FORMAT $delim = substr($str, 0, 1); $chrs = substr($str, 1, -1); $utf8 = ''; $strlen_chrs = strlen($chrs); for ($c = 0; $c < $strlen_chrs; ++$c) { $substr_chrs_c_2 = substr($chrs, $c, 2); $ord_chrs_c = ord($chrs{$c}); switch (true) { case $substr_chrs_c_2 == '\b': $utf8 .= chr(0x08); ++$c; break; case $substr_chrs_c_2 == '\t': $utf8 .= chr(0x09); ++$c; break; case $substr_chrs_c_2 == '\n': $utf8 .= chr(0x0A); ++$c; break; case $substr_chrs_c_2 == '\f': $utf8 .= chr(0x0C); ++$c; break; case $substr_chrs_c_2 == '\r': $utf8 .= chr(0x0D); ++$c; break; case $substr_chrs_c_2 == '\\"': case $substr_chrs_c_2 == '\\\'': case $substr_chrs_c_2 == '\\\\': case $substr_chrs_c_2 == '\\/': if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || ($delim == "'" && $substr_chrs_c_2 != '\\"')) { $utf8 .= $chrs{++$c}; } break; case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): // single, escaped unicode character $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) . chr(hexdec(substr($chrs, ($c + 4), 2))); $utf8 .= $this->utf162utf8($utf16); $c += 5; break; case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): $utf8 .= $chrs{$c}; break; case ($ord_chrs_c & 0xE0) == 0xC0: // characters U-00000080 - U-000007FF, mask 110XXXXX //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 $utf8 .= substr($chrs, $c, 2); ++$c; break; case ($ord_chrs_c & 0xF0) == 0xE0: // characters U-00000800 - U-0000FFFF, mask 1110XXXX // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 $utf8 .= substr($chrs, $c, 3); $c += 2; break; case ($ord_chrs_c & 0xF8) == 0xF0: // characters U-00010000 - U-001FFFFF, mask 11110XXX // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 $utf8 .= substr($chrs, $c, 4); $c += 3; break; case ($ord_chrs_c & 0xFC) == 0xF8: // characters U-00200000 - U-03FFFFFF, mask 111110XX // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 $utf8 .= substr($chrs, $c, 5); $c += 4; break; case ($ord_chrs_c & 0xFE) == 0xFC: // characters U-04000000 - U-7FFFFFFF, mask 1111110X // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 $utf8 .= substr($chrs, $c, 6); $c += 5; break; } } return $utf8; } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { // array, or object notation if ($str{0} == '[') { $stk = array(SERVICES_JSON_IN_ARR); $arr = array(); } else { if ($this->use & SERVICES_JSON_LOOSE_TYPE) { $stk = array(SERVICES_JSON_IN_OBJ); $obj = array(); } else { $stk = array(SERVICES_JSON_IN_OBJ); $obj = new stdClass(); } } array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => 0, 'delim' => false)); $chrs = substr($str, 1, -1); $chrs = $this->reduce_string($chrs); if ($chrs == '') { if (reset($stk) == SERVICES_JSON_IN_ARR) { return $arr; } else { return $obj; } } //print("\nparsing {$chrs}\n"); $strlen_chrs = strlen($chrs); for ($c = 0; $c <= $strlen_chrs; ++$c) { $top = end($stk); $substr_chrs_c_2 = substr($chrs, $c, 2); if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { // found a comma that is not inside a string, array, etc., // OR we've reached the end of the character list $slice = substr($chrs, $top['where'], ($c - $top['where'])); array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); if (reset($stk) == SERVICES_JSON_IN_ARR) { // we are in an array, so just push an element onto the stack array_push($arr, $this->decode($slice)); } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { // we are in an object, so figure // out the property name and set an // element in an associative array, // for now $parts = array(); if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { // "name":value pair $key = $this->decode($parts[1]); $val = $this->decode($parts[2]); if ($this->use & SERVICES_JSON_LOOSE_TYPE) { $obj[$key] = $val; } else { $obj->$key = $val; } } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { // name:value pair, where name is unquoted $key = $parts[1]; $val = $this->decode($parts[2]); if ($this->use & SERVICES_JSON_LOOSE_TYPE) { $obj[$key] = $val; } else { $obj->$key = $val; } } } } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { // found a quote, and we are not inside a string array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); //print("Found start of string at {$c}\n"); } elseif (($chrs{$c} == $top['delim']) && ($top['what'] == SERVICES_JSON_IN_STR) && ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { // found a quote, we're in a string, and it's not escaped // we know that it's not escaped becase there is _not_ an // odd number of backslashes at the end of the string so far array_pop($stk); //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); } elseif (($chrs{$c} == '[') && in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { // found a left-bracket, and we are in an array, object, or slice array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); //print("Found start of array at {$c}\n"); } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { // found a right-bracket, and we're in an array array_pop($stk); //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); } elseif (($chrs{$c} == '{') && in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { // found a left-brace, and we are in an array, object, or slice array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); //print("Found start of object at {$c}\n"); } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { // found a right-brace, and we're in an object array_pop($stk); //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); } elseif (($substr_chrs_c_2 == '/*') && in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { // found a comment start, and we are in an array, object, or slice array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); $c++; //print("Found start of comment at {$c}\n"); } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { // found a comment end, and we're in one now array_pop($stk); $c++; for ($i = $top['where']; $i <= $c; ++$i) $chrs = substr_replace($chrs, ' ', $i, 1); //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); } } if (reset($stk) == SERVICES_JSON_IN_ARR) { return $arr; } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { return $obj; } } } } /** * @todo Ultimately, this should just call PEAR::isError() */ function isError($data, $code = null) { if (class_exists('pear')) { return PEAR::isError($data, $code); } elseif (is_object($data) && (get_class($data) == 'services_json_error' || is_subclass_of($data, 'services_json_error'))) { return true; } return false; } } if (class_exists('PEAR_Error')) { class Services_JSON_Error extends PEAR_Error { function Services_JSON_Error($message = 'unknown error', $code = null, $mode = null, $options = null, $userinfo = null) { parent::PEAR_Error($message, $code, $mode, $options, $userinfo); } } } else { /** * @todo Ultimately, this class shall be descended from PEAR_Error */ class Services_JSON_Error { function Services_JSON_Error($message = 'unknown error', $code = null, $mode = null, $options = null, $userinfo = null) { } } } } ?>