miles@Mercury 9 年之前
當前提交
94e5bcb056
共有 39 個文件被更改,包括 7340 次插入0 次删除
  1. 249 0
      Abstract.php
  2. 147 0
      Client.php
  3. 143 0
      DuoshuoClient.php
  4. 29 0
      Exception.php
  5. 81 0
      LocalServer.php
  6. 1384 0
      WordPress.php
  7. 45 0
      api.php
  8. 27 0
      bind.php
  9. 80 0
      comments-seo.php
  10. 53 0
      comments.php
  11. 94 0
      common-script.html
  12. 899 0
      compat-json.php
  13. 18 0
      config.php
  14. 376 0
      duoshuo.php
  15. 2139 0
      embed-formatted-v1.2.js
  16. 二進制
      images/head-icon.gif
  17. 二進制
      images/icon.gif
  18. 二進制
      images/menu-icon.png
  19. 二進制
      images/service_icons_32x32.png
  20. 二進制
      images/waiting.gif
  21. 3 0
      index.php
  22. 29 0
      manage.php
  23. 334 0
      nanoSha2.php
  24. 11 0
      oauth-proxy.php
  25. 28 0
      preferences.php
  26. 16 0
      profile.php
  27. 382 0
      readme.txt
  28. 二進制
      screenshot-1.png
  29. 二進制
      screenshot-2.png
  30. 二進制
      screenshot-3.png
  31. 二進制
      screenshot-4.png
  32. 二進制
      screenshot-5.png
  33. 二進制
      screenshot-6.jpg
  34. 211 0
      settings.php
  35. 14 0
      statistics.php
  36. 54 0
      styles.css
  37. 10 0
      sync.php
  38. 53 0
      themes.php
  39. 431 0
      widgets.php

+ 249 - 0
Abstract.php

@@ -0,0 +1,249 @@
+<?php
+class Duoshuo_Abstract {
+	const DOMAIN = 'duoshuo.com';
+	const STATIC_DOMAIN = 'static.duoshuo.com';
+	const VERSION = '1.2';
+	
+	/**
+	 * 
+	 * @var string
+	 */
+	public $shortName;
+	
+	/**
+	 * 
+	 * @var string
+	 */
+	public $secret;
+	
+	/**
+	 * 默认的获取Client的函数,可以被派生
+	 * @param string|int $userId
+	 * @return Duoshuo_Client
+	 */
+	public function getClient($userId = 0){
+		return new Duoshuo_Client($this->shortName, $this->secret);
+	}
+	
+	public function syncLog(){
+		$this->updateOption('sync_lock',  time());
+		
+		$last_log_id = $this->getOption('last_log_id');
+		if (!$last_log_id)
+			$last_log_id = 0;
+		
+		$limit = 20;
+			
+		$params = array(
+				'limit' => $limit,
+				'order' => 'asc',
+		);
+			
+		$client = $this->getClient();
+		
+		$posts = array();
+		$affectedThreads = array();
+		
+		//do{
+			
+			$params['since_id'] = $last_log_id;
+			$response = $client->request('GET', 'log/list', $params);
+			
+			if (is_string($response))
+				throw new Duoshuo_Exception($response, Duoshuo_Exception::INTERNAL_SERVER_ERROR);
+			
+			if (!isset($response['response']))
+				throw new Duoshuo_Exception($response['message'], $response['code']);
+			
+			foreach($response['response'] as $log){
+				switch($log['action']){
+					case 'create':
+						$affected = $this->createPost($log['meta']);
+						break;
+					case 'approve':
+						$affected = $this->approvePost($log['meta']);
+						break;
+					case 'spam':
+						$affected = $this->spamPost($log['meta']);
+						break;
+					case 'delete':
+						$affected = $this->deletePost($log['meta']);
+						break;
+					case 'delete-forever':
+						$affected = $this->deleteForeverPost($log['meta']);
+						break;
+					case 'update'://现在并没有update操作的逻辑
+					default:
+						$affected = array();
+				}
+				
+				//合并
+				if (is_array($affected))
+					$affectedThreads = array_merge($affectedThreads, $affected);
+		
+				if (strlen($log['log_id']) > strlen($last_log_id) || strcmp($log['log_id'], $last_log_id) > 0)
+					$last_log_id = $log['log_id'];
+			}
+			
+			$this->updateOption('last_log_id', $last_log_id);
+		
+		//} while (count($response['response']) == $limit);//如果返回和最大请求条数一致,则再取一次
+			
+		$this->updateOption('sync_lock',  0);
+		
+		//更新静态文件
+		if ($this->getOption('sync_to_local') && $this->plugin->getOption('seo_enabled'))
+			$this->refreshThreads(array_unique($affectedThreads));
+		
+		return count($response['response']);
+	}
+	
+	function rfc3339_to_mysql($string){
+		if (method_exists('DateTime', 'createFromFormat')){	//	php 5.3.0
+			return DateTime::createFromFormat(DateTime::RFC3339, $string)->format('Y-m-d H:i:s');
+		}
+		else{
+			$timestamp = strtotime($string);
+			return gmdate('Y-m-d H:i:s', $timestamp  + $this->timezone() * 3600);
+		}
+	}
+	
+	function rfc3339_to_mysql_gmt($string){
+		if (method_exists('DateTime', 'createFromFormat')){	//	php 5.3.0
+			return DateTime::createFromFormat(DateTime::RFC3339, $string)->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s');
+		}
+		else{
+			$timestamp = strtotime($string);
+			return gmdate('Y-m-d H:i:s', $timestamp);
+		}
+	}
+	
+	static function encodeJWT($payload, $key){
+		$header = array('typ' => 'JWT', 'alg' => 'HS256');
+	
+		$segments = array(
+			str_replace('=', '', strtr(base64_encode(json_encode($header)), '+/', '-_')),
+			str_replace('=', '', strtr(base64_encode(json_encode($payload)), '+/', '-_')),
+		);
+		$signing_input = implode('.', $segments);
+	
+		$signature = self::hmacsha256($signing_input, $key);
+	
+		$segments[] = str_replace('=', '', strtr(base64_encode($signature), '+/', '-_'));
+	
+		return implode('.', $segments);
+	}
+	
+	// from: http://www.php.net/manual/en/function.sha1.php#39492
+	// Calculate HMAC-SHA1 according to RFC2104
+	// http://www.ietf.org/rfc/rfc2104.txt
+	static function hmacsha1($data, $key) {
+		if (function_exists('hash_hmac'))
+			return hash_hmac('sha1', $data, $key, true);
+	
+		$blocksize=64;
+		if (strlen($key)>$blocksize)
+			$key=pack('H*', sha1($key));
+		$key=str_pad($key,$blocksize,chr(0x00));
+		$ipad=str_repeat(chr(0x36),$blocksize);
+		$opad=str_repeat(chr(0x5c),$blocksize);
+		$hmac = pack(
+				'H*',sha1(
+						($key^$opad).pack(
+								'H*',sha1(
+										($key^$ipad).$data
+								)
+						)
+				)
+		);
+		return $hmac;
+	}
+	
+	/**
+	 * from: http://www.php.net/manual/en/function.sha1.php#39492
+	 * Calculate HMAC-SHA1 according to RFC2104
+	 * http://www.ietf.org/rfc/rfc2104.txt
+	 * Used in OAuth1 and remoteAuth
+	 */
+	static function hmacsha256($data, $key) {
+		if (function_exists('hash_hmac'))
+			return hash_hmac('sha256', $data, $key, true);
+		
+		if (!class_exists('nanoSha2', false))
+			require 'nanoSha2.php';
+		
+		$nanoSha2 = new nanoSha2();
+		
+	    $blocksize=64;
+	    if (strlen($key)>$blocksize)
+	        $key=pack('H*', $nanoSha2->hash($key, true));
+	    $key=str_pad($key,$blocksize,chr(0x00));
+	    $ipad=str_repeat(chr(0x36),$blocksize);
+	    $opad=str_repeat(chr(0x5c),$blocksize);
+	    $hmac = pack(
+	                'H*',$nanoSha2->hash(
+	                    ($key^$opad).pack(
+	                        'H*', $nanoSha2->hash(($key^$ipad).$data, true)
+	                    ),
+	                	true
+	                )
+	            );
+	    return $hmac;
+	}
+	
+	function exportUsers($users){
+		if (count($users) === 0)
+			return 0;
+		
+		$params = array('users'=>array());
+		foreach($users as $user)
+			$params['users'][] = $this->packageUser($user);
+		 
+		$remoteResponse = $this->getClient()->request('POST', 'users/import', $params);
+		
+		//	@deprecated 不再需要记录duoshuo_user_id
+		if (is_array($remoteResponse) && isset($remoteResponse['response'])){
+			foreach($remoteResponse['response'] as $userId => $duoshuoUserId)
+				$this->updateUserMeta($userId, 'duoshuo_user_id', $duoshuoUserId);
+		}
+		
+		return count($users);
+	}
+	
+	function exportPosts($threads){
+		if (count($threads) === 0)
+			return 0;
+	
+		$params = array(
+			'threads'	=>	array(),
+		);
+		foreach($threads as $index => $thread){
+			$params['threads'][] = $this->packageThread($thread);
+		}
+	
+		$remoteResponse = $this->getClient()->request('POST','threads/import', $params);
+		
+		if (is_array($remoteResponse) && isset($remoteResponse['response'])){
+			foreach($remoteResponse['response'] as $threadId => $duoshuoThreadId)
+				$this->updateThreadMeta($threadId, 'duoshuo_thread_id', $duoshuoThreadId);
+		}
+		
+		return count($threads);
+	}
+	
+	function exportComments($comments){
+		if (count($comments) === 0)
+			return 0;
+	
+		$params = array(
+			'posts'	=>	array()
+		);
+	
+		foreach($comments as $comment)
+			$params['posts'][] = $this->packageComment($comment);
+	
+		$remoteResponse = $this->getClient()->request('POST', 'posts/import', $params);
+	
+		return count($comments);
+	}
+}

+ 147 - 0
Client.php

@@ -0,0 +1,147 @@
+<?php
+/**
+ * 
+ * @link http://duoshuo.com/
+ * @author shen2
+ *
+ */
+class Duoshuo_Client{
+	
+	var $end_point = 'http://api.duoshuo.com/';
+	var $format = 'json';
+	var $userAgent;
+	var $shortName;
+	var $secret;
+	var $jwt;
+	var $accessToken;
+	var $http;
+	
+	public function __construct($shortName = null, $secret = null, $jwt = null, $accessToken = null){
+		global $wp_version;
+		
+		$this->shortName = $shortName;
+		$this->secret = $secret;
+		$this->jwt = $jwt;
+		$this->accessToken = $accessToken;
+		$this->http = new WP_Http();
+		$this->userAgent = 'WordPress/' . $wp_version . '|Duoshuo/'. Duoshuo_WordPress::VERSION;
+	}
+	
+	/**
+	 * 
+	 * @param $method
+	 * @param $path
+	 * @param $params
+	 * @throws Duoshuo_Exception
+	 * @return array
+	 */
+	public function request($method, $path, $params = array()){
+        $params['short_name'] = $this->shortName;
+        $params['secret'] = $this->secret;
+        
+        if ($this->jwt)
+			$params['jwt'] = $this->jwt;
+        
+        if ($this->accessToken)
+        	$params['access_token'] = $this->accessToken;
+		
+		$url = $this->end_point . $path. '.' . $this->format;
+		
+		return $this->httpRequest($url, $method, $params);
+	}
+	
+	public function httpRequest($url, $method, $params){
+		$args = array(
+			'method' => $method,
+			'timeout' => 60,
+			'redirection' => 5,
+			'httpversion' => '1.0',
+			'user-agent' => $this->userAgent,
+			//'blocking' => true,
+			'headers' => array('Expect'=>''),
+			//'cookies' => array(),
+			//'compress' => false,
+			//'decompress' => true,
+			'sslverify' => false,
+			//'stream' => false,
+			//'filename' => null
+		);
+		
+		switch($method){
+			case 'GET':
+				$url .= '?' . http_build_query($params, null, '&');	// overwrite arg_separator.output
+				break;
+			case 'POST':
+				$args['body'] = $params;	// http类自己会做 http_build_query($params, null, '&') 并指定Content-Type
+				break;
+			default:
+		}
+		
+		$response = $this->http->request($url, $args);
+			
+		if (isset($response->errors)){
+			if (isset($response->errors['http_request_failed'])){
+				$message = $response->errors['http_request_failed'][0];
+				if ($message == 'name lookup timed out')
+					$message = 'DNS解析超时,请重试或检查你的主机的域名解析(DNS)设置。';
+				elseif (stripos($message, 'Could not open handle for fopen') === 0)
+					$message = '无法打开fopen句柄,请重试或联系多说管理员。http://dev.duoshuo.com/';
+				elseif (stripos($message, 'Couldn\'t resolve host') === 0)
+					$message = '无法解析duoshuo.com域名,请重试或检查你的主机的域名解析(DNS)设置。';
+				elseif (stripos($message, 'Operation timed out after ') === 0)
+					$message = '操作超时,请重试或联系多说管理员。http://dev.duoshuo.com/';
+				throw new Duoshuo_Exception($message, Duoshuo_Exception::REQUEST_TIMED_OUT);
+			}
+            else
+            	throw new Duoshuo_Exception('连接服务器失败, 详细信息:' . json_encode($response->errors), Duoshuo_Exception::REQUEST_TIMED_OUT);
+		}
+
+		$json = json_decode($response['body'], true);
+		return $json === null ? $response['body'] : $json;
+	}
+	
+	/**
+	 * 
+	 * @param string $type
+	 * @param array $keys
+	 */
+	public function getAccessToken( $type, $keys ) {
+		$params = array(
+			'client_id'	=>	$this->shortName,
+			'client_secret' => $this->secret,
+		);
+		
+		switch($type){
+		case 'token':
+			$params['grant_type'] = 'refresh_token';
+			$params['refresh_token'] = $keys['refresh_token'];
+			break;
+		case 'code':
+			$params['grant_type'] = 'authorization_code';
+			$params['code'] = $keys['code'];
+			$params['redirect_uri'] = $keys['redirect_uri'];
+			break;
+		case 'password':
+			$params['grant_type'] = 'password';
+			$params['username'] = $keys['username'];
+			$params['password'] = $keys['password'];
+			break;
+		default:
+			throw new Duoshuo_Exception("wrong auth type");
+		}
+
+		$accessTokenUrl = $this->end_point . 'oauth2/access_token';
+		$response = $this->httpRequest($accessTokenUrl, 'POST', $params);
+		
+		$token = $response;
+		if ( is_array($token) && !isset($token['error']) ) {
+			$this->access_token = $token['access_token'];
+			if (isset($token['refresh_token'])) //	可能没有refresh_token
+				$this->refresh_token = $token['refresh_token'];
+		} else {
+			throw new Duoshuo_Exception("get access token failed." . $token['error']);
+		}
+		
+		return $token;
+	}
+}

