├── LICENSE ├── README.md └── duoshuo ├── Abstract.php ├── Client.php ├── Exception.php ├── LocalServer.php ├── WordPress.php ├── api.php ├── comments-seo.php ├── comments.php ├── common-script.html ├── compat-json.php ├── config.php ├── duoshuo.php ├── images ├── head-icon.gif ├── icon.gif ├── menu-icon.png ├── service_icons_32x32.png └── waiting.gif ├── index.php ├── manage.php ├── nanoSha2.php ├── oauth-proxy.php ├── preferences.php ├── profile.php ├── readme.txt ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.png ├── screenshot-4.png ├── screenshot-5.png ├── screenshot-6.jpg ├── settings.php ├── statistics.php ├── styles.css ├── sync.php ├── themes.php └── widgets.php /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2010 DISQUS 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | duoshuo-wordpress 2 | ================= 3 | 4 | 多说评论插件 for WordPress 5 | 6 | 在这里你可以看到多说插件的最新开发进展,如果你想下载稳定版,请访问WordPress的官方网站: 7 | http://wordpress.org/extend/plugins/duoshuo/ 8 | -------------------------------------------------------------------------------- /duoshuo/Abstract.php: -------------------------------------------------------------------------------- 1 | shortName, $this->secret); 26 | } 27 | 28 | public function syncLog(){ 29 | $this->updateOption('sync_lock', time()); 30 | 31 | $last_log_id = $this->getOption('last_log_id'); 32 | if (!$last_log_id) 33 | $last_log_id = 0; 34 | 35 | $limit = 20; 36 | 37 | $params = array( 38 | 'limit' => $limit, 39 | 'order' => 'asc', 40 | ); 41 | 42 | $client = $this->getClient(); 43 | 44 | $posts = array(); 45 | $affectedThreads = array(); 46 | 47 | //do{ 48 | 49 | $params['since_id'] = $last_log_id; 50 | $response = $client->request('GET', 'log/list', $params); 51 | 52 | if (is_string($response)) 53 | throw new Duoshuo_Exception($response, Duoshuo_Exception::INTERNAL_SERVER_ERROR); 54 | 55 | if (!isset($response['response'])) 56 | throw new Duoshuo_Exception($response['message'], $response['code']); 57 | 58 | foreach($response['response'] as $log){ 59 | switch($log['action']){ 60 | case 'create': 61 | $affected = $this->createPost($log['meta']); 62 | break; 63 | case 'approve': 64 | $affected = $this->approvePost($log['meta']); 65 | break; 66 | case 'spam': 67 | $affected = $this->spamPost($log['meta']); 68 | break; 69 | case 'delete': 70 | $affected = $this->deletePost($log['meta']); 71 | break; 72 | case 'delete-forever': 73 | $affected = $this->deleteForeverPost($log['meta']); 74 | break; 75 | case 'update'://现在并没有update操作的逻辑 76 | default: 77 | $affected = array(); 78 | } 79 | 80 | //合并 81 | if (is_array($affected)) 82 | $affectedThreads = array_merge($affectedThreads, $affected); 83 | 84 | if (strlen($log['log_id']) > strlen($last_log_id) || strcmp($log['log_id'], $last_log_id) > 0) 85 | $last_log_id = $log['log_id']; 86 | } 87 | 88 | $this->updateOption('last_log_id', $last_log_id); 89 | 90 | //} while (count($response['response']) == $limit);//如果返回和最大请求条数一致,则再取一次 91 | 92 | $this->updateOption('sync_lock', 0); 93 | 94 | //更新静态文件 95 | if ($this->getOption('sync_to_local') && $this->plugin->getOption('seo_enabled')) 96 | $this->refreshThreads(array_unique($affectedThreads)); 97 | 98 | return count($response['response']); 99 | } 100 | 101 | function rfc3339_to_mysql($string){ 102 | if (method_exists('DateTime', 'createFromFormat')){ // php 5.3.0 103 | return DateTime::createFromFormat(DateTime::RFC3339, $string)->format('Y-m-d H:i:s'); 104 | } 105 | else{ 106 | $timestamp = strtotime($string); 107 | return gmdate('Y-m-d H:i:s', $timestamp + $this->timezone() * 3600); 108 | } 109 | } 110 | 111 | function rfc3339_to_mysql_gmt($string){ 112 | if (method_exists('DateTime', 'createFromFormat')){ // php 5.3.0 113 | return DateTime::createFromFormat(DateTime::RFC3339, $string)->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s'); 114 | } 115 | else{ 116 | $timestamp = strtotime($string); 117 | return gmdate('Y-m-d H:i:s', $timestamp); 118 | } 119 | } 120 | 121 | static function encodeJWT($payload, $key){ 122 | $header = array('typ' => 'JWT', 'alg' => 'HS256'); 123 | 124 | $segments = array( 125 | str_replace('=', '', strtr(base64_encode(json_encode($header)), '+/', '-_')), 126 | str_replace('=', '', strtr(base64_encode(json_encode($payload)), '+/', '-_')), 127 | ); 128 | $signing_input = implode('.', $segments); 129 | 130 | $signature = self::hmacsha256($signing_input, $key); 131 | 132 | $segments[] = str_replace('=', '', strtr(base64_encode($signature), '+/', '-_')); 133 | 134 | return implode('.', $segments); 135 | } 136 | 137 | // from: http://www.php.net/manual/en/function.sha1.php#39492 138 | // Calculate HMAC-SHA1 according to RFC2104 139 | // http://www.ietf.org/rfc/rfc2104.txt 140 | static function hmacsha1($data, $key) { 141 | if (function_exists('hash_hmac')) 142 | return hash_hmac('sha1', $data, $key, true); 143 | 144 | $blocksize=64; 145 | if (strlen($key)>$blocksize) 146 | $key=pack('H*', sha1($key)); 147 | $key=str_pad($key,$blocksize,chr(0x00)); 148 | $ipad=str_repeat(chr(0x36),$blocksize); 149 | $opad=str_repeat(chr(0x5c),$blocksize); 150 | $hmac = pack( 151 | 'H*',sha1( 152 | ($key^$opad).pack( 153 | 'H*',sha1( 154 | ($key^$ipad).$data 155 | ) 156 | ) 157 | ) 158 | ); 159 | return $hmac; 160 | } 161 | 162 | /** 163 | * from: http://www.php.net/manual/en/function.sha1.php#39492 164 | * Calculate HMAC-SHA1 according to RFC2104 165 | * http://www.ietf.org/rfc/rfc2104.txt 166 | * Used in OAuth1 and remoteAuth 167 | */ 168 | static function hmacsha256($data, $key) { 169 | if (function_exists('hash_hmac')) 170 | return hash_hmac('sha256', $data, $key, true); 171 | 172 | if (!class_exists('nanoSha2', false)) 173 | require 'nanoSha2.php'; 174 | 175 | $nanoSha2 = new nanoSha2(); 176 | 177 | $blocksize=64; 178 | if (strlen($key)>$blocksize) 179 | $key=pack('H*', $nanoSha2->hash($key, true)); 180 | $key=str_pad($key,$blocksize,chr(0x00)); 181 | $ipad=str_repeat(chr(0x36),$blocksize); 182 | $opad=str_repeat(chr(0x5c),$blocksize); 183 | $hmac = pack( 184 | 'H*',$nanoSha2->hash( 185 | ($key^$opad).pack( 186 | 'H*', $nanoSha2->hash(($key^$ipad).$data, true) 187 | ), 188 | true 189 | ) 190 | ); 191 | return $hmac; 192 | } 193 | 194 | function exportUsers($users){ 195 | if (count($users) === 0) 196 | return 0; 197 | 198 | $params = array('users'=>array()); 199 | foreach($users as $user) 200 | $params['users'][] = $this->packageUser($user); 201 | 202 | $remoteResponse = $this->getClient()->request('POST', 'users/import', $params); 203 | 204 | // @deprecated 不再需要记录duoshuo_user_id 205 | if (is_array($remoteResponse) && isset($remoteResponse['response'])){ 206 | foreach($remoteResponse['response'] as $userId => $duoshuoUserId) 207 | $this->updateUserMeta($userId, 'duoshuo_user_id', $duoshuoUserId); 208 | } 209 | 210 | return count($users); 211 | } 212 | 213 | function exportPosts($threads){ 214 | if (count($threads) === 0) 215 | return 0; 216 | 217 | $params = array( 218 | 'threads' => array(), 219 | ); 220 | foreach($threads as $index => $thread){ 221 | $params['threads'][] = $this->packageThread($thread); 222 | } 223 | 224 | $remoteResponse = $this->getClient()->request('POST','threads/import', $params); 225 | 226 | if (is_array($remoteResponse) && isset($remoteResponse['response'])){ 227 | foreach($remoteResponse['response'] as $threadId => $duoshuoThreadId) 228 | $this->updateThreadMeta($threadId, 'duoshuo_thread_id', $duoshuoThreadId); 229 | } 230 | 231 | return count($threads); 232 | } 233 | 234 | function exportComments($comments){ 235 | if (count($comments) === 0) 236 | return 0; 237 | 238 | $params = array( 239 | 'posts' => array() 240 | ); 241 | 242 | foreach($comments as $comment) 243 | $params['posts'][] = $this->packageComment($comment); 244 | 245 | $remoteResponse = $this->getClient()->request('POST', 'posts/import', $params); 246 | 247 | return count($comments); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /duoshuo/Client.php: -------------------------------------------------------------------------------- 1 | shortName = $shortName; 23 | $this->secret = $secret; 24 | $this->jwt = $jwt; 25 | $this->accessToken = $accessToken; 26 | $this->http = new WP_Http(); 27 | $this->userAgent = 'WordPress/' . $wp_version . '|Duoshuo/'. Duoshuo_WordPress::VERSION; 28 | } 29 | 30 | /** 31 | * 32 | * @param $method 33 | * @param $path 34 | * @param $params 35 | * @throws Duoshuo_Exception 36 | * @return array 37 | */ 38 | public function request($method, $path, $params = array()){ 39 | $params['short_name'] = $this->shortName; 40 | $params['secret'] = $this->secret; 41 | 42 | if ($this->jwt) 43 | $params['jwt'] = $this->jwt; 44 | 45 | if ($this->accessToken) 46 | $params['access_token'] = $this->accessToken; 47 | 48 | $url = $this->end_point . $path. '.' . $this->format; 49 | 50 | return $this->httpRequest($url, $method, $params); 51 | } 52 | 53 | public function httpRequest($url, $method, $params){ 54 | $args = array( 55 | 'method' => $method, 56 | 'timeout' => 60, 57 | 'redirection' => 5, 58 | 'httpversion' => '1.0', 59 | 'user-agent' => $this->userAgent, 60 | //'blocking' => true, 61 | 'headers' => array('Expect'=>''), 62 | //'cookies' => array(), 63 | //'compress' => false, 64 | //'decompress' => true, 65 | 'sslverify' => false, 66 | //'stream' => false, 67 | //'filename' => null 68 | ); 69 | 70 | switch($method){ 71 | case 'GET': 72 | $url .= '?' . http_build_query($params, null, '&'); // overwrite arg_separator.output 73 | break; 74 | case 'POST': 75 | $args['body'] = $params; // http类自己会做 http_build_query($params, null, '&') 并指定Content-Type 76 | break; 77 | default: 78 | } 79 | 80 | $response = $this->http->request($url, $args); 81 | 82 | if (isset($response->errors)){ 83 | if (isset($response->errors['http_request_failed'])){ 84 | $message = $response->errors['http_request_failed'][0]; 85 | if ($message == 'name lookup timed out') 86 | $message = 'DNS解析超时,请重试或检查你的主机的域名解析(DNS)设置。'; 87 | elseif (stripos($message, 'Could not open handle for fopen') === 0) 88 | $message = '无法打开fopen句柄,请重试或联系多说管理员。http://dev.duoshuo.com/'; 89 | elseif (stripos($message, 'Couldn\'t resolve host') === 0) 90 | $message = '无法解析duoshuo.com域名,请重试或检查你的主机的域名解析(DNS)设置。'; 91 | elseif (stripos($message, 'Operation timed out after ') === 0) 92 | $message = '操作超时,请重试或联系多说管理员。http://dev.duoshuo.com/'; 93 | throw new Duoshuo_Exception($message, Duoshuo_Exception::REQUEST_TIMED_OUT); 94 | } 95 | else 96 | throw new Duoshuo_Exception('连接服务器失败, 详细信息:' . json_encode($response->errors), Duoshuo_Exception::REQUEST_TIMED_OUT); 97 | } 98 | 99 | $json = json_decode($response['body'], true); 100 | return $json === null ? $response['body'] : $json; 101 | } 102 | 103 | /** 104 | * 105 | * @param string $type 106 | * @param array $keys 107 | */ 108 | public function getAccessToken( $type, $keys ) { 109 | $params = array( 110 | 'client_id' => $this->shortName, 111 | 'client_secret' => $this->secret, 112 | ); 113 | 114 | switch($type){ 115 | case 'token': 116 | $params['grant_type'] = 'refresh_token'; 117 | $params['refresh_token'] = $keys['refresh_token']; 118 | break; 119 | case 'code': 120 | $params['grant_type'] = 'authorization_code'; 121 | $params['code'] = $keys['code']; 122 | $params['redirect_uri'] = $keys['redirect_uri']; 123 | break; 124 | case 'password': 125 | $params['grant_type'] = 'password'; 126 | $params['username'] = $keys['username']; 127 | $params['password'] = $keys['password']; 128 | break; 129 | default: 130 | throw new Duoshuo_Exception("wrong auth type"); 131 | } 132 | 133 | $accessTokenUrl = $this->end_point . 'oauth2/access_token'; 134 | $response = $this->httpRequest($accessTokenUrl, 'POST', $params); 135 | 136 | $token = $response; 137 | if ( is_array($token) && !isset($token['error']) ) { 138 | $this->access_token = $token['access_token']; 139 | if (isset($token['refresh_token'])) // 可能没有refresh_token 140 | $this->refresh_token = $token['refresh_token']; 141 | } else { 142 | throw new Duoshuo_Exception("get access token failed." . $token['error']); 143 | } 144 | 145 | return $token; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /duoshuo/Exception.php: -------------------------------------------------------------------------------- 1 | plugin = $plugin; 15 | } 16 | 17 | /** 18 | * 从服务器pull评论到本地 19 | * 20 | * @param array $input 21 | */ 22 | public function sync_log($input = array()){ 23 | $syncLock = $this->plugin->getOption('sync_lock');//检查是否正在同步评论 同步完成后该值会置0 24 | if($syncLock && $syncLock > time()- 300){//正在或5分钟内发生过写回但没置0 25 | $this->response = array( 26 | 'code' => Duoshuo_Exception::SUCCESS, 27 | 'response'=> '同步中,请稍候', 28 | ); 29 | return; 30 | } 31 | 32 | try{ 33 | $this->response['affected'] = $this->plugin->syncLog(); 34 | $this->response['last_log_id'] = $this->plugin->getOption('last_log_id'); 35 | } 36 | catch(Exception $ex){ 37 | //$this->plugin->updateOption('sync_lock', $ex->getLine()); 38 | } 39 | 40 | $this->response['code'] = Duoshuo_Exception::SUCCESS; 41 | } 42 | 43 | public function update_option($input = array()){ 44 | //duoshuo_short_name 45 | //duoshuo_secret 46 | //duoshuo_notice 47 | foreach($input as $optionName => $optionValue) 48 | if (substr($optionName, 0, 8) === 'duoshuo_'){ 49 | update_option($_POST['option'], $_POST['value']); 50 | } 51 | $this->response['code'] = 0; 52 | } 53 | 54 | public function dispatch($input){ 55 | if (!isset($input['signature'])) 56 | throw new Duoshuo_Exception('Invalid signature.', Duoshuo_Exception::INVALID_SIGNATURE); 57 | 58 | $signature = $input['signature']; 59 | unset($input['signature']); 60 | 61 | ksort($input); 62 | $baseString = http_build_query($input, null, '&'); 63 | 64 | $expectSignature = base64_encode(Duoshuo_Abstract::hmacsha1($baseString, $this->plugin->getOption('secret'))); 65 | if ($signature !== $expectSignature) 66 | throw new Duoshuo_Exception('Invalid signature, expect: ' . $expectSignature . '. (' . $baseString . ')', Duoshuo_Exception::INVALID_SIGNATURE); 67 | 68 | $method = $input['action']; 69 | 70 | if (!method_exists($this, $method)) 71 | throw new Duoshuo_Exception('Unknown action.', Duoshuo_Exception::OPERATION_NOT_SUPPORTED); 72 | 73 | $this->response = array(); 74 | $this->$method($input); 75 | $this->sendResponse(); 76 | } 77 | 78 | public function sendResponse(){ 79 | echo json_encode($this->response); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /duoshuo/WordPress.php: -------------------------------------------------------------------------------- 1 | 0, 30 | 'duoshuo_api_hostname' => 'api.duoshuo.com', 31 | 'duoshuo_cron_sync_enabled' => 1, 32 | 'duoshuo_seo_enabled' => 1, 33 | 'duoshuo_postpone_print_scripts'=> 0, 34 | 'duoshuo_cc_fix' => 1, 35 | 'duoshuo_sync_pingback_and_trackback'=> 0, 36 | 'duoshuo_social_login_enabled' => 1, 37 | 'duoshuo_comments_wrapper_intro'=> '', 38 | 'duoshuo_comments_wrapper_outro'=> '', 39 | 'duoshuo_last_log_id' => 0, 40 | 'duoshuo_theme' => 'default', 41 | 'duoshuo_style_patch' => 1, 42 | ); 43 | 44 | protected function __construct(){ 45 | $this->shortName = $this->getOption('short_name'); 46 | $this->secret = $this->getOption('secret'); 47 | 48 | foreach (self::$_defaultOptions as $optionName => $value) 49 | if (get_option($optionName) === false) 50 | update_option($optionName, $value); 51 | 52 | $this->pluginDirUrl = plugin_dir_url(__FILE__); 53 | } 54 | 55 | /** 56 | * 57 | * @return Duoshuo_WordPress 58 | */ 59 | public static function getInstance(){ 60 | if (self::$_instance === null) 61 | self::$_instance = new self(); 62 | return self::$_instance; 63 | } 64 | 65 | public function timezone(){ 66 | return get_option('gmt_offset'); 67 | } 68 | 69 | public function getOption($key){ 70 | return get_option('duoshuo_' . $key); 71 | } 72 | 73 | public function updateOption($key, $value){ 74 | return update_option('duoshuo_' . $key, $value); 75 | } 76 | 77 | public function deleteOption($key){ 78 | return delete_option('duoshuo_' . $key); 79 | } 80 | 81 | public function updateUserMeta($userId, $metaKey, $metaValue){ 82 | return function_exists('update_user_meta') 83 | ? update_user_meta($userId, $metaKey, $metaValue) 84 | : update_usermeta($userId, $metaKey, $metaValue); 85 | } 86 | 87 | public function getUserMeta($userId, $metaKey, $single = false){ 88 | //get_user_meta 从3.0开始有效: get_usermeta($user->ID, $blog_prefix.'capabilities', true); 89 | return function_exists('get_user_meta') 90 | ? get_user_meta($userId, $metaKey, true) 91 | : get_usermeta($userId, $metaKey); 92 | } 93 | 94 | public function updateThreadMeta($threadId, $metaKey, $metaValue){ 95 | return update_post_meta($threadId, $metaKey, $metaValue); 96 | } 97 | 98 | public function threadKey($post){ 99 | return $post->post_status == 'inherit' 100 | ? ($post->post_parent ? $post->post_parent : null) 101 | : $post->ID; 102 | } 103 | 104 | public function topPost($post){ 105 | return $post->post_status == 'inherit' 106 | ? ($post->post_parent ? get_post($post->post_parent) : null) 107 | : $post; 108 | } 109 | 110 | public function get_blog_prefix(){ 111 | global $wpdb; 112 | return method_exists($wpdb,'get_blog_prefix') 113 | ? $wpdb->get_blog_prefix() 114 | : $wpdb->prefix; 115 | } 116 | 117 | public function connected(){ 118 | $connected_failed = get_option('duoshuo_connect_failed'); 119 | return $connected_failed 120 | ? (time() - $connected_failed > 1800) 121 | : true; 122 | } 123 | 124 | public function connectFailed(){ 125 | update_option('duoshuo_connect_failed', time()); 126 | } 127 | 128 | public function normalizeUrl($url){ 129 | if (strpos($url, '/') === 0){ 130 | $siteurl = get_option('siteurl'); 131 | 132 | if (strlen($siteurl) >=8 && (false !== $pos = strpos($siteurl, '/', 8))) 133 | $siteurl = substr($siteurl, 0, $pos); 134 | 135 | return $siteurl . $url; 136 | } 137 | else{ 138 | return $url; 139 | } 140 | } 141 | 142 | public function allowedHtml($tags, $context = ''){ 143 | if (!isset($tags['img'])) 144 | $tags['img'] = array(); 145 | 146 | $tags['img']['src'] = true; 147 | return $tags; 148 | } 149 | 150 | public function setJwtCookie($logged_in_cookie, $expire, $expiration, $user_id, $scheme){ 151 | $jwt = $this->jwt($user_id); 152 | $secure = $scheme == 'secure_auth'; 153 | $secure_logged_in_cookie = apply_filters('secure_logged_in_cookie', false, $user_id, $secure); 154 | 155 | // $httponly = false 156 | //setcookie('duoshuo_token', $jwt, $expire, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN, $secure, true); 157 | setcookie('duoshuo_token', $jwt, $expire, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, $secure); 158 | setcookie('duoshuo_token', $jwt, $expire, COOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie); 159 | if ( COOKIEPATH != SITECOOKIEPATH ) 160 | setcookie('duoshuo_token', $jwt, $expire, SITECOOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie); 161 | 162 | $this->syncUserToRemote($user_id); 163 | } 164 | 165 | public function clearJwtCookie(){ 166 | // 3.5版本之前没有定义 YEAR_IN_SECONDS 167 | //setcookie( 'duoshuo_token', ' ', time() - 31536000, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN ); 168 | setcookie( 'duoshuo_token', ' ', time() - 31536000, ADMIN_COOKIE_PATH, COOKIE_DOMAIN ); 169 | setcookie( 'duoshuo_token', ' ', time() - 31536000, COOKIEPATH, COOKIE_DOMAIN ); 170 | 171 | if ( COOKIEPATH != SITECOOKIEPATH ) 172 | setcookie( 'duoshuo_token', ' ', time() - 31536000, SITECOOKIEPATH, COOKIE_DOMAIN ); 173 | } 174 | 175 | public function oauthConnect(){ 176 | if (!$this->connected()) 177 | return ; 178 | 179 | if (!isset($_GET['code'])) 180 | return false; 181 | 182 | try{ 183 | $keys = array( 184 | 'code' => $_GET['code'], 185 | 'redirect_uri' => (is_ssl()?'https':'http').'://duoshuo.com/login-callback/', 186 | ); 187 | 188 | $token = $this->getClient()->getAccessToken('code', $keys); 189 | 190 | if ($token['code'] != 0) 191 | return false; 192 | 193 | $this->userLogin($token); 194 | } 195 | catch(Duoshuo_Exception $e){ 196 | $this->connectFailed(); 197 | } 198 | } 199 | 200 | public function userLogin($token){ 201 | global $wpdb, $error; 202 | 203 | nocache_headers(); 204 | if (isset($token['user_key'])){//登陆成功 205 | $user = get_user_by('id', $token['user_key']); 206 | 207 | $this->updateUserMeta($user->ID, 'duoshuo_access_token', $token['access_token']); 208 | 209 | wp_clear_auth_cookie(); 210 | wp_set_auth_cookie($user->ID, true, is_ssl()); 211 | wp_set_current_user($user->ID); 212 | 213 | if (isset($_GET['redirect_to'])){ 214 | // wordpress 采用的是redirect_to字段 215 | wp_redirect($_GET['redirect_to']); 216 | exit; 217 | } 218 | } 219 | else{ 220 | if (isset($_GET['redirect_to']) && $_GET['redirect_to'] !== admin_url()){// 如果是在内容页登录,无论如何都把用户带回内容页 221 | wp_redirect($_GET['redirect_to']); 222 | exit; 223 | } 224 | else{ 225 | $client = new Duoshuo_Client($this->shortName, $this->secret, null, $token['access_token']); 226 | $response = $client->request('GET', 'users/profile', array('user_id'=> $token['user_id'])); 227 | 228 | if (get_option('users_can_register')){// 如果站点开启注册 229 | // 要求用户输入帐号进行绑定 230 | $query = array( 231 | 'action' => 'register', 232 | //'duoshuo_user_id' => $token['user_id'], 233 | 'duoshuo_access_token'=>$token['access_token'], 234 | ); 235 | 236 | $this->duoshuoUserId = $token['user_id']; 237 | 238 | $registerUrl = site_url( 'wp-login.php?' . http_build_query($query), 'login' ); 239 | $error = '' 240 | . '' . esc_html($response['response']['name']) . ',欢迎!
' 241 | . '请绑定已注册的本站帐号;
' 242 | . '如果你还没有本站帐号,请注册'; 243 | } 244 | else{// 如果站点未开启注册 245 | //如果是从wp-login页面发起的请求,就不触发重定向 246 | $error = '' 247 | . '' . esc_html($response['response']['name']) . ',欢迎!
' 248 | . '你还没有和本站的用户帐号绑定,
' 249 | . '如果你已经注册,请先登录之后绑定社交帐号'; 250 | } 251 | } 252 | } 253 | } 254 | 255 | public function bindUser($user_login, $user = null){ 256 | // 早期版本wp_login接口只有第一个参数 257 | if (empty($user)){ 258 | $user = get_user_by('login', $user_login); 259 | } 260 | 261 | if (isset($_POST['duoshuo_user_id'])){ 262 | $query = array( 263 | 'master_user_id'=> $_POST['duoshuo_user_id'], 264 | 'jwt' => $this->jwt($user->ID), 265 | 'redirect_uri' => admin_url(), 266 | ); 267 | 268 | wp_redirect((is_ssl()?'https':'http').'://duoshuo.com/merge/?'. http_build_query($query, null, '&')); 269 | exit; 270 | } 271 | } 272 | 273 | public function userRegisterHook($userId){ 274 | if (!$this->connected()) 275 | return ; 276 | 277 | // WP_User 278 | $user = get_user_by('id', $userId); 279 | try{ 280 | if (isset($_POST['user_login']) && isset($_POST['duoshuo_access_token'])){ 281 | // 已登录多说帐号,且多说帐号并未与本站user_key绑定 282 | $client = new Duoshuo_Client($this->shortName, $this->secret, null, $_POST['duoshuo_access_token']); 283 | 284 | $params = array( 285 | 'user' => $this->packageUser($user), 286 | ); 287 | $remoteResponse = $client->request('POST', 'sites/join', $params); 288 | // 不再需要记录duoshuo_user_id 289 | } 290 | else{ 291 | // 未登录多说帐号 292 | $this->exportUsers(array($user)); 293 | } 294 | } 295 | catch(Duoshuo_Exception $e){ 296 | $this->connectFailed(); 297 | } 298 | } 299 | 300 | public function originalCommentsNotice(){ 301 | echo '
' 302 | . '

