Cleantalk_Antispam - Version 1.2.0

Version Notes

Added automatic reception of API key.
Added universal anti-spam forms checking.

Download this release

Release Info

Developer CleanTalk.Org
Extension Cleantalk_Antispam
Version 1.2.0
Comparing to
See all releases


Code changes from version 1.0.0 to 1.2.0

app/code/community/Cleantalk/Antispam/Block/adminhtml/Notifications.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Cleantalk_Antispam_Block_Adminhtml_Notifications extends Mage_Adminhtml_Block_Template
3
+ {
4
+ public function getMessage()
5
+ {
6
+ /*
7
+ * Here you have check if there's a message to be displayed or not
8
+ */
9
+ $show_notice=intval(Mage::getStoreConfig('general/cleantalk/show_notice'));
10
+ $message='';
11
+ if($show_notice==1)
12
+ {
13
+ $message = "Like Anti-spam by CleanTalk? Help others learn about CleanTalk! <a target='_blank' href='http://www.magentocommerce.com/magento-connect/antispam-by-cleantalk.html'>Leave a review at the Magento Connect</a> <a href='?close_notice=1' style='float:right;'>Close</a>";
14
+ }
15
+ return $message;
16
+ }
17
+ }
18
+ ?>
app/code/community/Cleantalk/Antispam/Model/Api.php CHANGED
@@ -7,6 +7,19 @@ class Cleantalk_Antispam_Model_Api extends Mage_Core_Model_Abstract
7
  {
8
  $this->_init('antispam/api');
9
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  /**
12
  * Universal method for page addon
@@ -101,7 +114,7 @@ ctSetCookie("%s", "%s");
101
  $ct_request->sender_email = isset($arEntity['sender_email']) ? $arEntity['sender_email'] : '';
102
  $ct_request->sender_nickname = isset($arEntity['sender_nickname']) ? $arEntity['sender_nickname'] : '';
103
  $ct_request->sender_ip = isset($arEntity['sender_ip']) ? $arEntity['sender_ip'] : $sender_ip;
104
- $ct_request->agent = 'magento-100';
105
  $ct_request->js_on = $checkjs;
106
  $ct_request->sender_info = $sender_info;
107
 
7
  {
8
  $this->_init('antispam/api');
9
  }
10
+
11
+ /**
12
+ * Universal method for error message
13
+ * @return error template
14
+ */
15
+
16
+ static function CleantalkDie($message)
17
+ {
18
+ $error_tpl=file_get_contents(dirname(__FILE__)."/error.html");
19
+ print str_replace('%ERROR_TEXT%',$message,$error_tpl);
20
+ die();
21
+ }
22
+
23
 
24
  /**
25
  * Universal method for page addon
114
  $ct_request->sender_email = isset($arEntity['sender_email']) ? $arEntity['sender_email'] : '';
115
  $ct_request->sender_nickname = isset($arEntity['sender_nickname']) ? $arEntity['sender_nickname'] : '';
116
  $ct_request->sender_ip = isset($arEntity['sender_ip']) ? $arEntity['sender_ip'] : $sender_ip;
117
+ $ct_request->agent = 'magento-120';
118
  $ct_request->js_on = $checkjs;
119
  $ct_request->sender_info = $sender_info;
120
 
app/code/community/Cleantalk/Antispam/Model/Observer.php ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Cleantalk_Antispam_Model_Observer
4
+ {
5
+ public function interceptOutput(Varien_Event_Observer $observer)
6
+ {
7
+ $transport = $observer->getTransport();
8
+ $html = $transport->getHtml();
9
+ if(strpos($html,'<div class="middle"')!==false&&isset($_GET['cleantalk_message']))
10
+ {
11
+ $html=str_replace('<div class="middle"','<div class="notification-global notification-global-error"><b>CleanTalk error: '.$_GET['cleantalk_message'].'</b></div><div class="middle"',$html);
12
+ $transport->setHtml($html);
13
+ }
14
+ $show_notice=intval(Mage::getStoreConfig('general/cleantalk/show_notice'));
15
+ if(strpos($html,'<div class="middle"')!==false&&$show_notice==1)
16
+ {
17
+ $message = "Like Anti-spam by CleanTalk? Help others learn about CleanTalk! <a target='_blank' href='http://www.magentocommerce.com/magento-connect/antispam-by-cleantalk.html'>Leave a review at the Magento Connect</a> <a href='?close_notice=1' style='float:right;'>Close</a>";
18
+ $html=str_replace('<div class="middle"','<div class="notification-global notification-global-notice">'.$message.'</div><div class="middle"',$html);
19
+ $transport->setHtml($html);
20
+ }
21
+ if(strpos($html,'%LINK_TEXT%')!==false)
22
+ {
23
+ $api_key = Mage::getStoreConfig('general/cleantalk/api_key');
24
+ if(trim($api_key)=='')
25
+ {
26
+ Mage::app()->cleanCache();
27
+ $admin_email=Mage::getStoreConfig('trans_email/ident_general/email');
28
+ $button="<input type='button' style='margin-top:5px;-webkit-border-bottom-left-radius: 5px;-webkit-border-bottom-right-radius: 5px;-webkit-border-radius: 5px;-webkit-border-top-left-radius: 5px;-webkit-border-top-right-radius: 5px;background: #3399FF;border-radius: 5px;box-sizing: border-box;color: #FFFFFF;font: normal normal 400 14px/16.2px \"Open Sans\";padding:3px;border:0px none;cursor:pointer;' value='Get access key automatically' onclick='location.href=\"?get_auto_key=1\"'><br /><a target='_blank' href='https://cleantalk.org/register?platform=magento&email=".$admin_email."&website=".$_SERVER['HTTP_HOST']."'>Click here to get access key manually</a><br />Admin e-mail (".$admin_email.") will be used for registration<br /><a target='__blank' href='http://cleantalk.org/publicoffer' style='color:#e5e5e5'>License agreement</a>";
29
+ $html=str_replace('%LINK_TEXT%',$button,$html);
30
+ }
31
+ else
32
+ {
33
+ $html=str_replace('%LINK_TEXT%',"<a target='__blank' href='http://cleantalk.org/my' >Click here to get anti-spam statistics</a>",$html);
34
+ }
35
+ $transport->setHtml($html);
36
+ }
37
+
38
+ }
39
+ public function interceptQuery(Varien_Event_Observer $observer)
40
+ {
41
+ if(isset($_GET['close_notice']))
42
+ {
43
+ $config = new Mage_Core_Model_Config();
44
+ $config->saveConfig('general/cleantalk/show_notice', 0, 'default', 0);
45
+ Mage::app()->cleanCache();
46
+ header('Location: .');
47
+ die();
48
+ }
49
+
50
+ if(isset($_GET['get_auto_key']))
51
+ {
52
+ Mage::getSingleton('core/session', array('name'=>'adminhtml'));
53
+ if(Mage::getSingleton('admin/session')->isLoggedIn())
54
+ {
55
+ require_once 'lib/cleantalk.class.php';
56
+ $admin_email=Mage::getStoreConfig('trans_email/ident_general/email');
57
+ $site=$_SERVER['HTTP_HOST'];
58
+ $result = getAutoKey($admin_email,$site,'magento');
59
+ if ($result)
60
+ {
61
+ $result = json_decode($result, true);
62
+ if (isset($result['data']) && is_array($result['data']))
63
+ {
64
+ $result = $result['data'];
65
+ }
66
+ else if(isset($result['error_no']))
67
+ {
68
+ header('Location: ?cleantalk_message='.$result['error_message']);
69
+ die();
70
+ }
71
+ if(isset($result['auth_key']))
72
+ {
73
+ Mage::app()->cleanCache();
74
+ $config = new Mage_Core_Model_Config();
75
+ $config->saveConfig('general/cleantalk/api_key', $result['auth_key'], 'default', 0);
76
+ Cleantalk_Antispam_Model_Observer::CleantalkTestMessage($result['auth_key']);
77
+ }
78
+ header('Location: .');
79
+ die();
80
+ }
81
+ }
82
+ }
83
+
84
+ if(isset($_POST['groups']['cleantalk']['fields']['api_key']['value']))
85
+ {
86
+ $new_key=$_POST['groups']['cleantalk']['fields']['api_key']['value'];
87
+ $old_key = Mage::getStoreConfig('general/cleantalk/api_key');
88
+ if($old_key!=$new_key&&$new_key!='')
89
+ {
90
+ Cleantalk_Antispam_Model_Observer::CleantalkTestMessage($new_key);
91
+ }
92
+ }
93
+
94
+ $isCustomForms = Mage::getStoreConfig('general/cleantalk/custom_forms');
95
+ //Mage::getSingleton('core/session', array('name'=>'adminhtml'));
96
+ if(isset($_COOKIE['adminhtml']))
97
+ {
98
+ $key=Mage::getStoreConfig('general/cleantalk/api_key');
99
+ $last_checked=intval(Mage::getStoreConfig('general/cleantalk/last_checked'));
100
+ $last_status=intval(Mage::getStoreConfig('general/cleantalk/is_paid'));
101
+ $new_checked=time();
102
+
103
+ if($key!='')
104
+ {
105
+ $new_status=$last_status;
106
+ if($new_checked-$last_checked>3600)
107
+ {
108
+ require_once 'lib/cleantalk.class.php';
109
+ $url = 'https://api.cleantalk.org';
110
+ $dt=Array(
111
+ 'auth_key'=>$key,
112
+ 'method_name'=> 'get_account_status');
113
+ $result=sendRawRequest($url,$dt,false);
114
+ if($result!==null)
115
+ {
116
+ $result=json_decode($result);
117
+ if(isset($result->data)&&isset($result->data->paid))
118
+ {
119
+ $new_status=intval($result->data->paid);
120
+ if($last_status!=1&&$new_status==1)
121
+ {
122
+ $config = new Mage_Core_Model_Config();
123
+ $config->saveConfig('general/cleantalk/is_paid', '1', 'default', 0);
124
+ $config->saveConfig('general/cleantalk/show_notice', '1', 'default', 0);
125
+ Mage::app()->cleanCache();
126
+ }
127
+ }
128
+ }
129
+ $config = new Mage_Core_Model_Config();
130
+ $config->saveConfig('general/cleantalk/last_checked', $new_checked, 'default', 0);
131
+ }
132
+ }
133
+ }
134
+ if(!isset($_COOKIE['adminhtml'])&&$isCustomForms==1&&sizeof($_POST)>0)
135
+ {
136
+
137
+ $sender_email = null;
138
+ $message = '';
139
+ Cleantalk_Antispam_Model_Observer::cleantalkGetFields($sender_email,$message,$_POST);
140
+ if($sender_email!==null)
141
+ {
142
+ $aMessage = array();
143
+ $aMessage['type'] = 'comment';
144
+ $aMessage['sender_email'] = $sender_email;
145
+ $aMessage['sender_nickname'] = '';
146
+ $aMessage['message_title'] = '';
147
+ $aMessage['message_body'] = $message;
148
+ $aMessage['example_title'] = '';
149
+ $aMessage['example_body'] = '';
150
+ $aMessage['example_comments'] = '';
151
+
152
+ $model = Mage::getModel('antispam/api');
153
+ $aResult = $model->CheckSpam($aMessage, FALSE);
154
+
155
+ if(isset($aResult) && is_array($aResult))
156
+ {
157
+ if($aResult['errno'] == 0)
158
+ {
159
+ if($aResult['allow'] == 0)
160
+ {
161
+ if (preg_match('//u', $aResult['ct_result_comment']))
162
+ {
163
+ $comment_str = preg_replace('/^[^\*]*?\*\*\*|\*\*\*[^\*]*?$/iu', '', $aResult['ct_result_comment']);
164
+ $comment_str = preg_replace('/<[^<>]*>/iu', '', $comment_str);
165
+ }
166
+ else
167
+ {
168
+ $comment_str = preg_replace('/^[^\*]*?\*\*\*|\*\*\*[^\*]*?$/i', '', $aResult['ct_result_comment']);
169
+ $comment_str = preg_replace('/<[^<>]*>/i', '', $comment_str);
170
+ }
171
+ Mage::getModel('antispam/api')->CleantalkDie($comment_str);
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+
180
+ /*
181
+ * Sends test message when api key is changed
182
+ */
183
+ public function CleantalkTestMessage($key)
184
+ {
185
+ require_once 'lib/cleantalk.class.php';
186
+ $url = 'http://moderate.cleantalk.org/api2.0';
187
+ $dt=Array(
188
+ 'auth_key'=>$key,
189
+ 'method_name'=> 'check_message',
190
+ 'message'=>'CleanTalk connection test',
191
+ 'example'=>null,
192
+ 'agent'=>'magento-120',
193
+ 'sender_ip'=>$_SERVER['REMOTE_ADDR'],
194
+ 'sender_email'=>'stop_email@example.com',
195
+ 'sender_nickname'=>'CleanTalk',
196
+ 'js_on'=>1);
197
+ $result=sendRawRequest($url,$dt,true);
198
+ return $result;
199
+ }
200
+
201
+ /**
202
+ * Get all fields from array
203
+ * @param string email variable
204
+ * @param string message variable
205
+ * @param array array, containing fields
206
+ */
207
+
208
+ static function cleantalkGetFields(&$email,&$message,$arr)
209
+ {
210
+ $is_continue=true;
211
+ foreach($arr as $key=>$value)
212
+ {
213
+ if(strpos($key,'ct_checkjs')!==false)
214
+ {
215
+ $email=null;
216
+ $message='';
217
+ $is_continue=false;
218
+ }
219
+ }
220
+ if($is_continue)
221
+ {
222
+ foreach($arr as $key=>$value)
223
+ {
224
+ if(!is_array($value))
225
+ {
226
+ if ($email === null && preg_match("/^\S+@\S+\.\S+$/", $value))
227
+ {
228
+ $email = $value;
229
+ }
230
+ else
231
+ {
232
+ $message.="$value\n";
233
+ }
234
+ }
235
+ else
236
+ {
237
+ Cleantalk_Antispam_Model_Observer::cleantalkGetFields($email,$message,$value);
238
+ }
239
+ }
240
+ }
241
+ }
242
+ }
243
+
244
+ ?>
app/code/community/Cleantalk/Antispam/Model/error.html ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <!-- Ticket #11289, IE bug fix: always pad the error page with enough characters such that it is greater than 512 bytes, even after gzip compression abcdefghijklmnopqrstuvwxyz1234567890aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz11223344556677889900abacbcbdcdcededfefegfgfhghgihihjijikjkjlklkmlmlnmnmononpopoqpqprqrqsrsrtstsubcbcdcdedefefgfabcadefbghicjkldmnoepqrfstugvwxhyz1i234j567k890laabmbccnddeoeffpgghqhiirjjksklltmmnunoovppqwqrrxsstytuuzvvw0wxx1yyz2z113223434455666777889890091abc2def3ghi4jkl5mno6pqr7stu8vwx9yz11aab2bcc3dd4ee5ff6gg7hh8ii9j0jk1kl2lmm3nnoo4p5pq6qrr7ss8tt9uuvv0wwx1x2yyzz13aba4cbcb5dcdc6dedfef8egf9gfh0ghg1ihi2hji3jik4jkj5lkl6kml7mln8mnm9ono
3
+ -->
4
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">
5
+ <head>
6
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
7
+ <title>Blacklisted</title>
8
+ <style type="text/css">
9
+ html {
10
+ background: #f1f1f1;
11
+ }
12
+ body {
13
+ background: #fff;
14
+ color: #444;
15
+ font-family: "Open Sans", sans-serif;
16
+ margin: 2em auto;
17
+ padding: 1em 2em;
18
+ max-width: 700px;
19
+ -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.13);
20
+ box-shadow: 0 1px 3px rgba(0,0,0,0.13);
21
+ }
22
+ h1 {
23
+ border-bottom: 1px solid #dadada;
24
+ clear: both;
25
+ color: #666;
26
+ font: 24px "Open Sans", sans-serif;
27
+ margin: 30px 0 0 0;
28
+ padding: 0;
29
+ padding-bottom: 7px;
30
+ }
31
+ #error-page {
32
+ margin-top: 50px;
33
+ }
34
+ #error-page p {
35
+ font-size: 14px;
36
+ line-height: 1.5;
37
+ margin: 25px 0 20px;
38
+ }
39
+ a {
40
+ color: #21759B;
41
+ text-decoration: none;
42
+ }
43
+ a:hover {
44
+ color: #D54E21;
45
+ }
46
+
47
+ </style>
48
+ </head>
49
+ <body id="error-page">
50
+ <p><center><b style="color: #49C73B;">Clean</b><b style="color: #349ebf;">Talk.</b> Spam protection</center><br><br>
51
+ %ERROR_TEXT%
52
+ <script>setTimeout("history.back()", 5000);</script></p>
53
+ <p><a href='javascript:history.back()'>&laquo; Back</a></p></body>
54
+ </html>
app/code/community/Cleantalk/Antispam/Model/lib/JSON.php CHANGED
@@ -1,806 +1,806 @@
1
- <?php
2
- /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
-
4
- /**
5
- * Converts to and from JSON format.
6
- *
7
- * JSON (JavaScript Object Notation) is a lightweight data-interchange
8
- * format. It is easy for humans to read and write. It is easy for machines
9
- * to parse and generate. It is based on a subset of the JavaScript
10
- * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
11
- * This feature can also be found in Python. JSON is a text format that is
12
- * completely language independent but uses conventions that are familiar
13
- * to programmers of the C-family of languages, including C, C++, C#, Java,
14
- * JavaScript, Perl, TCL, and many others. These properties make JSON an
15
- * ideal data-interchange language.
16
- *
17
- * This package provides a simple encoder and decoder for JSON notation. It
18
- * is intended for use with client-side Javascript applications that make
19
- * use of HTTPRequest to perform server communication functions - data can
20
- * be encoded into JSON notation for use in a client-side javascript, or
21
- * decoded from incoming Javascript requests. JSON format is native to
22
- * Javascript, and can be directly eval()'ed with no further parsing
23
- * overhead
24
- *
25
- * All strings should be in ASCII or UTF-8 format!
26
- *
27
- * LICENSE: Redistribution and use in source and binary forms, with or
28
- * without modification, are permitted provided that the following
29
- * conditions are met: Redistributions of source code must retain the
30
- * above copyright notice, this list of conditions and the following
31
- * disclaimer. Redistributions in binary form must reproduce the above
32
- * copyright notice, this list of conditions and the following disclaimer
33
- * in the documentation and/or other materials provided with the
34
- * distribution.
35
- *
36
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
37
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
38
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
39
- * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
40
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
41
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
42
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
44
- * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
45
- * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
46
- * DAMAGE.
47
- *
48
- * @category
49
- * @package Services_JSON
50
- * @author Michal Migurski <mike-json@teczno.com>
51
- * @author Matt Knapp <mdknapp[at]gmail[dot]com>
52
- * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
53
- * @copyright 2005 Michal Migurski
54
- * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
55
- * @license http://www.opensource.org/licenses/bsd-license.php
56
- * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
57
- */
58
-
59
- /**
60
- * Marker constant for Services_JSON::decode(), used to flag stack state
61
- */
62
- define('SERVICES_JSON_SLICE', 1);
63
-
64
- /**
65
- * Marker constant for Services_JSON::decode(), used to flag stack state
66
- */
67
- define('SERVICES_JSON_IN_STR', 2);
68
-
69
- /**
70
- * Marker constant for Services_JSON::decode(), used to flag stack state
71
- */
72
- define('SERVICES_JSON_IN_ARR', 3);
73
-
74
- /**
75
- * Marker constant for Services_JSON::decode(), used to flag stack state
76
- */
77
- define('SERVICES_JSON_IN_OBJ', 4);
78
-
79
- /**
80
- * Marker constant for Services_JSON::decode(), used to flag stack state
81
- */
82
- define('SERVICES_JSON_IN_CMT', 5);
83
-
84
- /**
85
- * Behavior switch for Services_JSON::decode()
86
- */
87
- define('SERVICES_JSON_LOOSE_TYPE', 16);
88
-
89
- /**
90
- * Behavior switch for Services_JSON::decode()
91
- */
92
- define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
93
-
94
- /**
95
- * Converts to and from JSON format.
96
- *
97
- * Brief example of use:
98
- *
99
- * <code>
100
- * // create a new instance of Services_JSON
101
- * $json = new Services_JSON();
102
- *
103
- * // convert a complexe value to JSON notation, and send it to the browser
104
- * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
105
- * $output = $json->encode($value);
106
- *
107
- * print($output);
108
- * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
109
- *
110
- * // accept incoming POST data, assumed to be in JSON notation
111
- * $input = file_get_contents('php://input', 1000000);
112
- * $value = $json->decode($input);
113
- * </code>
114
- */
115
- class Services_JSON
116
- {
117
- /**
118
- * constructs a new JSON instance
119
- *
120
- * @param int $use object behavior flags; combine with boolean-OR
121
- *
122
- * possible values:
123
- * - SERVICES_JSON_LOOSE_TYPE: loose typing.
124
- * "{...}" syntax creates associative arrays
125
- * instead of objects in decode().
126
- * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
127
- * Values which can't be encoded (e.g. resources)
128
- * appear as NULL instead of throwing errors.
129
- * By default, a deeply-nested resource will
130
- * bubble up with an error, so all return values
131
- * from encode() should be checked with isError()
132
- */
133
- function Services_JSON($use = 0)
134
- {
135
- $this->use = $use;
136
- }
137
-
138
- /**
139
- * convert a string from one UTF-16 char to one UTF-8 char
140
- *
141
- * Normally should be handled by mb_convert_encoding, but
142
- * provides a slower PHP-only method for installations
143
- * that lack the multibye string extension.
144
- *
145
- * @param string $utf16 UTF-16 character
146
- * @return string UTF-8 character
147
- * @access private
148
- */
149
- function utf162utf8($utf16)
150
- {
151
- // oh please oh please oh please oh please oh please
152
- if(function_exists('mb_convert_encoding')) {
153
- return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
154
- }
155
-
156
- $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
157
-
158
- switch(true) {
159
- case ((0x7F & $bytes) == $bytes):
160
- // this case should never be reached, because we are in ASCII range
161
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
162
- return chr(0x7F & $bytes);
163
-
164
- case (0x07FF & $bytes) == $bytes:
165
- // return a 2-byte UTF-8 character
166
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
167
- return chr(0xC0 | (($bytes >> 6) & 0x1F))
168
- . chr(0x80 | ($bytes & 0x3F));
169
-
170
- case (0xFFFF & $bytes) == $bytes:
171
- // return a 3-byte UTF-8 character
172
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
173
- return chr(0xE0 | (($bytes >> 12) & 0x0F))
174
- . chr(0x80 | (($bytes >> 6) & 0x3F))
175
- . chr(0x80 | ($bytes & 0x3F));
176
- }
177
-
178
- // ignoring UTF-32 for now, sorry
179
- return '';
180
- }
181
-
182
- /**
183
- * convert a string from one UTF-8 char to one UTF-16 char
184
- *
185
- * Normally should be handled by mb_convert_encoding, but
186
- * provides a slower PHP-only method for installations
187
- * that lack the multibye string extension.
188
- *
189
- * @param string $utf8 UTF-8 character
190
- * @return string UTF-16 character
191
- * @access private
192
- */
193
- function utf82utf16($utf8)
194
- {
195
- // oh please oh please oh please oh please oh please
196
- if(function_exists('mb_convert_encoding')) {
197
- return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
198
- }
199
-
200
- switch(strlen($utf8)) {
201
- case 1:
202
- // this case should never be reached, because we are in ASCII range
203
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
204
- return $utf8;
205
-
206
- case 2:
207
- // return a UTF-16 character from a 2-byte UTF-8 char
208
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
209
- return chr(0x07 & (ord($utf8{0}) >> 2))
210
- . chr((0xC0 & (ord($utf8{0}) << 6))
211
- | (0x3F & ord($utf8{1})));
212
-
213
- case 3:
214
- // return a UTF-16 character from a 3-byte UTF-8 char
215
- // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
216
- return chr((0xF0 & (ord($utf8{0}) << 4))
217
- | (0x0F & (ord($utf8{1}) >> 2)))
218
- . chr((0xC0 & (ord($utf8{1}) << 6))
219
- | (0x7F & ord($utf8{2})));
220
- }
221
-
222
- // ignoring UTF-32 for now, sorry
223
- return '';
224
- }
225
-
226
- /**
227
- * encodes an arbitrary variable into JSON format
228
- *
229
- * @param mixed $var any number, boolean, string, array, or object to be encoded.
230
- * see argument 1 to Services_JSON() above for array-parsing behavior.
231
- * if var is a strng, note that encode() always expects it
232
- * to be in ASCII or UTF-8 format!
233
- *
234
- * @return mixed JSON string representation of input var or an error if a problem occurs
235
- * @access public
236
- */
237
- function encode($var)
238
- {
239
- switch (gettype($var)) {
240
- case 'boolean':
241
- return $var ? 'true' : 'false';
242
-
243
- case 'NULL':
244
- return 'null';
245
-
246
- case 'integer':
247
- return (int) $var;
248
-
249
- case 'double':
250
- case 'float':
251
- return (float) $var;
252
-
253
- case 'string':
254
- // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
255
- $ascii = '';
256
- $strlen_var = strlen($var);
257
-
258
- /*
259
- * Iterate over every character in the string,
260
- * escaping with a slash or encoding to UTF-8 where necessary
261
- */
262
- for ($c = 0; $c < $strlen_var; ++$c) {
263
-
264
- $ord_var_c = ord($var{$c});
265
-
266
- switch (true) {
267
- case $ord_var_c == 0x08:
268
- $ascii .= '\b';
269
- break;
270
- case $ord_var_c == 0x09:
271
- $ascii .= '\t';
272
- break;
273
- case $ord_var_c == 0x0A:
274
- $ascii .= '\n';
275
- break;
276
- case $ord_var_c == 0x0C:
277
- $ascii .= '\f';
278
- break;
279
- case $ord_var_c == 0x0D:
280
- $ascii .= '\r';
281
- break;
282
-
283
- case $ord_var_c == 0x22:
284
- case $ord_var_c == 0x2F:
285
- case $ord_var_c == 0x5C:
286
- // double quote, slash, slosh
287
- $ascii .= '\\'.$var{$c};
288
- break;
289
-
290
- case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
291
- // characters U-00000000 - U-0000007F (same as ASCII)
292
- $ascii .= $var{$c};
293
- break;
294
-
295
- case (($ord_var_c & 0xE0) == 0xC0):
296
- // characters U-00000080 - U-000007FF, mask 110XXXXX
297
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
298
- $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
299
- $c += 1;
300
- $utf16 = $this->utf82utf16($char);
301
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
302
- break;
303
-
304
- case (($ord_var_c & 0xF0) == 0xE0):
305
- // characters U-00000800 - U-0000FFFF, mask 1110XXXX
306
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
307
- $char = pack('C*', $ord_var_c,
308
- ord($var{$c + 1}),
309
- ord($var{$c + 2}));
310
- $c += 2;
311
- $utf16 = $this->utf82utf16($char);
312
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
313
- break;
314
-
315
- case (($ord_var_c & 0xF8) == 0xF0):
316
- // characters U-00010000 - U-001FFFFF, mask 11110XXX
317
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
318
- $char = pack('C*', $ord_var_c,
319
- ord($var{$c + 1}),
320
- ord($var{$c + 2}),
321
- ord($var{$c + 3}));
322
- $c += 3;
323
- $utf16 = $this->utf82utf16($char);
324
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
325
- break;
326
-
327
- case (($ord_var_c & 0xFC) == 0xF8):
328
- // characters U-00200000 - U-03FFFFFF, mask 111110XX
329
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
330
- $char = pack('C*', $ord_var_c,
331
- ord($var{$c + 1}),
332
- ord($var{$c + 2}),
333
- ord($var{$c + 3}),
334
- ord($var{$c + 4}));
335
- $c += 4;
336
- $utf16 = $this->utf82utf16($char);
337
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
338
- break;
339
-
340
- case (($ord_var_c & 0xFE) == 0xFC):
341
- // characters U-04000000 - U-7FFFFFFF, mask 1111110X
342
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
343
- $char = pack('C*', $ord_var_c,
344
- ord($var{$c + 1}),
345
- ord($var{$c + 2}),
346
- ord($var{$c + 3}),
347
- ord($var{$c + 4}),
348
- ord($var{$c + 5}));
349
- $c += 5;
350
- $utf16 = $this->utf82utf16($char);
351
- $ascii .= sprintf('\u%04s', bin2hex($utf16));
352
- break;
353
- }
354
- }
355
-
356
- return '"'.$ascii.'"';
357
-
358
- case 'array':
359
- /*
360
- * As per JSON spec if any array key is not an integer
361
- * we must treat the the whole array as an object. We
362
- * also try to catch a sparsely populated associative
363
- * array with numeric keys here because some JS engines
364
- * will create an array with empty indexes up to
365
- * max_index which can cause memory issues and because
366
- * the keys, which may be relevant, will be remapped
367
- * otherwise.
368
- *
369
- * As per the ECMA and JSON specification an object may
370
- * have any string as a property. Unfortunately due to
371
- * a hole in the ECMA specification if the key is a
372
- * ECMA reserved word or starts with a digit the
373
- * parameter is only accessible using ECMAScript's
374
- * bracket notation.
375
- */
376
-
377
- // treat as a JSON object
378
- if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
379
- $properties = array_map(array($this, 'name_value'),
380
- array_keys($var),
381
- array_values($var));
382
-
383
- foreach($properties as $property) {
384
- if(Services_JSON::isError($property)) {
385
- return $property;
386
- }
387
- }
388
-
389
- return '{' . join(',', $properties) . '}';
390
- }
391
-
392
- // treat it like a regular array
393
- $elements = array_map(array($this, 'encode'), $var);
394
-
395
- foreach($elements as $element) {
396
- if(Services_JSON::isError($element)) {
397
- return $element;
398
- }
399
- }
400
-
401
- return '[' . join(',', $elements) . ']';
402
-
403
- case 'object':
404
- $vars = get_object_vars($var);
405
-
406
- $properties = array_map(array($this, 'name_value'),
407
- array_keys($vars),
408
- array_values($vars));
409
-
410
- foreach($properties as $property) {
411
- if(Services_JSON::isError($property)) {
412
- return $property;
413
- }
414
- }
415
-
416
- return '{' . join(',', $properties) . '}';
417
-
418
- default:
419
- return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
420
- ? 'null'
421
- : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
422
- }
423
- }
424
-
425
- /**
426
- * array-walking function for use in generating JSON-formatted name-value pairs
427
- *
428
- * @param string $name name of key to use
429
- * @param mixed $value reference to an array element to be encoded
430
- *
431
- * @return string JSON-formatted name-value pair, like '"name":value'
432
- * @access private
433
- */
434
- function name_value($name, $value)
435
- {
436
- $encoded_value = $this->encode($value);
437
-
438
- if(Services_JSON::isError($encoded_value)) {
439
- return $encoded_value;
440
- }
441
-
442
- return $this->encode(strval($name)) . ':' . $encoded_value;
443
- }
444
-
445
- /**
446
- * reduce a string by removing leading and trailing comments and whitespace
447
- *
448
- * @param $str string string value to strip of comments and whitespace
449
- *
450
- * @return string string value stripped of comments and whitespace
451
- * @access private
452
- */
453
- function reduce_string($str)
454
- {
455
- $str = preg_replace(array(
456
-
457
- // eliminate single line comments in '// ...' form
458
- '#^\s*//(.+)$#m',
459
-
460
- // eliminate multi-line comments in '/* ... */' form, at start of string
461
- '#^\s*/\*(.+)\*/#Us',
462
-
463
- // eliminate multi-line comments in '/* ... */' form, at end of string
464
- '#/\*(.+)\*/\s*$#Us'
465
-
466
- ), '', $str);
467
-
468
- // eliminate extraneous space
469
- return trim($str);
470
- }
471
-
472
- /**
473
- * decodes a JSON string into appropriate variable
474
- *
475
- * @param string $str JSON-formatted string
476
- *
477
- * @return mixed number, boolean, string, array, or object
478
- * corresponding to given JSON input string.
479
- * See argument 1 to Services_JSON() above for object-output behavior.
480
- * Note that decode() always returns strings
481
- * in ASCII or UTF-8 format!
482
- * @access public
483
- */
484
- function decode($str)
485
- {
486
- $str = $this->reduce_string($str);
487
-
488
- switch (strtolower($str)) {
489
- case 'true':
490
- return true;
491
-
492
- case 'false':
493
- return false;
494
-
495
- case 'null':
496
- return null;
497
-
498
- default:
499
- $m = array();
500
-
501
- if (is_numeric($str)) {
502
- // Lookie-loo, it's a number
503
-
504
- // This would work on its own, but I'm trying to be
505
- // good about returning integers where appropriate:
506
- // return (float)$str;
507
-
508
- // Return float or int, as appropriate
509
- return ((float)$str == (integer)$str)
510
- ? (integer)$str
511
- : (float)$str;
512
-
513
- } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
514
- // STRINGS RETURNED IN UTF-8 FORMAT
515
- $delim = substr($str, 0, 1);
516
- $chrs = substr($str, 1, -1);
517
- $utf8 = '';
518
- $strlen_chrs = strlen($chrs);
519
-
520
- for ($c = 0; $c < $strlen_chrs; ++$c) {
521
-
522
- $substr_chrs_c_2 = substr($chrs, $c, 2);
523
- $ord_chrs_c = ord($chrs{$c});
524
-
525
- switch (true) {
526
- case $substr_chrs_c_2 == '\b':
527
- $utf8 .= chr(0x08);
528
- ++$c;
529
- break;
530
- case $substr_chrs_c_2 == '\t':
531
- $utf8 .= chr(0x09);
532
- ++$c;
533
- break;
534
- case $substr_chrs_c_2 == '\n':
535
- $utf8 .= chr(0x0A);
536
- ++$c;
537
- break;
538
- case $substr_chrs_c_2 == '\f':
539
- $utf8 .= chr(0x0C);
540
- ++$c;
541
- break;
542
- case $substr_chrs_c_2 == '\r':
543
- $utf8 .= chr(0x0D);
544
- ++$c;
545
- break;
546
-
547
- case $substr_chrs_c_2 == '\\"':
548
- case $substr_chrs_c_2 == '\\\'':
549
- case $substr_chrs_c_2 == '\\\\':
550
- case $substr_chrs_c_2 == '\\/':
551
- if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
552
- ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
553
- $utf8 .= $chrs{++$c};
554
- }
555
- break;
556
-
557
- case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
558
- // single, escaped unicode character
559
- $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
560
- . chr(hexdec(substr($chrs, ($c + 4), 2)));
561
- $utf8 .= $this->utf162utf8($utf16);
562
- $c += 5;
563
- break;
564
-
565
- case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
566
- $utf8 .= $chrs{$c};
567
- break;
568
-
569
- case ($ord_chrs_c & 0xE0) == 0xC0:
570
- // characters U-00000080 - U-000007FF, mask 110XXXXX
571
- //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
572
- $utf8 .= substr($chrs, $c, 2);
573
- ++$c;
574
- break;
575
-
576
- case ($ord_chrs_c & 0xF0) == 0xE0:
577
- // characters U-00000800 - U-0000FFFF, mask 1110XXXX
578
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
579
- $utf8 .= substr($chrs, $c, 3);
580
- $c += 2;
581
- break;
582
-
583
- case ($ord_chrs_c & 0xF8) == 0xF0:
584
- // characters U-00010000 - U-001FFFFF, mask 11110XXX
585
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
586
- $utf8 .= substr($chrs, $c, 4);
587
- $c += 3;
588
- break;
589
-
590
- case ($ord_chrs_c & 0xFC) == 0xF8:
591
- // characters U-00200000 - U-03FFFFFF, mask 111110XX
592
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
593
- $utf8 .= substr($chrs, $c, 5);
594
- $c += 4;
595
- break;
596
-
597
- case ($ord_chrs_c & 0xFE) == 0xFC:
598
- // characters U-04000000 - U-7FFFFFFF, mask 1111110X
599
- // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
600
- $utf8 .= substr($chrs, $c, 6);
601
- $c += 5;
602
- break;
603
-
604
- }
605
-
606
- }
607
-
608
- return $utf8;
609
-
610
- } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
611
- // array, or object notation
612
-
613
- if ($str{0} == '[') {
614
- $stk = array(SERVICES_JSON_IN_ARR);
615
- $arr = array();
616
- } else {
617
- if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
618
- $stk = array(SERVICES_JSON_IN_OBJ);
619
- $obj = array();
620
- } else {
621
- $stk = array(SERVICES_JSON_IN_OBJ);
622
- $obj = new stdClass();
623
- }
624
- }
625
-
626
- array_push($stk, array('what' => SERVICES_JSON_SLICE,
627
- 'where' => 0,
628
- 'delim' => false));
629
-
630
- $chrs = substr($str, 1, -1);
631
- $chrs = $this->reduce_string($chrs);
632
-
633
- if ($chrs == '') {
634
- if (reset($stk) == SERVICES_JSON_IN_ARR) {
635
- return $arr;
636
-
637
- } else {
638
- return $obj;
639
-
640
- }
641
- }
642
-
643
- //print("\nparsing {$chrs}\n");
644
-
645
- $strlen_chrs = strlen($chrs);
646
-
647
- for ($c = 0; $c <= $strlen_chrs; ++$c) {
648
-
649
- $top = end($stk);
650
- $substr_chrs_c_2 = substr($chrs, $c, 2);
651
-
652
- if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
653
- // found a comma that is not inside a string, array, etc.,
654
- // OR we've reached the end of the character list
655
- $slice = substr($chrs, $top['where'], ($c - $top['where']));
656
- array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
657
- //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
658
-
659
- if (reset($stk) == SERVICES_JSON_IN_ARR) {
660
- // we are in an array, so just push an element onto the stack
661
- array_push($arr, $this->decode($slice));
662
-
663
- } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
664
- // we are in an object, so figure
665
- // out the property name and set an
666
- // element in an associative array,
667
- // for now
668
- $parts = array();
669
-
670
- if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
671
- // "name":value pair
672
- $key = $this->decode($parts[1]);
673
- $val = $this->decode($parts[2]);
674
-
675
- if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
676
- $obj[$key] = $val;
677
- } else {
678
- $obj->$key = $val;
679
- }
680
- } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
681
- // name:value pair, where name is unquoted
682
- $key = $parts[1];
683
- $val = $this->decode($parts[2]);
684
-
685
- if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
686
- $obj[$key] = $val;
687
- } else {
688
- $obj->$key = $val;
689
- }
690
- }
691
-
692
- }
693
-
694
- } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
695
- // found a quote, and we are not inside a string
696
- array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
697
- //print("Found start of string at {$c}\n");
698
-
699
- } elseif (($chrs{$c} == $top['delim']) &&
700
- ($top['what'] == SERVICES_JSON_IN_STR) &&
701
- ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
702
- // found a quote, we're in a string, and it's not escaped
703
- // we know that it's not escaped becase there is _not_ an
704
- // odd number of backslashes at the end of the string so far
705
- array_pop($stk);
706
- //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
707
-
708
- } elseif (($chrs{$c} == '[') &&
709
- in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
710
- // found a left-bracket, and we are in an array, object, or slice
711
- array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
712
- //print("Found start of array at {$c}\n");
713
-
714
- } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
715
- // found a right-bracket, and we're in an array
716
- array_pop($stk);
717
- //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
718
-
719
- } elseif (($chrs{$c} == '{') &&
720
- in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
721
- // found a left-brace, and we are in an array, object, or slice
722
- array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
723
- //print("Found start of object at {$c}\n");
724
-
725
- } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
726
- // found a right-brace, and we're in an object
727
- array_pop($stk);
728
- //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
729
-
730
- } elseif (($substr_chrs_c_2 == '/*') &&
731
- in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
732
- // found a comment start, and we are in an array, object, or slice
733
- array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
734
- $c++;
735
- //print("Found start of comment at {$c}\n");
736
-
737
- } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
738
- // found a comment end, and we're in one now
739
- array_pop($stk);
740
- $c++;
741
-
742
- for ($i = $top['where']; $i <= $c; ++$i)
743
- $chrs = substr_replace($chrs, ' ', $i, 1);
744
-
745
- //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
746
-
747
- }
748
-
749
- }
750
-
751
- if (reset($stk) == SERVICES_JSON_IN_ARR) {
752
- return $arr;
753
-
754
- } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
755
- return $obj;
756
-
757
- }
758
-
759
- }
760
- }
761
- }
762
-
763
- /**
764
- * @todo Ultimately, this should just call PEAR::isError()
765
- */
766
- function isError($data, $code = null)
767
- {
768
- if (class_exists('pear')) {
769
- return PEAR::isError($data, $code);
770
- } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
771
- is_subclass_of($data, 'services_json_error'))) {
772
- return true;
773
- }
774
-
775
- return false;
776
- }
777
- }
778
-
779
- if (class_exists('PEAR_Error')) {
780
-
781
- class Services_JSON_Error extends PEAR_Error
782
- {
783
- function Services_JSON_Error($message = 'unknown error', $code = null,
784
- $mode = null, $options = null, $userinfo = null)
785
- {
786
- parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
787
- }
788
- }
789
-
790
- } else {
791
-
792
- /**
793
- * @todo Ultimately, this class shall be descended from PEAR_Error
794
- */
795
- class Services_JSON_Error
796
- {
797
- function Services_JSON_Error($message = 'unknown error', $code = null,
798
- $mode = null, $options = null, $userinfo = null)
799
- {
800
-
801
- }
802
- }
803
-
804
- }
805
-
806
- ?>
1
+ <?php
2
+ /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
+
4
+ /**
5
+ * Converts to and from JSON format.
6
+ *
7
+ * JSON (JavaScript Object Notation) is a lightweight data-interchange
8
+ * format. It is easy for humans to read and write. It is easy for machines
9
+ * to parse and generate. It is based on a subset of the JavaScript
10
+ * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
11
+ * This feature can also be found in Python. JSON is a text format that is
12
+ * completely language independent but uses conventions that are familiar
13
+ * to programmers of the C-family of languages, including C, C++, C#, Java,
14
+ * JavaScript, Perl, TCL, and many others. These properties make JSON an
15
+ * ideal data-interchange language.
16
+ *
17
+ * This package provides a simple encoder and decoder for JSON notation. It
18
+ * is intended for use with client-side Javascript applications that make
19
+ * use of HTTPRequest to perform server communication functions - data can
20
+ * be encoded into JSON notation for use in a client-side javascript, or
21
+ * decoded from incoming Javascript requests. JSON format is native to
22
+ * Javascript, and can be directly eval()'ed with no further parsing
23
+ * overhead
24
+ *
25
+ * All strings should be in ASCII or UTF-8 format!
26
+ *
27
+ * LICENSE: Redistribution and use in source and binary forms, with or
28
+ * without modification, are permitted provided that the following
29
+ * conditions are met: Redistributions of source code must retain the
30
+ * above copyright notice, this list of conditions and the following
31
+ * disclaimer. Redistributions in binary form must reproduce the above
32
+ * copyright notice, this list of conditions and the following disclaimer
33
+ * in the documentation and/or other materials provided with the
34
+ * distribution.
35
+ *
36
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
37
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
38
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
39
+ * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
40
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
41
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
42
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
44
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
45
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
46
+ * DAMAGE.
47
+ *
48
+ * @category
49
+ * @package Services_JSON
50
+ * @author Michal Migurski <mike-json@teczno.com>
51
+ * @author Matt Knapp <mdknapp[at]gmail[dot]com>
52
+ * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
53
+ * @copyright 2005 Michal Migurski
54
+ * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
55
+ * @license http://www.opensource.org/licenses/bsd-license.php
56
+ * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
57
+ */
58
+
59
+ /**
60
+ * Marker constant for Services_JSON::decode(), used to flag stack state
61
+ */
62
+ define('SERVICES_JSON_SLICE', 1);
63
+
64
+ /**
65
+ * Marker constant for Services_JSON::decode(), used to flag stack state
66
+ */
67
+ define('SERVICES_JSON_IN_STR', 2);
68
+
69
+ /**
70
+ * Marker constant for Services_JSON::decode(), used to flag stack state
71
+ */
72
+ define('SERVICES_JSON_IN_ARR', 3);
73
+
74
+ /**
75
+ * Marker constant for Services_JSON::decode(), used to flag stack state
76
+ */
77
+ define('SERVICES_JSON_IN_OBJ', 4);
78
+
79
+ /**
80
+ * Marker constant for Services_JSON::decode(), used to flag stack state
81
+ */
82
+ define('SERVICES_JSON_IN_CMT', 5);
83
+
84
+ /**
85
+ * Behavior switch for Services_JSON::decode()
86
+ */
87
+ define('SERVICES_JSON_LOOSE_TYPE', 16);
88
+
89
+ /**
90
+ * Behavior switch for Services_JSON::decode()
91
+ */
92
+ define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
93
+
94
+ /**
95
+ * Converts to and from JSON format.
96
+ *
97
+ * Brief example of use:
98
+ *
99
+ * <code>
100
+ * // create a new instance of Services_JSON
101
+ * $json = new Services_JSON();
102
+ *
103
+ * // convert a complexe value to JSON notation, and send it to the browser
104
+ * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
105
+ * $output = $json->encode($value);
106
+ *
107
+ * print($output);
108
+ * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
109
+ *
110
+ * // accept incoming POST data, assumed to be in JSON notation
111
+ * $input = file_get_contents('php://input', 1000000);
112
+ * $value = $json->decode($input);
113
+ * </code>
114
+ */
115
+ class Services_JSON
116
+ {
117
+ /**
118
+ * constructs a new JSON instance
119
+ *
120
+ * @param int $use object behavior flags; combine with boolean-OR
121
+ *
122
+ * possible values:
123
+ * - SERVICES_JSON_LOOSE_TYPE: loose typing.
124
+ * "{...}" syntax creates associative arrays
125
+ * instead of objects in decode().
126
+ * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
127
+ * Values which can't be encoded (e.g. resources)
128
+ * appear as NULL instead of throwing errors.
129
+ * By default, a deeply-nested resource will
130
+ * bubble up with an error, so all return values
131
+ * from encode() should be checked with isError()
132
+ */
133
+ function Services_JSON($use = 0)
134
+ {
135
+ $this->use = $use;
136
+ }
137
+
138
+ /**
139
+ * convert a string from one UTF-16 char to one UTF-8 char
140
+ *
141
+ * Normally should be handled by mb_convert_encoding, but
142
+ * provides a slower PHP-only method for installations
143
+ * that lack the multibye string extension.
144
+ *
145
+ * @param string $utf16 UTF-16 character
146
+ * @return string UTF-8 character
147
+ * @access private
148
+ */
149
+ function utf162utf8($utf16)
150
+ {
151
+ // oh please oh please oh please oh please oh please
152
+ if(function_exists('mb_convert_encoding')) {
153
+ return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
154
+ }
155
+
156
+ $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
157
+
158
+ switch(true) {
159
+ case ((0x7F & $bytes) == $bytes):
160
+ // this case should never be reached, because we are in ASCII range
161
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
162
+ return chr(0x7F & $bytes);
163
+
164
+ case (0x07FF & $bytes) == $bytes:
165
+ // return a 2-byte UTF-8 character
166
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
167
+ return chr(0xC0 | (($bytes >> 6) & 0x1F))
168
+ . chr(0x80 | ($bytes & 0x3F));
169
+
170
+ case (0xFFFF & $bytes) == $bytes:
171
+ // return a 3-byte UTF-8 character
172
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
173
+ return chr(0xE0 | (($bytes >> 12) & 0x0F))
174
+ . chr(0x80 | (($bytes >> 6) & 0x3F))
175
+ . chr(0x80 | ($bytes & 0x3F));
176
+ }
177
+
178
+ // ignoring UTF-32 for now, sorry
179
+ return '';
180
+ }
181
+
182
+ /**
183
+ * convert a string from one UTF-8 char to one UTF-16 char
184
+ *
185
+ * Normally should be handled by mb_convert_encoding, but
186
+ * provides a slower PHP-only method for installations
187
+ * that lack the multibye string extension.
188
+ *
189
+ * @param string $utf8 UTF-8 character
190
+ * @return string UTF-16 character
191
+ * @access private
192
+ */
193
+ function utf82utf16($utf8)
194
+ {
195
+ // oh please oh please oh please oh please oh please
196
+ if(function_exists('mb_convert_encoding')) {
197
+ return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
198
+ }
199
+
200
+ switch(strlen($utf8)) {
201
+ case 1:
202
+ // this case should never be reached, because we are in ASCII range
203
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
204
+ return $utf8;
205
+
206
+ case 2:
207
+ // return a UTF-16 character from a 2-byte UTF-8 char
208
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
209
+ return chr(0x07 & (ord($utf8{0}) >> 2))
210
+ . chr((0xC0 & (ord($utf8{0}) << 6))
211
+ | (0x3F & ord($utf8{1})));
212
+
213
+ case 3:
214
+ // return a UTF-16 character from a 3-byte UTF-8 char
215
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
216
+ return chr((0xF0 & (ord($utf8{0}) << 4))
217
+ | (0x0F & (ord($utf8{1}) >> 2)))
218
+ . chr((0xC0 & (ord($utf8{1}) << 6))
219
+ | (0x7F & ord($utf8{2})));
220
+ }
221
+
222
+ // ignoring UTF-32 for now, sorry
223
+ return '';
224
+ }
225
+
226
+ /**
227
+ * encodes an arbitrary variable into JSON format
228
+ *
229
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
230
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
231
+ * if var is a strng, note that encode() always expects it
232
+ * to be in ASCII or UTF-8 format!
233
+ *
234
+ * @return mixed JSON string representation of input var or an error if a problem occurs
235
+ * @access public
236
+ */
237
+ function encode($var)
238
+ {
239
+ switch (gettype($var)) {
240
+ case 'boolean':
241
+ return $var ? 'true' : 'false';
242
+
243
+ case 'NULL':
244
+ return 'null';
245
+
246
+ case 'integer':
247
+ return (int) $var;
248
+
249
+ case 'double':
250
+ case 'float':
251
+ return (float) $var;
252
+
253
+ case 'string':
254
+ // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
255
+ $ascii = '';
256
+ $strlen_var = strlen($var);
257
+
258
+ /*
259
+ * Iterate over every character in the string,
260
+ * escaping with a slash or encoding to UTF-8 where necessary
261
+ */
262
+ for ($c = 0; $c < $strlen_var; ++$c) {
263
+
264
+ $ord_var_c = ord($var{$c});
265
+
266
+ switch (true) {
267
+ case $ord_var_c == 0x08:
268
+ $ascii .= '\b';
269
+ break;
270
+ case $ord_var_c == 0x09:
271
+ $ascii .= '\t';
272
+ break;
273
+ case $ord_var_c == 0x0A:
274
+ $ascii .= '\n';
275
+ break;
276
+ case $ord_var_c == 0x0C:
277
+ $ascii .= '\f';
278
+ break;
279
+ case $ord_var_c == 0x0D:
280
+ $ascii .= '\r';
281
+ break;
282
+
283
+ case $ord_var_c == 0x22:
284
+ case $ord_var_c == 0x2F:
285
+ case $ord_var_c == 0x5C:
286
+ // double quote, slash, slosh
287
+ $ascii .= '\\'.$var{$c};
288
+ break;
289
+
290
+ case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
291
+ // characters U-00000000 - U-0000007F (same as ASCII)
292
+ $ascii .= $var{$c};
293
+ break;
294
+
295
+ case (($ord_var_c & 0xE0) == 0xC0):
296
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
297
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
298
+ $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
299
+ $c += 1;
300
+ $utf16 = $this->utf82utf16($char);
301
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
302
+ break;
303
+
304
+ case (($ord_var_c & 0xF0) == 0xE0):
305
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
306
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
307
+ $char = pack('C*', $ord_var_c,
308
+ ord($var{$c + 1}),
309
+ ord($var{$c + 2}));
310
+ $c += 2;
311
+ $utf16 = $this->utf82utf16($char);
312
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
313
+ break;
314
+
315
+ case (($ord_var_c & 0xF8) == 0xF0):
316
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
317
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
318
+ $char = pack('C*', $ord_var_c,
319
+ ord($var{$c + 1}),
320
+ ord($var{$c + 2}),
321
+ ord($var{$c + 3}));
322
+ $c += 3;
323
+ $utf16 = $this->utf82utf16($char);
324
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
325
+ break;
326
+
327
+ case (($ord_var_c & 0xFC) == 0xF8):
328
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
329
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
330
+ $char = pack('C*', $ord_var_c,
331
+ ord($var{$c + 1}),
332
+ ord($var{$c + 2}),
333
+ ord($var{$c + 3}),
334
+ ord($var{$c + 4}));
335
+ $c += 4;
336
+ $utf16 = $this->utf82utf16($char);
337
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
338
+ break;
339
+
340
+ case (($ord_var_c & 0xFE) == 0xFC):
341
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
342
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
343
+ $char = pack('C*', $ord_var_c,
344
+ ord($var{$c + 1}),
345
+ ord($var{$c + 2}),
346
+ ord($var{$c + 3}),
347
+ ord($var{$c + 4}),
348
+ ord($var{$c + 5}));
349
+ $c += 5;
350
+ $utf16 = $this->utf82utf16($char);
351
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
352
+ break;
353
+ }
354
+ }
355
+
356
+ return '"'.$ascii.'"';
357
+
358
+ case 'array':
359
+ /*
360
+ * As per JSON spec if any array key is not an integer
361
+ * we must treat the the whole array as an object. We
362
+ * also try to catch a sparsely populated associative
363
+ * array with numeric keys here because some JS engines
364
+ * will create an array with empty indexes up to
365
+ * max_index which can cause memory issues and because
366
+ * the keys, which may be relevant, will be remapped
367
+ * otherwise.
368
+ *
369
+ * As per the ECMA and JSON specification an object may
370
+ * have any string as a property. Unfortunately due to
371
+ * a hole in the ECMA specification if the key is a
372
+ * ECMA reserved word or starts with a digit the
373
+ * parameter is only accessible using ECMAScript's
374
+ * bracket notation.
375
+ */
376
+
377
+ // treat as a JSON object
378
+ if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
379
+ $properties = array_map(array($this, 'name_value'),
380
+ array_keys($var),
381
+ array_values($var));
382
+
383
+ foreach($properties as $property) {
384
+ if(Services_JSON::isError($property)) {
385
+ return $property;
386
+ }
387
+ }
388
+
389
+ return '{' . join(',', $properties) . '}';
390
+ }
391
+
392
+ // treat it like a regular array
393
+ $elements = array_map(array($this, 'encode'), $var);
394
+
395
+ foreach($elements as $element) {
396
+ if(Services_JSON::isError($element)) {
397
+ return $element;
398
+ }
399
+ }
400
+
401
+ return '[' . join(',', $elements) . ']';
402
+
403
+ case 'object':
404
+ $vars = get_object_vars($var);
405
+
406
+ $properties = array_map(array($this, 'name_value'),
407
+ array_keys($vars),
408
+ array_values($vars));
409
+
410
+ foreach($properties as $property) {
411
+ if(Services_JSON::isError($property)) {
412
+ return $property;
413
+ }
414
+ }
415
+
416
+ return '{' . join(',', $properties) . '}';
417
+
418
+ default:
419
+ return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
420
+ ? 'null'
421
+ : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
422
+ }
423
+ }
424
+
425
+ /**
426
+ * array-walking function for use in generating JSON-formatted name-value pairs
427
+ *
428
+ * @param string $name name of key to use
429
+ * @param mixed $value reference to an array element to be encoded
430
+ *
431
+ * @return string JSON-formatted name-value pair, like '"name":value'
432
+ * @access private
433
+ */
434
+ function name_value($name, $value)
435
+ {
436
+ $encoded_value = $this->encode($value);
437
+
438
+ if(Services_JSON::isError($encoded_value)) {
439
+ return $encoded_value;
440
+ }
441
+
442
+ return $this->encode(strval($name)) . ':' . $encoded_value;
443
+ }
444
+
445
+ /**
446
+ * reduce a string by removing leading and trailing comments and whitespace
447
+ *
448
+ * @param $str string string value to strip of comments and whitespace
449
+ *
450
+ * @return string string value stripped of comments and whitespace
451
+ * @access private
452
+ */
453
+ function reduce_string($str)
454
+ {
455
+ $str = preg_replace(array(
456
+
457
+ // eliminate single line comments in '// ...' form
458
+ '#^\s*//(.+)$#m',
459
+
460
+ // eliminate multi-line comments in '/* ... */' form, at start of string
461
+ '#^\s*/\*(.+)\*/#Us',
462
+
463
+ // eliminate multi-line comments in '/* ... */' form, at end of string
464
+ '#/\*(.+)\*/\s*$#Us'
465
+
466
+ ), '', $str);
467
+
468
+ // eliminate extraneous space
469
+ return trim($str);
470
+ }
471
+
472
+ /**
473
+ * decodes a JSON string into appropriate variable
474
+ *
475
+ * @param string $str JSON-formatted string
476
+ *
477
+ * @return mixed number, boolean, string, array, or object
478
+ * corresponding to given JSON input string.
479
+ * See argument 1 to Services_JSON() above for object-output behavior.
480
+ * Note that decode() always returns strings
481
+ * in ASCII or UTF-8 format!
482
+ * @access public
483
+ */
484
+ function decode($str)
485
+ {
486
+ $str = $this->reduce_string($str);
487
+
488
+ switch (strtolower($str)) {
489
+ case 'true':
490
+ return true;
491
+
492
+ case 'false':
493
+ return false;
494
+
495
+ case 'null':
496
+ return null;
497
+
498
+ default:
499
+ $m = array();
500
+
501
+ if (is_numeric($str)) {
502
+ // Lookie-loo, it's a number
503
+
504
+ // This would work on its own, but I'm trying to be
505
+ // good about returning integers where appropriate:
506
+ // return (float)$str;
507
+
508
+ // Return float or int, as appropriate
509
+ return ((float)$str == (integer)$str)
510
+ ? (integer)$str
511
+ : (float)$str;
512
+
513
+ } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
514
+ // STRINGS RETURNED IN UTF-8 FORMAT
515
+ $delim = substr($str, 0, 1);
516
+ $chrs = substr($str, 1, -1);
517
+ $utf8 = '';
518
+ $strlen_chrs = strlen($chrs);
519
+
520
+ for ($c = 0; $c < $strlen_chrs; ++$c) {
521
+
522
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
523
+ $ord_chrs_c = ord($chrs{$c});
524
+
525
+ switch (true) {
526
+ case $substr_chrs_c_2 == '\b':
527
+ $utf8 .= chr(0x08);
528
+ ++$c;
529
+ break;
530
+ case $substr_chrs_c_2 == '\t':
531
+ $utf8 .= chr(0x09);
532
+ ++$c;
533
+ break;
534
+ case $substr_chrs_c_2 == '\n':
535
+ $utf8 .= chr(0x0A);
536
+ ++$c;
537
+ break;
538
+ case $substr_chrs_c_2 == '\f':
539
+ $utf8 .= chr(0x0C);
540
+ ++$c;
541
+ break;
542
+ case $substr_chrs_c_2 == '\r':
543
+ $utf8 .= chr(0x0D);
544
+ ++$c;
545
+ break;
546
+
547
+ case $substr_chrs_c_2 == '\\"':
548
+ case $substr_chrs_c_2 == '\\\'':
549
+ case $substr_chrs_c_2 == '\\\\':
550
+ case $substr_chrs_c_2 == '\\/':
551
+ if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
552
+ ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
553
+ $utf8 .= $chrs{++$c};
554
+ }
555
+ break;
556
+
557
+ case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
558
+ // single, escaped unicode character
559
+ $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
560
+ . chr(hexdec(substr($chrs, ($c + 4), 2)));
561
+ $utf8 .= $this->utf162utf8($utf16);
562
+ $c += 5;
563
+ break;
564
+
565
+ case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
566
+ $utf8 .= $chrs{$c};
567
+ break;
568
+
569
+ case ($ord_chrs_c & 0xE0) == 0xC0:
570
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
571
+ //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
572
+ $utf8 .= substr($chrs, $c, 2);
573
+ ++$c;
574
+ break;
575
+
576
+ case ($ord_chrs_c & 0xF0) == 0xE0:
577
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
578
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
579
+ $utf8 .= substr($chrs, $c, 3);
580
+ $c += 2;
581
+ break;
582
+
583
+ case ($ord_chrs_c & 0xF8) == 0xF0:
584
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
585
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
586
+ $utf8 .= substr($chrs, $c, 4);
587
+ $c += 3;
588
+ break;
589
+
590
+ case ($ord_chrs_c & 0xFC) == 0xF8:
591
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
592
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
593
+ $utf8 .= substr($chrs, $c, 5);
594
+ $c += 4;
595
+ break;
596
+
597
+ case ($ord_chrs_c & 0xFE) == 0xFC:
598
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
599
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
600
+ $utf8 .= substr($chrs, $c, 6);
601
+ $c += 5;
602
+ break;
603
+
604
+ }
605
+
606
+ }
607
+
608
+ return $utf8;
609
+
610
+ } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
611
+ // array, or object notation
612
+
613
+ if ($str{0} == '[') {
614
+ $stk = array(SERVICES_JSON_IN_ARR);
615
+ $arr = array();
616
+ } else {
617
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
618
+ $stk = array(SERVICES_JSON_IN_OBJ);
619
+ $obj = array();
620
+ } else {
621
+ $stk = array(SERVICES_JSON_IN_OBJ);
622
+ $obj = new stdClass();
623
+ }
624
+ }
625
+
626
+ array_push($stk, array('what' => SERVICES_JSON_SLICE,
627
+ 'where' => 0,
628
+ 'delim' => false));
629
+
630
+ $chrs = substr($str, 1, -1);
631
+ $chrs = $this->reduce_string($chrs);
632
+
633
+ if ($chrs == '') {
634
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
635
+ return $arr;
636
+
637
+ } else {
638
+ return $obj;
639
+
640
+ }
641
+ }
642
+
643
+ //print("\nparsing {$chrs}\n");
644
+
645
+ $strlen_chrs = strlen($chrs);
646
+
647
+ for ($c = 0; $c <= $strlen_chrs; ++$c) {
648
+
649
+ $top = end($stk);
650
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
651
+
652
+ if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
653
+ // found a comma that is not inside a string, array, etc.,
654
+ // OR we've reached the end of the character list
655
+ $slice = substr($chrs, $top['where'], ($c - $top['where']));
656
+ array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
657
+ //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
658
+
659
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
660
+ // we are in an array, so just push an element onto the stack
661
+ array_push($arr, $this->decode($slice));
662
+
663
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
664
+ // we are in an object, so figure
665
+ // out the property name and set an
666
+ // element in an associative array,
667
+ // for now
668
+ $parts = array();
669
+
670
+ if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
671
+ // "name":value pair
672
+ $key = $this->decode($parts[1]);
673
+ $val = $this->decode($parts[2]);
674
+
675
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
676
+ $obj[$key] = $val;
677
+ } else {
678
+ $obj->$key = $val;
679
+ }
680
+ } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
681
+ // name:value pair, where name is unquoted
682
+ $key = $parts[1];
683
+ $val = $this->decode($parts[2]);
684
+
685
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
686
+ $obj[$key] = $val;
687
+ } else {
688
+ $obj->$key = $val;
689
+ }
690
+ }
691
+
692
+ }
693
+
694
+ } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
695
+ // found a quote, and we are not inside a string
696
+ array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
697
+ //print("Found start of string at {$c}\n");
698
+
699
+ } elseif (($chrs{$c} == $top['delim']) &&
700
+ ($top['what'] == SERVICES_JSON_IN_STR) &&
701
+ ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
702
+ // found a quote, we're in a string, and it's not escaped
703
+ // we know that it's not escaped becase there is _not_ an
704
+ // odd number of backslashes at the end of the string so far
705
+ array_pop($stk);
706
+ //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
707
+
708
+ } elseif (($chrs{$c} == '[') &&
709
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
710
+ // found a left-bracket, and we are in an array, object, or slice
711
+ array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
712
+ //print("Found start of array at {$c}\n");
713
+
714
+ } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
715
+ // found a right-bracket, and we're in an array
716
+ array_pop($stk);
717
+ //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
718
+
719
+ } elseif (($chrs{$c} == '{') &&
720
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
721
+ // found a left-brace, and we are in an array, object, or slice
722
+ array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
723
+ //print("Found start of object at {$c}\n");
724
+
725
+ } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
726
+ // found a right-brace, and we're in an object
727
+ array_pop($stk);
728
+ //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
729
+
730
+ } elseif (($substr_chrs_c_2 == '/*') &&
731
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
732
+ // found a comment start, and we are in an array, object, or slice
733
+ array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
734
+ $c++;
735
+ //print("Found start of comment at {$c}\n");
736
+
737
+ } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
738
+ // found a comment end, and we're in one now
739
+ array_pop($stk);
740
+ $c++;
741
+
742
+ for ($i = $top['where']; $i <= $c; ++$i)
743
+ $chrs = substr_replace($chrs, ' ', $i, 1);
744
+
745
+ //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
746
+
747
+ }
748
+
749
+ }
750
+
751
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
752
+ return $arr;
753
+
754
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
755
+ return $obj;
756
+
757
+ }
758
+
759
+ }
760
+ }
761
+ }
762
+
763
+ /**
764
+ * @todo Ultimately, this should just call PEAR::isError()
765
+ */
766
+ function isError($data, $code = null)
767
+ {
768
+ if (class_exists('pear')) {
769
+ return PEAR::isError($data, $code);
770
+ } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
771
+ is_subclass_of($data, 'services_json_error'))) {
772
+ return true;
773
+ }
774
+
775
+ return false;
776
+ }
777
+ }
778
+
779
+ if (class_exists('PEAR_Error')) {
780
+
781
+ class Services_JSON_Error extends PEAR_Error
782
+ {
783
+ function Services_JSON_Error($message = 'unknown error', $code = null,
784
+ $mode = null, $options = null, $userinfo = null)
785
+ {
786
+ parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
787
+ }
788
+ }
789
+
790
+ } else {
791
+
792
+ /**
793
+ * @todo Ultimately, this class shall be descended from PEAR_Error
794
+ */
795
+ class Services_JSON_Error
796
+ {
797
+ function Services_JSON_Error($message = 'unknown error', $code = null,
798
+ $mode = null, $options = null, $userinfo = null)
799
+ {
800
+
801
+ }
802
+ }
803
+
804
+ }
805
+
806
+ ?>
app/code/community/Cleantalk/Antispam/Model/lib/cleantalk.class.php CHANGED
@@ -2,11 +2,11 @@
2
  /**
3
  * Cleantalk base class
4
  *
5
- * @version 1.29
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 https://github.com/CleanTalk/php-antispam
12
  *
@@ -43,7 +43,7 @@ class CleantalkResponse {
43
  * @var int
44
  */