+ 143 - 0
DuoshuoClient.php

@@ -0,0 +1,143 @@
+<?php
+/**
+ * 
+ * @link http://duoshuo.com/
+ * @author shen2
+ *
+ */
+class DuoshuoClient{
+	var $end_point = 'http://duoshuo.com/api/';
+	var $format = 'json';
+	var $userAgent;
+	var $shortName;
+	var $secret;
+	var $accessToken;
+	var $http;
+	
+	function __construct($shortName = null, $secret = null, $remoteAuth = null, $accessToken = null){
+		global $wp_version;
+		
+		$this->shortName = $shortName;
+		$this->secret = $secret;
+		$this->remoteAuth = $remoteAuth;
+		$this->accessToken = $accessToken;
+		$this->http = new WP_Http();
+		$this->userAgent = 'WordPress/' . $wp_version . '|Duoshuo/'. Duoshuo::VERSION;
+	}
+	
+	/**
+	 * 
+	 * @param $method
+	 * @param $path
+	 * @param $params
+	 * @throws Duoshuo_Exception
+	 * @return array
+	 */
+	function request($method, $path, $params = array()){
+        $params['short_name'] = $this->shortName;
+		$params['remote_auth'] = $this->remoteAuth;
+        
+        if ($this->accessToken)
+        	$params['access_token'] = $this->accessToken;
+		
+		$url = $this->end_point . $path. '.' . $this->format;
+		
+		return $this->httpRequest($url, $method, $params);
+	}
+	
+	function httpRequest($url, $method, $params){
+		$args = array(
+			'method' => $method,
+			'timeout' => 60,
+			'redirection' => 5,
+			'httpversion' => '1.0',
+			'user-agent' => $this->userAgent,
+			//'blocking' => true,
+			'headers' => array('Expect'=>''),
+			//'cookies' => array(),
+			//'compress' => false,
+			//'decompress' => true,
+			'sslverify' => false,
+			//'stream' => false,
+			//'filename' => null
+		);
+		
+		switch($method){
+			case 'GET':
+				$url .= '?' . http_build_query($params, null, '&');	// overwrite arg_separator.output
+				break;
+			case 'POST':
+				$args['body'] = $params;	// http类自己会做 http_build_query($params, null, '&') 并指定Content-Type
+				break;
+			default:
+		}
+		
+		$response = $this->http->request($url, $args);
+			
+		if (isset($response->errors)){
+			if (isset($response->errors['http_request_failed'])){
+				$message = $response->errors['http_request_failed'][0];
+				if ($message == 'name lookup timed out')
+					$message = 'DNS解析超时,请重试或检查你的主机的域名解析(DNS)设置。';
+				elseif (stripos($message, 'Could not open handle for fopen') === 0)
+					$message = '无法打开fopen句柄,请重试或联系多说管理员。http://duoshuo.com/';
+				elseif (stripos($message, 'Couldn\'t resolve host') === 0)
+					$message = '无法解析duoshuo.com域名,请重试或检查你的主机的域名解析(DNS)设置。';
+				elseif (stripos($message, 'Operation timed out after ') === 0)
+					$message = '操作超时,请重试或联系多说管理员。http://duoshuo.com/';
+				throw new Duoshuo_Exception($message, Duoshuo_Exception::REQUEST_TIMED_OUT);
+			}
+            else
+            	throw new Duoshuo_Exception('连接服务器失败, 详细信息:' . json_encode($response->errors), Duoshuo_Exception::REQUEST_TIMED_OUT);
+		}
+
+		$json = json_decode($response['body'], true);
+		return $json === null ? $response['body'] : $json;
+	}
+	
+	/**
+	 * 
+	 * @param string $type
+	 * @param array $keys
+	 */
+	function getAccessToken( $type, $keys ) {
+		$params = array(
+			'client_id'	=>	$this->shortName,
+			'client_secret' => $this->secret,
+		);
+		
+		switch($type){
+		case 'token':
+			$params['grant_type'] = 'refresh_token';
+			$params['refresh_token'] = $keys['refresh_token'];
+			break;
+		case 'code':
+			$params['grant_type'] = 'authorization_code';
+			$params['code'] = $keys['code'];
+			$params['redirect_uri'] = $keys['redirect_uri'];
+			break;
+		case 'password':
+			$params['grant_type'] = 'password';
+			$params['username'] = $keys['username'];
+			$params['password'] = $keys['password'];
+			break;
+		default:
+			throw new Duoshuo_Exception("wrong auth type");
+		}
+
+		$accessTokenUrl = 'http://api.duoshuo.com/oauth2/access_token';
+		$response = $this->httpRequest($accessTokenUrl, 'POST', $params);
+		
+		$token = $response;
+		if ( is_array($token) && !isset($token['error']) ) {
+			$this->access_token = $token['access_token'];
+			if (isset($token['refresh_token'])) //	可能没有refresh_token
+				$this->refresh_token = $token['refresh_token'];
+		} else {
+			var_dump($response);var_dump($params);var_dump($token);	// 用来调试
+			throw new Duoshuo_Exception("get access token failed." . $token['error']);
+		}
+		
+		return $token;
+	}
+}

+ 29 - 0
Exception.php

@@ -0,0 +1,29 @@
+<?php
+class Duoshuo_Exception extends Exception{
+	const SUCCESS		= 0;
+	const ENDPOINT_NOT_VALID = 1;
+	const MISSING_OR_INVALID_ARGUMENT = 2;
+	const ENDPOINT_RESOURCE_NOT_VALID = 3;
+	const NO_AUTHENTICATED = 4;
+	const INVALID_API_KEY = 5;
+	const INVALID_API_VERSION = 6;
+	const CANNOT_ACCESS = 7;
+	const OBJECT_NOT_FOUND = 8;
+	const API_NO_PRIVILEGE = 9;
+	const OPERATION_NOT_SUPPORTED = 10;
+	const API_KEY_INVALID = 11;
+	const NO_PRIVILEGE = 12;
+	const RESOURCE_RATE_LIMIT_EXCEEDED = 13;
+	const ACCOUNT_RATE_LIMIT_EXCEEDED = 14;
+	const INTERNAL_SERVER_ERROR = 15;
+	const REQUEST_TIMED_OUT = 16;
+	const NO_ACCESS_TO_THIS_FEATURE = 17;
+	const INVALID_SIGNATURE = 18;
+	
+	const USER_DENIED_YOUR_REQUEST = 21;
+	const EXPIRED_TOKEN = 22;
+	const REDIRECT_URI_MISMATCH = 23;
+	const DUPLICATE_CONNECTED_ACCOUNT = 24;
+	
+	const PLUGIN_DEACTIVATED = 30;
+}

+ 81 - 0
LocalServer.php

@@ -0,0 +1,81 @@
+<?php
+
+class Duoshuo_LocalServer{
+	
+	protected $response = array();
+	
+	/**
+	 * 
+	 * @var Duoshuo_WordPress
+	 */
+	protected $plugin;
+	
+	public function __construct($plugin){
+		$this->plugin = $plugin;
+	}
+	
+	/**
+	 * 从服务器pull评论到本地
+	 * 
+	 * @param array $input
+	 */
+	public function sync_log($input = array()){
+		$syncLock = $this->plugin->getOption('sync_lock');//检查是否正在同步评论 同步完成后该值会置0
+		if($syncLock && $syncLock > time()- 300){//正在或5分钟内发生过写回但没置0
+			$this->response = array(
+				'code'	=>	Duoshuo_Exception::SUCCESS,
+				'response'=> '同步中,请稍候',
+			);
+			return;
+		}
+		
+		try{
+			$this->response['affected'] = $this->plugin->syncLog();
+			$this->response['last_log_id'] = $this->plugin->getOption('last_log_id');
+		}
+		catch(Exception $ex){
+			//$this->plugin->updateOption('sync_lock', $ex->getLine());
+		}
+		
+		$this->response['code'] = Duoshuo_Exception::SUCCESS;
+	}
+	
+	public function update_option($input = array()){
+		//duoshuo_short_name
+		//duoshuo_secret
+		//duoshuo_notice
+		foreach($input as $optionName => $optionValue)
+			if (substr($optionName, 0, 8) === 'duoshuo_'){
+				update_option($_POST['option'], $_POST['value']);
+			}
+		$this->response['code'] = 0;
+	}
+	
+	public function dispatch($input){
+		if (!isset($input['signature']))
+			throw new Duoshuo_Exception('Invalid signature.', Duoshuo_Exception::INVALID_SIGNATURE);
+	
+		$signature = $input['signature'];
+		unset($input['signature']);
+	
+		ksort($input);
+		$baseString = http_build_query($input, null, '&');
+	
+		$expectSignature = base64_encode(Duoshuo_Abstract::hmacsha1($baseString, $this->plugin->getOption('secret')));
+		if ($signature !== $expectSignature)
+			throw new Duoshuo_Exception('Invalid signature, expect: ' . $expectSignature . '. (' . $baseString . ')', Duoshuo_Exception::INVALID_SIGNATURE);
+	
+		$method = $input['action'];
+	
+		if (!method_exists($this, $method))
+			throw new Duoshuo_Exception('Unknown action.', Duoshuo_Exception::OPERATION_NOT_SUPPORTED);
+		
+		$this->response = array();
+		$this->$method($input);
+		$this->sendResponse();
+	}
+	
+	public function sendResponse(){
+		echo json_encode($this->response);
+	}
+}

File diff suppressed because it is too large
+ 1384 - 0
WordPress.php


+ 45 - 0
api.php

@@ -0,0 +1,45 @@
+<?php
+/*
+if ( ! isset( $_REQUEST['action'] ) )
+	die('-1');*/
+
+if (!extension_loaded('json'))
+	include dirname(__FILE__) . '/compat-json.php';
+
+require '../../../wp-load.php';
+
+require '../../../wp-admin/includes/admin.php';
+
+do_action('admin_init');
+
+if (!headers_sent()) {
+	nocache_headers();
+	header('Content-Type: text/javascript; charset=utf-8');
+}
+
+if (!class_exists('Duoshuo_WordPress', false)){
+	$response = array(
+		'code'			=>	30,
+		'errorMessage'	=>	'Duoshuo plugin hasn\'t been activated.'
+	);
+	echo json_encode($response);
+	exit;
+}
+
+require DUOSHUO_PLUGIN_PATH . '/LocalServer.php';
+
+$plugin = Duoshuo_WordPress::getInstance();
+
+try{
+	if ($_SERVER['REQUEST_METHOD'] == 'POST'){
+		$input = $_POST;
+		if (isset($input['spam_confirmed']))	//D-Z Theme 会给POST设置这个参数
+			unset($input['spam_confirmed']);
+		
+		$server = new Duoshuo_LocalServer($plugin);
+		$server->dispatch($input);
+	}
+}
+catch (Exception $e){
+	$plugin->sendException($e);
+}

+ 27 - 0
bind.php

@@ -0,0 +1,27 @@
+<link rel="stylesheet" href="<?php echo self::$pluginDirUrl; ?>styles.css" type="text/css" />
+<?php
+/**
+ * 给那些已经有wordpress帐号但是没有access token的用户使用,绑定帐号
+ */
+$query = http_build_query(array(
+	'sso'	=>	1,
+	'remote_auth'=>Duoshuo::remoteAuth(),
+), null, '&');
+?>
+<h3>绑定社交帐号之后您便可以享用多说的服务了,多说可以让您:</h3>
+<ul>
+	<li>无需密码,用社交帐号即可登录本博客</li>
+	<li>发布文章时同步发布微博</li>
+	<li>发布评论时同步发微博</li>
+	<li>别人对你的回复,第一时间提醒你,不再错过精彩评论</li>
+</ul>
+<ul class="ds-service-icon">
+	<li><a class="ds-weibo" href="<?php echo 'http://' . self::$shortName . '.duoshuo.com/bind/weibo/?' . $query;?>" title="绑定微博帐号"></a></li>
+	<li><a class="ds-qq" href="<?php echo 'http://' . self::$shortName . '.duoshuo.com/bind/qq/?' . $query;?>" title="绑定QQ帐号"></a></li>
+	<li><a class="ds-renren" href="<?php echo 'http://' . self::$shortName . '.duoshuo.com/bind/renren/?' . $query;?>" title="绑定人人帐号"></a></li>
+	<li><a class="ds-kaixin" href="<?php echo 'http://' . self::$shortName . '.duoshuo.com/bind/kaixin/?' . $query;?>" title="绑定开心帐号"></a></li>
+	<li><a class="ds-douban" href="<?php echo 'http://' . self::$shortName . '.duoshuo.com/bind/douban/?' . $query;?>" title="绑定豆瓣帐号"></a></li>
+	<li><a class="ds-netease" href="<?php echo 'http://' . self::$shortName . '.duoshuo.com/bind/netease/?' . $query;?>" title="绑定网易帐号"></a></li>
+	<li><a class="ds-sohu" href="<?php echo 'http://' . self::$shortName . '.duoshuo.com/bind/sohu/?' . $query;?>" title="绑定搜狐帐号"></a></li>
+	<li><a class="ds-baidu" href="<?php echo 'http://' . self::$shortName . '.duoshuo.com/bind/baidu/?' . $query;?>" title="绑定百度帐号"></a></li>
+</ul>

+ 80 - 0
comments-seo.php