多说正在努力地为您的网站提供强大的社会化评论服务,WordPress原生评论数据现在仅用于备份;

' 303 | . '

多说会将每一条评论实时写回本地数据库,您在多说删除/审核了评论,也同样会同步到本地数据;

' 304 | . '

您在本页做的任何管理评论操作,都不会对多说评论框上的评论起作用,请访问评论管理后台进行评论管理。

' 305 | . '
'; 306 | } 307 | 308 | /** 309 | * 310 | * @return Duoshuo_Client 311 | */ 312 | public function getClient($userId = 0){ //如果不输入参数,就是游客 313 | if ($userId !== null){ 314 | $accessToken = $this->getUserMeta($userId, 'duoshuo_access_token'); 315 | 316 | if (is_string($accessToken)) 317 | $client = new Duoshuo_Client($this->shortName, $this->secret, $this->jwt($userId), $accessToken); 318 | } 319 | if (!isset($client)) 320 | $client = new Duoshuo_Client($this->shortName, $this->secret, $this->jwt($userId)); 321 | 322 | $apiHostname = $this->getOption('api_hostname'); 323 | if ($apiHostname) 324 | $client->end_point = 'http://' . $apiHostname . '/'; 325 | 326 | return $client; 327 | } 328 | 329 | public function config(){ 330 | /*if ($_SERVER['REQUEST_METHOD'] == 'POST' && !($this->shortName && $this->secret)){ 331 | self::registerSite(); 332 | }*/ 333 | include dirname(__FILE__) . '/config.php'; 334 | } 335 | 336 | public function sync(){ 337 | include dirname(__FILE__) . '/sync.php'; 338 | } 339 | 340 | public function manage(){ 341 | include dirname(__FILE__) . '/manage.php'; 342 | } 343 | 344 | public function themes(){ 345 | include dirname(__FILE__) . '/themes.php'; 346 | } 347 | 348 | public function preferences(){ 349 | include dirname(__FILE__) . '/preferences.php'; 350 | } 351 | 352 | public function checkDependency($name){ 353 | global $wp_version; 354 | 355 | switch ($name){ 356 | case 'php': 357 | return array(PHP_VERSION, version_compare(PHP_VERSION, '5.3.0', '<') ? '建议升级php到5.3或以上' : true); 358 | case 'wordpress': 359 | return array($wp_version, version_compare($wp_version, '3.3.0', '<') ? '建议升级WordPress到3.3或以上' : true); 360 | case 'json': 361 | if (!extension_loaded('json')) 362 | return array('缺少json扩展', '安装并启用JSON扩展'); 363 | return array(true, true); 364 | case 'curl': 365 | if (!extension_loaded('curl')) 366 | return array('缺少curl扩展', '安装并启用curl扩展'); 367 | if (!function_exists('curl_init')) 368 | return array('curl_init()被禁用', '启用curl_init()函数'); 369 | if (!function_exists('curl_exec')) 370 | return array('curl_exec()被禁用', '启用curl_exec()函数'); 371 | return array(true, true); 372 | case 'fopen': 373 | if (!function_exists('fopen')) 374 | return array('fopen()被禁用', '启用fopen()函数'); 375 | if (!function_exists('ini_get')) 376 | return array('ini_get()被禁用', '启用ini_get()函数'); 377 | if (!ini_get('allow_url_fopen')) 378 | return array('allow_url_fopen被关闭', '在php.ini中设置allow_url_fopen = On'); 379 | return array(true, true); 380 | case 'fsockopen': 381 | if (!function_exists('fsockopen')) 382 | return array('fsockopen()被禁用', '启用fsockopen()函数'); 383 | return array(true, true); 384 | case 'hash_hmac': 385 | if (!function_exists('hash_hmac')) 386 | return array('hash_hmac()不存在', '升级php到5.3或以上'); 387 | return array(true, true); 388 | default: 389 | return array(true, true); 390 | } 391 | } 392 | 393 | public function settings(){ 394 | include dirname(__FILE__) . '/settings.php'; 395 | } 396 | 397 | public function statistics(){ 398 | include dirname(__FILE__) . '/statistics.php'; 399 | } 400 | 401 | public function profile(){ 402 | include dirname(__FILE__) . '/profile.php'; 403 | } 404 | 405 | public function reset(){ 406 | global $wpdb; 407 | //delete_option('duoshuo_short_name'); 408 | 409 | // 删除状态有关的值 410 | delete_option('duoshuo_secret'); 411 | delete_option('duoshuo_synchronized'); 412 | delete_option('duoshuo_connect_failed'); 413 | delete_option('duoshuo_notice'); 414 | delete_option('duoshuo_sync_lock'); 415 | 416 | // 删除所有选项 417 | foreach (self::$_defaultOptions as $optionName => $value) 418 | delete_option($optionName); 419 | 420 | // WP 2.9 以后支持这个函数 421 | if (function_exists('delete_metadata')){ 422 | delete_metadata('user', 0, 'duoshuo_access_token', '', true); 423 | delete_metadata('user', 0, 'duoshuo_user_id', '', true); 424 | delete_metadata('post', 0, 'duoshuo_thread_id', '', true); 425 | delete_metadata('comment', 0, 'duoshuo_parent_id', '', true); 426 | delete_metadata('comment', 0, 'duoshuo_post_id', '', true); 427 | } 428 | 429 | //清空comment_agent字段 430 | $wpdb->get_results($wpdb->prepare("update wp_comments set comment_agent='' where comment_agent LIKE '%%Duoshuo/%%'")); 431 | 432 | $redirect_url = add_query_arg('message', 'reset', admin_url('admin.php?page=duoshuo')); 433 | wp_redirect($redirect_url); 434 | exit; 435 | } 436 | 437 | /** 438 | * 关闭默认的评论,避免spammer 439 | */ 440 | public function commentsOpen($open, $post_id = null) { 441 | //if ($this->EMBED || get_post_meta($post_id, 'duoshuo_thread_id', true)) 442 | // return false; 443 | $script_name = array_pop( explode( '/', $_SERVER['PHP_SELF'] ) ); 444 | if (preg_match('/wp\-comments\-post\.php$/', $script_name) && get_post_meta($post_id, 'duoshuo_status', true) !== 'disabled') 445 | return false; 446 | 447 | return $open; 448 | } 449 | 450 | public function commentsTemplate($value){ 451 | global $wpdb, $post, $comments; 452 | 453 | $topPost = $this->topPost($post); 454 | 455 | if ($topPost === null) // 可能是inherit 但post_parent=0 456 | return $value; 457 | 458 | if ( !( is_singular() && ( have_comments() || 'open' == $topPost->comment_status ) ) ) { 459 | return $value; 460 | } 461 | 462 | if (get_post_meta($topPost->ID, 'duoshuo_status', true) == 'disabled') 463 | return $value; 464 | 465 | /* 466 | if ( !dsq_is_installed() || !dsq_can_replace() ) { 467 | return $value; 468 | }*/ 469 | 470 | $threadId = get_post_meta($topPost->ID, 'duoshuo_thread_id', true); 471 | 472 | if (empty($threadId) && $this->connected()){ 473 | $this->syncUserToRemote($topPost->post_author); 474 | $this->syncPostToRemote($topPost->ID, $topPost); 475 | try{ 476 | $comments = $wpdb->get_results($wpdb->prepare("SELECT * FROM $wpdb->comments where comment_post_ID = %d AND comment_agent NOT LIKE '%%Duoshuo/%%' order by comment_ID asc", $topPost->ID)); 477 | $this->exportComments($comments); 478 | } 479 | catch(Duoshuo_Exception $e){ 480 | $this->connectFailed(); 481 | } 482 | } 483 | 484 | $this->EMBED = true; 485 | return dirname(__FILE__) . '/comments.php'; 486 | // return $value; 487 | } 488 | 489 | public function commentsPopupLinkAttributes($attribs){ 490 | global $post; 491 | 492 | $threadKey = $this->threadKey($post); 493 | if ($threadKey === null) // post_status = inherit, post_parent = 0 494 | return $attribs; 495 | 496 | if (has_filter('comments_number', array($this, 'commentsText'))) 497 | remove_filter('comments_number', array($this, 'commentsText')); 498 | 499 | $attribs .= ' class="ds-thread-count" data-thread-key="' . $threadKey .'"'; 500 | return $attribs; 501 | } 502 | 503 | public function commentsText($comment_text, $number = null){ 504 | global $post; 505 | 506 | $threadKey = $this->threadKey($post); 507 | if ($threadKey === null) // post_status = inherit, post_parent = 0 508 | return $comment_text; 509 | 510 | $attribs = 'class="ds-thread-count" data-thread-key="' . $threadKey .'"'; 511 | if (preg_match('/^<([a-z]+)( .*)?>(.*)<\/([a-z]+)>$/i', $comment_text, $matches) && $matches[1] == $matches[4]) 512 | return "<$matches[1] $attribs$matches[2]>$matches[3]"; 513 | else 514 | return "$comment_text"; 515 | } 516 | 517 | public function jwt($userId = null){ 518 | if ($userId === null) 519 | $user = wp_get_current_user(); 520 | elseif($userId != 0) 521 | $user = get_user_by( 'id', $userId); 522 | 523 | if (empty($user) || !$user->ID) 524 | return null; 525 | 526 | $token = array( 527 | 'short_name'=> $this->shortName, 528 | 'user_key' => $user->ID, 529 | 'name' => $user->display_name, 530 | ); 531 | 532 | return self::encodeJWT($token, $this->secret); 533 | } 534 | 535 | /** 536 | * @deprecated 537 | * @param string|int $userId 538 | * @return mixed 539 | */ 540 | public function userData($userId = null){ // null 代表当前登录用户,0代表游客 541 | if ($userId === null) 542 | $current_user = wp_get_current_user(); 543 | elseif($userId != 0) 544 | $current_user = get_user_by( 'id', $userId); 545 | 546 | if (isset($current_user) && $current_user->ID) { 547 | $avatar_tag = get_avatar($current_user->ID); 548 | $avatar_data = array(); 549 | preg_match('/(src)=((\'|")[^(\'|")]*(\'|"))/i', $avatar_tag, $avatar_data); 550 | $avatar = htmlspecialchars_decode(str_replace(array('"', "'"), '', $avatar_data[2]), ENT_QUOTES); 551 | 552 | return array( 553 | 'id' => $current_user->ID, 554 | 'name' => $current_user->display_name, 555 | 'avatar' => $avatar, 556 | 'email' => $current_user->user_email, 557 | ); 558 | } 559 | else{ 560 | return array(); 561 | } 562 | } 563 | 564 | public function buildQuery($options = array()){ 565 | $query = array( 566 | 'short_name' => $this->shortName, 567 | 'sso' => array( 568 | 'login'=> site_url('wp-login.php', 'login') .'?action=duoshuo_login', 569 | 'logout'=> htmlspecialchars_decode(wp_logout_url(), ENT_QUOTES), 570 | ), 571 | ); 572 | 573 | if ($theme = $this->getOption('theme')) 574 | $query['theme'] = $theme; 575 | 576 | if ($this->getOption('style_patch')) 577 | $query['stylePatch'] = 'wordpress/' . str_replace(' ', '_', function_exists('wp_get_theme') ? wp_get_theme()->get('Name') : get_current_theme()); 578 | 579 | if (!empty($options)) 580 | $query['options'] = $options; 581 | 582 | return $query; 583 | } 584 | 585 | public function appendScripts(){ 586 | if ($this->_scriptsPrinted) 587 | return; 588 | $this->_scriptsPrinted = true; 589 | ?> 590 | 595 | 596 | 597 | 598 | 599 | _scriptsPrinted) 607 | return; 608 | $this->_scriptsPrinted = true; 609 | ?> 610 | duoshuoUserId)){ // 登录后发现没有本站帐号,输入帐号进行绑定 ?> 627 | 628 | 631 | 632 | 'duoshuo_login', 'redirect_to'=>urlencode(admin_url())), site_url('wp-login.php', 'login'));?> 636 |
637 | printScripts();?> 638 | 642 | shortName = $_GET['short_name']; 650 | $this->secret = $_GET['secret']; 651 | ?> 652 | 655 | getOption('debug')); 665 | 666 | $progress = $this->getOption('synchronized'); 667 | 668 | if (!$progress || is_numeric($progress))// 之前已经完成了导出流程 669 | $progress = 'user/0'; 670 | 671 | list($type, $offset) = explode('/', $progress); 672 | 673 | try{ 674 | switch($type){ 675 | case 'user': 676 | $limit = 30; 677 | // 不包括user_login, user_pass 678 | $columns = array('ID', 'user_nicename', 'user_email', 'user_url', 'user_registered', 'display_name'); 679 | $users = $wpdb->get_results( $wpdb->prepare("SELECT " . implode(',', $columns) . " FROM $wpdb->users order by ID asc limit %d,%d", $offset, $limit)); 680 | $count = $this->exportUsers($users); 681 | break; 682 | case 'post': 683 | $limit = 10; 684 | $columns = array('ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_excerpt', 'post_status', 'comment_status', 'ping_status', 'post_name', 'post_modified_gmt', 'guid', 'post_type', 'post_parent'); 685 | $posts = $wpdb->get_results( $wpdb->prepare("SELECT " . implode(',', $columns) . " FROM $wpdb->posts where post_type not in ('attachment', 'nav_menu_item', 'revision') and post_status not in ('auto-draft', 'draft', 'trash', 'inherit') order by ID asc limit %d,%d", $offset, $limit) );// 'inherit' 不再进行同步 686 | $count = $this->exportPosts($posts); 687 | break; 688 | case 'comment': 689 | $limit = 50; 690 | $comments = $wpdb->get_results( $wpdb->prepare("SELECT * FROM $wpdb->comments where comment_agent NOT LIKE '%%Duoshuo/%%' order by comment_ID asc limit %d,%d", $offset, $limit)); 691 | $count = $this->exportComments($comments); 692 | break; 693 | default: 694 | } 695 | 696 | if ($count == $limit){ 697 | $progress = $type . '/' . ($offset + $limit); 698 | } 699 | elseif($type == 'user') 700 | $progress = 'post/0'; 701 | elseif($type == 'post') 702 | $progress = 'comment/0'; 703 | elseif($type == 'comment') 704 | $progress = time(); 705 | 706 | update_option('duoshuo_synchronized', $progress); 707 | $response = array( 708 | 'progress'=>$progress, 709 | 'code' => 0 710 | ); 711 | $this->sendJsonResponse($response); 712 | } 713 | catch(Duoshuo_Exception $e){ 714 | $this->sendException($e); 715 | } 716 | } 717 | 718 | public function packageOptions(){ 719 | global $wp_version; 720 | 721 | $options = array( 722 | 'url' => get_option('home'), 723 | 'siteurl' => get_option('siteurl'), 724 | 'admin_email' => get_option('admin_email'), 725 | 'timezone' => get_option('timezone_string'), 726 | 'use_smilies' => get_option('use_smilies'), 727 | 'name' => html_entity_decode(get_option('blogname'), ENT_QUOTES, 'UTF-8'), 728 | 'description' => html_entity_decode(get_option('blogdescription'), ENT_QUOTES, 'UTF-8'), 729 | 'system_theme' => function_exists('wp_get_theme') ? wp_get_theme()->get('Name') : get_current_theme(),//'current_theme'=>'system_theme', 730 | 'system_version'=> $wp_version, 731 | 'plugin_version'=> self::VERSION, 732 | 'local_api_url' => $this->pluginDirUrl . 'api.php', 733 | 'oauth_proxy_url'=> $this->pluginDirUrl . 'oauth-proxy.php', 734 | ); 735 | 736 | $akismet_api_key = get_option('wordpress_api_key'); 737 | if ($akismet_api_key) 738 | $options['akismet_api_key'] = $akismet_api_key; 739 | 740 | return $options; 741 | } 742 | 743 | /** 744 | * 通知多说服务器更新站点信息 745 | */ 746 | public function updateSite(){ 747 | if (!$this->connected()) 748 | return; 749 | 750 | $params = $this->packageOptions(); 751 | $user = wp_get_current_user(); 752 | 753 | try{ 754 | $response = $this->getClient($user->ID)->request('POST', 'sites/settings', $params); 755 | 756 | if (is_string($response)){ 757 | $this->errorMessages[] = $response; 758 | } 759 | elseif(isset($response['code']) && $response['code'] != 0){ 760 | $this->errorMessages[] = $response['errorMessage']; 761 | } 762 | } 763 | catch(Duoshuo_Exception $e){ 764 | $this->connectFailed(); 765 | } 766 | } 767 | /* 768 | public function updatedOption($option, $oldvalue = null, $newvalue = null){ 769 | $options = array('blogname', 'blogdescription', 'home', 'siteurl', 'admin_email', 'timezone_string', 'use_smilies', 'system_theme', 'akismet_api_key'); 770 | 771 | if (in_array($option, $options)) 772 | $this->needToUpdateSite = true; 773 | }*/ 774 | 775 | public function syncUserToRemote($userId){ 776 | if (!$this->connected()) 777 | return ; 778 | 779 | // WP_User 780 | $userData = get_user_by('id', $userId); 781 | 782 | try{ 783 | $this->exportUsers(array($userData)); 784 | } 785 | catch(Duoshuo_Exception $e){ 786 | $this->connectFailed(); 787 | } 788 | } 789 | 790 | /** 791 | * 同步这篇文章到所有社交网站 792 | * @param string $postId 793 | */ 794 | public function syncPostToRemote($postId, $post = null){ 795 | if (!$this->connected()) 796 | return; 797 | 798 | if ($post == null) 799 | $post = get_post($postId); 800 | 801 | if (in_array($post->post_type, array('nav_menu_item', 'revision', 'attachment')) 802 | || in_array($post->post_status, array('inherit', 'auto-draft', 'draft', 'trash'))) //'inherit' 不再进行同步 803 | return ; 804 | 805 | $params = $this->packageThread($post); 806 | 807 | if (isset($_POST['sync_to'])){ 808 | if ($_POST['sync_to'][0] == 'placeholder') 809 | unset($_POST['sync_to'][0]); 810 | $params['sync_to'] = implode(',', $_POST['sync_to']); 811 | } 812 | 813 | try{ 814 | $response = $this->getClient($post->post_author)->request('POST', 'threads/sync', $params); 815 | 816 | unset($_POST['sync_to']); //避免某些插件多次触发save_post 817 | 818 | if (is_array($response) && isset($response['code']) && $response['code'] == 0 && isset($response['response'])) 819 | update_post_meta($post->ID, 'duoshuo_thread_id', $response['response']['thread_id']); 820 | } 821 | catch(Duoshuo_Exception $e){ 822 | $this->connectFailed(); 823 | } 824 | } 825 | 826 | public function packageUser($user){ 827 | static $roleMap = array( 828 | 'administrator' => 'administrator', 829 | 'editor' => 'editor', 830 | 'author' => 'author', 831 | 'contributor' => 'user', 832 | 'subscriber' => 'user', 833 | ); 834 | 835 | if ($user instanceof WP_User){ // wordpress 3.3 836 | $userData = $user->data; 837 | unset($userData->user_pass); 838 | unset($userData->user_login); 839 | $capabilities = $user->caps; 840 | } 841 | else{ 842 | $userData = $user; 843 | unset($userData->user_pass); 844 | unset($userData->user_login); 845 | $capabilities = $this->getUserMeta($user->ID, $this->get_blog_prefix().'capabilities', true); 846 | } 847 | 848 | $data = array( 849 | 'user_key' => $userData->ID, 850 | 'name' => $userData->display_name, 851 | 'email' => $userData->user_email, 852 | 'url' => $userData->user_url, 853 | 'created_at'=> $userData->user_registered, 854 | 'meta' => json_encode($userData), 855 | ); 856 | 857 | $avatar_data = array(); 858 | if (preg_match('/(src)=((\'|")[^(\'|")]*(\'|"))/i', get_avatar($userData->ID), $avatar_data)) 859 | $data['avatar_url'] = htmlspecialchars_decode(str_replace(array('"', "'"), '', $avatar_data[2]), ENT_QUOTES); 860 | 861 | foreach($roleMap as $wpRole => $role) 862 | if (isset($capabilities[$wpRole]) && $capabilities[$wpRole]){ 863 | $data['role'] = $role; 864 | break; 865 | } 866 | 867 | return $data; 868 | } 869 | 870 | public function packageThread($post){ 871 | $post->custom = get_post_custom($post->ID); 872 | $meta = clone ($post); 873 | unset($meta->post_title); 874 | unset($meta->post_content); 875 | unset($meta->post_excerpt); 876 | unset($meta->post_date_gmt); 877 | unset($meta->post_modified_gmt); 878 | unset($meta->post_name); 879 | unset($meta->post_status); 880 | unset($meta->comment_status); 881 | unset($meta->ping_status); 882 | unset($meta->guid); 883 | unset($meta->post_type); 884 | unset($meta->post_author); 885 | unset($meta->ID); 886 | 887 | $params = array( 888 | 'thread_key'=> $post->ID, 889 | 'author_key'=> $post->post_author, 890 | 'title' => html_entity_decode($post->post_title, ENT_QUOTES, 'UTF-8'), 891 | 'content' => $post->post_content, 892 | 'excerpt' => $post->post_excerpt, 893 | 'created_at'=> mysql2date('Y-m-d\TH:i:s+00:00', $post->post_date_gmt), 894 | 'updated_at'=> mysql2date('Y-m-d\TH:i:s+00:00', $post->post_modified_gmt), 895 | 'ip' => $_SERVER['REMOTE_ADDR'], 896 | 'url' => get_permalink($post), 897 | 'slug' => $post->post_name, 898 | 'status' => $post->post_status, 899 | 'comment_status'=> $post->comment_status, 900 | 'ping_status'=> $post->ping_status, 901 | 'guid' => $post->guid, 902 | 'type' => $post->post_type, 903 | 'meta' => json_encode($meta), 904 | 'source' => 'wordpress', 905 | ); 906 | 907 | if (!class_exists('nggLoader', false) || class_exists('nggRewrite', false)) 908 | $params['filtered_content'] = str_replace(']]>', ']]>', apply_filters('the_content', $post->post_content)); 909 | 910 | if (function_exists('get_post_thumbnail_id')){ // WordPress 2.9开始支持 911 | $post_thumbnail_id = get_post_thumbnail_id( $post->ID ); 912 | if ( $post_thumbnail_id ) { 913 | $params['thumbnail'] = $this->normalizeUrl(wp_get_attachment_url($post_thumbnail_id)); 914 | //$image = wp_get_attachment_image_src( $post_thumbnail_id, $size, false); 915 | //list($src, $width, $height) = $image; 916 | //$meta = wp_get_attachment_metadata($id); 917 | //'large-feature' 918 | //'post-thumbnail' 919 | } 920 | } 921 | 922 | $args = array( 923 | 'post_parent' => $post->ID, 924 | 'post_status' => 'inherit', 925 | 'post_type' => 'attachment', 926 | 'post_mime_type' => 'image', 927 | 'order' => 'ASC', 928 | 'orderby' => 'menu_order ID' 929 | ); 930 | $images = array(); 931 | $children = get_children($args); 932 | if (is_array($children)) 933 | foreach($children as $attachment) 934 | $images[] = $this->normalizeUrl(wp_get_attachment_url($attachment->ID)); 935 | if (!empty($images)) 936 | $params['images'] = json_encode($images); 937 | 938 | /* 939 | $authorId = $this->getUserMeta($post->post_author, 'duoshuo_user_id', true); 940 | if (!empty($authorId)) 941 | $params['author_id'] = $authorId; 942 | 943 | $threadId = get_post_meta($post->ID, 'duoshuo_thread_id', true); 944 | if (!empty($threadId)) 945 | $params['thread_id'] = $threadId;*/ 946 | return $params; 947 | } 948 | 949 | public function packageComment($comment){ 950 | static $statusMap = array( 951 | '0' => 'pending', 952 | '1' => 'approved', 953 | 'trash' => 'deleted', 954 | 'spam' => 'spam', 955 | 'post-trashed'=>'thread-deleted', 956 | ); 957 | $meta = clone ($comment); 958 | unset($meta->comment_ID); 959 | unset($meta->comment_post_ID); 960 | unset($meta->comment_author); 961 | unset($meta->comment_author_email); 962 | unset($meta->comment_author_url); 963 | unset($meta->comment_author_IP); 964 | unset($meta->comment_date_gmt); 965 | unset($meta->comment_content); 966 | unset($meta->comment_karma); 967 | unset($meta->comment_approved); 968 | unset($meta->comment_agent); 969 | unset($meta->comment_type); 970 | unset($meta->comment_parent); 971 | unset($meta->user_id); 972 | 973 | $data = array( 974 | 'thread_key' => $comment->comment_post_ID, 975 | 'post_key' => $comment->comment_ID, 976 | 'author_key' => $comment->user_id, 977 | 'author_name' => htmlspecialchars_decode($comment->comment_author, ENT_QUOTES), 978 | 'author_email' => $comment->comment_author_email, 979 | 'author_url' => $comment->comment_author_url, 980 | 'created_at' => str_replace(' ', 'T', $comment->comment_date_gmt) . '+00:00', 981 | 'message' => $comment->comment_content, 982 | 'agent' => $comment->comment_agent, 983 | 'type' => $comment->comment_type, 984 | 'ip' => $comment->comment_author_IP, 985 | 'status' => $statusMap[$comment->comment_approved], 986 | 'parent_key' => $comment->comment_parent, // TODO 接收的地方要处理一下 987 | 'meta' => json_encode($meta), // comment_date, comment_karma 988 | ); 989 | //'source' => 'import', 990 | 991 | return $data; 992 | } 993 | 994 | public function exportOneComment($comment_ID){ 995 | $comment = get_comment($comment_ID); 996 | 997 | return $this->exportComments(array($comment)); 998 | } 999 | 1000 | /** 1001 | * 从服务器pull评论到本地 1002 | * 1003 | * @param array $posts 1004 | */ 1005 | public function createPost($post){ 1006 | global $wpdb; 1007 | 1008 | // 将img加到白名单中,仅对WordPress 3.5.0以后有效 1009 | if (!has_filter('wp_kses_allowed_html', array($this, 'allowedHtml'))) 1010 | add_filter('wp_kses_allowed_html', array($this, 'allowedHtml')); 1011 | 1012 | static $approvedMap = array( 1013 | 'pending' => '0', 1014 | 'approved' => '1', 1015 | 'deleted' => 'trash', 1016 | 'spam' => 'spam', 1017 | 'thread-deleted'=>'post-trashed', 1018 | ); 1019 | 1020 | $post_id = isset($post['thread_key']) 1021 | ? $post['thread_key'] 1022 | : $wpdb->get_var("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'duoshuo_thread_id' AND meta_value = $post[thread_id]"); 1023 | 1024 | if (!is_numeric($post_id)) // 找不到对应的文章 1025 | return array(); 1026 | 1027 | $data = array( 1028 | 'comment_author' => trim(strip_tags($post['author_name'])), 1029 | 'comment_author_email'=>$post['author_email'], 1030 | 'comment_author_url'=> $post['author_url'], 1031 | 'comment_author_IP' => $post['ip'], 1032 | 'comment_date' => $this->rfc3339_to_mysql($post['created_at']), 1033 | 'comment_date_gmt' => $this->rfc3339_to_mysql_gmt($post['created_at']), 1034 | 'comment_content' => $post['message'], 1035 | 'comment_approved' => $approvedMap[$post['status']], 1036 | 'comment_agent' => 'Duoshuo/' . self::VERSION . ':' . $post['post_id'], 1037 | 'comment_type' => $post['type'], 1038 | 'comment_post_ID' => $post_id, 1039 | //'comment_karma' 1040 | ); 1041 | 1042 | if ($post['parent_id']){ 1043 | $parent_id = $wpdb->get_var( "SELECT comment_ID FROM $wpdb->commentmeta WHERE meta_key = 'duoshuo_post_id' AND meta_value = '$post[parent_id]'"); 1044 | 1045 | if (isset($parent_id)) 1046 | $data['comment_parent'] = $parent_id; 1047 | } 1048 | 1049 | $author_id = isset($post['author_key']) 1050 | ? $post['author_key'] 1051 | : $wpdb->get_var( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = 'duoshuo_user_id' AND meta_value = $post[author_id]"); 1052 | 1053 | if (is_numeric($author_id)) 1054 | $data['user_id'] = $author_id; 1055 | 1056 | if (isset($post['post_key'])){ 1057 | $data['comment_ID'] = $post['post_key']; 1058 | } 1059 | elseif(isset($post['post_id'])){ 1060 | $data['comment_ID'] = $wpdb->get_var( "SELECT comment_ID FROM $wpdb->commentmeta WHERE meta_key = 'duoshuo_post_id' AND meta_value = '$post[post_id]'"); 1061 | } 1062 | 1063 | if(isset($data['comment_ID'])){ 1064 | // wp_update_comment 中会做 wp_filter_comment 1065 | wp_update_comment($data); 1066 | } 1067 | else{ 1068 | $data = wp_filter_comment($data); 1069 | $data['comment_ID'] = wp_insert_comment($data); 1070 | } 1071 | 1072 | if ($post['parent_id']) 1073 | update_comment_meta($data['comment_ID'], 'duoshuo_parent_id', $post['parent_id']); 1074 | else 1075 | delete_comment_meta($data['comment_ID'], 'duoshuo_parent_id'); 1076 | 1077 | update_comment_meta($data['comment_ID'], 'duoshuo_post_id', $post['post_id']); 1078 | 1079 | return array($post_id); 1080 | } 1081 | 1082 | public function deleteForeverPost($postIdArray){ 1083 | global $wpdb; 1084 | 1085 | $commentIdArray = $wpdb->get_col( "SELECT comment_ID FROM $wpdb->commentmeta WHERE meta_key = 'duoshuo_post_id' AND meta_value IN ('" . implode("', '", $postIdArray) . "')"); 1086 | 1087 | if (count($commentIdArray)){ 1088 | $commentIdArray = $wpdb->get_col( "SELECT comment_ID FROM $wpdb->comments WHERE comment_ID IN ('" . implode("', '", $commentIdArray) . "')"); 1089 | 1090 | foreach ($commentIdArray as $commentId) 1091 | wp_delete_comment($commentId, true); 1092 | } 1093 | 1094 | return array(); 1095 | } 1096 | 1097 | public function deletePost($postIdArray){ 1098 | global $wpdb; 1099 | 1100 | $commentIdArray = $wpdb->get_col( "SELECT comment_ID FROM $wpdb->commentmeta WHERE meta_key = 'duoshuo_post_id' AND meta_value IN ('" . implode("', '", $postIdArray) . "')"); 1101 | 1102 | if (count($commentIdArray)){ 1103 | $commentIdArray = $wpdb->get_col( "SELECT comment_ID FROM $wpdb->comments WHERE comment_ID IN ('" . implode("', '", $commentIdArray) . "')"); 1104 | 1105 | foreach ($commentIdArray as $commentId) 1106 | wp_trash_comment($commentId); 1107 | } 1108 | 1109 | return array(); 1110 | } 1111 | 1112 | public function spamPost($postIdArray){ 1113 | global $wpdb; 1114 | 1115 | $commentIdArray = $wpdb->get_col( "SELECT comment_ID FROM $wpdb->commentmeta WHERE meta_key = 'duoshuo_post_id' AND meta_value IN ('" . implode("', '", $postIdArray) . "')"); 1116 | 1117 | if (count($commentIdArray)){ 1118 | $commentIdArray = $wpdb->get_col( "SELECT comment_ID FROM $wpdb->comments WHERE comment_ID IN ('" . implode("', '", $commentIdArray) . "')"); 1119 | 1120 | foreach($commentIdArray as $commentId) 1121 | wp_spam_comment($commentId); 1122 | } 1123 | 1124 | return array(); 1125 | } 1126 | 1127 | public function approvePost($postIdArray){ 1128 | global $wpdb; 1129 | 1130 | $commentIdArray = $wpdb->get_col( "SELECT comment_ID FROM $wpdb->commentmeta WHERE meta_key = 'duoshuo_post_id' AND meta_value IN ('" . implode("', '", $postIdArray) . "')"); 1131 | 1132 | if (count($commentIdArray)){ 1133 | $commentIdArray = $wpdb->get_col( "SELECT comment_ID FROM $wpdb->comments WHERE comment_ID IN ('" . implode("', '", $commentIdArray) . "')"); 1134 | 1135 | foreach ($commentIdArray as $commentId) 1136 | wp_set_comment_status($commentId, 'approve'); 1137 | } 1138 | 1139 | return array(); 1140 | } 1141 | 1142 | 1143 | public function notices(){ 1144 | foreach($this->errorMessages as $message) 1145 | echo '

