├── .gitignore ├── README.md ├── composer.json ├── config └── mqtt.php ├── index.php ├── src ├── Sam.php ├── SamConnection.php ├── SamMessage.php └── lib │ └── Mqtt.php └── tests ├── 16x16_loading.gif ├── README ├── index.php ├── jquery.js ├── jquery.label_over.js └── send_mqtt.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /vendor 3 | composer.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Mqtt Publisher 2 | Mqtt is used to publish messages from backend to andriod. 3 | 4 | ### Getting start 5 | ` 6 | composer require luzhuqun/laravel-mqtt-publish 7 | ` 8 | ### How to use 9 | 10 | use Lzq\Mqtt\SamMessage; 11 | use Lzq\Mqtt\SamConnection; 12 | 13 | $conn = new SAMConnection();//create a new connection object 14 | 15 | $conn->connect('mqtt', array('SAM_HOST' => '192.168.10.147', 'SAM_PORT' => '1883'));//start initialise the connection 16 | 17 | $msgCpu = new SAMMessage('hehe');//create a new MQTT message with the output of the shell command as the body 18 | 19 | $conn->send('topic://'.'tokudu/ab7867d9fd60db65', $msgCpu);//send the message on the topic cpu 20 | 21 | $conn->disconnect(); 22 | 23 | ### Learn more 24 | A complet mqtt service incluede publisher, service and subscriber. 25 | ##### service 26 | [mosquitto](https://github.com/eclipse/mosquitto) 27 | ##### subscriber 28 | [AndroidPushNotificationsDemo](https://github.com/tokudu/AndroidPushNotificationsDemo) 29 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "luzhuqun/laravel-mqtt-publish", 3 | "description": "A simple Laravel 5 mqtt publisher", 4 | "keywords": ["mqtt","laravel","publish"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "luzhuqun", 9 | "email": "595500783@qq.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.4.0" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "Lzq\\Mqtt\\": "src" 18 | } 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { 22 | "Lzq\\Mqtt\\Tests\\": "tests/" 23 | } 24 | }, 25 | "minimum-stability": "dev" 26 | } 27 | 28 | -------------------------------------------------------------------------------- /config/mqtt.php: -------------------------------------------------------------------------------- 1 | env('MQTT_SERVER_HOST', '192.168.10.147'), 5 | 'MQTT_SERVER_POST' => env('MQTT_SERVER_POST', '1883'), 6 | 'SAM_MQTT_CLEANSTART' => env('SAM_MQTT_CLEANSTART', 'SAM_MQTT_CLEANSTART'), 7 | 'SAM_MQTT_QOS' => env('SAM_MQTT_QOS', 2), 8 | 'SAM_MQTT_SUB_SEPARATOR' => env('SAM_MQTT_SUB_SEPARATOR', '#-#'), 9 | ]; 10 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | connect('mqtt', array('SAM_HOST' => '192.168.10.147', 'SAM_PORT' => '1883')); 12 | 13 | //create a new MQTT message with the output of the shell command as the body 14 | $msgCpu = new SAMMessage('hehe'); 15 | 16 | //send the message on the topic cpu 17 | $conn->send('topic://'.'tokudu/ab7867d9fd60db65', $msgCpu); 18 | 19 | $conn->disconnect(); -------------------------------------------------------------------------------- /src/Sam.php: -------------------------------------------------------------------------------- 1 | eol = "\n"; 18 | if (isset($_SERVER['REQUEST_URI'])) { 19 | $this->eol = '
'; 20 | } 21 | } 22 | 23 | protected function e($s) 24 | { 25 | echo '-->'.$s."$this->eol"; 26 | } 27 | 28 | protected function t($s) 29 | { 30 | echo ' '.$s."$this->eol"; 31 | } 32 | 33 | protected function x($s) 34 | { 35 | echo '<--'.$s."$this->eol"; 36 | } 37 | } -------------------------------------------------------------------------------- /src/SamConnection.php: -------------------------------------------------------------------------------- 1 | debug) { 17 | $this->e('SAMConnection()'); 18 | } 19 | 20 | if ($this->debug) { 21 | $this->x('SAMConnection()'); 22 | } 23 | } 24 | 25 | public function create($proto) 26 | { 27 | if ($this->debug) { 28 | $this->e("SAMConnection.Create(proto=$proto)"); 29 | } 30 | $rc = false; 31 | /* search the PHP config for a factory to use... */ 32 | $x = get_cfg_var('sam.factory.'.$proto); 33 | if ($this->debug) { 34 | $this->t('SAMConnection.Create() get_cfg_var() "'.$x.'"'); 35 | } 36 | 37 | /* If there is no configuration (php.ini) entry for this protocol, default it. */ 38 | if (strlen($x) == 0) { 39 | /* for every protocol other than MQTT assume we will use XMS */ 40 | if ($proto != 'mqtt') { 41 | $x = 'xms'; 42 | if (!class_exists('SAMXMSConnection')) { 43 | global $eol; 44 | $l = (strstr(PHP_OS, 'WIN') ? 'php_' : '').'sam_xms.'.(strstr(PHP_OS, 'WIN') ? 'dll' : 'so'); 45 | echo $eol.'Unable to access SAM XMS capabilities. Ensure the '.$l.' library is defined as an extension.'.$eol; 46 | $rc = false; 47 | } else { 48 | $rc = new SAMXMSConnection(); 49 | } 50 | } else { 51 | $x = 'mqtt'; 52 | $rc = new Mqtt(); 53 | } 54 | } 55 | 56 | if ($this->debug && $rc) { 57 | $this->t('SAMConnection.Create() rc = '.get_class($rc)); 58 | } 59 | if ($this->debug) { 60 | $this->x('SAMConnection.Create()'); 61 | } 62 | return $rc; 63 | } 64 | 65 | public function commit() 66 | { 67 | if ($this->debug) { 68 | $this->e('SAMConnection.Commit()'); 69 | } 70 | $rc = true; 71 | 72 | if (!$this->connection) { 73 | $errNo = 106; 74 | $error = 'No active connection!'; 75 | $rc = false; 76 | } else { 77 | /* Call the method on the underlying connection object... */ 78 | $rc = $this->connection->commit($target, $options); 79 | $this->errNo = $this->connection->errNo; 80 | $this->error = $this->connection->error; 81 | if (!$rc) { 82 | if ($this->debug) { 83 | $this->t("SAMConnection.Commit() commit failed ($this->errNo) $this->error"); 84 | } 85 | $rc = false; 86 | } 87 | } 88 | 89 | if ($this->debug) { 90 | $this->x("SAMConnection.Commit() rc=$rc"); 91 | } 92 | return $rc; 93 | } 94 | 95 | /* --------------------------------- 96 | Connect 97 | --------------------------------- */ 98 | public function connect($proto = '', $options = array()) 99 | { 100 | if ($this->debug) { 101 | $this->e('SAMConnection.connect()'); 102 | } 103 | $rc = false; 104 | 105 | if ($options['SAM_PORT'] == '') { 106 | $this->port = 1883; 107 | } else { 108 | $this->port = $options['SAM_PORT']; 109 | } 110 | if ($options['SAM_HOST'] == '') { 111 | $this->host = 'localhost'; 112 | } else { 113 | $this->host = $options['SAM_HOST']; 114 | } 115 | 116 | if ($proto == '') { 117 | $errNo = 101; 118 | $error = 'Incorrect number of parameters on connect call!'; 119 | $rc = false; 120 | } else { 121 | $this->connection = $this->create($proto); 122 | if (!$this->connection) { 123 | $errNo = 102; 124 | $error = 'Unsupported protocol!'; 125 | $rc = false; 126 | } else { 127 | if ($this->debug) { 128 | $this->t("SAMConnection.Connect() connection created for protocol $proto"); 129 | } 130 | 131 | $this->connection->setdebug($this->debug); 132 | 133 | /* Call the connect method on the newly created connection object... */ 134 | $rc = $this->connection->connect($proto, $options); 135 | $this->errNo = $this->connection->errNo; 136 | $this->error = $this->connection->error; 137 | if (!$rc) { 138 | if ($this->debug) { 139 | $this->t("SAMConnection.Connect() connect failed ($this->errNo) $this->error"); 140 | } 141 | } else { 142 | $rc = true; 143 | } 144 | } 145 | } 146 | 147 | if ($this->debug) { 148 | $this->x("SAMConnection.Connect() rc=$rc"); 149 | } 150 | return $rc; 151 | } 152 | 153 | /* --------------------------------- 154 | Disconnect 155 | --------------------------------- */ 156 | public function disconnect() 157 | { 158 | if ($this->debug) { 159 | $this->e('SAMConnection.Disconnect()'); 160 | } 161 | $rc = true; 162 | 163 | if (!$this->connection) { 164 | $errNo = 106; 165 | $error = 'No active connection!'; 166 | $rc = false; 167 | } else { 168 | /* Call the method on the underlying connection object... */ 169 | $rc = $this->connection->Disconnect(); 170 | $this->errNo = $this->connection->errNo; 171 | $this->error = $this->connection->error; 172 | if (!$rc) { 173 | if ($this->debug) { 174 | $this->t("SAMConnection.Disconnect() Disconnect failed ($this->errNo) $this->error"); 175 | } 176 | } else { 177 | $rc = true; 178 | $this->connection = false; 179 | } 180 | } 181 | 182 | if ($this->debug) { 183 | $this->x("SAMConnection.Disconnect() rc=$rc"); 184 | } 185 | return $rc; 186 | } 187 | 188 | /* --------------------------------- 189 | IsConnected 190 | --------------------------------- */ 191 | public function isConnected() 192 | { 193 | if ($this->debug) { 194 | $this->e('SAMConnection.IsConnected()'); 195 | } 196 | $rc = true; 197 | 198 | if (!$this->connection) { 199 | $errNo = 106; 200 | $error = 'No active connection!'; 201 | $rc = false; 202 | } else { 203 | /* Call the method on the underlying connection object... */ 204 | $rc = $this->connection->isconnected(); 205 | $this->errNo = $this->connection->errNo; 206 | $this->error = $this->connection->error; 207 | if (!$rc) { 208 | if ($this->debug) { 209 | $this->t("SAMConnection.IsConnected() isconnected failed ($this->errNo) $this->error"); 210 | } 211 | $rc = false; 212 | } 213 | } 214 | 215 | if ($this->debug) { 216 | $this->x("SAMConnection.IsConnected() rc=$rc"); 217 | } 218 | return $rc; 219 | } 220 | 221 | /* --------------------------------- 222 | Peek 223 | --------------------------------- */ 224 | public function peek($target, $options = array()) 225 | { 226 | if ($this->debug) { 227 | $this->e('SAMConnection.Peek()'); 228 | } 229 | $rc = true; 230 | 231 | if (!$this->connection) { 232 | $errNo = 106; 233 | $error = 'No active connection!'; 234 | $rc = false; 235 | } else { 236 | /* Call the method on the underlying connection object... */ 237 | $rc = $this->connection->peek($target, $options); 238 | $this->errNo = $this->connection->errNo; 239 | $this->error = $this->connection->error; 240 | if (!$rc) { 241 | if ($this->debug) { 242 | $this->t("SAMConnection.Peek() peek failed ($this->errNo) $this->error"); 243 | } 244 | $rc = false; 245 | } 246 | } 247 | 248 | if ($this->debug) { 249 | $this->x("SAMConnection.Peek() rc=$rc"); 250 | } 251 | return $rc; 252 | } 253 | 254 | /* --------------------------------- 255 | PeekAll 256 | --------------------------------- */ 257 | public function peekAll($target, $options = array()) 258 | { 259 | if ($this->debug) { 260 | $this->e('SAMConnection.PeekAll()'); 261 | } 262 | $rc = true; 263 | 264 | if (!$this->connection) { 265 | $errNo = 106; 266 | $error = 'No active connection!'; 267 | $rc = false; 268 | } else { 269 | /* Call the method on the underlying connection object... */ 270 | $rc = $this->connection->peekall($target, $options); 271 | $this->errNo = $this->connection->errNo; 272 | $this->error = $this->connection->error; 273 | if (!$rc) { 274 | if ($this->debug) { 275 | $this->t("SAMConnection.PeekAll() peekall failed ($this->errNo) $this->error"); 276 | } 277 | $rc = false; 278 | } 279 | } 280 | 281 | if ($this->debug) { 282 | $this->x("SAMConnection.PeekAll() rc=$rc"); 283 | } 284 | return $rc; 285 | } 286 | 287 | /* --------------------------------- 288 | Receive 289 | --------------------------------- */ 290 | public function receive($target, $options = array()) 291 | { 292 | if ($this->debug) { 293 | $this->e('SAMConnection.Receive()'); 294 | } 295 | $rc = true; 296 | 297 | if (!$this->connection) { 298 | $errNo = 106; 299 | $error = 'No active connection!'; 300 | $rc = false; 301 | } else { 302 | /* Call the receive method on the underlying connection object... */ 303 | $rc = $this->connection->receive($target, $options); 304 | $this->errNo = $this->connection->errNo; 305 | $this->error = $this->connection->error; 306 | if (!$rc) { 307 | if ($this->debug) { 308 | $this->t("SAMConnection.Receive() receive failed ($this->errNo) $this->error"); 309 | } 310 | } 311 | } 312 | 313 | if ($this->debug) { 314 | $this->x("SAMConnection.Receive() rc=$rc"); 315 | } 316 | return $rc; 317 | } 318 | 319 | /* --------------------------------- 320 | Remove 321 | --------------------------------- */ 322 | public function remove($target, $options = array()) 323 | { 324 | if ($this->debug) { 325 | $this->e('SAMConnection.Remove()'); 326 | } 327 | $rc = true; 328 | 329 | if (!$this->connection) { 330 | $errNo = 106; 331 | $error = 'No active connection!'; 332 | $rc = false; 333 | } else { 334 | /* Call the method on the underlying connection object... */ 335 | $rc = $this->connection->remove($target, $options); 336 | $this->errNo = $this->connection->errNo; 337 | $this->error = $this->connection->error; 338 | if (!$rc) { 339 | if ($this->debug) { 340 | $this->t("SAMConnection.Remove() remove failed ($this->errNo) $this->error"); 341 | } 342 | $rc = false; 343 | } 344 | } 345 | 346 | if ($this->debug) { 347 | $this->x("SAMConnection.Remove() rc=$rc"); 348 | } 349 | return $rc; 350 | } 351 | 352 | /* --------------------------------- 353 | Rollback 354 | --------------------------------- */ 355 | public function rollback() 356 | { 357 | if ($this->debug) { 358 | $this->e('SAMConnection.Rollback()'); 359 | } 360 | $rc = true; 361 | 362 | if (!$this->connection) { 363 | $errNo = 106; 364 | $error = 'No active connection!'; 365 | $rc = false; 366 | } else { 367 | /* Call the method on the underlying connection object... */ 368 | $rc = $this->connection->rollback($target, $options); 369 | $this->errNo = $this->connection->errNo; 370 | $this->error = $this->connection->error; 371 | if (!$rc) { 372 | if ($this->debug) { 373 | $this->t("SAMConnection.Rollback() rollback failed ($this->errNo) $this->error"); 374 | } 375 | $rc = false; 376 | } 377 | } 378 | 379 | if ($this->debug) { 380 | $this->x("SAMConnection.Rollback() rc=$rc"); 381 | } 382 | return $rc; 383 | } 384 | 385 | /* --------------------------------- 386 | Send 387 | --------------------------------- */ 388 | public function send($target, $msg, $options = array()) 389 | { 390 | if ($this->debug) { 391 | $this->e('SAMConnection.Send()'); 392 | } 393 | $rc = true; 394 | 395 | if (!$this->connection) { 396 | $errNo = 106; 397 | $error = 'No active connection!'; 398 | $rc = false; 399 | } else { 400 | /* Call the send method on the underlying connection object... */ 401 | $rc = $this->connection->send($target, $msg, $options); 402 | $this->errNo = $this->connection->errNo; 403 | $this->error = $this->connection->error; 404 | if (!$rc) { 405 | if ($this->debug) { 406 | $this->t("SAMConnection.Send() send failed ($this->errNo) $this->error"); 407 | } 408 | $rc = false; 409 | } 410 | } 411 | 412 | if ($this->debug) { 413 | $this->x("SAMConnection.Send() rc=$rc"); 414 | } 415 | return $rc; 416 | } 417 | 418 | /* --------------------------------- 419 | SetDebug 420 | --------------------------------- */ 421 | public function setDebug($option = false) 422 | { 423 | if ($this->debug) { 424 | $this->e("SAMConnection.setDebug($option)"); 425 | } 426 | 427 | $this->debug = $option; 428 | 429 | if ($this->connection) { 430 | $this->connection->setdebug($option); 431 | } 432 | 433 | if ($this->debug) { 434 | $this->x('SAMConnection.SetDebug()'); 435 | } 436 | return; 437 | } 438 | 439 | /* --------------------------------- 440 | Subscribe 441 | --------------------------------- */ 442 | public function subscribe($topic, $options = array()) 443 | { 444 | if ($this->debug) { 445 | $this->e("SAMConnection.Subscribe($topic)"); 446 | } 447 | $rc = true; 448 | 449 | if (!$this->connection) { 450 | $errNo = 106; 451 | $error = 'No active connection!'; 452 | $rc = false; 453 | } else { 454 | /* Call the subscribe method on the underlying connection object... */ 455 | $rc = $this->connection->subscribe($topic, $options); 456 | $this->errNo = $this->connection->errNo; 457 | $this->error = $this->connection->error; 458 | if (!$rc) { 459 | if ($this->debug) { 460 | $this->t("SAMConnection.Subscribe() subscribe failed ($this->errNo) $this->error"); 461 | } 462 | $rc = false; 463 | } 464 | } 465 | 466 | if ($this->debug) { 467 | $this->x("SAMConnection.Subscribe() rc=$rc"); 468 | } 469 | return $rc; 470 | } 471 | 472 | /* --------------------------------- 473 | Unsubscribe 474 | --------------------------------- */ 475 | public function unsubscribe($sub_id) 476 | { 477 | if ($this->debug) { 478 | $this->e("SAMConnection.unsubscribe($sub_id)"); 479 | } 480 | $rc = true; 481 | 482 | if (!$this->connection) { 483 | $errNo = 106; 484 | $error = 'No active connection!'; 485 | $rc = false; 486 | } else { 487 | /* Call the subscribe method on the underlying connection object... */ 488 | $rc = $this->connection->unsubscribe($sub_id); 489 | $this->errNo = $this->connection->errNo; 490 | $this->error = $this->connection->error; 491 | if (!$rc) { 492 | if ($this->debug) { 493 | $this->t("SAMConnection.unsubscribe() unsubscribe failed ($this->errNo) $this->error"); 494 | } 495 | $rc = false; 496 | } 497 | } 498 | 499 | if ($this->debug) { 500 | $this->x("SAMConnection.unsubscribe() rc=$rc"); 501 | } 502 | return $rc; 503 | } 504 | } -------------------------------------------------------------------------------- /src/SamMessage.php: -------------------------------------------------------------------------------- 1 | body = $body; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/lib/Mqtt.php: -------------------------------------------------------------------------------- 1 | 1, 42 | "MQTT_CONNACK" => 2, 43 | "MQTT_PUBLISH" => 3, 44 | "MQTT_PUBACK" => 4, 45 | "MQTT_PUBREC" => 5, 46 | "MQTT_PUBREL" => 6, 47 | "MQTT_PUBCOMP" => 7, 48 | "MQTT_SUBSCRIBE" => 8, 49 | "MQTT_SUBACK" => 9, 50 | "MQTT_UNSUBSCRIBE" => 10, 51 | "MQTT_UNSUBACK" => 11, 52 | "MQTT_PINGREC" => 12, 53 | "MQTT_PINGRESP" => 13, 54 | "MQTT_DISCONNECT" => 14); 55 | 56 | /* --------------------------------- 57 | Constructor 58 | --------------------------------- */ 59 | function SAMConnection_MQTT() { 60 | if ($this->debug) $this->$this->e('SAMConnection_MQTT()'); 61 | 62 | if ($this->debug) $this->x('SAMConnection_MQTT()'); 63 | } 64 | 65 | /* --------------------------------- 66 | Commit 67 | --------------------------------- */ 68 | function Commit() { 69 | if ($this->debug) $this->$this->e('SAMConnection_MQTT.Commit()'); 70 | 71 | $errNo = 100; 72 | $error = 'Unsupported operation for MQTT protocol!'; 73 | $rc = false; 74 | 75 | if ($this->debug) $this->x("SAMConnection_MQTT.Commit() rc=$rc"); 76 | return $rc; 77 | } 78 | 79 | /* --------------------------------- 80 | Connect 81 | --------------------------------- */ 82 | function Connect($proto, $options=array()) { 83 | if ($this->debug) $this->e('SAMConnection_MQTT.Connect()'); 84 | 85 | /* Check our optional parameter array for the necessary bits... */ 86 | if ($options['SAM_PORT'] == '') { 87 | $this->port = 1883; 88 | } else { 89 | $this->port = $options['SAM_PORT']; 90 | } 91 | if ($options['SAM_HOST'] == '') { 92 | $this->host = 'localhost'; 93 | } else { 94 | $this->host = $options['SAM_HOST']; 95 | } 96 | 97 | $this->cleanstart = in_array(SAM_MQTT_CLEANSTART, $options); 98 | 99 | if ($this->debug) $this->t("SAMConnection_MQTT.Connect() host=$this->host, port=$this->port, cleanstart=$this->cleanstart"); 100 | 101 | if ($this->checkHost($this->host, $this->port)) { 102 | $this->virtualConnected = true; 103 | } else { 104 | $this->virtualConnected = false; 105 | } 106 | 107 | if ($this->debug) $this->x("SAMConnection_MQTT.Connect() rc=$this->virtualConnected"); 108 | return $this->virtualConnected; 109 | } 110 | 111 | /* --------------------------------- 112 | Disconnect 113 | --------------------------------- */ 114 | function Disconnect() { 115 | if ($this->debug) $this->e('SAMConnection_MQTT.Disconnect()'); 116 | $rc = false; 117 | 118 | if ($this->virtualConnected) { 119 | if ($this->connected) { 120 | $msg = $this->fixed_header("MQTT_DISCONNECT").pack('C', 0); 121 | fwrite($this->sock, $msg); 122 | $response = fgets($this->sock, 128); 123 | if ($this->debug) t('SAMConnection_MQTT.Disconnect() response is '.strlen($response).' bytes'); 124 | if (strlen($response) == 0) { 125 | fclose($this->sock); 126 | $this->sock = NULL; 127 | } 128 | } 129 | $this->virtualConnected = false; 130 | $this->connected = false; 131 | $rc = true; 132 | } 133 | 134 | if ($this->debug) $this->x("SAMConnection_MQTT.Disconnect() rc=$rc"); 135 | return $rc; 136 | } 137 | 138 | /* --------------------------------- 139 | IsConnected 140 | --------------------------------- */ 141 | function IsConnected() { 142 | if ($this->debug) $this->e('SAMConnection_MQTT.IsConnected()'); 143 | $rc = false; 144 | 145 | if ($this->connected) { 146 | $rc = true; 147 | } 148 | 149 | if ($this->debug) $this->x("SAMConnection_MQTT.IsConnected() rc=$rc"); 150 | return $rc; 151 | } 152 | 153 | /* --------------------------------- 154 | Peek 155 | --------------------------------- */ 156 | function Peek() { 157 | if ($this->debug) $this->e('SAMConnection_MQTT.Peek()'); 158 | 159 | $errNo = 100; 160 | $error = 'Unsupported operation for MQTT protocol!'; 161 | $rc = false; 162 | 163 | if ($this->debug) $this->x("SAMConnection_MQTT.Peek() rc=$rc"); 164 | return $rc; 165 | } 166 | 167 | /* --------------------------------- 168 | PeekAll 169 | --------------------------------- */ 170 | function PeekAll() { 171 | if ($this->debug) $this->e('SAMConnection_MQTT.PeekAll()'); 172 | 173 | $errNo = 100; 174 | $error = 'Unsupported operation for MQTT protocol!'; 175 | $rc = false; 176 | 177 | if ($this->debug) $this->x("SAMConnection_MQTT.PeekAll() rc=$rc"); 178 | return $rc; 179 | } 180 | 181 | /* --------------------------------- 182 | Receive 183 | --------------------------------- */ 184 | function Receive($sub_id, $options=array()) { 185 | if ($this->debug) $this->e('SAMConnection_MQTT.Receive()'); 186 | $rc = false; 187 | 188 | /* strip the topic from the rear of the subscription id... */ 189 | $x = strpos($sub_id, SAM_MQTT_SUB_SEPARATOR); 190 | if (!$x) { 191 | $this->errNo = 279; 192 | $this->error = 'Specified subscription id ('.$sub_id.') is not valid!'; 193 | return false; 194 | } 195 | $topic = substr($sub_id, $x + strlen(SAM_MQTT_SUB_SEPARATOR)); 196 | $si = substr($sub_id, 0, $x); 197 | 198 | /* Are we already connected? */ 199 | if (!$this->connected) { 200 | if ($this->debug) t('SAMConnection_MQTT.Receive() Not connected.'); 201 | /* No, so open up the connection... */ 202 | $this->sub_id = $si; 203 | $rc = $this->do_connect_now(); 204 | } else { 205 | /* We are already connected. Are we using the right subscriber id? */ 206 | if ($this->sub_id != $si) { 207 | if ($this->debug) t('SAMConnection_MQTT.Receive() Connected with wrong sub_id.'); 208 | /* No, We better reconnect then... */ 209 | $this->disconnect(); 210 | $this->sub_id = $si; 211 | $rc = $this->do_connect_now(); 212 | } else { 213 | if ($this->debug) t('SAMConnection_MQTT.Receive() Connected OK.'); 214 | $rc = true; 215 | } 216 | } 217 | 218 | if ($rc) { 219 | 220 | /* have we got a timeout specified? */ 221 | if ($options[SAM_WAIT] > 1) { 222 | $m = $options[SAM_WAIT] % 1000; 223 | $s = ($options[SAM_WAIT] - $m) /1000; 224 | if ($this->debug) t('SAMConnection_MQTT.Receive() timeout='.$options[SAM_WAIT]." ($s secs $m millisecs)"); 225 | stream_set_timeout($this->sock, $s, $m); 226 | if ($this->debug) t('SAMConnection_MQTT.Receive() timeout set.'); 227 | } else { 228 | if ($this->debug) t('SAMConnection_MQTT.Receive() no timeout value found!'); 229 | } 230 | 231 | $hdr = $this->read_fixed_header($this->sock); 232 | if (!$hdr) { 233 | $this->errNo = 500; 234 | $this->error = 'Receive request failed, timed out with no data!'; 235 | $rc = false; 236 | } else { 237 | if ($hdr['mtype'] == $this->operations['MQTT_PUBLISH']) { 238 | $len = $this->read_remaining_length($this->sock); 239 | if ($len > 1) { 240 | /* read the topic length... */ 241 | $topic = $this->read_topic($this->sock); 242 | if (!$topic) { 243 | $this->errNo = 303; 244 | $this->error = 'Receive request failed, message format invalid!'; 245 | $rc = false; 246 | } else { 247 | if ($this->debug) t('SAMConnection_MQTT.Receive() topic='.$topic); 248 | $len -= (strlen($topic) + 2); 249 | /* If QoS 1 or 2 then read the message id... */ 250 | if ($hdr['qos'] > 0) { 251 | $idb = fread($this->sock, 2); 252 | $len -= 2; 253 | $fields = unpack('na', $idb); 254 | $mid = $fields['a']; 255 | if ($this->debug) t('SAMConnection_MQTT.Receive() mid='.$mid); 256 | } 257 | $payload = fread($this->sock, $len); 258 | if ($this->debug) t('SAMConnection_MQTT.Receive() payload='.$payload); 259 | $rc = new SAMMessage(); 260 | $rc->body = $payload; 261 | $rc->header->SAM_MQTT_TOPIC = 'topic://'.$topic; 262 | $rc->header->SAM_MQTT_QOS = $hdr['qos']; 263 | $rc->header->SAM_TYPE = 'SAM_BYTES'; 264 | } 265 | } else { 266 | $this->errNo = 303; 267 | $this->error = 'Receive request failed, received message too short! No topic data'; 268 | $rc = false; 269 | } 270 | } else { 271 | if ($this->debug) t('SAMConnection_MQTT.Receive() Receive failed response mtype = '.$mtype); 272 | $rc = false; 273 | } 274 | } 275 | } 276 | 277 | if ($this->debug) $this->x("SAMConnection_MQTT.Receive() rc=$rc"); 278 | return $rc; 279 | } 280 | 281 | /* --------------------------------- 282 | Remove 283 | --------------------------------- */ 284 | function Remove() { 285 | if ($this->debug) $this->e('SAMConnection_MQTT.Remove()'); 286 | 287 | $errNo = 100; 288 | $error = 'Unsupported operation for MQTT protocol!'; 289 | $rc = false; 290 | 291 | if ($this->debug) $this->x("SAMConnection_MQTT.Remove() rc=$rc"); 292 | return $rc; 293 | } 294 | 295 | /* --------------------------------- 296 | Rollback 297 | --------------------------------- */ 298 | function Rollback() { 299 | if ($this->debug) $this->e('SAMConnection_MQTT.Rollback()'); 300 | 301 | $errNo = 100; 302 | $error = 'Unsupported operation for MQTT protocol!'; 303 | $rc = false; 304 | 305 | if ($this->debug) $this->x("SAMConnection_MQTT.Rollback() rc=$rc"); 306 | return $rc; 307 | } 308 | 309 | /* --------------------------------- 310 | Send 311 | --------------------------------- */ 312 | function Send($topic, $message, $options=array()) { 313 | if ($this->debug) $this->e('SAMConnection_MQTT.Send()'); 314 | $rc = true; 315 | 316 | /* check the format of the topic... */ 317 | if (strncmp($topic, 'topic://', 8) == 0) { 318 | $t = substr($topic, 8); 319 | } else { 320 | $this->errNo = 279; 321 | $this->error = 'Specified target ('.$topic.') is not a valid topic!'; 322 | return false; 323 | } 324 | 325 | if (in_array(SAM_MQTT_QOS, $options)) { 326 | $qos = $options[SAM_MQTT_QOS]; 327 | } else { 328 | $qos = 0; 329 | } 330 | 331 | /* Are we already connected? */ 332 | if (!$this->connected) { 333 | /* No, so open up the connection... */ 334 | $this->do_connect_now(); 335 | } 336 | 337 | $mid = rand(); 338 | $variable = $this->utf($t); 339 | if ($qos > 0) { 340 | $variable .= pack('n', $mid); 341 | } 342 | 343 | $payload = $message->body; 344 | 345 | // add in the remaining length field and fix it together 346 | $msg = $this->fixed_header("MQTT_PUBLISH", 0, $qos) . $this->remaining_length(strlen($variable)+strlen($payload)) . $variable . $payload; 347 | 348 | fwrite($this->sock, $msg); 349 | if ($qos > 0) { 350 | $hdr = $this->read_fixed_header($this->sock); 351 | if ($hdr) { 352 | /* is this a QoS level 1 message being sent? */ 353 | if ($qos == 1) { 354 | /* Yup, so we should get a PUBACK response message... */ 355 | if ($hdr['mtype'] == $this->operations['MQTT_PUBACK']) { 356 | $len = $this->read_remaining_length($this->sock); 357 | if ($len > 0) { 358 | $response = fread($this->sock, $len); 359 | } 360 | if ($len < 2) { 361 | if ($this->debug) $this->t("SAMConnection_MQTT.Send() send failed, incorrect length response ($len) received!"); 362 | $this->errNo = 302; 363 | $this->error = 'Send request failed!'; 364 | $rc = false; 365 | } else { 366 | $rc = true; 367 | } 368 | } else { 369 | if ($this->debug) t('SAMConnection_MQTT.Send() Send failed response mtype = '.$mtype.' Expected PUBREC!'); 370 | $rc = false; 371 | } 372 | } else { 373 | /* lets assume it's QoS level 2... */ 374 | /* We should get a PUBREC response message... */ 375 | if ($hdr['mtype'] == $this->operations['MQTT_PUBREC']) { 376 | $len = $this->read_remaining_length($this->sock); 377 | if ($len > 0) { 378 | $response = fread($this->sock, $len); 379 | } 380 | if ($len < 2) { 381 | if ($this->debug) $this->t("SAMConnection_MQTT.Send() send failed, incorrect length response ($len) received!"); 382 | $this->errNo = 302; 383 | $this->error = 'Send request failed!'; 384 | $rc = false; 385 | } else { 386 | $rc = true; 387 | /* Now we can send a PUBREL message... */ 388 | $variable = pack('n', $mid); 389 | $msg = $this->fixed_header("MQTT_PUBREL").$this->remaining_length(strlen($variable)).$variable; 390 | fwrite($this->sock, $msg); 391 | 392 | /* get a response... */ 393 | $hdr = $this->read_fixed_header($this->sock); 394 | if ($hdr['mtype'] == $this->operations['MQTT_PUBCOMP']) { 395 | $len = $this->read_remaining_length($this->sock); 396 | if ($len > 0) { 397 | $response = fread($this->sock, $len); 398 | } 399 | if ($len < 2) { 400 | if ($this->debug) $this->t("SAMConnection_MQTT.Send() send failed, incorrect length response ($len) received!"); 401 | $this->errNo = 302; 402 | $this->error = 'Send request failed!'; 403 | $rc = false; 404 | } else { 405 | $rc = true; 406 | } 407 | } else { 408 | if ($this->debug) t('SAMConnection_MQTT.Send() Send failed response mtype = '.$mtype.' Expected PUBCOMP!'); 409 | $rc = false; 410 | } 411 | } 412 | } else { 413 | if ($this->debug) t('SAMConnection_MQTT.Send() Send failed response mtype = '.$mtype); 414 | $rc = false; 415 | } 416 | } 417 | } 418 | } 419 | 420 | if ($this->debug) $this->x("SAMConnection_MQTT.Send() rc=$rc"); 421 | return $rc; 422 | } 423 | 424 | /* --------------------------------- 425 | SetDebug 426 | --------------------------------- */ 427 | function SetDebug($option=false) { 428 | $this->debug = $option; 429 | return; 430 | } 431 | 432 | /* --------------------------------- 433 | Subscribe 434 | --------------------------------- */ 435 | function Subscribe($topic, $options=array()) { 436 | if ($this->debug) e("SAMConnection_MQTT.Subscribe($topic)"); 437 | $rc = true; 438 | 439 | /* check the format of the topic... */ 440 | if (strncmp($topic, 'topic://', 8) == 0) { 441 | $t = substr($topic, 8); 442 | } else { 443 | $this->errNo = 279; 444 | $this->error = 'Specified target ('.$topic.') is not a valid topic!'; 445 | return false; 446 | } 447 | 448 | if (in_array(SAM_MQTT_QOS, $options)) { 449 | $qos = $options[SAM_MQTT_QOS]; 450 | } else { 451 | $qos = 0; 452 | } 453 | 454 | /* Are we already connected? */ 455 | if (!$this->connected) { 456 | /* No, so open up the connection... */ 457 | if (!$this->do_connect_now()) { 458 | return false; 459 | } 460 | } 461 | 462 | // variable header: message id (16 bits) 463 | $x = rand(1, 16000); 464 | $variable = pack('n', $x); 465 | 466 | // payload: client ID 467 | $payload = $this->utf($t).pack('C', $qos); 468 | 469 | // add in the remaining length field and fix it together 470 | $msg = $this->fixed_header("MQTT_SUBSCRIBE", 0, 1) . $this->remaining_length(strlen($variable)+strlen($payload)) . $variable . $payload; 471 | 472 | fwrite($this->sock, $msg); 473 | $hdr = $this->read_fixed_header($this->sock); 474 | if (!$hdr) { 475 | if ($this->debug) $this->t("SAMConnection_MQTT.Subscribe() subscribe failed, no response from broker!"); 476 | $this->errNo = 301; 477 | $this->error = 'Subscribe request failed, no response from broker!'; 478 | $rc = false; 479 | } else { 480 | if ($hdr['mtype'] == $this->operations['MQTT_SUBACK']) { 481 | $len = $this->read_remaining_length($this->sock); 482 | if ($len > 0) { 483 | $response = fread($this->sock, $len); 484 | /* Return the subscription id with the topic appended to it so we can unsubscribe easily... */ 485 | $rc = $this->sub_id.SAM_MQTT_SUB_SEPARATOR.$t; 486 | } 487 | if ($len < 3) { 488 | if ($this->debug) $this->t("SAMConnection_MQTT.Subscribe() subscribe failed, incorrect length response ($len) received!"); 489 | $this->errNo = 301; 490 | $this->error = 'Subscribe request failed, incorrect length response ($len) received!'; 491 | $rc = false; 492 | } 493 | } else { 494 | if ($this->debug) t('SAMConnection_MQTT.Subscribe() subscribe failed response mtype = '.$mtype); 495 | $rc = false; 496 | } 497 | } 498 | 499 | if ($this->debug) $this->x("SAMConnection_MQTT.Subscribe() rc=$rc"); 500 | return $rc; 501 | } 502 | 503 | /* --------------------------------- 504 | Unsubscribe 505 | --------------------------------- */ 506 | function Unsubscribe($sub_id) { 507 | if ($this->debug) e("SAMConnection_MQTT.Unsubscribe($sub_id)"); 508 | 509 | /* Detach the topic from the rear of the subscription id... */ 510 | $x = strpos($sub_id, SAM_MQTT_SUB_SEPARATOR); 511 | if (!$x) { 512 | $this->errNo = 279; 513 | $this->error = 'Specified subscription id ('.$sub_id.') is not valid!'; 514 | return false; 515 | } 516 | 517 | $topic = substr($sub_id, $x + strlen(SAM_MQTT_SUB_SEPARATOR)); 518 | $si = substr($sub_id, 0, $x); 519 | 520 | 521 | /* Are we already connected? */ 522 | if (!$this->connected) { 523 | if ($this->debug) t('SAMConnection_MQTT.Unsubscribe() Not connected.'); 524 | /* No, so open up the connection... */ 525 | $this->sub_id = $si; 526 | $rc = $this->do_connect_now(); 527 | } else { 528 | /* We are already connected. Are we using the right subscriber id? */ 529 | if ($this->sub_id != $si) { 530 | if ($this->debug) t('SAMConnection_MQTT.Unsubscribe() Connected with wrong sub_id.'); 531 | /* No, We better reconnect then... */ 532 | $this->disconnect(); 533 | $this->sub_id = $si; 534 | $rc = $this->do_connect_now(); 535 | } else { 536 | if ($this->debug) t('SAMConnection_MQTT.Unsubscribe() Connected OK.'); 537 | $rc = true; 538 | } 539 | } 540 | 541 | /* variable header: message id (16 bits) */ 542 | $x = rand(1, 16000); 543 | $variable = pack('n', $x); 544 | 545 | /* payload: client ID */ 546 | $payload = $this->utf($topic); 547 | 548 | /* add in the remaining length field and fix it together */ 549 | $msg = $this->fixed_header("MQTT_UNSUBSCRIBE", 0, 1) . $this->remaining_length(strlen($variable)+strlen($payload)) . $variable . $payload; 550 | 551 | fwrite($this->sock, $msg); 552 | $hdr = $this->read_fixed_header($this->sock); 553 | if (!$hdr) { 554 | if ($this->debug) $this->t("SAMConnection_MQTT.Unsubscribe() unsubscribe failed, no response from broker!"); 555 | $this->errNo = 302; 556 | $this->error = 'Unsubscribe request failed, no response from broker!'; 557 | $rc = false; 558 | } else { 559 | if ($hdr['mtype'] == $this->operations['MQTT_UNSUBACK']) { 560 | $len = $this->read_remaining_length($this->sock); 561 | if ($len > 0) { 562 | $response = fread($this->sock, $len); 563 | $rc = $this->sub_id; 564 | } 565 | if ($len != 2) { 566 | if ($this->debug) $this->t("SAMConnection_MQTT.Unsubscribe() unsubscribe failed, incorrect length response ($len) received!"); 567 | $this->errNo = 301; 568 | $this->error = "Unsubscribe request failed, incorrect length response ($len) received!"; 569 | $rc = false; 570 | } 571 | } else { 572 | if ($this->debug) t('SAMConnection_MQTT.Unsubscribe() unsubscribe failed response mtype = '.$hdr['mtype']); 573 | $rc = false; 574 | } 575 | } 576 | 577 | if ($this->debug) $this->x("SAMConnection_MQTT.Unsubscribe() rc=$rc"); 578 | return $rc; 579 | } 580 | 581 | 582 | 583 | function remaining_length($l) { 584 | /* return the remaining length field bytes for an integer input parameter */ 585 | if ($this->debug) $this->t("SAMConnection_MQTT.remaining_length() l=$l"); 586 | 587 | $rlf = ''; 588 | do { 589 | $digit = $l % 128; 590 | $l = ($l - $digit)/128; 591 | if ($this->debug) $this->t("SAMConnection_MQTT.remaining_length() digit=$digit l=$l"); 592 | 593 | # if there are more digits to encode, set the top bit of this digit 594 | if ( $l > 0 ) { 595 | $digit += 128; 596 | } 597 | $digit = pack('C', $digit); 598 | 599 | $rlf .= $digit; 600 | if ($this->debug) $this->t("SAMConnection_MQTT.remaining_length() rlf=$rlf"); 601 | } while ($l > 0); 602 | 603 | return $rlf; 604 | } 605 | 606 | function utf($s) { 607 | /* return the UTF-8 encoded version of the parameter */ 608 | $l = strlen($s); 609 | $b1 = pack('C', $l/256); 610 | $b2 = pack('C', $l%256); 611 | $rc = $b1.$b2.$s; 612 | return $rc; 613 | } 614 | 615 | function fixed_header($operation, $dup=0, $qos=0, $retain=0) { 616 | /* fixed header: msg type (4) dup (1) qos (2) retain (1) */ 617 | return pack('C', ($this->operations[$operation] * 16) + ($dup * 4) + ($qos * 2) + $retain); 618 | } 619 | 620 | function checkHost($hostname, $port) { 621 | if ($this->debug) e("SAMConnection_MQTT.checkHost($hostname)"); 622 | $rc = false; 623 | 624 | $fp = fsockopen($hostname, $port); 625 | if (!$fp) { 626 | $rc = false; 627 | } else { 628 | $this->sock = $fp; 629 | $rc = true; 630 | } 631 | if ($this->debug) $this->x("SAMConnection_MQTT.checkHost(rc=$rc)"); 632 | return $rc; 633 | } 634 | 635 | function do_connect_now() { 636 | $rc = true; 637 | 638 | /* Do we have a client/subscriber id yet? */ 639 | if ($this->sub_id == '') { 640 | /* No, so create a unique one... */ 641 | $this->sub_id = uniqid('', true); 642 | if ($this->debug) $this->t("SAMConnection_MQTT.do_connect_now() sub_id=$this->sub_id"); 643 | } else { 644 | if ($this->debug) $this->t("SAMConnection_MQTT.do_connect_now() using existing sub_id=$this->sub_id"); 645 | } 646 | 647 | if ($this->cleanstart) { 648 | $x = "\x03"; 649 | } else { 650 | $x = "\x00"; 651 | } 652 | $variable = $this->utf('MQIsdp')."\x03$x\x00\x00"; 653 | 654 | /* payload is subscriber id */ 655 | $payload = $this->utf($this->sub_id); 656 | 657 | /* add in the remaining length field and fix it together */ 658 | $msg = $this->fixed_header("MQTT_CONNECT") . $this->remaining_length(strlen($variable)+strlen($payload)) . $variable . $payload; 659 | 660 | $errNo = 0; 661 | $errstr = ''; 662 | 663 | if (!$this->virtualConnected) { 664 | $fp = fsockopen($this->host, $this->port, $errNo, $errstr); 665 | if (!$fp) { 666 | if ($this->debug) $this->t("SAMConnection_MQTT.do_connect_now() fsockopen failed! ($errNo) $errstr"); 667 | $this->errNo = 208; 668 | $this->error = 'Unable to open socket to broker!'; 669 | $this->sock = NULL; 670 | return false; 671 | } else { 672 | $this->virtualConnected = true; 673 | $this->sock = $fp; 674 | } 675 | } 676 | 677 | stream_set_timeout($this->sock, 10); 678 | fwrite($this->sock, $msg); 679 | 680 | $hdr = $this->read_fixed_header($this->sock); 681 | if ($hdr) { 682 | if ($hdr['mtype'] == $this->operations['MQTT_CONNACK']) { 683 | $len = $this->read_remaining_length($this->sock); 684 | if ($len < 2) { 685 | if ($this->debug) $this->t("SAMConnection_MQTT.do_connect_now() connect failed, incorrect length response ($len) received!"); 686 | $this->errNo = 218; 687 | $this->error = 'Unable to open connection to broker!'; 688 | $rc = false; 689 | } else { 690 | $response = fread($this->sock, $len); 691 | $fields = unpack('Ccomp/Cretcode', $response); 692 | if ($fields['retcode'] == 0) { 693 | $rc = $this->sock; 694 | $this->connected = true; 695 | $rc = true; 696 | if ($this->debug) t('SAMConnection_MQTT.do_connect_now() connected OK'); 697 | } else { 698 | if ($this->debug) t('SAMConnection_MQTT.do_connect_now() connect failed retcode = '.$fields['retcode']); 699 | $rc = false; 700 | if ($fields['retcode'] == 2) { 701 | $this->sub_id = ''; 702 | $this->errNo = 279; 703 | $this->error = 'Invalid subscription id!'; 704 | } 705 | } 706 | } 707 | } else { 708 | if ($this->debug) t('SAMConnection_MQTT.do_connect_now() connect failed response mtype = '.$mtype); 709 | $rc = false; 710 | } 711 | } 712 | 713 | if (!$rc) { 714 | fclose($this->sock); 715 | $this->sock = NULL; 716 | $this->virtualConnected = false; 717 | } 718 | 719 | return $rc; 720 | } 721 | 722 | function read_fixed_header($conn) { 723 | $rc = false; 724 | $response = fread($conn, 1); 725 | if (strlen($response) > 0) { 726 | $fields = unpack('Cbyte1', $response); 727 | $x = $fields['byte1']; 728 | $ret = $x % 2; 729 | $x -= $ret; 730 | $qos = ($x % 8) / 2; 731 | $x -= ($qos * 2); 732 | $dup = ($x % 16) / 8; 733 | $x -= ($dup * 8); 734 | $mtype = $x / 16; 735 | if ($this->debug) $this->t("SAMConnection_MQTT.read_fixed_header() mtype=$mtype, dup=$dup, qos=$qos, retain=$ret"); 736 | $rc = array('mtype' => $mtype, 'dup' => $dup, 'qos' => $qos, 'retain' => $ret); 737 | } 738 | return $rc; 739 | } 740 | 741 | function read_remaining_length($conn) { 742 | $rc = 0; 743 | $m = 1; 744 | while (!feof($conn)) { 745 | $byte = fgetc($conn); 746 | $fields = unpack('Ca', $byte); 747 | $x = $fields['a']; 748 | if ($this->debug) t('SAMConnection_MQTT.read_remaining_length() byte ('.strlen($byte).') = '.$x); 749 | if ($x < 128) { 750 | $rc += $x * $m; 751 | break; 752 | } else { 753 | $rc += (($x - 128) * $m); 754 | } 755 | $m *= 128; 756 | } 757 | if ($this->debug) t('SAMConnection_MQTT.read_remaining_length() remaining length = '.$rc); 758 | return $rc; 759 | } 760 | 761 | function read_topic($conn) { 762 | if ($this->debug) $this->e('SAMConnection_MQTT.read_topic()'); 763 | $rc = false; 764 | while (!feof($conn)) { 765 | $tlen = fread($conn, 2); 766 | $fields = unpack('na', $tlen); 767 | if ($this->debug) t('SAMConnection_MQTT.read_topic() topic length='.$fields['a']); 768 | $rc = fread($conn, $fields['a']); 769 | break; 770 | } 771 | if ($this->debug) $this->x("SAMConnection_MQTT.read_topic(rc=$rc)"); 772 | return $rc; 773 | } 774 | 775 | } -------------------------------------------------------------------------------- /tests/16x16_loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIRootUser/laravel-mqtt-publish/05597071d0c64cc7fc8f25f0b6b704bd17b37866/tests/16x16_loading.gif -------------------------------------------------------------------------------- /tests/README: -------------------------------------------------------------------------------- 1 | Everything here is licenced under Apache 2.0 2 | http://www.apache.org/licenses/LICENSE-2.0 -------------------------------------------------------------------------------- /tests/index.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | TOKUDU Android Push Demo 13 | 175 | 176 | 177 | 210 | 211 | 212 |
213 | 214 | 215 | 218 | 219 | 220 | 227 | 228 | 258 | 259 |
216 |
Tokudu Android Push Demo
217 |
221 |
222 | Server status: 223 | 224 |
226 |
229 |
230 |
231 |
232 | 233 | 234 |
235 |
236 | 237 | 238 |
239 |
240 | 241 |
242 | sending... 243 |
244 |
245 |
246 |
247 |
248 | Message sent! 249 |
250 |
251 |
252 |
253 |
254 | Send Push Message 255 |
256 |
257 |
260 |
261 | 262 | 263 | ​ -------------------------------------------------------------------------------- /tests/jquery.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | /* 3 | * jQuery 1.2.6 - New Wave Javascript 4 | * 5 | * Copyright (c) 2008 John Resig (jquery.com) 6 | * Dual licensed under the MIT (MIT-LICENSE.txt) 7 | * and GPL (GPL-LICENSE.txt) licenses. 8 | * 9 | * $Date: 2008-05-27 21:17:26 +0200 (Di, 27 Mai 2008) $ 10 | * $Rev: 5700 $ 11 | */ 12 | 13 | // Map over jQuery in case of overwrite 14 | var _jQuery = window.jQuery, 15 | // Map over the $ in case of overwrite 16 | _$ = window.$; 17 | 18 | var jQuery = window.jQuery = window.$ = function( selector, context ) { 19 | // The jQuery object is actually just the init constructor 'enhanced' 20 | return new jQuery.fn.init( selector, context ); 21 | }; 22 | 23 | // A simple way to check for HTML strings or ID strings 24 | // (both of which we optimize for) 25 | var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/, 26 | 27 | // Is it a simple selector 28 | isSimple = /^.[^:#\[\.]*$/, 29 | 30 | // Will speed up references to undefined, and allows munging its name. 31 | undefined; 32 | 33 | jQuery.fn = jQuery.prototype = { 34 | init: function( selector, context ) { 35 | // Make sure that a selection was provided 36 | selector = selector || document; 37 | 38 | // Handle $(DOMElement) 39 | if ( selector.nodeType ) { 40 | this[0] = selector; 41 | this.length = 1; 42 | return this; 43 | } 44 | // Handle HTML strings 45 | if ( typeof selector == "string" ) { 46 | // Are we dealing with HTML string or an ID? 47 | var match = quickExpr.exec( selector ); 48 | 49 | // Verify a match, and that no context was specified for #id 50 | if ( match && (match[1] || !context) ) { 51 | 52 | // HANDLE: $(html) -> $(array) 53 | if ( match[1] ) 54 | selector = jQuery.clean( [ match[1] ], context ); 55 | 56 | // HANDLE: $("#id") 57 | else { 58 | var elem = document.getElementById( match[3] ); 59 | 60 | // Make sure an element was located 61 | if ( elem ){ 62 | // Handle the case where IE and Opera return items 63 | // by name instead of ID 64 | if ( elem.id != match[3] ) 65 | return jQuery().find( selector ); 66 | 67 | // Otherwise, we inject the element directly into the jQuery object 68 | return jQuery( elem ); 69 | } 70 | selector = []; 71 | } 72 | 73 | // HANDLE: $(expr, [context]) 74 | // (which is just equivalent to: $(content).find(expr) 75 | } else 76 | return jQuery( context ).find( selector ); 77 | 78 | // HANDLE: $(function) 79 | // Shortcut for document ready 80 | } else if ( jQuery.isFunction( selector ) ) 81 | return jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector ); 82 | 83 | return this.setArray(jQuery.makeArray(selector)); 84 | }, 85 | 86 | // The current version of jQuery being used 87 | jquery: "1.2.6", 88 | 89 | // The number of elements contained in the matched element set 90 | size: function() { 91 | return this.length; 92 | }, 93 | 94 | // The number of elements contained in the matched element set 95 | length: 0, 96 | 97 | // Get the Nth element in the matched element set OR 98 | // Get the whole matched element set as a clean array 99 | get: function( num ) { 100 | return num == undefined ? 101 | 102 | // Return a 'clean' array 103 | jQuery.makeArray( this ) : 104 | 105 | // Return just the object 106 | this[ num ]; 107 | }, 108 | 109 | // Take an array of elements and push it onto the stack 110 | // (returning the new matched element set) 111 | pushStack: function( elems ) { 112 | // Build a new jQuery matched element set 113 | var ret = jQuery( elems ); 114 | 115 | // Add the old object onto the stack (as a reference) 116 | ret.prevObject = this; 117 | 118 | // Return the newly-formed element set 119 | return ret; 120 | }, 121 | 122 | // Force the current matched set of elements to become 123 | // the specified array of elements (destroying the stack in the process) 124 | // You should use pushStack() in order to do this, but maintain the stack 125 | setArray: function( elems ) { 126 | // Resetting the length to 0, then using the native Array push 127 | // is a super-fast way to populate an object with array-like properties 128 | this.length = 0; 129 | Array.prototype.push.apply( this, elems ); 130 | 131 | return this; 132 | }, 133 | 134 | // Execute a callback for every element in the matched set. 135 | // (You can seed the arguments with an array of args, but this is 136 | // only used internally.) 137 | each: function( callback, args ) { 138 | return jQuery.each( this, callback, args ); 139 | }, 140 | 141 | // Determine the position of an element within 142 | // the matched set of elements 143 | index: function( elem ) { 144 | var ret = -1; 145 | 146 | // Locate the position of the desired element 147 | return jQuery.inArray( 148 | // If it receives a jQuery object, the first element is used 149 | elem && elem.jquery ? elem[0] : elem 150 | , this ); 151 | }, 152 | 153 | attr: function( name, value, type ) { 154 | var options = name; 155 | 156 | // Look for the case where we're accessing a style value 157 | if ( name.constructor == String ) 158 | if ( value === undefined ) 159 | return this[0] && jQuery[ type || "attr" ]( this[0], name ); 160 | 161 | else { 162 | options = {}; 163 | options[ name ] = value; 164 | } 165 | 166 | // Check to see if we're setting style values 167 | return this.each(function(i){ 168 | // Set all the styles 169 | for ( name in options ) 170 | jQuery.attr( 171 | type ? 172 | this.style : 173 | this, 174 | name, jQuery.prop( this, options[ name ], type, i, name ) 175 | ); 176 | }); 177 | }, 178 | 179 | css: function( key, value ) { 180 | // ignore negative width and height values 181 | if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) 182 | value = undefined; 183 | return this.attr( key, value, "curCSS" ); 184 | }, 185 | 186 | text: function( text ) { 187 | if ( typeof text != "object" && text != null ) 188 | return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); 189 | 190 | var ret = ""; 191 | 192 | jQuery.each( text || this, function(){ 193 | jQuery.each( this.childNodes, function(){ 194 | if ( this.nodeType != 8 ) 195 | ret += this.nodeType != 1 ? 196 | this.nodeValue : 197 | jQuery.fn.text( [ this ] ); 198 | }); 199 | }); 200 | 201 | return ret; 202 | }, 203 | 204 | wrapAll: function( html ) { 205 | if ( this[0] ) 206 | // The elements to wrap the target around 207 | jQuery( html, this[0].ownerDocument ) 208 | .clone() 209 | .insertBefore( this[0] ) 210 | .map(function(){ 211 | var elem = this; 212 | 213 | while ( elem.firstChild ) 214 | elem = elem.firstChild; 215 | 216 | return elem; 217 | }) 218 | .append(this); 219 | 220 | return this; 221 | }, 222 | 223 | wrapInner: function( html ) { 224 | return this.each(function(){ 225 | jQuery( this ).contents().wrapAll( html ); 226 | }); 227 | }, 228 | 229 | wrap: function( html ) { 230 | return this.each(function(){ 231 | jQuery( this ).wrapAll( html ); 232 | }); 233 | }, 234 | 235 | append: function() { 236 | return this.domManip(arguments, true, false, function(elem){ 237 | if (this.nodeType == 1) 238 | this.appendChild( elem ); 239 | }); 240 | }, 241 | 242 | prepend: function() { 243 | return this.domManip(arguments, true, true, function(elem){ 244 | if (this.nodeType == 1) 245 | this.insertBefore( elem, this.firstChild ); 246 | }); 247 | }, 248 | 249 | before: function() { 250 | return this.domManip(arguments, false, false, function(elem){ 251 | this.parentNode.insertBefore( elem, this ); 252 | }); 253 | }, 254 | 255 | after: function() { 256 | return this.domManip(arguments, false, true, function(elem){ 257 | this.parentNode.insertBefore( elem, this.nextSibling ); 258 | }); 259 | }, 260 | 261 | end: function() { 262 | return this.prevObject || jQuery( [] ); 263 | }, 264 | 265 | find: function( selector ) { 266 | var elems = jQuery.map(this, function(elem){ 267 | return jQuery.find( selector, elem ); 268 | }); 269 | 270 | return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ? 271 | jQuery.unique( elems ) : 272 | elems ); 273 | }, 274 | 275 | clone: function( events ) { 276 | // Do the clone 277 | var ret = this.map(function(){ 278 | if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) { 279 | // IE copies events bound via attachEvent when 280 | // using cloneNode. Calling detachEvent on the 281 | // clone will also remove the events from the orignal 282 | // In order to get around this, we use innerHTML. 283 | // Unfortunately, this means some modifications to 284 | // attributes in IE that are actually only stored 285 | // as properties will not be copied (such as the 286 | // the name attribute on an input). 287 | var clone = this.cloneNode(true), 288 | container = document.createElement("div"); 289 | container.appendChild(clone); 290 | return jQuery.clean([container.innerHTML])[0]; 291 | } else 292 | return this.cloneNode(true); 293 | }); 294 | 295 | // Need to set the expando to null on the cloned set if it exists 296 | // removeData doesn't work here, IE removes it from the original as well 297 | // this is primarily for IE but the data expando shouldn't be copied over in any browser 298 | var clone = ret.find("*").andSelf().each(function(){ 299 | if ( this[ expando ] != undefined ) 300 | this[ expando ] = null; 301 | }); 302 | 303 | // Copy the events from the original to the clone 304 | if ( events === true ) 305 | this.find("*").andSelf().each(function(i){ 306 | if (this.nodeType == 3) 307 | return; 308 | var events = jQuery.data( this, "events" ); 309 | 310 | for ( var type in events ) 311 | for ( var handler in events[ type ] ) 312 | jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data ); 313 | }); 314 | 315 | // Return the cloned set 316 | return ret; 317 | }, 318 | 319 | filter: function( selector ) { 320 | return this.pushStack( 321 | jQuery.isFunction( selector ) && 322 | jQuery.grep(this, function(elem, i){ 323 | return selector.call( elem, i ); 324 | }) || 325 | 326 | jQuery.multiFilter( selector, this ) ); 327 | }, 328 | 329 | not: function( selector ) { 330 | if ( selector.constructor == String ) 331 | // test special case where just one selector is passed in 332 | if ( isSimple.test( selector ) ) 333 | return this.pushStack( jQuery.multiFilter( selector, this, true ) ); 334 | else 335 | selector = jQuery.multiFilter( selector, this ); 336 | 337 | var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; 338 | return this.filter(function() { 339 | return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; 340 | }); 341 | }, 342 | 343 | add: function( selector ) { 344 | return this.pushStack( jQuery.unique( jQuery.merge( 345 | this.get(), 346 | typeof selector == 'string' ? 347 | jQuery( selector ) : 348 | jQuery.makeArray( selector ) 349 | ))); 350 | }, 351 | 352 | is: function( selector ) { 353 | return !!selector && jQuery.multiFilter( selector, this ).length > 0; 354 | }, 355 | 356 | hasClass: function( selector ) { 357 | return this.is( "." + selector ); 358 | }, 359 | 360 | val: function( value ) { 361 | if ( value == undefined ) { 362 | 363 | if ( this.length ) { 364 | var elem = this[0]; 365 | 366 | // We need to handle select boxes special 367 | if ( jQuery.nodeName( elem, "select" ) ) { 368 | var index = elem.selectedIndex, 369 | values = [], 370 | options = elem.options, 371 | one = elem.type == "select-one"; 372 | 373 | // Nothing was selected 374 | if ( index < 0 ) 375 | return null; 376 | 377 | // Loop through all the selected options 378 | for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { 379 | var option = options[ i ]; 380 | 381 | if ( option.selected ) { 382 | // Get the specifc value for the option 383 | value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value; 384 | 385 | // We don't need an array for one selects 386 | if ( one ) 387 | return value; 388 | 389 | // Multi-Selects return an array 390 | values.push( value ); 391 | } 392 | } 393 | 394 | return values; 395 | 396 | // Everything else, we just grab the value 397 | } else 398 | return (this[0].value || "").replace(/\r/g, ""); 399 | 400 | } 401 | 402 | return undefined; 403 | } 404 | 405 | if( value.constructor == Number ) 406 | value += ''; 407 | 408 | return this.each(function(){ 409 | if ( this.nodeType != 1 ) 410 | return; 411 | 412 | if ( value.constructor == Array && /radio|checkbox/.test( this.type ) ) 413 | this.checked = (jQuery.inArray(this.value, value) >= 0 || 414 | jQuery.inArray(this.name, value) >= 0); 415 | 416 | else if ( jQuery.nodeName( this, "select" ) ) { 417 | var values = jQuery.makeArray(value); 418 | 419 | jQuery( "option", this ).each(function(){ 420 | this.selected = (jQuery.inArray( this.value, values ) >= 0 || 421 | jQuery.inArray( this.text, values ) >= 0); 422 | }); 423 | 424 | if ( !values.length ) 425 | this.selectedIndex = -1; 426 | 427 | } else 428 | this.value = value; 429 | }); 430 | }, 431 | 432 | html: function( value ) { 433 | return value == undefined ? 434 | (this[0] ? 435 | this[0].innerHTML : 436 | null) : 437 | this.empty().append( value ); 438 | }, 439 | 440 | replaceWith: function( value ) { 441 | return this.after( value ).remove(); 442 | }, 443 | 444 | eq: function( i ) { 445 | return this.slice( i, i + 1 ); 446 | }, 447 | 448 | slice: function() { 449 | return this.pushStack( Array.prototype.slice.apply( this, arguments ) ); 450 | }, 451 | 452 | map: function( callback ) { 453 | return this.pushStack( jQuery.map(this, function(elem, i){ 454 | return callback.call( elem, i, elem ); 455 | })); 456 | }, 457 | 458 | andSelf: function() { 459 | return this.add( this.prevObject ); 460 | }, 461 | 462 | data: function( key, value ){ 463 | var parts = key.split("."); 464 | parts[1] = parts[1] ? "." + parts[1] : ""; 465 | 466 | if ( value === undefined ) { 467 | var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); 468 | 469 | if ( data === undefined && this.length ) 470 | data = jQuery.data( this[0], key ); 471 | 472 | return data === undefined && parts[1] ? 473 | this.data( parts[0] ) : 474 | data; 475 | } else 476 | return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){ 477 | jQuery.data( this, key, value ); 478 | }); 479 | }, 480 | 481 | removeData: function( key ){ 482 | return this.each(function(){ 483 | jQuery.removeData( this, key ); 484 | }); 485 | }, 486 | 487 | domManip: function( args, table, reverse, callback ) { 488 | var clone = this.length > 1, elems; 489 | 490 | return this.each(function(){ 491 | if ( !elems ) { 492 | elems = jQuery.clean( args, this.ownerDocument ); 493 | 494 | if ( reverse ) 495 | elems.reverse(); 496 | } 497 | 498 | var obj = this; 499 | 500 | if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) ) 501 | obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") ); 502 | 503 | var scripts = jQuery( [] ); 504 | 505 | jQuery.each(elems, function(){ 506 | var elem = clone ? 507 | jQuery( this ).clone( true )[0] : 508 | this; 509 | 510 | // execute all scripts after the elements have been injected 511 | if ( jQuery.nodeName( elem, "script" ) ) 512 | scripts = scripts.add( elem ); 513 | else { 514 | // Remove any inner scripts for later evaluation 515 | if ( elem.nodeType == 1 ) 516 | scripts = scripts.add( jQuery( "script", elem ).remove() ); 517 | 518 | // Inject the elements into the document 519 | callback.call( obj, elem ); 520 | } 521 | }); 522 | 523 | scripts.each( evalScript ); 524 | }); 525 | } 526 | }; 527 | 528 | // Give the init function the jQuery prototype for later instantiation 529 | jQuery.fn.init.prototype = jQuery.fn; 530 | 531 | function evalScript( i, elem ) { 532 | if ( elem.src ) 533 | jQuery.ajax({ 534 | url: elem.src, 535 | async: false, 536 | dataType: "script" 537 | }); 538 | 539 | else 540 | jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); 541 | 542 | if ( elem.parentNode ) 543 | elem.parentNode.removeChild( elem ); 544 | } 545 | 546 | function now(){ 547 | return +new Date; 548 | } 549 | 550 | jQuery.extend = jQuery.fn.extend = function() { 551 | // copy reference to target object 552 | var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; 553 | 554 | // Handle a deep copy situation 555 | if ( target.constructor == Boolean ) { 556 | deep = target; 557 | target = arguments[1] || {}; 558 | // skip the boolean and the target 559 | i = 2; 560 | } 561 | 562 | // Handle case when target is a string or something (possible in deep copy) 563 | if ( typeof target != "object" && typeof target != "function" ) 564 | target = {}; 565 | 566 | // extend jQuery itself if only one argument is passed 567 | if ( length == i ) { 568 | target = this; 569 | --i; 570 | } 571 | 572 | for ( ; i < length; i++ ) 573 | // Only deal with non-null/undefined values 574 | if ( (options = arguments[ i ]) != null ) 575 | // Extend the base object 576 | for ( var name in options ) { 577 | var src = target[ name ], copy = options[ name ]; 578 | 579 | // Prevent never-ending loop 580 | if ( target === copy ) 581 | continue; 582 | 583 | // Recurse if we're merging object values 584 | if ( deep && copy && typeof copy == "object" && !copy.nodeType ) 585 | target[ name ] = jQuery.extend( deep, 586 | // Never move original objects, clone them 587 | src || ( copy.length != null ? [ ] : { } ) 588 | , copy ); 589 | 590 | // Don't bring in undefined values 591 | else if ( copy !== undefined ) 592 | target[ name ] = copy; 593 | 594 | } 595 | 596 | // Return the modified object 597 | return target; 598 | }; 599 | 600 | var expando = "jQuery" + now(), uuid = 0, windowData = {}, 601 | // exclude the following css properties to add px 602 | exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, 603 | // cache defaultView 604 | defaultView = document.defaultView || {}; 605 | 606 | jQuery.extend({ 607 | noConflict: function( deep ) { 608 | window.$ = _$; 609 | 610 | if ( deep ) 611 | window.jQuery = _jQuery; 612 | 613 | return jQuery; 614 | }, 615 | 616 | // See test/unit/core.js for details concerning this function. 617 | isFunction: function( fn ) { 618 | return !!fn && typeof fn != "string" && !fn.nodeName && 619 | fn.constructor != Array && /^[\s[]?function/.test( fn + "" ); 620 | }, 621 | 622 | // check if an element is in a (or is an) XML document 623 | isXMLDoc: function( elem ) { 624 | return elem.documentElement && !elem.body || 625 | elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; 626 | }, 627 | 628 | // Evalulates a script in a global context 629 | globalEval: function( data ) { 630 | data = jQuery.trim( data ); 631 | 632 | if ( data ) { 633 | // Inspired by code by Andrea Giammarchi 634 | // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html 635 | var head = document.getElementsByTagName("head")[0] || document.documentElement, 636 | script = document.createElement("script"); 637 | 638 | script.type = "text/javascript"; 639 | if ( jQuery.browser.msie ) 640 | script.text = data; 641 | else 642 | script.appendChild( document.createTextNode( data ) ); 643 | 644 | // Use insertBefore instead of appendChild to circumvent an IE6 bug. 645 | // This arises when a base node is used (#2709). 646 | head.insertBefore( script, head.firstChild ); 647 | head.removeChild( script ); 648 | } 649 | }, 650 | 651 | nodeName: function( elem, name ) { 652 | return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); 653 | }, 654 | 655 | cache: {}, 656 | 657 | data: function( elem, name, data ) { 658 | elem = elem == window ? 659 | windowData : 660 | elem; 661 | 662 | var id = elem[ expando ]; 663 | 664 | // Compute a unique ID for the element 665 | if ( !id ) 666 | id = elem[ expando ] = ++uuid; 667 | 668 | // Only generate the data cache if we're 669 | // trying to access or manipulate it 670 | if ( name && !jQuery.cache[ id ] ) 671 | jQuery.cache[ id ] = {}; 672 | 673 | // Prevent overriding the named cache with undefined values 674 | if ( data !== undefined ) 675 | jQuery.cache[ id ][ name ] = data; 676 | 677 | // Return the named cache data, or the ID for the element 678 | return name ? 679 | jQuery.cache[ id ][ name ] : 680 | id; 681 | }, 682 | 683 | removeData: function( elem, name ) { 684 | elem = elem == window ? 685 | windowData : 686 | elem; 687 | 688 | var id = elem[ expando ]; 689 | 690 | // If we want to remove a specific section of the element's data 691 | if ( name ) { 692 | if ( jQuery.cache[ id ] ) { 693 | // Remove the section of cache data 694 | delete jQuery.cache[ id ][ name ]; 695 | 696 | // If we've removed all the data, remove the element's cache 697 | name = ""; 698 | 699 | for ( name in jQuery.cache[ id ] ) 700 | break; 701 | 702 | if ( !name ) 703 | jQuery.removeData( elem ); 704 | } 705 | 706 | // Otherwise, we want to remove all of the element's data 707 | } else { 708 | // Clean up the element expando 709 | try { 710 | delete elem[ expando ]; 711 | } catch(e){ 712 | // IE has trouble directly removing the expando 713 | // but it's ok with using removeAttribute 714 | if ( elem.removeAttribute ) 715 | elem.removeAttribute( expando ); 716 | } 717 | 718 | // Completely remove the data cache 719 | delete jQuery.cache[ id ]; 720 | } 721 | }, 722 | 723 | // args is for internal usage only 724 | each: function( object, callback, args ) { 725 | var name, i = 0, length = object.length; 726 | 727 | if ( args ) { 728 | if ( length == undefined ) { 729 | for ( name in object ) 730 | if ( callback.apply( object[ name ], args ) === false ) 731 | break; 732 | } else 733 | for ( ; i < length; ) 734 | if ( callback.apply( object[ i++ ], args ) === false ) 735 | break; 736 | 737 | // A special, fast, case for the most common use of each 738 | } else { 739 | if ( length == undefined ) { 740 | for ( name in object ) 741 | if ( callback.call( object[ name ], name, object[ name ] ) === false ) 742 | break; 743 | } else 744 | for ( var value = object[0]; 745 | i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} 746 | } 747 | 748 | return object; 749 | }, 750 | 751 | prop: function( elem, value, type, i, name ) { 752 | // Handle executable functions 753 | if ( jQuery.isFunction( value ) ) 754 | value = value.call( elem, i ); 755 | 756 | // Handle passing in a number to a CSS property 757 | return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ? 758 | value + "px" : 759 | value; 760 | }, 761 | 762 | className: { 763 | // internal only, use addClass("class") 764 | add: function( elem, classNames ) { 765 | jQuery.each((classNames || "").split(/\s+/), function(i, className){ 766 | if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) 767 | elem.className += (elem.className ? " " : "") + className; 768 | }); 769 | }, 770 | 771 | // internal only, use removeClass("class") 772 | remove: function( elem, classNames ) { 773 | if (elem.nodeType == 1) 774 | elem.className = classNames != undefined ? 775 | jQuery.grep(elem.className.split(/\s+/), function(className){ 776 | return !jQuery.className.has( classNames, className ); 777 | }).join(" ") : 778 | ""; 779 | }, 780 | 781 | // internal only, use hasClass("class") 782 | has: function( elem, className ) { 783 | return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; 784 | } 785 | }, 786 | 787 | // A method for quickly swapping in/out CSS properties to get correct calculations 788 | swap: function( elem, options, callback ) { 789 | var old = {}; 790 | // Remember the old values, and insert the new ones 791 | for ( var name in options ) { 792 | old[ name ] = elem.style[ name ]; 793 | elem.style[ name ] = options[ name ]; 794 | } 795 | 796 | callback.call( elem ); 797 | 798 | // Revert the old values 799 | for ( var name in options ) 800 | elem.style[ name ] = old[ name ]; 801 | }, 802 | 803 | css: function( elem, name, force ) { 804 | if ( name == "width" || name == "height" ) { 805 | var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; 806 | 807 | function getWH() { 808 | val = name == "width" ? elem.offsetWidth : elem.offsetHeight; 809 | var padding = 0, border = 0; 810 | jQuery.each( which, function() { 811 | padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; 812 | border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; 813 | }); 814 | val -= Math.round(padding + border); 815 | } 816 | 817 | if ( jQuery(elem).is(":visible") ) 818 | getWH(); 819 | else 820 | jQuery.swap( elem, props, getWH ); 821 | 822 | return Math.max(0, val); 823 | } 824 | 825 | return jQuery.curCSS( elem, name, force ); 826 | }, 827 | 828 | curCSS: function( elem, name, force ) { 829 | var ret, style = elem.style; 830 | 831 | // A helper method for determining if an element's values are broken 832 | function color( elem ) { 833 | if ( !jQuery.browser.safari ) 834 | return false; 835 | 836 | // defaultView is cached 837 | var ret = defaultView.getComputedStyle( elem, null ); 838 | return !ret || ret.getPropertyValue("color") == ""; 839 | } 840 | 841 | // We need to handle opacity special in IE 842 | if ( name == "opacity" && jQuery.browser.msie ) { 843 | ret = jQuery.attr( style, "opacity" ); 844 | 845 | return ret == "" ? 846 | "1" : 847 | ret; 848 | } 849 | // Opera sometimes will give the wrong display answer, this fixes it, see #2037 850 | if ( jQuery.browser.opera && name == "display" ) { 851 | var save = style.outline; 852 | style.outline = "0 solid black"; 853 | style.outline = save; 854 | } 855 | 856 | // Make sure we're using the right name for getting the float value 857 | if ( name.match( /float/i ) ) 858 | name = styleFloat; 859 | 860 | if ( !force && style && style[ name ] ) 861 | ret = style[ name ]; 862 | 863 | else if ( defaultView.getComputedStyle ) { 864 | 865 | // Only "float" is needed here 866 | if ( name.match( /float/i ) ) 867 | name = "float"; 868 | 869 | name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); 870 | 871 | var computedStyle = defaultView.getComputedStyle( elem, null ); 872 | 873 | if ( computedStyle && !color( elem ) ) 874 | ret = computedStyle.getPropertyValue( name ); 875 | 876 | // If the element isn't reporting its values properly in Safari 877 | // then some display: none elements are involved 878 | else { 879 | var swap = [], stack = [], a = elem, i = 0; 880 | 881 | // Locate all of the parent display: none elements 882 | for ( ; a && color(a); a = a.parentNode ) 883 | stack.unshift(a); 884 | 885 | // Go through and make them visible, but in reverse 886 | // (It would be better if we knew the exact display type that they had) 887 | for ( ; i < stack.length; i++ ) 888 | if ( color( stack[ i ] ) ) { 889 | swap[ i ] = stack[ i ].style.display; 890 | stack[ i ].style.display = "block"; 891 | } 892 | 893 | // Since we flip the display style, we have to handle that 894 | // one special, otherwise get the value 895 | ret = name == "display" && swap[ stack.length - 1 ] != null ? 896 | "none" : 897 | ( computedStyle && computedStyle.getPropertyValue( name ) ) || ""; 898 | 899 | // Finally, revert the display styles back 900 | for ( i = 0; i < swap.length; i++ ) 901 | if ( swap[ i ] != null ) 902 | stack[ i ].style.display = swap[ i ]; 903 | } 904 | 905 | // We should always get a number back from opacity 906 | if ( name == "opacity" && ret == "" ) 907 | ret = "1"; 908 | 909 | } else if ( elem.currentStyle ) { 910 | var camelCase = name.replace(/\-(\w)/g, function(all, letter){ 911 | return letter.toUpperCase(); 912 | }); 913 | 914 | ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; 915 | 916 | // From the awesome hack by Dean Edwards 917 | // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 918 | 919 | // If we're not dealing with a regular pixel number 920 | // but a number that has a weird ending, we need to convert it to pixels 921 | if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { 922 | // Remember the original values 923 | var left = style.left, rsLeft = elem.runtimeStyle.left; 924 | 925 | // Put in the new values to get a computed value out 926 | elem.runtimeStyle.left = elem.currentStyle.left; 927 | style.left = ret || 0; 928 | ret = style.pixelLeft + "px"; 929 | 930 | // Revert the changed values 931 | style.left = left; 932 | elem.runtimeStyle.left = rsLeft; 933 | } 934 | } 935 | 936 | return ret; 937 | }, 938 | 939 | clean: function( elems, context ) { 940 | var ret = []; 941 | context = context || document; 942 | // !context.createElement fails in IE with an error but returns typeof 'object' 943 | if (typeof context.createElement == 'undefined') 944 | context = context.ownerDocument || context[0] && context[0].ownerDocument || document; 945 | 946 | jQuery.each(elems, function(i, elem){ 947 | if ( !elem ) 948 | return; 949 | 950 | if ( elem.constructor == Number ) 951 | elem += ''; 952 | 953 | // Convert html string into DOM nodes 954 | if ( typeof elem == "string" ) { 955 | // Fix "XHTML"-style tags in all browsers 956 | elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ 957 | return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? 958 | all : 959 | front + ">"; 960 | }); 961 | 962 | // Trim whitespace, otherwise indexOf won't work as expected 963 | var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div"); 964 | 965 | var wrap = 966 | // option or optgroup 967 | !tags.indexOf("", "" ] || 969 | 970 | !tags.indexOf("", "" ] || 972 | 973 | tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && 974 | [ 1, "", "
" ] || 975 | 976 | !tags.indexOf("", "" ] || 978 | 979 | // matched above 980 | (!tags.indexOf("", "" ] || 982 | 983 | !tags.indexOf("", "" ] || 985 | 986 | // IE can't serialize and