@@ -0,0 +1,80 @@
+<?php
+if (!function_exists('duoshuo_comment')){ 
+	function duoshuo_comment( $comment, $args, $depth ) {
+		$GLOBALS['comment'] = $comment;
+		switch ( $comment->comment_type ) :
+			case 'pingback' :
+			case 'trackback' :
+		?>
+		<li class="post pingback">
+			<p><?php _e( 'Pingback:', 'duoshuo' ); ?> <?php comment_author_link(); ?><?php edit_comment_link( __( 'Edit', 'duoshuo' ), '<span class="edit-link">', '</span>' ); ?></p>
+		<?php
+		//	end_el函数会自己输出一个</li>
+				break;
+			default :
+		?>
+		<li <?php comment_class(); ?> id="li-comment-<?php comment_ID(); ?>">
+			<article id="comment-<?php comment_ID(); ?>" class="comment">
+				<footer class="comment-meta">
+					<cite class="comment-author vcard">
+						<?php
+							/* translators: 1: comment author, 2: date and time */
+							printf( __( '%1$s on %2$s <span class="says">said:</span>', 'duoshuo' ),
+								sprintf( '<span class="fn">%s</span>', get_comment_author_link() ),
+								sprintf( '<a rel="nofollow" href="%1$s"><time pubdate datetime="%2$s">%3$s</time></a>',
+									esc_url( get_comment_link( $comment->comment_ID ) ),
+									get_comment_time( 'c' ),
+									/* translators: 1: date, 2: time */
+									sprintf( __( '%1$s at %2$s', 'duoshuo' ), get_comment_date(), get_comment_time() )
+								)
+							);
+						?>
+					</cite><!-- .comment-author .vcard -->
+				</footer>
+	
+				<div class="comment-content">
+				<?php //comment_text(); ?>
+				<?php $current_comment = get_comment_text(); ?>
+				<?php 
+					$current_comment = preg_replace('/(http\:\/\/img.t.sinajs.cn\/t35\/style\/images\/common\/face\/ext\/normal\/)/i',"https://www.solomp.com/custom/catimg.php?src=$1",$current_comment);
+					$current_comment = preg_replace('/(http\:\/\/static.duoshuo.com\/images)/i',"https://www.solomp.com/custom/catimg.php?src=$1",$current_comment);
+				?>
+				<?php echo $current_comment;?>
+				</div>
+				
+			</article><!-- #comment-## -->
+		<?php
+		//	end_el函数会自己输出一个</li>
+				break;
+		endswitch;
+	}
+}
+?>
+	<div id="ds-ssr">
+
+		<?php if (get_comment_pages_count() > 1 && get_option('page_comments')): ?>
+		<nav id="comment-nav-above">
+			<h1 class="assistive-text"><?php _e( 'Comment navigation', 'duoshuo' ); ?></h1>
+			<div class="nav-previous"><?php previous_comments_link( __( '&larr; Older Comments', 'duoshuo' ) ); ?></div>
+			<div class="nav-next"><?php next_comments_link( __( 'Newer Comments &rarr;', 'duoshuo' ) ); ?></div>
+		</nav>
+		<?php endif;?>
+
+            <ol id="commentlist">
+                <?php
+                    /* Loop through and list the comments. Tell wp_list_comments()
+                     * to use Duoshuo::comment() to format the comments.
+                     */
+                    wp_list_comments(array('callback' => 'duoshuo_comment'));
+                ?>
+            </ol>
+
+		<?php if ( get_comment_pages_count() > 1 && get_option( 'page_comments' ) ) :?>
+		<nav id="comment-nav-below">
+			<h1 class="assistive-text"><?php _e( 'Comment navigation', 'duoshuo' ); ?></h1>
+			<div class="nav-previous"><?php previous_comments_link( __( '&larr; Older Comments', 'duoshuo' ) ); ?></div>
+			<div class="nav-next"><?php next_comments_link( __( 'Newer Comments &rarr;', 'duoshuo' ) ); ?></div>
+		</nav>
+		<?php endif; // check for comment navigation ?>
+            
+    </div>

+ 53 - 0
comments.php

@@ -0,0 +1,53 @@
+<?php 
+if (!isset($post))
+	global $post;
+
+$duoshuoPlugin = Duoshuo_WordPress::getInstance();
+$duoshuoPlugin->printScripts();
+
+$topPost = $duoshuoPlugin->topPost($post);
+
+if ($intro = get_option('duoshuo_comments_wrapper_intro'))
+	echo $intro;
+?>
+<a name="comments"></a>
+
+<?php
+if (current_user_can('moderate_comments')):
+	$threadId = get_post_meta($topPost->ID, 'duoshuo_thread_id', true);
+	if (empty($threadId)):?>
+		<p>这篇文章的评论尚未同步到多说,<a href="<?php echo admin_url('admin.php?page=duoshuo-settings');?>">点此同步</a></p>
+	<?php endif;
+endif;
+ 
+$data = array(
+	'thread-key'=>	$topPost->ID,
+	'author-key'=>	$topPost->post_author,
+	'title'		=>	$topPost->post_title,
+	'url'		=>	get_permalink($topPost->ID),
+	//'order'		=>	'desc',
+	//'limit'		=>	20,
+);
+
+$attribs = '';
+foreach ($data as $key => $value)
+	$attribs .= ' data-' . $key . '="' . esc_attr($value) . '"';
+?>
+<div class="ds-thread"<?php echo $attribs;?>></div>
+
+<?php
+static $threadInitialized = false;
+if (!$threadInitialized):
+	$threadInitialized = true;?>
+<script type="text/javascript">
+	if (typeof DUOSHUO !== 'undefined')
+		DUOSHUO.EmbedThread('.ds-thread');
+</script>
+<?php endif;
+
+if (get_option('duoshuo_seo_enabled')): //直接输出HTML评论
+	require 'comments-seo.php';
+endif;
+
+if ($outro = get_option('duoshuo_comments_wrapper_outro'))
+	echo $outro;

+ 94 - 0
common-script.html

@@ -0,0 +1,94 @@
+<style>
+#ds-export{
+	margin-bottom:3em;
+}
+.message-complete, .ds-exported .message-start, .ds-exporting .message-start, .status{
+	display:none;
+}
+.ds-exported .message-complete, .message-start, .ds-exporting .status{
+	display:block;
+}
+</style>
+<script type="text/javascript">
+function fireSyncLog(progress){
+	var $ = jQuery, total = 0;
+	$('#ds-sync .status').empty();
+	$('#ds-sync').addClass('ds-exporting');
+    var syncProgress = function () {
+		$.ajax({
+	        url:ajaxurl,
+	        data:{action: 'duoshuo_sync_log'},
+	        error:duoshuoOnError,
+	        success:function(response) {
+	        	if (response.code == 0){
+	        		if (response.count){
+		        		total += response.count;
+		        		$('#ds-sync .status').html('已经同步了' + total + '条记录');
+		        		syncProgress();
+	        		}
+	        		else{
+	        			$('#ds-sync .status').html('全部同步完成');
+	        		}
+	        	}
+	        	else{
+	        		alert(response.errorMessage);
+	        	}
+	        },
+	        dataType:'json'
+	    });
+	};
+	syncProgress();
+}
+function fireExport(){
+	var $ = jQuery;
+	$('#ds-export .status').empty();//.removeClass('ds-export-fail').html('处理中...');
+    $('#ds-export').addClass('ds-exporting');
+    $('#ds-export .status').html('开始同步 <img src="<?php echo $this->pluginDirUrl . 'images/waiting.gif';?>" />');
+    
+    var exportProgress = function (){
+    	var rdigit = /\d/;
+        $.ajax({
+            url:ajaxurl,
+            data:{action: 'duoshuo_export'},
+            error:duoshuoOnError,
+            success:function(response) {
+            	if (response.code == 0){
+            		if (rdigit.test(response.progress) && !isNaN(response.progress)){
+            	    	$('#ds-export').removeClass('ds-exporting').addClass('ds-exported');
+            		}
+            		else{
+            			var lang = {'user':'用户', 'post':'文章', 'comment':'评论'}, progress = response.progress.split('/');
+            			$('#ds-export .status').html('正在同步' + lang[progress[0]] + '/' + progress[1] + ' <img src="<?php echo $this->pluginDirUrl . 'images/waiting.gif';?>" />');
+            			exportProgress();
+            		}
+            	}
+            	else{
+                    alert(response.errorMessage);
+            	}
+            },
+            dataType:'json'
+        });
+    };
+    exportProgress();
+    return false;
+}
+function duoshuoOnError(jqXHR, textStatus){
+    switch(textStatus){
+    case 'parsererror':
+    	alert('解析错误,联系多说客服帮助解决问题:' + jqXHR.responseText);
+    	break;
+    case "abort":
+    	break;
+    case "notmodified":
+    case "error":
+    case "timeout":
+    default:
+    	var dict = {
+    		notmodified	: '没有变化',
+        	error		: '出错了',
+        	timeout		: '超时了'
+        };
+        alert(dict[textStatus] + ', 联系多说客服帮助解决问题');
+    }
+}
+</script>

+ 899 - 0
compat-json.php

@@ -0,0 +1,899 @@
+<?php
+if ( !class_exists( 'Services_JSON' ) ) :
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+/**
+ * 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 <mike-json@teczno.com>
+ * @author		Matt Knapp <mdknapp[at]gmail[dot]com>
+ * @author		Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
+ * @copyright	2005 Michal Migurski
+ * @version     CVS: $Id: JSON.php 288200 2009-09-09 15:41:29Z alan_k $
+ * @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:
+ *
+ * <code>
+ * // 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);
+ * </code>
+ */
+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 (and sends JSON Header)
+	*
+	* @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)
+	{
+		header('Content-type: application/json');
+		return $this->_encode($var);
+	}
+	/**
+	* encodes an arbitrary variable into JSON format without JSON Header - warning - may allow CSS!!!!)
+	*
+	* @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 encodeUnsafe($var)
+	{
+		return $this->_encode($var);
+	}
+	/**
+	* PRIVATE CODE that does the work of 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
+							if ($c+1 >= $strlen_var) {
+								$c += 1;
+								$ascii .= '?';
+								break;
+							}
+
+							$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):
+							if ($c+2 >= $strlen_var) {
+								$c += 2;
+								$ascii .= '?';
+								break;
+							}
+							// 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):
+							if ($c+3 >= $strlen_var) {
+								$c += 3;
+								$ascii .= '?';
+								break;
+							}
+							// 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
+							if ($c+4 >= $strlen_var) {
+								$c += 4;
+								$ascii .= '?';
+								break;
+							}
+							$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):
+						if ($c+5 >= $strlen_var) {
+								$c += 5;
+								$ascii .= '?';
+								break;
+							}
+							// 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)
+		{
+
+		}
+	}
+
+}
+endif;
+
+
+/**
+ * 为了兼容低版本的php而增加的函数集
+ * json从5.2.0开始支持,wordpress 2.9开始提供json函数的兼容性代码
+ */
+if ( !function_exists('json_encode') ) {
+	function json_encode( $string ) {
+		global $wp_json;
+
+		if ( !is_a($wp_json, 'Services_JSON') ) {
+			$wp_json = new Services_JSON();
+		}
+
+		return $wp_json->encodeUnsafe( $string );
+	}
+}
+
+if ( !function_exists('json_decode') ) {
+	function json_decode( $string, $assoc_array = false ) {
+		global $wp_json;
+
+		if ( !is_a($wp_json, 'Services_JSON') ) {
+			$wp_json = new Services_JSON();
+		}
+
+		$res = $wp_json->decode( $string );
+		if ( $assoc_array )
+			$res = _json_decode_object_helper( $res );
+		return $res;
+	}
+	function _json_decode_object_helper($data) {
+		if ( is_object($data) )
+			$data = get_object_vars($data);
+		return is_array($data) ? array_map(__FUNCTION__, $data) : $data;
+	}
+}

+ 18 - 0
config.php

@@ -0,0 +1,18 @@
+<link rel="stylesheet" href="<?php echo $this->pluginDirUrl; ?>styles.css" type="text/css" />
+<div class="wrap">
+<?php echo screen_icon();?><h2>注册站点</h2>
+<?php
+$user = wp_get_current_user();
+$params = $this->packageOptions() + array(
+	'system'	=>	'wordpress',
+	'callback'	=>	admin_url('admin.php?page=duoshuo'),
+	'user_key'	=>	$user->ID,
+	'user_name'	=>	$user->display_name,
+	'sync_log'	=>	1,
+);
+
+$adminUrl = is_ssl() ? 'https://' : 'http://';
+$adminUrl .=  self::DOMAIN . '/connect-site/?'. http_build_query($params, null, '&')
+?>
+<iframe id="duoshuo-remote-window" src="<?php echo $adminUrl;?>" style="width:100%;height:580px;"></iframe>
+</div>

+ 376 - 0
duoshuo.php