'.$message.'

'; 1146 | 1147 | $duoshuo_notice = $this->getOption('notice'); 1148 | if (!empty($duoshuo_notice)){//系统推送的通知 1149 | echo '
'.$duoshuo_notice.'
'; 1150 | } 1151 | elseif ($duoshuo_notice === false){ 1152 | update_option('duoshuo_notice', ''); 1153 | } 1154 | 1155 | $messages = array( 1156 | 'registered'=>'注册成功,请同步数据', 1157 | 'reset' =>'已重置', 1158 | ); 1159 | if (isset($_GET['message']) && isset($messages[$_GET['message']])) 1160 | echo '

'.$messages[$_GET['message']].'

'; 1161 | } 1162 | 1163 | public function showException($e){ 1164 | echo '

' . $e->getMessage() . '

'; 1165 | } 1166 | 1167 | public function sendException($e){ 1168 | $response = array( 1169 | 'code' => $e->getCode(), 1170 | 'errorMessage'=>$e->getMessage(), 1171 | ); 1172 | echo json_encode($response); 1173 | exit; 1174 | } 1175 | 1176 | public function sendJsonResponse($response){ 1177 | if (!headers_sent()) { 1178 | nocache_headers(); 1179 | header('Content-type: application/json; charset=UTF-8'); 1180 | } 1181 | 1182 | echo json_encode($response); 1183 | exit; 1184 | } 1185 | 1186 | //发布文章时候的同步设置 1187 | public function syncOptions(){ 1188 | global $post; 1189 | 1190 | switch($post->post_status){ 1191 | case 'auto-draft': 1192 | case 'inherit': 1193 | case 'draft': 1194 | case 'trash': 1195 | break; 1196 | case 'publish': 1197 | break; 1198 | default: 1199 | } 1200 | 1201 | $query = array( 1202 | 'callback' => 'getSyncOptionsCallback', 1203 | //'require' => 'site,visitor,serverTime', 1204 | 'jwt' => $this->jwt(), 1205 | ); 1206 | 1207 | if ($post->ID) 1208 | $query['thread_key'] = $post->ID; 1209 | 1210 | $jsonpUrl = (is_ssl()?'https':'http').'://' . $this->shortName . '.duoshuo.com/api/users/syncOptions.jsonp?' . http_build_query($query, null, '&'); 1211 | ?> 1212 | 1266 | 1270 |
1271 | 1272 | getOption('debug')); 1292 | 1293 | try{ 1294 | $response = array( 1295 | 'count' => $this->syncLog(), 1296 | 'code' => 0 1297 | ); 1298 | $this->sendJsonResponse($response); 1299 | } 1300 | catch(Duoshuo_Exception $e){ 1301 | if ($e->getCode() == Duoshuo_Exception::REQUEST_TIMED_OUT){ 1302 | $this->connectFailed(); 1303 | $this->updateOption('sync_lock', 0); 1304 | } 1305 | 1306 | $this->sendException($e); 1307 | } 1308 | } 1309 | 1310 | public function actionsFilter($actions){ 1311 | /** 1312 | * TODO 1313 | $actions['ds-comments'] = '管理评论'; 1314 | */ 1315 | return $actions; 1316 | } 1317 | 1318 | public function pluginActionLinks($links, $file) { 1319 | if (empty($this->shortName) || empty($this->secret)) // 如果已经设置好了站点信息,就不显示安装 || !is_numeric($this->getOption('synchronized')) 1320 | array_unshift($links, ''.__('Install').''); 1321 | else 1322 | array_unshift($links, ''.__('Settings').''); 1323 | return $links; 1324 | } 1325 | 1326 | public function dashboardWidget(){ 1327 | if ( !$widget_options = get_option( 'dashboard_widget_options' ) ) 1328 | $widget_options = array(); 1329 | 1330 | if ( !isset($widget_options['dashboard_duoshuo']) ) 1331 | $widget_options['dashboard_duoshuo'] = array(); 1332 | 1333 | $widgets = get_option( 'dashboard_widget_options' ); 1334 | $total_items = isset( $widgets['dashboard_duoshuo'] ) && isset( $widgets['dashboard_duoshuo']['items'] ) 1335 | ? absint( $widgets['dashboard_duoshuo']['items'] ) : 5; 1336 | 1337 | echo ''; 1338 | 1339 | $this->printScripts(); 1340 | } 1341 | 1342 | /** 1343 | * The recent comments dashboard widget control. 1344 | * 1345 | * @since 3.0.0 1346 | */ 1347 | public function dashboardWidgetControl() { 1348 | if ( !$widget_options = get_option( 'dashboard_widget_options' ) ) 1349 | $widget_options = array(); 1350 | 1351 | if ( !isset($widget_options['dashboard_duoshuo']) ) 1352 | $widget_options['dashboard_duoshuo'] = array(); 1353 | 1354 | if ( 'POST' == $_SERVER['REQUEST_METHOD'] && isset($_POST['widget-duoshuo']) ) { 1355 | $number = absint( $_POST['widget-duoshuo']['items'] ); 1356 | $widget_options['dashboard_duoshuo']['items'] = $number; 1357 | update_option( 'dashboard_widget_options', $widget_options ); 1358 | } 1359 | 1360 | $number = isset( $widget_options['dashboard_duoshuo']['items'] ) ? (int) $widget_options['dashboard_duoshuo']['items'] : ''; 1361 | 1362 | echo '

'; 1363 | echo '

'; 1364 | } 1365 | 1366 | public function registerSettings(){ 1367 | register_setting('duoshuo', 'duoshuo_short_name'); 1368 | register_setting('duoshuo', 'duoshuo_secret'); 1369 | 1370 | foreach (self::$_defaultOptions as $optionName => $value) 1371 | register_setting('duoshuo', $optionName); 1372 | } 1373 | 1374 | public function updateLocalOptions(){ 1375 | foreach (self::$_defaultOptions as $optionName => $value) 1376 | if (isset($_POST[$optionName])) 1377 | update_option($optionName, stripslashes($_POST[$optionName])); 1378 | 1379 | //stripslashes($_POST['duoshuo_comments_wrapper_intro']) 1380 | //stripslashes($_POST['duoshuo_comments_wrapper_outro']) 1381 | } 1382 | } 1383 | -------------------------------------------------------------------------------- /duoshuo/api.php: -------------------------------------------------------------------------------- 1 | 30, 23 | 'errorMessage' => 'Duoshuo plugin hasn\'t been activated.' 24 | ); 25 | echo json_encode($response); 26 | exit; 27 | } 28 | 29 | require DUOSHUO_PLUGIN_PATH . '/LocalServer.php'; 30 | 31 | $plugin = Duoshuo_WordPress::getInstance(); 32 | 33 | try{ 34 | if ($_SERVER['REQUEST_METHOD'] == 'POST'){ 35 | $input = $_POST; 36 | if (isset($input['spam_confirmed'])) //D-Z Theme 会给POST设置这个参数 37 | unset($input['spam_confirmed']); 38 | 39 | $server = new Duoshuo_LocalServer($plugin); 40 | $server->dispatch($input); 41 | } 42 | } 43 | catch (Exception $e){ 44 | $plugin->sendException($e); 45 | } 46 | -------------------------------------------------------------------------------- /duoshuo/comments-seo.php: -------------------------------------------------------------------------------- 1 | comment_type ) : 6 | case 'pingback' : 7 | case 'trackback' : 8 | ?> 9 |
  • 10 |

    ', '' ); ?>

    11 | 13 | break; 14 | default : 15 | ?> 16 |
  • id="li-comment-"> 17 |
    18 |
    19 | 20 | said:', 'duoshuo' ), 23 | sprintf( '%s', get_comment_author_link() ), 24 | sprintf( '', 25 | esc_url( get_comment_link( $comment->comment_ID ) ), 26 | get_comment_time( 'c' ), 27 | /* translators: 1: date, 2: time */ 28 | sprintf( __( '%1$s at %2$s', 'duoshuo' ), get_comment_date(), get_comment_time() ) 29 | ) 30 | ); 31 | ?> 32 | 33 |
    34 | 35 |
    36 | 37 |
    38 | 40 | break; 41 | endswitch; 42 | } 43 | } 44 | ?> 45 |
    46 | 47 | 1 && get_option('page_comments')): ?> 48 | 53 | 54 | 55 |
      56 | 'duoshuo_comment')); 61 | ?> 62 |
    63 | 64 | 1 && get_option( 'page_comments' ) ) :?> 65 | 70 | 71 | 72 |
    -------------------------------------------------------------------------------- /duoshuo/comments.php: -------------------------------------------------------------------------------- 1 | printScripts(); 7 | 8 | $topPost = $duoshuoPlugin->topPost($post); 9 | 10 | if ($intro = get_option('duoshuo_comments_wrapper_intro')) 11 | echo $intro; 12 | ?> 13 | 14 | 15 | ID, 'duoshuo_thread_id', true); 18 | if (empty($threadId)):?> 19 |

    这篇文章的评论尚未同步到多说,点此同步

    20 | $topPost->ID, 25 | 'author-key'=> $topPost->post_author, 26 | 'title' => $topPost->post_title, 27 | 'url' => get_permalink($topPost->ID), 28 | //'order' => 'desc', 29 | //'limit' => 20, 30 | ); 31 | 32 | $attribs = ''; 33 | foreach ($data as $key => $value) 34 | $attribs .= ' data-' . $key . '="' . esc_attr($value) . '"'; 35 | ?> 36 |
    >
    37 | 38 | 42 | 46 | 2 | #ds-export{ 3 | margin-bottom:3em; 4 | } 5 | .message-complete, .ds-exported .message-start, .ds-exporting .message-start, .status{ 6 | display:none; 7 | } 8 | .ds-exported .message-complete, .message-start, .ds-exporting .status{ 9 | display:block; 10 | } 11 | 12 | 95 | -------------------------------------------------------------------------------- /duoshuo/compat-json.php: -------------------------------------------------------------------------------- 1 | 51 | * @author Matt Knapp 52 | * @author Brett Stimmerman 53 | * @copyright 2005 Michal Migurski 54 | * @version CVS: $Id: JSON.php 288200 2009-09-09 15:41:29Z alan_k $ 55 | * @license http://www.opensource.org/licenses/bsd-license.php 56 | * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 57 | */ 58 | 59 | /** 60 | * Marker constant for Services_JSON::decode(), used to flag stack state 61 | */ 62 | define('SERVICES_JSON_SLICE', 1); 63 | 64 | /** 65 | * Marker constant for Services_JSON::decode(), used to flag stack state 66 | */ 67 | define('SERVICES_JSON_IN_STR', 2); 68 | 69 | /** 70 | * Marker constant for Services_JSON::decode(), used to flag stack state 71 | */ 72 | define('SERVICES_JSON_IN_ARR', 3); 73 | 74 | /** 75 | * Marker constant for Services_JSON::decode(), used to flag stack state 76 | */ 77 | define('SERVICES_JSON_IN_OBJ', 4); 78 | 79 | /** 80 | * Marker constant for Services_JSON::decode(), used to flag stack state 81 | */ 82 | define('SERVICES_JSON_IN_CMT', 5); 83 | 84 | /** 85 | * Behavior switch for Services_JSON::decode() 86 | */ 87 | define('SERVICES_JSON_LOOSE_TYPE', 16); 88 | 89 | /** 90 | * Behavior switch for Services_JSON::decode() 91 | */ 92 | define('SERVICES_JSON_SUPPRESS_ERRORS', 32); 93 | 94 | /** 95 | * Converts to and from JSON format. 96 | * 97 | * Brief example of use: 98 | * 99 | * 100 | * // create a new instance of Services_JSON 101 | * $json = new Services_JSON(); 102 | * 103 | * // convert a complexe value to JSON notation, and send it to the browser 104 | * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); 105 | * $output = $json->encode($value); 106 | * 107 | * print($output); 108 | * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] 109 | * 110 | * // accept incoming POST data, assumed to be in JSON notation 111 | * $input = file_get_contents('php://input', 1000000); 112 | * $value = $json->decode($input); 113 | * 114 | */ 115 | class Services_JSON 116 | { 117 | /** 118 | * constructs a new JSON instance 119 | * 120 | * @param int $use object behavior flags; combine with boolean-OR 121 | * 122 | * possible values: 123 | * - SERVICES_JSON_LOOSE_TYPE: loose typing. 124 | * "{...}" syntax creates associative arrays 125 | * instead of objects in decode(). 126 | * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. 127 | * Values which can't be encoded (e.g. resources) 128 | * appear as NULL instead of throwing errors. 129 | * By default, a deeply-nested resource will 130 | * bubble up with an error, so all return values 131 | * from encode() should be checked with isError() 132 | */ 133 | function Services_JSON($use = 0) 134 | { 135 | $this->use = $use; 136 | } 137 | 138 | /** 139 | * convert a string from one UTF-16 char to one UTF-8 char 140 | * 141 | * Normally should be handled by mb_convert_encoding, but 142 | * provides a slower PHP-only method for installations 143 | * that lack the multibye string extension. 144 | * 145 | * @param string $utf16 UTF-16 character 146 | * @return string UTF-8 character 147 | * @access private 148 | */ 149 | function utf162utf8($utf16) 150 | { 151 | // oh please oh please oh please oh please oh please 152 | if(function_exists('mb_convert_encoding')) { 153 | return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); 154 | } 155 | 156 | $bytes = (ord($utf16[0]) << 8) | ord($utf16[1]); 157 | 158 | switch(true) { 159 | case ((0x7F & $bytes) == $bytes): 160 | // this case should never be reached, because we are in ASCII range 161 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 162 | return chr(0x7F & $bytes); 163 | 164 | case (0x07FF & $bytes) == $bytes: 165 | // return a 2-byte UTF-8 character 166 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 167 | return chr(0xC0 | (($bytes >> 6) & 0x1F)) 168 | . chr(0x80 | ($bytes & 0x3F)); 169 | 170 | case (0xFFFF & $bytes) == $bytes: 171 | // return a 3-byte UTF-8 character 172 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 173 | return chr(0xE0 | (($bytes >> 12) & 0x0F)) 174 | . chr(0x80 | (($bytes >> 6) & 0x3F)) 175 | . chr(0x80 | ($bytes & 0x3F)); 176 | } 177 | 178 | // ignoring UTF-32 for now, sorry 179 | return ''; 180 | } 181 | 182 | /** 183 | * convert a string from one UTF-8 char to one UTF-16 char 184 | * 185 | * Normally should be handled by mb_convert_encoding, but 186 | * provides a slower PHP-only method for installations 187 | * that lack the multibye string extension. 188 | * 189 | * @param string $utf8 UTF-8 character 190 | * @return string UTF-16 character 191 | * @access private 192 | */ 193 | function utf82utf16($utf8) 194 | { 195 | // oh please oh please oh please oh please oh please 196 | if(function_exists('mb_convert_encoding')) { 197 | return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); 198 | } 199 | 200 | switch(strlen($utf8)) { 201 | case 1: 202 | // this case should never be reached, because we are in ASCII range 203 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 204 | return $utf8; 205 | 206 | case 2: 207 | // return a UTF-16 character from a 2-byte UTF-8 char 208 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 209 | return chr(0x07 & (ord($utf8[0]) >> 2)) 210 | . chr((0xC0 & (ord($utf8[0]) << 6)) 211 | | (0x3F & ord($utf8[1]))); 212 | 213 | case 3: 214 | // return a UTF-16 character from a 3-byte UTF-8 char 215 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 216 | return chr((0xF0 & (ord($utf8[0]) << 4)) 217 | | (0x0F & (ord($utf8[1]) >> 2))) 218 | . chr((0xC0 & (ord($utf8[1]) << 6)) 219 | | (0x7F & ord($utf8[2]))); 220 | } 221 | 222 | // ignoring UTF-32 for now, sorry 223 | return ''; 224 | } 225 | 226 | /** 227 | * encodes an arbitrary variable into JSON format (and sends JSON Header) 228 | * 229 | * @param mixed $var any number, boolean, string, array, or object to be encoded. 230 | * see argument 1 to Services_JSON() above for array-parsing behavior. 231 | * if var is a strng, note that encode() always expects it 232 | * to be in ASCII or UTF-8 format! 233 | * 234 | * @return mixed JSON string representation of input var or an error if a problem occurs 235 | * @access public 236 | */ 237 | function encode($var) 238 | { 239 | header('Content-type: application/json'); 240 | return $this->_encode($var); 241 | } 242 | /** 243 | * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow CSS!!!!) 244 | * 245 | * @param mixed $var any number, boolean, string, array, or object to be encoded. 246 | * see argument 1 to Services_JSON() above for array-parsing behavior. 247 | * if var is a strng, note that encode() always expects it 248 | * to be in ASCII or UTF-8 format! 249 | * 250 | * @return mixed JSON string representation of input var or an error if a problem occurs 251 | * @access public 252 | */ 253 | function encodeUnsafe($var) 254 | { 255 | return $this->_encode($var); 256 | } 257 | /** 258 | * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format 259 | * 260 | * @param mixed $var any number, boolean, string, array, or object to be encoded. 261 | * see argument 1 to Services_JSON() above for array-parsing behavior. 262 | * if var is a strng, note that encode() always expects it 263 | * to be in ASCII or UTF-8 format! 264 | * 265 | * @return mixed JSON string representation of input var or an error if a problem occurs 266 | * @access public 267 | */ 268 | function _encode($var) 269 | { 270 | 271 | switch (gettype($var)) { 272 | case 'boolean': 273 | return $var ? 'true' : 'false'; 274 | 275 | case 'NULL': 276 | return 'null'; 277 | 278 | case 'integer': 279 | return (int) $var; 280 | 281 | case 'double': 282 | case 'float': 283 | return (float) $var; 284 | 285 | case 'string': 286 | // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT 287 | $ascii = ''; 288 | $strlen_var = strlen($var); 289 | 290 | /* 291 | * Iterate over every character in the string, 292 | * escaping with a slash or encoding to UTF-8 where necessary 293 | */ 294 | for ($c = 0; $c < $strlen_var; ++$c) { 295 | 296 | $ord_var_c = ord($var[$c]); 297 | 298 | switch (true) { 299 | case $ord_var_c == 0x08: 300 | $ascii .= '\b'; 301 | break; 302 | case $ord_var_c == 0x09: 303 | $ascii .= '\t'; 304 | break; 305 | case $ord_var_c == 0x0A: 306 | $ascii .= '\n'; 307 | break; 308 | case $ord_var_c == 0x0C: 309 | $ascii .= '\f'; 310 | break; 311 | case $ord_var_c == 0x0D: 312 | $ascii .= '\r'; 313 | break; 314 | 315 | case $ord_var_c == 0x22: 316 | case $ord_var_c == 0x2F: 317 | case $ord_var_c == 0x5C: 318 | // double quote, slash, slosh 319 | $ascii .= '\\'.$var[$c]; 320 | break; 321 | 322 | case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): 323 | // characters U-00000000 - U-0000007F (same as ASCII) 324 | $ascii .= $var[$c]; 325 | break; 326 | 327 | case (($ord_var_c & 0xE0) == 0xC0): 328 | // characters U-00000080 - U-000007FF, mask 110XXXXX 329 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 330 | if ($c+1 >= $strlen_var) { 331 | $c += 1; 332 | $ascii .= '?'; 333 | break; 334 | } 335 | 336 | $char = pack('C*', $ord_var_c, ord($var[$c + 1])); 337 | $c += 1; 338 | $utf16 = $this->utf82utf16($char); 339 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 340 | break; 341 | 342 | case (($ord_var_c & 0xF0) == 0xE0): 343 | if ($c+2 >= $strlen_var) { 344 | $c += 2; 345 | $ascii .= '?'; 346 | break; 347 | } 348 | // characters U-00000800 - U-0000FFFF, mask 1110XXXX 349 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 350 | $char = pack('C*', $ord_var_c, 351 | @ord($var[$c + 1]), 352 | @ord($var[$c + 2])); 353 | $c += 2; 354 | $utf16 = $this->utf82utf16($char); 355 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 356 | break; 357 | 358 | case (($ord_var_c & 0xF8) == 0xF0): 359 | if ($c+3 >= $strlen_var) { 360 | $c += 3; 361 | $ascii .= '?'; 362 | break; 363 | } 364 | // characters U-00010000 - U-001FFFFF, mask 11110XXX 365 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 366 | $char = pack('C*', $ord_var_c, 367 | ord($var[$c + 1]), 368 | ord($var[$c + 2]), 369 | ord($var[$c + 3])); 370 | $c += 3; 371 | $utf16 = $this->utf82utf16($char); 372 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 373 | break; 374 | 375 | case (($ord_var_c & 0xFC) == 0xF8): 376 | // characters U-00200000 - U-03FFFFFF, mask 111110XX 377 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 378 | if ($c+4 >= $strlen_var) { 379 | $c += 4; 380 | $ascii .= '?'; 381 | break; 382 | } 383 | $char = pack('C*', $ord_var_c, 384 | ord($var[$c + 1]), 385 | ord($var[$c + 2]), 386 | ord($var[$c + 3]), 387 | ord($var[$c + 4])); 388 | $c += 4; 389 | $utf16 = $this->utf82utf16($char); 390 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 391 | break; 392 | 393 | case (($ord_var_c & 0xFE) == 0xFC): 394 | if ($c+5 >= $strlen_var) { 395 | $c += 5; 396 | $ascii .= '?'; 397 | break; 398 | } 399 | // characters U-04000000 - U-7FFFFFFF, mask 1111110X 400 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 401 | $char = pack('C*', $ord_var_c, 402 | ord($var[$c + 1]), 403 | ord($var[$c + 2]), 404 | ord($var[$c + 3]), 405 | ord($var[$c + 4]), 406 | ord($var[$c + 5])); 407 | $c += 5; 408 | $utf16 = $this->utf82utf16($char); 409 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 410 | break; 411 | } 412 | } 413 | return '"'.$ascii.'"'; 414 | 415 | case 'array': 416 | /* 417 | * As per JSON spec if any array key is not an integer 418 | * we must treat the the whole array as an object. We 419 | * also try to catch a sparsely populated associative 420 | * array with numeric keys here because some JS engines 421 | * will create an array with empty indexes up to 422 | * max_index which can cause memory issues and because 423 | * the keys, which may be relevant, will be remapped 424 | * otherwise. 425 | * 426 | * As per the ECMA and JSON specification an object may 427 | * have any string as a property. Unfortunately due to 428 | * a hole in the ECMA specification if the key is a 429 | * ECMA reserved word or starts with a digit the 430 | * parameter is only accessible using ECMAScript's 431 | * bracket notation. 432 | */ 433 | 434 | // treat as a JSON object 435 | if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { 436 | $properties = array_map(array($this, 'name_value'), 437 | array_keys($var), 438 | array_values($var)); 439 | 440 | foreach($properties as $property) { 441 | if(Services_JSON::isError($property)) { 442 | return $property; 443 | } 444 | } 445 | 446 | return '{' . join(',', $properties) . '}'; 447 | } 448 | 449 | // treat it like a regular array 450 | $elements = array_map(array($this, '_encode'), $var); 451 | 452 | foreach($elements as $element) { 453 | if(Services_JSON::isError($element)) { 454 | return $element; 455 | } 456 | } 457 | 458 | return '[' . join(',', $elements) . ']'; 459 | 460 | case 'object': 461 | $vars = get_object_vars($var); 462 | 463 | $properties = array_map(array($this, 'name_value'), 464 | array_keys($vars), 465 | array_values($vars)); 466 | 467 | foreach($properties as $property) { 468 | if(Services_JSON::isError($property)) { 469 | return $property; 470 | } 471 | } 472 | 473 | return '{' . join(',', $properties) . '}'; 474 | 475 | default: 476 | return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) 477 | ? 'null' 478 | : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); 479 | } 480 | } 481 | 482 | /** 483 | * array-walking function for use in generating JSON-formatted name-value pairs 484 | * 485 | * @param string $name name of key to use 486 | * @param mixed $value reference to an array element to be encoded 487 | * 488 | * @return string JSON-formatted name-value pair, like '"name":value' 489 | * @access private 490 | */ 491 | function name_value($name, $value) 492 | { 493 | $encoded_value = $this->_encode($value); 494 | 495 | if(Services_JSON::isError($encoded_value)) { 496 | return $encoded_value; 497 | } 498 | 499 | return $this->_encode(strval($name)) . ':' . $encoded_value; 500 | } 501 | 502 | /** 503 | * reduce a string by removing leading and trailing comments and whitespace 504 | * 505 | * @param $str string string value to strip of comments and whitespace 506 | * 507 | * @return string string value stripped of comments and whitespace 508 | * @access private 509 | */ 510 | function reduce_string($str) 511 | { 512 | $str = preg_replace(array( 513 | 514 | // eliminate single line comments in '// ...' form 515 | '#^\s*//(.+)$#m', 516 | 517 | // eliminate multi-line comments in '/* ... */' form, at start of string 518 | '#^\s*/\*(.+)\*/#Us', 519 | 520 | // eliminate multi-line comments in '/* ... */' form, at end of string 521 | '#/\*(.+)\*/\s*$#Us' 522 | 523 | ), '', $str); 524 | 525 | // eliminate extraneous space 526 | return trim($str); 527 | } 528 | 529 | /** 530 | * decodes a JSON string into appropriate variable 531 | * 532 | * @param string $str JSON-formatted string 533 | * 534 | * @return mixed number, boolean, string, array, or object 535 | * corresponding to given JSON input string. 536 | * See argument 1 to Services_JSON() above for object-output behavior. 537 | * Note that decode() always returns strings 538 | * in ASCII or UTF-8 format! 539 | * @access public 540 | */ 541 | function decode($str) 542 | { 543 | $str = $this->reduce_string($str); 544 | 545 | switch (strtolower($str)) { 546 | case 'true': 547 | return true; 548 | 549 | case 'false': 550 | return false; 551 | 552 | case 'null': 553 | return null; 554 | 555 | default: 556 | $m = array(); 557 | 558 | if (is_numeric($str)) { 559 | // Lookie-loo, it's a number 560 | 561 | // This would work on its own, but I'm trying to be 562 | // good about returning integers where appropriate: 563 | // return (float)$str; 564 | 565 | // Return float or int, as appropriate 566 | return ((float)$str == (integer)$str) 567 | ? (integer)$str 568 | : (float)$str; 569 | 570 | } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { 571 | // STRINGS RETURNED IN UTF-8 FORMAT 572 | $delim = substr($str, 0, 1); 573 | $chrs = substr($str, 1, -1); 574 | $utf8 = ''; 575 | $strlen_chrs = strlen($chrs); 576 | 577 | for ($c = 0; $c < $strlen_chrs; ++$c) { 578 | 579 | $substr_chrs_c_2 = substr($chrs, $c, 2); 580 | $ord_chrs_c = ord($chrs[$c]); 581 | 582 | switch (true) { 583 | case $substr_chrs_c_2 == '\b': 584 | $utf8 .= chr(0x08); 585 | ++$c; 586 | break; 587 | case $substr_chrs_c_2 == '\t': 588 | $utf8 .= chr(0x09); 589 | ++$c; 590 | break; 591 | case $substr_chrs_c_2 == '\n': 592 | $utf8 .= chr(0x0A); 593 | ++$c; 594 | break; 595 | case $substr_chrs_c_2 == '\f': 596 | $utf8 .= chr(0x0C); 597 | ++$c; 598 | break; 599 | case $substr_chrs_c_2 == '\r': 600 | $utf8 .= chr(0x0D); 601 | ++$c; 602 | break; 603 | 604 | case $substr_chrs_c_2 == '\\"': 605 | case $substr_chrs_c_2 == '\\\'': 606 | case $substr_chrs_c_2 == '\\\\': 607 | case $substr_chrs_c_2 == '\\/': 608 | if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || 609 | ($delim == "'" && $substr_chrs_c_2 != '\\"')) { 610 | $utf8 .= $chrs[++$c]; 611 | } 612 | break; 613 | 614 | case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): 615 | // single, escaped unicode character 616 | $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) 617 | . chr(hexdec(substr($chrs, ($c + 4), 2))); 618 | $utf8 .= $this->utf162utf8($utf16); 619 | $c += 5; 620 | break; 621 | 622 | case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): 623 | $utf8 .= $chrs[$c]; 624 | break; 625 | 626 | case ($ord_chrs_c & 0xE0) == 0xC0: 627 | // characters U-00000080 - U-000007FF, mask 110XXXXX 628 | //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 629 | $utf8 .= substr($chrs, $c, 2); 630 | ++$c; 631 | break; 632 | 633 | case ($ord_chrs_c & 0xF0) == 0xE0: 634 | // characters U-00000800 - U-0000FFFF, mask 1110XXXX 635 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 636 | $utf8 .= substr($chrs, $c, 3); 637 | $c += 2; 638 | break; 639 | 640 | case ($ord_chrs_c & 0xF8) == 0xF0: 641 | // characters U-00010000 - U-001FFFFF, mask 11110XXX 642 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 643 | $utf8 .= substr($chrs, $c, 4); 644 | $c += 3; 645 | break; 646 | 647 | case ($ord_chrs_c & 0xFC) == 0xF8: 648 | // characters U-00200000 - U-03FFFFFF, mask 111110XX 649 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 650 | $utf8 .= substr($chrs, $c, 5); 651 | $c += 4; 652 | break; 653 | 654 | case ($ord_chrs_c & 0xFE) == 0xFC: 655 | // characters U-04000000 - U-7FFFFFFF, mask 1111110X 656 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 657 | $utf8 .= substr($chrs, $c, 6); 658 | $c += 5; 659 | break; 660 | 661 | } 662 | 663 | } 664 | 665 | return $utf8; 666 | 667 | } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { 668 | // array, or object notation 669 | 670 | if ($str[0] == '[') { 671 | $stk = array(SERVICES_JSON_IN_ARR); 672 | $arr = array(); 673 | } else { 674 | if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 675 | $stk = array(SERVICES_JSON_IN_OBJ); 676 | $obj = array(); 677 | } else { 678 | $stk = array(SERVICES_JSON_IN_OBJ); 679 | $obj = new stdClass(); 680 | } 681 | } 682 | 683 | array_push($stk, array('what' => SERVICES_JSON_SLICE, 684 | 'where' => 0, 685 | 'delim' => false)); 686 | 687 | $chrs = substr($str, 1, -1); 688 | $chrs = $this->reduce_string($chrs); 689 | 690 | if ($chrs == '') { 691 | if (reset($stk) == SERVICES_JSON_IN_ARR) { 692 | return $arr; 693 | 694 | } else { 695 | return $obj; 696 | 697 | } 698 | } 699 | 700 | //print("\nparsing {$chrs}\n"); 701 | 702 | $strlen_chrs = strlen($chrs); 703 | 704 | for ($c = 0; $c <= $strlen_chrs; ++$c) { 705 | 706 | $top = end($stk); 707 | $substr_chrs_c_2 = substr($chrs, $c, 2); 708 | 709 | if (($c == $strlen_chrs) || (($chrs[$c] == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { 710 | // found a comma that is not inside a string, array, etc., 711 | // OR we've reached the end of the character list 712 | $slice = substr($chrs, $top['where'], ($c - $top['where'])); 713 | array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); 714 | //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 715 | 716 | if (reset($stk) == SERVICES_JSON_IN_ARR) { 717 | // we are in an array, so just push an element onto the stack 718 | array_push($arr, $this->decode($slice)); 719 | 720 | } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { 721 | // we are in an object, so figure 722 | // out the property name and set an 723 | // element in an associative array, 724 | // for now 725 | $parts = array(); 726 | 727 | if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 728 | // "name":value pair 729 | $key = $this->decode($parts[1]); 730 | $val = $this->decode($parts[2]); 731 | 732 | if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 733 | $obj[$key] = $val; 734 | } else { 735 | $obj->$key = $val; 736 | } 737 | } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 738 | // name:value pair, where name is unquoted 739 | $key = $parts[1]; 740 | $val = $this->decode($parts[2]); 741 | 742 | if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 743 | $obj[$key] = $val; 744 | } else { 745 | $obj->$key = $val; 746 | } 747 | } 748 | 749 | } 750 | 751 | } elseif ((($chrs[$c] == '"') || ($chrs[$c] == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { 752 | // found a quote, and we are not inside a string 753 | array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c])); 754 | //print("Found start of string at {$c}\n"); 755 | 756 | } elseif (($chrs[$c] == $top['delim']) && 757 | ($top['what'] == SERVICES_JSON_IN_STR) && 758 | ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { 759 | // found a quote, we're in a string, and it's not escaped 760 | // we know that it's not escaped becase there is _not_ an 761 | // odd number of backslashes at the end of the string so far 762 | array_pop($stk); 763 | //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); 764 | 765 | } elseif (($chrs[$c] == '[') && 766 | in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 767 | // found a left-bracket, and we are in an array, object, or slice 768 | array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); 769 | //print("Found start of array at {$c}\n"); 770 | 771 | } elseif (($chrs[$c] == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { 772 | // found a right-bracket, and we're in an array 773 | array_pop($stk); 774 | //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 775 | 776 | } elseif (($chrs[$c] == '{') && 777 | in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 778 | // found a left-brace, and we are in an array, object, or slice 779 | array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); 780 | //print("Found start of object at {$c}\n"); 781 | 782 | } elseif (($chrs[$c] == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { 783 | // found a right-brace, and we're in an object 784 | array_pop($stk); 785 | //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 786 | 787 | } elseif (($substr_chrs_c_2 == '/*') && 788 | in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 789 | // found a comment start, and we are in an array, object, or slice 790 | array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); 791 | $c++; 792 | //print("Found start of comment at {$c}\n"); 793 | 794 | } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { 795 | // found a comment end, and we're in one now 796 | array_pop($stk); 797 | $c++; 798 | 799 | for ($i = $top['where']; $i <= $c; ++$i) 800 | $chrs = substr_replace($chrs, ' ', $i, 1); 801 | 802 | //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 803 | 804 | } 805 | 806 | } 807 | 808 | if (reset($stk) == SERVICES_JSON_IN_ARR) { 809 | return $arr; 810 | 811 | } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { 812 | return $obj; 813 | 814 | } 815 | 816 | } 817 | } 818 | } 819 | 820 | /** 821 | * @todo Ultimately, this should just call PEAR::isError() 822 | */ 823 | function isError($data, $code = null) 824 | { 825 | if (class_exists('pear')) { 826 | return PEAR::isError($data, $code); 827 | } elseif (is_object($data) && (get_class($data) == 'services_json_error' || 828 | is_subclass_of($data, 'services_json_error'))) { 829 | return true; 830 | } 831 | 832 | return false; 833 | } 834 | } 835 | 836 | if (class_exists('PEAR_Error')) { 837 | 838 | class Services_JSON_Error extends PEAR_Error 839 | { 840 | function Services_JSON_Error($message = 'unknown error', $code = null, 841 | $mode = null, $options = null, $userinfo = null) 842 | { 843 | parent::PEAR_Error($message, $code, $mode, $options, $userinfo); 844 | } 845 | } 846 | 847 | } else { 848 | 849 | /** 850 | * @todo Ultimately, this class shall be descended from PEAR_Error 851 | */ 852 | class Services_JSON_Error 853 | { 854 | function Services_JSON_Error($message = 'unknown error', $code = null, 855 | $mode = null, $options = null, $userinfo = null) 856 | { 857 | 858 | } 859 | } 860 | 861 | } 862 | endif; 863 | 864 | 865 | /** 866 | * 为了兼容低版本的php而增加的函数集 867 | * json从5.2.0开始支持,wordpress 2.9开始提供json函数的兼容性代码 868 | */ 869 | if ( !function_exists('json_encode') ) { 870 | function json_encode( $string ) { 871 | global $wp_json; 872 | 873 | if ( !is_a($wp_json, 'Services_JSON') ) { 874 | $wp_json = new Services_JSON(); 875 | } 876 | 877 | return $wp_json->encodeUnsafe( $string ); 878 | } 879 | } 880 | 881 | if ( !function_exists('json_decode') ) { 882 | function json_decode( $string, $assoc_array = false ) { 883 | global $wp_json; 884 | 885 | if ( !is_a($wp_json, 'Services_JSON') ) { 886 | $wp_json = new Services_JSON(); 887 | } 888 | 889 | $res = $wp_json->decode( $string ); 890 | if ( $assoc_array ) 891 | $res = _json_decode_object_helper( $res ); 892 | return $res; 893 | } 894 | function _json_decode_object_helper($data) { 895 | if ( is_object($data) ) 896 | $data = get_object_vars($data); 897 | return is_array($data) ? array_map(__FUNCTION__, $data) : $data; 898 | } 899 | } 900 | -------------------------------------------------------------------------------- /duoshuo/config.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 |

    注册站点

    4 | packageOptions() + array( 7 | 'system' => 'wordpress', 8 | 'callback' => admin_url('admin.php?page=duoshuo'), 9 | 'user_key' => $user->ID, 10 | 'user_name' => $user->display_name, 11 | 'sync_log' => 1, 12 | ); 13 | 14 | $adminUrl = is_ssl() ? 'https://' : 'http://'; 15 | $adminUrl .= self::DOMAIN . '/connect-site/?'. http_build_query($params, null, '&') 16 | ?> 17 | 18 |
    19 | -------------------------------------------------------------------------------- /duoshuo/duoshuo.php: -------------------------------------------------------------------------------- 1 |

    您的php版本低于5.0,请升级php到最新版,多说就能为您服务了。

    '; 17 | } 18 | add_action('admin_notices', 'duoshuo_php_version_warning'); 19 | } 20 | return; 21 | } 22 | 23 | if (version_compare( $wp_version, '2.8', '<' )){ 24 | if(is_admin()){ 25 | function duoshuo_wp_version_warning(){ 26 | echo '

    您的WordPress版本低于2.8,请升级WordPress到最新版,多说就能为您服务了。

    '; 27 | } 28 | add_action('admin_notices', 'duoshuo_wp_version_warning'); 29 | } 30 | return; 31 | } 32 | 33 | function duoshuo_get_available_transport(){ 34 | if (extension_loaded('curl') && function_exists('curl_init') && function_exists('curl_exec')) 35 | return 'curl'; 36 | 37 | if (function_exists('fopen') && function_exists('ini_get') && ini_get('allow_url_fopen')) 38 | return 'streams'; 39 | 40 | if (function_exists('fsockopen') && (false === ($option = get_option( 'disable_fsockopen' )) || time() - $option >= 43200)) 41 | return 'fsockopen'; 42 | 43 | return false; 44 | } 45 | 46 | $transport = duoshuo_get_available_transport(); 47 | if ($transport === false){ 48 | if(is_admin()){ 49 | function duoshuo_transport_warning(){ 50 | echo '

    没有可用的 HTTP 传输器,请联系你的主机商,安装或开启curl

    '; 51 | } 52 | add_action('admin_notices', 'duoshuo_transport_warning'); 53 | } 54 | return; 55 | } 56 | 57 | if (!extension_loaded('json')) 58 | include DUOSHUO_PLUGIN_PATH . '/compat-json.php'; 59 | 60 | require DUOSHUO_PLUGIN_PATH . '/Exception.php'; 61 | require DUOSHUO_PLUGIN_PATH . '/Client.php'; 62 | require DUOSHUO_PLUGIN_PATH . '/Abstract.php'; 63 | require DUOSHUO_PLUGIN_PATH . '/WordPress.php'; 64 | 65 | function duoshuo_admin_initialize(){ 66 | global $wp_version, $duoshuoPlugin, $plugin_page; 67 | 68 | //在admin界面内执行的action 69 | // wordpress2.8 以后都支持这个过滤器 70 | add_filter('plugin_action_links_duoshuo/duoshuo.php', array($duoshuoPlugin, 'pluginActionLinks'), 10, 2); 71 | 72 | if (empty($duoshuoPlugin->shortName) || empty($duoshuoPlugin->secret)){//你尚未安装这个插件。 73 | function duoshuo_config_warning(){ 74 | echo '

    只要再配置一下多说帐号,多说就能开始为您服务了。

    '; 75 | } 76 | 77 | if ($plugin_page !== 'duoshuo') 78 | add_action('admin_notices', 'duoshuo_config_warning'); 79 | return ; 80 | } 81 | 82 | add_action('admin_notices', array($duoshuoPlugin, 'notices')); 83 | 84 | add_action('switch_theme', array($duoshuoPlugin, 'updateSite')); 85 | // support from WP 2.9 86 | //add_action('updated_option', array($duoshuoPlugin, 'updatedOption')); 87 | 88 | add_filter('post_row_actions', array($duoshuoPlugin, 'actionsFilter')); 89 | 90 | if (function_exists('get_post_types')){// support from WP 2.9 91 | $post_types = get_post_types( array('public' => true, 'show_in_nav_menus' => true), 'objects'); 92 | 93 | foreach($post_types as $type => $object) 94 | add_meta_box('duoshuo-sidebox', '同时发布到', array($duoshuoPlugin,'syncOptions'), $type, 'side', 'high'); 95 | } 96 | else{ 97 | add_meta_box('duoshuo-sidebox', '同时发布到', array($duoshuoPlugin,'syncOptions'), 'post', 'side', 'high'); 98 | add_meta_box('duoshuo-sidebox', '同时发布到', array($duoshuoPlugin,'syncOptions'), 'page', 'side', 'high'); 99 | } 100 | //wp 3.0以下不支持此项功能 101 | /** 102 | * TODO 103 | if ($post !== null && 'publish' == $post->post_status || 'private' == $post->post_status) 104 | add_meta_box('duoshuo-comments', '来自社交网站的评论(多说)', array($duoshuoPlugin,'managePostComments'), 'post', 'normal', 'low'); 105 | */ 106 | 107 | add_action('post_comment_status_meta_box-options', array($duoshuoPlugin, 'commentStatusMetaBoxOptions')); 108 | 109 | add_action('wp_dashboard_setup', 'duoshuo_add_dashboard_widget'); 110 | 111 | //// backwards compatible (before WP 3.0) 112 | if (version_compare( $wp_version, '3.0', '<' ) && current_user_can('administrator')){ 113 | function duoshuo_wp_version_notice(){ 114 | echo '

    您的WordPress版本低于3.0,如果您能升级WordPress,多说就能更好地为您服务。

    '; 115 | } 116 | add_action(get_plugin_page_hook('duoshuo', 'duoshuo'), 'duoshuo_wp_version_notice'); 117 | add_action(get_plugin_page_hook('duoshuo-preferences', 'duoshuo'), 'duoshuo_wp_version_notice'); 118 | add_action(get_plugin_page_hook('duoshuo-settings', 'duoshuo'), 'duoshuo_wp_version_notice'); 119 | } 120 | 121 | if (!is_numeric($duoshuoPlugin->getOption('synchronized')) && current_user_can('administrator')){ 122 | function duoshuo_unsynchronized_notice(){ 123 | echo '

    上一次同步没有完成,点此继续同步

    '; 124 | } 125 | 126 | add_action(get_plugin_page_hook('duoshuo', 'duoshuo'), 'duoshuo_unsynchronized_notice'); 127 | add_action(get_plugin_page_hook('duoshuo-preferences', 'duoshuo'), 'duoshuo_unsynchronized_notice'); 128 | add_action(get_plugin_page_hook('duoshuo-settings', 'duoshuo'), 'duoshuo_unsynchronized_notice'); 129 | } 130 | 131 | add_action('admin_head-edit-comments.php', array($duoshuoPlugin, 'originalCommentsNotice')); 132 | 133 | if (defined('DOING_AJAX')){ 134 | add_action('wp_ajax_duoshuo_export', array($duoshuoPlugin, 'export')); 135 | add_action('wp_ajax_duoshuo_sync_log', array($duoshuoPlugin, 'syncLogAction')); 136 | } 137 | 138 | duoshuo_common_initialize(); 139 | } 140 | 141 | function duoshuo_initialize(){ 142 | global $duoshuoPlugin; 143 | 144 | if (empty($duoshuoPlugin->shortName) || empty($duoshuoPlugin->secret)){ 145 | return; 146 | } 147 | 148 | if ($duoshuoPlugin->getOption('social_login_enabled')){ 149 | add_action('login_form', array($duoshuoPlugin, 'loginForm')); 150 | add_action('register_form', array($duoshuoPlugin, 'loginForm')); 151 | } 152 | 153 | // wp2.8 以后支持这个事件 154 | add_action(get_option('duoshuo_postpone_print_scripts') ? 'wp_print_footer_scripts' : 'wp_print_scripts', array($duoshuoPlugin, 'appendScripts')); 155 | 156 | //以下应该根据是否设置,选择是否启用 157 | add_filter('comments_template', array($duoshuoPlugin,'commentsTemplate')); 158 | 159 | if (get_option('duoshuo_cc_fix')){ //直接输出HTML评论 160 | add_filter('comments_popup_link_attributes', array($duoshuoPlugin, 'commentsPopupLinkAttributes')); 161 | add_filter('comments_number', array($duoshuoPlugin, 'commentsText')); 162 | } 163 | 164 | if (get_option('duoshuo_sync_pingback_and_trackback')){ 165 | add_action('trackback_post', array($duoshuoPlugin, 'exportOneComment')); 166 | add_action('pingback_post', array($duoshuoPlugin, 'exportOneComment')); 167 | } 168 | 169 | duoshuo_common_initialize(); 170 | } 171 | 172 | function duoshuo_common_initialize(){ 173 | global $duoshuoPlugin; 174 | // 没有用cookie方式保持身份,所以不需要重定向 175 | //add_action('wp_logout', array($duoshuoPlugin, 'logout')); 176 | add_filter('comments_open', array($duoshuoPlugin, 'commentsOpen'), 10, 2); 177 | add_action('set_auth_cookie', array($duoshuoPlugin, 'setJwtCookie'), 10, 5); 178 | add_action('clear_auth_cookie', array($duoshuoPlugin, 'clearJwtCookie')); 179 | 180 | add_action('profile_update', array($duoshuoPlugin, 'syncUserToRemote')); 181 | add_action('user_register', array($duoshuoPlugin, 'userRegisterHook')); 182 | add_action('wp_login', array($duoshuoPlugin, 'bindUser'), 10, 2); 183 | 184 | if ($duoshuoPlugin->getOption('cron_sync_enabled')){ 185 | add_action('duoshuo_sync_log_cron', array($duoshuoPlugin, 'syncLog')); 186 | if (!wp_next_scheduled('duoshuo_sync_log_cron')){ 187 | wp_schedule_event(time(), 'hourly', 'duoshuo_sync_log_cron'); 188 | } 189 | } 190 | } 191 | 192 | // Register widgets. 193 | function duoshuo_register_widgets(){ 194 | require_once dirname(__FILE__) . '/widgets.php'; 195 | 196 | register_widget('Duoshuo_Widget_Recent_Visitors'); 197 | //register_widget('Duoshuo_Widget_Top_Commenters'); 198 | 199 | register_widget('Duoshuo_Widget_Recent_Comments'); 200 | register_widget('Duoshuo_Widget_Top_Threads'); 201 | 202 | register_widget('Duoshuo_Widget_Qqt_Follow'); 203 | } 204 | 205 | function duoshuo_add_pages() { 206 | global $duoshuoPlugin; 207 | 208 | if (empty($duoshuoPlugin->shortName) || empty($duoshuoPlugin->secret)){ // 尚未安装 209 | add_object_page( 210 | '安装', 211 | '多说评论', 212 | 'moderate_comments', // 权限 213 | 'duoshuo', 214 | array($duoshuoPlugin, 'config'), 215 | $duoshuoPlugin->pluginDirUrl . 'images/menu-icon.png' 216 | ); 217 | } 218 | else{ // 已经安装成功 219 | if (current_user_can('moderate_comments')){ 220 | if(get_option('duoshuo_synchronized') === false){ 221 | add_object_page( 222 | '数据同步', 223 | '多说评论', 224 | 'moderate_comments', 225 | 'duoshuo', 226 | array($duoshuoPlugin, 'sync'), 227 | $duoshuoPlugin->pluginDirUrl . 'images/menu-icon.png' 228 | ); 229 | add_submenu_page( 230 | 'duoshuo', 231 | '多说评论管理', 232 | '评论管理', 233 | 'moderate_comments', 234 | 'duoshuo-manage', 235 | array($duoshuoPlugin,'manage') 236 | ); 237 | } 238 | else{ 239 | add_object_page( 240 | '多说评论管理', 241 | '多说评论', 242 | 'moderate_comments', 243 | 'duoshuo', 244 | array($duoshuoPlugin,'manage'), 245 | $duoshuoPlugin->pluginDirUrl . 'images/menu-icon.png' 246 | ); 247 | } 248 | add_submenu_page( 249 | 'duoshuo',//$parent_slug 250 | '个性化设置',//page_title 251 | '个性化设置',//menu_title 252 | 'manage_options',//权限 253 | 'duoshuo-preferences',//menu_slug 254 | array($duoshuoPlugin, 'preferences')//function 255 | ); 256 | add_submenu_page( 257 | 'duoshuo',//$parent_slug 258 | '主题设置',//page_title 259 | '主题设置',//menu_title 260 | 'manage_options',//权限 261 | 'duoshuo-themes',//menu_slug 262 | array($duoshuoPlugin, 'themes')//function 263 | ); 264 | add_submenu_page( 265 | 'duoshuo',//$parent_slug 266 | '高级选项',//page_title 267 | '高级选项',//menu_title 268 | 'manage_options',//权限 269 | 'duoshuo-settings',//menu_slug 270 | array($duoshuoPlugin, 'settings')//function 271 | ); 272 | add_submenu_page( 273 | 'duoshuo',//$parent_slug 274 | '数据统计',//page_title 275 | '数据统计',//menu_title 276 | 'manage_options',//权限 277 | 'duoshuo-statistics',//menu_slug 278 | array($duoshuoPlugin, 'statistics')//function 279 | ); 280 | add_submenu_page( 281 | 'duoshuo',//$parent_slug 282 | '我的多说帐号',//page_title 283 | '我的多说帐号',//menu_title 284 | 'level_0',//权限 285 | 'duoshuo-profile',//menu_slug 286 | array($duoshuoPlugin, 'profile')//function 287 | ); 288 | } 289 | elseif(current_user_can('level_0')){ 290 | add_submenu_page( 291 | 'profile.php',//$parent_slug 292 | '我的多说帐号',//page_title 293 | '我的多说帐号',//menu_title 294 | 'level_0',//权限 295 | 'duoshuo-profile',//menu_slug 296 | array($duoshuoPlugin, 'profile')//function 297 | ); 298 | } 299 | } 300 | } 301 | 302 | function duoshuo_add_dashboard_widget(){ 303 | global $duoshuoPlugin; 304 | 305 | wp_add_dashboard_widget('dashboard_duoshuo', '多说最新评论', array($duoshuoPlugin, 'dashboardWidget'), array($duoshuoPlugin, 'dashboardWidgetControl')); 306 | } 307 | 308 | function duoshuo_request_handler(){ 309 | global $duoshuoPlugin, $parent_file; 310 | 311 | if ($_SERVER['REQUEST_METHOD'] == 'POST'){ 312 | switch ($parent_file){ 313 | case 'duoshuo': 314 | if (isset($_POST['duoshuo_reset'])) 315 | $duoshuoPlugin->reset(); 316 | if (isset($_POST['duoshuo_local_options'])) 317 | $duoshuoPlugin->updateLocalOptions(); 318 | break; 319 | default: 320 | } 321 | } 322 | elseif ($_SERVER['REQUEST_METHOD'] == 'GET'){ 323 | switch ($parent_file){ 324 | case 'options-general.php': 325 | if (isset($_GET['settings-updated'])) 326 | $duoshuoPlugin->updateSite(); 327 | break; 328 | case 'duoshuo': 329 | if (isset($_GET['duoshuo_connect_site'])) 330 | $duoshuoPlugin->connectSite(); 331 | if (isset($_GET['duoshuo_theme'])){ 332 | update_option('duoshuo_theme', $_GET['duoshuo_theme']); 333 | } 334 | break; 335 | default: 336 | } 337 | } 338 | } 339 | 340 | function duoshuo_deactivate($network_wide = false){ 341 | // 升级插件的时候也会停用插件 342 | //delete_option('duoshuo_synchronized'); 343 | } 344 | 345 | 346 | $duoshuoPlugin = Duoshuo_WordPress::getInstance(); 347 | 348 | if(is_admin()){//在admin界面内执行的action 349 | register_deactivation_hook(__FILE__, 'duoshuo_deactivate'); 350 | add_action('admin_menu', 'duoshuo_add_pages', 10); 351 | add_action('admin_init', 'duoshuo_request_handler'); 352 | add_action('admin_init', array($duoshuoPlugin, 'registerSettings')); 353 | add_action('admin_init', 'duoshuo_admin_initialize'); 354 | } 355 | else{ 356 | add_action('init', 'duoshuo_initialize'); 357 | add_action('login_form_duoshuo_login', array($duoshuoPlugin, 'oauthConnect')); 358 | //add_action('login_form_duoshuo_logout', array($duoshuoPlugin,'oauthDisconnect')); 359 | } 360 | 361 | add_action('widgets_init', 'duoshuo_register_widgets'); 362 | 363 | add_action('save_post', array($duoshuoPlugin, 'savePostDuoshuoStatus')); 364 | add_action('save_post', array($duoshuoPlugin, 'syncPostToRemote'), 10, 2); 365 | 366 | /* 367 | if (function_exists('get_post_types')){ // cron jobs runs in common mode, sometimes 368 | foreach(get_post_types() as $type) 369 | if ($type !== 'nav_menu_item' && $type !== 'revision') 370 | add_action('publish_' . $type, array($duoshuoPlugin,'syncPostToRemote')); 371 | } 372 | else{ 373 | add_action('publish_post', array($duoshuoPlugin,'syncPostToRemote')); 374 | add_action('publish_page', array($duoshuoPlugin,'syncPostToRemote')); 375 | } 376 | */ 377 | -------------------------------------------------------------------------------- /duoshuo/images/head-icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duoshuo/duoshuo-wordpress/c7aa8e44b87fffc483d3ed62046a3f4bd8ca41cb/duoshuo/images/head-icon.gif -------------------------------------------------------------------------------- /duoshuo/images/icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duoshuo/duoshuo-wordpress/c7aa8e44b87fffc483d3ed62046a3f4bd8ca41cb/duoshuo/images/icon.gif -------------------------------------------------------------------------------- /duoshuo/images/menu-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duoshuo/duoshuo-wordpress/c7aa8e44b87fffc483d3ed62046a3f4bd8ca41cb/duoshuo/images/menu-icon.png -------------------------------------------------------------------------------- /duoshuo/images/service_icons_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duoshuo/duoshuo-wordpress/c7aa8e44b87fffc483d3ed62046a3f4bd8ca41cb/duoshuo/images/service_icons_32x32.png -------------------------------------------------------------------------------- /duoshuo/images/waiting.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duoshuo/duoshuo-wordpress/c7aa8e44b87fffc483d3ed62046a3f4bd8ca41cb/duoshuo/images/waiting.gif -------------------------------------------------------------------------------- /duoshuo/index.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /duoshuo/manage.php: -------------------------------------------------------------------------------- 1 | 2 | $this->jwt(), 5 | ); 6 | 7 | $adminUrl= is_ssl() ? 'https://' : 'http://'; 8 | $adminUrl .= $this->shortName . '.' . self::DOMAIN.'/admin/?' . http_build_query($params, null, '&'); 9 | 10 | ?> 11 |
    12 | 13 |

    多说评论管理 14 | 在新窗口中打开 15 |

    16 | 17 |
    18 | 19 | 30 | -------------------------------------------------------------------------------- /duoshuo/nanoSha2.php: -------------------------------------------------------------------------------- 1 | . 26 | * 27 | * Include: 28 | * 29 | * require_once("[path/]sha256.inc.php"); 30 | * 31 | * Usage Options: 32 | * 33 | * 1) $shaStr = hash('sha256', $string_to_hash); 34 | * 35 | * 2) $shaStr = sha256($string_to_hash[, bool ignore_php5_hash = false]); 36 | * 37 | * 3) $obj = new nanoSha2([bool $upper_case_output = false]); 38 | * $shaStr = $obj->hash($string_to_hash[, bool $ignore_php5_hash = false]); 39 | * 40 | * Reference: http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html 41 | * 42 | * 2007-12-13: Cleaned up for initial public release 43 | * 2008-05-10: Moved all helper functions into a class. API access unchanged. 44 | * 2009-06-23: Created abstraction of hash() routine 45 | * 2009-07-23: Added detection of 32 vs 64bit platform, and patches. 46 | * Ability to define "_NANO_SHA2_UPPER" to yeild upper case hashes. 47 | * 2009-08-01: Added ability to attempt to use mhash() prior to running pure 48 | * php code. 49 | * 50 | * NOTE: Some sporadic versions of PHP do not handle integer overflows the 51 | * same as the majority of builds. If you get hash results of: 52 | * 7fffffff7fffffff7fffffff7fffffff7fffffff7fffffff7fffffff7fffffff 53 | * 54 | * If you do not have permissions to change PHP versions (if you did 55 | * you'd probably upgrade to PHP 5 anyway) it is advised you install a 56 | * module that will allow you to use their hashing routines, examples are: 57 | * - mhash module : http://ca3.php.net/mhash 58 | * - Suhosin : http://www.hardened-php.net/suhosin/ 59 | * 60 | * If you install the Suhosin module, this script will transparently 61 | * use their routine and define the PHP routine as _nano_sha256(). 62 | * 63 | * If the mhash module is present, and $ignore_php5_hash = false the 64 | * script will attempt to use the output from mhash prior to running 65 | * the PHP code. 66 | */ 67 | class nanoSha2 68 | { 69 | // php 4 - 5 compatable class properties 70 | var $toUpper; 71 | var $platform; 72 | 73 | // Php 4 - 6 compatable constructor 74 | function nanoSha2($toUpper = false) { 75 | // Determine if the caller wants upper case or not. 76 | $this->toUpper = is_bool($toUpper) 77 | ? $toUpper 78 | : ((defined('_NANO_SHA2_UPPER')) ? true : false); 79 | 80 | // Deteremine if the system is 32 or 64 bit. 81 | $tmpInt = (int)4294967295; 82 | $this->platform = ($tmpInt > 0) ? 64 : 32; 83 | } 84 | 85 | // Do the SHA-256 Padding routine (make input a multiple of 512 bits) 86 | function char_pad($str) 87 | { 88 | $tmpStr = $str; 89 | 90 | $l = strlen($tmpStr)*8; // # of bits from input string 91 | 92 | $tmpStr .= "\x80"; // append the "1" bit followed by 7 0's 93 | 94 | $k = (512 - (($l + 8 + 64) % 512)) / 8; // # of 0 bytes to append 95 | $k += 4; // PHP Strings will never exceed (2^31)-1, 1st 32bits of 96 | // the 64-bit value representing $l can be all 0's 97 | 98 | for ($x = 0; $x < $k; $x++) { 99 | $tmpStr .= "\0"; 100 | } 101 | 102 | // append the 32-bits representing # of bits from input string ($l) 103 | $tmpStr .= chr((($l>>24) & 0xFF)); 104 | $tmpStr .= chr((($l>>16) & 0xFF)); 105 | $tmpStr .= chr((($l>>8) & 0xFF)); 106 | $tmpStr .= chr(($l & 0xFF)); 107 | 108 | return $tmpStr; 109 | } 110 | 111 | // Here are the bitwise and functions as defined in FIPS180-2 Standard 112 | function addmod2n($x, $y, $n = 4294967296) // Z = (X + Y) mod 2^32 113 | { 114 | $mask = 0x80000000; 115 | 116 | if ($x < 0) { 117 | $x &= 0x7FFFFFFF; 118 | $x = (float)$x + $mask; 119 | } 120 | 121 | if ($y < 0) { 122 | $y &= 0x7FFFFFFF; 123 | $y = (float)$y + $mask; 124 | } 125 | 126 | $r = $x + $y; 127 | 128 | if ($r >= $n) { 129 | while ($r >= $n) { 130 | $r -= $n; 131 | } 132 | } 133 | 134 | return (int)$r; 135 | } 136 | 137 | // Logical bitwise right shift (PHP default is arithmetic shift) 138 | function SHR($x, $n) // x >> n 139 | { 140 | if ($n >= 32) { // impose some limits to keep it 32-bit 141 | return (int)0; 142 | } 143 | 144 | if ($n <= 0) { 145 | return (int)$x; 146 | } 147 | 148 | $mask = 0x40000000; 149 | 150 | if ($x < 0) { 151 | $x &= 0x7FFFFFFF; 152 | $mask = $mask >> ($n-1); 153 | return ($x >> $n) | $mask; 154 | } 155 | 156 | return (int)$x >> (int)$n; 157 | } 158 | 159 | function ROTR($x, $n) { return (int)(($this->SHR($x, $n) | ($x << (32-$n)) & 0xFFFFFFFF)); } 160 | function Ch($x, $y, $z) { return ($x & $y) ^ ((~$x) & $z); } 161 | function Maj($x, $y, $z) { return ($x & $y) ^ ($x & $z) ^ ($y & $z); } 162 | function Sigma0($x) { return (int) ($this->ROTR($x, 2)^$this->ROTR($x, 13)^$this->ROTR($x, 22)); } 163 | function Sigma1($x) { return (int) ($this->ROTR($x, 6)^$this->ROTR($x, 11)^$this->ROTR($x, 25)); } 164 | function sigma_0($x) { return (int) ($this->ROTR($x, 7)^$this->ROTR($x, 18)^$this->SHR($x, 3)); } 165 | function sigma_1($x) { return (int) ($this->ROTR($x, 17)^$this->ROTR($x, 19)^$this->SHR($x, 10)); } 166 | 167 | /* 168 | * Custom functions to provide PHP support 169 | */ 170 | // split a byte-string into integer array values 171 | function int_split($input) 172 | { 173 | $l = strlen($input); 174 | 175 | if ($l <= 0) { 176 | return (int)0; 177 | } 178 | 179 | if (($l % 4) != 0) { // invalid input 180 | return false; 181 | } 182 | 183 | for ($i = 0; $i < $l; $i += 4) 184 | { 185 | $int_build = (ord($input[$i]) << 24); 186 | $int_build += (ord($input[$i+1]) << 16); 187 | $int_build += (ord($input[$i+2]) << 8); 188 | $int_build += (ord($input[$i+3])); 189 | 190 | $result[] = $int_build; 191 | } 192 | 193 | return $result; 194 | } 195 | 196 | /** 197 | * Process and return the hash. 198 | * 199 | * @param $str Input string to hash 200 | * @param $ig_func Option param to ignore checking for php > 5.1.2 201 | * @return string Hexadecimal representation of the message digest 202 | */ 203 | function hash($str, $ig_func = false) 204 | { 205 | unset($binStr); // binary representation of input string 206 | unset($hexStr); // 256-bit message digest in readable hex format 207 | 208 | // check for php's internal sha256 function, ignore if ig_func==true 209 | if ($ig_func == false) { 210 | if (version_compare(PHP_VERSION,'5.1.2','>=')) { 211 | return hash("sha256", $str, false); 212 | } else if (function_exists('mhash') && defined('MHASH_SHA256')) { 213 | return base64_encode(bin2hex(mhash(MHASH_SHA256, $str))); 214 | } 215 | } 216 | 217 | /* 218 | * SHA-256 Constants 219 | * Sequence of sixty-four constant 32-bit words representing the 220 | * first thirty-two bits of the fractional parts of the cube roots 221 | * of the first sixtyfour prime numbers. 222 | */ 223 | $K = array((int)0x428a2f98, (int)0x71374491, (int)0xb5c0fbcf, 224 | (int)0xe9b5dba5, (int)0x3956c25b, (int)0x59f111f1, 225 | (int)0x923f82a4, (int)0xab1c5ed5, (int)0xd807aa98, 226 | (int)0x12835b01, (int)0x243185be, (int)0x550c7dc3, 227 | (int)0x72be5d74, (int)0x80deb1fe, (int)0x9bdc06a7, 228 | (int)0xc19bf174, (int)0xe49b69c1, (int)0xefbe4786, 229 | (int)0x0fc19dc6, (int)0x240ca1cc, (int)0x2de92c6f, 230 | (int)0x4a7484aa, (int)0x5cb0a9dc, (int)0x76f988da, 231 | (int)0x983e5152, (int)0xa831c66d, (int)0xb00327c8, 232 | (int)0xbf597fc7, (int)0xc6e00bf3, (int)0xd5a79147, 233 | (int)0x06ca6351, (int)0x14292967, (int)0x27b70a85, 234 | (int)0x2e1b2138, (int)0x4d2c6dfc, (int)0x53380d13, 235 | (int)0x650a7354, (int)0x766a0abb, (int)0x81c2c92e, 236 | (int)0x92722c85, (int)0xa2bfe8a1, (int)0xa81a664b, 237 | (int)0xc24b8b70, (int)0xc76c51a3, (int)0xd192e819, 238 | (int)0xd6990624, (int)0xf40e3585, (int)0x106aa070, 239 | (int)0x19a4c116, (int)0x1e376c08, (int)0x2748774c, 240 | (int)0x34b0bcb5, (int)0x391c0cb3, (int)0x4ed8aa4a, 241 | (int)0x5b9cca4f, (int)0x682e6ff3, (int)0x748f82ee, 242 | (int)0x78a5636f, (int)0x84c87814, (int)0x8cc70208, 243 | (int)0x90befffa, (int)0xa4506ceb, (int)0xbef9a3f7, 244 | (int)0xc67178f2); 245 | 246 | // Pre-processing: Padding the string 247 | $binStr = $this->char_pad($str); 248 | 249 | // Parsing the Padded Message (Break into N 512-bit blocks) 250 | $M = str_split($binStr, 64); 251 | 252 | // Set the initial hash values 253 | $h[0] = (int)0x6a09e667; 254 | $h[1] = (int)0xbb67ae85; 255 | $h[2] = (int)0x3c6ef372; 256 | $h[3] = (int)0xa54ff53a; 257 | $h[4] = (int)0x510e527f; 258 | $h[5] = (int)0x9b05688c; 259 | $h[6] = (int)0x1f83d9ab; 260 | $h[7] = (int)0x5be0cd19; 261 | 262 | // loop through message blocks and compute hash. ( For i=1 to N : ) 263 | $N = count($M); 264 | for ($i = 0; $i < $N; $i++) 265 | { 266 | // Break input block into 16 32bit words (message schedule prep) 267 | $MI = $this->int_split($M[$i]); 268 | 269 | // Initialize working variables 270 | $_a = (int)$h[0]; 271 | $_b = (int)$h[1]; 272 | $_c = (int)$h[2]; 273 | $_d = (int)$h[3]; 274 | $_e = (int)$h[4]; 275 | $_f = (int)$h[5]; 276 | $_g = (int)$h[6]; 277 | $_h = (int)$h[7]; 278 | unset($_s0); 279 | unset($_s1); 280 | unset($_T1); 281 | unset($_T2); 282 | $W = array(); 283 | 284 | // Compute the hash and update 285 | for ($t = 0; $t < 16; $t++) 286 | { 287 | // Prepare the first 16 message schedule values as we loop 288 | $W[$t] = $MI[$t]; 289 | 290 | // Compute hash 291 | $_T1 = $this->addmod2n($this->addmod2n($this->addmod2n($this->addmod2n($_h, $this->Sigma1($_e)), $this->Ch($_e, $_f, $_g)), $K[$t]), $W[$t]); 292 | $_T2 = $this->addmod2n($this->Sigma0($_a), $this->Maj($_a, $_b, $_c)); 293 | 294 | // Update working variables 295 | $_h = $_g; $_g = $_f; $_f = $_e; $_e = $this->addmod2n($_d, $_T1); 296 | $_d = $_c; $_c = $_b; $_b = $_a; $_a = $this->addmod2n($_T1, $_T2); 297 | } 298 | 299 | for (; $t < 64; $t++) 300 | { 301 | // Continue building the message schedule as we loop 302 | $_s0 = $W[($t+1)&0x0F]; 303 | $_s0 = $this->sigma_0($_s0); 304 | $_s1 = $W[($t+14)&0x0F]; 305 | $_s1 = $this->sigma_1($_s1); 306 | 307 | $W[$t&0xF] = $this->addmod2n($this->addmod2n($this->addmod2n($W[$t&0xF], $_s0), $_s1), $W[($t+9)&0x0F]); 308 | 309 | // Compute hash 310 | $_T1 = $this->addmod2n($this->addmod2n($this->addmod2n($this->addmod2n($_h, $this->Sigma1($_e)), $this->Ch($_e, $_f, $_g)), $K[$t]), $W[$t&0xF]); 311 | $_T2 = $this->addmod2n($this->Sigma0($_a), $this->Maj($_a, $_b, $_c)); 312 | 313 | // Update working variables 314 | $_h = $_g; $_g = $_f; $_f = $_e; $_e = $this->addmod2n($_d, $_T1); 315 | $_d = $_c; $_c = $_b; $_b = $_a; $_a = $this->addmod2n($_T1, $_T2); 316 | } 317 | 318 | $h[0] = $this->addmod2n($h[0], $_a); 319 | $h[1] = $this->addmod2n($h[1], $_b); 320 | $h[2] = $this->addmod2n($h[2], $_c); 321 | $h[3] = $this->addmod2n($h[3], $_d); 322 | $h[4] = $this->addmod2n($h[4], $_e); 323 | $h[5] = $this->addmod2n($h[5], $_f); 324 | $h[6] = $this->addmod2n($h[6], $_g); 325 | $h[7] = $this->addmod2n($h[7], $_h); 326 | } 327 | 328 | // Convert the 32-bit words into human readable hexadecimal format. 329 | $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]); 330 | 331 | return ($this->toUpper) ? strtoupper($hexStr) : $hexStr; 332 | } 333 | 334 | } 335 | -------------------------------------------------------------------------------- /duoshuo/oauth-proxy.php: -------------------------------------------------------------------------------- 1 | pluginDirUrl; ?>styles.css" type="text/css" /> 2 | $this->jwt(), 5 | ); 6 | 7 | $adminUrl = is_ssl() ? 'https://' : 'http://'; 8 | $adminUrl .= $this->shortName . '.' . self::DOMAIN . '/admin/settings/?' . http_build_query($params, null, '&'); 9 | 10 | ?> 11 |
    12 | 13 |

    多说评论框设置 14 | 在新窗口中打开

    15 | 16 |
    17 | 18 | 29 | -------------------------------------------------------------------------------- /duoshuo/profile.php: -------------------------------------------------------------------------------- 1 | $this->jwt(), 4 | ); 5 | $settingsUrl = is_ssl()? 'https://' : 'http://'; 6 | $settingsUrl .= self::DOMAIN . '/settings/?' . http_build_query($params, null, '&'); 7 | ?> 8 | 9 | 10 |
    11 | 12 |

    我的多说帐号 13 | 在新窗口中打开 14 |

    15 | 16 |
    17 | -------------------------------------------------------------------------------- /duoshuo/readme.txt: -------------------------------------------------------------------------------- 1 | === 多说社会化评论框 === 2 | Contributors: shen2 3 | Donate link: http://duoshuo.com/ 4 | Tags: comments, social, share, spam, weibo, qzone, youyan, pinglunla, widget, 评论, 社会化, 分享, 微博, QQ, 腾讯, 新浪微博, 垃圾评论 5 | Requires at least: 2.8 6 | Tested up to: 3.6 7 | Stable tag: 1.0 8 | 9 | 追求最佳用户体验的社会化评论框,为中小网站提供新浪微博、QQ、人人、开心、豆瓣等多帐号登录并评论功能。 10 | 11 | == Description == 12 | 13 | 追求最佳用户体验的社会化评论框,为中小网站提供新浪微博、QQ(腾讯微博和QQ空间)、人人、开心、豆瓣、网易微博、搜狐微博、百度等多帐号登录并评论功能,显示网页相关的新浪微博和腾讯微博。 14 | “多说”帮你搭建更活跃,互动性更强的评论平台,提高用户黏性和流量。它还有众多实用特性,功能强大且永久免费。 15 | 官方网站:[duoshuo.com](http://duoshuo.com/ "多说网") 16 | 17 | = 特色 = 18 | 19 | 1. 多账号登录:可用新浪微博、QQ(腾讯微博和QQ空间)、人人、开心、豆瓣、网易微博、搜狐微博、百度账号登录发表评论,不必输入邮箱 20 | 1. 单点登录功能支持:已经登陆WordPress的用户,评论框身份和WordPress身份保持一致 21 | 1. 轻松安装:安装WordPress插件或插入一段代码,安装从未如此简单 22 | 1. 优质的速度和稳定性:保证300毫秒或更短加载时间,99.9%正常服务时间 23 | 1. 与社交网站紧密结合:评论同时能分享到各大社交网站,评论框中能自动显示网页相关新浪微博和腾讯微博 24 | 1. 数据实时本地保存:评论内容实时保存到您的WordPress本地服务器,并可将多说评论数据导出,数据永远归你所有 25 | 1. 手机界面深度优化适配:自动识别访问者使用的移动设备,自动适应各种宽度的手机浏览器 26 | 1. 主题样式随意切换:CSS代码全面开源,你可以深度定制属于自己的主题样式 27 | 28 | = 评论框原来可以如此精彩 = 29 | * 多账号登录:让网站的用户轻松加入你的社区参与讨论,支持新浪微博(Sina Weibo)、QQ(Tencent Weibo和Qzone)、人人(Renren)、豆瓣(Douban)、开心网(Kaixin001)、网易微博(Netease)、搜狐微博(Sohu)、百度(Baidu)账号和邮箱登录,更多登录方式陆续添加中。 30 | * 回复提醒:如果评论收到回复,评论者在任何安装了多说系统的网页都可以收到提示,回访你的网站 31 | * 评论标记喜欢:鼓励用户留下言之有物的评论 32 | * 评论界面自定制:自定义CSS,评论框位置,评论排列顺序,多级回复…多说的自定制选项让评论框自然融入你的网站 33 | 34 | = 高效强大的管理后台服务帮你过滤垃圾评论 = 35 | 36 | * 数据本地保存:评论数据实时保存到WordPress本地服务器,并可随时将多说评论导出,不用担心数据丢失 37 | * 智能识别:利用Akismat和其他安装了多说评论系统网站的数据库,智能识别垃圾评论 38 | * 易用的管理后台:一站式后台帮你轻松、高效的处理海量评论。你可以在后台方便的将评论进行分类和删除,只有正常评论才会显示在网站的页面上。 39 | * 多账号管理:多个管理员可登陆管理同一个网站的评论,并可设定管理员、编辑等不同管理权限 40 | * 优质的速度和稳定性:300毫秒或更短加载时间,99.9%正常服务时间,加载评论时访问多说服务器,降低网站自身服务器压力 41 | * 特别为评论内容设计的SEO优化 42 | 43 | = 让多说成为纽带,将你的网站与外界相连 = 44 | 45 | * 分享评论或文章:只需简单勾选,评论或文章即可分享到各大社交网站。带有评论的分享会让评论者的好友更有兴趣访问你的网站 46 | * 发文章同步到各大社交网站:WordPress网站可以在发布文章的同时将文章同步到各大社交网站,轻松更新多个微博和博客 47 | * 微博评论同步:无需设置,自动显示带有文章url的新浪微博和腾讯微博,及其评论和转发 48 | * 跨网站登录:访客在别的网站上登录多说后,再访问你的网站时,可以不用再登录,直接评论 49 | 50 | = 细节特色 = 51 | 52 | * 显示文章相关新浪和腾讯微博 53 | * 回复提醒:页面浮框提醒和邮件提醒 54 | * 最新评论挂件 55 | * 采用Akismet过滤垃圾评论 56 | * 插入表情 57 | * 喜欢文章并分享到社交网络功能 58 | * 支持HTML解析 59 | * 支持Gravatar头像 60 | * 自定义官方微博账号 61 | * 写文章同步到微博时第一张图片作为微博配图 62 | * 支持自定义CSS追加修改多说样式 63 | * 支持首页最新评论列表更新 64 | * 支持文章标题旁边的评论计数更新 65 | * 启用多说时拒绝垃圾广告机器人利用WP接口发布评论 66 | * 定时发微博功能 67 | 68 | = 很容易就能找到我们 = 69 | 70 | > 官方网站:[duoshuo.com](http://duoshuo.com/ "多说网") 71 | > 新浪微博:[@多说网](http://weibo.com/duoshuo) 72 | > 腾讯微博:[多说网](http://t.qq.com/duo-shuo) 73 | > 人人主页:[多说网](http://page.renren.com/699168408) 74 | > 豆瓣小站:[多说网](http://site.douban.com/duoshuo) 75 | > 网易微博:[@多说网](http://t.163.com/duoshuo) 76 | > 搜狐微博:[@多说网](http://duoshuo.t.sohu.com/) 77 | > 电话:010-82827537 78 | > QQ:1175762238 79 | > QQ:2310391001 80 | 81 | = 同类产品 = 82 | * Disqus 83 | * IntenseDebate 84 | * livefyre 85 | * uyan (youyan-social-comment-system) 86 | * pinglunla 87 | * denglu 88 | * wp-connect 89 | * Social Medias Connect 90 | 91 | == Installation == 92 | 93 | 1. 在WordPress 插件库中搜索"duoshuo", 下载并启用 94 | 2. 在WordPress “设置 -> 多说”中,绑定社交账号,设置二级域名并一键注册 95 | 3. 同步评论到多说,并进行其他设置 96 | 97 | == Frequently Asked Questions == 98 | 99 | 1. 我的网站想使用多说,请问对网站系统有什么基本要求吗? 100 | 101 | 对于WordPress网站,建议WordPress版本在3.0或以上,这样能完整的实现所有功能。当然,如果版本在WordPress 3.0以下,多说也会尽力支持。多说也支持WordPress Mu,不同博客可以分别启用。 102 | 建议Php程序5.0以上,推荐5.3.0以上,安装curl扩展。 103 | 我们鼓励大家抛弃老掉牙的IE6,所以多说管理页面不支持IE6,强烈建议各位网站主大人使用IE 8,9,Chrome或Firefox浏览器。 104 | 105 | 1. 网站的评论数相当多,同步到多说的过程中断了怎么办? 106 | 107 | 在客户服务中,我们发现部分香港和美国主机的用户,以及数据量比较大的用户,在同步中,出现中断的情况。多说WordPress插件支持同步过程中的断点续传,如果您发现同步数据不动了,或者中断报错,一般情况下再刷新一次页面,重新开始同步过程就好。我们保证数据不会出现重复和冗余。如果多次出现这种情况,请和多说的管理员联系(QQ 2310391001,新浪微博:@多说网)。 108 | 109 | 对于已有大量文章和评论需要同步的网站,建议您在安装前与我们联系,以便我们为您提供更好更及时地支持。 110 | 111 | 1. 发文章时同时发布微博,这里面有什么玄机? 112 | 113 | 发文章时,如果文章写了摘要,那么发出来的微博是“文章摘要+文章链接”的格式。(如果您找不到哪里写摘要,请在编辑文章页面上方找到“显示选项”,在其中勾选上“摘要”) 114 | 如果没有写摘要,那么发出来的微博是“【文章题目】+文章正文+文章链接”的格式。其中文章题目长度为40个中文字或80个英文字。 115 | 116 | 文章中的第一张图片也会发到微博里,优先选择文章的特色图像作为微博配图(如果您找不到哪里设置特色图像,请在编辑文章页面上方找到“显示选项”,在其中勾选上“特色图像”)。只有上传到WordPress中的图片会被发到微博。对于外链图片,目前只支持jpg格式的图片。 117 | 118 | 1. 文章评论能保存到WordPress本地数据库吗? 119 | 120 | 多说WordPress插件支持评论实时保存到网站本地数据库,但这种保存只起到备份作用,在“多说评论”里删除一条评论并不会在本地做同步删除。 121 | 122 | 当您在localhost测试时,由于我们无法链接到你的服务器,保存到本地不是实时的,而是半个小时写回一次。 123 | 124 | == Screenshots == 125 | 1. 文章/页面下的评论框,支持用新浪微博、QQ、人人、豆瓣、网易微博、搜狐微博、开心网、百度账号帐号登录 126 | 2. 针对手机界面深度优化,响应式的设计自动适配各种移动终端 127 | 3. 评论列表单级回复样式 128 | 4. 你的评论被回复之后,实时提醒 129 | 5. 多款主题供您选择,深色背景的网站毫无违和感。CSS样式代码全面开源,供您深度定制属于自己的评论框 130 | 6. 集成在WordPress后台的多说评论管理界面,智能过滤垃圾评论 131 | 132 | == Changelog == 133 | = 1.2 (2015-11-23) = 134 | * [修正]修复多说在https下无法使用的问题 135 | * [改进]更彻底的清空站点配置,重置所有关联数据 136 | 137 | = 1.1 (2013-8-28) = 138 | * [新增]评论框主题设定功能 139 | * [新增]环境依赖检查 140 | * [新增]多说CSS样式开源啦 141 | * [改进]针对WordPress主题的自适应主题补丁功能 142 | * [改进]用社交帐号登录之后的提示信息 143 | * [改进]多说评论数据同步回本地时,保留表情图片 144 | * [修正]用社交帐号登录之后跳转到登录页的问题 145 | * [修正]兼容其他插件设置了__autoload()的情况 146 | 147 | = 1.0 (2013-3-14) = 148 | * [新增]支持核心代码后置功能 149 | * [新增]允许单篇文章或页面启用或禁用多说评论框 150 | * [新增]社交登录的新用户允许绑定和注册 151 | * [新增]查看统计数据功能 152 | * [新增]新评论实时桌面提醒 153 | * [新增]反向评论回流功能检测 154 | * [新增]顶起来的评论功能 155 | * [新增]文本自定义功能 156 | * [改进]单点登录的实现方式,全面兼容 W3 Total Cache 和 WP super Cache 157 | * [改进]在非标准WP主题下依然能够正常渲染评论框、最新评论挂件、最新访客挂件等 158 | * [改进]最新访客挂件(小工具)增加头像尺寸选项 159 | * [改进]文章评论计数和原主题结合更好 160 | * [改进]调整卸载功能的说明文字,补充文档 161 | * [改进]管理后台代码重构,速度更快更流畅 162 | * [改进]网页中的相对路径图片,依然能够发微博 (感谢雷锋网的反馈) 163 | * [修正]删除站点之后无法重新设置帐号的问题 164 | * [修正]代码后置在某些版本WP中的问题 (感谢水煮鱼的反馈) 165 | * [修正]网络环境不佳的情况下登录出错的问题 166 | 167 | = 0.9 (2012-10-31) = 168 | * [新增]自定义APIKey功能(暂时仅支持日均访问量大于10000的站点,暂时仅支持新浪微博) 169 | * [新增]评论框支持直接转发/评论微博 170 | * [新增]分布式服务器架构,支持更多更大站点,速度更快 171 | * [改进]允许用户选择不同的API服务器域名 172 | * [改进]在多说服务器无法连接的情况下仍然不影响正常服务 173 | * [改进]分享到社交网络功能改成了ajax模式,编辑文章页面打开更快 174 | * [改进]增强兼容性,避免和个别插件冲突导致微博多次发布 175 | * [改进]从多说服务器同步数据到WordPress数据库,一键点击之后同步所有记录 176 | * [修正]多说分布式架构升级之后,32位php服务器同步评论数据到本地出错的问题 177 | * [修正]同步数据到多说时出现的错误 178 | * [修正]SAE用户同步数据时出现重复同步的问题 179 | * [修正]同步未完成的用户也能进入管理界面 180 | * [修正]部分插件冲突导致的redeclare class问题 181 | 182 | = 0.8 (2012-08-04) = 183 | * [新增]ajax加载文章的评论数 184 | * [新增]热评文章挂件(小工具) 185 | * [新增]首页(仪表盘)新增多说最新评论挂件 186 | * [新增]评论管理界面增加彻底删除评论功能 187 | * [新增]用户管理界面可以直接搜索用户添加管理员 188 | * [新增]管理界面支持导入评论数据文件功能 189 | * [新增]手动同步多说服务器上的数据到本地数据库功能 190 | * [新增]调试功能开关 191 | * [新增]卸载功能(慎用) 192 | * [新增]支持Google帐号登录 193 | * [改进]大规模重构了代码,提高复用度,删除了无用的函数 194 | * [改进]完美支持WordPress 3.4 195 | * [改进]在多说管理界面中做的删除、通过、标记为垃圾评论等操作,都会同步到本地数据库(感谢众多忠实用户的反馈) 196 | * [改进]个人设置页面和站点功能设置页面直接集成iframe管理界面 197 | * [改进]网络不通畅情况下,不影响其他WordPress功能(感谢众多忠实用户的反馈) 198 | * [改进]评论管理界面的导航,更大的操作空间 199 | * [改进]文章的附件不再采用单独的评论框,而是和文章共享同一个评论框 200 | * [改进]不仅通过Akismet同时还启用多说自己的垃圾评论过滤系统处理社交用户评论(感谢众多忠实用户的反馈) 201 | * [修正]反向同步评论数据到本地时,过滤XSS代码(感谢科学松鼠会的反馈) 202 | * [修正]用户合并帐号之后无法用社交帐号登录多说的问题 203 | * [修正]特殊场景下绑定不上社交帐号的问题 204 | * [修正]一个网页中插入多个多说评论框时的bug 205 | 206 | = 0.7.2 (2012-05) = 207 | * [新增]最活跃的用户挂件(小工具) 208 | * [改进]将标题中的特殊字符进行转义 209 | 210 | = 0.7.1 (2012-04-29) = 211 | * [新增]查看对话功能(感谢夜不如新的建议) 212 | * [新增]等待审核的评论计数器 213 | * [新增]自定义文章类型开始支持同步发布到微博(感谢我爱水煮鱼和陆召的建议) 214 | * [新增]发布到搜狐微博的功能支持 215 | * [新增]最近访客挂件(小工具) 216 | * [新增]自带腾讯微博-收听组件(小工具) 217 | * [改进]评论转发到微博后实时更新文章相关微博 218 | * [改进]用社交帐号登录功能可以开启关闭(感谢myqa的反馈) 219 | * [改进]管理后台菜单和标题显示多说图标(感谢我爱水煮鱼的建议) 220 | * [改进]启用多说评论时,关闭WP后台的评论接口,避免垃圾评论利用WP后台评论接口 221 | * [修正]附件图片无法进行评论的问题 222 | * [修正]pingback和trackback无法审核的问题 223 | * [修正]绑定社交帐号无效的问题(感谢静水流深/vatcom_dong的反馈) 224 | 225 | = 0.7 (2012-04-15) = 226 | * [新增]单点登录功能支持,已经登陆WordPress的用户,评论框身份和WordPress身份保持一致 227 | * [新增]更适合深度讨论交流的平铺模式(嵌套层级设置为1即可启用) 228 | * [新增]显示文章相关的腾讯微博 229 | * [新增]用社交网站帐号登录WordPress的功能 230 | * [新增]完美支持即将发布的WordPress 3.4 231 | * [改进]站点首页的新留言及回复提醒功能 232 | * [改进]登录WordPress后台后,显示权限不足的问题 233 | * [修正]文章同步时间相差8小时的问题 234 | * [修正]最新评论小工具(widget)修改设置无效的bug 235 | * [修正]相对时间无效的问题 236 | 237 | = 0.6.1 (2012-03-07) = 238 | * [新增]定时发微博功能 239 | * [新增]最新评论挂件隐藏管理员评论功能 240 | * [新增]显示文章转载记录功能 241 | * [改进]允许一个网页内出现多个评论框 242 | * [改进]提升文章同步速度 243 | * [修正]NextGen Gallery和多说不兼容,导致同步文章时出错的bug(感谢爱发现的反馈) 244 | * [修正]同步文章出错的bug 245 | * [修正]SEO的HTML代码未闭合的bug(感谢ourctc.com的反馈) 246 | * [修正]最新评论挂件显示头像开关无效的bug 247 | 248 | = 0.6 (2012-02-21) = 249 | * [新增]最新评论侧栏挂件(widget) 250 | * [新增]自定义新浪微博/QQ开放平台API Key 251 | * [新增]自定义微博官方帐号 252 | * [新增]自定义版权信息 253 | * [新增]对于文章中ShortCode的支持 254 | * [新增]帐号合并功能 255 | * [新增]解除绑定功能 256 | * [新增]优先采用文章摘要来发布微博 257 | * [新增]意见反馈提示 258 | * [新增]个人评论汇总页面 259 | * [改进]同步数据时更人性化的提示信息 260 | * [改进]akismet垃圾评论过滤可以关闭(感谢天涯海阁的反馈) 261 | * [改进]提升同步数据的速度和稳定性(成功一次导入了10万条评论)(感谢万戈、邻居的耳朵、科学松鼠会的支持) 262 | * [改进]优化了在常见的主题模版下的样式 263 | * [改进]同步文章到QQ空间和人人时的格式混乱问题 264 | * [改进]进一步提升加载速度 265 | * [修正]IE下不能插入表情的bug 266 | * [修正]保存评论到本地时丢失层级关系的问题 267 | * [修正]升级插件时要求重新同步数据的问题 268 | * [修正]连接出错时候的提示信息错误(感谢leyi的反馈) 269 | 270 | = 0.5 (2012-02-07) = 271 | * [新增]显示文章相关微博 272 | * [新增]插入表情功能 273 | * [新增]喜欢文章功能 274 | * [新增]回复提醒功能 275 | * [新增]自定义评论框CSS功能(感谢“DNSpod团队博客”的反馈) 276 | * [新增]采用Akismet过滤垃圾评论 277 | * [改进]安装流程,避免出现多个帐号的现象 278 | * [改进]启用多说时拒绝垃圾广告机器人利用WP接口发布评论(感谢“绿色精品软件”的反馈) 279 | * [改进]同步文章到微博的模版 280 | * [改进]站点信息更新时,同时更新多说的站点设置 281 | * [改进]关闭文章评论功能(感谢“鸸鹋动物园”的反馈) 282 | * [修正]同步数据慢的问题(感谢“懂得”、“十万个为什么”、“UbuntuSoft”、“前端集合”的反馈) 283 | * [修正]重复同步文章导致部分评论看不到的问题(感谢“UbuntuSoft”的反馈) 284 | * [修正]网易微博头像显示不正常的问题(感谢“动漫X档案”的反馈) 285 | * [修正]和Prototype不兼容的问题(感谢“小权”的反馈) 286 | * [修正]管理后台IE6识别错误的问题(感谢“朋朋”的反馈) 287 | * [修正]WordPress2.8-2.9版本中数据同步不正常的问题(感谢“豆果美食”的反馈) 288 | * [修正]几个安全性漏洞 289 | 290 | = 0.4.3 (2012-01-17) = 291 | * [新增]全面支持开心网、搜狐微博、网易微博、百度帐号登陆 292 | * [新增]焕然一新的评论管理后台 293 | * [改进]链接增加nofollow 294 | * [修正]WordPress 3.2以前版本的同步数据兼容性问题 295 | * [修正]部分特殊主题的评论框加载 296 | * [修正]部分用户gravatar头像不显示的问题 297 | 298 | = 0.4.2 (2012-01-10) = 299 | * [新增]同步数据时支持断点续传 300 | * [改进]SEO优化,避免和SuperCache类冲突 301 | * [修正]ipad上日期显示不正确的问题 302 | 303 | = 0.4.1 (2011-12-26) = 304 | * [新增]高级自定义,可以设置评论框嵌套标签 305 | * [新增]SEO功能/本地评论导入功能的开关 306 | * [修正]同步评论到多说的bug 307 | * [修正]低版本php的兼容性问题 308 | * [修正]网速不快的情况下同步数据超时的问题 309 | 310 | = 0.4 (2011-12-25) = 311 | * [新增]支持多说评论反向写回本地数据库(实时的哟) 312 | * [新增]SEO支持,搜索引擎能够爬到评论内容 313 | * [新增]支持HTML解析(白名单制) 314 | * [新增]支持插入视频、图片、表情 315 | * [改进]即使没有curl支持,也可以使用其他方式连接多说服务器 316 | * [改进]安装流程 317 | * [改进]管理界面菜单位置 318 | * [改进]个人帐号管理界面 319 | * [修正]IE6/7下加载评论框报错 320 | * [修正]IE6/7下时间显示错误 321 | * [修正]腾讯微博头像显示问题 322 | 323 | = 0.3.3 (2011-12-14) = 324 | * [修正]一个安全漏洞 325 | * [修正]在WordPress3.3下注册站点和注册用户时的bug 326 | * [新增]账号共享功能,允许多人用同一个微博账号发布微博 327 | 328 | = 0.3.2 (2011-12-13) = 329 | * [改进]在发布文章同步到微博时带上图片 330 | * [改进]文章没有同步时自动同步 331 | 332 | = 0.3.1 (2011-12-12) = 333 | * [修正]在WordPress 3.3下面同步用户不正常的bug 334 | * [修正]新发布的文章不显示评论框的bug 335 | 336 | = 0.3 (2011-12-12) = 337 | * [新增]评论管理界面上线 338 | * [改进]全面支持WordPress 3.3 339 | 340 | = 0.2 (2011-12-6) = 341 | * [改进]发文章同步到微博、QQ空间和人人的功能 342 | * [新增]登录WordPress后台时,可以同时登录多说 343 | * [新增]WordPress管理界面内就可以绑定社交帐号 344 | * [新增]支持Gravatar头像 345 | 346 | = 0.1 (2011-12-2) = 347 | * [新增]基本的功能 348 | 349 | == Upgrade Notice == 350 | 351 | = 0.3 = 352 | 如果系统提示你“不是管理员或编辑”,请登出WordPress再重新登录 353 | 354 | = 0.2 = 355 | 之前发布的文章,如果没有出现多说评论框,请点击“多说评论框设置 > 同步评论到多说”。 356 | 357 | = 0.1 = 358 | 发布文章功能可能工作不正常。 359 | 360 | == DEMO == 361 | 362 | 已经有数千家网站开始使用多说,其中包括: 363 | 364 | 1. [站长之家](http://www.chinaz.com/ "站长之家") 365 | 1. [DoNews](http://www.donews.com/ "DoNews") 366 | 1. [泡泡网](http://www.pcpop.com/ "泡泡网") 367 | 1. [IT168](http://www.it168.com/ "IT168") 368 | 1. [下厨房](http://blog.xiachufang.com/ "下厨房") 369 | 1. [邻居的耳朵](http://kxt.fm/ "邻居的耳朵") 370 | 1. [DNSpod](http://blog.dnspod.cn/ "DNSpod") 371 | 1. [Web2.0Share](http://http://www.web20share.com/ "Web2.0Share") 372 | 1. [Ubuntusoft](http://www.ubuntusoft.com "Ubuntusoft") 373 | 1. [分享网络2.0](www.showeb20.com/ "分享网络2.0") 374 | 1. [懒人图库](http://www.lanrentuku.com/ "学会偷懒 懒出境界") 375 | 1. [天堂图片网](http://www.ivsky.com/ "天堂图片网") 376 | 1. [豆果美食](http://blog.douguo.com/ "豆果美食") 377 | 1. [精品绿色便携软件](http://www.portablesoft.org/ "精品绿色便携软件") 378 | 1. [鸸鹋动物园](http://www.ermiao.com/ "鸸鹋动物园") 379 | 1. [Aladd设计量贩铺](http://aladd.net/ "高品质设计分享平台") 380 | 1. [MacGG](http://www.macgg.com/ "MacGG") 381 | 1. [蘑菇爱家居](http://mooogu.cn/blog "蘑菇爱家居") 382 | 1. [微奇生活](http://www.vikilife.com/ "创意玩意集散地") 383 | -------------------------------------------------------------------------------- /duoshuo/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duoshuo/duoshuo-wordpress/c7aa8e44b87fffc483d3ed62046a3f4bd8ca41cb/duoshuo/screenshot-1.png -------------------------------------------------------------------------------- /duoshuo/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duoshuo/duoshuo-wordpress/c7aa8e44b87fffc483d3ed62046a3f4bd8ca41cb/duoshuo/screenshot-2.png -------------------------------------------------------------------------------- /duoshuo/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duoshuo/duoshuo-wordpress/c7aa8e44b87fffc483d3ed62046a3f4bd8ca41cb/duoshuo/screenshot-3.png -------------------------------------------------------------------------------- /duoshuo/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duoshuo/duoshuo-wordpress/c7aa8e44b87fffc483d3ed62046a3f4bd8ca41cb/duoshuo/screenshot-4.png -------------------------------------------------------------------------------- /duoshuo/screenshot-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duoshuo/duoshuo-wordpress/c7aa8e44b87fffc483d3ed62046a3f4bd8ca41cb/duoshuo/screenshot-5.png -------------------------------------------------------------------------------- /duoshuo/screenshot-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duoshuo/duoshuo-wordpress/c7aa8e44b87fffc483d3ed62046a3f4bd8ca41cb/duoshuo/screenshot-6.jpg -------------------------------------------------------------------------------- /duoshuo/settings.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 4 |

    多说评论框设置

    5 | 6 |

    高级设定

    7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 60 | 61 | 62 | 63 | 68 | 69 | 70 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 88 | 89 | 90 |
    多说域名.duoshuo.com
    密钥
    多说API服务器 22 |
      23 |
    • (如果你的博客服务器在国内,推荐)
    • 24 |
    • (如果你的博客服务器在国外,推荐)
    • 25 |
    • (除非你的博客服务器DNS出现故障,否则不推荐)
    • 26 |
    27 |
    本地数据备份 32 | 33 |
    SEO优化 38 | 39 |
    Pingback和Trackback 44 | 45 |
    脚本后置 50 | 51 |
    主题适配 56 | 57 | 58 |

    59 |
    评论数修正 64 | 65 | 66 |

    如果你的主题模板没有显示评论数,或者没有按照WordPress的标准,你可能需要修改模板。参见:WordPress主题中的文章评论数

    67 |
    社交帐号登录 72 | 73 |
    评论框前缀
    仅在主题和评论框的div嵌套不正确的情况下使用
    评论框后缀
    仅在主题和评论框的div嵌套不正确的情况下使用
    调试开关 86 | 87 |
    仅在出现故障向多说汇报错误信息时打开
    91 |

    92 |
    93 | 94 |

    数据同步

    95 |
    96 |

    同步本地数据库中的评论到多说

    97 |

    98 |

    同步完成

    99 |
    100 | 104 | 105 | 106 |
    107 |

    清空多说站点配置

    108 |
    109 | 110 |

    如果你希望本博客和其他多说站点进行绑定,或者创建新的多说站点,点此

    111 |
    112 |
    113 | 114 |
    115 |

    环境依赖检查

    116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 'php版本', 128 | 'wordpress' => 'WordPress版本', 129 | 'json' => 'json扩展', 130 | 'curl' => 'curl扩展', 131 | 'fopen' => 'fopen()', 132 | 'fsockopen' => 'fsockopen()', 133 | 'hash_hmac' => 'hash_hmac()', 134 | ); 135 | foreach($dependencies as $key => $name): 136 | list($status, $result) = $this->checkDependency($key);?> 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
    依赖状态结果
    OK' : $result;?>
    145 |

    curl扩展、fopen()、fsockopen()只需支持一个即可,推荐使用curl扩展

    146 |
    147 | 148 |

    常见问题和参考链接

    149 | 153 | 154 |

    意见反馈

    155 |

    你的意见是多说成长的原动力,欢迎给我们留言,或许你想要的功能下一个版本就会实现哦!

    156 |

    多说正在招人!如果你相信改变世界不是资本而是技术;如果你不只是想完成任务,还希望你的巧妙构思实现意想不到的好处;如果你希望和跟你一样聪明的人一起工作。那么你不妨加入我们!

    157 |

    158 | 159 | 160 |

    161 | 'QQ空间', 164 | 'weibo' => '新浪微博', 165 | 'qqt' => '腾讯微博', 166 | 'renren'=> '人人网', 167 | 'kaixin'=> '开心网', 168 | 'douban'=> '豆瓣网', 169 | 'netease'=> '网易微博', 170 | 'sohu' => '搜狐微博', 171 | ); 172 | 173 | ?> 174 |

    我们永远相信,分享是一种美德

    175 |

    把多说分享给你的朋友:

    176 |
      177 | $serviceName):?> 178 |
    • 179 | 180 |
    181 | 210 | 211 |
    212 | -------------------------------------------------------------------------------- /duoshuo/statistics.php: -------------------------------------------------------------------------------- 1 | 2 | $this->jwt(), 5 | ); 6 | $adminUrl = is_ssl()? 'https://' : 'http://'; 7 | $adminUrl .= $this->shortName . '.' . self::DOMAIN . '/admin/statistics/?' . http_build_query($params, null, '&'); 8 | ?> 9 |
    10 | 11 |

    数据统计 12 | 在新窗口中打开

    13 | 14 |
    15 | -------------------------------------------------------------------------------- /duoshuo/styles.css: -------------------------------------------------------------------------------- 1 | .ds-service-icon li{ 2 | float: left; 3 | margin-right:6px; 4 | } 5 | .ds-service-icon a { 6 | display: block; 7 | cursor:pointer; 8 | width: 32px !important; 9 | height: 32px !important; 10 | background: url(images/service_icons_32x32.png) no-repeat; 11 | overflow: hidden; 12 | text-indent:-9999px; 13 | } 14 | .ds-service-icon a.ds-weibo {background-position: 0 0;} 15 | .ds-service-icon a.ds-qzone {background-position: 0 -32px;} 16 | .ds-service-icon a.ds-qqt {background-position: 0 -1760px;} 17 | .ds-service-icon a.ds-renren {background-position: 0 -64px;} 18 | .ds-service-icon a.ds-kaixin {background-position: 0 -192px;} 19 | .ds-service-icon a.ds-netease {background-position: 0 -320px;} 20 | .ds-service-icon a.ds-sohu {background-position: 0 -1248px;} 21 | .ds-service-icon a.ds-qq {background-position: 0 -2208px;} 22 | .ds-service-icon a.ds-douban {background-position: 0 -224px;} 23 | .ds-service-icon a.ds-baidu {background-position: 0 -352px;} 24 | .ds-service-icon a.ds-taobao {background-position: 0 -2016px;} 25 | .ds-service-icon a.ds-msn {background-position: 0 -864px;} 26 | 27 | #icon-duoshuo{ 28 | background: url("images/head-icon.gif") no-repeat scroll 0 4px transparent; 29 | } 30 | 31 | .ds-icon-yes{ 32 | display:inline-block; 33 | background: url("images/icon.gif") no-repeat 0 -10px transparent; 34 | width: 15px; 35 | height: 12px; 36 | text-indent:-9999px; 37 | } 38 | .ds-icon-no{ 39 | display:inline-block; 40 | background: url("images/icon.gif") no-repeat 0 -22px transparent; 41 | width: 15px; 42 | height: 12px; 43 | text-indent:-9999px; 44 | } 45 | 46 | .ds-dependencies{ 47 | width:500px; 48 | border-collapse: collapse; 49 | border: 1px solid #ebebeb; 50 | } 51 | .ds-dependencies td, .ds-dependencies th {padding: 8px; text-align:left;} 52 | .ds-dependencies thead tr{background-color : #ebebeb;} 53 | .ds-dependencies tbody tr:nth-child(even){background-color : #ebebeb;} 54 | .ds-dependencies tbody tr:nth-child(odd){background-color : #FFF;} 55 | -------------------------------------------------------------------------------- /duoshuo/sync.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 |

    数据同步

    4 |
    5 |

    安装成功了!只要一键将您的用户、文章和评论信息同步到多说,多说就可以开始为您服务了!开始同步

    6 |

    7 |

    同步完成,现在你可以设置管理

    8 |
    9 | 10 |
    11 | -------------------------------------------------------------------------------- /duoshuo/themes.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 | Fork me on GitHub 4 | 5 |

    多说主题设置

    6 | 7 |
    8 | 9 |
      10 |

      多说的CSS样式已经开源啦! github:duoshuo/duoshuo-embed.css

      11 |

      你可以打造属于自己的主题,在开发者中心分享你的主题,还有可能被官方推荐哟!

      12 |
      13 | 23 | 45 | 46 | shortName .".duoshuo.com/api/sites/themes.jsonp?callback=loadDuoshuoThemes"; 49 | 50 | ?> 51 | 52 | 53 |
      54 | -------------------------------------------------------------------------------- /duoshuo/widgets.php: -------------------------------------------------------------------------------- 1 | 'ds-widget-recent-comments', 'description' => '最新评论(由多说提供)' ); 9 | parent::__construct('ds-recent-comments', '最新评论(多说)', $widget_ops); 10 | 11 | $this->alt_option_name = 'duoshuo_widget_recent_comments'; 12 | 13 | if ( is_active_widget(false, false, $this->id_base) ) 14 | add_action( 'wp_head', array(&$this, 'recent_comments_style') ); 15 | 16 | //add_action( 'comment_post', array(&$this, 'flush_widget_cache') ); 17 | //add_action( 'transition_comment_status', array(&$this, 'flush_widget_cache') ); 18 | 19 | $this->duoshuoPlugin = Duoshuo_WordPress::getInstance(); 20 | } 21 | 22 | function recent_comments_style() { 23 | if ( ! current_theme_supports( 'widgets' ) )// Temp hack #14876 24 | return; 25 | } 26 | 27 | function widget( $args, $instance ) { 28 | global $comments, $comment; 29 | 30 | if ( ! isset( $args['widget_id'] ) ) 31 | $args['widget_id'] = $this->id; 32 | 33 | extract($args, EXTR_SKIP); 34 | 35 | $output = ''; 36 | $title = apply_filters( 'widget_title', empty( $instance['title'] ) ? __( 'Recent Comments' ) : $instance['title'], $instance, $this->id_base ); 37 | 38 | if ( empty( $instance['number'] ) || ! $number = absint( $instance['number'] ) ) 39 | $number = 10; 40 | 41 | $output .= $before_widget; 42 | if ( $title ) 43 | $output .= $before_title . $title . $after_title; 44 | 45 | $data = array( 46 | 'num_items' => $number, 47 | 'show_avatars'=>isset($instance['show_avatars']) ? $instance['show_avatars'] : 1, 48 | 'show_time'=> isset($instance['show_time']) ? $instance['show_time'] : 1, 49 | 'show_title'=> isset($instance['show_title']) ? $instance['show_title'] : 1, 50 | 'show_admin'=> isset($instance['show_admin']) ? $instance['show_admin'] : 1, 51 | 'avatar_size'=> 30, 52 | 'excerpt_length'=> isset($instance['excerpt_length']) ? $instance['excerpt_length'] : 70, 53 | ); 54 | $attribs = ''; 55 | foreach ($data as $key => $value) 56 | $attribs .= ' data-' . str_replace('_','-',$key) . '="' . esc_attr($value) . '"'; 57 | $output .= '
        ' 58 | . $after_widget; 59 | echo $output;?> 60 | duoshuoPlugin->printScripts(); 65 | } 66 | 67 | 68 | function update( $new_instance, $old_instance ) { 69 | $instance = $old_instance; 70 | $instance['title'] = strip_tags($new_instance['title']); 71 | $instance['number'] = absint( $new_instance['number'] ); 72 | $instance['excerpt_length'] = absint( $new_instance['excerpt_length'] ); 73 | $instance['show_avatars'] = absint( $new_instance['show_avatars'] ); 74 | $instance['show_time'] = absint( $new_instance['show_time'] ); 75 | $instance['show_title'] = absint( $new_instance['show_title'] ); 76 | $instance['show_admin'] = absint( $new_instance['show_admin'] ); 77 | 78 | $alloptions = wp_cache_get( 'alloptions', 'options' ); 79 | if ( isset($alloptions['duoshuo_widget_recent_comments']) ) 80 | delete_option('duoshuo_widget_recent_comments'); 81 | 82 | return $instance; 83 | } 84 | 85 | function form( $instance ) { 86 | $title = isset($instance['title']) ? esc_attr($instance['title']) : ''; 87 | $number = isset($instance['number']) ? absint($instance['number']) : 5; 88 | $show_avatars = isset($instance['show_avatars']) ? absint( $instance['show_avatars']) : 1; 89 | $show_title = isset($instance['show_title']) ? absint($instance['show_title']) : 1; 90 | $show_time = isset($instance['show_time']) ? absint($instance['show_time']) : 1; 91 | $show_admin = isset($instance['show_admin']) ? absint($instance['show_admin']) : 1; 92 | $excerpt_length = isset($instance['excerpt_length']) ? absint($instance['excerpt_length']) : 70; 93 | ?> 94 |

        95 |

        96 | 97 |

        98 | 99 | /> 100 | 101 |

        102 | 103 |

        104 | 105 | /> 106 | 107 |

        108 | 109 |

        110 | 111 | /> 112 | 113 |

        114 | 115 |

        116 | 117 | /> 118 | 119 |

        120 | 121 |

        122 |

        123 | 124 | 125 |

        126 |

        127 | 'ds-widget-top-threads', 'description' => '热评文章(由多说提供)'); 137 | parent::__construct('ds-top-threads', '热评文章(多说)', $widget_ops); 138 | 139 | $this->alt_option_name = 'duoshuo_widget_top_threads'; 140 | 141 | $this->duoshuoPlugin = Duoshuo_WordPress::getInstance(); 142 | } 143 | 144 | function widget( $args, $instance ) { 145 | global $comments, $comment; 146 | 147 | if ( ! isset( $args['widget_id'] ) ) 148 | $args['widget_id'] = $this->id; 149 | 150 | extract($args, EXTR_SKIP); 151 | 152 | $output = ''; 153 | $title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '热评文章' : $instance['title'], $instance, $this->id_base ); 154 | 155 | if ( empty( $instance['number'] ) || ! $number = absint( $instance['number'] ) ) 156 | $number = 5; 157 | 158 | $output .= $before_widget; 159 | if ( $title ) 160 | $output .= $before_title . $title . $after_title; 161 | 162 | $data = array( 163 | 'num_items' => $number, 164 | 'range' => isset($instance['range']) ? $instance['range'] : 'weekly', 165 | //'show_avatars'=>isset($instance['show_avatars']) ? $instance['show_avatars'] : 1, 166 | //'avatar_size'=> 30, 167 | ); 168 | $attribs = ''; 169 | foreach ($data as $key => $value) 170 | $attribs .= ' data-' . str_replace('_','-',$key) . '="' . esc_attr($value) . '"'; 171 | $output .= '
          ' 172 | . $after_widget; 173 | echo $output;?> 174 | duoshuoPlugin->printScripts(); 179 | } 180 | 181 | function update( $new_instance, $old_instance ) { 182 | $instance = $old_instance; 183 | $instance['range'] = $new_instance['range']; 184 | $instance['title'] = strip_tags($new_instance['title']); 185 | $instance['number'] = absint( $new_instance['number'] ); 186 | //$instance['show_avatars'] = absint( $new_instance['show_avatars'] ); 187 | 188 | $alloptions = wp_cache_get( 'alloptions', 'options' ); 189 | if ( isset($alloptions['duoshuo_widget_top_threads']) ) 190 | delete_option('duoshuo_widget_top_threads'); 191 | 192 | return $instance; 193 | } 194 | 195 | function form( $instance ) { 196 | $title = isset($instance['title']) ? esc_attr($instance['title']) : ''; 197 | $range = isset($instance['range']) ? esc_attr($instance['range']) : 'weekly'; 198 | $number = isset($instance['number']) ? absint($instance['number']) : 5; 199 | //$show_avatars = isset($instance['show_avatars']) ? absint( $instance['show_avatars']) : 1; 200 | ?> 201 |

          202 |

          203 | 204 |

          205 | 206 | 207 | 208 |

          209 | 216 |

          217 |

          218 | 'ds-widget-recent-visitors', 'description' => '最近访客(由多说提供)' ); 226 | parent::__construct('ds-recent-visitors', '最近访客(多说)', $widget_ops); 227 | 228 | $this->alt_option_name = 'duoshuo_widget_recent_visitors'; 229 | 230 | if ( is_active_widget(false, false, $this->id_base) ) 231 | add_action( 'wp_head', array(&$this, 'printScripts') ); 232 | 233 | //add_action( 'comment_post', array(&$this, 'flush_widget_cache') ); 234 | //add_action( 'transition_comment_status', array(&$this, 'flush_widget_cache') ); 235 | 236 | $this->duoshuoPlugin = Duoshuo_WordPress::getInstance(); 237 | } 238 | 239 | function printScripts() { 240 | if ( ! current_theme_supports( 'widgets' ) )// Temp hack #14876 241 | return; 242 | } 243 | 244 | function widget( $args, $instance ) { 245 | global $comments, $comment; 246 | 247 | if ( ! isset( $args['widget_id'] ) ) 248 | $args['widget_id'] = $this->id; 249 | 250 | extract($args, EXTR_SKIP); 251 | 252 | $output = ''; 253 | $title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '最近访客' : $instance['title'], $instance, $this->id_base ); 254 | 255 | if ( empty( $instance['number'] ) || ! $number = absint( $instance['number'] ) ) 256 | $number = 12; 257 | 258 | $output .= $before_widget; 259 | if ( $title ) 260 | $output .= $before_title . $title . $after_title; 261 | 262 | $data = array( 263 | 'num_items' => $number, 264 | 'show_time'=> isset($instance['show_time']) ? $instance['show_time'] : 1, 265 | 'avatar_size'=> isset($instance['avatar_size']) ? $instance['avatar_size'] : 50, 266 | ); 267 | $attribs = ''; 268 | foreach ($data as $key => $value) 269 | $attribs .= ' data-' . str_replace('_','-',$key) . '="' . esc_attr($value) . '"'; 270 | $output .= '
            ' 271 | . $after_widget; 272 | echo $output;?> 273 | duoshuoPlugin->printScripts(); 278 | } 279 | 280 | 281 | function update( $new_instance, $old_instance ) { 282 | $instance = $old_instance; 283 | $instance['title'] = strip_tags($new_instance['title']); 284 | $instance['number'] = absint( $new_instance['number'] ); 285 | $instance['show_time'] = absint( $new_instance['show_time'] ); 286 | $instance['avatar_size'] = absint( $new_instance['avatar_size'] ); 287 | 288 | $alloptions = wp_cache_get( 'alloptions', 'options' ); 289 | if ( isset($alloptions['duoshuo_widget_recent_visitors']) ) 290 | delete_option('duoshuo_widget_recent_visitors'); 291 | 292 | return $instance; 293 | } 294 | 295 | function form( $instance ) { 296 | $title = isset($instance['title']) ? esc_attr($instance['title']) : ''; 297 | $number = isset($instance['number']) ? absint($instance['number']) : 15; 298 | $show_time = isset($instance['show_time']) ? absint($instance['show_time']) : 1; 299 | $avatar_size = isset($instance['avatar_size']) ? absint($instance['avatar_size']) : 50; 300 | ?> 301 |

            302 |

            303 | 304 | 309 | 310 |

            311 |

            312 |

            313 | px

            314 | 'ds-widget-qqt-follow', 'description' => '腾讯微博-收听组件(由多说提供)' ); 323 | parent::__construct('ds-qqt-follow', '腾讯微博-收听(多说)', $widget_ops); 324 | 325 | $this->alt_option_name = 'duoshuo_widget_qqt_follow'; 326 | 327 | } 328 | 329 | function widget( $args, $instance ) { 330 | 331 | if ( ! isset( $args['widget_id'] ) ) 332 | $args['widget_id'] = $this->id; 333 | 334 | extract($args, EXTR_SKIP); 335 | 336 | $output = $before_widget; 337 | 338 | $title = apply_filters( 'widget_title', isset( $instance['title'] ) ? $instance['title'] : '', $instance, $this->id_base ); 339 | 340 | if ( $title ) 341 | $output .= $before_title . $title . $after_title; 342 | 343 | $params = array( 344 | 'c' => 'follow', 345 | 'a' => 'quick', 346 | 'name'=>isset($instance['qqt_name']) ? $instance['qqt_name'] : 'duo-shuo', 347 | 'style'=>isset($instance['qqt_style']) ? $instance['qqt_style'] : 1, 348 | 't' => time() . sprintf("%03d", microtime() * 1000), 349 | 'f' => isset($instance['qqt_followers']) ? $instance['qqt_followers'] : 1, 350 | ); 351 | 352 | switch($params['style']){ 353 | case 1: 354 | $width = $params['f'] ? 227 : 167; 355 | $height = 75; 356 | break; 357 | case 2: 358 | $width = $params['f'] ? 191 : 136; 359 | $height = 38; 360 | break; 361 | case 3: 362 | $width = $params['f'] ? 168 : 125; 363 | $height = 20; 364 | break; 365 | case 4: 366 | $width = $params['f'] ? 182 : 125; 367 | $height = 27; 368 | break; 369 | case 5: 370 | $width = $params['f'] ? 178 : 125; 371 | $height = 24; 372 | break; 373 | default: 374 | } 375 | 376 | $attribs = array( 377 | 'scrolling' => 'no', 378 | 'width' => $width, 379 | 'height' => $height, 380 | 'frameborder'=> 0, 381 | 'allowtransparency'=>'true', 382 | 'marginheight'=>0, 383 | 'marginwidth'=> 0, 384 | 'src' => (is_ssl()?'https':'http').'://follow.v.t.qq.com/index.php?' . http_build_query($params, null, '&'), 385 | ); 386 | 387 | $output .= '' . $after_widget; 391 | echo $output; 392 | } 393 | 394 | 395 | function update( $new_instance, $old_instance ) { 396 | $instance = $old_instance; 397 | $instance['title'] = strip_tags($new_instance['title']); 398 | $instance['qqt_name'] = strip_tags($new_instance['qqt_name']); 399 | $instance['qqt_style'] = absint( $new_instance['qqt_style'] ); 400 | $instance['qqt_followers'] = absint( $new_instance['qqt_followers'] ); 401 | 402 | $alloptions = wp_cache_get( 'alloptions', 'options' ); 403 | if ( isset($alloptions['duoshuo_widget_qqt_follow']) ) 404 | delete_option('duoshuo_widget_qqt_follow'); 405 | 406 | return $instance; 407 | } 408 | 409 | function form( $instance ) { 410 | $title = isset($instance['title']) ? $instance['title'] : ''; 411 | $qqt_name = isset($instance['qqt_name']) ? $instance['qqt_name'] : ''; 412 | $qqt_style = isset($instance['qqt_style']) ? absint( $instance['qqt_style']) : 1; 413 | $qqt_followers = isset($instance['qqt_followers']) ? absint( $instance['qqt_followers']) : 1; 414 | ?> 415 |

            416 |

            417 |

            418 |

            419 |

            420 | /> 421 |

            422 |
              423 |
            • 424 |
            • 425 |
            • 426 |
            • 427 |
            • 428 |
            429 |