Spam protection, AntiSpam, FireWall by CleanTalk - Version 2.4.9

Version Description

  • Fixed extra debugging in base class
Download this release

Release Info

Developer shagimuratov
Plugin Icon 128x128 Spam protection, AntiSpam, FireWall by CleanTalk
Version 2.4.9
Comparing to
See all releases

Version 2.4.9

cleantalk-rel.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ //< target : http://wp2.host/wp-admin/comment.php?c=26&action=approvecomment&_wpnonce=338ed90533
2
+ //> target : http://wp2.host/wp-admin/comment.php?c=26&action=unapprovecomment&_wpnonce=338ed90533
3
+
4
+ //< data : action=dim-comment&id=26&dimClass=unapproved&_ajax_nonce=338ed90533&new=approved
5
+ //> data : action=dim-comment&id=26&dimClass=unapproved&_ajax_nonce=338ed90533&new=unapproved
6
+
7
+ jQuery(document).ajaxSuccess(function(e, xhr, settings) {
8
+ if(settings['target'].toString().indexOf('action=approvecomment') > -1){
9
+ window.location.reload();
10
+ }
11
+ });
cleantalk.class.php ADDED
@@ -0,0 +1,857 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Cleantalk base class
4
+ *
5
+ * @version 0.20.1
6
+ * @package Cleantalk
7
+ * @subpackage Base
8
+ * @author Сleantalk team (welcome@cleantalk.ru)
9
+ * @copyright (C) 2013 СleanTalk team (http://cleantalk.org)
10
+ * @license GNU/GPL: http://www.gnu.org/copyleft/gpl.html
11
+ * @see http://cleantalk.ru/wiki/doku.php/api
12
+ *
13
+ */
14
+
15
+ /**
16
+ * @ignore
17
+ */
18
+ require_once(dirname(__FILE__) . '/cleantalk.xmlrpc.php');
19
+
20
+ /**
21
+ * Response class
22
+ */
23
+ class CleantalkResponse {
24
+
25
+ /**
26
+ * Unique user ID
27
+ * @var string
28
+ */
29
+ public $sender_id = null;
30
+
31
+ /**
32
+ * Is stop words
33
+ * @var int
34
+ */
35
+ public $stop_words = null;
36
+
37
+ /**
38
+ * Cleantalk comment
39
+ * @var string
40
+ */
41
+ public $comment = null;
42
+
43
+ /**
44
+ * Is blacklisted
45
+ * @var int
46
+ */
47
+ public $blacklisted = null;
48
+
49
+ /**
50
+ * Is allow, 1|0
51
+ * @var int
52
+ */
53
+ public $allow = null;
54
+
55
+ /**
56
+ * Request ID
57
+ * @var int
58
+ */
59
+ public $id = null;
60
+
61
+ /**
62
+ * Request errno
63
+ * @var int
64
+ */
65
+ public $errno = null;
66
+
67
+ /**
68
+ * Error string
69
+ * @var string
70
+ */
71
+ public $errstr = null;
72
+
73
+ /**
74
+ * Is fast submit, 1|0
75
+ * @var string
76
+ */
77
+ public $fast_submit = null;
78
+
79
+ /**
80
+ * Is spam comment
81
+ * @var string
82
+ */
83
+ public $spam = null;
84
+
85
+ /**
86
+ * Is JS
87
+ * @var type
88
+ */
89
+ public $js_disabled = null;
90
+
91
+ /**
92
+ * Sms check
93
+ * @var type
94
+ */
95
+ public $sms_allow = null;
96
+
97
+ /**
98
+ * Sms code result
99
+ * @var type
100
+ */
101
+ public $sms = null;
102
+
103
+ /**
104
+ * Sms error code
105
+ * @var type
106
+ */
107
+ public $sms_error_code = null;
108
+
109
+ /**
110
+ * Sms error code
111
+ * @var type
112
+ */
113
+ public $sms_error_text = null;
114
+
115
+ /**
116
+ * Stop queue message, 1|0
117
+ * @var int
118
+ */
119
+ public $stop_queue = null;
120
+
121
+ /**
122
+ * Account shuld by deactivated after registration, 1|0
123
+ * @var int
124
+ */
125
+ public $inactive = null;
126
+
127
+ /**
128
+ * Create server response
129
+ *
130
+ * @param type $response
131
+ * @param xmlrpcresp $obj
132
+ */
133
+ function __construct($response = null, xmlrpcresp $obj = null) {
134
+ if ($response && is_array($response) && count($response) > 0) {
135
+ foreach ($response as $param => $value) {
136
+ $this->{$param} = $value;
137
+ }
138
+ } else {
139
+ $this->errno = $obj->errno;
140
+ $this->errstr = $obj->errstr;
141
+
142
+ $this->errstr = preg_replace("/.+(\*\*\*.+\*\*\*).+/", "$1", $this->errstr);
143
+
144
+ // Разбираем xmlrpcresp ответ с клинтолка
145
+ if ($obj->val !== 0) {
146
+ $this->sender_id = (isset($obj->val['sender_id'])) ? $obj->val['sender_id'] : null;
147
+ $this->stop_words = isset($obj->val['stop_words']) ? $obj->val['stop_words'] : null;
148
+ $this->comment = $obj->val['comment'];
149
+ $this->blacklisted = (isset($obj->val['blacklisted'])) ? $obj->val['blacklisted'] : null;
150
+ $this->allow = (isset($obj->val['allow'])) ? $obj->val['allow'] : null;
151
+ $this->id = (isset($obj->val['id'])) ? $obj->val['id'] : null;
152
+ $this->fast_submit = (isset($obj->val['fast_submit'])) ? $obj->val['fast_submit'] : 0;
153
+ $this->spam = (isset($obj->val['spam'])) ? $obj->val['spam'] : 0;
154
+ $this->js_disabled = (isset($obj->val['js_disabled'])) ? $obj->val['js_disabled'] : 0;
155
+ $this->sms_allow = (isset($obj->val['sms_allow'])) ? $obj->val['sms_allow'] : null;
156
+ $this->sms = (isset($obj->val['sms'])) ? $obj->val['sms'] : null;
157
+ $this->sms_error_code = (isset($obj->val['sms_error_code'])) ? $obj->val['sms_error_code'] : null;
158
+ $this->sms_error_text = (isset($obj->val['sms_error_text'])) ? $obj->val['sms_error_text'] : null;
159
+ $this->stop_queue = (isset($obj->val['stop_queue'])) ? $obj->val['stop_queue'] : 0;
160
+ $this->inactive = (isset($obj->val['inactive'])) ? $obj->val['inactive'] : 0;
161
+ } else {
162
+ $this->comment = $this->errstr . '. Automoderator cleantalk.org';
163
+ }
164
+ }
165
+ }
166
+
167
+ }
168
+
169
+ /**
170
+ * Request class
171
+ */
172
+ class CleantalkRequest {
173
+
174
+ const VERSION = '0.7';
175
+
176
+ /**
177
+ * User message
178
+ * @var string
179
+ */
180
+ public $message = null;
181
+
182
+ /**
183
+ * Post example with last comments
184
+ * @var string
185
+ */
186
+ public $example = null;
187
+
188
+ /**
189
+ * Auth key
190
+ * @var string
191
+ */
192
+ public $auth_key = null;
193
+
194
+ /**
195
+ * Engine
196
+ * @var string
197
+ */
198
+ public $agent = null;
199
+
200
+ /**
201
+ * Is check for stoplist,
202
+ * valid are 0|1
203
+ * @var int
204
+ */
205
+ public $stoplist_check = 1;
206
+
207
+ /**
208
+ * Language server response,
209
+ * valid are 'en' or 'ru'
210
+ * @var string
211
+ */
212
+ public $response_lang = 'en';
213
+
214
+ /**
215
+ * User IP
216
+ * @var strings
217
+ */
218
+ public $sender_ip = null;
219
+
220
+ /**
221
+ * User email
222
+ * @var strings
223
+ */
224
+ public $sender_email = null;
225
+
226
+ /**
227
+ * User nickname
228
+ * @var string
229
+ */
230
+ public $sender_nickname = null;
231
+
232
+ /**
233
+ * Sender info JSON string
234
+ * @var string
235
+ */
236
+ public $sender_info = null;
237
+
238
+ /**
239
+ * Post info JSON string
240
+ * @var string
241
+ */
242
+ public $post_info = null;
243
+
244
+ /**
245
+ * Is allow links, email and icq,
246
+ * valid are 1|0
247
+ * @var int
248
+ */
249
+ public $allow_links = 0;
250
+
251
+ /**
252
+ * Time form filling
253
+ * @var int
254
+ */
255
+ public $submit_time = null;
256
+
257
+ /**
258
+ * Is enable Java Script,
259
+ * valid are 0|1|2
260
+ * Status:
261
+ * null - JS html code not inserted into phpBB templates
262
+ * 0 - JS disabled at the client browser
263
+ * 1 - JS enabled at the client broswer
264
+ * @var int
265
+ */
266
+ public $js_on = null;
267
+
268
+ /**
269
+ * user time zone
270
+ * @var string
271
+ */
272
+ public $tz = null;
273
+
274
+ /**
275
+ * Feedback string,
276
+ * valid are 'requset_id:(1|0)'
277
+ * @var string
278
+ */
279
+ public $feedback = null;
280
+
281
+ /**
282
+ * Phone number
283
+ * @var type
284
+ */
285
+ public $phone = null;
286
+
287
+ /**
288
+ * Fill params with constructor
289
+ * @param type $params
290
+ */
291
+ public function __construct($params = null) {
292
+ if (is_array($params) && count($params) > 0) {
293
+ foreach ($params as $param => $value) {
294
+ $this->{$param} = $value;
295
+ }
296
+ }
297
+ }
298
+
299
+ }
300
+
301
+ /**
302
+ * Cleantalk class create request
303
+ */
304
+ class Cleantalk {
305
+
306
+ /**
307
+ * Debug level
308
+ * @var int
309
+ */
310
+ public $debug = 0;
311
+
312
+ /**
313
+ * Maximum data size in bytes
314
+ * @var int
315
+ */
316
+ private $dataMaxSise = 32768;
317
+
318
+ /**
319
+ * Data compression rate
320
+ * @var int
321
+ */
322
+ private $compressRate = 6;
323
+
324
+ /**
325
+ * Server connection timeout in seconds
326
+ * @var int
327
+ */
328
+ private $server_timeout = 2;
329
+
330
+ /**
331
+ * Cleantalk server url
332
+ * @var string
333
+ */
334
+ public $server_url = null;
335
+
336
+ /**
337
+ * Last work url
338
+ * @var string
339
+ */
340
+ public $work_url = null;
341
+
342
+ /**
343
+ * WOrk url ttl
344
+ * @var int
345
+ */
346
+ public $server_ttl = null;
347
+
348
+ /**
349
+ * Time wotk_url changer
350
+ * @var int
351
+ */
352
+ public $server_changed = null;
353
+
354
+ /**
355
+ * Flag is change server url
356
+ * @var bool
357
+ */
358
+ public $server_change = false;
359
+
360
+ /**
361
+ * Use TRUE when need stay on server. Example: send feedback
362
+ * @var bool
363
+ */
364
+ public $stay_on_server = false;
365
+
366
+ /**
367
+ * Function checks whether it is possible to publish the message
368
+ * @param CleantalkRequest $request
369
+ * @return type
370
+ */
371
+ public function isAllowMessage(CleantalkRequest $request) {
372
+ $error_params = $this->filterRequest('check_message', $request);
373
+
374
+ if (!empty($error_params)) {
375
+ $response = new CleantalkResponse(
376
+ array(
377
+ 'allow' => 0,
378
+ 'comment' => 'CleanTalk. Request params error: ' . implode(', ', $error_params)
379
+ ), null);
380
+
381
+ return $response;
382
+ }
383
+
384
+ $msg = $this->createMsg('check_message', $request);
385
+ return $this->xmlRequest($msg);
386
+ }
387
+
388
+ /**
389
+ * Function checks whether it is possible to publish the message
390
+ * @param CleantalkRequest $request
391
+ * @return type
392
+ */
393
+ public function isAllowUser(CleantalkRequest $request) {
394
+ $error_params = $this->filterRequest('check_newuser', $request);
395
+
396
+ if (!empty($error_params)) {
397
+ $response = new CleantalkResponse(
398
+ array(
399
+ 'allow' => 0,
400
+ 'comment' => 'CleanTalk. Request params error: ' . implode(', ', $error_params)
401
+ ), null);
402
+
403
+ return $response;
404
+ }
405
+
406
+ $msg = $this->createMsg('check_newuser', $request);
407
+ return $this->xmlRequest($msg);
408
+ }
409
+
410
+ /**
411
+ * Function sends the results of manual moderation
412
+ *
413
+ * @param CleantalkRequest $request
414
+ * @return type
415
+ */
416
+ public function sendFeedback(CleantalkRequest $request) {
417
+ $error_params = $this->filterRequest('send_feedback', $request);
418
+
419
+ if (!empty($error_params)) {
420
+ $response = new CleantalkResponse(
421
+ array(
422
+ 'allow' => 0,
423
+ 'comment' => 'Cleantalk. Spam protect. Request params error: ' . implode(', ', $error_params)
424
+ ), null);
425
+
426
+ return $response;
427
+ }
428
+
429
+ $msg = $this->createMsg('send_feedback', $request);
430
+ return $this->xmlRequest($msg);
431
+ }
432
+
433
+ /**
434
+ * Filter request params
435
+ * @param CleantalkRequest $request
436
+ * @return type
437
+ */
438
+ private function filterRequest($method, CleantalkRequest $request) {
439
+ $error_params = array();
440
+
441
+ // general and optional
442
+ foreach ($request as $param => $value) {
443
+ if (in_array($param, array('message', 'example', 'agent',
444
+ 'sender_info', 'sender_nickname', 'post_info', 'phone')) && !empty($value)) {
445
+ if (!is_string($value) && !is_integer($value)) {
446
+ $error_params[] = $param;
447
+ }
448
+ }
449
+
450
+ if (in_array($param, array('stoplist_check', 'allow_links')) && !empty($value)) {
451
+ if (!in_array($value, array(1, 2))) {
452
+ $error_params[] = $param;
453
+ }
454
+ }
455
+
456
+ if (in_array($param, array('js_on')) && !empty($value)) {
457
+ if (!is_integer($value)) {
458
+ $error_params[] = $param;
459
+ }
460
+ }
461
+
462
+ if ($param == 'sender_ip' && !empty($value)) {
463
+ if (!is_string($value)) {
464
+ $error_params[] = $param;
465
+ }
466
+ }
467
+
468
+ if ($param == 'sender_email' && !empty($value)) {
469
+ if (!is_string($value)) {
470
+ $error_params[] = $param;
471
+ }
472
+ }
473
+
474
+ if ($param == 'submit_time' && !empty($value)) {
475
+ if (!is_int($value)) {
476
+ $error_params[] = $param;
477
+ }
478
+ }
479
+ }
480
+
481
+ // special and must be
482
+ switch ($method) {
483
+ case 'check_message':
484
+
485
+ // Convert strings to UTF8
486
+ $this->message = $this->stringToUTF8($request->message);
487
+ $this->example = $this->stringToUTF8($request->example);
488
+
489
+ $request->message = $this->compressData($request->message);
490
+ $request->example = $this->compressData($request->example);
491
+ break;
492
+
493
+ case 'check_newuser':
494
+ if (empty($request->sender_nickname)) {
495
+ $error_params[] = 'sender_nickname';
496
+ }
497
+ if (empty($request->sender_email)) {
498
+ $error_params[] = 'sender_email';
499
+ }
500
+ break;
501
+
502
+ case 'send_feedback':
503
+ if (empty($request->feedback)) {
504
+ $error_params[] = 'feedback';
505
+ }
506
+ break;
507
+ }
508
+
509
+ if (isset($request->sender_info)) {
510
+ if (function_exists('json_decode')){
511
+ $sender_info = json_decode($request->sender_info, true);
512
+
513
+ // Save request's creation timestamps
514
+ if (function_exists('microtime'))
515
+ $sender_info['request_submit_time'] = (float) (time() + (float) microtime());
516
+
517
+ if (function_exists('json_encode')){
518
+ $request->sender_info = json_encode($sender_info);
519
+
520
+ }
521
+ }
522
+ }
523
+
524
+ return $error_params;
525
+ }
526
+
527
+ /**
528
+ * Compress data and encode to base64
529
+ * @param type string
530
+ * @return string
531
+ */
532
+ private function compressData($data = null){
533
+
534
+ if (strlen($data) > $this->dataMaxSise && function_exists('gzencode') && function_exists('base64_encode')){
535
+
536
+ $localData = gzencode($data, $this->compressRate, FORCE_GZIP);
537
+
538
+ if ($localData === false)
539
+ return $data;
540
+
541
+ $localData = base64_encode($localData);
542
+
543
+ if ($localData === false)
544
+ return $data;
545
+
546
+ return $localData;
547
+ }
548
+
549
+ return $data;
550
+ }
551
+
552
+ /**
553
+ * Create msg for cleantalk server
554
+ * @param type $method
555
+ * @param CleantalkRequest $request
556
+ * @return \xmlrpcmsg
557
+ */
558
+ private function createMsg($method, CleantalkRequest $request) {
559
+ switch ($method) {
560
+ case 'check_message':
561
+ $params = array(
562
+ 'message' => $request->message,
563
+ 'base_text' => $request->example,
564
+ 'auth_key' => $request->auth_key,
565
+ 'agent' => $request->agent,
566
+ 'sender_info' => $request->sender_info,
567
+ 'ct_stop_words' => $request->stoplist_check,
568
+ 'response_lang' => $request->response_lang,
569
+ 'session_ip' => $request->sender_ip,
570
+ 'user_email' => $request->sender_email,
571
+ 'user_name' => $request->sender_nickname,
572
+ 'post_info' => $request->post_info,
573
+ 'ct_links' => $request->allow_links,
574
+ 'submit_time' => $request->submit_time,
575
+ 'js_on' => $request->js_on);
576
+ break;
577
+
578
+ case 'check_newuser':
579
+ $params = array(
580
+ 'auth_key' => $request->auth_key,
581
+ 'agent' => $request->agent,
582
+ 'response_lang' => $request->response_lang,
583
+ 'session_ip' => $request->sender_ip,
584
+ 'user_email' => $request->sender_email,
585
+ 'user_name' => $request->sender_nickname,
586
+ 'tz' => $request->tz,
587
+ 'submit_time' => $request->submit_time,
588
+ 'js_on' => $request->js_on,
589
+ 'phone' => $request->phone,
590
+ 'sender_info' => $request->sender_info);
591
+ break;
592
+
593
+ case 'send_feedback':
594
+ if (is_array($request->feedback)) {
595
+ $feedback = implode(';', $request->feedback);
596
+ } else {
597
+ $feedback = $request->feedback;
598
+ }
599
+
600
+ $params = array(
601
+ 'auth_key' => $request->auth_key, // !
602
+ 'feedback' => $feedback);
603
+ break;
604
+ }
605
+
606
+ $xmlvars = array();
607
+ foreach ($params as $param) {
608
+ $xmlvars[] = new xmlrpcval($param);
609
+ }
610
+
611
+ $ct_params = new xmlrpcmsg(
612
+ $method,
613
+ array(new xmlrpcval($xmlvars, "array"))
614
+ );
615
+
616
+ return $ct_params;
617
+ }
618
+
619
+ /**
620
+ * XM-Request
621
+ * @param xmlrpcmsg $msg
622
+ * @return boolean|\CleantalkResponse
623
+ */
624
+ private function xmlRequest(xmlrpcmsg $msg) {
625
+ if (((isset($this->work_url) && $this->work_url !== '') && ($this->server_changed + $this->server_ttl > time()))
626
+ || $this->stay_on_server == true) {
627
+
628
+ $url = (!empty($this->work_url)) ? $this->work_url : $this->server_url;
629
+ $ct_request = new xmlrpc_client($url);
630
+ $ct_request->request_charset_encoding = 'utf-8';
631
+ $ct_request->return_type = 'phpvals';
632
+ $ct_request->setDebug($this->debug);
633
+
634
+ $result = $ct_request->send($msg, $this->server_timeout);
635
+ }
636
+
637
+ if ((!isset($result) || $result->errno != 0) && $this->stay_on_server == false) {
638
+ $matches = array();
639
+ preg_match("#^(http://|https://)([a-z\.\-0-9]+):?(\d*)$#i", $this->server_url, $matches);
640
+ $url_prefix = $matches[1];
641
+ $pool = $matches[2];
642
+ $port = $matches[3];
643
+ if (empty($url_prefix))
644
+ $url_prefix = 'http://';
645
+ if (empty($pool)) {
646
+ return false;
647
+ } else {
648
+ foreach ($this->get_servers_ip($pool) as $server) {
649
+ if ($server['host'] === 'localhost' || $server['ip'] === null) {
650
+ $work_url = $url_prefix . $server['host'];
651
+ } else {
652
+ $server_host = gethostbyaddr($server['ip']);
653
+ $work_url = $url_prefix . $server_host;
654
+ }
655
+
656
+ $work_url = ($port !== '') ? $work_url . ':' . $port : $work_url;
657
+
658
+ $this->work_url = $work_url;
659
+ $this->server_ttl = $server['ttl'];
660
+ $ct_request = new xmlrpc_client($this->work_url);
661
+ $ct_request->request_charset_encoding = 'utf-8';
662
+ $ct_request->return_type = 'phpvals';
663
+ $ct_request->setDebug($this->debug);
664
+ $result = $ct_request->send($msg, $this->server_timeout);
665
+
666
+ if (!$result->faultCode()) {
667
+ $this->server_change = true;
668
+ break;
669
+ }
670
+ }
671
+ }
672
+ }
673
+
674
+ $response = new CleantalkResponse(null, $result);
675
+
676
+ if (!empty($response->sender_id)) {
677
+ $this->setSenderId($response->sender_id);
678
+ }
679
+ return $response;
680
+ }
681
+
682
+ /**
683
+ * Function DNS request
684
+ * @param $host
685
+ * @return array
686
+ */
687
+ public function get_servers_ip($host) {
688
+ $response = null;
689
+ if (!isset($host))
690
+ return $response;
691
+
692
+ if (function_exists('dns_get_record')) {
693
+ $records = dns_get_record($host, DNS_A);
694
+
695
+ if ($records !== FALSE) {
696
+ foreach ($records as $server) {
697
+ $response[] = $server;
698
+ }
699
+ }
700
+ }
701
+
702
+ if (count($response) == 0 && function_exists('gethostbynamel')) {
703
+ $records = gethostbynamel($host);
704
+
705
+ if ($records !== FALSE) {
706
+ foreach ($records as $server) {
707
+ $response[] = array("ip" => $server,
708
+ "host" => $host,
709
+ "ttl" => $this->server_ttl
710
+ );
711
+ }
712
+ }
713
+ }
714
+
715
+ if (count($response) == 0) {
716
+ $response[] = array("ip" => null,
717
+ "host" => $host,
718
+ "ttl" => $this->server_ttl
719
+ );
720
+ } else {
721
+
722
+ // $i - to resolve collisions with localhost and
723
+ $i = 0;
724
+ $r_temp = null;
725
+ foreach ($response as $server) {
726
+ $ping = $this->httpPing($server['ip']);
727
+
728
+ // -1 server is down, skips not reachable server
729
+ if ($ping != -1)
730
+ $r_temp[$ping * 10000 + $i] = $server;
731
+
732
+ $i++;
733
+ }
734
+ if (count($r_temp)){
735
+ ksort($r_temp);
736
+ $response = $r_temp;
737
+ }
738
+ }
739
+
740
+ return $response;
741
+ }
742
+
743
+ /**
744
+ * Function to get the SenderID
745
+ * @return string
746
+ */
747
+ public function getSenderId() {
748
+ return ( isset($_COOKIE['ct_sender_id']) && !empty($_COOKIE['ct_sender_id']) ) ? $_COOKIE['ct_sender_id'] : '';
749
+ }
750
+
751
+ /**
752
+ * Function to change the SenderID
753
+ * @param $senderId
754
+ * @return bool
755
+ */
756
+ private function setSenderId($senderId) {
757
+ return @setcookie('ct_sender_id', $senderId);
758
+ }
759
+
760
+ /**
761
+ * Function to get the message hash from Cleantalk.ru comment
762
+ * @param $message
763
+ * @return null
764
+ */
765
+ public function getCleantalkCommentHash($message) {
766
+ $matches = array();
767
+ if (preg_match('/\n\n\*\*\*.+([a-z0-9]{32}).+\*\*\*$/', $message, $matches))
768
+ return $matches[1];
769
+ else if (preg_match('/\<br.*\>[\n]{0,1}\<br.*\>[\n]{0,1}\*\*\*.+([a-z0-9]{32}).+\*\*\*$/', $message, $matches))
770
+ return $matches[1];
771
+
772
+ return NULL;
773
+ }
774
+
775
+ /**
776
+ * Function adds to the post comment Cleantalk.ru
777
+ * @param $message
778
+ * @param $comment
779
+ * @return string
780
+ */
781
+ public function addCleantalkComment($message, $comment) {
782
+ $comment = preg_match('/\*\*\*(.+)\*\*\*/', $comment, $matches) ? $comment : '*** ' . $comment . ' ***';
783
+ return $message . "\n\n" . $comment;
784
+ }
785
+
786
+ /**
787
+ * Function deletes the comment Cleantalk.ru
788
+ * @param $message
789
+ * @return mixed
790
+ */
791
+ public function delCleantalkComment($message) {
792
+ $message = preg_replace('/\n\n\*\*\*.+\*\*\*$/', '', $message);
793
+ $message = preg_replace('/\<br.*\>[\n]{0,1}\<br.*\>[\n]{0,1}\*\*\*.+\*\*\*$/', '', $message);
794
+ return $message;
795
+ }
796
+
797
+ /*
798
+ Get user IP
799
+ */
800
+ public function ct_session_ip( $data_ip )
801
+ {
802
+ if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
803
+ {
804
+ $forwarded_for = (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) ? htmlentities($_SERVER['HTTP_X_FORWARDED_FOR']) : '';
805
+ }
806
+
807
+ // 127.0.0.1 usually used at reverse proxy
808
+ $session_ip = ($data_ip == '127.0.0.1' && !empty($forwarded_for)) ? $forwarded_for : $data_ip;
809
+
810
+ return $session_ip;
811
+ }
812
+
813
+ /**
814
+ * Function to check response time
815
+ * param string
816
+ * @return int
817
+ */
818
+ function httpPing($host){
819
+
820
+ // Skip localhost ping cause it raise error at fsockopen.
821
+ // And return minimun value
822
+ if ($host == 'localhost')
823
+ return 0.001;
824
+
825
+ $starttime = microtime(true);
826
+ $file = @fsockopen ($host, 80, $errno, $errstr, $this->server_timeout);
827
+ $stoptime = microtime(true);
828
+ $status = 0;
829
+
830
+ if (!$file) {
831
+ $status = -1; // Site is down
832
+ } else {
833
+ fclose($file);
834
+ $status = ($stoptime - $starttime);
835
+ $status = round($status, 4);
836
+ }
837
+
838
+ return $status;
839
+ }
840
+
841
+ /**
842
+ * Function convert string to UTF8 and removes non UTF8 characters
843
+ * param string
844
+ * @return string
845
+ */
846
+ function stringToUTF8($str){
847
+
848
+ if (!preg_match('//u', $str) && function_exists('mb_detect_encoding') && function_exists('mb_convert_encoding')) {
849
+ $encoding = mb_detect_encoding($str);
850
+ $srt = mb_convert_encoding($str, 'UTF-8', $encoding);
851
+ }
852
+
853
+ return $str;
854
+ }
855
+ }
856
+
857
+ ?>
cleantalk.php ADDED
@@ -0,0 +1,765 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: CleanTalk. Spam protection
4
+ Plugin URI: http://cleantalk.org/wordpress
5
+ Description: Plugin stops spambots without move spam comments to trash or manual approval queue, invisible for users and admins. Every new comment compares with article and previous comments. If the relevance of the comment is good enough it gets approval at the blog without manual approval.
6
+ Version: 2.4.9
7
+ Author: СleanTalk team <welcome@cleantalk.ru>
8
+ Author URI: http://cleantalk.org
9
+ */
10
+
11
+ add_action('init', 'ct_init_locale');
12
+ add_action('delete_comment', 'ct_delete_comment_meta'); // param - comment ID
13
+ add_action('comment_form', 'ct_add_hidden_fields');
14
+ add_action('parse_request', 'ct_set_session');
15
+ add_action('admin_notices', 'admin_notice_message');
16
+ add_filter('preprocess_comment', 'ct_check'); // param - comment data array
17
+
18
+ if (is_admin()) {
19
+ add_action('admin_init', 'ct_admin_init');
20
+ add_action('admin_menu', 'ct_admin_add_page');
21
+ add_action('admin_enqueue_scripts', 'ct_enqueue_scripts');
22
+ add_action('comment_unapproved_to_approved', 'ct_comment_approved'); // param - comment object
23
+ add_action('comment_approved_to_unapproved', 'ct_comment_unapproved'); // param - comment object
24
+ add_action('comment_unapproved_to_spam', 'ct_comment_spam'); // param - comment object
25
+ add_action('comment_approved_to_spam', 'ct_comment_spam'); // param - comment object
26
+ add_action('trash_comment', 'ct_comment_trash'); // param - comment ID
27
+ add_filter('get_comment_text', 'ct_get_comment_text'); // param - current comment text
28
+ add_filter('unspam_comment', 'ct_unspam_comment');
29
+ }
30
+
31
+ /**
32
+ * Inner function - Current Cleantalk options
33
+ * @return mixed[] Array of options
34
+ */
35
+ function ct_get_options() {
36
+ $options = get_option('cleantalk_settings');
37
+ if (!is_array($options))
38
+ $options = array();
39
+ return array_merge(ct_def_options(), $options);
40
+ }
41
+
42
+ /**
43
+ * Inner function - Default Cleantalk options
44
+ * @return mixed[] Array of default options
45
+ */
46
+ function ct_def_options() {
47
+ $lang = get_bloginfo('language');
48
+ return array(
49
+ 'stopwords' => '1',
50
+ 'allowlinks' => '0',
51
+ 'language' => 'en',
52
+ 'server' => 'http://moderate.cleantalk.ru',
53
+ 'apikey' => __('enter key', 'cleantalk'),
54
+ 'autoPubRevelantMess' => '1'
55
+ );
56
+ }
57
+
58
+ /**
59
+ * Inner function - Stores ang returns cleantalk hash of current comment
60
+ * @param string New hash or NULL
61
+ * @return string New hash or current hash depending on parameter
62
+ */
63
+ function ct_hash($new_hash = '') {
64
+ /**
65
+ * Current hash
66
+ */
67
+ static $hash;
68
+
69
+ if (!empty($new_hash)) {
70
+ $hash = $new_hash;
71
+ }
72
+ return $hash;
73
+ }
74
+
75
+ /**
76
+ * Inner function - Write manual moderation results to PHP sessions
77
+ * @param string $hash Cleantalk comment hash
78
+ * @param string $message comment_content
79
+ * @param int $allow flag good comment (1) or bad (0)
80
+ * @return string comment_content w\o cleantalk resume
81
+ */
82
+ function ct_feedback($hash, $message, $allow) {
83
+ require_once('cleantalk.class.php');
84
+ $options = ct_get_options();
85
+
86
+ $config = get_option('cleantalk_server');
87
+
88
+ $ct = new Cleantalk();
89
+ $ct->work_url = $config['ct_work_url'];
90
+ $ct->server_url = $options['server'];
91
+ $ct->server_ttl = $config['ct_server_ttl'];
92
+ $ct->server_changed = $config['ct_server_changed'];
93
+
94
+ if (empty($hash)) {
95
+ $hash = $ct->getCleantalkCommentHash($message);
96
+ }
97
+ $resultMessage = $ct->delCleantalkComment($message);
98
+
99
+ $ct_feedback = $hash . ':' . $allow . ';';
100
+ if (empty($_SESSION['feedback_request'])) {
101
+ $_SESSION['feedback_request'] = $ct_feedback;
102
+ } else {
103
+ $_SESSION['feedback_request'] .= $ct_feedback;
104
+ }
105
+
106
+ return $resultMessage;
107
+ }
108
+
109
+ /**
110
+ * Inner function - Sends the results of moderation
111
+ * @param string $feedback_request
112
+ * @return bool
113
+ */
114
+ function ct_send_feedback($feedback_request = null) {
115
+
116
+ if (empty($feedback_request) && isset($_SESSION['feedback_request']) && preg_match("/^[a-z0-9\;\:]+$/", $_SESSION['feedback_request'])) {
117
+ $feedback_request = $_SESSION['feedback_request'];
118
+ unset($_SESSION['feedback_request']);
119
+ }
120
+
121
+ if ($feedback_request !== null) {
122
+ require_once('cleantalk.class.php');
123
+ $options = ct_get_options();
124
+
125
+ $config = get_option('cleantalk_server');
126
+
127
+ $ct = new Cleantalk();
128
+ $ct->work_url = $config['ct_work_url'];
129
+ $ct->server_url = $options['server'];
130
+ $ct->server_ttl = $config['ct_server_ttl'];
131
+ $ct->server_changed = $config['ct_server_changed'];
132
+
133
+ $ct_request = new CleantalkRequest();
134
+ $ct_request->auth_key = $options['apikey'];
135
+ $ct_request->feedback = $feedback_request;
136
+
137
+ $ct->sendFeedback($ct_request);
138
+
139
+ if ($ct->server_change) {
140
+ update_option(
141
+ 'cleantalk_server', array(
142
+ 'ct_work_url' => $ct->work_url,
143
+ 'ct_server_ttl' => $ct->server_ttl,
144
+ 'ct_server_changed' => time()
145
+ )
146
+ );
147
+ }
148
+ return true;
149
+ }
150
+
151
+ return false;
152
+ }
153
+
154
+ /**
155
+ * Public action 'init' - Inits locale
156
+ */
157
+ function ct_init_locale() {
158
+ load_plugin_textdomain('cleantalk', false, basename(dirname(__FILE__)) . '/i18n');
159
+ if(!session_id()) {
160
+ session_name('cleantalksession');
161
+ session_start();
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Public action 'delete_comment' - Deletes comment's meta before deleting comment
167
+ * @param int $post_id Post ID, not used
168
+ */
169
+ function ct_delete_comment_meta($comment_id) {
170
+ delete_comment_meta($comment_id, 'ct_hash');
171
+ }
172
+
173
+ /**
174
+ * Public action 'comment_form' - Adds hidden filed to define avaialbility of client's JavaScript
175
+ * @param int $post_id Post ID, not used
176
+ */
177
+ function ct_add_hidden_fields($post_id = 0) {
178
+ $ct_checkjs_def = 0;
179
+ $ct_checkjs_key = ct_get_checkjs_value();
180
+
181
+ if (ct_is_user_enable() === false) {
182
+ return false;
183
+ }
184
+ ?>
185
+ <input type="hidden" id="ct_checkjs" name="ct_checkjs" value="0">
186
+ <script type="text/javascript">
187
+ // <![CDATA[
188
+ document.getElementById("ct_checkjs").value = document.getElementById("ct_checkjs").value.replace("<?php echo $ct_checkjs_def ?>", "<?php echo $ct_checkjs_key ?>");
189
+ // ]]>
190
+ </script>
191
+ <?php
192
+ }
193
+
194
+ /**
195
+ * Public action 'parse_request' - Inits session value
196
+ */
197
+ function ct_set_session() {
198
+ if (ct_is_user_enable() === false) {
199
+ return false;
200
+ }
201
+ // this action is called any time WP process GET request (shows any page)
202
+ // this action is called AFTER wp-comments-post.php executing and AFTER ct_check calling so we can create new session value here
203
+ // it can be any action between init and send_headers, see http://codex.wordpress.org/Plugin_API/Action_Reference
204
+
205
+ $_SESSION['formtime'] = time();
206
+ }
207
+
208
+ /**
209
+ * Get user role
210
+ * @global type $current_user
211
+ * @return type
212
+ */
213
+ function ct_get_user_role() {
214
+ global $current_user;
215
+
216
+ $user_roles = $current_user->roles;
217
+ $user_role = array_shift($user_roles);
218
+
219
+ return $user_role;
220
+ }
221
+
222
+ /**
223
+ * Is enable for user group
224
+ * @return boolean
225
+ */
226
+ function ct_is_user_enable() {
227
+ $disable_roles = array('administrator', 'editor', 'author');
228
+ $user_role = ct_get_user_role();
229
+ if (in_array($user_role, $disable_roles)) {
230
+ return false;
231
+ }
232
+ return true;
233
+ }
234
+ /**
235
+ * Public filter 'preprocess_comment' - Checks comment by cleantalk server
236
+ * @param mixed[] $comment Comment data array
237
+ * @return mixed[] New data array of comment
238
+ */
239
+ function ct_check($comment) {
240
+ // this action is called just when WP process POST request (adds new comment)
241
+ // this action is called by wp-comments-post.php
242
+ // after processing WP makes redirect to post page with comment's form by GET request (see above)
243
+ global $wpdb, $current_user, $comment_post_id;
244
+
245
+ if (ct_is_user_enable() === false) {
246
+ return $comment;
247
+ }
248
+ $options = ct_get_options();
249
+
250
+ $comment_post_id = $comment['comment_post_ID'];
251
+
252
+ $post = get_post($comment_post_id);
253
+
254
+ $checkjs = null;
255
+ if (!isset($_POST['ct_checkjs'])) {
256
+ $checkjs = null;
257
+ } elseif($_POST['ct_checkjs'] == ct_get_checkjs_value()) {
258
+ $checkjs = 1;
259
+ } elseif ($_POST['ct_checkjs'] !== 0) {
260
+ $checkjs = 0;
261
+ }
262
+
263
+ if (array_key_exists('formtime', $_SESSION)) {
264
+ $submit_time = time() - (int) $_SESSION['formtime'];
265
+ } else {
266
+ $submit_time = null;
267
+ }
268
+
269
+ $example = null;
270
+ if (function_exists('json_encode')) {
271
+ $blog_lang = substr(get_locale(), 0, 2);
272
+ $user_info = array(
273
+ 'cms_lang' => $blog_lang,
274
+ 'REFFERRER' => @$_SERVER['HTTP_REFERER'],
275
+ 'USER_AGENT' => @$_SERVER['HTTP_USER_AGENT'],
276
+ 'sender_url' => $comment['comment_author_url'],
277
+ );
278
+
279
+ $user_info = json_encode($user_info);
280
+ if ($user_info === false)
281
+ $user_info = '';
282
+
283
+ $post_info['comment_type'] = $comment['comment_type'];
284
+ $post_info['post_url'] = ct_post_url(null, $comment_post_id);
285
+
286
+ $post_info = json_encode($post_info);
287
+ if ($post_info === false)
288
+ $post_info = '';
289
+
290
+ if ($post !== null){
291
+ $example['title'] = $post->post_title;
292
+ $example['body'] = $post->post_content;
293
+ $example['comments'] = null;
294
+
295
+ $last_comments = get_comments(array('status' => 'approve', 'number' >= 10, 'post_id' => $comment_post_id));
296
+ foreach ($last_comments as $post_comment){
297
+ $example['comments'] .= "\n\n" . $post_comment->comment_content;
298
+ }
299
+
300
+ $example = json_encode($example);
301
+ }
302
+ }
303
+
304
+ // Use plain string format if've failed with JSON
305
+ if ($example === false || $example === null){
306
+ $example = ($post->post_title !== null) ? $post->post_title : '';
307
+ $example .= ($post->post_content !== null) ? "\n\n" . $post->post_content : '';
308
+ }
309
+
310
+ require_once('cleantalk.class.php');
311
+
312
+ $config = get_option('cleantalk_server');
313
+
314
+ $ct = new Cleantalk();
315
+ $ct->work_url = $config['ct_work_url'];
316
+ $ct->server_url = $options['server'];
317
+ $ct->server_ttl = $config['ct_server_ttl'];
318
+ $ct->server_changed = $config['ct_server_changed'];
319
+
320
+ $ct_request = new CleantalkRequest();
321
+
322
+ $ct_request->auth_key = $options['apikey'];
323
+ $ct_request->message = $comment['comment_content'];
324
+ $ct_request->example = $example;
325
+ $ct_request->sender_email = $comment['comment_author_email'];
326
+ $ct_request->sender_nickname = $comment['comment_author'];
327
+ $ct_request->sender_ip = $ct->ct_session_ip($_SERVER['REMOTE_ADDR']);
328
+ $ct_request->agent = 'wordpress-249';
329
+ $ct_request->sender_info = $user_info;
330
+ $ct_request->stoplist_check = $options['stopwords'];
331
+ $ct_request->response_lang = $options['language'];
332
+ $ct_request->allow_links = $options['allowlinks'];
333
+ $ct_request->submit_time = $submit_time;
334
+ $ct_request->js_on = $checkjs;
335
+ $ct_request->post_info = $post_info;
336
+
337
+ $ct_result = $ct->isAllowMessage($ct_request);
338
+ if ($ct->server_change) {
339
+ update_option(
340
+ 'cleantalk_server', array(
341
+ 'ct_work_url' => $ct->work_url,
342
+ 'ct_server_ttl' => $ct->server_ttl,
343
+ 'ct_server_changed' => time()
344
+ )
345
+ );
346
+ }
347
+
348
+ if ($ct_result->stop_queue == 1) {
349
+ $err_text = '<center><b style="color: #49C73B;">Clean</b><b style="color: #349ebf;">Talk.</b> ' . __('Spam protection', 'cleantalk') . "</center><br><br>\n" . $ct_result->comment;
350
+ $err_text .= '<script>setTimeout("history.back()", 5000);</script>';
351
+ wp_die($err_text, 'Blacklisted', array('back_link' => true));
352
+ } else {
353
+ ct_hash($ct_result->id);
354
+ if ($ct_result->allow == 1 && $options['autoPubRevelantMess'] == 1) {
355
+ add_filter('pre_comment_approved', 'ct_set_approved');
356
+ } elseif($ct_result->allow == 0) {
357
+ if (!empty($ct_result->stop_words)) {
358
+ global $ct_stop_words;
359
+ $ct_stop_words = $ct_result->stop_words;
360
+ add_action('comment_post', 'ct_mark_red', 11, 2);
361
+ }
362
+ $comment['comment_content'] = $ct->addCleantalkComment($comment['comment_content'], $ct_result->comment);
363
+ if ($ct_result->spam == 1) {
364
+ add_filter('pre_comment_approved', 'ct_set_comment_spam');
365
+ global $ct_comment;
366
+ $ct_comment = $ct_result->comment;
367
+ add_action('comment_post', 'ct_die', 12, 2);
368
+ } else {
369
+ add_filter('pre_comment_approved', 'ct_set_not_approved');
370
+ }
371
+ }
372
+
373
+ add_action('comment_post', 'ct_set_meta', 10, 2);
374
+ }
375
+ return $comment;
376
+ }
377
+
378
+ /**
379
+ * Get post url
380
+ * @param int $comment_id
381
+ * @param int $comment_post_id
382
+ * @return string|bool
383
+ */
384
+ function ct_post_url($comment_id = null, $comment_post_id) {
385
+
386
+ if (empty($comment_post_id))
387
+ return null;
388
+
389
+ if ($comment_id === null) {
390
+ $last_comment = get_comments('number=1');
391
+ $comment_id = isset($last_comment[0]->comment_ID) ? (int) $last_comment[0]->comment_ID + 1 : 1;
392
+ }
393
+ $permalink = get_permalink($comment_post_id);
394
+
395
+ $post_url = null;
396
+ if ($permalink !== null)
397
+ $post_url = $permalink . '#comment-' . $comment_id;
398
+
399
+ return $post_url;
400
+ }
401
+
402
+ /**
403
+ * Set die page with Cleantalk comment.
404
+ * @global type $ct_comment
405
+ $err_text = '<center><b style="color: #49C73B;">Clean</b><b style="color: #349ebf;">Talk.</b> ' . __('Spam protection', 'cleantalk') . "</center><br><br>\n" . $ct_comment;
406
+ * @param type $comment_status
407
+ */
408
+ function ct_die($comment_id, $comment_status) {
409
+ global $ct_comment;
410
+ $err_text = '<center><b style="color: #49C73B;">Clean</b><b style="color: #349ebf;">Talk.</b> ' . __('Spam protection', 'cleantalk') . "</center><br><br>\n" . $ct_comment;
411
+ $err_text .= '<script>setTimeout("history.back()", 5000);</script>';
412
+ wp_die($err_text, 'Blacklisted', array('back_link' => true));
413
+ }
414
+ /**
415
+ * Public filter 'pre_comment_approved' - Mark comment unapproved always
416
+ * @return int Zero
417
+ */
418
+ function ct_set_not_approved() {
419
+ return 0;
420
+ }
421
+
422
+ /**
423
+ * @author Artem Leontiev
424
+ * Public filter 'pre_comment_approved' - Mark comment approved always
425
+ * @return int 1
426
+ */
427
+ function ct_set_approved() {
428
+ return 1;
429
+ }
430
+
431
+ /**
432
+ * Public filter 'pre_comment_approved' - Mark comment unapproved always
433
+ * @return int Zero
434
+ */
435
+ function ct_set_comment_spam() {
436
+ return 'spam';
437
+ }
438
+
439
+ /**
440
+ * Public action 'comment_post' - Store cleantalk hash in comment meta 'ct_hash'
441
+ * @param int $comment_id Comment ID
442
+ * @param mixed $comment_status Approval status ("spam", or 0/1), not used
443
+ */
444
+ function ct_set_meta($comment_id, $comment_status) {
445
+ /* update_comment_meta($comment_id, 'ct_hash', 'huz');
446
+ return; */
447
+
448
+ global $comment_post_id;
449
+
450
+ $hash1 = ct_hash();
451
+ if (!empty($hash1)) {
452
+ update_comment_meta($comment_id, 'ct_hash', $hash1);
453
+
454
+ if (function_exists('base64_encode')) {
455
+ $post_url = ct_post_url($comment_id, $comment_post_id);
456
+ $post_url = base64_encode($post_url);
457
+ if ($post_url === false)
458
+ return false;
459
+
460
+ // 01 - URL to approved comment
461
+ $feedback_request = $hash1 . ':' . '01' . ':' . $post_url . ';';
462
+ ct_send_feedback($feedback_request);
463
+ }
464
+ }
465
+
466
+ return true;
467
+ }
468
+
469
+ /**
470
+ * Admin action 'comment_unapproved_to_approved' - Approve comment, sends good feedback to cleantalk, removes cleantalk resume
471
+ * @param object $comment_object Comment object
472
+ * @return boolean TRUE
473
+ */
474
+ function ct_comment_approved($comment_object) {
475
+ $comment = get_comment($comment_object->comment_ID, 'ARRAY_A');
476
+ $hash = get_comment_meta($comment_object->comment_ID, 'ct_hash', true);
477
+ $comment['comment_content'] = ct_unmark_red($comment['comment_content']);
478
+ $comment['comment_content'] = ct_feedback($hash, $comment['comment_content'], 1);
479
+ $comment['comment_approved'] = 1;
480
+ wp_update_comment($comment);
481
+ return true;
482
+ }
483
+
484
+ /**
485
+ * Admin action 'comment_approved_to_unapproved' - Unapprove comment, sends bad feedback to cleantalk
486
+ * @param object $comment_object Comment object
487
+ * @return boolean TRUE
488
+ */
489
+ function ct_comment_unapproved($comment_object) {
490
+ $comment = get_comment($comment_object->comment_ID, 'ARRAY_A');
491
+ $hash = get_comment_meta($comment_object->comment_ID, 'ct_hash', true);
492
+ ct_feedback($hash, $comment['comment_content'], 0);
493
+ $comment['comment_approved'] = 0;
494
+ wp_update_comment($comment);
495
+ return true;
496
+ }
497
+
498
+ /**
499
+ * Admin actions 'comment_unapproved_to_spam', 'comment_approved_to_spam' - Mark comment as spam, sends bad feedback to cleantalk
500
+ * @param object $comment_object Comment object
501
+ * @return boolean TRUE
502
+ */
503
+ function ct_comment_spam($comment_object) {
504
+ $comment = get_comment($comment_object->comment_ID, 'ARRAY_A');
505
+ $hash = get_comment_meta($comment_object->comment_ID, 'ct_hash', true);
506
+ ct_feedback($hash, $comment['comment_content'], 0);
507
+ $comment['comment_approved'] = 'spam';
508
+ wp_update_comment($comment);
509
+ return true;
510
+ }
511
+
512
+
513
+ /**
514
+ * Unspam comment
515
+ * @param type $comment_id
516
+ */
517
+ function ct_unspam_comment($comment_id) {
518
+ update_comment_meta($comment_id, '_wp_trash_meta_status', 1);
519
+ $comment = get_comment($comment_id, 'ARRAY_A');
520
+ $hash = get_comment_meta($comment_id, 'ct_hash', true);
521
+ $comment['comment_content'] = ct_unmark_red($comment['comment_content']);
522
+ $comment['comment_content'] = ct_feedback($hash, $comment['comment_content'], 1);
523
+ wp_update_comment($comment);
524
+ }
525
+
526
+ /**
527
+ * Admin action 'trash_comment' - Sends bad feedback to cleantalk
528
+ * @param int $comment_id Comment ID
529
+ * @return boolean TRUE
530
+ */
531
+ function ct_comment_trash($comment_id) {
532
+ $comment = get_comment($comment_id, 'ARRAY_A');
533
+ $hash = get_comment_meta($comment_id, 'ct_hash', true);
534
+ ct_feedback($hash, $comment['comment_content'], 0);
535
+ return true;
536
+ }
537
+
538
+ /**
539
+ * Admin filter 'get_comment_text' - Adds some info to comment text to display
540
+ * @param string $current_text Current comment text
541
+ * @return string New comment text
542
+ */
543
+ function ct_get_comment_text($current_text) {
544
+ global $comment;
545
+ $new_text = $current_text;
546
+ if (isset($comment) && is_object($comment)) {
547
+ $hash = get_comment_meta($comment->comment_ID, 'ct_hash', true);
548
+ if (!empty($hash)) {
549
+ $new_text .= '<hr>Cleantalk ID = ' . $hash;
550
+ }
551
+ }
552
+ return $new_text;
553
+ }
554
+
555
+ /**
556
+ * Admin action 'admin_enqueue_scripts' - Enqueue admin script of reloading admin page after needed AJAX events
557
+ * @param string $hook URL of hooked page
558
+ */
559
+ function ct_enqueue_scripts($hook) {
560
+ if ($hook == 'edit-comments.php')
561
+ wp_enqueue_script('ct_reload_script', plugins_url('/cleantalk-rel.js', __FILE__));
562
+ }
563
+
564
+ /**
565
+ * Admin action 'admin_menu' - Add the admin options page
566
+ */
567
+ function ct_admin_add_page() {
568
+ add_options_page(__('CleanTalk settings', 'cleantalk'), '<b style="color: #49C73B;">Clean</b><b style="color: #349ebf;">Talk</b>. Spam protection', 'manage_options', 'cleantalk', 'ct_settings_page');
569
+ }
570
+
571
+ /**
572
+ * Admin action 'admin_init' - Add the admin settings and such
573
+ */
574
+ function ct_admin_init() {
575
+ register_setting('cleantalk_settings', 'cleantalk_settings', 'ct_settings_validate');
576
+ add_settings_section('cleantalk_settings_main', __('Main settings', 'cleantalk'), 'ct_section_settings_main', 'cleantalk');
577
+ add_settings_field('cleantalk_stopwords', __('Stop words checking', 'cleantalk'), 'ct_input_stopwords', 'cleantalk', 'cleantalk_settings_main');
578
+ add_settings_field('cleantalk_language', __('System messages language', 'cleantalk'), 'ct_input_language', 'cleantalk', 'cleantalk_settings_main');
579
+ add_settings_field('cleantalk_autoPubRevelantMess', __('Publicate relevant comments', 'cleantalk'), 'ct_input_autoPubRevelantMess', 'cleantalk', 'cleantalk_settings_main');
580
+ add_settings_field('cleantalk_apikey', __('Access key', 'cleantalk'), 'ct_input_apikey', 'cleantalk', 'cleantalk_settings_main');
581
+ }
582
+
583
+ /**
584
+ * Admin callback function - Displays description of 'main' plugin parameters section
585
+ */
586
+ function ct_section_settings_main() {
587
+ }
588
+
589
+ /**
590
+ * Admin callback function - Displays inputs of 'stopwords' plugin parameter
591
+ */
592
+ function ct_input_stopwords() {
593
+ $options = ct_get_options();
594
+ $value = $options['stopwords'];
595
+ echo "<input type='radio' id='cleantalk_stopwords0' name='cleantalk_settings[stopwords]' value='0' " . ($value == '0' ? 'checked' : '') . " /><label for='cleantalk_stopwords0'> " . __('No') . "</label>";
596
+ echo '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
597
+ echo "<input type='radio' id='cleantalk_stopwords1' name='cleantalk_settings[stopwords]' value='1' " . ($value == '1' ? 'checked' : '') . " /><label for='cleantalk_stopwords1'> " . __('Yes') . "</label>";
598
+ admin_addDescriptionsFields(__('Comments with rude and swear words will be moved to manual moderation.', 'cleantalk'));
599
+ }
600
+
601
+ /**
602
+ * @author Artem Leontiev
603
+ * Admin callback function - Displays inputs of 'Publicate relevant comments' plugin parameter
604
+ *
605
+ * @return null
606
+ */
607
+ function ct_input_autoPubRevelantMess () {
608
+ $options = ct_get_options();
609
+ $value = $options['autoPubRevelantMess'];
610
+ echo "<input type='radio' id='cleantalk_autoPubRevelantMess0' name='cleantalk_settings[autoPubRevelantMess]' value='0' " . ($value == '0' ? 'checked' : '') . " /><label for='cleantalk_autoPubRevelantMess0'> " . __('No') . "</label>";
611
+ echo '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
612
+ echo "<input type='radio' id='cleantalk_autoPubRevelantMess1' name='cleantalk_settings[autoPubRevelantMess]' value='1' " . ($value == '1' ? 'checked' : '') . " /><label for='cleantalk_autoPubRevelantMess1'> " . __('Yes') . "</label>";
613
+ admin_addDescriptionsFields(__('Relevant comments will be automatic publicated at the blog.', 'cleantalk'));
614
+ }
615
+
616
+ /**
617
+ * Admin callback function - Displays inputs of 'allowlinks' plugin parameter
618
+ */
619
+ function ct_input_allowlinks() {
620
+ $options = ct_get_options();
621
+ $value = $options['allowlinks'];
622
+ echo "<input type='radio' id='cleantalk_allowlinks0' name='cleantalk_settings[allowlinks]' value='0' " . ($value == '0' ? 'checked' : '') . " /><label for='cleantalk_allowlinks0'> " . __('No') . "</label>";
623
+ echo '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
624
+ echo "<input type='radio' id='cleantalk_allowlinks1' name='cleantalk_settings[allowlinks]' value='1' " . ($value == '1' ? 'checked' : '') . " /><label for='cleantalk_allowlinks1'> " . __('Yes') . "</label>";
625
+ }
626
+
627
+ /**
628
+ * Admin callback function - Displays inputs of 'language' plugin parameter
629
+ */
630
+ function ct_input_language() {
631
+ $options = ct_get_options();
632
+ $value = $options['language'];
633
+ echo "<input type='radio' id='cleantalk_language0' name='cleantalk_settings[language]' value='en' " . ($value == 'en' ? 'checked' : '') . " /><label for='cleantalk_language0'> " . __('English', 'cleantalk') . "</label>";
634
+ echo '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
635
+ echo "<input type='radio' id='cleantalk_language1' name='cleantalk_settings[language]' value='ru' " . ($value == 'ru' ? 'checked' : '') . " /><label for='cleantalk_language1'> " . __('Russian', 'cleantalk') . "</label>";
636
+ admin_addDescriptionsFields(__('This language will be used to notice blogs owner and commentators.', 'cleantalk'));
637
+ }
638
+
639
+ /**
640
+ * Admin callback function - Displays inputs of 'apikey' plugin parameter
641
+ */
642
+ function ct_input_apikey() {
643
+ $options = ct_get_options();
644
+ $def_options = __('enter key', 'cleantalk');
645
+ $value = $options['apikey'];
646
+ $def_value = $def_options['apikey'];
647
+ echo "<input id='cleantalk_apikey' name='cleantalk_settings[apikey]' size='10' type='text' value='$value' onfocus=\"if(this.value=='$def_value') this.value='';\"/>";
648
+ echo "<a target='__blank' style='margin-left: 10px' href='http://cleantalk.org/install/wordpress?step=2'>".__('Click here to get access key', 'cleantalk')."</a>";
649
+ }
650
+
651
+ /**
652
+ * Admin callback function - Plugin parameters validator
653
+ */
654
+ function ct_settings_validate($input) {
655
+ return $input;
656
+ }
657
+
658
+
659
+ /**
660
+ * Admin callback function - Displays plugin options page
661
+ */
662
+ function ct_settings_page() {
663
+ ?>
664
+ <div>
665
+ <h2><b style="color: #49C73B;">Clean</b><b style="color: #349ebf;">Talk</b>. Spam protection
666
+
667
+ </h2>
668
+ <form action="options.php" method="post">
669
+ <?php settings_fields('cleantalk_settings'); ?>
670
+ <?php do_settings_sections('cleantalk'); ?>
671
+ <br>
672
+ <br>
673
+ <br>
674
+ <input name="Submit" type="submit" value="<?php esc_attr_e('Save Changes'); ?>" />
675
+ </form></div>
676
+
677
+ <?php
678
+ }
679
+
680
+ /**
681
+ * Mark bad words
682
+ * @global string $ct_stop_words
683
+ * @param int $comment_id
684
+ * @param int $comment_status Not use
685
+ */
686
+ function ct_mark_red($comment_id, $comment_status) {
687
+ global $ct_stop_words;
688
+
689
+ $comment = get_comment($comment_id, 'ARRAY_A');
690
+ $message = $comment['comment_content'];
691
+ foreach (explode(':', $ct_stop_words) as $word) {
692
+ $message = preg_replace("/($word)/ui", '<font rel="cleantalk" color="#FF1000">' . "$1" . '</font>', $message);
693
+
694
+ }
695
+ $comment['comment_content'] = $message;
696
+ kses_remove_filters();
697
+ wp_update_comment($comment);
698
+ }
699
+
700
+ /**
701
+ * Unmark bad words
702
+ * @param string $message
703
+ * @return string Cleat comment
704
+ */
705
+ function ct_unmark_red($message) {
706
+ $message = preg_replace("/\<font rel\=\"cleantalk\" color\=\"\#FF1000\"\>(\S+)\<\/font>/iu", '$1', $message);
707
+
708
+ return $message;
709
+ }
710
+
711
+ /**
712
+ * Notice blog owner if plugin using without Access key
713
+ * @return bool
714
+ */
715
+ function admin_notice_message(){
716
+
717
+ if (ct_active() === false)
718
+ return false;
719
+
720
+ $options = ct_get_options();
721
+ if ($options['apikey'] === 'enter key' || $options['apikey'] === '')
722
+ echo '<div class="updated"><p>' . __("Please enter the Access Key in <a href=\"options-general.php?page=cleantalk\">CleanTalk plugin</a> settings to enable protection from spam in comments!", 'cleantalk') . '</p></div>';
723
+
724
+ ct_send_feedback();
725
+
726
+ return true;
727
+ }
728
+
729
+ /**
730
+ * @author Artem Leontiev
731
+ *
732
+ * Add descriptions for field
733
+ */
734
+ function admin_addDescriptionsFields($descr = '') {
735
+ echo "<p style='color: #666 !important'>$descr</p>";
736
+ }
737
+
738
+ /**
739
+ * Tests plugin activation status
740
+ * @return bool
741
+ */
742
+
743
+ function ct_active(){
744
+ $ct_active = false;
745
+ foreach (get_option('active_plugins') as $k => $v) {
746
+ if (preg_match("/cleantalk.php$/", $v))
747
+ $ct_active = true;
748
+ }
749
+
750
+ return $ct_active;
751
+ }
752
+
753
+ /**
754
+ * Get ct_get_checkjs_value
755
+ * @return string
756
+ */
757
+ function ct_get_checkjs_value() {
758
+ $options = ct_get_options();
759
+
760
+ return md5($options['apikey'] . '+' . get_settings('admin_email'));
761
+ }
762
+
763
+
764
+
765
+ ?>
cleantalk.xmlrpc.php ADDED
@@ -0,0 +1,3777 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // by Edd Dumbill (C) 1999-2002
3
+ // <edd@usefulinc.com>
4
+ // $Id: xmlrpc.inc,v 1.174 2009/03/16 19:36:38 ggiunta Exp $
5
+
6
+ // Copyright (c) 1999,2000,2002 Edd Dumbill.
7
+ // All rights reserved.
8
+ //
9
+ // Redistribution and use in source and binary forms, with or without
10
+ // modification, are permitted provided that the following conditions
11
+ // are met:
12
+ //
13
+ // * Redistributions of source code must retain the above copyright
14
+ // notice, this list of conditions and the following disclaimer.
15
+ //
16
+ // * Redistributions in binary form must reproduce the above
17
+ // copyright notice, this list of conditions and the following
18
+ // disclaimer in the documentation and/or other materials provided
19
+ // with the distribution.
20
+ //
21
+ // * Neither the name of the "XML-RPC for PHP" nor the names of its
22
+ // contributors may be used to endorse or promote products derived
23
+ // from this software without specific prior written permission.
24
+ //
25
+ // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26
+ // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27
+ // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28
+ // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29
+ // REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30
+ // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31
+ // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32
+ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33
+ // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34
+ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35
+ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36
+ // OF THE POSSIBILITY OF SUCH DAMAGE.
37
+
38
+ if(!function_exists('xml_parser_create'))
39
+ {
40
+ // For PHP 4 onward, XML functionality is always compiled-in on windows:
41
+ // no more need to dl-open it. It might have been compiled out on *nix...
42
+ if(strtoupper(substr(PHP_OS, 0, 3) != 'WIN'))
43
+ {
44
+ dl('xml.so');
45
+ }
46
+ }
47
+
48
+ // G. Giunta 2005/01/29: declare global these variables,
49
+ // so that xmlrpc.inc will work even if included from within a function
50
+ // Milosch: 2005/08/07 - explicitly request these via $GLOBALS where used.
51
+ $GLOBALS['xmlrpcI4']='i4';
52
+ $GLOBALS['xmlrpcInt']='int';
53
+ $GLOBALS['xmlrpcBoolean']='boolean';
54
+ $GLOBALS['xmlrpcDouble']='double';
55
+ $GLOBALS['xmlrpcString']='string';
56
+ $GLOBALS['xmlrpcDateTime']='dateTime.iso8601';
57
+ $GLOBALS['xmlrpcBase64']='base64';
58
+ $GLOBALS['xmlrpcArray']='array';
59
+ $GLOBALS['xmlrpcStruct']='struct';
60
+ $GLOBALS['xmlrpcValue']='undefined';
61
+
62
+ $GLOBALS['xmlrpcTypes']=array(
63
+ $GLOBALS['xmlrpcI4'] => 1,
64
+ $GLOBALS['xmlrpcInt'] => 1,
65
+ $GLOBALS['xmlrpcBoolean'] => 1,
66
+ $GLOBALS['xmlrpcString'] => 1,
67
+ $GLOBALS['xmlrpcDouble'] => 1,
68
+ $GLOBALS['xmlrpcDateTime'] => 1,
69
+ $GLOBALS['xmlrpcBase64'] => 1,
70
+ $GLOBALS['xmlrpcArray'] => 2,
71
+ $GLOBALS['xmlrpcStruct'] => 3
72
+ );
73
+
74
+ $GLOBALS['xmlrpc_valid_parents'] = array(
75
+ 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),
76
+ 'BOOLEAN' => array('VALUE'),
77
+ 'I4' => array('VALUE'),
78
+ 'INT' => array('VALUE'),
79
+ 'STRING' => array('VALUE'),
80
+ 'DOUBLE' => array('VALUE'),
81
+ 'DATETIME.ISO8601' => array('VALUE'),
82
+ 'BASE64' => array('VALUE'),
83
+ 'MEMBER' => array('STRUCT'),
84
+ 'NAME' => array('MEMBER'),
85
+ 'DATA' => array('ARRAY'),
86
+ 'ARRAY' => array('VALUE'),
87
+ 'STRUCT' => array('VALUE'),
88
+ 'PARAM' => array('PARAMS'),
89
+ 'METHODNAME' => array('METHODCALL'),
90
+ 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'),
91
+ 'FAULT' => array('METHODRESPONSE'),
92
+ 'NIL' => array('VALUE'), // only used when extension activated
93
+ 'EX:NIL' => array('VALUE') // only used when extension activated
94
+ );
95
+
96
+ // define extra types for supporting NULL (useful for json or <NIL/>)
97
+ $GLOBALS['xmlrpcNull']='null';
98
+ $GLOBALS['xmlrpcTypes']['null']=1;
99
+
100
+ // Not in use anymore since 2.0. Shall we remove it?
101
+ /// @deprecated
102
+ $GLOBALS['xmlEntities']=array(
103
+ 'amp' => '&',
104
+ 'quot' => '"',
105
+ 'lt' => '<',
106
+ 'gt' => '>',
107
+ 'apos' => "'"
108
+ );
109
+
110
+ // tables used for transcoding different charsets into us-ascii xml
111
+
112
+ $GLOBALS['xml_iso88591_Entities']=array();
113
+ $GLOBALS['xml_iso88591_Entities']['in'] = array();
114
+ $GLOBALS['xml_iso88591_Entities']['out'] = array();
115
+ for ($i = 0; $i < 32; $i++)
116
+ {
117
+ $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
118
+ $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';';
119
+ }
120
+ for ($i = 160; $i < 256; $i++)
121
+ {
122
+ $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
123
+ $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';';
124
+ }
125
+
126
+ /// @todo add to iso table the characters from cp_1252 range, i.e. 128 to 159?
127
+ /// These will NOT be present in true ISO-8859-1, but will save the unwary
128
+ /// windows user from sending junk (though no luck when reciving them...)
129
+ /*
130
+ $GLOBALS['xml_cp1252_Entities']=array();
131
+ for ($i = 128; $i < 160; $i++)
132
+ {
133
+ $GLOBALS['xml_cp1252_Entities']['in'][] = chr($i);
134
+ }
135
+ $GLOBALS['xml_cp1252_Entities']['out'] = array(
136
+ '&#x20AC;', '?', '&#x201A;', '&#x0192;',
137
+ '&#x201E;', '&#x2026;', '&#x2020;', '&#x2021;',
138
+ '&#x02C6;', '&#x2030;', '&#x0160;', '&#x2039;',
139
+ '&#x0152;', '?', '&#x017D;', '?',
140
+ '?', '&#x2018;', '&#x2019;', '&#x201C;',
141
+ '&#x201D;', '&#x2022;', '&#x2013;', '&#x2014;',
142
+ '&#x02DC;', '&#x2122;', '&#x0161;', '&#x203A;',
143
+ '&#x0153;', '?', '&#x017E;', '&#x0178;'
144
+ );
145
+ */
146
+
147
+ $GLOBALS['xmlrpcerr'] = array(
148
+ 'unknown_method'=>1,
149
+ 'invalid_return'=>2,
150
+ 'incorrect_params'=>3,
151
+ 'introspect_unknown'=>4,
152
+ 'http_error'=>5,
153
+ 'no_data'=>6,
154
+ 'no_ssl'=>7,
155
+ 'curl_fail'=>8,
156
+ 'invalid_request'=>15,
157
+ 'no_curl'=>16,
158
+ 'server_error'=>17,
159
+ 'multicall_error'=>18,
160
+ 'multicall_notstruct'=>9,
161
+ 'multicall_nomethod'=>10,
162
+ 'multicall_notstring'=>11,
163
+ 'multicall_recursion'=>12,
164
+ 'multicall_noparams'=>13,
165
+ 'multicall_notarray'=>14,
166
+
167
+ 'cannot_decompress'=>103,
168
+ 'decompress_fail'=>104,
169
+ 'dechunk_fail'=>105,
170
+ 'server_cannot_decompress'=>106,
171
+ 'server_decompress_fail'=>107
172
+ );
173
+
174
+ $GLOBALS['xmlrpcstr'] = array(
175
+ 'unknown_method'=>'Unknown method',
176
+ 'invalid_return'=>'Invalid return payload: enable debugging to examine incoming payload',
177
+ 'incorrect_params'=>'Incorrect parameters passed to method',
178
+ 'introspect_unknown'=>"Can't introspect: method unknown",
179
+ 'http_error'=>"Didn't receive 200 OK from remote server.",
180
+ 'no_data'=>'No data received from server.',
181
+ 'no_ssl'=>'No SSL support compiled in.',
182
+ 'curl_fail'=>'CURL error',
183
+ 'invalid_request'=>'Invalid request payload',
184
+ 'no_curl'=>'No CURL support compiled in.',
185
+ 'server_error'=>'Internal server error',
186
+ 'multicall_error'=>'Received from server invalid multicall response',
187
+ 'multicall_notstruct'=>'system.multicall expected struct',
188
+ 'multicall_nomethod'=>'missing methodName',
189
+ 'multicall_notstring'=>'methodName is not a string',
190
+ 'multicall_recursion'=>'recursive system.multicall forbidden',
191
+ 'multicall_noparams'=>'missing params',
192
+ 'multicall_notarray'=>'params is not an array',
193
+
194
+ 'cannot_decompress'=>'Received from server compressed HTTP and cannot decompress',
195
+ 'decompress_fail'=>'Received from server invalid compressed HTTP',
196
+ 'dechunk_fail'=>'Received from server invalid chunked HTTP',
197
+ 'server_cannot_decompress'=>'Received from client compressed HTTP request and cannot decompress',
198
+ 'server_decompress_fail'=>'Received from client invalid compressed HTTP request'
199
+ );
200
+
201
+ // The charset encoding used by the server for received messages and
202
+ // by the client for received responses when received charset cannot be determined
203
+ // or is not supported
204
+ $GLOBALS['xmlrpc_defencoding']='UTF-8';
205
+
206
+ // The encoding used internally by PHP.
207
+ // String values received as xml will be converted to this, and php strings will be converted to xml
208
+ // as if having been coded with this
209
+ // $GLOBALS['xmlrpc_internalencoding']='ISO-8859-1';
210
+ $GLOBALS['xmlrpc_internalencoding']='UTF-8'; // by Denis Shagimuratov
211
+
212
+ $GLOBALS['xmlrpcName']='XML-RPC for PHP';
213
+ $GLOBALS['xmlrpcVersion']='3.0.0.beta';
214
+
215
+ // let user errors start at 800
216
+ $GLOBALS['xmlrpcerruser']=800;
217
+ // let XML parse errors start at 100
218
+ $GLOBALS['xmlrpcerrxml']=100;
219
+
220
+ // formulate backslashes for escaping regexp
221
+ // Not in use anymore since 2.0. Shall we remove it?
222
+ /// @deprecated
223
+ $GLOBALS['xmlrpc_backslash']=chr(92).chr(92);
224
+
225
+ // set to TRUE to enable correct decoding of <NIL/> and <EX:NIL/> values
226
+ $GLOBALS['xmlrpc_null_extension']=false;
227
+
228
+ // set to TRUE to enable encoding of php NULL values to <EX:NIL/> instead of <NIL/>
229
+ $GLOBALS['xmlrpc_null_apache_encoding']=false;
230
+
231
+ // used to store state during parsing
232
+ // quick explanation of components:
233
+ // ac - used to accumulate values
234
+ // isf - used to indicate a parsing fault (2) or xmlrpcresp fault (1)
235
+ // isf_reason - used for storing xmlrpcresp fault string
236
+ // lv - used to indicate "looking for a value": implements
237
+ // the logic to allow values with no types to be strings
238
+ // params - used to store parameters in method calls
239
+ // method - used to store method name
240
+ // stack - array with genealogy of xml elements names:
241
+ // used to validate nesting of xmlrpc elements
242
+ $GLOBALS['_xh']=null;
243
+
244
+ /**
245
+ * Convert a string to the correct XML representation in a target charset
246
+ * To help correct communication of non-ascii chars inside strings, regardless
247
+ * of the charset used when sending requests, parsing them, sending responses
248
+ * and parsing responses, an option is to convert all non-ascii chars present in the message
249
+ * into their equivalent 'charset entity'. Charset entities enumerated this way
250
+ * are independent of the charset encoding used to transmit them, and all XML
251
+ * parsers are bound to understand them.
252
+ * Note that in the std case we are not sending a charset encoding mime type
253
+ * along with http headers, so we are bound by RFC 3023 to emit strict us-ascii.
254
+ *
255
+ * @todo do a bit of basic benchmarking (strtr vs. str_replace)
256
+ * @todo make usage of iconv() or recode_string() or mb_string() where available
257
+ */
258
+ function xmlrpc_encode_entitites($data, $src_encoding='', $dest_encoding='')
259
+ {
260
+ if ($src_encoding == '')
261
+ {
262
+ // lame, but we know no better...
263
+ $src_encoding = $GLOBALS['xmlrpc_internalencoding'];
264
+ }
265
+
266
+ switch(strtoupper($src_encoding.'_'.$dest_encoding))
267
+ {
268
+ case 'ISO-8859-1_':
269
+ case 'ISO-8859-1_US-ASCII':
270
+ $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
271
+ $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
272
+ break;
273
+ case 'ISO-8859-1_UTF-8':
274
+ $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
275
+ $escaped_data = utf8_encode($escaped_data);
276
+ break;
277
+ case 'ISO-8859-1_ISO-8859-1':
278
+ case 'US-ASCII_US-ASCII':
279
+ case 'US-ASCII_UTF-8':
280
+ case 'US-ASCII_':
281
+ case 'US-ASCII_ISO-8859-1':
282
+ case 'UTF-8_UTF-8':
283
+ //case 'CP1252_CP1252':
284
+ $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
285
+ break;
286
+ case 'UTF-8_':
287
+ case 'UTF-8_US-ASCII':
288
+ case 'UTF-8_ISO-8859-1':
289
+ // NB: this will choke on invalid UTF-8, going most likely beyond EOF
290
+ $escaped_data = '';
291
+ // be kind to users creating string xmlrpcvals out of different php types
292
+ $data = (string) $data;
293
+ $ns = strlen ($data);
294
+ for ($nn = 0; $nn < $ns; $nn++)
295
+ {
296
+ $ch = $data[$nn];
297
+ $ii = ord($ch);
298
+ //1 7 0bbbbbbb (127)
299
+ if ($ii < 128)
300
+ {
301
+ /// @todo shall we replace this with a (supposedly) faster str_replace?
302
+ switch($ii){
303
+ case 34:
304
+ $escaped_data .= '&quot;';
305
+ break;
306
+ case 38:
307
+ $escaped_data .= '&amp;';
308
+ break;
309
+ case 39:
310
+ $escaped_data .= '&apos;';
311
+ break;
312
+ case 60:
313
+ $escaped_data .= '&lt;';
314
+ break;
315
+ case 62:
316
+ $escaped_data .= '&gt;';
317
+ break;
318
+ default:
319
+ $escaped_data .= $ch;
320
+ } // switch
321
+ }
322
+ //2 11 110bbbbb 10bbbbbb (2047)
323
+ else if ($ii>>5 == 6)
324
+ {
325
+ $b1 = ($ii & 31);
326
+ $ii = ord($data[$nn+1]);
327
+ $b2 = ($ii & 63);
328
+ $ii = ($b1 * 64) + $b2;
329
+ $ent = sprintf ('&#%d;', $ii);
330
+ $escaped_data .= $ent;
331
+ $nn += 1;
332
+ }
333
+ //3 16 1110bbbb 10bbbbbb 10bbbbbb
334
+ else if ($ii>>4 == 14)
335
+ {
336
+ $b1 = ($ii & 15);
337
+ $ii = ord($data[$nn+1]);
338
+ $b2 = ($ii & 63);
339
+ $ii = ord($data[$nn+2]);
340
+ $b3 = ($ii & 63);
341
+ $ii = ((($b1 * 64) + $b2) * 64) + $b3;
342
+ $ent = sprintf ('&#%d;', $ii);
343
+ $escaped_data .= $ent;
344
+ $nn += 2;
345
+ }
346
+ //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
347
+ else if ($ii>>3 == 30)
348
+ {
349
+ $b1 = ($ii & 7);
350
+ $ii = ord($data[$nn+1]);
351
+ $b2 = ($ii & 63);
352
+ $ii = ord($data[$nn+2]);
353
+ $b3 = ($ii & 63);
354
+ $ii = ord($data[$nn+3]);
355
+ $b4 = ($ii & 63);
356
+ $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;
357
+ $ent = sprintf ('&#%d;', $ii);
358
+ $escaped_data .= $ent;
359
+ $nn += 3;
360
+ }
361
+ }
362
+ break;
363
+ /*
364
+ case 'CP1252_':
365
+ case 'CP1252_US-ASCII':
366
+ $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
367
+ $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
368
+ $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
369
+ break;
370
+ case 'CP1252_UTF-8':
371
+ $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
372
+ /// @todo we could use real UTF8 chars here instead of xml entities... (note that utf_8 encode all allone will NOT convert them)
373
+ $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
374
+ $escaped_data = utf8_encode($escaped_data);
375
+ break;
376
+ case 'CP1252_ISO-8859-1':
377
+ $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
378
+ // we might as well replave all funky chars with a '?' here, but we are kind and leave it to the receiving application layer to decide what to do with these weird entities...
379
+ $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
380
+ break;
381
+ */
382
+ default:
383
+ $escaped_data = '';
384
+ error_log("Converting from $src_encoding to $dest_encoding: not supported...");
385
+ }
386
+ return $escaped_data;
387
+ }
388
+
389
+ /// xml parser handler function for opening element tags
390
+ function xmlrpc_se($parser, $name, $attrs, $accept_single_vals=false)
391
+ {
392
+ // if invalid xmlrpc already detected, skip all processing
393
+ if ($GLOBALS['_xh']['isf'] < 2)
394
+ {
395
+ // check for correct element nesting
396
+ // top level element can only be of 2 types
397
+ /// @todo optimization creep: save this check into a bool variable, instead of using count() every time:
398
+ /// there is only a single top level element in xml anyway
399
+ if (count($GLOBALS['_xh']['stack']) == 0)
400
+ {
401
+ if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && (
402
+ $name != 'VALUE' && !$accept_single_vals))
403
+ {
404
+ $GLOBALS['_xh']['isf'] = 2;
405
+ $GLOBALS['_xh']['isf_reason'] = 'missing top level xmlrpc element';
406
+ return;
407
+ }
408
+ else
409
+ {
410
+ $GLOBALS['_xh']['rt'] = strtolower($name);
411
+ $GLOBALS['_xh']['rt'] = strtolower($name);
412
+ }
413
+ }
414
+ else
415
+ {
416
+ // not top level element: see if parent is OK
417
+ $parent = end($GLOBALS['_xh']['stack']);
418
+ if (!array_key_exists($name, $GLOBALS['xmlrpc_valid_parents']) || !in_array($parent, $GLOBALS['xmlrpc_valid_parents'][$name]))
419
+ {
420
+ $GLOBALS['_xh']['isf'] = 2;
421
+ $GLOBALS['_xh']['isf_reason'] = "xmlrpc element $name cannot be child of $parent";
422
+ return;
423
+ }
424
+ }
425
+
426
+ switch($name)
427
+ {
428
+ // optimize for speed switch cases: most common cases first
429
+ case 'VALUE':
430
+ /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element
431
+ $GLOBALS['_xh']['vt']='value'; // indicator: no value found yet
432
+ $GLOBALS['_xh']['ac']='';
433
+ $GLOBALS['_xh']['lv']=1;
434
+ $GLOBALS['_xh']['php_class']=null;
435
+ break;
436
+ case 'I4':
437
+ case 'INT':
438
+ case 'STRING':
439
+ case 'BOOLEAN':
440
+ case 'DOUBLE':
441
+ case 'DATETIME.ISO8601':
442
+ case 'BASE64':
443
+ if ($GLOBALS['_xh']['vt']!='value')
444
+ {
445
+ //two data elements inside a value: an error occurred!
446
+ $GLOBALS['_xh']['isf'] = 2;
447
+ $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
448
+ return;
449
+ }
450
+ $GLOBALS['_xh']['ac']=''; // reset the accumulator
451
+ break;
452
+ case 'STRUCT':
453
+ case 'ARRAY':
454
+ if ($GLOBALS['_xh']['vt']!='value')
455
+ {
456
+ //two data elements inside a value: an error occurred!
457
+ $GLOBALS['_xh']['isf'] = 2;
458
+ $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
459
+ return;
460
+ }
461
+ // create an empty array to hold child values, and push it onto appropriate stack
462
+ $cur_val = array();
463
+ $cur_val['values'] = array();
464
+ $cur_val['type'] = $name;
465
+ // check for out-of-band information to rebuild php objs
466
+ // and in case it is found, save it
467
+ if (@isset($attrs['PHP_CLASS']))
468
+ {
469
+ $cur_val['php_class'] = $attrs['PHP_CLASS'];
470
+ }
471
+ $GLOBALS['_xh']['valuestack'][] = $cur_val;
472
+ $GLOBALS['_xh']['vt']='data'; // be prepared for a data element next
473
+ break;
474
+ case 'DATA':
475
+ if ($GLOBALS['_xh']['vt']!='data')
476
+ {
477
+ //two data elements inside a value: an error occurred!
478
+ $GLOBALS['_xh']['isf'] = 2;
479
+ $GLOBALS['_xh']['isf_reason'] = "found two data elements inside an array element";
480
+ return;
481
+ }
482
+ case 'METHODCALL':
483
+ case 'METHODRESPONSE':
484
+ case 'PARAMS':
485
+ // valid elements that add little to processing
486
+ break;
487
+ case 'METHODNAME':
488
+ case 'NAME':
489
+ /// @todo we could check for 2 NAME elements inside a MEMBER element
490
+ $GLOBALS['_xh']['ac']='';
491
+ break;
492
+ case 'FAULT':
493
+ $GLOBALS['_xh']['isf']=1;
494
+ break;
495
+ case 'MEMBER':
496
+ $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name']=''; // set member name to null, in case we do not find in the xml later on
497
+ //$GLOBALS['_xh']['ac']='';
498
+ // Drop trough intentionally
499
+ case 'PARAM':
500
+ // clear value type, so we can check later if no value has been passed for this param/member
501
+ $GLOBALS['_xh']['vt']=null;
502
+ break;
503
+ case 'NIL':
504
+ case 'EX:NIL':
505
+ if ($GLOBALS['xmlrpc_null_extension'])
506
+ {
507
+ if ($GLOBALS['_xh']['vt']!='value')
508
+ {
509
+ //two data elements inside a value: an error occurred!
510
+ $GLOBALS['_xh']['isf'] = 2;
511
+ $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
512
+ return;
513
+ }
514
+ $GLOBALS['_xh']['ac']=''; // reset the accumulator
515
+ break;
516
+ }
517
+ // we do not support the <NIL/> extension, so
518
+ // drop through intentionally
519
+ default:
520
+ /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
521
+ $GLOBALS['_xh']['isf'] = 2;
522
+ $GLOBALS['_xh']['isf_reason'] = "found not-xmlrpc xml element $name";
523
+ break;
524
+ }
525
+
526
+ // Save current element name to stack, to validate nesting
527
+ $GLOBALS['_xh']['stack'][] = $name;
528
+
529
+ /// @todo optimization creep: move this inside the big switch() above
530
+ if($name!='VALUE')
531
+ {
532
+ $GLOBALS['_xh']['lv']=0;
533
+ }
534
+ }
535
+ }
536
+
537
+ /// Used in decoding xml chunks that might represent single xmlrpc values
538
+ function xmlrpc_se_any($parser, $name, $attrs)
539
+ {
540
+ xmlrpc_se($parser, $name, $attrs, true);
541
+ }
542
+
543
+ /// xml parser handler function for close element tags
544
+ function xmlrpc_ee($parser, $name, $rebuild_xmlrpcvals = true)
545
+ {
546
+ if ($GLOBALS['_xh']['isf'] < 2)
547
+ {
548
+ // push this element name from stack
549
+ // NB: if XML validates, correct opening/closing is guaranteed and
550
+ // we do not have to check for $name == $curr_elem.
551
+ // we also checked for proper nesting at start of elements...
552
+ $curr_elem = array_pop($GLOBALS['_xh']['stack']);
553
+
554
+ switch($name)
555
+ {
556
+ case 'VALUE':
557
+ // This if() detects if no scalar was inside <VALUE></VALUE>
558
+ if ($GLOBALS['_xh']['vt']=='value')
559
+ {
560
+ $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
561
+ $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcString'];
562
+ }
563
+
564
+ if ($rebuild_xmlrpcvals)
565
+ {
566
+ // build the xmlrpc val out of the data received, and substitute it
567
+ $temp = new xmlrpcval($GLOBALS['_xh']['value'], $GLOBALS['_xh']['vt']);
568
+ // in case we got info about underlying php class, save it
569
+ // in the object we're rebuilding
570
+ if (isset($GLOBALS['_xh']['php_class']))
571
+ $temp->_php_class = $GLOBALS['_xh']['php_class'];
572
+ // check if we are inside an array or struct:
573
+ // if value just built is inside an array, let's move it into array on the stack
574
+ $vscount = count($GLOBALS['_xh']['valuestack']);
575
+ if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
576
+ {
577
+ $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $temp;
578
+ }
579
+ else
580
+ {
581
+ $GLOBALS['_xh']['value'] = $temp;
582
+ }
583
+ }
584
+ else
585
+ {
586
+ /// @todo this needs to treat correctly php-serialized objects,
587
+ /// since std deserializing is done by php_xmlrpc_decode,
588
+ /// which we will not be calling...
589
+ if (isset($GLOBALS['_xh']['php_class']))
590
+ {
591
+ }
592
+
593
+ // check if we are inside an array or struct:
594
+ // if value just built is inside an array, let's move it into array on the stack
595
+ $vscount = count($GLOBALS['_xh']['valuestack']);
596
+ if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
597
+ {
598
+ $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $GLOBALS['_xh']['value'];
599
+ }
600
+ }
601
+ break;
602
+ case 'BOOLEAN':
603
+ case 'I4':
604
+ case 'INT':
605
+ case 'STRING':
606
+ case 'DOUBLE':
607
+ case 'DATETIME.ISO8601':
608
+ case 'BASE64':
609
+ $GLOBALS['_xh']['vt']=strtolower($name);
610
+ /// @todo: optimization creep - remove the if/elseif cycle below
611
+ /// since the case() in which we are already did that
612
+ if ($name=='STRING')
613
+ {
614
+ $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
615
+ }
616
+ elseif ($name=='DATETIME.ISO8601')
617
+ {
618
+ if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $GLOBALS['_xh']['ac']))
619
+ {
620
+ error_log('XML-RPC: invalid value received in DATETIME: '.$GLOBALS['_xh']['ac']);
621
+ }
622
+ $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcDateTime'];
623
+ $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
624
+ }
625
+ elseif ($name=='BASE64')
626
+ {
627
+ /// @todo check for failure of base64 decoding / catch warnings
628
+ $GLOBALS['_xh']['value']=base64_decode($GLOBALS['_xh']['ac']);
629
+ }
630
+ elseif ($name=='BOOLEAN')
631
+ {
632
+ // special case here: we translate boolean 1 or 0 into PHP
633
+ // constants true or false.
634
+ // Strings 'true' and 'false' are accepted, even though the
635
+ // spec never mentions them (see eg. Blogger api docs)
636
+ // NB: this simple checks helps a lot sanitizing input, ie no
637
+ // security problems around here
638
+ if ($GLOBALS['_xh']['ac']=='1' || strcasecmp($GLOBALS['_xh']['ac'], 'true') == 0)
639
+ {
640
+ $GLOBALS['_xh']['value']=true;
641
+ }
642
+ else
643
+ {
644
+ // log if receiveing something strange, even though we set the value to false anyway
645
+ if ($GLOBALS['_xh']['ac']!='0' && strcasecmp($GLOBALS['_xh']['ac'], 'false') != 0)
646
+ error_log('XML-RPC: invalid value received in BOOLEAN: '.$GLOBALS['_xh']['ac']);
647
+ $GLOBALS['_xh']['value']=false;
648
+ }
649
+ }
650
+ elseif ($name=='DOUBLE')
651
+ {
652
+ // we have a DOUBLE
653
+ // we must check that only 0123456789-.<space> are characters here
654
+ // NOTE: regexp could be much stricter than this...
655
+ if (!preg_match('/^[+-eE0123456789 \t.]+$/', $GLOBALS['_xh']['ac']))
656
+ {
657
+ /// @todo: find a better way of throwing an error than this!
658
+ error_log('XML-RPC: non numeric value received in DOUBLE: '.$GLOBALS['_xh']['ac']);
659
+ $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
660
+ }
661
+ else
662
+ {
663
+ // it's ok, add it on
664
+ $GLOBALS['_xh']['value']=(double)$GLOBALS['_xh']['ac'];
665
+ }
666
+ }
667
+ else
668
+ {
669
+ // we have an I4/INT
670
+ // we must check that only 0123456789-<space> are characters here
671
+ if (!preg_match('/^[+-]?[0123456789 \t]+$/', $GLOBALS['_xh']['ac']))
672
+ {
673
+ /// @todo find a better way of throwing an error than this!
674
+ error_log('XML-RPC: non numeric value received in INT: '.$GLOBALS['_xh']['ac']);
675
+ $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
676
+ }
677
+ else
678
+ {
679
+ // it's ok, add it on
680
+ $GLOBALS['_xh']['value']=(int)$GLOBALS['_xh']['ac'];
681
+ }
682
+ }
683
+ //$GLOBALS['_xh']['ac']=''; // is this necessary?
684
+ $GLOBALS['_xh']['lv']=3; // indicate we've found a value
685
+ break;
686
+ case 'NAME':
687
+ $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name'] = $GLOBALS['_xh']['ac'];
688
+ break;
689
+ case 'MEMBER':
690
+ //$GLOBALS['_xh']['ac']=''; // is this necessary?
691
+ // add to array in the stack the last element built,
692
+ // unless no VALUE was found
693
+ if ($GLOBALS['_xh']['vt'])
694
+ {
695
+ $vscount = count($GLOBALS['_xh']['valuestack']);
696
+ $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][$GLOBALS['_xh']['valuestack'][$vscount-1]['name']] = $GLOBALS['_xh']['value'];
697
+ } else
698
+ error_log('XML-RPC: missing VALUE inside STRUCT in received xml');
699
+ break;
700
+ case 'DATA':
701
+ //$GLOBALS['_xh']['ac']=''; // is this necessary?
702
+ $GLOBALS['_xh']['vt']=null; // reset this to check for 2 data elements in a row - even if they're empty
703
+ break;
704
+ case 'STRUCT':
705
+ case 'ARRAY':
706
+ // fetch out of stack array of values, and promote it to current value
707
+ $curr_val = array_pop($GLOBALS['_xh']['valuestack']);
708
+ $GLOBALS['_xh']['value'] = $curr_val['values'];
709
+ $GLOBALS['_xh']['vt']=strtolower($name);
710
+ if (isset($curr_val['php_class']))
711
+ {
712
+ $GLOBALS['_xh']['php_class'] = $curr_val['php_class'];
713
+ }
714
+ break;
715
+ case 'PARAM':
716
+ // add to array of params the current value,
717
+ // unless no VALUE was found
718
+ if ($GLOBALS['_xh']['vt'])
719
+ {
720
+ $GLOBALS['_xh']['params'][]=$GLOBALS['_xh']['value'];
721
+ $GLOBALS['_xh']['pt'][]=$GLOBALS['_xh']['vt'];
722
+ }
723
+ else
724
+ error_log('XML-RPC: missing VALUE inside PARAM in received xml');
725
+ break;
726
+ case 'METHODNAME':
727
+ $GLOBALS['_xh']['method']=preg_replace('/^[\n\r\t ]+/', '', $GLOBALS['_xh']['ac']);
728
+ break;
729
+ case 'NIL':
730
+ case 'EX:NIL':
731
+ if ($GLOBALS['xmlrpc_null_extension'])
732
+ {
733
+ $GLOBALS['_xh']['vt']='null';
734
+ $GLOBALS['_xh']['value']=null;
735
+ $GLOBALS['_xh']['lv']=3;
736
+ break;
737
+ }
738
+ // drop through intentionally if nil extension not enabled
739
+ case 'PARAMS':
740
+ case 'FAULT':
741
+ case 'METHODCALL':
742
+ case 'METHORESPONSE':
743
+ break;
744
+ default:
745
+ // End of INVALID ELEMENT!
746
+ // shall we add an assert here for unreachable code???
747
+ break;
748
+ }
749
+ }
750
+ }
751
+
752
+ /// Used in decoding xmlrpc requests/responses without rebuilding xmlrpc values
753
+ function xmlrpc_ee_fast($parser, $name)
754
+ {
755
+ xmlrpc_ee($parser, $name, false);
756
+ }
757
+
758
+ /// xml parser handler function for character data
759
+ function xmlrpc_cd($parser, $data)
760
+ {
761
+ // skip processing if xml fault already detected
762
+ if ($GLOBALS['_xh']['isf'] < 2)
763
+ {
764
+ // "lookforvalue==3" means that we've found an entire value
765
+ // and should discard any further character data
766
+ if($GLOBALS['_xh']['lv']!=3)
767
+ {
768
+ // G. Giunta 2006-08-23: useless change of 'lv' from 1 to 2
769
+ //if($GLOBALS['_xh']['lv']==1)
770
+ //{
771
+ // if we've found text and we're just in a <value> then
772
+ // say we've found a value
773
+ //$GLOBALS['_xh']['lv']=2;
774
+ //}
775
+ // we always initialize the accumulator before starting parsing, anyway...
776
+ //if(!@isset($GLOBALS['_xh']['ac']))
777
+ //{
778
+ // $GLOBALS['_xh']['ac'] = '';
779
+ //}
780
+ $GLOBALS['_xh']['ac'].=$data;
781
+ }
782
+ }
783
+ }
784
+
785
+ /// xml parser handler function for 'other stuff', ie. not char data or
786
+ /// element start/end tag. In fact it only gets called on unknown entities...
787
+ function xmlrpc_dh($parser, $data)
788
+ {
789
+ // skip processing if xml fault already detected
790
+ if ($GLOBALS['_xh']['isf'] < 2)
791
+ {
792
+ if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';')
793
+ {
794
+ // G. Giunta 2006-08-25: useless change of 'lv' from 1 to 2
795
+ //if($GLOBALS['_xh']['lv']==1)
796
+ //{
797
+ // $GLOBALS['_xh']['lv']=2;
798
+ //}
799
+ $GLOBALS['_xh']['ac'].=$data;
800
+ }
801
+ }
802
+ return true;
803
+ }
804
+
805
+ class xmlrpc_client
806
+ {
807
+ var $path;
808
+ var $server;
809
+ var $port=0;
810
+ var $method='http';
811
+ var $errno;
812
+ var $errstr;
813
+ var $debug=0;
814
+ var $username='';
815
+ var $password='';
816
+ var $authtype=1;
817
+ var $cert='';
818
+ var $certpass='';
819
+ var $cacert='';
820
+ var $cacertdir='';
821
+ var $key='';
822
+ var $keypass='';
823
+ var $verifypeer=true;
824
+ var $verifyhost=1;
825
+ var $no_multicall=false;
826
+ var $proxy='';
827
+ var $proxyport=0;
828
+ var $proxy_user='';
829
+ var $proxy_pass='';
830
+ var $proxy_authtype=1;
831
+ var $cookies=array();
832
+ var $extracurlopts=array();
833
+
834
+ /**
835
+ * List of http compression methods accepted by the client for responses.
836
+ * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
837
+ *
838
+ * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since
839
+ * in those cases it will be up to CURL to decide the compression methods
840
+ * it supports. You might check for the presence of 'zlib' in the output of
841
+ * curl_version() to determine wheter compression is supported or not
842
+ */
843
+ var $accepted_compression = array();
844
+ /**
845
+ * Name of compression scheme to be used for sending requests.
846
+ * Either null, gzip or deflate
847
+ */
848
+ var $request_compression = '';
849
+ /**
850
+ * CURL handle: used for keep-alive connections (PHP 4.3.8 up, see:
851
+ * http://curl.haxx.se/docs/faq.html#7.3)
852
+ */
853
+ var $xmlrpc_curl_handle = null;
854
+ /// Wheter to use persistent connections for http 1.1 and https
855
+ var $keepalive = false;
856
+ /// Charset encodings that can be decoded without problems by the client
857
+ var $accepted_charset_encodings = array();
858
+ /// Charset encoding to be used in serializing request. NULL = use ASCII
859
+ var $request_charset_encoding = '';
860
+ /**
861
+ * Decides the content of xmlrpcresp objects returned by calls to send()
862
+ * valid strings are 'xmlrpcvals', 'phpvals' or 'xml'
863
+ */
864
+ var $return_type = 'xmlrpcvals';
865
+ /**
866
+ * Sent to servers in http headers
867
+ */
868
+ var $user_agent;
869
+
870
+ /**
871
+ * @param string $path either the complete server URL or the PATH part of the xmlrc server URL, e.g. /xmlrpc/server.php
872
+ * @param string $server the server name / ip address
873
+ * @param integer $port the port the server is listening on, defaults to 80 or 443 depending on protocol used
874
+ * @param string $method the http protocol variant: defaults to 'http', 'https' and 'http11' can be used if CURL is installed
875
+ */
876
+ function xmlrpc_client($path, $server='', $port='', $method='')
877
+ {
878
+ // allow user to specify all params in $path
879
+ if($server == '' and $port == '' and $method == '')
880
+ {
881
+ $parts = parse_url($path);
882
+ $server = $parts['host'];
883
+ $path = isset($parts['path']) ? $parts['path'] : '';
884
+ if(isset($parts['query']))
885
+ {
886
+ $path .= '?'.$parts['query'];
887
+ }
888
+ if(isset($parts['fragment']))
889
+ {
890
+ $path .= '#'.$parts['fragment'];
891
+ }
892
+ if(isset($parts['port']))
893
+ {
894
+ $port = $parts['port'];
895
+ }
896
+ if(isset($parts['scheme']))
897
+ {
898
+ $method = $parts['scheme'];
899
+ }
900
+ if(isset($parts['user']))
901
+ {
902
+ $this->username = $parts['user'];
903
+ }
904
+ if(isset($parts['pass']))
905
+ {
906
+ $this->password = $parts['pass'];
907
+ }
908
+ }
909
+ if($path == '' || $path[0] != '/')
910
+ {
911
+ $this->path='/'.$path;
912
+ }
913
+ else
914
+ {
915
+ $this->path=$path;
916
+ }
917
+ $this->server=$server;
918
+ if($port != '')
919
+ {
920
+ $this->port=$port;
921
+ }
922
+ if($method != '')
923
+ {
924
+ $this->method=$method;
925
+ }
926
+
927
+ // if ZLIB is enabled, let the client by default accept compressed responses
928
+ if(function_exists('gzinflate') || (
929
+ function_exists('curl_init') && (($info = curl_version()) &&
930
+ ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
931
+ ))
932
+ {
933
+ $this->accepted_compression = array('gzip', 'deflate');
934
+ }
935
+
936
+ // keepalives: enabled by default
937
+ $this->keepalive = true;
938
+
939
+ // by default the xml parser can support these 3 charset encodings
940
+ $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
941
+
942
+ // initialize user_agent string
943
+ $this->user_agent = $GLOBALS['xmlrpcName'] . ' ' . $GLOBALS['xmlrpcVersion'];
944
+ }
945
+
946
+ /**
947
+ * Enables/disables the echoing to screen of the xmlrpc responses received
948
+ * @param integer $debug values 0, 1 and 2 are supported (2 = echo sent msg too, before received response)
949
+ * @access public
950
+ */
951
+ function setDebug($in)
952
+ {
953
+ $this->debug=$in;
954
+ }
955
+
956
+ /**
957
+ * Add some http BASIC AUTH credentials, used by the client to authenticate
958
+ * @param string $u username
959
+ * @param string $p password
960
+ * @param integer $t auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC (basic auth)
961
+ * @access public
962
+ */
963
+ function setCredentials($u, $p, $t=1)
964
+ {
965
+ $this->username=$u;
966
+ $this->password=$p;
967
+ $this->authtype=$t;
968
+ }
969
+
970
+ /**
971
+ * Add a client-side https certificate
972
+ * @param string $cert
973
+ * @param string $certpass
974
+ * @access public
975
+ */
976
+ function setCertificate($cert, $certpass)
977
+ {
978
+ $this->cert = $cert;
979
+ $this->certpass = $certpass;
980
+ }
981
+
982
+ /**
983
+ * Add a CA certificate to verify server with (see man page about
984
+ * CURLOPT_CAINFO for more details
985
+ * @param string $cacert certificate file name (or dir holding certificates)
986
+ * @param bool $is_dir set to true to indicate cacert is a dir. defaults to false
987
+ * @access public
988
+ */
989
+ function setCaCertificate($cacert, $is_dir=false)
990
+ {
991
+ if ($is_dir)
992
+ {
993
+ $this->cacertdir = $cacert;
994
+ }
995
+ else
996
+ {
997
+ $this->cacert = $cacert;
998
+ }
999
+ }
1000
+
1001
+ /**
1002
+ * Set attributes for SSL communication: private SSL key
1003
+ * NB: does not work in older php/curl installs
1004
+ * Thanks to Daniel Convissor
1005
+ * @param string $key The name of a file containing a private SSL key
1006
+ * @param string $keypass The secret password needed to use the private SSL key
1007
+ * @access public
1008
+ */
1009
+ function setKey($key, $keypass)
1010
+ {
1011
+ $this->key = $key;
1012
+ $this->keypass = $keypass;
1013
+ }
1014
+
1015
+ /**
1016
+ * Set attributes for SSL communication: verify server certificate
1017
+ * @param bool $i enable/disable verification of peer certificate
1018
+ * @access public
1019
+ */
1020
+ function setSSLVerifyPeer($i)
1021
+ {
1022
+ $this->verifypeer = $i;
1023
+ }
1024
+
1025
+ /**
1026
+ * Set attributes for SSL communication: verify match of server cert w. hostname
1027
+ * @param int $i
1028
+ * @access public
1029
+ */
1030
+ function setSSLVerifyHost($i)
1031
+ {
1032
+ $this->verifyhost = $i;
1033
+ }
1034
+
1035
+ /**
1036
+ * Set proxy info
1037
+ * @param string $proxyhost
1038
+ * @param string $proxyport Defaults to 8080 for HTTP and 443 for HTTPS
1039
+ * @param string $proxyusername Leave blank if proxy has public access
1040
+ * @param string $proxypassword Leave blank if proxy has public access
1041
+ * @param int $proxyauthtype set to constant CURLAUTH_NTLM to use NTLM auth with proxy
1042
+ * @access public
1043
+ */
1044
+ function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 1)
1045
+ {
1046
+ $this->proxy = $proxyhost;
1047
+ $this->proxyport = $proxyport;
1048
+ $this->proxy_user = $proxyusername;
1049
+ $this->proxy_pass = $proxypassword;
1050
+ $this->proxy_authtype = $proxyauthtype;
1051
+ }
1052
+
1053
+ /**
1054
+ * Enables/disables reception of compressed xmlrpc responses.
1055
+ * Note that enabling reception of compressed responses merely adds some standard
1056
+ * http headers to xmlrpc requests. It is up to the xmlrpc server to return
1057
+ * compressed responses when receiving such requests.
1058
+ * @param string $compmethod either 'gzip', 'deflate', 'any' or ''
1059
+ * @access public
1060
+ */
1061
+ function setAcceptedCompression($compmethod)
1062
+ {
1063
+ if ($compmethod == 'any')
1064
+ $this->accepted_compression = array('gzip', 'deflate');
1065
+ else
1066
+ $this->accepted_compression = array($compmethod);
1067
+ }
1068
+
1069
+ /**
1070
+ * Enables/disables http compression of xmlrpc request.
1071
+ * Take care when sending compressed requests: servers might not support them
1072
+ * (and automatic fallback to uncompressed requests is not yet implemented)
1073
+ * @param string $compmethod either 'gzip', 'deflate' or ''
1074
+ * @access public
1075
+ */
1076
+ function setRequestCompression($compmethod)
1077
+ {
1078
+ $this->request_compression = $compmethod;
1079
+ }
1080
+
1081
+ /**
1082
+ * Adds a cookie to list of cookies that will be sent to server.
1083
+ * NB: setting any param but name and value will turn the cookie into a 'version 1' cookie:
1084
+ * do not do it unless you know what you are doing
1085
+ * @param string $name
1086
+ * @param string $value
1087
+ * @param string $path
1088
+ * @param string $domain
1089
+ * @param int $port
1090
+ * @access public
1091
+ *
1092
+ * @todo check correctness of urlencoding cookie value (copied from php way of doing it...)
1093
+ */
1094
+ function setCookie($name, $value='', $path='', $domain='', $port=null)
1095
+ {
1096
+ $this->cookies[$name]['value'] = urlencode($value);
1097
+ if ($path || $domain || $port)
1098
+ {
1099
+ $this->cookies[$name]['path'] = $path;
1100
+ $this->cookies[$name]['domain'] = $domain;
1101
+ $this->cookies[$name]['port'] = $port;
1102
+ $this->cookies[$name]['version'] = 1;
1103
+ }
1104
+ else
1105
+ {
1106
+ $this->cookies[$name]['version'] = 0;
1107
+ }
1108
+ }
1109
+
1110
+ /**
1111
+ * Directly set cURL options, for extra flexibility
1112
+ * It allows eg. to bind client to a specific IP interface / address
1113
+ * @param $options array
1114
+ */
1115
+ function SetCurlOptions( $options )
1116
+ {
1117
+ $this->extracurlopts = $options;
1118
+ }
1119
+
1120
+ /**
1121
+ * Set user-agent string that will be used by this client instance
1122
+ * in http headers sent to the server
1123
+ */
1124
+ function SetUserAgent( $agentstring )
1125
+ {
1126
+ $this->user_agent = $agentstring;
1127
+ }
1128
+
1129
+ /**
1130
+ * Send an xmlrpc request
1131
+ * @param mixed $msg The message object, or an array of messages for using multicall, or the complete xml representation of a request
1132
+ * @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply
1133
+ * @param string $method if left unspecified, the http protocol chosen during creation of the object will be used
1134
+ * @return xmlrpcresp
1135
+ * @access public
1136
+ */
1137
+ function& send($msg, $timeout=0, $method='')
1138
+ {
1139
+ // if user deos not specify http protocol, use native method of this client
1140
+ // (i.e. method set during call to constructor)
1141
+ if($method == '')
1142
+ {
1143
+ $method = $this->method;
1144
+ }
1145
+
1146
+ if(is_array($msg))
1147
+ {
1148
+ // $msg is an array of xmlrpcmsg's
1149
+ $r = $this->multicall($msg, $timeout, $method);
1150
+ return $r;
1151
+ }
1152
+ elseif(is_string($msg))
1153
+ {
1154
+ $n = new xmlrpcmsg('');
1155
+ $n->payload = $msg;
1156
+ $msg = $n;
1157
+ }
1158
+
1159
+ // where msg is an xmlrpcmsg
1160
+ $msg->debug=$this->debug;
1161
+
1162
+ if($method == 'https')
1163
+ {
1164
+ $r =& $this->sendPayloadHTTPS(
1165
+ $msg,
1166
+ $this->server,
1167
+ $this->port,
1168
+ $timeout,
1169
+ $this->username,
1170
+ $this->password,
1171
+ $this->authtype,
1172
+ $this->cert,
1173
+ $this->certpass,
1174
+ $this->cacert,
1175
+ $this->cacertdir,
1176
+ $this->proxy,
1177
+ $this->proxyport,
1178
+ $this->proxy_user,
1179
+ $this->proxy_pass,
1180
+ $this->proxy_authtype,
1181
+ $this->keepalive,
1182
+ $this->key,
1183
+ $this->keypass
1184
+ );
1185
+ }
1186
+ elseif($method == 'http11')
1187
+ {
1188
+ $r =& $this->sendPayloadCURL(
1189
+ $msg,
1190
+ $this->server,
1191
+ $this->port,
1192
+ $timeout,
1193
+ $this->username,
1194
+ $this->password,
1195
+ $this->authtype,
1196
+ null,
1197
+ null,
1198
+ null,
1199
+ null,
1200
+ $this->proxy,
1201
+ $this->proxyport,
1202
+ $this->proxy_user,
1203
+ $this->proxy_pass,
1204
+ $this->proxy_authtype,
1205
+ 'http',
1206
+ $this->keepalive
1207
+ );
1208
+ }
1209
+ else
1210
+ {
1211
+ $r =& $this->sendPayloadHTTP10(
1212
+ $msg,
1213
+ $this->server,
1214
+ $this->port,
1215
+ $timeout,
1216
+ $this->username,
1217
+ $this->password,
1218
+ $this->authtype,
1219
+ $this->proxy,
1220
+ $this->proxyport,
1221
+ $this->proxy_user,
1222
+ $this->proxy_pass,
1223
+ $this->proxy_authtype
1224
+ );
1225
+ }
1226
+
1227
+ return $r;
1228
+ }
1229
+
1230
+ /**
1231
+ * @access private
1232
+ */
1233
+ function &sendPayloadHTTP10($msg, $server, $port, $timeout=0,
1234
+ $username='', $password='', $authtype=1, $proxyhost='',
1235
+ $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1)
1236
+ {
1237
+ if($port==0)
1238
+ {
1239
+ $port=80;
1240
+ }
1241
+
1242
+ // Only create the payload if it was not created previously
1243
+ if(empty($msg->payload))
1244
+ {
1245
+ $msg->createPayload($this->request_charset_encoding);
1246
+ }
1247
+
1248
+ $payload = $msg->payload;
1249
+ // Deflate request body and set appropriate request headers
1250
+ if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
1251
+ {
1252
+ if($this->request_compression == 'gzip')
1253
+ {
1254
+ $a = @gzencode($payload);
1255
+ if($a)
1256
+ {
1257
+ $payload = $a;
1258
+ $encoding_hdr = "Content-Encoding: gzip\r\n";
1259
+ }
1260
+ }
1261
+ else
1262
+ {
1263
+ $a = @gzcompress($payload);
1264
+ if($a)
1265
+ {
1266
+ $payload = $a;
1267
+ $encoding_hdr = "Content-Encoding: deflate\r\n";
1268
+ }
1269
+ }
1270
+ }
1271
+ else
1272
+ {
1273
+ $encoding_hdr = '';
1274
+ }
1275
+
1276
+ // thanks to Grant Rauscher <grant7@firstworld.net> for this
1277
+ $credentials='';
1278
+ if($username!='')
1279
+ {
1280
+ $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
1281
+ if ($authtype != 1)
1282
+ {
1283
+ error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth is supported with HTTP 1.0');
1284
+ }
1285
+ }
1286
+
1287
+ $accepted_encoding = '';
1288
+ if(is_array($this->accepted_compression) && count($this->accepted_compression))
1289
+ {
1290
+ $accepted_encoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
1291
+ }
1292
+
1293
+ $proxy_credentials = '';
1294
+ if($proxyhost)
1295
+ {
1296
+ if($proxyport == 0)
1297
+ {
1298
+ $proxyport = 8080;
1299
+ }
1300
+ $connectserver = $proxyhost;
1301
+ $connectport = $proxyport;
1302
+ $uri = 'http://'.$server.':'.$port.$this->path;
1303
+ if($proxyusername != '')
1304
+ {
1305
+ if ($proxyauthtype != 1)
1306
+ {
1307
+ error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth to proxy is supported with HTTP 1.0');
1308
+ }
1309
+ $proxy_credentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyusername.':'.$proxypassword) . "\r\n";
1310
+ }
1311
+ }
1312
+ else
1313
+ {
1314
+ $connectserver = $server;
1315
+ $connectport = $port;
1316
+ $uri = $this->path;
1317
+ }
1318
+
1319
+ // Cookie generation, as per rfc2965 (version 1 cookies) or
1320
+ // netscape's rules (version 0 cookies)
1321
+ $cookieheader='';
1322
+ if (count($this->cookies))
1323
+ {
1324
+ $version = '';
1325
+ foreach ($this->cookies as $name => $cookie)
1326
+ {
1327
+ if ($cookie['version'])
1328
+ {
1329
+ $version = ' $Version="' . $cookie['version'] . '";';
1330
+ $cookieheader .= ' ' . $name . '="' . $cookie['value'] . '";';
1331
+ if ($cookie['path'])
1332
+ $cookieheader .= ' $Path="' . $cookie['path'] . '";';
1333
+ if ($cookie['domain'])
1334
+ $cookieheader .= ' $Domain="' . $cookie['domain'] . '";';
1335
+ if ($cookie['port'])
1336
+ $cookieheader .= ' $Port="' . $cookie['port'] . '";';
1337
+ }
1338
+ else
1339
+ {
1340
+ $cookieheader .= ' ' . $name . '=' . $cookie['value'] . ";";
1341
+ }
1342
+ }
1343
+ $cookieheader = 'Cookie:' . $version . substr($cookieheader, 0, -1) . "\r\n";
1344
+ }
1345
+
1346
+ $op= 'POST ' . $uri. " HTTP/1.0\r\n" .
1347
+ 'User-Agent: ' . $this->user_agent . "\r\n" .
1348
+ 'Host: '. $server . ':' . $port . "\r\n" .
1349
+ $credentials .
1350
+ $proxy_credentials .
1351
+ $accepted_encoding .
1352
+ $encoding_hdr .
1353
+ 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
1354
+ $cookieheader .
1355
+ 'Content-Type: ' . $msg->content_type . "\r\nContent-Length: " .
1356
+ strlen($payload) . "\r\n\r\n" .
1357
+ $payload;
1358
+
1359
+ if($this->debug > 1)
1360
+ {
1361
+ print "<PRE>\n---SENDING---\n" . htmlentities($op) . "\n---END---\n</PRE>";
1362
+ // let the client see this now in case http times out...
1363
+ flush();
1364
+ }
1365
+
1366
+ if($timeout>0)
1367
+ {
1368
+ $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr, $timeout);
1369
+ }
1370
+ else
1371
+ {
1372
+ $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr);
1373
+ }
1374
+ if($fp)
1375
+ {
1376
+ if($timeout>0 && function_exists('stream_set_timeout'))
1377
+ {
1378
+ stream_set_timeout($fp, $timeout);
1379
+ }
1380
+ }
1381
+ else
1382
+ {
1383
+ $this->errstr='Connect error: '.$this->errstr;
1384
+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr . ' (' . $this->errno . ')');
1385
+ return $r;
1386
+ }
1387
+
1388
+ if(!fputs($fp, $op, strlen($op)))
1389
+ {
1390
+ fclose($fp);
1391
+ $this->errstr='Write error';
1392
+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr);
1393
+ return $r;
1394
+ }
1395
+ else
1396
+ {
1397
+ // reset errno and errstr on succesful socket connection
1398
+ $this->errstr = '';
1399
+ }
1400
+ // G. Giunta 2005/10/24: close socket before parsing.
1401
+ // should yeld slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1402
+ $ipd='';
1403
+ do
1404
+ {
1405
+ // shall we check for $data === FALSE?
1406
+ // as per the manual, it signals an error
1407
+ $ipd.=fread($fp, 32768);
1408
+ } while(!feof($fp));
1409
+ fclose($fp);
1410
+ $r =& $msg->parseResponse($ipd, false, $this->return_type);
1411
+ return $r;
1412
+
1413
+ }
1414
+
1415
+ /**
1416
+ * @access private
1417
+ */
1418
+ function &sendPayloadHTTPS($msg, $server, $port, $timeout=0, $username='',
1419
+ $password='', $authtype=1, $cert='',$certpass='', $cacert='', $cacertdir='',
1420
+ $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1,
1421
+ $keepalive=false, $key='', $keypass='')
1422
+ {
1423
+ $r =& $this->sendPayloadCURL($msg, $server, $port, $timeout, $username,
1424
+ $password, $authtype, $cert, $certpass, $cacert, $cacertdir, $proxyhost, $proxyport,
1425
+ $proxyusername, $proxypassword, $proxyauthtype, 'https', $keepalive, $key, $keypass);
1426
+ return $r;
1427
+ }
1428
+
1429
+ /**
1430
+ * Contributed by Justin Miller <justin@voxel.net>
1431
+ * Requires curl to be built into PHP
1432
+ * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1433
+ * @access private
1434
+ */
1435
+ function &sendPayloadCURL($msg, $server, $port, $timeout=0, $username='',
1436
+ $password='', $authtype=1, $cert='', $certpass='', $cacert='', $cacertdir='',
1437
+ $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, $method='https',
1438
+ $keepalive=false, $key='', $keypass='')
1439
+ {
1440
+ if(!function_exists('curl_init'))
1441
+ {
1442
+ $this->errstr='CURL unavailable on this install';
1443
+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_curl'], $GLOBALS['xmlrpcstr']['no_curl']);
1444
+ return $r;
1445
+ }
1446
+ if($method == 'https')
1447
+ {
1448
+ if(($info = curl_version()) &&
1449
+ ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version']))))
1450
+ {
1451
+ $this->errstr='SSL unavailable on this install';
1452
+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_ssl'], $GLOBALS['xmlrpcstr']['no_ssl']);
1453
+ return $r;
1454
+ }
1455
+ }
1456
+
1457
+ if($port == 0)
1458
+ {
1459
+ if($method == 'http')
1460
+ {
1461
+ $port = 80;
1462
+ }
1463
+ else
1464
+ {
1465
+ $port = 443;
1466
+ }
1467
+ }
1468
+
1469
+ // Only create the payload if it was not created previously
1470
+ if(empty($msg->payload))
1471
+ {
1472
+ $msg->createPayload($this->request_charset_encoding);
1473
+ }
1474
+
1475
+ // Deflate request body and set appropriate request headers
1476
+ $payload = $msg->payload;
1477
+ if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
1478
+ {
1479
+ if($this->request_compression == 'gzip')
1480
+ {
1481
+ $a = @gzencode($payload);
1482
+ if($a)
1483
+ {
1484
+ $payload = $a;
1485
+ $encoding_hdr = 'Content-Encoding: gzip';
1486
+ }
1487
+ }
1488
+ else
1489
+ {
1490
+ $a = @gzcompress($payload);
1491
+ if($a)
1492
+ {
1493
+ $payload = $a;
1494
+ $encoding_hdr = 'Content-Encoding: deflate';
1495
+ }
1496
+ }
1497
+ }
1498
+ else
1499
+ {
1500
+ $encoding_hdr = '';
1501
+ }
1502
+
1503
+ if($this->debug > 1)
1504
+ {
1505
+ print "<PRE>\n---SENDING---\n" . htmlentities($payload) . "\n---END---\n</PRE>";
1506
+ // let the client see this now in case http times out...
1507
+ flush();
1508
+ }
1509
+
1510
+ if(!$keepalive || !$this->xmlrpc_curl_handle)
1511
+ {
1512
+ $curl = curl_init($method . '://' . $server . ':' . $port . $this->path);
1513
+ if($keepalive)
1514
+ {
1515
+ $this->xmlrpc_curl_handle = $curl;
1516
+ }
1517
+ }
1518
+ else
1519
+ {
1520
+ $curl = $this->xmlrpc_curl_handle;
1521
+ }
1522
+
1523
+ // results into variable
1524
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
1525
+
1526
+ if($this->debug)
1527
+ {
1528
+ curl_setopt($curl, CURLOPT_VERBOSE, 1);
1529
+ }
1530
+ curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
1531
+ // required for XMLRPC: post the data
1532
+ curl_setopt($curl, CURLOPT_POST, 1);
1533
+ // the data
1534
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1535
+
1536
+ // return the header too
1537
+ curl_setopt($curl, CURLOPT_HEADER, 1);
1538
+
1539
+ // will only work with PHP >= 5.0
1540
+ // NB: if we set an empty string, CURL will add http header indicating
1541
+ // ALL methods it is supporting. This is possibly a better option than
1542
+ // letting the user tell what curl can / cannot do...
1543
+ if(is_array($this->accepted_compression) && count($this->accepted_compression))
1544
+ {
1545
+ //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
1546
+ // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1547
+ if (count($this->accepted_compression) == 1)
1548
+ {
1549
+ curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
1550
+ }
1551
+ else
1552
+ curl_setopt($curl, CURLOPT_ENCODING, '');
1553
+ }
1554
+ // extra headers
1555
+ $headers = array('Content-Type: ' . $msg->content_type , 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
1556
+ // if no keepalive is wanted, let the server know it in advance
1557
+ if(!$keepalive)
1558
+ {
1559
+ $headers[] = 'Connection: close';
1560
+ }
1561
+ // request compression header
1562
+ if($encoding_hdr)
1563
+ {
1564
+ $headers[] = $encoding_hdr;
1565
+ }
1566
+
1567
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1568
+ // timeout is borked
1569
+ if($timeout)
1570
+ {
1571
+ curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
1572
+ }
1573
+
1574
+ if($username && $password)
1575
+ {
1576
+ curl_setopt($curl, CURLOPT_USERPWD, $username.':'.$password);
1577
+ if (defined('CURLOPT_HTTPAUTH'))
1578
+ {
1579
+ curl_setopt($curl, CURLOPT_HTTPAUTH, $authtype);
1580
+ }
1581
+ else if ($authtype != 1)
1582
+ {
1583
+ error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth is supported by the current PHP/curl install');
1584
+ }
1585
+ }
1586
+
1587
+ if($method == 'https')
1588
+ {
1589
+ // set cert file
1590
+ if($cert)
1591
+ {
1592
+ curl_setopt($curl, CURLOPT_SSLCERT, $cert);
1593
+ }
1594
+ // set cert password
1595
+ if($certpass)
1596
+ {
1597
+ curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certpass);
1598
+ }
1599
+ // whether to verify remote host's cert
1600
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
1601
+ // set ca certificates file/dir
1602
+ if($cacert)
1603
+ {
1604
+ curl_setopt($curl, CURLOPT_CAINFO, $cacert);
1605
+ }
1606
+ if($cacertdir)
1607
+ {
1608
+ curl_setopt($curl, CURLOPT_CAPATH, $cacertdir);
1609
+ }
1610
+ // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1611
+ if($key)
1612
+ {
1613
+ curl_setopt($curl, CURLOPT_SSLKEY, $key);
1614
+ }
1615
+ // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1616
+ if($keypass)
1617
+ {
1618
+ curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keypass);
1619
+ }
1620
+ // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used
1621
+ curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
1622
+ }
1623
+
1624
+ // proxy info
1625
+ if($proxyhost)
1626
+ {
1627
+ if($proxyport == 0)
1628
+ {
1629
+ $proxyport = 8080; // NB: even for HTTPS, local connection is on port 8080
1630
+ }
1631
+ curl_setopt($curl, CURLOPT_PROXY, $proxyhost.':'.$proxyport);
1632
+ //curl_setopt($curl, CURLOPT_PROXYPORT,$proxyport);
1633
+ if($proxyusername)
1634
+ {
1635
+ curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyusername.':'.$proxypassword);
1636
+ if (defined('CURLOPT_PROXYAUTH'))
1637
+ {
1638
+ curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyauthtype);
1639
+ }
1640
+ else if ($proxyauthtype != 1)
1641
+ {
1642
+ error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1643
+ }
1644
+ }
1645
+ }
1646
+
1647
+ // NB: should we build cookie http headers by hand rather than let CURL do it?
1648
+ // the following code does not honour 'expires', 'path' and 'domain' cookie attributes
1649
+ // set to client obj the the user...
1650
+ if (count($this->cookies))
1651
+ {
1652
+ $cookieheader = '';
1653
+ foreach ($this->cookies as $name => $cookie)
1654
+ {
1655
+ $cookieheader .= $name . '=' . $cookie['value'] . '; ';
1656
+ }
1657
+ curl_setopt($curl, CURLOPT_COOKIE, substr($cookieheader, 0, -2));
1658
+ }
1659
+
1660
+ foreach ($this->extracurlopts as $opt => $val)
1661
+ {
1662
+ curl_setopt($curl, $opt, $val);
1663
+ }
1664
+
1665
+ $result = curl_exec($curl);
1666
+
1667
+ if ($this->debug > 1)
1668
+ {
1669
+ print "<PRE>\n---CURL INFO---\n";
1670
+ foreach(curl_getinfo($curl) as $name => $val)
1671
+ print $name . ': ' . htmlentities($val). "\n";
1672
+ print "---END---\n</PRE>";
1673
+ }
1674
+
1675
+ if(!$result) /// @todo we should use a better check here - what if we get back '' or '0'?
1676
+ {
1677
+ $this->errstr='no response';
1678
+ $resp=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['curl_fail'], $GLOBALS['xmlrpcstr']['curl_fail']. ': '. curl_error($curl));
1679
+ curl_close($curl);
1680
+ if($keepalive)
1681
+ {
1682
+ $this->xmlrpc_curl_handle = null;
1683
+ }
1684
+ }
1685
+ else
1686
+ {
1687
+ if(!$keepalive)
1688
+ {
1689
+ curl_close($curl);
1690
+ }
1691
+ $resp =& $msg->parseResponse($result, true, $this->return_type);
1692
+ }
1693
+ return $resp;
1694
+ }
1695
+
1696
+ /**
1697
+ * Send an array of request messages and return an array of responses.
1698
+ * Unless $this->no_multicall has been set to true, it will try first
1699
+ * to use one single xmlrpc call to server method system.multicall, and
1700
+ * revert to sending many successive calls in case of failure.
1701
+ * This failure is also stored in $this->no_multicall for subsequent calls.
1702
+ * Unfortunately, there is no server error code universally used to denote
1703
+ * the fact that multicall is unsupported, so there is no way to reliably
1704
+ * distinguish between that and a temporary failure.
1705
+ * If you are sure that server supports multicall and do not want to
1706
+ * fallback to using many single calls, set the fourth parameter to FALSE.
1707
+ *
1708
+ * NB: trying to shoehorn extra functionality into existing syntax has resulted
1709
+ * in pretty much convoluted code...
1710
+ *
1711
+ * @param array $msgs an array of xmlrpcmsg objects
1712
+ * @param integer $timeout connection timeout (in seconds)
1713
+ * @param string $method the http protocol variant to be used
1714
+ * @param boolean fallback When true, upon receiveing an error during multicall, multiple single calls will be attempted
1715
+ * @return array
1716
+ * @access public
1717
+ */
1718
+ function multicall($msgs, $timeout=0, $method='', $fallback=true)
1719
+ {
1720
+ if ($method == '')
1721
+ {
1722
+ $method = $this->method;
1723
+ }
1724
+ if(!$this->no_multicall)
1725
+ {
1726
+ $results = $this->_try_multicall($msgs, $timeout, $method);
1727
+ if(is_array($results))
1728
+ {
1729
+ // System.multicall succeeded
1730
+ return $results;
1731
+ }
1732
+ else
1733
+ {
1734
+ // either system.multicall is unsupported by server,
1735
+ // or call failed for some other reason.
1736
+ if ($fallback)
1737
+ {
1738
+ // Don't try it next time...
1739
+ $this->no_multicall = true;
1740
+ }
1741
+ else
1742
+ {
1743
+ if (is_a($results, 'xmlrpcresp'))
1744
+ {
1745
+ $result = $results;
1746
+ }
1747
+ else
1748
+ {
1749
+ $result = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['multicall_error'], $GLOBALS['xmlrpcstr']['multicall_error']);
1750
+ }
1751
+ }
1752
+ }
1753
+ }
1754
+ else
1755
+ {
1756
+ // override fallback, in case careless user tries to do two
1757
+ // opposite things at the same time
1758
+ $fallback = true;
1759
+ }
1760
+
1761
+ $results = array();
1762
+ if ($fallback)
1763
+ {
1764
+ // system.multicall is (probably) unsupported by server:
1765
+ // emulate multicall via multiple requests
1766
+ foreach($msgs as $msg)
1767
+ {
1768
+ $results[] =& $this->send($msg, $timeout, $method);
1769
+ }
1770
+ }
1771
+ else
1772
+ {
1773
+ // user does NOT want to fallback on many single calls:
1774
+ // since we should always return an array of responses,
1775
+ // return an array with the same error repeated n times
1776
+ foreach($msgs as $msg)
1777
+ {
1778
+ $results[] = $result;
1779
+ }
1780
+ }
1781
+ return $results;
1782
+ }
1783
+
1784
+ /**
1785
+ * Attempt to boxcar $msgs via system.multicall.
1786
+ * Returns either an array of xmlrpcreponses, an xmlrpc error response
1787
+ * or false (when received response does not respect valid multicall syntax)
1788
+ * @access private
1789
+ */
1790
+ function _try_multicall($msgs, $timeout, $method)
1791
+ {
1792
+ // Construct multicall message
1793
+ $calls = array();
1794
+ foreach($msgs as $msg)
1795
+ {
1796
+ $call['methodName'] = new xmlrpcval($msg->method(),'string');
1797
+ $numParams = $msg->getNumParams();
1798
+ $params = array();
1799
+ for($i = 0; $i < $numParams; $i++)
1800
+ {
1801
+ $params[$i] = $msg->getParam($i);
1802
+ }
1803
+ $call['params'] = new xmlrpcval($params, 'array');
1804
+ $calls[] = new xmlrpcval($call, 'struct');
1805
+ }
1806
+ $multicall = new xmlrpcmsg('system.multicall');
1807
+ $multicall->addParam(new xmlrpcval($calls, 'array'));
1808
+
1809
+ // Attempt RPC call
1810
+ $result =& $this->send($multicall, $timeout, $method);
1811
+
1812
+ if($result->faultCode() != 0)
1813
+ {
1814
+ // call to system.multicall failed
1815
+ return $result;
1816
+ }
1817
+
1818
+ // Unpack responses.
1819
+ $rets = $result->value();
1820
+
1821
+ if ($this->return_type == 'xml')
1822
+ {
1823
+ return $rets;
1824
+ }
1825
+ else if ($this->return_type == 'phpvals')
1826
+ {
1827
+ ///@todo test this code branch...
1828
+ $rets = $result->value();
1829
+ if(!is_array($rets))
1830
+ {
1831
+ return false; // bad return type from system.multicall
1832
+ }
1833
+ $numRets = count($rets);
1834
+ if($numRets != count($msgs))
1835
+ {
1836
+ return false; // wrong number of return values.
1837
+ }
1838
+
1839
+ $response = array();
1840
+ for($i = 0; $i < $numRets; $i++)
1841
+ {
1842
+ $val = $rets[$i];
1843
+ if (!is_array($val)) {
1844
+ return false;
1845
+ }
1846
+ switch(count($val))
1847
+ {
1848
+ case 1:
1849
+ if(!isset($val[0]))
1850
+ {
1851
+ return false; // Bad value
1852
+ }
1853
+ // Normal return value
1854
+ $response[$i] = new xmlrpcresp($val[0], 0, '', 'phpvals');
1855
+ break;
1856
+ case 2:
1857
+ /// @todo remove usage of @: it is apparently quite slow
1858
+ $code = @$val['faultCode'];
1859
+ if(!is_int($code))
1860
+ {
1861
+ return false;
1862
+ }
1863
+ $str = @$val['faultString'];
1864
+ if(!is_string($str))
1865
+ {
1866
+ return false;
1867
+ }
1868
+ $response[$i] = new xmlrpcresp(0, $code, $str);
1869
+ break;
1870
+ default:
1871
+ return false;
1872
+ }
1873
+ }
1874
+ return $response;
1875
+ }
1876
+ else // return type == 'xmlrpcvals'
1877
+ {
1878
+ $rets = $result->value();
1879
+ if($rets->kindOf() != 'array')
1880
+ {
1881
+ return false; // bad return type from system.multicall
1882
+ }
1883
+ $numRets = $rets->arraysize();
1884
+ if($numRets != count($msgs))
1885
+ {
1886
+ return false; // wrong number of return values.
1887
+ }
1888
+
1889
+ $response = array();
1890
+ for($i = 0; $i < $numRets; $i++)
1891
+ {
1892
+ $val = $rets->arraymem($i);
1893
+ switch($val->kindOf())
1894
+ {
1895
+ case 'array':
1896
+ if($val->arraysize() != 1)
1897
+ {
1898
+ return false; // Bad value
1899
+ }
1900
+ // Normal return value
1901
+ $response[$i] = new xmlrpcresp($val->arraymem(0));
1902
+ break;
1903
+ case 'struct':
1904
+ $code = $val->structmem('faultCode');
1905
+ if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int')
1906
+ {
1907
+ return false;
1908
+ }
1909
+ $str = $val->structmem('faultString');
1910
+ if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string')
1911
+ {
1912
+ return false;
1913
+ }
1914
+ $response[$i] = new xmlrpcresp(0, $code->scalarval(), $str->scalarval());
1915
+ break;
1916
+ default:
1917
+ return false;
1918
+ }
1919
+ }
1920
+ return $response;
1921
+ }
1922
+ }
1923
+ } // end class xmlrpc_client
1924
+
1925
+ class xmlrpcresp
1926
+ {
1927
+ var $val = 0;
1928
+ var $valtyp;
1929
+ var $errno = 0;
1930
+ var $errstr = '';
1931
+ var $payload;
1932
+ var $hdrs = array();
1933
+ var $_cookies = array();
1934
+ var $content_type = 'text/xml';
1935
+ var $raw_data = '';
1936
+
1937
+ /**
1938
+ * @param mixed $val either an xmlrpcval obj, a php value or the xml serialization of an xmlrpcval (a string)
1939
+ * @param integer $fcode set it to anything but 0 to create an error response
1940
+ * @param string $fstr the error string, in case of an error response
1941
+ * @param string $valtyp either 'xmlrpcvals', 'phpvals' or 'xml'
1942
+ *
1943
+ * @todo add check that $val / $fcode / $fstr is of correct type???
1944
+ * NB: as of now we do not do it, since it might be either an xmlrpcval or a plain
1945
+ * php val, or a complete xml chunk, depending on usage of xmlrpc_client::send() inside which creator is called...
1946
+ */
1947
+ function xmlrpcresp($val, $fcode = 0, $fstr = '', $valtyp='')
1948
+ {
1949
+ if($fcode != 0)
1950
+ {
1951
+ // error response
1952
+ $this->errno = $fcode;
1953
+ $this->errstr = $fstr;
1954
+ //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later.
1955
+ }
1956
+ else
1957
+ {
1958
+ // successful response
1959
+ $this->val = $val;
1960
+ if ($valtyp == '')
1961
+ {
1962
+ // user did not declare type of response value: try to guess it
1963
+ if (is_object($this->val) && is_a($this->val, 'xmlrpcval'))
1964
+ {
1965
+ $this->valtyp = 'xmlrpcvals';
1966
+ }
1967
+ else if (is_string($this->val))
1968
+ {
1969
+ $this->valtyp = 'xml';
1970
+
1971
+ }
1972
+ else
1973
+ {
1974
+ $this->valtyp = 'phpvals';
1975
+ }
1976
+ }
1977
+ else
1978
+ {
1979
+ // user declares type of resp value: believe him
1980
+ $this->valtyp = $valtyp;
1981
+ }
1982
+ }
1983
+ }
1984
+
1985
+ /**
1986
+ * Returns the error code of the response.
1987
+ * @return integer the error code of this response (0 for not-error responses)
1988
+ * @access public
1989
+ */
1990
+ function faultCode()
1991
+ {
1992
+ return $this->errno;
1993
+ }
1994
+
1995
+ /**
1996
+ * Returns the error code of the response.
1997
+ * @return string the error string of this response ('' for not-error responses)
1998
+ * @access public
1999
+ */
2000
+ function faultString()
2001
+ {
2002
+ return $this->errstr;
2003
+ }
2004
+
2005
+ /**
2006
+ * Returns the value received by the server.
2007
+ * @return mixed the xmlrpcval object returned by the server. Might be an xml string or php value if the response has been created by specially configured xmlrpc_client objects
2008
+ * @access public
2009
+ */
2010
+ function value()
2011
+ {
2012
+ return $this->val;
2013
+ }
2014
+
2015
+ /**
2016
+ * Returns an array with the cookies received from the server.
2017
+ * Array has the form: $cookiename => array ('value' => $val, $attr1 => $val1, $attr2 = $val2, ...)
2018
+ * with attributes being e.g. 'expires', 'path', domain'.
2019
+ * NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past)
2020
+ * are still present in the array. It is up to the user-defined code to decide
2021
+ * how to use the received cookies, and wheter they have to be sent back with the next
2022
+ * request to the server (using xmlrpc_client::setCookie) or not
2023
+ * @return array array of cookies received from the server
2024
+ * @access public
2025
+ */
2026
+ function cookies()
2027
+ {
2028
+ return $this->_cookies;
2029
+ }
2030
+
2031
+ /**
2032
+ * Returns xml representation of the response. XML prologue not included
2033
+ * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
2034
+ * @return string the xml representation of the response
2035
+ * @access public
2036
+ */
2037
+ function serialize($charset_encoding='')
2038
+ {
2039
+ if ($charset_encoding != '')
2040
+ $this->content_type = 'text/xml; charset=' . $charset_encoding;
2041
+ else
2042
+ $this->content_type = 'text/xml';
2043
+ $result = "<methodResponse>\n";
2044
+ if($this->errno)
2045
+ {
2046
+ // G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients
2047
+ // by xml-encoding non ascii chars
2048
+ $result .= "<fault>\n" .
2049
+ "<value>\n<struct><member><name>faultCode</name>\n<value><int>" . $this->errno .
2050
+ "</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string>" .
2051
+ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) . "</string></value>\n</member>\n" .
2052
+ "</struct>\n</value>\n</fault>";
2053
+ }
2054
+ else
2055
+ {
2056
+ if(!is_object($this->val) || !is_a($this->val, 'xmlrpcval'))
2057
+ {
2058
+ if (is_string($this->val) && $this->valtyp == 'xml')
2059
+ {
2060
+ $result .= "<params>\n<param>\n" .
2061
+ $this->val .
2062
+ "</param>\n</params>";
2063
+ }
2064
+ else
2065
+ {
2066
+ /// @todo try to build something serializable?
2067
+ die('cannot serialize xmlrpcresp objects whose content is native php values');
2068
+ }
2069
+ }
2070
+ else
2071
+ {
2072
+ $result .= "<params>\n<param>\n" .
2073
+ $this->val->serialize($charset_encoding) .
2074
+ "</param>\n</params>";
2075
+ }
2076
+ }
2077
+ $result .= "\n</methodResponse>";
2078
+ $this->payload = $result;
2079
+ return $result;
2080
+ }
2081
+ }
2082
+
2083
+ class xmlrpcmsg
2084
+ {
2085
+ var $payload;
2086
+ var $methodname;
2087
+ var $params=array();
2088
+ var $debug=0;
2089
+ var $content_type = 'text/xml';
2090
+
2091
+ /**
2092
+ * @param string $meth the name of the method to invoke
2093
+ * @param array $pars array of parameters to be paased to the method (xmlrpcval objects)
2094
+ */
2095
+ function xmlrpcmsg($meth, $pars=0)
2096
+ {
2097
+ $this->methodname=$meth;
2098
+ if(is_array($pars) && count($pars)>0)
2099
+ {
2100
+ for($i=0; $i<count($pars); $i++)
2101
+ {
2102
+ $this->addParam($pars[$i]);
2103
+ }
2104
+ }
2105
+ }
2106
+
2107
+ /**
2108
+ * @access private
2109
+ */
2110
+ function xml_header($charset_encoding='')
2111
+ {
2112
+ if ($charset_encoding != '')
2113
+ {
2114
+ return "<?xml version=\"1.0\" encoding=\"$charset_encoding\" ?" . ">\n<methodCall>\n";
2115
+ }
2116
+ else
2117
+ {
2118
+ return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n";
2119
+ }
2120
+ }
2121
+
2122
+ /**
2123
+ * @access private
2124
+ */
2125
+ function xml_footer()
2126
+ {
2127
+ return '</methodCall>';
2128
+ }
2129
+
2130
+ /**
2131
+ * @access private
2132
+ */
2133
+ function kindOf()
2134
+ {
2135
+ return 'msg';
2136
+ }
2137
+
2138
+ /**
2139
+ * @access private
2140
+ */
2141
+ function createPayload($charset_encoding='')
2142
+ {
2143
+ if ($charset_encoding != '')
2144
+ $this->content_type = 'text/xml; charset=' . $charset_encoding;
2145
+ else
2146
+ $this->content_type = 'text/xml';
2147
+ $this->payload=$this->xml_header($charset_encoding);
2148
+ $this->payload.='<methodName>' . $this->methodname . "</methodName>\n";
2149
+ $this->payload.="<params>\n";
2150
+ for($i=0; $i<count($this->params); $i++)
2151
+ {
2152
+ $p=$this->params[$i];
2153
+ $this->payload.="<param>\n" . $p->serialize($charset_encoding) .
2154
+ "</param>\n";
2155
+ }
2156
+ $this->payload.="</params>\n";
2157
+ $this->payload.=$this->xml_footer();
2158
+ }
2159
+
2160
+ /**
2161
+ * Gets/sets the xmlrpc method to be invoked
2162
+ * @param string $meth the method to be set (leave empty not to set it)
2163
+ * @return string the method that will be invoked
2164
+ * @access public
2165
+ */
2166
+ function method($meth='')
2167
+ {
2168
+ if($meth!='')
2169
+ {
2170
+ $this->methodname=$meth;
2171
+ }
2172
+ return $this->methodname;
2173
+ }
2174
+
2175
+ /**
2176
+ * Returns xml representation of the message. XML prologue included
2177
+ * @return string the xml representation of the message, xml prologue included
2178
+ * @access public
2179
+ */
2180
+ function serialize($charset_encoding='')
2181
+ {
2182
+ $this->createPayload($charset_encoding);
2183
+ return $this->payload;
2184
+ }
2185
+
2186
+ /**
2187
+ * Add a parameter to the list of parameters to be used upon method invocation
2188
+ * @param xmlrpcval $par
2189
+ * @return boolean false on failure
2190
+ * @access public
2191
+ */
2192
+ function addParam($par)
2193
+ {
2194
+ // add check: do not add to self params which are not xmlrpcvals
2195
+ if(is_object($par) && is_a($par, 'xmlrpcval'))
2196
+ {
2197
+ $this->params[]=$par;
2198
+ return true;
2199
+ }
2200
+ else
2201
+ {
2202
+ return false;
2203
+ }
2204
+ }
2205
+
2206
+ /**
2207
+ * Returns the nth parameter in the message. The index zero-based.
2208
+ * @param integer $i the index of the parameter to fetch (zero based)
2209
+ * @return xmlrpcval the i-th parameter
2210
+ * @access public
2211
+ */
2212
+ function getParam($i) { return $this->params[$i]; }
2213
+
2214
+ /**
2215
+ * Returns the number of parameters in the messge.
2216
+ * @return integer the number of parameters currently set
2217
+ * @access public
2218
+ */
2219
+ function getNumParams() { return count($this->params); }
2220
+
2221
+ /**
2222
+ * Given an open file handle, read all data available and parse it as axmlrpc response.
2223
+ * NB: the file handle is not closed by this function.
2224
+ * NNB: might have trouble in rare cases to work on network streams, as we
2225
+ * check for a read of 0 bytes instead of feof($fp).
2226
+ * But since checking for feof(null) returns false, we would risk an
2227
+ * infinite loop in that case, because we cannot trust the caller
2228
+ * to give us a valid pointer to an open file...
2229
+ * @access public
2230
+ * @return xmlrpcresp
2231
+ * @todo add 2nd & 3rd param to be passed to ParseResponse() ???
2232
+ */
2233
+ function &parseResponseFile($fp)
2234
+ {
2235
+ $ipd='';
2236
+ while($data=fread($fp, 32768))
2237
+ {
2238
+ $ipd.=$data;
2239
+ }
2240
+ //fclose($fp);
2241
+ $r =& $this->parseResponse($ipd);
2242
+ return $r;
2243
+ }
2244
+
2245
+ /**
2246
+ * Parses HTTP headers and separates them from data.
2247
+ * @access private
2248
+ */
2249
+ function &parseResponseHeaders(&$data, $headers_processed=false)
2250
+ {
2251
+ // Support "web-proxy-tunelling" connections for https through proxies
2252
+ if(preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data))
2253
+ {
2254
+ // Look for CR/LF or simple LF as line separator,
2255
+ // (even though it is not valid http)
2256
+ $pos = strpos($data,"\r\n\r\n");
2257
+ if($pos || is_int($pos))
2258
+ {
2259
+ $bd = $pos+4;
2260
+ }
2261
+ else
2262
+ {
2263
+ $pos = strpos($data,"\n\n");
2264
+ if($pos || is_int($pos))
2265
+ {
2266
+ $bd = $pos+2;
2267
+ }
2268
+ else
2269
+ {
2270
+ // No separation between response headers and body: fault?
2271
+ $bd = 0;
2272
+ }
2273
+ }
2274
+ if ($bd)
2275
+ {
2276
+ // this filters out all http headers from proxy.
2277
+ // maybe we could take them into account, too?
2278
+ $data = substr($data, $bd);
2279
+ }
2280
+ else
2281
+ {
2282
+ error_log('XML-RPC: '.__METHOD__.': HTTPS via proxy error, tunnel connection possibly failed');
2283
+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (HTTPS via proxy error, tunnel connection possibly failed)');
2284
+ return $r;
2285
+ }
2286
+ }
2287
+
2288
+ // Strip HTTP 1.1 100 Continue header if present
2289
+ while(preg_match('/^HTTP\/1\.1 1[0-9]{2} /', $data))
2290
+ {
2291
+ $pos = strpos($data, 'HTTP', 12);
2292
+ // server sent a Continue header without any (valid) content following...
2293
+ // give the client a chance to know it
2294
+ if(!$pos && !is_int($pos)) // works fine in php 3, 4 and 5
2295
+ {
2296
+ break;
2297
+ }
2298
+ $data = substr($data, $pos);
2299
+ }
2300
+ if(!preg_match('/^HTTP\/[0-9.]+ 200 /', $data))
2301
+ {
2302
+ $errstr= substr($data, 0, strpos($data, "\n")-1);
2303
+ error_log('XML-RPC: '.__METHOD__.': HTTP error, got response: ' .$errstr);
2304
+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')');
2305
+ return $r;
2306
+ }
2307
+
2308
+ $GLOBALS['_xh']['headers'] = array();
2309
+ $GLOBALS['_xh']['cookies'] = array();
2310
+
2311
+ // be tolerant to usage of \n instead of \r\n to separate headers and data
2312
+ // (even though it is not valid http)
2313
+ $pos = strpos($data,"\r\n\r\n");
2314
+ if($pos || is_int($pos))
2315
+ {
2316
+ $bd = $pos+4;
2317
+ }
2318
+ else
2319
+ {
2320
+ $pos = strpos($data,"\n\n");
2321
+ if($pos || is_int($pos))
2322
+ {
2323
+ $bd = $pos+2;
2324
+ }
2325
+ else
2326
+ {
2327
+ // No separation between response headers and body: fault?
2328
+ // we could take some action here instead of going on...
2329
+ $bd = 0;
2330
+ }
2331
+ }
2332
+ // be tolerant to line endings, and extra empty lines
2333
+ $ar = preg_split("/\r?\n/", trim(substr($data, 0, $pos)));
2334
+ while(list(,$line) = @each($ar))
2335
+ {
2336
+ // take care of multi-line headers and cookies
2337
+ $arr = explode(':',$line,2);
2338
+ if(count($arr) > 1)
2339
+ {
2340
+ $header_name = strtolower(trim($arr[0]));
2341
+ /// @todo some other headers (the ones that allow a CSV list of values)
2342
+ /// do allow many values to be passed using multiple header lines.
2343
+ /// We should add content to $GLOBALS['_xh']['headers'][$header_name]
2344
+ /// instead of replacing it for those...
2345
+ if ($header_name == 'set-cookie' || $header_name == 'set-cookie2')
2346
+ {
2347
+ if ($header_name == 'set-cookie2')
2348
+ {
2349
+ // version 2 cookies:
2350
+ // there could be many cookies on one line, comma separated
2351
+ $cookies = explode(',', $arr[1]);
2352
+ }
2353
+ else
2354
+ {
2355
+ $cookies = array($arr[1]);
2356
+ }
2357
+ foreach ($cookies as $cookie)
2358
+ {
2359
+ // glue together all received cookies, using a comma to separate them
2360
+ // (same as php does with getallheaders())
2361
+ if (isset($GLOBALS['_xh']['headers'][$header_name]))
2362
+ $GLOBALS['_xh']['headers'][$header_name] .= ', ' . trim($cookie);
2363
+ else
2364
+ $GLOBALS['_xh']['headers'][$header_name] = trim($cookie);
2365
+ // parse cookie attributes, in case user wants to correctly honour them
2366
+ // feature creep: only allow rfc-compliant cookie attributes?
2367
+ // @todo support for server sending multiple time cookie with same name, but using different PATHs
2368
+ $cookie = explode(';', $cookie);
2369
+ foreach ($cookie as $pos => $val)
2370
+ {
2371
+ $val = explode('=', $val, 2);
2372
+ $tag = trim($val[0]);
2373
+ $val = trim(@$val[1]);
2374
+ /// @todo with version 1 cookies, we should strip leading and trailing " chars
2375
+ if ($pos == 0)
2376
+ {
2377
+ $cookiename = $tag;
2378
+ $GLOBALS['_xh']['cookies'][$tag] = array();
2379
+ $GLOBALS['_xh']['cookies'][$cookiename]['value'] = urldecode($val);
2380
+ }
2381
+ else
2382
+ {
2383
+ if ($tag != 'value')
2384
+ {
2385
+ $GLOBALS['_xh']['cookies'][$cookiename][$tag] = $val;
2386
+ }
2387
+ }
2388
+ }
2389
+ }
2390
+ }
2391
+ else
2392
+ {
2393
+ $GLOBALS['_xh']['headers'][$header_name] = trim($arr[1]);
2394
+ }
2395
+ }
2396
+ elseif(isset($header_name))
2397
+ {
2398
+ /// @todo version1 cookies might span multiple lines, thus breaking the parsing above
2399
+ $GLOBALS['_xh']['headers'][$header_name] .= ' ' . trim($line);
2400
+ }
2401
+ }
2402
+
2403
+ $data = substr($data, $bd);
2404
+
2405
+ if($this->debug && count($GLOBALS['_xh']['headers']))
2406
+ {
2407
+ print '<PRE>';
2408
+ foreach($GLOBALS['_xh']['headers'] as $header => $value)
2409
+ {
2410
+ print htmlentities("HEADER: $header: $value\n");
2411
+ }
2412
+ foreach($GLOBALS['_xh']['cookies'] as $header => $value)
2413
+ {
2414
+ print htmlentities("COOKIE: $header={$value['value']}\n");
2415
+ }
2416
+ print "</PRE>\n";
2417
+ }
2418
+
2419
+ // if CURL was used for the call, http headers have been processed,
2420
+ // and dechunking + reinflating have been carried out
2421
+ if(!$headers_processed)
2422
+ {
2423
+ // Decode chunked encoding sent by http 1.1 servers
2424
+ if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked')
2425
+ {
2426
+ if(!$data = decode_chunked($data))
2427
+ {
2428
+ error_log('XML-RPC: '.__METHOD__.': errors occurred when trying to rebuild the chunked data received from server');
2429
+ $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']);
2430
+ return $r;
2431
+ }
2432
+ }
2433
+
2434
+ // Decode gzip-compressed stuff
2435
+ // code shamelessly inspired from nusoap library by Dietrich Ayala
2436
+ if(isset($GLOBALS['_xh']['headers']['content-encoding']))
2437
+ {
2438
+ $GLOBALS['_xh']['headers']['content-encoding'] = str_replace('x-', '', $GLOBALS['_xh']['headers']['content-encoding']);
2439
+ if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip')
2440
+ {
2441
+ // if decoding works, use it. else assume data wasn't gzencoded
2442
+ if(function_exists('gzinflate'))
2443
+ {
2444
+ if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data))
2445
+ {
2446
+ $data = $degzdata;
2447
+ if($this->debug)
2448
+ print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
2449
+ }
2450
+ elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
2451
+ {
2452
+ $data = $degzdata;
2453
+ if($this->debug)
2454
+ print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
2455
+ }
2456
+ else
2457
+ {
2458
+ error_log('XML-RPC: '.__METHOD__.': errors occurred when trying to decode the deflated data received from server');
2459
+ $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']);
2460
+ return $r;
2461
+ }
2462
+ }
2463
+ else
2464
+ {
2465
+ error_log('XML-RPC: '.__METHOD__.': the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
2466
+ $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']);
2467
+ return $r;
2468
+ }
2469
+ }
2470
+ }
2471
+ } // end of 'if needed, de-chunk, re-inflate response'
2472
+
2473
+ // real stupid hack to avoid PHP complaining about returning NULL by ref
2474
+ $r = null;
2475
+ $r =& $r;
2476
+ return $r;
2477
+ }
2478
+
2479
+ /**
2480
+ * Parse the xmlrpc response contained in the string $data and return an xmlrpcresp object.
2481
+ * @param string $data the xmlrpc response, eventually including http headers
2482
+ * @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and consequent decoding
2483
+ * @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals'
2484
+ * @return xmlrpcresp
2485
+ * @access public
2486
+ */
2487
+ function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals')
2488
+ {
2489
+ if($this->debug)
2490
+ {
2491
+ //by maHo, replaced htmlspecialchars with htmlentities
2492
+ print "<PRE>---GOT---\n" . htmlentities($data) . "\n---END---\n</PRE>";
2493
+ }
2494
+
2495
+ if($data == '')
2496
+ {
2497
+ error_log('XML-RPC: '.__METHOD__.': no response received from server.');
2498
+ $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']);
2499
+ return $r;
2500
+ }
2501
+
2502
+ $GLOBALS['_xh']=array();
2503
+
2504
+ $raw_data = $data;
2505
+ // parse the HTTP headers of the response, if present, and separate them from data
2506
+ if(substr($data, 0, 4) == 'HTTP')
2507
+ {
2508
+ $r =& $this->parseResponseHeaders($data, $headers_processed);
2509
+ if ($r)
2510
+ {
2511
+ // failed processing of HTTP response headers
2512
+ // save into response obj the full payload received, for debugging
2513
+ $r->raw_data = $data;
2514
+ return $r;
2515
+ }
2516
+ }
2517
+ else
2518
+ {
2519
+ $GLOBALS['_xh']['headers'] = array();
2520
+ $GLOBALS['_xh']['cookies'] = array();
2521
+ }
2522
+
2523
+ if($this->debug)
2524
+ {
2525
+ $start = strpos($data, '<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
2526
+ if ($start)
2527
+ {
2528
+ $start += strlen('<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
2529
+ $end = strpos($data, '-->', $start);
2530
+ $comments = substr($data, $start, $end-$start);
2531
+ print "<PRE>---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n</PRE>";
2532
+ }
2533
+ }
2534
+
2535
+ // be tolerant of extra whitespace in response body
2536
+ $data = trim($data);
2537
+
2538
+ /// @todo return an error msg if $data=='' ?
2539
+
2540
+ // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
2541
+ // idea from Luca Mariano <luca.mariano@email.it> originally in PEARified version of the lib
2542
+ $pos = strrpos($data, '</methodResponse>');
2543
+ if($pos !== false)
2544
+ {
2545
+ $data = substr($data, 0, $pos+17);
2546
+ }
2547
+
2548
+ // if user wants back raw xml, give it to him
2549
+ if ($return_type == 'xml')
2550
+ {
2551
+ $r = new xmlrpcresp($data, 0, '', 'xml');
2552
+ $r->hdrs = $GLOBALS['_xh']['headers'];
2553
+ $r->_cookies = $GLOBALS['_xh']['cookies'];
2554
+ $r->raw_data = $raw_data;
2555
+ return $r;
2556
+ }
2557
+
2558
+ // try to 'guestimate' the character encoding of the received response
2559
+ $resp_encoding = guess_encoding(@$GLOBALS['_xh']['headers']['content-type'], $data);
2560
+
2561
+ $GLOBALS['_xh']['ac']='';
2562
+ //$GLOBALS['_xh']['qt']=''; //unused...
2563
+ $GLOBALS['_xh']['stack'] = array();
2564
+ $GLOBALS['_xh']['valuestack'] = array();
2565
+ $GLOBALS['_xh']['isf']=0; // 0 = OK, 1 for xmlrpc fault responses, 2 = invalid xmlrpc
2566
+ $GLOBALS['_xh']['isf_reason']='';
2567
+ $GLOBALS['_xh']['rt']=''; // 'methodcall or 'methodresponse'
2568
+
2569
+ // if response charset encoding is not known / supported, try to use
2570
+ // the default encoding and parse the xml anyway, but log a warning...
2571
+ if (!in_array($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2572
+ // the following code might be better for mb_string enabled installs, but
2573
+ // makes the lib about 200% slower...
2574
+ //if (!is_valid_charset($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2575
+ {
2576
+ error_log('XML-RPC: '.__METHOD__.': invalid charset encoding of received response: '.$resp_encoding);
2577
+ $resp_encoding = $GLOBALS['xmlrpc_defencoding'];
2578
+ }
2579
+ $parser = xml_parser_create($resp_encoding);
2580
+ xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
2581
+ // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
2582
+ // the xml parser to give us back data in the expected charset.
2583
+ // What if internal encoding is not in one of the 3 allowed?
2584
+ // we use the broadest one, ie. utf8
2585
+ // This allows to send data which is native in various charset,
2586
+ // by extending xmlrpc_encode_entitites() and setting xmlrpc_internalencoding
2587
+ if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2588
+ {
2589
+ xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
2590
+ }
2591
+ else
2592
+ {
2593
+ xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
2594
+ }
2595
+
2596
+ if ($return_type == 'phpvals')
2597
+ {
2598
+ xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
2599
+ }
2600
+ else
2601
+ {
2602
+ xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
2603
+ }
2604
+
2605
+ xml_set_character_data_handler($parser, 'xmlrpc_cd');
2606
+ xml_set_default_handler($parser, 'xmlrpc_dh');
2607
+
2608
+ // first error check: xml not well formed
2609
+ if(!xml_parse($parser, $data, count($data)))
2610
+ {
2611
+ // thanks to Peter Kocks <peter.kocks@baygate.com>
2612
+ if((xml_get_current_line_number($parser)) == 1)
2613
+ {
2614
+ $errstr = 'XML error at line 1, check URL';
2615
+ }
2616
+ else
2617
+ {
2618
+ $errstr = sprintf('XML error: %s at line %d, column %d',
2619
+ xml_error_string(xml_get_error_code($parser)),
2620
+ xml_get_current_line_number($parser), xml_get_current_column_number($parser));
2621
+ }
2622
+ error_log($errstr);
2623
+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')');
2624
+ xml_parser_free($parser);
2625
+ if($this->debug)
2626
+ {
2627
+ print $errstr;
2628
+ }
2629
+ $r->hdrs = $GLOBALS['_xh']['headers'];
2630
+ $r->_cookies = $GLOBALS['_xh']['cookies'];
2631
+ $r->raw_data = $raw_data;
2632
+ return $r;
2633
+ }
2634
+ xml_parser_free($parser);
2635
+ // second error check: xml well formed but not xml-rpc compliant
2636
+ if ($GLOBALS['_xh']['isf'] > 1)
2637
+ {
2638
+ if ($this->debug)
2639
+ {
2640
+ /// @todo echo something for user?
2641
+ }
2642
+
2643
+ $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
2644
+ $GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']);
2645
+ }
2646
+ // third error check: parsing of the response has somehow gone boink.
2647
+ // NB: shall we omit this check, since we trust the parsing code?
2648
+ elseif ($return_type == 'xmlrpcvals' && !is_object($GLOBALS['_xh']['value']))
2649
+ {
2650
+ // something odd has happened
2651
+ // and it's time to generate a client side error
2652
+ // indicating something odd went on
2653
+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
2654
+ $GLOBALS['xmlrpcstr']['invalid_return']);
2655
+ }
2656
+ else
2657
+ {
2658
+ if ($this->debug)
2659
+ {
2660
+ print "<PRE>---PARSED---\n";
2661
+ // somehow htmlentities chokes on var_export, and some full html string...
2662
+ //print htmlentitites(var_export($GLOBALS['_xh']['value'], true));
2663
+ print htmlspecialchars(var_export($GLOBALS['_xh']['value'], true));
2664
+ print "\n---END---</PRE>";
2665
+ }
2666
+
2667
+ // note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object.
2668
+ $v =& $GLOBALS['_xh']['value'];
2669
+
2670
+ if($GLOBALS['_xh']['isf'])
2671
+ {
2672
+ /// @todo we should test here if server sent an int and a string,
2673
+ /// and/or coerce them into such...
2674
+ if ($return_type == 'xmlrpcvals')
2675
+ {
2676
+ $errno_v = $v->structmem('faultCode');
2677
+ $errstr_v = $v->structmem('faultString');
2678
+ $errno = $errno_v->scalarval();
2679
+ $errstr = $errstr_v->scalarval();
2680
+ }
2681
+ else
2682
+ {
2683
+ $errno = $v['faultCode'];
2684
+ $errstr = $v['faultString'];
2685
+ }
2686
+
2687
+ if($errno == 0)
2688
+ {
2689
+ // FAULT returned, errno needs to reflect that
2690
+ $errno = -1;
2691
+ }
2692
+
2693
+ $r = new xmlrpcresp(0, $errno, $errstr);
2694
+ }
2695
+ else
2696
+ {
2697
+ $r=new xmlrpcresp($v, 0, '', $return_type);
2698
+ }
2699
+ }
2700
+
2701
+ $r->hdrs = $GLOBALS['_xh']['headers'];
2702
+ $r->_cookies = $GLOBALS['_xh']['cookies'];
2703
+ $r->raw_data = $raw_data;
2704
+ return $r;
2705
+ }
2706
+ }
2707
+
2708
+ class xmlrpcval
2709
+ {
2710
+ var $me=array();
2711
+ var $mytype=0;
2712
+ var $_php_class=null;
2713
+
2714
+ /**
2715
+ * @param mixed $val
2716
+ * @param string $type any valid xmlrpc type name (lowercase). If null, 'string' is assumed
2717
+ */
2718
+ function xmlrpcval($val=-1, $type='')
2719
+ {
2720
+ /// @todo: optimization creep - do not call addXX, do it all inline.
2721
+ /// downside: booleans will not be coerced anymore
2722
+ if($val!==-1 || $type!='')
2723
+ {
2724
+ // optimization creep: inlined all work done by constructor
2725
+ switch($type)
2726
+ {
2727
+ case '':
2728
+ $this->mytype=1;
2729
+ $this->me['string']=$val;
2730
+ break;
2731
+ case 'i4':
2732
+ case 'int':
2733
+ case 'double':
2734
+ case 'string':
2735
+ case 'boolean':
2736
+ case 'dateTime.iso8601':
2737
+ case 'base64':
2738
+ case 'null':
2739
+ $this->mytype=1;
2740
+ $this->me[$type]=$val;
2741
+ break;
2742
+ case 'array':
2743
+ $this->mytype=2;
2744
+ $this->me['array']=$val;
2745
+ break;
2746
+ case 'struct':
2747
+ $this->mytype=3;
2748
+ $this->me['struct']=$val;
2749
+ break;
2750
+ default:
2751
+ error_log("XML-RPC: ".__METHOD__.": not a known type ($type)");
2752
+ }
2753
+ /*if($type=='')
2754
+ {
2755
+ $type='string';
2756
+ }
2757
+ if($GLOBALS['xmlrpcTypes'][$type]==1)
2758
+ {
2759
+ $this->addScalar($val,$type);
2760
+ }
2761
+ elseif($GLOBALS['xmlrpcTypes'][$type]==2)
2762
+ {
2763
+ $this->addArray($val);
2764
+ }
2765
+ elseif($GLOBALS['xmlrpcTypes'][$type]==3)
2766
+ {
2767
+ $this->addStruct($val);
2768
+ }*/
2769
+ }
2770
+ }
2771
+
2772
+ /**
2773
+ * Add a single php value to an (unitialized) xmlrpcval
2774
+ * @param mixed $val
2775
+ * @param string $type
2776
+ * @return int 1 or 0 on failure
2777
+ */
2778
+ function addScalar($val, $type='string')
2779
+ {
2780
+ $typeof=@$GLOBALS['xmlrpcTypes'][$type];
2781
+ if($typeof!=1)
2782
+ {
2783
+ error_log("XML-RPC: ".__METHOD__.": not a scalar type ($type)");
2784
+ return 0;
2785
+ }
2786
+
2787
+ // coerce booleans into correct values
2788
+ // NB: we should either do it for datetimes, integers and doubles, too,
2789
+ // or just plain remove this check, implemented on booleans only...
2790
+ if($type==$GLOBALS['xmlrpcBoolean'])
2791
+ {
2792
+ if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false')))
2793
+ {
2794
+ $val=true;
2795
+ }
2796
+ else
2797
+ {
2798
+ $val=false;
2799
+ }
2800
+ }
2801
+
2802
+ switch($this->mytype)
2803
+ {
2804
+ case 1:
2805
+ error_log('XML-RPC: '.__METHOD__.': scalar xmlrpcval can have only one value');
2806
+ return 0;
2807
+ case 3:
2808
+ error_log('XML-RPC: '.__METHOD__.': cannot add anonymous scalar to struct xmlrpcval');
2809
+ return 0;
2810
+ case 2:
2811
+ // we're adding a scalar value to an array here
2812
+ //$ar=$this->me['array'];
2813
+ //$ar[]=new xmlrpcval($val, $type);
2814
+ //$this->me['array']=$ar;
2815
+ // Faster (?) avoid all the costly array-copy-by-val done here...
2816
+ $this->me['array'][]=new xmlrpcval($val, $type);
2817
+ return 1;
2818
+ default:
2819
+ // a scalar, so set the value and remember we're scalar
2820
+ $this->me[$type]=$val;
2821
+ $this->mytype=$typeof;
2822
+ return 1;
2823
+ }
2824
+ }
2825
+
2826
+ /**
2827
+ * Add an array of xmlrpcval objects to an xmlrpcval
2828
+ * @param array $vals
2829
+ * @return int 1 or 0 on failure
2830
+ * @access public
2831
+ *
2832
+ * @todo add some checking for $vals to be an array of xmlrpcvals?
2833
+ */
2834
+ function addArray($vals)
2835
+ {
2836
+ if($this->mytype==0)
2837
+ {
2838
+ $this->mytype=$GLOBALS['xmlrpcTypes']['array'];
2839
+ $this->me['array']=$vals;
2840
+ return 1;
2841
+ }
2842
+ elseif($this->mytype==2)
2843
+ {
2844
+ // we're adding to an array here
2845
+ $this->me['array'] = array_merge($this->me['array'], $vals);
2846
+ return 1;
2847
+ }
2848
+ else
2849
+ {
2850
+ error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']');
2851
+ return 0;
2852
+ }
2853
+ }
2854
+
2855
+ /**
2856
+ * Add an array of named xmlrpcval objects to an xmlrpcval
2857
+ * @param array $vals
2858
+ * @return int 1 or 0 on failure
2859
+ * @access public
2860
+ *
2861
+ * @todo add some checking for $vals to be an array?
2862
+ */
2863
+ function addStruct($vals)
2864
+ {
2865
+ if($this->mytype==0)
2866
+ {
2867
+ $this->mytype=$GLOBALS['xmlrpcTypes']['struct'];
2868
+ $this->me['struct']=$vals;
2869
+ return 1;
2870
+ }
2871
+ elseif($this->mytype==3)
2872
+ {
2873
+ // we're adding to a struct here
2874
+ $this->me['struct'] = array_merge($this->me['struct'], $vals);
2875
+ return 1;
2876
+ }
2877
+ else
2878
+ {
2879
+ error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']');
2880
+ return 0;
2881
+ }
2882
+ }
2883
+
2884
+ // poor man's version of print_r ???
2885
+ // DEPRECATED!
2886
+ function dump($ar)
2887
+ {
2888
+ foreach($ar as $key => $val)
2889
+ {
2890
+ echo "$key => $val<br />";
2891
+ if($key == 'array')
2892
+ {
2893
+ while(list($key2, $val2) = each($val))
2894
+ {
2895
+ echo "-- $key2 => $val2<br />";
2896
+ }
2897
+ }
2898
+ }
2899
+ }
2900
+
2901
+ /**
2902
+ * Returns a string containing "struct", "array" or "scalar" describing the base type of the value
2903
+ * @return string
2904
+ * @access public
2905
+ */
2906
+ function kindOf()
2907
+ {
2908
+ switch($this->mytype)
2909
+ {
2910
+ case 3:
2911
+ return 'struct';
2912
+ break;
2913
+ case 2:
2914
+ return 'array';
2915
+ break;
2916
+ case 1:
2917
+ return 'scalar';
2918
+ break;
2919
+ default:
2920
+ return 'undef';
2921
+ }
2922
+ }
2923
+
2924
+ /**
2925
+ * @access private
2926
+ */
2927
+ function serializedata($typ, $val, $charset_encoding='')
2928
+ {
2929
+ $rs='';
2930
+ switch(@$GLOBALS['xmlrpcTypes'][$typ])
2931
+ {
2932
+ case 1:
2933
+ switch($typ)
2934
+ {
2935
+ case $GLOBALS['xmlrpcBase64']:
2936
+ $rs.="<${typ}>" . base64_encode($val) . "</${typ}>";
2937
+ break;
2938
+ case $GLOBALS['xmlrpcBoolean']:
2939
+ $rs.="<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
2940
+ break;
2941
+ case $GLOBALS['xmlrpcString']:
2942
+ // G. Giunta 2005/2/13: do NOT use htmlentities, since
2943
+ // it will produce named html entities, which are invalid xml
2944
+ $rs.="<${typ}>" . xmlrpc_encode_entitites($val, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding). "</${typ}>";
2945
+ break;
2946
+ case $GLOBALS['xmlrpcInt']:
2947
+ case $GLOBALS['xmlrpcI4']:
2948
+ $rs.="<${typ}>".(int)$val."</${typ}>";
2949
+ break;
2950
+ case $GLOBALS['xmlrpcDouble']:
2951
+ // avoid using standard conversion of float to string because it is locale-dependent,
2952
+ // and also because the xmlrpc spec forbids exponential notation.
2953
+ // sprintf('%F') could be most likely ok but it fails eg. on 2e-14.
2954
+ // The code below tries its best at keeping max precision while avoiding exp notation,
2955
+ // but there is of course no limit in the number of decimal places to be used...
2956
+ $rs.="<${typ}>".preg_replace('/\\.?0+$/','',number_format((double)$val, 128, '.', ''))."</${typ}>";
2957
+ break;
2958
+ case $GLOBALS['xmlrpcDateTime']:
2959
+ if (is_string($val))
2960
+ {
2961
+ $rs.="<${typ}>${val}</${typ}>";
2962
+ }
2963
+ else if(is_a($val, 'DateTime'))
2964
+ {
2965
+ $rs.="<${typ}>".$val->format('Ymd\TH:i:s')."</${typ}>";
2966
+ }
2967
+ else if(is_int($val))
2968
+ {
2969
+ $rs.="<${typ}>".strftime("%Y%m%dT%H:%M:%S", $val)."</${typ}>";
2970
+ }
2971
+ else
2972
+ {
2973
+ // not really a good idea here: but what shall we output anyway? left for backward compat...
2974
+ $rs.="<${typ}>${val}</${typ}>";
2975
+ }
2976
+ break;
2977
+ case $GLOBALS['xmlrpcNull']:
2978
+ if ($GLOBALS['xmlrpc_null_apache_encoding'])
2979
+ {
2980
+ $rs.="<ex:nil/>";
2981
+ }
2982
+ else
2983
+ {
2984
+ $rs.="<nil/>";
2985
+ }
2986
+ break;
2987
+ default:
2988
+ // no standard type value should arrive here, but provide a possibility
2989
+ // for xmlrpcvals of unknown type...
2990
+ $rs.="<${typ}>${val}</${typ}>";
2991
+ }
2992
+ break;
2993
+ case 3:
2994
+ // struct
2995
+ if ($this->_php_class)
2996
+ {
2997
+ $rs.='<struct php_class="' . $this->_php_class . "\">\n";
2998
+ }
2999
+ else
3000
+ {
3001
+ $rs.="<struct>\n";
3002
+ }
3003
+ foreach($val as $key2 => $val2)
3004
+ {
3005
+ $rs.='<member><name>'.xmlrpc_encode_entitites($key2, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding)."</name>\n";
3006
+ //$rs.=$this->serializeval($val2);
3007
+ $rs.=$val2->serialize($charset_encoding);
3008
+ $rs.="</member>\n";
3009
+ }
3010
+ $rs.='</struct>';
3011
+ break;
3012
+ case 2:
3013
+ // array
3014
+ $rs.="<array>\n<data>\n";
3015
+ for($i=0; $i<count($val); $i++)
3016
+ {
3017
+ //$rs.=$this->serializeval($val[$i]);
3018
+ $rs.=$val[$i]->serialize($charset_encoding);
3019
+ }
3020
+ $rs.="</data>\n</array>";
3021
+ break;
3022
+ default:
3023
+ break;
3024
+ }
3025
+ return $rs;
3026
+ }
3027
+
3028
+ /**
3029
+ * Returns xml representation of the value. XML prologue not included
3030
+ * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
3031
+ * @return string
3032
+ * @access public
3033
+ */
3034
+ function serialize($charset_encoding='')
3035
+ {
3036
+ // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
3037
+ //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
3038
+ //{
3039
+ reset($this->me);
3040
+ list($typ, $val) = each($this->me);
3041
+ return '<value>' . $this->serializedata($typ, $val, $charset_encoding) . "</value>\n";
3042
+ //}
3043
+ }
3044
+
3045
+ // DEPRECATED
3046
+ function serializeval($o)
3047
+ {
3048
+ // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
3049
+ //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
3050
+ //{
3051
+ $ar=$o->me;
3052
+ reset($ar);
3053
+ list($typ, $val) = each($ar);
3054
+ return '<value>' . $this->serializedata($typ, $val) . "</value>\n";
3055
+ //}
3056
+ }
3057
+
3058
+ /**
3059
+ * Checks wheter a struct member with a given name is present.
3060
+ * Works only on xmlrpcvals of type struct.
3061
+ * @param string $m the name of the struct member to be looked up
3062
+ * @return boolean
3063
+ * @access public
3064
+ */
3065
+ function structmemexists($m)
3066
+ {
3067
+ return array_key_exists($m, $this->me['struct']);
3068
+ }
3069
+
3070
+ /**
3071
+ * Returns the value of a given struct member (an xmlrpcval object in itself).
3072
+ * Will raise a php warning if struct member of given name does not exist
3073
+ * @param string $m the name of the struct member to be looked up
3074
+ * @return xmlrpcval
3075
+ * @access public
3076
+ */
3077
+ function structmem($m)
3078
+ {
3079
+ return $this->me['struct'][$m];
3080
+ }
3081
+
3082
+ /**
3083
+ * Reset internal pointer for xmlrpcvals of type struct.
3084
+ * @access public
3085
+ */
3086
+ function structreset()
3087
+ {
3088
+ reset($this->me['struct']);
3089
+ }
3090
+
3091
+ /**
3092
+ * Return next member element for xmlrpcvals of type struct.
3093
+ * @return xmlrpcval
3094
+ * @access public
3095
+ */
3096
+ function structeach()
3097
+ {
3098
+ return each($this->me['struct']);
3099
+ }
3100
+
3101
+ // DEPRECATED! this code looks like it is very fragile and has not been fixed
3102
+ // for a long long time. Shall we remove it for 2.0?
3103
+ function getval()
3104
+ {
3105
+ // UNSTABLE
3106
+ reset($this->me);
3107
+ list($a,$b)=each($this->me);
3108
+ // contributed by I Sofer, 2001-03-24
3109
+ // add support for nested arrays to scalarval
3110
+ // i've created a new method here, so as to
3111
+ // preserve back compatibility
3112
+
3113
+ if(is_array($b))
3114
+ {
3115
+ @reset($b);
3116
+ while(list($id,$cont) = @each($b))
3117
+ {
3118
+ $b[$id] = $cont->scalarval();
3119
+ }
3120
+ }
3121
+
3122
+ // add support for structures directly encoding php objects
3123
+ if(is_object($b))
3124
+ {
3125
+ $t = get_object_vars($b);
3126
+ @reset($t);
3127
+ while(list($id,$cont) = @each($t))
3128
+ {
3129
+ $t[$id] = $cont->scalarval();
3130
+ }
3131
+ @reset($t);
3132
+ while(list($id,$cont) = @each($t))
3133
+ {
3134
+ @$b->$id = $cont;
3135
+ }
3136
+ }
3137
+ // end contrib
3138
+ return $b;
3139
+ }
3140
+
3141
+ /**
3142
+ * Returns the value of a scalar xmlrpcval
3143
+ * @return mixed
3144
+ * @access public
3145
+ */
3146
+ function scalarval()
3147
+ {
3148
+ reset($this->me);
3149
+ list(,$b)=each($this->me);
3150
+ return $b;
3151
+ }
3152
+
3153
+ /**
3154
+ * Returns the type of the xmlrpcval.
3155
+ * For integers, 'int' is always returned in place of 'i4'
3156
+ * @return string
3157
+ * @access public
3158
+ */
3159
+ function scalartyp()
3160
+ {
3161
+ reset($this->me);
3162
+ list($a,)=each($this->me);
3163
+ if($a==$GLOBALS['xmlrpcI4'])
3164
+ {
3165
+ $a=$GLOBALS['xmlrpcInt'];
3166
+ }
3167
+ return $a;
3168
+ }
3169
+
3170
+ /**
3171
+ * Returns the m-th member of an xmlrpcval of struct type
3172
+ * @param integer $m the index of the value to be retrieved (zero based)
3173
+ * @return xmlrpcval
3174
+ * @access public
3175
+ */
3176
+ function arraymem($m)
3177
+ {
3178
+ return $this->me['array'][$m];
3179
+ }
3180
+
3181
+ /**
3182
+ * Returns the number of members in an xmlrpcval of array type
3183
+ * @return integer
3184
+ * @access public
3185
+ */
3186
+ function arraysize()
3187
+ {
3188
+ return count($this->me['array']);
3189
+ }
3190
+
3191
+ /**
3192
+ * Returns the number of members in an xmlrpcval of struct type
3193
+ * @return integer
3194
+ * @access public
3195
+ */
3196
+ function structsize()
3197
+ {
3198
+ return count($this->me['struct']);
3199
+ }
3200
+ }
3201
+
3202
+
3203
+ // date helpers
3204
+
3205
+ /**
3206
+ * Given a timestamp, return the corresponding ISO8601 encoded string.
3207
+ *
3208
+ * Really, timezones ought to be supported
3209
+ * but the XML-RPC spec says:
3210
+ *
3211
+ * "Don't assume a timezone. It should be specified by the server in its
3212
+ * documentation what assumptions it makes about timezones."
3213
+ *
3214
+ * These routines always assume localtime unless
3215
+ * $utc is set to 1, in which case UTC is assumed
3216
+ * and an adjustment for locale is made when encoding
3217
+ *
3218
+ * @param int $timet (timestamp)
3219
+ * @param int $utc (0 or 1)
3220
+ * @return string
3221
+ */
3222
+ function iso8601_encode($timet, $utc=0)
3223
+ {
3224
+ if(!$utc)
3225
+ {
3226
+ $t=strftime("%Y%m%dT%H:%M:%S", $timet);
3227
+ }
3228
+ else
3229
+ {
3230
+ if(function_exists('gmstrftime'))
3231
+ {
3232
+ // gmstrftime doesn't exist in some versions
3233
+ // of PHP
3234
+ $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet);
3235
+ }
3236
+ else
3237
+ {
3238
+ $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z'));
3239
+ }
3240
+ }
3241
+ return $t;
3242
+ }
3243
+
3244
+ /**
3245
+ * Given an ISO8601 date string, return a timet in the localtime, or UTC
3246
+ * @param string $idate
3247
+ * @param int $utc either 0 or 1
3248
+ * @return int (datetime)
3249
+ */
3250
+ function iso8601_decode($idate, $utc=0)
3251
+ {
3252
+ $t=0;
3253
+ if(preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs))
3254
+ {
3255
+ if($utc)
3256
+ {
3257
+ $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
3258
+ }
3259
+ else
3260
+ {
3261
+ $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
3262
+ }
3263
+ }
3264
+ return $t;
3265
+ }
3266
+
3267
+ /**
3268
+ * Takes an xmlrpc value in PHP xmlrpcval object format and translates it into native PHP types.
3269
+ *
3270
+ * Works with xmlrpc message objects as input, too.
3271
+ *
3272
+ * Given proper options parameter, can rebuild generic php object instances
3273
+ * (provided those have been encoded to xmlrpc format using a corresponding
3274
+ * option in php_xmlrpc_encode())
3275
+ * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
3276
+ * This means that the remote communication end can decide which php code will
3277
+ * get executed on your server, leaving the door possibly open to 'php-injection'
3278
+ * style of attacks (provided you have some classes defined on your server that
3279
+ * might wreak havoc if instances are built outside an appropriate context).
3280
+ * Make sure you trust the remote server/client before eanbling this!
3281
+ *
3282
+ * @author Dan Libby (dan@libby.com)
3283
+ *
3284
+ * @param xmlrpcval $xmlrpc_val
3285
+ * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects; if 'dates_as_objects' is set xmlrpc datetimes are decoded as php DateTime objects (standard is
3286
+ * @return mixed
3287
+ */
3288
+ function php_xmlrpc_decode($xmlrpc_val, $options=array())
3289
+ {
3290
+ switch($xmlrpc_val->kindOf())
3291
+ {
3292
+ case 'scalar':
3293
+ if (in_array('extension_api', $options))
3294
+ {
3295
+ reset($xmlrpc_val->me);
3296
+ list($typ,$val) = each($xmlrpc_val->me);
3297
+ switch ($typ)
3298
+ {
3299
+ case 'dateTime.iso8601':
3300
+ $xmlrpc_val->scalar = $val;
3301
+ $xmlrpc_val->xmlrpc_type = 'datetime';
3302
+ $xmlrpc_val->timestamp = iso8601_decode($val);
3303
+ return $xmlrpc_val;
3304
+ case 'base64':
3305
+ $xmlrpc_val->scalar = $val;
3306
+ $xmlrpc_val->type = $typ;
3307
+ return $xmlrpc_val;
3308
+ default:
3309
+ return $xmlrpc_val->scalarval();
3310
+ }
3311
+ }
3312
+ if (in_array('dates_as_objects', $options) && $xmlrpc_val->scalartyp() == 'dateTime.iso8601')
3313
+ {
3314
+ // we return a Datetime object instead of a string
3315
+ // since now the constructor of xmlrpcval accepts safely strings, ints and datetimes,
3316
+ // we cater to all 3 cases here
3317
+ $out = $xmlrpc_val->scalarval();
3318
+ if (is_string($out))
3319
+ {
3320
+ $out = strtotime($out);
3321
+ }
3322
+ if (is_int($out))
3323
+ {
3324
+ $result = new Datetime();
3325
+ $result->setTimestamp($out);
3326
+ return $result;
3327
+ }
3328
+ elseif (is_a($out, 'Datetime'))
3329
+ {
3330
+ return $out;
3331
+ }
3332
+ }
3333
+ return $xmlrpc_val->scalarval();
3334
+ case 'array':
3335
+ $size = $xmlrpc_val->arraysize();
3336
+ $arr = array();
3337
+ for($i = 0; $i < $size; $i++)
3338
+ {
3339
+ $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options);
3340
+ }
3341
+ return $arr;
3342
+ case 'struct':
3343
+ $xmlrpc_val->structreset();
3344
+ // If user said so, try to rebuild php objects for specific struct vals.
3345
+ /// @todo should we raise a warning for class not found?
3346
+ // shall we check for proper subclass of xmlrpcval instead of
3347
+ // presence of _php_class to detect what we can do?
3348
+ if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''
3349
+ && class_exists($xmlrpc_val->_php_class))
3350
+ {
3351
+ $obj = @new $xmlrpc_val->_php_class;
3352
+ while(list($key,$value)=$xmlrpc_val->structeach())
3353
+ {
3354
+ $obj->$key = php_xmlrpc_decode($value, $options);
3355
+ }
3356
+ return $obj;
3357
+ }
3358
+ else
3359
+ {
3360
+ $arr = array();
3361
+ while(list($key,$value)=$xmlrpc_val->structeach())
3362
+ {
3363
+ $arr[$key] = php_xmlrpc_decode($value, $options);
3364
+ }
3365
+ return $arr;
3366
+ }
3367
+ case 'msg':
3368
+ $paramcount = $xmlrpc_val->getNumParams();
3369
+ $arr = array();
3370
+ for($i = 0; $i < $paramcount; $i++)
3371
+ {
3372
+ $arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i));
3373
+ }
3374
+ return $arr;
3375
+ }
3376
+ }
3377
+
3378
+ // This constant left here only for historical reasons...
3379
+ // it was used to decide if we have to define xmlrpc_encode on our own, but
3380
+ // we do not do it anymore
3381
+ if(function_exists('xmlrpc_decode'))
3382
+ {
3383
+ define('XMLRPC_EPI_ENABLED','1');
3384
+ }
3385
+ else
3386
+ {
3387
+ define('XMLRPC_EPI_ENABLED','0');
3388
+ }
3389
+
3390
+ /**
3391
+ * Takes native php types and encodes them into xmlrpc PHP object format.
3392
+ * It will not re-encode xmlrpcval objects.
3393
+ *
3394
+ * Feature creep -- could support more types via optional type argument
3395
+ * (string => datetime support has been added, ??? => base64 not yet)
3396
+ *
3397
+ * If given a proper options parameter, php object instances will be encoded
3398
+ * into 'special' xmlrpc values, that can later be decoded into php objects
3399
+ * by calling php_xmlrpc_decode() with a corresponding option
3400
+ *
3401
+ * @author Dan Libby (dan@libby.com)
3402
+ *
3403
+ * @param mixed $php_val the value to be converted into an xmlrpcval object
3404
+ * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
3405
+ * @return xmlrpcval
3406
+ */
3407
+ function php_xmlrpc_encode($php_val, $options=array())
3408
+ {
3409
+ $type = gettype($php_val);
3410
+ switch($type)
3411
+ {
3412
+ case 'string':
3413
+ if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val))
3414
+ $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcDateTime']);
3415
+ else
3416
+ $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcString']);
3417
+ break;
3418
+ case 'integer':
3419
+ $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcInt']);
3420
+ break;
3421
+ case 'double':
3422
+ $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcDouble']);
3423
+ break;
3424
+ // <G_Giunta_2001-02-29>
3425
+ // Add support for encoding/decoding of booleans, since they are supported in PHP
3426
+ case 'boolean':
3427
+ $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcBoolean']);
3428
+ break;
3429
+ // </G_Giunta_2001-02-29>
3430
+ case 'array':
3431
+ // PHP arrays can be encoded to either xmlrpc structs or arrays,
3432
+ // depending on wheter they are hashes or plain 0..n integer indexed
3433
+ // A shorter one-liner would be
3434
+ // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
3435
+ // but execution time skyrockets!
3436
+ $j = 0;
3437
+ $arr = array();
3438
+ $ko = false;
3439
+ foreach($php_val as $key => $val)
3440
+ {
3441
+ $arr[$key] = php_xmlrpc_encode($val, $options);
3442
+ if(!$ko && $key !== $j)
3443
+ {
3444
+ $ko = true;
3445
+ }
3446
+ $j++;
3447
+ }
3448
+ if($ko)
3449
+ {
3450
+ $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']);
3451
+ }
3452
+ else
3453
+ {
3454
+ $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcArray']);
3455
+ }
3456
+ break;
3457
+ case 'object':
3458
+ if(is_a($php_val, 'xmlrpcval'))
3459
+ {
3460
+ $xmlrpc_val = $php_val;
3461
+ }
3462
+ else if(is_a($php_val, 'DateTime'))
3463
+ {
3464
+ $xmlrpc_val = new xmlrpcval($php_val->format('Ymd\TH:i:s'), $GLOBALS['xmlrpcStruct']);
3465
+ }
3466
+ else
3467
+ {
3468
+ $arr = array();
3469
+ reset($php_val);
3470
+ while(list($k,$v) = each($php_val))
3471
+ {
3472
+ $arr[$k] = php_xmlrpc_encode($v, $options);
3473
+ }
3474
+ $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']);
3475
+ if (in_array('encode_php_objs', $options))
3476
+ {
3477
+ // let's save original class name into xmlrpcval:
3478
+ // might be useful later on...
3479
+ $xmlrpc_val->_php_class = get_class($php_val);
3480
+ }
3481
+ }
3482
+ break;
3483
+ case 'NULL':
3484
+ if (in_array('extension_api', $options))
3485
+ {
3486
+ $xmlrpc_val = new xmlrpcval('', $GLOBALS['xmlrpcString']);
3487
+ }
3488
+ else if (in_array('null_extension', $options))
3489
+ {
3490
+ $xmlrpc_val = new xmlrpcval('', $GLOBALS['xmlrpcNull']);
3491
+ }
3492
+ else
3493
+ {
3494
+ $xmlrpc_val = new xmlrpcval();
3495
+ }
3496
+ break;
3497
+ case 'resource':
3498
+ if (in_array('extension_api', $options))
3499
+ {
3500
+ $xmlrpc_val = new xmlrpcval((int)$php_val, $GLOBALS['xmlrpcInt']);
3501
+ }
3502
+ else
3503
+ {
3504
+ $xmlrpc_val = new xmlrpcval();
3505
+ }
3506
+ // catch "user function", "unknown type"
3507
+ default:
3508
+ // giancarlo pinerolo <ping@alt.it>
3509
+ // it has to return
3510
+ // an empty object in case, not a boolean.
3511
+ $xmlrpc_val = new xmlrpcval();
3512
+ break;
3513
+ }
3514
+ return $xmlrpc_val;
3515
+ }
3516
+
3517
+ /**
3518
+ * Convert the xml representation of a method response, method request or single
3519
+ * xmlrpc value into the appropriate object (a.k.a. deserialize)
3520
+ * @param string $xml_val
3521
+ * @param array $options
3522
+ * @return mixed false on error, or an instance of either xmlrpcval, xmlrpcmsg or xmlrpcresp
3523
+ */
3524
+ function php_xmlrpc_decode_xml($xml_val, $options=array())
3525
+ {
3526
+ $GLOBALS['_xh'] = array();
3527
+ $GLOBALS['_xh']['ac'] = '';
3528
+ $GLOBALS['_xh']['stack'] = array();
3529
+ $GLOBALS['_xh']['valuestack'] = array();
3530
+ $GLOBALS['_xh']['params'] = array();
3531
+ $GLOBALS['_xh']['pt'] = array();
3532
+ $GLOBALS['_xh']['isf'] = 0;
3533
+ $GLOBALS['_xh']['isf_reason'] = '';
3534
+ $GLOBALS['_xh']['method'] = false;
3535
+ $GLOBALS['_xh']['rt'] = '';
3536
+ /// @todo 'guestimate' encoding
3537
+ $parser = xml_parser_create();
3538
+ xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
3539
+ // What if internal encoding is not in one of the 3 allowed?
3540
+ // we use the broadest one, ie. utf8!
3541
+ if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
3542
+ {
3543
+ xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
3544
+ }
3545
+ else
3546
+ {
3547
+ xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
3548
+ }
3549
+ xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee');
3550
+ xml_set_character_data_handler($parser, 'xmlrpc_cd');
3551
+ xml_set_default_handler($parser, 'xmlrpc_dh');
3552
+ if(!xml_parse($parser, $xml_val, 1))
3553
+ {
3554
+ $errstr = sprintf('XML error: %s at line %d, column %d',
3555
+ xml_error_string(xml_get_error_code($parser)),
3556
+ xml_get_current_line_number($parser), xml_get_current_column_number($parser));
3557
+ error_log($errstr);
3558
+ xml_parser_free($parser);
3559
+ return false;
3560
+ }
3561
+ xml_parser_free($parser);
3562
+ if ($GLOBALS['_xh']['isf'] > 1) // test that $GLOBALS['_xh']['value'] is an obj, too???
3563
+ {
3564
+ error_log($GLOBALS['_xh']['isf_reason']);
3565
+ return false;
3566
+ }
3567
+ switch ($GLOBALS['_xh']['rt'])
3568
+ {
3569
+ case 'methodresponse':
3570
+ $v =& $GLOBALS['_xh']['value'];
3571
+ if ($GLOBALS['_xh']['isf'] == 1)
3572
+ {
3573
+ $vc = $v->structmem('faultCode');
3574
+ $vs = $v->structmem('faultString');
3575
+ $r = new xmlrpcresp(0, $vc->scalarval(), $vs->scalarval());
3576
+ }
3577
+ else
3578
+ {
3579
+ $r = new xmlrpcresp($v);
3580
+ }
3581
+ return $r;
3582
+ case 'methodcall':
3583
+ $m = new xmlrpcmsg($GLOBALS['_xh']['method']);
3584
+ for($i=0; $i < count($GLOBALS['_xh']['params']); $i++)
3585
+ {
3586
+ $m->addParam($GLOBALS['_xh']['params'][$i]);
3587
+ }
3588
+ return $m;
3589
+ case 'value':
3590
+ return $GLOBALS['_xh']['value'];
3591
+ default:
3592
+ return false;
3593
+ }
3594
+ }
3595
+
3596
+ /**
3597
+ * decode a string that is encoded w/ "chunked" transfer encoding
3598
+ * as defined in rfc2068 par. 19.4.6
3599
+ * code shamelessly stolen from nusoap library by Dietrich Ayala
3600
+ *
3601
+ * @param string $buffer the string to be decoded
3602
+ * @return string
3603
+ */
3604
+ function decode_chunked($buffer)
3605
+ {
3606
+ // length := 0
3607
+ $length = 0;
3608
+ $new = '';
3609
+
3610
+ // read chunk-size, chunk-extension (if any) and crlf
3611
+ // get the position of the linebreak
3612
+ $chunkend = strpos($buffer,"\r\n") + 2;
3613
+ $temp = substr($buffer,0,$chunkend);
3614
+ $chunk_size = hexdec( trim($temp) );
3615
+ $chunkstart = $chunkend;
3616
+ while($chunk_size > 0)
3617
+ {
3618
+ $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size);
3619
+
3620
+ // just in case we got a broken connection
3621
+ if($chunkend == false)
3622
+ {
3623
+ $chunk = substr($buffer,$chunkstart);
3624
+ // append chunk-data to entity-body
3625
+ $new .= $chunk;
3626
+ $length += strlen($chunk);
3627
+ break;
3628
+ }
3629
+
3630
+ // read chunk-data and crlf
3631
+ $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
3632
+ // append chunk-data to entity-body
3633
+ $new .= $chunk;
3634
+ // length := length + chunk-size
3635
+ $length += strlen($chunk);
3636
+ // read chunk-size and crlf
3637
+ $chunkstart = $chunkend + 2;
3638
+
3639
+ $chunkend = strpos($buffer,"\r\n",$chunkstart)+2;
3640
+ if($chunkend == false)
3641
+ {
3642
+ break; //just in case we got a broken connection
3643
+ }
3644
+ $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
3645
+ $chunk_size = hexdec( trim($temp) );
3646
+ $chunkstart = $chunkend;
3647
+ }
3648
+ return $new;
3649
+ }
3650
+
3651
+ /**
3652
+ * xml charset encoding guessing helper function.
3653
+ * Tries to determine the charset encoding of an XML chunk received over HTTP.
3654
+ * NB: according to the spec (RFC 3023), if text/xml content-type is received over HTTP without a content-type,
3655
+ * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,
3656
+ * which will be most probably using UTF-8 anyway...
3657
+ *
3658
+ * @param string $httpheaders the http Content-type header
3659
+ * @param string $xmlchunk xml content buffer
3660
+ * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)
3661
+ *
3662
+ * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
3663
+ */
3664
+ function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null)
3665
+ {
3666
+ // discussion: see http://www.yale.edu/pclt/encoding/
3667
+ // 1 - test if encoding is specified in HTTP HEADERS
3668
+
3669
+ //Details:
3670
+ // LWS: (\13\10)?( |\t)+
3671
+ // token: (any char but excluded stuff)+
3672
+ // quoted string: " (any char but double quotes and cointrol chars)* "
3673
+ // header: Content-type = ...; charset=value(; ...)*
3674
+ // where value is of type token, no LWS allowed between 'charset' and value
3675
+ // Note: we do not check for invalid chars in VALUE:
3676
+ // this had better be done using pure ereg as below
3677
+ // Note 2: we might be removing whitespace/tabs that ought to be left in if
3678
+ // the received charset is a quoted string. But nobody uses such charset names...
3679
+
3680
+ /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
3681
+ $matches = array();
3682
+ if(preg_match('/;\s*charset\s*=([^;]+)/i', $httpheader, $matches))
3683
+ {
3684
+ return strtoupper(trim($matches[1], " \t\""));
3685
+ }
3686
+
3687
+ // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
3688
+ // (source: http://www.w3.org/TR/2000/REC-xml-20001006)
3689
+ // NOTE: actually, according to the spec, even if we find the BOM and determine
3690
+ // an encoding, we should check if there is an encoding specified
3691
+ // in the xml declaration, and verify if they match.
3692
+ /// @todo implement check as described above?
3693
+ /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
3694
+ if(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk))
3695
+ {
3696
+ return 'UCS-4';
3697
+ }
3698
+ elseif(preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk))
3699
+ {
3700
+ return 'UTF-16';
3701
+ }
3702
+ elseif(preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk))
3703
+ {
3704
+ return 'UTF-8';
3705
+ }
3706
+
3707
+ // 3 - test if encoding is specified in the xml declaration
3708
+ // Details:
3709
+ // SPACE: (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
3710
+ // EQ: SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
3711
+ if (preg_match('/^<\?xml\s+version\s*=\s*'. "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))".
3712
+ '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
3713
+ $xmlchunk, $matches))
3714
+ {
3715
+ return strtoupper(substr($matches[2], 1, -1));
3716
+ }
3717
+
3718
+ // 4 - if mbstring is available, let it do the guesswork
3719
+ // NB: we favour finding an encoding that is compatible with what we can process
3720
+ if(extension_loaded('mbstring'))
3721
+ {
3722
+ if($encoding_prefs)
3723
+ {
3724
+ $enc = mb_detect_encoding($xmlchunk, $encoding_prefs);
3725
+ }
3726
+ else
3727
+ {
3728
+ $enc = mb_detect_encoding($xmlchunk);
3729
+ }
3730
+ // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
3731
+ // IANA also likes better US-ASCII, so go with it
3732
+ if($enc == 'ASCII')
3733
+ {
3734
+ $enc = 'US-'.$enc;
3735
+ }
3736
+ return $enc;
3737
+ }
3738
+ else
3739
+ {
3740
+ // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
3741
+ // Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types
3742
+ // this should be the standard. And we should be getting text/xml as request and response.
3743
+ // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
3744
+ return $GLOBALS['xmlrpc_defencoding'];
3745
+ }
3746
+ }
3747
+
3748
+ /**
3749
+ * Checks if a given charset encoding is present in a list of encodings or
3750
+ * if it is a valid subset of any encoding in the list
3751
+ * @param string $encoding charset to be tested
3752
+ * @param mixed $validlist comma separated list of valid charsets (or array of charsets)
3753
+ */
3754
+ function is_valid_charset($encoding, $validlist)
3755
+ {
3756
+ $charset_supersets = array(
3757
+ 'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
3758
+ 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8',
3759
+ 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12',
3760
+ 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8',
3761
+ 'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN')
3762
+ );
3763
+ if (is_string($validlist))
3764
+ $validlist = explode(',', $validlist);
3765
+ if (@in_array(strtoupper($encoding), $validlist))
3766
+ return true;
3767
+ else
3768
+ {
3769
+ if (array_key_exists($encoding, $charset_supersets))
3770
+ foreach ($validlist as $allowed)
3771
+ if (in_array($allowed, $charset_supersets[$encoding]))
3772
+ return true;
3773
+ return false;
3774
+ }
3775
+ }
3776
+
3777
+ ?>
i18n/cleantalk-ru_RU.mo ADDED
Binary file
readme.txt ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Plugin Name ===
2
+ Contributors: znaeff, default.asp, shagimuratov, aleontiev
3
+ Tags: comments, spam, cleantalk, anti-spam, antispam, captcha, comment, spambot, spam-bot, recaptcha, block spam, Spam Free, spamfree
4
+ Requires at least: 3.1.2
5
+ License: GPLv2
6
+ Tested up to: 3.5.1
7
+ Stable tag: trunk
8
+
9
+ Spam free WordPress.
10
+
11
+ == Description ==
12
+
13
+ Plugin stops spambots without move spam comments to trash or manual approval queue, invisible for users and admins. Every new comment compares with article and previous comments. If the relevance of the comment is good enough it gets approval at the blog without manual approval.
14
+
15
+ 1. Stop spam-bots in comments.
16
+ 1. Invisible for users.
17
+ 1. Antispam without CAPTCHA.
18
+ 1. Automatic approval not spam comments.
19
+
20
+ This plugin is a client application for anti-spam service cleantalk.org. It is free to use for small and medium sized blogs.
21
+
22
+ == Installation ==
23
+
24
+ Please use <a href="http://cleantalk.org/install/wordpress" target="_blank">Setup manual</a> at the plugin's site
25
+
26
+ == Frequently Asked Questions ==
27
+
28
+ = How plugin stop spam? =
29
+ Plugin use several simple tests to stop spambots
30
+
31
+ * Blacklists checks by Email, IP in lists with several billions records.
32
+ * JavaScript availability.
33
+ * Comment submit time.
34
+ * Relevance test for the comment.
35
+ * Spam signatures.
36
+
37
+ = How plugin works? =
38
+
39
+ Plugin sends a comment's text and several previous approved comments to the servers. Servers evaluates the relevance of the comment's text on the topic, tests on spam and finaly provides a solution - to publish or put on manual moderation of comments. If a comment is placed on manual moderation, the plugin adds to the text of a comment explaining the reason for the ban server publishing.
40
+
41
+ = Why do I need one more anti-spam plugin? =
42
+
43
+ 1. The plugin is more effective than CAPTCHA because use several methods to stop spambots.
44
+ 1. This plugin stops spam-bots automatically, plugin invisible for blog visitors and admins.
45
+ 1. CleanTalk automatically approves relevant, not spam comments.
46
+
47
+ == Screenshots ==
48
+
49
+ 1. Plug-in's anti-spam work scheme
50
+ 1. CleanTalk stops spam comment
51
+ 1. CleanTalk settings to filter spam-bots
52
+ 1. Service Control panel at cleantalk.org
53
+
54
+ == Changelog ==
55
+
56
+ = 2.4.9 =
57
+ * Fixed extra debugging in base class
58
+
59
+ = 2.4.8 =
60
+ * Enabled convertion to UTF8 for comment and example text
61
+ * Optimized PHP code
62
+
63
+ = 2.3.8 =
64
+ * Enabled selection the fastest server in the pool
65
+ * Fixed work server in plugin's config
66
+
67
+ = 2.2.3 =
68
+ * Secured md5 string for JavaScript test
69
+ * Added requests's timestamp to calculate request work time
70
+ * Update base CleanTalk's PHP class
71
+
72
+ = 2.1.2 =
73
+ * Improved perfomance for processing large comments (over 32kb size)
74
+ * Improved perfomance for bulk operations with comments in Comments panel
75
+ * Added feedback request with URL to approved comment
76
+
77
+ = 2.0.2 =
78
+ * Fixed bug with JavaScript test and WordPress cache plugins
79
+
80
+ = 2.0.1 =
81
+ * Added option "Publicate relevant comments" to plugin's options.
82
+ * Added descriptions to plugin options
83
+
84
+ = 1.5.4 =
85
+ * Fixed HTTP_REFERER transmission to the servers
86
+ * Improved JavaScript spam test
87
+ * Optimized PHP code
88
+
89
+ = 1.4.4 =
90
+ * Pingback, trackback comments has moved to manual moderataion
91
+ * Added transmission to the serves comment type and URL
92
+ * Post title, body and comments separated into individual data elements
93
+ * Added priority for matched words in the comment with post title
94
+ * Enabled stop words filtration as default option
95
+
96
+ = 1.3.4 =
97
+ * Removed PHP debugging.
98
+
99
+ = 1.3.3 =
100
+ * Added notice at admin panel about empty Access key in plugin settings
101
+ * Removed HTTP link to the site project from post page
102
+ * Removed unused options from settings page
103
+ * Tested up to WordPress 3.5
104
+
105
+ = 1.2.3 =
106
+ * Fixed bug with session_start.
107
+
108
+ = 1.2.2 =
109
+ * Plugin rename to CleanTalk. Spam prevent plugin
110
+ * Integration Base Class version 0.7
111
+ * Added fast submit check
112
+ * Added check website in form
113
+ * Added feedbacks for change comment status (Not spam, unapprove)
114
+ * Added function move comment in spam folder if CleanTalk say is spam
115
+ * Disable checking for user groups Administrator, Author, Editor
116
+ * Marked red color bad words
117
+
118
+ = 1.1.2 =
119
+ * Addition: Title of the post attached to the example text in auto publication tool.
120
+ * Tested with WordPress 3.4.1.
121
+
122
+ = 1.1.1 =
123
+ * HTTP_REFERER bug fixed
124
+
125
+ = 1.1.1 =
126
+ * Added user locale support, tested up to WP 3.4
127
+
128
+ = 1.1.0 =
129
+ * First version
130
+
131
+ == Upgrade Notice ==
132
+
133
+ = 1.1.2 =
134
+ * Addition: Title of the post attached to the example text in auto publication tool.
135
+ * Tested with WordPress 3.4.1.
136
+
137
+ = 1.1.1 =
138
+ * Added user locale support, tested up to WP 3.4
139
+
140
+ = 1.1.0 =
141
+ * First version
screenshot-1.png ADDED
Binary file
screenshot-2.png ADDED
Binary file
screenshot-3.png ADDED
Binary file
screenshot-4.png ADDED
Binary file