@@ -0,0 +1,376 @@
+<?php
+/*
+Plugin Name: 多说
+Plugin URI: http://wordpress.org/extend/plugins/duoshuo/
+Description: 追求最佳用户体验的社会化评论框,为中小网站提供“新浪微博、QQ、人人、豆瓣等多帐号登录并评论”功能。“多说”帮你搭建更活跃,互动性更强的评论平台,功能强大且永久免费。
+Author: 多说网
+Version: 1.2
+Author URI: http://duoshuo.com/
+*/
+
+define('DUOSHUO_PLUGIN_PATH', dirname(__FILE__));
+
+if (version_compare(PHP_VERSION, '5.0.0', '<')){
+	if(is_admin()){
+		function duoshuo_php_version_warning(){
+			echo '<div class="updated"><p><strong>您的php版本低于5.0,请升级php到最新版,多说就能为您服务了。</strong></p></div>';
+		}
+		add_action('admin_notices', 'duoshuo_php_version_warning');
+	}
+	return;
+}
+
+if (version_compare( $wp_version, '2.8', '<' )){
+	if(is_admin()){
+		function duoshuo_wp_version_warning(){
+			echo '<div class="updated"><p><strong>您的WordPress版本低于2.8,请升级WordPress到最新版,多说就能为您服务了。</strong></p></div>';
+		}
+		add_action('admin_notices', 'duoshuo_wp_version_warning');
+	}
+	return;
+}
+
+function duoshuo_get_available_transport(){
+	if (extension_loaded('curl') && function_exists('curl_init') && function_exists('curl_exec'))
+		return 'curl';
+	
+	if (function_exists('fopen') && function_exists('ini_get') && ini_get('allow_url_fopen'))
+		return 'streams';
+	
+	if (function_exists('fsockopen') && (false === ($option = get_option( 'disable_fsockopen' )) || time() - $option >= 43200))
+		return 'fsockopen';
+	
+	return false;
+}
+
+$transport = duoshuo_get_available_transport();
+if ($transport === false){
+	if(is_admin()){
+		function duoshuo_transport_warning(){
+			echo '<div class="updated"><p><strong>没有可用的 HTTP 传输器</strong>,请联系你的主机商,安装或开启curl</p></div>';
+		}
+		add_action('admin_notices', 'duoshuo_transport_warning');
+	}
+	return;
+}
+
+if (!extension_loaded('json'))
+	include DUOSHUO_PLUGIN_PATH . '/compat-json.php';
+
+require DUOSHUO_PLUGIN_PATH . '/Exception.php';
+require DUOSHUO_PLUGIN_PATH . '/Client.php';
+require DUOSHUO_PLUGIN_PATH . '/Abstract.php';
+require DUOSHUO_PLUGIN_PATH . '/WordPress.php';
+
+function duoshuo_admin_initialize(){
+	global $wp_version, $duoshuoPlugin, $plugin_page;
+	
+	//在admin界面内执行的action
+	// wordpress2.8 以后都支持这个过滤器
+	add_filter('plugin_action_links_duoshuo/duoshuo.php', array($duoshuoPlugin, 'pluginActionLinks'), 10, 2);
+	
+	if (empty($duoshuoPlugin->shortName) || empty($duoshuoPlugin->secret)){//你尚未安装这个插件。
+		function duoshuo_config_warning(){
+			echo '<div class="updated"><p><strong>只要再<a href="' . admin_url('admin.php?page=duoshuo') . '">配置一下</a>多说帐号,多说就能开始为您服务了。</strong></p></div>';
+		}
+		
+		if ($plugin_page !== 'duoshuo')
+			add_action('admin_notices', 'duoshuo_config_warning');
+		return ;
+	}
+	
+	add_action('admin_notices', array($duoshuoPlugin, 'notices'));
+	
+	add_action('switch_theme', array($duoshuoPlugin, 'updateSite'));
+	//	support from WP 2.9
+	//add_action('updated_option', array($duoshuoPlugin, 'updatedOption'));
+	
+	add_filter('post_row_actions', array($duoshuoPlugin, 'actionsFilter'));
+	
+	if (function_exists('get_post_types')){//	support from WP 2.9
+		$post_types = get_post_types( array('public' => true, 'show_in_nav_menus' => true), 'objects');
+		
+		foreach($post_types as $type => $object)
+			add_meta_box('duoshuo-sidebox', '同时发布到', array($duoshuoPlugin,'syncOptions'), $type, 'side', 'high');
+	}
+	else{
+		add_meta_box('duoshuo-sidebox', '同时发布到', array($duoshuoPlugin,'syncOptions'), 'post', 'side', 'high');
+		add_meta_box('duoshuo-sidebox', '同时发布到', array($duoshuoPlugin,'syncOptions'), 'page', 'side', 'high');
+	}
+	//wp 3.0以下不支持此项功能
+	/**
+	 * TODO 
+	if ($post !== null && 'publish' == $post->post_status || 'private' == $post->post_status)
+		add_meta_box('duoshuo-comments', '来自社交网站的评论(多说)', array($duoshuoPlugin,'managePostComments'), 'post', 'normal', 'low');
+	 */
+	
+	add_action('post_comment_status_meta_box-options', array($duoshuoPlugin, 'commentStatusMetaBoxOptions'));
+	
+	add_action('wp_dashboard_setup', 'duoshuo_add_dashboard_widget');
+	
+	//// backwards compatible (before WP 3.0)
+	if (version_compare( $wp_version, '3.0', '<' ) && current_user_can('administrator')){
+		function duoshuo_wp_version_notice(){
+			echo '<div class="updated"><p>您的WordPress版本低于3.0,如果您能升级WordPress,多说就能更好地为您服务。</p></div>';
+		}
+		add_action(get_plugin_page_hook('duoshuo', 'duoshuo'), 'duoshuo_wp_version_notice');
+		add_action(get_plugin_page_hook('duoshuo-preferences', 'duoshuo'), 'duoshuo_wp_version_notice');
+		add_action(get_plugin_page_hook('duoshuo-settings', 'duoshuo'), 'duoshuo_wp_version_notice');
+	}
+	
+	if (!is_numeric($duoshuoPlugin->getOption('synchronized')) && current_user_can('administrator')){
+		function duoshuo_unsynchronized_notice(){
+			echo '<div class="updated"><p>上一次同步没有完成,<a href="' . admin_url('admin.php?page=duoshuo-settings') . '">点此继续同步</a></p></div>';
+		}
+		
+		add_action(get_plugin_page_hook('duoshuo', 'duoshuo'), 'duoshuo_unsynchronized_notice');
+		add_action(get_plugin_page_hook('duoshuo-preferences', 'duoshuo'), 'duoshuo_unsynchronized_notice');
+		add_action(get_plugin_page_hook('duoshuo-settings', 'duoshuo'), 'duoshuo_unsynchronized_notice');
+	}
+	
+	add_action('admin_head-edit-comments.php', array($duoshuoPlugin, 'originalCommentsNotice'));
+	
+	if (defined('DOING_AJAX')){
+		add_action('wp_ajax_duoshuo_export', array($duoshuoPlugin, 'export'));
+		add_action('wp_ajax_duoshuo_sync_log', array($duoshuoPlugin, 'syncLogAction'));
+	}
+	
+	duoshuo_common_initialize();
+}
+	
+function duoshuo_initialize(){
+	global $duoshuoPlugin;
+	
+	if (empty($duoshuoPlugin->shortName) || empty($duoshuoPlugin->secret)){
+		return;
+	}
+	
+	if ($duoshuoPlugin->getOption('social_login_enabled')){
+		add_action('login_form', array($duoshuoPlugin, 'loginForm'));
+		add_action('register_form', array($duoshuoPlugin, 'loginForm'));
+	}
+	
+	// wp2.8 以后支持这个事件
+	add_action(get_option('duoshuo_postpone_print_scripts') ? 'wp_print_footer_scripts' : 'wp_print_scripts', array($duoshuoPlugin, 'appendScripts'));
+	
+	//以下应该根据是否设置,选择是否启用
+	add_filter('comments_template', array($duoshuoPlugin,'commentsTemplate'));
+	
+	if (get_option('duoshuo_cc_fix')){ //直接输出HTML评论
+		add_filter('comments_popup_link_attributes', array($duoshuoPlugin, 'commentsPopupLinkAttributes'));
+		add_filter('comments_number', array($duoshuoPlugin, 'commentsText'));
+	}
+	
+	if (get_option('duoshuo_sync_pingback_and_trackback')){
+		add_action('trackback_post', array($duoshuoPlugin, 'exportOneComment'));
+		add_action('pingback_post', array($duoshuoPlugin, 'exportOneComment'));
+	}
+	
+	duoshuo_common_initialize();
+}
+
+function duoshuo_common_initialize(){
+	global $duoshuoPlugin;
+	// 没有用cookie方式保持身份,所以不需要重定向
+	//add_action('wp_logout', array($duoshuoPlugin, 'logout'));
+	add_filter('comments_open', array($duoshuoPlugin, 'commentsOpen'), 10, 2);
+	add_action('set_auth_cookie', array($duoshuoPlugin, 'setJwtCookie'), 10, 5);
+	add_action('clear_auth_cookie', array($duoshuoPlugin, 'clearJwtCookie'));
+	
+	add_action('profile_update', array($duoshuoPlugin, 'syncUserToRemote'));
+	add_action('user_register', array($duoshuoPlugin, 'userRegisterHook'));
+	add_action('wp_login', array($duoshuoPlugin, 'bindUser'), 10, 2);
+	
+	if ($duoshuoPlugin->getOption('cron_sync_enabled')){
+		add_action('duoshuo_sync_log_cron', array($duoshuoPlugin, 'syncLog'));
+		if (!wp_next_scheduled('duoshuo_sync_log_cron')){
+			wp_schedule_event(time(), 'hourly', 'duoshuo_sync_log_cron');
+		}
+	}
+}
+
+// Register widgets.
+function duoshuo_register_widgets(){
+	require_once dirname(__FILE__) . '/widgets.php';
+	
+	register_widget('Duoshuo_Widget_Recent_Visitors');
+	//register_widget('Duoshuo_Widget_Top_Commenters');
+	
+	register_widget('Duoshuo_Widget_Recent_Comments');
+	register_widget('Duoshuo_Widget_Top_Threads');
+	
+	register_widget('Duoshuo_Widget_Qqt_Follow');
+}
+
+function duoshuo_add_pages() {
+	global $duoshuoPlugin;
+	
+	if (empty($duoshuoPlugin->shortName) || empty($duoshuoPlugin->secret)){	//	尚未安装
+		add_object_page(
+			'安装',
+			'多说评论',
+			'moderate_comments',	//	权限
+			'duoshuo',
+			array($duoshuoPlugin, 'config'),
+			$duoshuoPlugin->pluginDirUrl . 'images/menu-icon.png' 
+		);
+	}
+	else{	// 已经安装成功
+		if (current_user_can('moderate_comments')){
+			if(get_option('duoshuo_synchronized') === false){
+				add_object_page(
+					'数据同步',
+					'多说评论',
+					'moderate_comments',
+					'duoshuo',
+					array($duoshuoPlugin, 'sync'),
+					$duoshuoPlugin->pluginDirUrl . 'images/menu-icon.png'
+				);
+				add_submenu_page(
+					'duoshuo',
+					'多说评论管理',
+					'评论管理',
+					'moderate_comments',
+					'duoshuo-manage',
+					array($duoshuoPlugin,'manage')
+				);
+			}
+			else{
+				add_object_page(
+					'多说评论管理',
+					'多说评论',
+					'moderate_comments',
+					'duoshuo',
+					array($duoshuoPlugin,'manage'),
+					$duoshuoPlugin->pluginDirUrl . 'images/menu-icon.png' 
+				);
+			}
+			add_submenu_page(
+		         'duoshuo',//$parent_slug
+		         '个性化设置',//page_title
+		         '个性化设置',//menu_title
+		         'manage_options',//权限
+		         'duoshuo-preferences',//menu_slug
+		         array($duoshuoPlugin, 'preferences')//function
+		    );
+			add_submenu_page(
+		         'duoshuo',//$parent_slug
+		         '主题设置',//page_title
+		         '主题设置',//menu_title
+		         'manage_options',//权限
+		         'duoshuo-themes',//menu_slug
+		         array($duoshuoPlugin, 'themes')//function
+		    );
+		    add_submenu_page(
+		         'duoshuo',//$parent_slug
+		         '高级选项',//page_title
+		         '高级选项',//menu_title
+		         'manage_options',//权限
+		         'duoshuo-settings',//menu_slug
+		         array($duoshuoPlugin, 'settings')//function
+		    );
+		    add_submenu_page(
+		         'duoshuo',//$parent_slug
+		         '数据统计',//page_title
+		         '数据统计',//menu_title
+		         'manage_options',//权限
+		         'duoshuo-statistics',//menu_slug
+		         array($duoshuoPlugin, 'statistics')//function
+		    );
+		    add_submenu_page(
+		         'duoshuo',//$parent_slug
+		         '我的多说帐号',//page_title
+		         '我的多说帐号',//menu_title
+		         'level_0',//权限
+		         'duoshuo-profile',//menu_slug
+		         array($duoshuoPlugin, 'profile')//function
+		    );
+		}
+		elseif(current_user_can('level_0')){
+			add_submenu_page(
+			'profile.php',//$parent_slug
+			'我的多说帐号',//page_title
+			'我的多说帐号',//menu_title
+			'level_0',//权限
+			'duoshuo-profile',//menu_slug
+			array($duoshuoPlugin, 'profile')//function
+			);
+		}
+	}
+}
+
+function duoshuo_add_dashboard_widget(){
+	global $duoshuoPlugin;
+	
+	wp_add_dashboard_widget('dashboard_duoshuo', '多说最新评论', array($duoshuoPlugin, 'dashboardWidget'), array($duoshuoPlugin, 'dashboardWidgetControl'));
+}
+
+function duoshuo_request_handler(){
+	global $duoshuoPlugin, $parent_file;
+	
+	if ($_SERVER['REQUEST_METHOD'] == 'POST'){
+		switch ($parent_file){
+			case 'duoshuo':
+				if (isset($_POST['duoshuo_reset']))
+					$duoshuoPlugin->reset();
+				if (isset($_POST['duoshuo_local_options']))
+					$duoshuoPlugin->updateLocalOptions();
+				break;
+			default:
+		}
+	}
+	elseif ($_SERVER['REQUEST_METHOD'] == 'GET'){
+		switch ($parent_file){
+			case 'options-general.php':
+				if (isset($_GET['settings-updated']))
+					$duoshuoPlugin->updateSite();
+				break;
+			case 'duoshuo':
+				if (isset($_GET['duoshuo_connect_site']))
+					$duoshuoPlugin->connectSite();
+				if (isset($_GET['duoshuo_theme'])){
+					update_option('duoshuo_theme', $_GET['duoshuo_theme']);
+				}
+				break;
+			default:
+		}
+	}
+}
+
+function duoshuo_deactivate($network_wide = false){
+	//	升级插件的时候也会停用插件
+	//delete_option('duoshuo_synchronized');
+}
+
+
+$duoshuoPlugin = Duoshuo_WordPress::getInstance();
+
+if(is_admin()){//在admin界面内执行的action
+	register_deactivation_hook(__FILE__, 'duoshuo_deactivate');
+	add_action('admin_menu', 'duoshuo_add_pages', 10);
+	add_action('admin_init', 'duoshuo_request_handler');
+	add_action('admin_init', array($duoshuoPlugin, 'registerSettings'));
+	add_action('admin_init', 'duoshuo_admin_initialize');
+}
+else{
+	add_action('init', 'duoshuo_initialize');
+	add_action('login_form_duoshuo_login', array($duoshuoPlugin, 'oauthConnect'));
+	//add_action('login_form_duoshuo_logout', array($duoshuoPlugin,'oauthDisconnect'));
+}
+
+add_action('widgets_init', 'duoshuo_register_widgets');
+
+add_action('save_post', array($duoshuoPlugin, 'savePostDuoshuoStatus'));
+add_action('save_post', array($duoshuoPlugin, 'syncPostToRemote'), 10, 2);
+
+/*
+if (function_exists('get_post_types')){	//	cron jobs runs in common mode, sometimes
+	foreach(get_post_types() as $type)
+		if ($type !== 'nav_menu_item' && $type !== 'revision')
+			add_action('publish_' . $type, array($duoshuoPlugin,'syncPostToRemote'));
+}
+else{
+	add_action('publish_post', array($duoshuoPlugin,'syncPostToRemote'));
+	add_action('publish_page', array($duoshuoPlugin,'syncPostToRemote'));
+}
+*/

File diff suppressed because it is too large
+ 2139 - 0
embed-formatted-v1.2.js


二進制
images/head-icon.gif


二進制
images/icon.gif


二進制
images/menu-icon.png


二進制
images/service_icons_32x32.png


二進制
images/waiting.gif


+ 3 - 0
index.php

@@ -0,0 +1,3 @@
+<?php
+// Silence is golden.
+?>

+ 29 - 0
manage.php

@@ -0,0 +1,29 @@
+<link rel="stylesheet" href="<?php echo $this->pluginDirUrl; ?>styles.css" type="text/css" />
+<?php
+$params = array(
+	'jwt'	=>	$this->jwt(),
+);
+
+$adminUrl= is_ssl() ? 'https://' : 'http://';
+$adminUrl .= $this->shortName . '.' . self::DOMAIN.'/admin/?' . http_build_query($params, null, '&');
+
+?>
+<div class="wrap">
+<?php screen_icon(); ?>
+<h2>多说评论管理
+<a class="add-new-h2" target="_blank" href="<?php echo $adminUrl;?>">在新窗口中打开</a>
+</h2>
+<iframe id="duoshuo-remote-window" src="<?php echo $adminUrl;?>" style="width:100%;"></iframe>
+</div>
+
+<script>
+jQuery(function(){
+var $ = jQuery,
+	iframe = $('#duoshuo-remote-window'),
+	resetIframeHeight = function(){
+		iframe.height($(window).height() - iframe.offset().top - 70);
+	};
+resetIframeHeight();
+$(window).resize(resetIframeHeight);
+});
+</script>