45
  public $stop_words = null;
46
-
47
  /**
48
  * Cleantalk comment
49
  * @var string
@@ -156,7 +156,7 @@ class CleantalkResponse {
156
  $this->errstr = $obj->errstr;
157
 
158
  $this->errstr = preg_replace("/.+(\*\*\*.+\*\*\*).+/", "$1", $this->errstr);
159
- // Разбираем ответ с клинтолка
160
  $this->stop_words = isset($obj->stop_words) ? utf8_decode($obj->stop_words) : null;
161
  $this->comment = isset($obj->comment) ? utf8_decode($obj->comment) : null;
162
  $this->blacklisted = (isset($obj->blacklisted)) ? $obj->blacklisted : null;
@@ -185,6 +185,12 @@ class CleantalkResponse {
185
  */
186
  class CleantalkRequest {
187
 
 
 
 
 
 
 
188
  /**
189
  * User message
190
  * @var string
@@ -343,7 +349,7 @@ class Cleantalk {
343
  * Server connection timeout in seconds
344
  * @var int
345
  */
346
- private $server_timeout = 15;
347
 
348
  /**
349
  * Cleantalk server url
@@ -399,24 +405,19 @@ class Cleantalk {
399
  */
400
  public $ssl_on = false;
401
 
 
 
 
 
 
 
402
  /**
403
  * Function checks whether it is possible to publish the message
404
  * @param CleantalkRequest $request
405
  * @return type
406
  */
407
  public function isAllowMessage(CleantalkRequest $request) {
408
- $error_params = $this->filterRequest('check_message', $request);
409
-
410
- if (!empty($error_params)) {
411
- $response = new CleantalkResponse(
412
- array(
413
- 'allow' => 0,
414
- 'comment' => 'CleanTalk. Request params error: ' . implode(', ', $error_params)
415
- ), null);
416
-
417
- return $response;
418
- }
419
-
420
  $msg = $this->createMsg('check_message', $request);
421
  return $this->httpRequest($msg);
422
  }
@@ -427,18 +428,7 @@ class Cleantalk {
427
  * @return type
428
  */
429
  public function isAllowUser(CleantalkRequest $request) {
430
- $error_params = $this->filterRequest('check_newuser', $request);
431
-
432
- if (!empty($error_params)) {
433
- $response = new CleantalkResponse(
434
- array(
435
- 'allow' => 0,
436
- 'comment' => 'CleanTalk. Request params error: ' . implode(', ', $error_params)
437
- ), null);
438
-
439
- return $response;
440
- }
441
-
442
  $msg = $this->createMsg('check_newuser', $request);
443
  return $this->httpRequest($msg);
444
  }
@@ -450,20 +440,8 @@ class Cleantalk {
450
  * @return type
451
  */
452
  public function sendFeedback(CleantalkRequest $request) {
453
- $error_params = $this->filterRequest('send_feedback', $request);
454
-
455
- if (!empty($error_params)) {
456
- $response = new CleantalkResponse(
457
- array(
458
- 'allow' => 0,
459
- 'comment' => 'Cleantalk. Spam protect. Request params error: ' . implode(', ', $error_params)
460
- ), null);
461
-
462
- return $response;
463
- }
464
-
465
  $msg = $this->createMsg('send_feedback', $request);
466
-
467
  return $this->httpRequest($msg);
468
  }
469
 
@@ -472,65 +450,46 @@ class Cleantalk {
472
  * @param CleantalkRequest $request
473
  * @return type
474
  */
475
- private function filterRequest($method, CleantalkRequest $request) {
476
- $error_params = array();
477
-
478
  // general and optional
479
  foreach ($request as $param => $value) {
480
  if (in_array($param, array('message', 'example', 'agent',
481
  'sender_info', 'sender_nickname', 'post_info', 'phone')) && !empty($value)) {
482
  if (!is_string($value) && !is_integer($value)) {
483
- $error_params[] = $param;
484
  }
485
  }
486
 
487
  if (in_array($param, array('stoplist_check', 'allow_links')) && !empty($value)) {
488
  if (!in_array($value, array(1, 2))) {
489
- $error_params[] = $param;
490
  }
491
  }
492
 
493
  if (in_array($param, array('js_on')) && !empty($value)) {
494
  if (!is_integer($value)) {
495
- $error_params[] = $param;
496
  }
497
  }
498
 
499
  if ($param == 'sender_ip' && !empty($value)) {
500
  if (!is_string($value)) {
501
- $error_params[] = $param;
502
  }
503
  }
504
 
505
  if ($param == 'sender_email' && !empty($value)) {
506
  if (!is_string($value)) {
507
- $error_params[] = $param;
508
  }
509
  }
510
 
511
  if ($param == 'submit_time' && !empty($value)) {
512
  if (!is_int($value)) {
513
- $error_params[] = $param;
514
  }
515
  }
516
  }
517
-
518
- // special and must be
519
- switch ($method) {
520
- case 'check_message':
521
- break;
522
-
523
- case 'check_newuser':
524
- break;
525
-
526
- case 'send_feedback':
527
- if (empty($request->feedback)) {
528
- $error_params[] = 'feedback';
529
- }
530
- break;
531
- }
532
-
533
- return $error_params;
534
  }
535
 
536
  /**
@@ -591,7 +550,16 @@ class Cleantalk {
591
  }
592
 
593
  $request->method_name = $method;
594
-
 
 
 
 
 
 
 
 
 
595
  return $request;
596
  }
597
 
@@ -628,6 +596,8 @@ class Cleantalk {
628
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
629
  // resolve 'Expect: 100-continue' issue
630
  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
 
 
631
 
632
  // Disabling CA cert verivication
633
  // Disabling common name verification
@@ -700,6 +670,7 @@ class Cleantalk {
700
  */
701
  private function httpRequest($msg) {
702
  $result = false;
 
703
  if (((isset($this->work_url) && $this->work_url !== '') && ($this->server_changed + $this->server_ttl > time()))
704
  || $this->stay_on_server == true) {
705
 
@@ -707,9 +678,8 @@ class Cleantalk {
707
 
708
  $result = $this->sendRequest($msg, $url, $this->server_timeout);
709
  }
710
-
711
  if (($result === false || $result->errno != 0) && $this->stay_on_server == false) {
712
-
713
  // Split server url to parts
714
  preg_match("@^(https?://)([^/:]+)(.*)@i", $this->server_url, $matches);
715
  $url_prefix = '';
@@ -735,13 +705,13 @@ class Cleantalk {
735
  if ($server['host'] === 'localhost' || $server['ip'] === null) {
736
  $work_url = $server['host'];
737
  } else {
738
- $server_host = gethostbyaddr($server['ip']);
739
  $work_url = $server_host;
740
  }
741
  $work_url = $url_prefix . $work_url;
742
  if (isset($url_suffix))
743
  $work_url = $work_url . $url_suffix;
744
-
745
  $this->work_url = $work_url;
746
  $this->server_ttl = $server['ttl'];
747
 
@@ -808,18 +778,29 @@ class Cleantalk {
808
  "ttl" => $this->server_ttl
809
  );
810
  } else {
811
-
812
- // $i - to resolve collisions with localhost and
813
  $i = 0;
814
  $r_temp = null;
 
815
  foreach ($response as $server) {
816
- $ping = $this->httpPing($server['ip']);
 
 
 
 
 
 
 
817
 
818
  // -1 server is down, skips not reachable server
819
- if ($ping != -1)
820
- $r_temp[$ping * 10000 + $i] = $server;
821
-
822
  $i++;
 
 
 
 
823
  }
824
  if (count($r_temp)){
825
  ksort($r_temp);
@@ -940,7 +921,6 @@ class Cleantalk {
940
  $file = @fsockopen ($host, 80, $errno, $errstr, $this->server_timeout);
941
  $stoptime = microtime(true);
942
  $status = 0;
943
-
944
  if (!$file) {
945
  $status = -1; // Site is down
946
  } else {
@@ -987,4 +967,96 @@ class Cleantalk {
987
  }
988
  }
989
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
990
  ?>
2
  /**
3
  * Cleantalk base class
4
  *
5
+ * @version 2.0
6
  * @package Cleantalk
7
  * @subpackage Base
8
+ * @author Cleantalk team (welcome@cleantalk.org)
9
+ * @copyright (C) 2014 CleanTalk team (http://cleantalk.org)
10
  * @license GNU/GPL: http://www.gnu.org/copyleft/gpl.html
11
  * @see https://github.com/CleanTalk/php-antispam
12
  *
43
  * @var int
44
  */
45
  public $stop_words = null;
46
+
47
  /**
48
  * Cleantalk comment
49
  * @var string
156
  $this->errstr = $obj->errstr;
157
 
158
  $this->errstr = preg_replace("/.+(\*\*\*.+\*\*\*).+/", "$1", $this->errstr);
159
+
160
  $this->stop_words = isset($obj->stop_words) ? utf8_decode($obj->stop_words) : null;
161
  $this->comment = isset($obj->comment) ? utf8_decode($obj->comment) : null;
162
  $this->blacklisted = (isset($obj->blacklisted)) ? $obj->blacklisted : null;
185
  */
186
  class CleantalkRequest {
187
 
188
+ /**
189
+ * All http request headers
190
+ * @var string
191
+ */
192
+ public $all_headers = null;
193
+
194
  /**
195
  * User message
196
  * @var string
349
  * Server connection timeout in seconds
350
  * @var int
351
  */
352
+ private $server_timeout = 3;
353
 
354
  /**
355
  * Cleantalk server url
405
  */
406
  public $ssl_on = false;
407
 
408
+ /**
409
+ * Minimal server response in miliseconds to catch the server
410
+ *
411
+ */
412
+ public $min_server_timeout = 50;
413
+
414
  /**
415
  * Function checks whether it is possible to publish the message
416
  * @param CleantalkRequest $request
417
  * @return type
418
  */
419
  public function isAllowMessage(CleantalkRequest $request) {
420
+ $this->filterRequest($request);
 
 
 
 
 
 
 
 
 
 
 
421
  $msg = $this->createMsg('check_message', $request);
422
  return $this->httpRequest($msg);
423
  }
428
  * @return type
429
  */
430
  public function isAllowUser(CleantalkRequest $request) {
431
+ $this->filterRequest($request);
 
 
 
 
 
 
 
 
 
 
 
432
  $msg = $this->createMsg('check_newuser', $request);
433
  return $this->httpRequest($msg);
434
  }
440
  * @return type
441
  */
442
  public function sendFeedback(CleantalkRequest $request) {
443
+ $this->filterRequest($request);
 
 
 
 
 
 
 
 
 
 
 
444
  $msg = $this->createMsg('send_feedback', $request);
 
445
  return $this->httpRequest($msg);
446
  }
447
 
450
  * @param CleantalkRequest $request
451
  * @return type
452
  */
453
+ private function filterRequest(CleantalkRequest &$request) {
 
 
454
  // general and optional
455
  foreach ($request as $param => $value) {
456
  if (in_array($param, array('message', 'example', 'agent',
457
  'sender_info', 'sender_nickname', 'post_info', 'phone')) && !empty($value)) {
458
  if (!is_string($value) && !is_integer($value)) {
459
+ $request->$param = NULL;
460
  }
461
  }
462
 
463
  if (in_array($param, array('stoplist_check', 'allow_links')) && !empty($value)) {
464
  if (!in_array($value, array(1, 2))) {
465
+ $request->$param = NULL;
466
  }
467
  }
468
 
469
  if (in_array($param, array('js_on')) && !empty($value)) {
470
  if (!is_integer($value)) {
471
+ $request->$param = NULL;
472
  }
473
  }
474
 
475
  if ($param == 'sender_ip' && !empty($value)) {
476
  if (!is_string($value)) {
477
+ $request->$param = NULL;
478
  }
479
  }
480
 
481
  if ($param == 'sender_email' && !empty($value)) {
482
  if (!is_string($value)) {
483
+ $request->$param = NULL;
484
  }
485
  }
486
 
487
  if ($param == 'submit_time' && !empty($value)) {
488
  if (!is_int($value)) {
489
+ $request->$param = NULL;
490
  }
491
  }
492
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  }
494
 
495
  /**
550
  }
551
 
552
  $request->method_name = $method;
553
+
554
+ //
555
+ // Removing non UTF8 characters from request, because non UTF8 or malformed characters break json_encode().
556
+ //
557
+ foreach ($request as $param => $value) {
558
+ if (!preg_match('//u', $value)) {
559
+ $request->{$param} = 'Nulled. Not UTF8 encoded or malformed.';
560
+ }
561
+ }
562
+
563
  return $request;
564
  }
565
 
596
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
597
  // resolve 'Expect: 100-continue' issue
598
  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
599
+ // see http://stackoverflow.com/a/23322368
600
+ curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
601
 
602
  // Disabling CA cert verivication
603
  // Disabling common name verification
670
  */
671
  private function httpRequest($msg) {
672
  $result = false;
673
+ $msg->all_headers=json_encode(apache_request_headers());
674
  if (((isset($this->work_url) && $this->work_url !== '') && ($this->server_changed + $this->server_ttl > time()))
675
  || $this->stay_on_server == true) {
676
 
678
 
679
  $result = $this->sendRequest($msg, $url, $this->server_timeout);
680
  }
681
+
682
  if (($result === false || $result->errno != 0) && $this->stay_on_server == false) {
 
683
  // Split server url to parts
684
  preg_match("@^(https?://)([^/:]+)(.*)@i", $this->server_url, $matches);
685
  $url_prefix = '';
705
  if ($server['host'] === 'localhost' || $server['ip'] === null) {
706
  $work_url = $server['host'];
707
  } else {
708
+ $server_host = $server['ip'];
709
  $work_url = $server_host;
710
  }
711
  $work_url = $url_prefix . $work_url;
712
  if (isset($url_suffix))
713
  $work_url = $work_url . $url_suffix;
714
+
715
  $this->work_url = $work_url;
716
  $this->server_ttl = $server['ttl'];
717
 
778
  "ttl" => $this->server_ttl
779
  );
780
  } else {
781
+ // $i - to resolve collisions with localhost
 
782
  $i = 0;
783
  $r_temp = null;
784
+ $fast_server_found = false;
785
  foreach ($response as $server) {
786
+
787
+ // Do not test servers because fast work server found
788
+ if ($fast_server_found) {
789
+ $ping = $this->min_server_timeout;
790
+ } else {
791
+ $ping = $this->httpPing($server['ip']);
792
+ $ping = $ping * 1000;
793
+ }
794
 
795
  // -1 server is down, skips not reachable server
796
+ if ($ping != -1) {
797
+ $r_temp[$ping + $i] = $server;
798
+ }
799
  $i++;
800
+
801
+ if ($ping < $this->min_server_timeout) {
802
+ $fast_server_found = true;
803
+ }
804
  }
805
  if (count($r_temp)){
806
  ksort($r_temp);
921
  $file = @fsockopen ($host, 80, $errno, $errstr, $this->server_timeout);
922
  $stoptime = microtime(true);
923
  $status = 0;
 
924
  if (!$file) {
925
  $status = -1; // Site is down
926
  } else {
967
  }
968
  }
969
 
970
+ /**
971
+ * Function gets access key automatically
972
+ *
973
+ * @param string website admin email
974
+ * @param string website host
975
+ * @param string website platform
976
+ * @return type
977
+ */
978
+
979
+ function getAutoKey($email, $host, $platform)
980
+ {
981
+ $request=Array();
982
+ $request['method_name'] = 'get_api_key';
983
+ $request['email'] = $email;
984
+ $request['website'] = $host;
985
+ $request['platform'] = $platform;
986
+ $url='https://api.cleantalk.org';
987
+ $result=sendRawRequest($url,$request);
988
+ return $result;
989
+ }
990
+
991
+ /**
992
+ * Function gets information about renew notice
993
+ *
994
+ * @param string api_key
995
+ * @return type
996
+ */
997
+
998
+ function noticePaidTill($api_key)
999
+ {
1000
+ $request=Array();
1001
+ $request['method_name'] = 'notice_paid_till';
1002
+ $request['auth_key'] = $api_key;
1003
+ $url='https://api.cleantalk.org';
1004
+ $result=sendRawRequest($url,$request);
1005
+ return $result;
1006
+ }
1007
+
1008
+ /**
1009
+ * Function sends raw request to API server
1010
+ *
1011
+ * @param string url of API server
1012
+ * @param array data to send
1013
+ * @param boolean is data have to be JSON encoded or not
1014
+ * @param integer connect timeout
1015
+ * @return type
1016
+ */
1017
+
1018
+ function sendRawRequest($url,$data,$isJSON=false,$timeout=3)
1019
+ {
1020
+ $result=null;
1021
+ if(!$isJSON)
1022
+ {
1023
+ $data=http_build_query($data);
1024
+ }
1025
+ else
1026
+ {
1027
+ $data= json_encode($data);
1028
+ }
1029
+ if (function_exists('curl_init') && function_exists('json_decode'))
1030
+ {
1031
+
1032
+ $ch = curl_init();
1033
+ curl_setopt($ch, CURLOPT_URL, $url);
1034
+ curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1035
+ curl_setopt($ch, CURLOPT_POST, true);
1036
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
1037
+
1038
+ // receive server response ...
1039
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1040
+ // resolve 'Expect: 100-continue' issue
1041
+ curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
1042
+
1043
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
1044
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
1045
+
1046
+ $result = curl_exec($ch);
1047
+ curl_close($ch);
1048
+ }
1049
+ else
1050
+ {
1051
+ $opts = array(
1052
+ 'http'=>array(
1053
+ 'method'=>"POST",
1054
+ 'content'=>$data)
1055
+ );
1056
+ $context = stream_context_create($opts);
1057
+ $result = @file_get_contents($url, 0, $context);
1058
+ }
1059
+ return $result;
1060
+ }
1061
+
1062
  ?>
app/code/community/Cleantalk/Antispam/etc/config.xml CHANGED
@@ -1,9 +1,12 @@
1
  <config>
2
  <modules>
3
  <Cleantalk_Antispam>
4
- <version>1.0.0</version>
5
  </Cleantalk_Antispam>
6
  </modules>
 
 
 
7
 
8
  <global>
9
  <rewrite>
@@ -57,6 +60,26 @@
57
  <class>Cleantalk_Antispam_Block</class>
58
  </antispam>
59
  </blocks>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
  </global>
62
 
@@ -70,13 +93,6 @@
70
  </args>
71
  </antispam>
72
  </routers>
73
- <layout>
74
- <updates>
75
- <antispam>
76
- <file>antispam.xml</file>
77
- </antispam>
78
- </updates>
79
- </layout>
80
  </frontend>
81
 
82
  </config>
1
  <config>
2
  <modules>
3
  <Cleantalk_Antispam>
4
+ <version>1.2.0</version>
5
  </Cleantalk_Antispam>
6
  </modules>
7
+ <adminhtml>
8
+
9
+ </adminhtml>
10
 
11
  <global>
12
  <rewrite>
60
  <class>Cleantalk_Antispam_Block</class>
61
  </antispam>
62
  </blocks>
63
+ <events>
64
+ <controller_front_init_before>
65
+ <observers>
66
+ <antispam>
67
+ <type>singleton</type>
68
+ <class>antispam/observer</class>
69
+ <method>interceptQuery</method>
70
+ </antispam>
71
+ </observers>
72
+ </controller_front_init_before>
73
+ <core_block_abstract_to_html_after>
74
+ <observers>
75
+ <antispam>
76
+ <type>singleton</type>
77
+ <class>antispam/observer</class>
78
+ <method>interceptOutput</method>
79
+ </antispam>
80
+ </observers>
81
+ </core_block_abstract_to_html_after>
82
+ </events>
83
 
84
  </global>
85
 
93
  </args>
94
  </antispam>
95
  </routers>
 
 
 
 
 
 
 
96
  </frontend>
97
 
98
  </config>
app/code/community/Cleantalk/Antispam/etc/system.xml CHANGED
@@ -11,15 +11,52 @@
11
  <show_in_website>1</show_in_website>
12
  <show_in_store>1</show_in_store>
13
  <fields>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  <api_key translate="label">
15
  <label>Access Key</label>
16
  <frontend_type>text</frontend_type>
17
- <comment><![CDATA[Click <a target="_blank" href="https://cleantalk.org/register?platform=magento">here</a> to get your access key]]></comment>
18
  <sort_order>1</sort_order>
19
  <show_in_default>1</show_in_default>
20
  <show_in_website>1</show_in_website>
21
  <show_in_store>1</show_in_store>
22
  </api_key>
 
 
 
 
 
 
 
 
 
 
23
  </fields>
24
  </cleantalk>
25
  </groups>
11
  <show_in_website>1</show_in_website>
12
  <show_in_store>1</show_in_store>
13
  <fields>
14
+ <is_paid translate="label">
15
+ <label>is_paid</label>
16
+ <frontend_type>text</frontend_type>
17
+ <comment>is_paid</comment>
18
+ <sort_order>1</sort_order>
19
+ <show_in_default>0</show_in_default>
20
+ <show_in_website>0</show_in_website>
21
+ <show_in_store>0</show_in_store>
22
+ </is_paid>
23
+ <last_checked translate="label">
24
+ <label>last_checked</label>
25
+ <frontend_type>text</frontend_type>
26
+ <comment>last_checked</comment>
27
+ <sort_order>1</sort_order>
28
+ <show_in_default>0</show_in_default>
29
+ <show_in_website>0</show_in_website>
30
+ <show_in_store>0</show_in_store>
31
+ </last_checked>
32
+ <show_notice translate="label">
33
+ <label>show_notice</label>
34
+ <frontend_type>text</frontend_type>
35
+ <comment>show_notice</comment>
36
+ <sort_order>1</sort_order>
37
+ <show_in_default>0</show_in_default>
38
+ <show_in_website>0</show_in_website>
39
+ <show_in_store>0</show_in_store>
40
+ </show_notice>
41
  <api_key translate="label">
42
  <label>Access Key</label>
43
  <frontend_type>text</frontend_type>
44
+ <comment><![CDATA[%LINK_TEXT%]]></comment>
45
  <sort_order>1</sort_order>
46
  <show_in_default>1</show_in_default>
47
  <show_in_website>1</show_in_website>
48
  <show_in_store>1</show_in_store>
49
  </api_key>
50
+ <custom_forms translate="label">
51
+ <label>Enable custom forms protection</label>
52
+ <frontend_type>select</frontend_type>
53
+ <source_model>adminhtml/system_config_source_yesno</source_model>
54
+ <comment><![CDATA[Anti spam protect for any Magento forms or themes contacts forms]]></comment>
55
+ <sort_order>1</sort_order>
56
+ <show_in_default>1</show_in_default>
57
+ <show_in_website>1</show_in_website>
58
+ <show_in_store>1</show_in_store>
59
+ </custom_forms>
60
  </fields>
61
  </cleantalk>
62
  </groups>
app/design/adminhtml/default/default/layout/cleantalk/antispam.xml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <layout version="0.1.0">
2
+ <default>
3
+ <reference name="notifications">
4
+ <block type="antispam/adminhtml_notifications" name="antispam_notifications" template="cleantalk/antispam/notifications.phtml"/>
5
+ </reference>
6
+ </default>
7
+ </layout>
app/design/frontend/base/default/layout/antispam.xml CHANGED
@@ -1,17 +1,7 @@
1
  <layout version="0.1.0">
2
- <customer_account_create>
3
- <reference name="content">
4
- <block type="antispam/antispam" name="account_create" template="cleantalk/antispam/page_addon.phtml" />
5
  </reference>
6
- </customer_account_create>
7
- <contacts_index_index>
8
- <reference name="content">
9
- <block type="antispam/antispam" name="index_index" template="cleantalk/antispam/page_addon.phtml" />
10
- </reference>
11
- </contacts_index_index>
12
- <review_product_list>
13
- <reference name="content">
14
- <block type="antispam/antispam" name="review_product_list" template="cleantalk/antispam/page_addon.phtml" />
15
- </reference>
16
- </review_product_list>
17
  </layout>
1
  <layout version="0.1.0">
2
+ <default>
3
+ <reference name="footer">
4
+ <block type="antispam/antispam" name="review_product_list" template="cleantalk/antispam/page_addon.phtml" />
5
  </reference>
6
+ </default>
 
 
 
 
 
 
 
 
 
 
7
  </layout>
package.xml CHANGED
@@ -1,7 +1,7 @@
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Cleantalk_Antispam</name>
4
- <version>1.0.0</version>
5
  <stability>stable</stability>
6
  <license>GPL</license>
7
  <channel>community</channel>
@@ -45,12 +45,12 @@ Current Magento module details:&#xD;
45
  &lt;/ul&gt;&#xD;
46
  &lt;/p&gt;&#xD;
47
  </description>
48
- <notes>First stable version.&#xD;
49
- </notes>
50
  <authors><author><name>CleanTalk.Org</name><user>cleantalk</user><email>welcome@cleantalk.org</email></author></authors>
51
- <date>2014-11-19</date>
52
- <time>06:17:02</time>
53
- <contents><target name="magecommunity"><dir name="Cleantalk"><dir name="Antispam"><dir name="Block"><file name="Antispam.php" hash="ac7ca7d7d54780fd2d10631751e510dc"/></dir><dir name="Helper"><file name="Data.php" hash="4634f33383dea8bdc330f42a3e5163b6"/></dir><dir name="Model"><file name="Api.php" hash="5c8cf1bd6c3ce7f585a0da34fc9b1fed"/><dir name="Resource"><file name="Server.php" hash="32f720d8083f09a59efb1516f2680ae2"/><file name="Timelabels.php" hash="e58a95495c8f34fdf757bcbd50335b57"/></dir><file name="Review.php" hash="8000551a7573d6e8b0a12f2a1897c067"/><file name="Server.php" hash="6f95b3579c3a2541011648f3c3eb374d"/><file name="Timelabels.php" hash="02a727f13ed833c233533b2f9f99a27d"/><dir name="lib"><file name="JSON.php" hash="3ce5a187c2869122f7cbcd14eec99447"/><file name="cleantalk.class.php" hash="6a7ca62f4c9f97186d96c20654d08e8d"/></dir></dir><dir name="controllers"><dir name="Contacts"><file name="IndexController.php" hash="ca35c9c50546bbe678920a1149b68c33"/></dir><dir name="Customer"><file name="AccountController.php" hash="b959e7e8e979dbafd3480565d5fd99a9"/></dir></dir><dir name="etc"><file name="config.xml" hash="2afe7407622f98511c9b6cb06ffc0d1b"/><file name="system.xml" hash="ad09554edb49ca3e30989e049b021a17"/></dir><dir name="sql"><dir name="cleantalk_antispam_setup"><file name="install-0.9.2.php" hash="a3f233fdf6c1987962cc7d579b555ab3"/></dir></dir></dir></dir></target><target name="magedesign"><dir name="frontend"><dir name="base"><dir name="default"><dir name="layout"><file name="antispam.xml" hash="7a13e3417d0fcebfba836e5f7679fc79"/></dir><dir name="template"><dir name="cleantalk"><dir name="antispam"><file name="page_addon.phtml" hash="0feda49e1149f0416cfe787b5a93eec4"/></dir></dir></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Cleantalk_Antispam.xml" hash="13a000f250a4c856aa9e9eb18bd4a827"/></dir></target></contents>
54
  <compatible/>
55
  <dependencies><required><php><min>5.1.0</min><max>6.0.0</max></php></required></dependencies>
56
  </package>
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Cleantalk_Antispam</name>
4
+ <version>1.2.0</version>
5
  <stability>stable</stability>
6
  <license>GPL</license>
7
  <channel>community</channel>
45
  &lt;/ul&gt;&#xD;
46
  &lt;/p&gt;&#xD;
47
  </description>
48
+ <notes>Added automatic reception of API key.&#xD;
49
+ Added universal anti-spam forms checking.</notes>
50
  <authors><author><name>CleanTalk.Org</name><user>cleantalk</user><email>welcome@cleantalk.org</email></author></authors>
51
+ <date>2015-04-28</date>
52
+ <time>18:22:19</time>
53
+ <contents><target name="magecommunity"><dir name="Cleantalk"><dir name="Antispam"><dir name="Block"><file name="Antispam.php" hash="ac7ca7d7d54780fd2d10631751e510dc"/><dir name="adminhtml"><file name="Notifications.php" hash="9cd13b972137786380697bbb7efd1be3"/></dir></dir><dir name="Helper"><file name="Data.php" hash="4634f33383dea8bdc330f42a3e5163b6"/></dir><dir name="Model"><file name="Api.php" hash="054763da367bbc3c0c4d0b6ef601b320"/><file name="Observer.php" hash="9d2a3f94c10f6e4b40bf658eb295e3a5"/><dir name="Resource"><file name="Server.php" hash="32f720d8083f09a59efb1516f2680ae2"/><file name="Timelabels.php" hash="e58a95495c8f34fdf757bcbd50335b57"/></dir><file name="Review.php" hash="8000551a7573d6e8b0a12f2a1897c067"/><file name="Server.php" hash="6f95b3579c3a2541011648f3c3eb374d"/><file name="Timelabels.php" hash="02a727f13ed833c233533b2f9f99a27d"/><file name="error.html" hash="4f34620367d82f9e77cfab7f35ea9df7"/><dir name="lib"><file name="JSON.php" hash="13c254265bb582c52f96d64971ab24df"/><file name="cleantalk.class.php" hash="6fc472296f93668473076523347eb2aa"/></dir></dir><dir name="controllers"><dir name="Contacts"><file name="IndexController.php" hash="ca35c9c50546bbe678920a1149b68c33"/></dir><dir name="Customer"><file name="AccountController.php" hash="b959e7e8e979dbafd3480565d5fd99a9"/></dir></dir><dir name="etc"><file name="config.xml" hash="7f88bac21bcfe51e648b6fa2333e3efd"/><file name="system.xml" hash="341390544fc21d544a38cf1570a0f975"/></dir><dir name="sql"><dir name="cleantalk_antispam_setup"><file name="install-0.9.2.php" hash="a3f233fdf6c1987962cc7d579b555ab3"/></dir></dir></dir></dir></target><target name="magedesign"><dir name="frontend"><dir name="base"><dir name="default"><dir name="layout"><file name="antispam.xml" hash="f2bd6b5e52407255d1ad1ce443bde0b2"/></dir><dir name="template"><dir name="cleantalk"><dir name="antispam"><file name="page_addon.phtml" hash="0feda49e1149f0416cfe787b5a93eec4"/></dir></dir></dir></dir></dir></dir><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><dir name="cleantalk"><file name="antispam.xml" hash="b4d926a64fc9d459acc3ed3618ac4a92"/></dir></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Cleantalk_Antispam.xml" hash="13a000f250a4c856aa9e9eb18bd4a827"/></dir></target></contents>
54
  <compatible/>
55
  <dependencies><required><php><min>5.1.0</min><max>6.0.0</max></php></required></dependencies>
56
  </package>