+ 334 - 0
nanoSha2.php

@@ -0,0 +1,334 @@
+<?php
+/*
+ * Transparent SHA-256 Implementation for PHP 4 and PHP 5
+ *
+ * Author: Perry McGee (pmcgee@nanolink.ca)
+ * Website: http://www.nanolink.ca/pub/sha256
+ *
+ * Copyright (C) 2006,2007,2008,2009 Nanolink Solutions
+ *
+ * Created: Feb 11, 2006
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation; either
+ *    version 2.1 of the License, or (at your option) any later version.
+ *
+ *    This library 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.  See the GNU
+ *    Lesser General Public License for more details.
+
+ *    You should have received a copy of the GNU Lesser General Public
+ *    License along with this library; if not, write to the Free Software
+ *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *    or see <http://www.gnu.org/licenses/>.
+ *
+ *  Include:
+ *
+ *   require_once("[path/]sha256.inc.php");
+ *
+ *  Usage Options:
+ *
+ *   1) $shaStr = hash('sha256', $string_to_hash);
+ *
+ *   2) $shaStr = sha256($string_to_hash[, bool ignore_php5_hash = false]);
+ *
+ *   3) $obj = new nanoSha2([bool $upper_case_output = false]);
+ *      $shaStr = $obj->hash($string_to_hash[, bool $ignore_php5_hash = false]);
+ *
+ * Reference: http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html
+ *
+ * 2007-12-13: Cleaned up for initial public release
+ * 2008-05-10: Moved all helper functions into a class.  API access unchanged.
+ * 2009-06-23: Created abstraction of hash() routine
+ * 2009-07-23: Added detection of 32 vs 64bit platform, and patches.
+ *             Ability to define "_NANO_SHA2_UPPER" to yeild upper case hashes.
+ * 2009-08-01: Added ability to attempt to use mhash() prior to running pure
+ *             php code.
+ *
+ * NOTE: Some sporadic versions of PHP do not handle integer overflows the
+ *       same as the majority of builds.  If you get hash results of:
+ *        7fffffff7fffffff7fffffff7fffffff7fffffff7fffffff7fffffff7fffffff
+ *
+ *       If you do not have permissions to change PHP versions (if you did
+ *       you'd probably upgrade to PHP 5 anyway) it is advised you install a
+ *       module that will allow you to use their hashing routines, examples are:
+ *       - mhash module : http://ca3.php.net/mhash
+ *       - Suhosin : http://www.hardened-php.net/suhosin/
+ *
+ *       If you install the Suhosin module, this script will transparently
+ *       use their routine and define the PHP routine as _nano_sha256().
+ *
+ *       If the mhash module is present, and $ignore_php5_hash = false the
+ *       script will attempt to use the output from mhash prior to running
+ *       the PHP code.
+ */
+    class nanoSha2
+    {
+        // php 4 - 5 compatable class properties
+        var     $toUpper;
+        var     $platform;
+
+        // Php 4 - 6 compatable constructor
+        function nanoSha2($toUpper = false) {
+            // Determine if the caller wants upper case or not.
+            $this->toUpper = is_bool($toUpper)
+                           ? $toUpper
+                           : ((defined('_NANO_SHA2_UPPER')) ? true : false);
+
+            // Deteremine if the system is 32 or 64 bit.
+            $tmpInt = (int)4294967295;
+            $this->platform = ($tmpInt > 0) ? 64 : 32;
+        }
+
+        // Do the SHA-256 Padding routine (make input a multiple of 512 bits)
+        function char_pad($str)
+        {
+            $tmpStr = $str;
+
+            $l = strlen($tmpStr)*8;     // # of bits from input string
+
+            $tmpStr .= "\x80";          // append the "1" bit followed by 7 0's
+
+            $k = (512 - (($l + 8 + 64) % 512)) / 8;   // # of 0 bytes to append
+            $k += 4;    // PHP Strings will never exceed (2^31)-1, 1st 32bits of
+                        // the 64-bit value representing $l can be all 0's
+
+            for ($x = 0; $x < $k; $x++) {
+                $tmpStr .= "\0";
+            }
+
+            // append the 32-bits representing # of bits from input string ($l)
+            $tmpStr .= chr((($l>>24) & 0xFF));
+            $tmpStr .= chr((($l>>16) & 0xFF));
+            $tmpStr .= chr((($l>>8) & 0xFF));
+            $tmpStr .= chr(($l & 0xFF));
+
+            return $tmpStr;
+        }
+
+        // Here are the bitwise and functions as defined in FIPS180-2 Standard
+        function addmod2n($x, $y, $n = 4294967296)      // Z = (X + Y) mod 2^32
+        {
+            $mask = 0x80000000;
+
+            if ($x < 0) {
+                $x &= 0x7FFFFFFF;
+                $x = (float)$x + $mask;
+            }
+
+            if ($y < 0) {
+                $y &= 0x7FFFFFFF;
+                $y = (float)$y + $mask;
+            }
+
+            $r = $x + $y;
+
+            if ($r >= $n) {
+                while ($r >= $n) {
+                    $r -= $n;
+                }
+            }
+
+            return (int)$r;
+        }
+
+        // Logical bitwise right shift (PHP default is arithmetic shift)
+        function SHR($x, $n)        // x >> n
+        {
+            if ($n >= 32) {      // impose some limits to keep it 32-bit
+                return (int)0;
+            }
+
+            if ($n <= 0) {
+                return (int)$x;
+            }
+
+            $mask = 0x40000000;
+
+            if ($x < 0) {
+                $x &= 0x7FFFFFFF;
+                $mask = $mask >> ($n-1);
+                return ($x >> $n) | $mask;
+            }
+
+            return (int)$x >> (int)$n;
+        }
+
+        function ROTR($x, $n) { return (int)(($this->SHR($x, $n) | ($x << (32-$n)) & 0xFFFFFFFF)); }
+        function Ch($x, $y, $z) { return ($x & $y) ^ ((~$x) & $z); }
+        function Maj($x, $y, $z) { return ($x & $y) ^ ($x & $z) ^ ($y & $z); }
+        function Sigma0($x) { return (int) ($this->ROTR($x, 2)^$this->ROTR($x, 13)^$this->ROTR($x, 22)); }
+        function Sigma1($x) { return (int) ($this->ROTR($x, 6)^$this->ROTR($x, 11)^$this->ROTR($x, 25)); }
+        function sigma_0($x) { return (int) ($this->ROTR($x, 7)^$this->ROTR($x, 18)^$this->SHR($x, 3)); }
+        function sigma_1($x) { return (int) ($this->ROTR($x, 17)^$this->ROTR($x, 19)^$this->SHR($x, 10)); }
+
+        /*
+         * Custom functions to provide PHP support
+         */
+        // split a byte-string into integer array values
+        function int_split($input)
+        {
+            $l = strlen($input);
+
+            if ($l <= 0) {
+                return (int)0;
+            }
+
+            if (($l % 4) != 0) { // invalid input
+                return false;
+            }
+
+            for ($i = 0; $i < $l; $i += 4)
+            {
+                $int_build  = (ord($input[$i]) << 24);
+                $int_build += (ord($input[$i+1]) << 16);
+                $int_build += (ord($input[$i+2]) << 8);
+                $int_build += (ord($input[$i+3]));
+
+                $result[] = $int_build;
+            }
+
+            return $result;
+        }
+
+        /**
+         * Process and return the hash.
+         *
+         * @param $str Input string to hash
+         * @param $ig_func Option param to ignore checking for php > 5.1.2
+         * @return string Hexadecimal representation of the message digest
+         */
+        function hash($str, $ig_func = false)
+        {
+            unset($binStr);     // binary representation of input string
+            unset($hexStr);     // 256-bit message digest in readable hex format
+
+            // check for php's internal sha256 function, ignore if ig_func==true
+            if ($ig_func == false) {
+                if (version_compare(PHP_VERSION,'5.1.2','>=')) {
+                    return hash("sha256", $str, false);
+                } else if (function_exists('mhash') && defined('MHASH_SHA256')) {
+                    return base64_encode(bin2hex(mhash(MHASH_SHA256, $str)));
+                }
+            }
+
+            /*
+             * SHA-256 Constants
+             *  Sequence of sixty-four constant 32-bit words representing the
+             *  first thirty-two bits of the fractional parts of the cube roots
+             *  of the first sixtyfour prime numbers.
+             */
+            $K = array((int)0x428a2f98, (int)0x71374491, (int)0xb5c0fbcf,
+                       (int)0xe9b5dba5, (int)0x3956c25b, (int)0x59f111f1,
+                       (int)0x923f82a4, (int)0xab1c5ed5, (int)0xd807aa98,
+                       (int)0x12835b01, (int)0x243185be, (int)0x550c7dc3,
+                       (int)0x72be5d74, (int)0x80deb1fe, (int)0x9bdc06a7,
+                       (int)0xc19bf174, (int)0xe49b69c1, (int)0xefbe4786,
+                       (int)0x0fc19dc6, (int)0x240ca1cc, (int)0x2de92c6f,
+                       (int)0x4a7484aa, (int)0x5cb0a9dc, (int)0x76f988da,
+                       (int)0x983e5152, (int)0xa831c66d, (int)0xb00327c8,
+                       (int)0xbf597fc7, (int)0xc6e00bf3, (int)0xd5a79147,
+                       (int)0x06ca6351, (int)0x14292967, (int)0x27b70a85,
+                       (int)0x2e1b2138, (int)0x4d2c6dfc, (int)0x53380d13,
+                       (int)0x650a7354, (int)0x766a0abb, (int)0x81c2c92e,
+                       (int)0x92722c85, (int)0xa2bfe8a1, (int)0xa81a664b,
+                       (int)0xc24b8b70, (int)0xc76c51a3, (int)0xd192e819,
+                       (int)0xd6990624, (int)0xf40e3585, (int)0x106aa070,
+                       (int)0x19a4c116, (int)0x1e376c08, (int)0x2748774c,
+                       (int)0x34b0bcb5, (int)0x391c0cb3, (int)0x4ed8aa4a,
+                       (int)0x5b9cca4f, (int)0x682e6ff3, (int)0x748f82ee,
+                       (int)0x78a5636f, (int)0x84c87814, (int)0x8cc70208,
+                       (int)0x90befffa, (int)0xa4506ceb, (int)0xbef9a3f7,
+                       (int)0xc67178f2);
+
+            // Pre-processing: Padding the string
+            $binStr = $this->char_pad($str);
+
+            // Parsing the Padded Message (Break into N 512-bit blocks)
+            $M = str_split($binStr, 64);
+
+            // Set the initial hash values
+            $h[0] = (int)0x6a09e667;
+            $h[1] = (int)0xbb67ae85;
+            $h[2] = (int)0x3c6ef372;
+            $h[3] = (int)0xa54ff53a;
+            $h[4] = (int)0x510e527f;
+            $h[5] = (int)0x9b05688c;
+            $h[6] = (int)0x1f83d9ab;
+            $h[7] = (int)0x5be0cd19;
+
+            // loop through message blocks and compute hash. ( For i=1 to N : )
+            $N = count($M);
+            for ($i = 0; $i < $N; $i++)
+            {
+                // Break input block into 16 32bit words (message schedule prep)
+                $MI = $this->int_split($M[$i]);
+
+                // Initialize working variables
+                $_a = (int)$h[0];
+                $_b = (int)$h[1];
+                $_c = (int)$h[2];
+                $_d = (int)$h[3];
+                $_e = (int)$h[4];
+                $_f = (int)$h[5];
+                $_g = (int)$h[6];
+                $_h = (int)$h[7];
+                unset($_s0);
+                unset($_s1);
+                unset($_T1);
+                unset($_T2);
+                $W = array();
+
+                // Compute the hash and update
+                for ($t = 0; $t < 16; $t++)
+                {
+                    // Prepare the first 16 message schedule values as we loop
+                    $W[$t] = $MI[$t];
+
+                    // Compute hash
+                    $_T1 = $this->addmod2n($this->addmod2n($this->addmod2n($this->addmod2n($_h, $this->Sigma1($_e)), $this->Ch($_e, $_f, $_g)), $K[$t]), $W[$t]);
+                    $_T2 = $this->addmod2n($this->Sigma0($_a), $this->Maj($_a, $_b, $_c));
+
+                    // Update working variables
+                    $_h = $_g; $_g = $_f; $_f = $_e; $_e = $this->addmod2n($_d, $_T1);
+                    $_d = $_c; $_c = $_b; $_b = $_a; $_a = $this->addmod2n($_T1, $_T2);
+                }
+
+                for (; $t < 64; $t++)
+                {
+                    // Continue building the message schedule as we loop
+                    $_s0 = $W[($t+1)&0x0F];
+                    $_s0 = $this->sigma_0($_s0);
+                    $_s1 = $W[($t+14)&0x0F];
+                    $_s1 = $this->sigma_1($_s1);
+
+                    $W[$t&0xF] = $this->addmod2n($this->addmod2n($this->addmod2n($W[$t&0xF], $_s0), $_s1), $W[($t+9)&0x0F]);
+
+                    // Compute hash
+                    $_T1 = $this->addmod2n($this->addmod2n($this->addmod2n($this->addmod2n($_h, $this->Sigma1($_e)), $this->Ch($_e, $_f, $_g)), $K[$t]), $W[$t&0xF]);
+                    $_T2 = $this->addmod2n($this->Sigma0($_a), $this->Maj($_a, $_b, $_c));
+
+                    // Update working variables
+                    $_h = $_g; $_g = $_f; $_f = $_e; $_e = $this->addmod2n($_d, $_T1);
+                    $_d = $_c; $_c = $_b; $_b = $_a; $_a = $this->addmod2n($_T1, $_T2);
+                }
+
+                $h[0] = $this->addmod2n($h[0], $_a);
+                $h[1] = $this->addmod2n($h[1], $_b);
+                $h[2] = $this->addmod2n($h[2], $_c);
+                $h[3] = $this->addmod2n($h[3], $_d);
+                $h[4] = $this->addmod2n($h[4], $_e);
+                $h[5] = $this->addmod2n($h[5], $_f);
+                $h[6] = $this->addmod2n($h[6], $_g);
+                $h[7] = $this->addmod2n($h[7], $_h);
+            }
+
+            // Convert the 32-bit words into human readable hexadecimal format.
+            $hexStr = sprintf("%08x%08x%08x%08x%08x%08x%08x%08x", $h[0], $h[1], $h[2], $h[3], $h[4], $h[5], $h[6], $h[7]);
+
+            return ($this->toUpper) ? strtoupper($hexStr) : $hexStr;
+        }
+
+    }

+ 11 - 0
oauth-proxy.php

@@ -0,0 +1,11 @@
+<?php
+if (empty($_GET['service']))
+	return;
+
+$endpoint =  (is_ssl()?'https':'http').'//duoshuo.com/login-callback/' . strtolower(trim($_GET['service'])) . '/';
+
+unset($_GET['service']);
+
+$callbackUrl = $endpoint . '?' . http_build_query($_GET, null, '&');
+
+header('Location: ' . $callbackUrl, true);

+ 28 - 0
preferences.php

@@ -0,0 +1,28 @@
+<link rel="stylesheet" href="<?php echo $this->pluginDirUrl; ?>styles.css" type="text/css" />
+<?php
+$params = array(
+	'jwt'	=>	$this->jwt(),
+);
+
+$adminUrl = is_ssl() ? 'https://' : 'http://';
+$adminUrl .= $this->shortName . '.' . self::DOMAIN . '/admin/settings/?' . http_build_query($params, null, '&');
+
+?>
+<div class="wrap">
+<?php screen_icon(); ?>
+<h2>多说评论框设置
+<a class="add-new-h2" target="_blank" href="<?php echo $adminUrl;?>">在新窗口中打开</a></h2>
+<iframe id="duoshuo-remote-window" src="<?php echo $adminUrl;?>" style="width:100%;"></iframe>
+</div>
+
+<script>
+jQuery(function(){
+var $ = jQuery,
+	iframe = $('#duoshuo-remote-window'),
+	resetIframeHeight = function(){
+		iframe.height($(window).height() - iframe.offset().top - 70);
+	};
+resetIframeHeight();
+$(window).resize(resetIframeHeight);
+});
+</script>

+ 16 - 0
profile.php

@@ -0,0 +1,16 @@
+<?php
+$params = array(
+	'jwt'	=>	$this->jwt(),
+);
+$settingsUrl = is_ssl()? 'https://' : 'http://';
+$settingsUrl .= self::DOMAIN . '/settings/?' . http_build_query($params, null, '&');
+?>
+<link rel="stylesheet" href="<?php echo $this->pluginDirUrl; ?>styles.css" type="text/css" />
+
+<div class="wrap">
+<?php screen_icon(); ?>
+<h2>我的多说帐号
+	<a class="add-new-h2" target="_blank" href="<?php echo $settingsUrl;?>">在新窗口中打开</a>
+</h2>
+<iframe id="duoshuo-remote-window" src="<?php echo $settingsUrl;?>" style="width:960px;height:750px;"></iframe>
+</div>

File diff suppressed because it is too large
+ 382 - 0
readme.txt


二進制
screenshot-1.png


二進制
screenshot-2.png


二進制
screenshot-3.png


二進制
screenshot-4.png


二進制
screenshot-5.png


二進制
screenshot-6.jpg


+ 211 - 0
settings.php

@@ -0,0 +1,211 @@
+<link rel="stylesheet" href="<?php echo $this->pluginDirUrl; ?>styles.css" type="text/css" />
+<div class="wrap">
+<?php screen_icon(); ?>
+<h2>多说评论框设置</h2>
+
+<h3>高级设定</h3>
+<form action="" method="post">
+<?php wp_nonce_field('duoshuo-local-options');?>
+	<table class="form-table">
+		<tbody>
+		<tr valign="top">
+			<th scope="row">多说域名</th>
+			<td><strong><?php echo get_option('duoshuo_short_name');?></strong>.duoshuo.com</td>
+		</tr>
+		<tr valign="top">
+			<th scope="row">密钥</th>
+			<td><?php echo get_option('duoshuo_secret');?></td>
+		</tr>
+		<tr valign="top">
+			<th scope="row">多说API服务器</th>
+			<td><?php $duoshuo_api_hostname = get_option('duoshuo_api_hostname');?>
+				<ul>
+					<li><label><input type="radio" name="duoshuo_api_hostname" value="api.duoshuo.com" <?php if ($duoshuo_api_hostname === 'api.duoshuo.com') echo ' checked="checked"';?>/>api.duoshuo.com</label> <span class="description">(如果你的博客服务器在国内,推荐)</span></li>
+					<li><label><input type="radio" name="duoshuo_api_hostname" value="api.duoshuo.org" <?php if ($duoshuo_api_hostname === 'api.duoshuo.org') echo ' checked="checked"';?>/>api.duoshuo.org</label> <span class="description">(如果你的博客服务器在国外,推荐)</span></li>
+					<li><label><input type="radio" name="duoshuo_api_hostname" value="118.144.80.201" <?php if ($duoshuo_api_hostname === '118.144.80.201') echo ' checked="checked"';?>/>118.144.80.201</label> <span class="description">(除非你的博客服务器DNS出现故障,否则不推荐)</span></li>
+				</ul>
+			</td>
+		</tr>
+		<tr valign="top">
+			<th scope="row">本地数据备份</th>
+			<td>
+				<input type="hidden" name="duoshuo_cron_sync_enabled" value="0">
+				<label><input type="checkbox" name="duoshuo_cron_sync_enabled" value="1" <?php if (get_option('duoshuo_cron_sync_enabled')) echo ' checked="checked"';?>/>定时从多说备份评论到本地</label></td>
+		</tr>
+		<tr valign="top">
+			<th scope="row">SEO优化</th>
+			<td>
+				<input type="hidden" name="duoshuo_seo_enabled" value="0">
+				<label><input type="checkbox" name="duoshuo_seo_enabled" value="1" <?php if (get_option('duoshuo_seo_enabled')) echo ' checked="checked"';?>/>搜索引擎爬虫访问网页时,显示静态HTML评论</label></td>
+		</tr>
+		<tr valign="top">
+			<th scope="row">Pingback和Trackback</th>
+			<td>
+				<input type="hidden" name="duoshuo_sync_pingback_and_trackback" value="0">
+				<label><input type="checkbox" name="duoshuo_sync_pingback_and_trackback" value="1" <?php if (get_option('duoshuo_sync_pingback_and_trackback')) echo ' checked="checked"';?>/>将接收到的Pingback和Trackback同步到多说</label></td>
+		</tr>
+		<tr valign="top">
+			<th scope="row">脚本后置</th>
+			<td>
+				<input type="hidden" name="duoshuo_postpone_print_scripts" value="0">
+				<label><input type="checkbox" name="duoshuo_postpone_print_scripts" value="1" <?php if (get_option('duoshuo_postpone_print_scripts')) echo ' checked="checked"';?> />在网页底部才插入多说核心脚本embed.js</label></td>
+		</tr>
+		<tr valign="top">
+			<th scope="row">主题适配</th>
+			<td>
+				<input type="hidden" name="duoshuo_style_patch" value="0">
+				<label><input type="checkbox" name="duoshuo_style_patch" value="1" <?php if (get_option('duoshuo_style_patch')) echo ' checked="checked"';?> />自动适配当前WordPress主题</label>
+				<p class="description"></p>
+			</td>
+		</tr>
+		<tr valign="top">
+			<th scope="row">评论数修正</th>
+			<td>
+				<input type="hidden" name="duoshuo_cc_fix" value="0">
+				<label><input type="checkbox" name="duoshuo_cc_fix" value="1" <?php if (get_option('duoshuo_cc_fix')) echo ' checked="checked"';?> />AJAX加载文章的评论数</label>
+				<p class="description">如果你的主题模板没有显示评论数,或者没有按照WordPress的标准,你可能需要修改模板。参见:<a href="http://dev.duoshuo.com/docs/50d7ecc6b2dcd51d2f0002e7">WordPress主题中的文章评论数</a></p>
+			</td>
+		</tr>
+		<tr valign="top">
+			<th scope="row">社交帐号登录</th>
+			<td>
+				<input type="hidden" name="duoshuo_social_login_enabled" value="0">
+				<label><input type="checkbox" name="duoshuo_social_login_enabled" value="1" <?php if (get_option('duoshuo_social_login_enabled')) echo ' checked="checked"';?>/>允许用社交帐号登录WordPress</label></td>
+		</tr>
+		<tr valign="top">
+			<th scope="row">评论框前缀</th>
+			<td><label><input type="text" class="regular-text code" name="duoshuo_comments_wrapper_intro" value="<?php echo esc_attr(get_option('duoshuo_comments_wrapper_intro'));?>" /></label><br /><span class="description">仅在主题和评论框的div嵌套不正确的情况下使用</span></td>
+		</tr>
+		<tr valign="top">
+			<th scope="row">评论框后缀</th>
+			<td><label><input type="text" class="regular-text code" name="duoshuo_comments_wrapper_outro" value="<?php echo esc_attr(get_option('duoshuo_comments_wrapper_outro'));?>" /></label><br /><span class="description">仅在主题和评论框的div嵌套不正确的情况下使用</span></td>
+		</tr>
+		<tr valign="top">
+			<th scope="row">调试开关</th>
+			<td>
+				<input type="hidden" name="duoshuo_debug" value="0">
+				<label><input type="checkbox" name="duoshuo_debug" value="1" <?php if (get_option('duoshuo_debug')) echo ' checked="checked"';?>/>Debug调试开关</label><br /><span class="description">仅在出现故障向多说汇报错误信息时打开</span></td>
+		</tr>
+		</tbody>
+	</table>
+	<p class="submit"><input type="submit" name="duoshuo_local_options" id="submit" class="button-primary" value="保存"></p>
+</form>
+
+<h3>数据同步</h3>
+<div id="ds-export">
+	<p class="message-start"><a href="javascript:void(0)" class="button" onclick="fireExport();return false;">同步本地数据库中的评论到多说</a></p>
+	<p class="status"></p>
+	<p class="message-complete">同步完成</p>
+</div>
+<div id="ds-sync">
+	<p class="message-start"><a href="javascript:void(0)" class="button" onclick="fireSyncLog();return false;">备份多说中的评论到本地数据库</a></p>
+	<p class="status"></p>
+</div>
+<?php include_once dirname(__FILE__) . '/common-script.html';?>
+
+<div>
+<h3>清空多说站点配置</h3>
+<form action="" method="post" onsubmit="return confirm('你确定要清空多说站点配置吗?');">
+	<input type="hidden" name="action" value="duoshuo_reset" />
+	<p>如果你希望本博客和其他多说站点进行绑定,或者创建新的多说站点,点此 <input type="submit" class="button" value="清空配置" name="duoshuo_reset" /></p>
+</form>
+</div>
+
+<div>
+	<h3>环境依赖检查</h3>
+	<table class="ds-dependencies">
+		<thead>
+			<tr>
+				<th>依赖</th>
+				<th>状态</th>
+				<th>结果</th>
+			</tr>
+		</thead>
+		<tbody>
+		<?php
+		$dependencies = array(
+			'php'		=>	'php版本',
+			'wordpress'	=>	'WordPress版本',
+			'json'		=>	'json扩展',
+			'curl'		=>	'curl扩展',
+			'fopen'		=>	'fopen()',
+			'fsockopen'	=>	'fsockopen()',
+			'hash_hmac'	=>	'hash_hmac()',
+		);
+		foreach($dependencies as $key => $name):
+			list($status, $result) = $this->checkDependency($key);?>
+			<tr>
+				<th><?php echo $name;?></th>
+				<td><?php echo $status === true ? '支持' : $status;?></td>
+				<td><?php echo $result === true ? '<span class="ds-icon-yes">OK</span>' : $result;?></td>
+			</tr>
+		<?php endforeach;?>
+		</tbody>
+	</table>
+	<p class="description">curl扩展、fopen()、fsockopen()只需支持一个即可,推荐使用curl扩展</p>
+</div>
+
+<h3>常见问题和参考链接</h3>
+<ul>
+	<li><a href="http://dev.duoshuo.com/docs/513b65c57c33a8320d003335" target="_blank">我的主题模板是自己开发的,启用多说之后评论框没有被替换怎么办?</a></li>
+	<li><a href="http://dev.duoshuo.com/docs/50d7ecc6b2dcd51d2f0002e7" target="_blank">多说WordPress插件常见问题</a></li>
+</ul>
+
+<h3>意见反馈</h3>
+<p>你的意见是多说成长的原动力,<a href="http://dev.duoshuo.com/wordpress-plugin" target="_blank">欢迎给我们留言</a>,或许你想要的功能下一个版本就会实现哦!</p>
+<p>多说正在招人!如果你相信改变世界不是资本而是技术;如果你不只是想完成任务,还希望你的巧妙构思实现意想不到的好处;如果你希望和跟你一样聪明的人一起工作。<a href="http://dev.duoshuo.com/threads/5138474ea7e92e7b60010bb9" target="_blank">那么你不妨加入我们!</a></p>
+<p>
+	<iframe width="120" height="23" frameborder="0" allowtransparency="true" marginwidth="0" marginheight="0" scrolling="no" frameborder="No" border="0" src="http://widget.weibo.com/relationship/followbutton.php?language=zh_cn&width=120&height=24&uid=2468548203&style=2&btn=red&dpc=1"></iframe>
+	<iframe id="previewmc" src="http://follow.v.t.qq.com/index.php?c=follow&a=quick&name=duo-shuo&style=3&t=1327999237149&f=1" allowtransparency="true" style="margin:0 auto;" frameborder="0" height="23" scrolling="no" width="100"></iframe>
+</p>
+<?php
+$services = array(
+	'qzone'	=>	'QQ空间',
+	'weibo'	=>	'新浪微博',
+	'qqt'	=>	'腾讯微博',
+	'renren'=>	'人人网',
+	'kaixin'=>	'开心网',
+	'douban'=>	'豆瓣网',
+	'netease'=>	'网易微博',
+	'sohu'	=>	'搜狐微博',
+);
+
+?>
+<h3>我们永远相信,分享是一种美德</h3>
+<p style="width:100%;overflow: hidden;">把多说分享给你的朋友:</p>
+<ul class="ds-share ds-service-icon">
+<?php foreach($services as $service => $serviceName):?>
+	<li><a class="ds-<?php echo $service;?>" title="<?php echo $serviceName;?>"></a></li>
+<?php endforeach;?>
+</ul>
+<script>
+jQuery(function(){
+	var $ = jQuery,
+		duoshuoName = {
+			weibo	: '@多说网',
+			qzone	: '@多说网',
+			qqt		: '@多说网',
+			renren	: '多说',
+			kaixin	: '多说',
+			douban	: '多说',
+			netease	: '@多说网',
+			sohu	: '@多说网'
+		},
+		handler = function(e){
+			var service = this.className.match(/ds\-(\w+)/)[1],
+				message = <?php echo json_encode('我的' . get_option('blogname') . '(' .get_option('siteurl') . ')装了');?> + duoshuoName[service] + ' 评论插件,用微博、QQ、人人帐号就能登录评论了,很给力。来试试吧!',
+				image = 'http://static.duoshuo.com/images/top.jpg',
+				title = '多说评论插件',
+				url = 'http://duoshuo.com';
+			window.open('http://<?php echo $this->shortName . '.' . self::DOMAIN;?>/share-proxy/?service=' + service + '&url=' + encodeURIComponent(url) + '&message=' + encodeURIComponent(message) + '&title=' + encodeURIComponent(title) + '&images=' + image,
+				'_blank',
+				'height=550,width=600,top=0,left=0,toolbar=no,menubar=no,resizable=yes,location=yes,status=no');
+			return false;
+		};
+	$.fn.delegate
+		? $('.ds-share').delegate('a', 'click', handler)
+		: $('.ds-share a').click(handler);
+});
+</script>
+
+</div>

+ 14 - 0
statistics.php

@@ -0,0 +1,14 @@
+<link rel="stylesheet" href="<?php echo $this->pluginDirUrl; ?>styles.css" type="text/css" />
+<?php
+$params = array(
+	'jwt'	=>	$this->jwt(),
+);
+$adminUrl = is_ssl()? 'https://' : 'http://';
+$adminUrl .= $this->shortName . '.' . self::DOMAIN . '/admin/statistics/?' . http_build_query($params, null, '&');
+?>
+<div class="wrap">
+<?php screen_icon(); ?>
+<h2>数据统计
+<a class="add-new-h2" target="_blank" href="<?php echo $adminUrl;?>">在新窗口中打开</a></h2>
+<iframe id="duoshuo-remote-window" src="<?php echo $adminUrl;?>" style="width:100%;height:920px"></iframe>
+</div>

+ 54 - 0
styles.css

@@ -0,0 +1,54 @@
+.ds-service-icon li{
+	float: left;
+	margin-right:6px;
+}
+.ds-service-icon a {
+	display: block;
+	cursor:pointer;
+	width: 32px !important;
+	height: 32px !important;
+	background: url(images/service_icons_32x32.png) no-repeat;
+	overflow: hidden;
+	text-indent:-9999px;
+}
+.ds-service-icon a.ds-weibo {background-position: 0 0;}
+.ds-service-icon a.ds-qzone {background-position: 0 -32px;}
+.ds-service-icon a.ds-qqt {background-position: 0 -1760px;}
+.ds-service-icon a.ds-renren {background-position: 0 -64px;}
+.ds-service-icon a.ds-kaixin {background-position: 0 -192px;}
+.ds-service-icon a.ds-netease {background-position: 0 -320px;}
+.ds-service-icon a.ds-sohu {background-position: 0 -1248px;}
+.ds-service-icon a.ds-qq {background-position: 0 -2208px;}
+.ds-service-icon a.ds-douban {background-position: 0 -224px;}
+.ds-service-icon a.ds-baidu {background-position: 0 -352px;}
+.ds-service-icon a.ds-taobao {background-position: 0 -2016px;}
+.ds-service-icon a.ds-msn {background-position: 0 -864px;}
+
+#icon-duoshuo{
+	background: url("images/head-icon.gif") no-repeat scroll 0 4px transparent;
+}
+
+.ds-icon-yes{
+	display:inline-block;
+	background: url("images/icon.gif") no-repeat 0 -10px transparent;
+	width: 15px;
+	height: 12px;
+	text-indent:-9999px;
+}
+.ds-icon-no{
+	display:inline-block;
+	background: url("images/icon.gif") no-repeat 0 -22px transparent;
+	width: 15px;
+	height: 12px;
+	text-indent:-9999px;
+}
+
+.ds-dependencies{
+	width:500px;
+	border-collapse: collapse;
+	border: 1px solid #ebebeb;
+}
+.ds-dependencies td, .ds-dependencies th {padding: 8px; text-align:left;}
+.ds-dependencies thead tr{background-color : #ebebeb;}
+.ds-dependencies tbody tr:nth-child(even){background-color : #ebebeb;}
+.ds-dependencies tbody tr:nth-child(odd){background-color : #FFF;}

+ 10 - 0
sync.php

@@ -0,0 +1,10 @@
+<link rel="stylesheet" href="<?php echo $this->pluginDirUrl; ?>styles.css" type="text/css" />
+<div class="wrap">
+<?php echo screen_icon();?><h2>数据同步</h2>
+<div id="ds-export">
+	<p class="message-start">安装成功了!只要一键将您的用户、文章和评论信息同步到多说,多说就可以开始为您服务了!<a href="javascript:void(0)" class="button-primary" onclick="fireExport();return false;">开始同步</a></p>
+	<p class="status"></p>
+	<p class="message-complete">同步完成,现在你可以<a href="<?php echo admin_url('admin.php?page=duoshuo-settings');?>">设置</a>或<a href="<?php echo admin_url('admin.php?page=duoshuo');?>">管理</a></p>
+</div>
+<?php include_once dirname(__FILE__) . '/common-script.html';?>
+</div>

+ 53 - 0
themes.php

@@ -0,0 +1,53 @@
+<link rel="stylesheet" href="<?php echo $this->pluginDirUrl; ?>styles.css" type="text/css" />
+<div class="wrap">
+<a href="https://github.com/duoshuo/duoshuo-embed.css" target="_blank"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png" alt="Fork me on GitHub"></a>
+<?php screen_icon(); ?>
+<h2>多说主题设置</h2>
+
+<form action="" method="post">
+<?php wp_nonce_field('duoshuo-local-options');?>
+	<ul class="ds-themes"></ul>
+	<p>多说的CSS样式已经开源啦! <a href="https://github.com/duoshuo/duoshuo-embed.css" target="_blank">github:duoshuo/duoshuo-embed.css</a></p>
+	<p>你可以打造属于自己的主题,在<a href="http://dev.duoshuo.com/discussion" target="_blank">开发者中心</a>分享你的主题,还有可能被官方推荐哟!</p>
+</form>
+<style>
+.ds-themes li{
+	display: inline-block;
+	margin-right: 10px;
+	overflow: hidden;
+	padding: 20px 20px 20px 0;
+	vertical-align: top;
+	width: 300px;
+}
+</style>
+<script>
+function loadDuoshuoThemes(json){
+	var $ = jQuery;
+	
+	$(function(){
+		var html = '';
+		$.each(json.response, function(key, theme){
+			html += '<li>'
+				+ '<img src="' + theme.screenshot + '" width="300" height="225" style="border:1px #CCC solid;" />'
+				+ '<h3>' + theme.name + '</h3>'
+				+ '<p>作者:<a href="' + theme.author_url + '" target="_blank">' + theme.author_name + '</a></p>'
+				+ '<div class="action-links">'
+					+ ( key == <?php echo json_encode($this->getOption('theme'));?>
+						? '<span class="">当前主题</span>'
+						: '<a href="admin.php?page=duoshuo-themes&amp;duoshuo_theme=' + key + '&amp;_wpnonce=<?php echo wp_create_nonce('set-duoshuo-theme'); ?>" class="activatelink" title="启用 “' + theme.name + '”">启用</a>')
+				+ '</div>'
+				+ '</li>';
+		});
+		$('.ds-themes').html(html);
+	});
+}
+</script>
+
+<?php
+$adminUrl = is_ssl() ? 'https://' : 'http://';
+$adminUrl .= $this->shortName .".duoshuo.com/api/sites/themes.jsonp?callback=loadDuoshuoThemes";
+
+?>
+<script src="<?php echo $adminUrl;?>"></script>
+
+</div>

+ 431 - 0
widgets.php

@@ -0,0 +1,431 @@
+<?php
+
+class Duoshuo_Widget_Recent_Comments extends WP_Widget {
+	
+	protected $duoshuoPlugin;
+	
+	function __construct() {
+		$widget_ops = array('classname' => 'ds-widget-recent-comments', 'description' => '最新评论(由多说提供)' );
+		parent::__construct('ds-recent-comments', '最新评论(多说)', $widget_ops);
+		
+		$this->alt_option_name = 'duoshuo_widget_recent_comments';
+
+		if ( is_active_widget(false, false, $this->id_base) )
+			add_action( 'wp_head', array(&$this, 'recent_comments_style') );
+
+		//add_action( 'comment_post', array(&$this, 'flush_widget_cache') );
+		//add_action( 'transition_comment_status', array(&$this, 'flush_widget_cache') );
+		
+		$this->duoshuoPlugin = Duoshuo_WordPress::getInstance();
+	}
+
+	function recent_comments_style() {
+		if ( ! current_theme_supports( 'widgets' ) )// Temp hack #14876
+			return;
+	}
+	
+	function widget( $args, $instance ) {
+		global $comments, $comment;
+
+		if ( ! isset( $args['widget_id'] ) )
+			$args['widget_id'] = $this->id;
+
+ 		extract($args, EXTR_SKIP);
+ 		
+ 		$output = '';
+ 		$title = apply_filters( 'widget_title', empty( $instance['title'] ) ? __( 'Recent Comments' ) : $instance['title'], $instance, $this->id_base );
+
+		if ( empty( $instance['number'] ) || ! $number = absint( $instance['number'] ) )
+ 			$number = 10;
+
+		$output .= $before_widget;
+		if ( $title )
+			$output .= $before_title . $title . $after_title;
+		
+		$data = array(
+			'num_items'	=>	$number,
+			'show_avatars'=>isset($instance['show_avatars']) ? $instance['show_avatars'] : 1,
+			'show_time'=>	isset($instance['show_time']) ? $instance['show_time'] : 1,
+			'show_title'=>	isset($instance['show_title']) ? $instance['show_title'] : 1,
+			'show_admin'=>	isset($instance['show_admin']) ? $instance['show_admin'] : 1,
+			'avatar_size'=>	30,
+			'excerpt_length'=>	isset($instance['excerpt_length']) ? $instance['excerpt_length'] : 70,
+		);
+		$attribs = '';
+		foreach ($data as $key => $value)
+			$attribs .= ' data-' . str_replace('_','-',$key) . '="' . esc_attr($value) . '"';
+		$output .= '<ul class="ds-recent-comments"' . $attribs . '></ul>'
+				. $after_widget;
+		echo $output;?>
+<script>
+if (typeof DUOSHUO !== 'undefined')
+	DUOSHUO.RecentComments && DUOSHUO.RecentComments('.ds-recent-comments');
+</script><?php 
+		$this->duoshuoPlugin->printScripts();
+	}
+
+
+	function update( $new_instance, $old_instance ) {
+		$instance = $old_instance;
+		$instance['title'] = strip_tags($new_instance['title']);
+		$instance['number'] = absint( $new_instance['number'] );
+		$instance['excerpt_length'] = absint( $new_instance['excerpt_length'] );
+		$instance['show_avatars'] =  absint( $new_instance['show_avatars'] );
+		$instance['show_time'] =  absint( $new_instance['show_time'] );
+		$instance['show_title'] =  absint( $new_instance['show_title'] );
+		$instance['show_admin'] =  absint( $new_instance['show_admin'] );
+
+		$alloptions = wp_cache_get( 'alloptions', 'options' );
+		if ( isset($alloptions['duoshuo_widget_recent_comments']) )
+			delete_option('duoshuo_widget_recent_comments');
+
+		return $instance;
+	}
+	
+	function form( $instance ) {
+		$title = isset($instance['title']) ? esc_attr($instance['title']) : '';
+		$number = isset($instance['number']) ? absint($instance['number']) : 5;
+		$show_avatars = isset($instance['show_avatars']) ? absint( $instance['show_avatars']) : 1;
+		$show_title = isset($instance['show_title']) ? absint($instance['show_title']) : 1;
+		$show_time = isset($instance['show_time']) ? absint($instance['show_time']) : 1;
+		$show_admin = isset($instance['show_admin']) ? absint($instance['show_admin']) : 1;
+		$excerpt_length = isset($instance['excerpt_length']) ? absint($instance['excerpt_length']) : 70;
+?>
+		<p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label>
+		<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo $title; ?>" /></p>
+
+		<p>
+			<input name="<?php echo $this->get_field_name('show_avatars'); ?>" type="hidden" value="0" />
+			<input id="<?php echo $this->get_field_id('show_avatars'); ?>" name="<?php echo $this->get_field_name('show_avatars'); ?>" type="checkbox" value="1" <?php if ($show_avatars) echo 'checked="checked" '?>/>
+			<label for="<?php echo $this->get_field_id('show_avatars'); ?>">显示头像</label>
+		</p>
+		
+		<p>
+			<input name="<?php echo $this->get_field_name('show_time'); ?>" type="hidden" value="0" />
+			<input id="<?php echo $this->get_field_id('show_time'); ?>" name="<?php echo $this->get_field_name('show_time'); ?>" type="checkbox" value="1" <?php if ($show_time) echo 'checked="checked" '?>/>
+			<label for="<?php echo $this->get_field_id('show_time'); ?>">显示评论时间</label>
+		</p>
+		
+		<p>
+			<input name="<?php echo $this->get_field_name('show_title'); ?>" type="hidden" value="0" />
+			<input id="<?php echo $this->get_field_id('show_title'); ?>" name="<?php echo $this->get_field_name('show_title'); ?>" type="checkbox" value="1" <?php if ($show_title) echo 'checked="checked" '?>/>
+			<label for="<?php echo $this->get_field_id('show_title'); ?>">显示文章标题</label>
+		</p>
+		
+		<p>
+			<input name="<?php echo $this->get_field_name('show_admin'); ?>" type="hidden" value="0" />
+			<input id="<?php echo $this->get_field_id('show_admin'); ?>" name="<?php echo $this->get_field_name('show_admin'); ?>" type="checkbox" value="1" <?php if ($show_admin) echo 'checked="checked" '?>/>
+			<label for="<?php echo $this->get_field_id('show_admin'); ?>">显示管理员评论</label>
+		</p>
+		
+		<p><label for="<?php echo $this->get_field_id('number'); ?>"><?php _e('Number of comments to show:'); ?></label>
+		<input id="<?php echo $this->get_field_id('number'); ?>" name="<?php echo $this->get_field_name('number'); ?>" type="text" value="<?php echo $number; ?>" size="3" /></p>
+		
+		
+		<p><label for="<?php echo $this->get_field_id('excerpt_length'); ?>">引文字数(中文):</label>
+		<input id="<?php echo $this->get_field_id('excerpt_length'); ?>" name="<?php echo $this->get_field_name('excerpt_length'); ?>" type="text" value="<?php echo $excerpt_length; ?>" size="5" /></p>
+<?php
+	}
+}
+
+class Duoshuo_Widget_Top_Threads extends WP_Widget {
+
+	protected $duoshuoPlugin;
+
+	function __construct() {
+		$widget_ops = array('classname' => 'ds-widget-top-threads', 'description' => '热评文章(由多说提供)');
+		parent::__construct('ds-top-threads', '热评文章(多说)', $widget_ops);
+
+		$this->alt_option_name = 'duoshuo_widget_top_threads';
+
+		$this->duoshuoPlugin = Duoshuo_WordPress::getInstance();
+	}
+
+	function widget( $args, $instance ) {
+		global $comments, $comment;
+
+		if ( ! isset( $args['widget_id'] ) )
+			$args['widget_id'] = $this->id;
+
+		extract($args, EXTR_SKIP);
+			
+		$output = '';
+		$title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '热评文章' : $instance['title'], $instance, $this->id_base );
+
+		if ( empty( $instance['number'] ) || ! $number = absint( $instance['number'] ) )
+			$number = 5;
+
+		$output .= $before_widget;
+		if ( $title )
+			$output .= $before_title . $title . $after_title;
+
+		$data = array(
+			'num_items'	=>	$number,
+			'range'		=>	isset($instance['range']) ? $instance['range'] : 'weekly',
+			//'show_avatars'=>isset($instance['show_avatars']) ? $instance['show_avatars'] : 1,
+			//'avatar_size'=>	30,
+		);
+		$attribs = '';
+		foreach ($data as $key => $value)
+			$attribs .= ' data-' . str_replace('_','-',$key) . '="' . esc_attr($value) . '"';
+		$output .= '<ul class="ds-top-threads"' . $attribs . '></ul>'
+				. $after_widget;
+		echo $output;?>
+<script>
+if (typeof DUOSHUO !== 'undefined')
+	DUOSHUO.TopThreads && DUOSHUO.TopThreads('.ds-top-threads');
+</script><?php 
+		$this->duoshuoPlugin->printScripts();
+	}
+
+	function update( $new_instance, $old_instance ) {
+		$instance = $old_instance;
+		$instance['range'] = $new_instance['range'];
+		$instance['title'] = strip_tags($new_instance['title']);
+		$instance['number'] = absint( $new_instance['number'] );
+		//$instance['show_avatars'] =  absint( $new_instance['show_avatars'] );
+	
+		$alloptions = wp_cache_get( 'alloptions', 'options' );
+		if ( isset($alloptions['duoshuo_widget_top_threads']) )
+			delete_option('duoshuo_widget_top_threads');
+
+		return $instance;
+	}
+	
+	function form( $instance ) {
+		$title = isset($instance['title']) ? esc_attr($instance['title']) : '';
+		$range = isset($instance['range']) ? esc_attr($instance['range']) : 'weekly';
+		$number = isset($instance['number']) ? absint($instance['number']) : 5;
+		//$show_avatars = isset($instance['show_avatars']) ? absint( $instance['show_avatars']) : 1;
+?>
+		<p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label>
+		<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo $title; ?>" /></p>
+
+		<p>
+			<label><input name="<?php echo $this->get_field_name('range'); ?>" type="radio" value="daily" <?php if ($range == 'daily') echo 'checked="checked" '?>/>24小时内</label>
+			<label><input name="<?php echo $this->get_field_name('range'); ?>" type="radio" value="weekly" <?php if ($range == 'weekly') echo 'checked="checked" '?>/>7天内</label>
+			<label><input name="<?php echo $this->get_field_name('range'); ?>" type="radio" value="monthly" <?php if ($range == 'monthly') echo 'checked="checked" '?>/>30天内</label>
+		</p>
+		<!-- 
+		<p>
+			<input name="<?php echo $this->get_field_name('show_avatars'); ?>" type="hidden" value="0" />
+			<input id="<?php echo $this->get_field_id('show_avatars'); ?>" name="<?php echo $this->get_field_name('show_avatars'); ?>" type="checkbox" value="1" <?php if ($show_avatars) echo 'checked="checked" '?>/>
+			<label for="<?php echo $this->get_field_id('show_avatars'); ?>">显示头像</label>
+		</p>
+		 -->
+		<p><label for="<?php echo $this->get_field_id('number'); ?>"><?php _e('Number of posts to show:'); ?></label>
+		<input id="<?php echo $this->get_field_id('number'); ?>" name="<?php echo $this->get_field_name('number'); ?>" type="text" value="<?php echo $number; ?>" size="3" /></p>
+<?php
+	}
+}
+
+class Duoshuo_Widget_Recent_Visitors extends WP_Widget {
+	
+	function __construct() {
+		$widget_ops = array('classname' => 'ds-widget-recent-visitors', 'description' => '最近访客(由多说提供)' );
+		parent::__construct('ds-recent-visitors', '最近访客(多说)', $widget_ops);
+		
+		$this->alt_option_name = 'duoshuo_widget_recent_visitors';
+
+		if ( is_active_widget(false, false, $this->id_base) )
+			add_action( 'wp_head', array(&$this, 'printScripts') );
+
+		//add_action( 'comment_post', array(&$this, 'flush_widget_cache') );
+		//add_action( 'transition_comment_status', array(&$this, 'flush_widget_cache') );
+		
+		$this->duoshuoPlugin = Duoshuo_WordPress::getInstance();
+	}
+
+	function printScripts() {
+		if ( ! current_theme_supports( 'widgets' ) )// Temp hack #14876
+			return;
+	}
+	
+	function widget( $args, $instance ) {
+		global $comments, $comment;
+
+		if ( ! isset( $args['widget_id'] ) )
+			$args['widget_id'] = $this->id;
+
+ 		extract($args, EXTR_SKIP);
+ 		
+ 		$output = '';
+ 		$title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '最近访客' : $instance['title'], $instance, $this->id_base );
+
+		if ( empty( $instance['number'] ) || ! $number = absint( $instance['number'] ) )
+ 			$number = 12;
+
+		$output .= $before_widget;
+		if ( $title )
+			$output .= $before_title . $title . $after_title;
+		
+		$data = array(
+			'num_items'	=>	$number,
+			'show_time'=>	isset($instance['show_time']) ? $instance['show_time'] : 1,
+			'avatar_size'=>	isset($instance['avatar_size']) ? $instance['avatar_size'] : 50,
+		);
+		$attribs = '';
+		foreach ($data as $key => $value)
+			$attribs .= ' data-' . str_replace('_','-',$key) . '="' . esc_attr($value) . '"';
+		$output .= '<ul class="ds-recent-visitors"' . $attribs . '></ul>'
+				. $after_widget;
+		echo $output;?>
+<script>
+if (typeof DUOSHUO !== 'undefined')
+	DUOSHUO.RecentVisitors('.ds-recent-visitors');
+</script><?php 
+		$this->duoshuoPlugin->printScripts();
+	}
+
+
+	function update( $new_instance, $old_instance ) {
+		$instance = $old_instance;
+		$instance['title'] = strip_tags($new_instance['title']);
+		$instance['number'] = absint( $new_instance['number'] );
+		$instance['show_time'] =  absint( $new_instance['show_time'] );
+		$instance['avatar_size'] =  absint( $new_instance['avatar_size'] );
+		
+		$alloptions = wp_cache_get( 'alloptions', 'options' );
+		if ( isset($alloptions['duoshuo_widget_recent_visitors']) )
+			delete_option('duoshuo_widget_recent_visitors');
+
+		return $instance;
+	}
+	
+	function form( $instance ) {
+		$title = isset($instance['title']) ? esc_attr($instance['title']) : '';
+		$number = isset($instance['number']) ? absint($instance['number']) : 15;
+		$show_time = isset($instance['show_time']) ? absint($instance['show_time']) : 1;
+		$avatar_size = isset($instance['avatar_size']) ? absint($instance['avatar_size']) : 50;
+?>
+		<p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label>
+		<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo $title; ?>" /></p>
+
+		<!-- p>
+			<input name="<?php echo $this->get_field_name('show_time'); ?>" type="hidden" value="0" />
+			<input id="<?php echo $this->get_field_id('show_time'); ?>" name="<?php echo $this->get_field_name('show_time'); ?>" type="checkbox" value="1" <?php if ($show_time) echo 'checked="checked" '?>/>
+			<label for="<?php echo $this->get_field_id('show_time'); ?>">显示访问时间</label>
+		</p -->
+		
+		<p><label for="<?php echo $this->get_field_id('number'); ?>">显示访客的数量:</label>
+		<input id="<?php echo $this->get_field_id('number'); ?>" name="<?php echo $this->get_field_name('number'); ?>" type="text" value="<?php echo $number; ?>" size="3" /></p>
+		<p><label for="<?php echo $this->get_field_id('avatar_size'); ?>">头像尺寸:</label>
+		<input id="<?php echo $this->get_field_id('avatar_size'); ?>" name="<?php echo $this->get_field_name('avatar_size'); ?>" type="text" value="<?php echo $avatar_size; ?>" size="3" />px</p>
+<?php
+	}
+}
+
+
+class Duoshuo_Widget_Qqt_Follow extends WP_Widget {
+	
+	function __construct() {
+		$widget_ops = array('classname' => 'ds-widget-qqt-follow', 'description' => '腾讯微博-收听组件(由多说提供)' );
+		parent::__construct('ds-qqt-follow', '腾讯微博-收听(多说)', $widget_ops);
+		
+		$this->alt_option_name = 'duoshuo_widget_qqt_follow';
+		
+	}
+	
+	function widget( $args, $instance ) {
+
+		if ( ! isset( $args['widget_id'] ) )
+			$args['widget_id'] = $this->id;
+
+ 		extract($args, EXTR_SKIP);
+ 		
+ 		$output = $before_widget;
+ 		
+ 		$title = apply_filters( 'widget_title', isset( $instance['title'] ) ? $instance['title'] : '', $instance, $this->id_base );
+
+		if ( $title )
+			$output .= $before_title . $title . $after_title;
+		
+		$params = array(
+			'c'	=>	'follow',
+			'a'	=>	'quick',
+			'name'=>isset($instance['qqt_name']) ? $instance['qqt_name'] : 'duo-shuo',
+			'style'=>isset($instance['qqt_style']) ? $instance['qqt_style'] : 1,
+			't'	=>	time() . sprintf("%03d", microtime() * 1000),
+			'f'	=>	isset($instance['qqt_followers']) ? $instance['qqt_followers'] : 1,
+		);
+		
+		switch($params['style']){
+			case 1:
+				$width = $params['f'] ? 227 : 167;
+				$height = 75;
+				break;
+			case 2:
+				$width = $params['f'] ? 191 : 136;
+				$height = 38;
+				break;
+			case 3:
+				$width = $params['f'] ? 168 : 125;
+				$height = 20;
+				break;
+			case 4:
+				$width = $params['f'] ? 182 : 125;
+				$height = 27;
+				break;
+			case 5:
+				$width = $params['f'] ? 178 : 125;
+				$height = 24;
+				break;
+			default:
+		}
+		
+		$attribs = array(
+			'scrolling'	=>	'no',
+			'width'		=>	$width,
+			'height'	=>	$height,
+			'frameborder'=>	0,
+			'allowtransparency'=>'true',
+			'marginheight'=>0,
+			'marginwidth'=>	0,
+			'src'		=> (is_ssl()?'https':'http').'://follow.v.t.qq.com/index.php?' . http_build_query($params, null, '&'),
+		);
+		
+		$output .= '<iframe '; 
+		foreach ($attribs as $key => $value)
+			$output .= ' ' . $key . '="' . $value . '"';
+		$output .= '></iframe>' . $after_widget;
+		echo $output;
+	}
+
+
+	function update( $new_instance, $old_instance ) {
+		$instance = $old_instance;
+		$instance['title'] = strip_tags($new_instance['title']);
+		$instance['qqt_name'] = strip_tags($new_instance['qqt_name']);
+		$instance['qqt_style'] = absint( $new_instance['qqt_style'] );
+		$instance['qqt_followers'] = absint( $new_instance['qqt_followers'] );
+		
+		$alloptions = wp_cache_get( 'alloptions', 'options' );
+		if ( isset($alloptions['duoshuo_widget_qqt_follow']) )
+			delete_option('duoshuo_widget_qqt_follow');
+
+		return $instance;
+	}
+	
+	function form( $instance ) {
+		$title = isset($instance['title']) ? $instance['title'] : '';
+		$qqt_name = isset($instance['qqt_name']) ? $instance['qqt_name'] : '';
+		$qqt_style = isset($instance['qqt_style']) ? absint( $instance['qqt_style']) : 1;
+		$qqt_followers = isset($instance['qqt_followers']) ? absint( $instance['qqt_followers']) : 1;
+?>
+		<p><label for="<?php echo $this->get_field_id('title'); ?>">标题:</label>
+			<input type="text" class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($title);?>"></p>
+		<p><label for="<?php echo $this->get_field_id('qqt_name'); ?>">腾讯微博帐号 (不含@,如:duo-shuo):</label>
+			<input type="text" class="widefat" id="<?php echo $this->get_field_id('qqt_name'); ?>" name="<?php echo $this->get_field_name('qqt_name'); ?>" value="<?php echo esc_attr($qqt_name);?>"></p>
+		<p><input name="<?php echo $this->get_field_name('qqt_followers'); ?>" type="hidden" value="0" />
+				<input id="<?php echo $this->get_field_id('qqt_followers'); ?>" name="<?php echo $this->get_field_name('qqt_followers'); ?>" type="checkbox" value="1" <?php if ($qqt_followers) echo 'checked="checked" '?>/>
+				<label for="<?php echo $this->get_field_id('qqt_followers'); ?>">显示已收听人数</label></p>
+		<ul>
+			<li><label><input name="<?php echo $this->get_field_name('qqt_style'); ?>" type="radio" value="1" <?php if ($qqt_style == 1) echo 'checked="checked" '?>/> 头像+收听按钮(样式丰富,更有效吸引听众)</label></li>
+			<li><label><input name="<?php echo $this->get_field_name('qqt_style'); ?>" type="radio" value="2" <?php if ($qqt_style == 2) echo 'checked="checked" '?>/> 收听按钮(适合有限的展现空间)</label></li>
+			<li><label><input name="<?php echo $this->get_field_name('qqt_style'); ?>" type="radio" value="3" <?php if ($qqt_style == 3) echo 'checked="checked" '?>/> 收听文字链(最简洁的状态)</label></li>
+			<li><label><input name="<?php echo $this->get_field_name('qqt_style'); ?>" type="radio" value="4" <?php if ($qqt_style == 4) echo 'checked="checked" '?>/> 收听按钮(清新蓝色)</label></li>
+			<li><label><input name="<?php echo $this->get_field_name('qqt_style'); ?>" type="radio" value="5" <?php if ($qqt_style == 5) echo 'checked="checked" '?>/> 收听按钮(小巧白色)</label></li>
+		</ul>
+<?php
+	}
+}