Browse Source

workerman

zhangxiaobin 1 year ago
parent
commit
ecadef252d
35 changed files with 14663 additions and 0 deletions
  1. 1 0
      .idea/bansheng.iml
  2. 1 0
      .idea/php.xml
  3. 116 0
      Applications/Chat/Events.php
  4. 459 0
      Applications/Chat/Web/css/bootstrap-theme.css
  5. 8 0
      Applications/Chat/Web/css/bootstrap-theme.min.css
  6. 7059 0
      Applications/Chat/Web/css/bootstrap.css
  7. 8 0
      Applications/Chat/Web/css/bootstrap.min.css
  8. 118 0
      Applications/Chat/Web/css/jquery-sinaEmotion-2.1.0.css
  9. 1 0
      Applications/Chat/Web/css/jquery-sinaEmotion-2.1.0.min.css
  10. 139 0
      Applications/Chat/Web/css/style.css
  11. BIN
      Applications/Chat/Web/img/workerman-todpole.png
  12. 212 0
      Applications/Chat/Web/index.php
  13. 280 0
      Applications/Chat/Web/js/jquery-sinaEmotion-2.1.0.js
  14. 9 0
      Applications/Chat/Web/js/jquery-sinaEmotion-2.1.0.min.js
  15. 1 0
      Applications/Chat/Web/js/jquery.min.js
  16. 3 0
      Applications/Chat/Web/js/swfobject.js
  17. 398 0
      Applications/Chat/Web/js/web_socket.js
  18. BIN
      Applications/Chat/Web/swf/WebSocketMain.swf
  19. 34 0
      Applications/Chat/start_businessworker.php
  20. 61 0
      Applications/Chat/start_gateway.php
  21. 27 0
      Applications/Chat/start_register.php
  22. 79 0
      Applications/Chat/start_web.php
  23. 1 0
      application/api/controller/Noble.php
  24. 25 0
      server.php
  25. 21 0
      vendor/workerman/gateway-worker/MIT-LICENSE.txt
  26. 38 0
      vendor/workerman/gateway-worker/README.md
  27. 12 0
      vendor/workerman/gateway-worker/composer.json
  28. 565 0
      vendor/workerman/gateway-worker/src/BusinessWorker.php
  29. 1028 0
      vendor/workerman/gateway-worker/src/Gateway.php
  30. 136 0
      vendor/workerman/gateway-worker/src/Lib/Context.php
  31. 76 0
      vendor/workerman/gateway-worker/src/Lib/Db.php
  32. 1977 0
      vendor/workerman/gateway-worker/src/Lib/DbConnection.php
  33. 1361 0
      vendor/workerman/gateway-worker/src/Lib/Gateway.php
  34. 216 0
      vendor/workerman/gateway-worker/src/Protocols/GatewayProtocol.php
  35. 193 0
      vendor/workerman/gateway-worker/src/Register.php

+ 1 - 0
.idea/bansheng.iml

@@ -50,6 +50,7 @@
       <excludeFolder url="file://$MODULE_DIR$/vendor/phpoffice/phpspreadsheet" />
       <excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
       <excludeFolder url="file://$MODULE_DIR$/vendor/workerman/gatewayclient" />
+      <excludeFolder url="file://$MODULE_DIR$/vendor/workerman/gateway-worker" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />

+ 1 - 0
.idea/php.xml

@@ -50,6 +50,7 @@
       <path value="$PROJECT_DIR$/vendor/phpoffice/phpspreadsheet" />
       <path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
       <path value="$PROJECT_DIR$/vendor/workerman/gatewayclient" />
+      <path value="$PROJECT_DIR$/vendor/workerman/gateway-worker" />
     </include_path>
   </component>
   <component name="PhpProjectSharedConfiguration" php_language_level="8.0">

+ 116 - 0
Applications/Chat/Events.php

@@ -0,0 +1,116 @@
+<?php
+/**
+ * This file is part of workerman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+
+/**
+ * 用于检测业务代码死循环或者长时间阻塞等问题
+ * 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload
+ * 然后观察一段时间workerman.log看是否有process_timeout异常
+ */
+//declare(ticks=1);
+
+/**
+ * 聊天主逻辑
+ * 主要是处理 onMessage onClose 
+ */
+use \GatewayWorker\Lib\Gateway;
+use \GatewayWorker\Lib\DbConnection;
+
+class Events
+{
+
+    public static function onWorkerStart(){
+
+        /*global $db;
+        $my_config = array(
+            'host'     => '127.0.0.1',
+            'port'     => '3306',
+            'user'     => 'youeryuan_online',
+            'password' => 'XwXXAFbp8kaYsLKF_1',
+            'dbname'   => 'youeryuan_online',
+            'charset'  => 'utf8',
+        );
+        $db = new DbConnection($my_config);*/
+
+
+
+
+
+    }
+   
+   /**
+    * 有消息时
+    * @param int $client_id
+    * @param mixed $message
+    */
+   public static function onMessage($client_id, $message)
+   {
+       $hex = [];
+       $receiver = '';
+       $nfcid = '';
+       for($i=0; $i<strlen($message); $i++)
+       {
+           //解析ASCII,16进制,小写
+           $one_message = strtolower(dechex(ord($message[$i])));
+
+           //数组
+           $hex[] = $one_message;
+
+           //设备id
+           if($i == 1){ $receiver = $one_message;}
+
+           //nfcid
+           if(in_array($i,[5,6,7,8])){
+               $nfcid .= $one_message;
+           }
+
+       }
+
+       if(count($hex) != 11){
+           return;
+       }
+
+       $hexjson = json_encode($hex);
+       echo $hexjson.PHP_EOL;
+       /*file_put_contents('1.json',$hexjson.PHP_EOL,FILE_APPEND);*/
+
+       //入库
+
+
+
+       return ;
+
+
+
+
+   }
+   
+   /**
+    * 当客户端断开连接时
+    * @param integer $client_id 客户端id
+    */
+   public static function onClose($client_id)
+   {
+       // debug
+      /* echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']}  client_id:$client_id onClose:''\n";
+       
+       // 从房间的客户端列表中删除
+       if(isset($_SESSION['room_id']))
+       {
+           $room_id = $_SESSION['room_id'];
+           $new_message = array('type'=>'logout', 'from_client_id'=>$client_id, 'from_client_name'=>$_SESSION['client_name'], 'time'=>date('Y-m-d H:i:s'));
+           Gateway::sendToGroup($room_id, json_encode($new_message));
+       }*/
+   }
+  
+}

+ 459 - 0
Applications/Chat/Web/css/bootstrap-theme.css

@@ -0,0 +1,459 @@
+/*!
+ * Bootstrap v3.0.1 by @fat and @mdo
+ * Copyright 2013 Twitter, Inc.
+ * Licensed under http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ */
+
+.btn-default,
+.btn-primary,
+.btn-success,
+.btn-info,
+.btn-warning,
+.btn-danger {
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.btn-default:active,
+.btn-primary:active,
+.btn-success:active,
+.btn-info:active,
+.btn-warning:active,
+.btn-danger:active,
+.btn-default.active,
+.btn-primary.active,
+.btn-success.active,
+.btn-info.active,
+.btn-warning.active,
+.btn-danger.active {
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.btn:active,
+.btn.active {
+  background-image: none;
+}
+
+.btn-default {
+  text-shadow: 0 1px 0 #fff;
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e0e0e0));
+  background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
+  background-image: -moz-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
+  background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);
+  background-repeat: repeat-x;
+  border-color: #dbdbdb;
+  border-color: #ccc;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-default:hover,
+.btn-default:focus {
+  background-color: #e0e0e0;
+  background-position: 0 -15px;
+}
+
+.btn-default:active,
+.btn-default.active {
+  background-color: #e0e0e0;
+  border-color: #dbdbdb;
+}
+
+.btn-primary {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#2d6ca2));
+  background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
+  background-image: -moz-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
+  background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
+  background-repeat: repeat-x;
+  border-color: #2b669a;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-primary:hover,
+.btn-primary:focus {
+  background-color: #2d6ca2;
+  background-position: 0 -15px;
+}
+
+.btn-primary:active,
+.btn-primary.active {
+  background-color: #2d6ca2;
+  border-color: #2b669a;
+}
+
+.btn-success {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#419641));
+  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
+  background-image: -moz-linear-gradient(top, #5cb85c 0%, #419641 100%);
+  background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
+  background-repeat: repeat-x;
+  border-color: #3e8f3e;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-success:hover,
+.btn-success:focus {
+  background-color: #419641;
+  background-position: 0 -15px;
+}
+
+.btn-success:active,
+.btn-success.active {
+  background-color: #419641;
+  border-color: #3e8f3e;
+}
+
+.btn-warning {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#eb9316));
+  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
+  background-image: -moz-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
+  background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
+  background-repeat: repeat-x;
+  border-color: #e38d13;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-warning:hover,
+.btn-warning:focus {
+  background-color: #eb9316;
+  background-position: 0 -15px;
+}
+
+.btn-warning:active,
+.btn-warning.active {
+  background-color: #eb9316;
+  border-color: #e38d13;
+}
+
+.btn-danger {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c12e2a));
+  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
+  background-image: -moz-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
+  background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
+  background-repeat: repeat-x;
+  border-color: #b92c28;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-danger:hover,
+.btn-danger:focus {
+  background-color: #c12e2a;
+  background-position: 0 -15px;
+}
+
+.btn-danger:active,
+.btn-danger.active {
+  background-color: #c12e2a;
+  border-color: #b92c28;
+}
+
+.btn-info {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#2aabd2));
+  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
+  background-image: -moz-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
+  background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
+  background-repeat: repeat-x;
+  border-color: #28a4c9;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-info:hover,
+.btn-info:focus {
+  background-color: #2aabd2;
+  background-position: 0 -15px;
+}
+
+.btn-info:active,
+.btn-info.active {
+  background-color: #2aabd2;
+  border-color: #28a4c9;
+}
+
+.thumbnail,
+.img-thumbnail {
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
+}
+
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+  background-color: #e8e8e8;
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8));
+  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+  background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+  background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
+}
+
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+  background-color: #357ebd;
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
+  background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
+  background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
+  background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
+}
+
+.navbar-default {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8));
+  background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
+  background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
+  background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);
+  background-repeat: repeat-x;
+  border-radius: 4px;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
+}
+
+.navbar-default .navbar-nav > .active > a {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f3f3f3));
+  background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
+  background-image: -moz-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
+  background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);
+  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
+}
+
+.navbar-brand,
+.navbar-nav > li > a {
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
+}
+
+.navbar-inverse {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222));
+  background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);
+  background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%);
+  background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.navbar-inverse .navbar-nav > .active > a {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#222222), to(#282828));
+  background-image: -webkit-linear-gradient(top, #222222 0%, #282828 100%);
+  background-image: -moz-linear-gradient(top, #222222 0%, #282828 100%);
+  background-image: linear-gradient(to bottom, #222222 0%, #282828 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);
+  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
+          box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
+}
+
+.navbar-inverse .navbar-brand,
+.navbar-inverse .navbar-nav > li > a {
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.navbar-static-top,
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  border-radius: 0;
+}
+
+.alert {
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.alert-success {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc));
+  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
+  background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
+  background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
+  background-repeat: repeat-x;
+  border-color: #b2dba1;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
+}
+
+.alert-info {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0));
+  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
+  background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
+  background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
+  background-repeat: repeat-x;
+  border-color: #9acfea;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
+}
+
+.alert-warning {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0));
+  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
+  background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
+  background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
+  background-repeat: repeat-x;
+  border-color: #f5e79e;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
+}
+
+.alert-danger {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3));
+  background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
+  background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
+  background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
+  background-repeat: repeat-x;
+  border-color: #dca7a7;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
+}
+
+.progress {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5));
+  background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
+  background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
+  background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
+}
+
+.progress-bar {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9));
+  background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
+  background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%);
+  background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
+}
+
+.progress-bar-success {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44));
+  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
+  background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%);
+  background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
+}
+
+.progress-bar-info {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5));
+  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
+  background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
+  background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
+}
+
+.progress-bar-warning {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f));
+  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
+  background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
+  background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
+}
+
+.progress-bar-danger {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c));
+  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
+  background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%);
+  background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
+}
+
+.list-group {
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
+}
+
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+  text-shadow: 0 -1px 0 #3071a9;
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3));
+  background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
+  background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%);
+  background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
+  background-repeat: repeat-x;
+  border-color: #3278b3;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
+}
+
+.panel {
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.panel-default > .panel-heading {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8));
+  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+  background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+  background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
+}
+
+.panel-primary > .panel-heading {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
+  background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
+  background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
+  background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
+}
+
+.panel-success > .panel-heading {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6));
+  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
+  background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
+  background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
+}
+
+.panel-info > .panel-heading {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3));
+  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
+  background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
+  background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
+}
+
+.panel-warning > .panel-heading {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc));
+  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
+  background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
+  background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
+}
+
+.panel-danger > .panel-heading {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc));
+  background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
+  background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
+  background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
+}
+
+.well {
+  background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5));
+  background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
+  background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
+  background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
+  background-repeat: repeat-x;
+  border-color: #dcdcdc;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
+  -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
+          box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
+}

File diff suppressed because it is too large
+ 8 - 0
Applications/Chat/Web/css/bootstrap-theme.min.css


+ 7059 - 0
Applications/Chat/Web/css/bootstrap.css

@@ -0,0 +1,7059 @@
+/*!
+ * Bootstrap v3.0.1 by @fat and @mdo
+ * Copyright 2013 Twitter, Inc.
+ * Licensed under http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ */
+
+/*! normalize.css v2.1.3 | MIT License | git.io/normalize */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+  display: block;
+}
+
+audio,
+canvas,
+video {
+  display: inline-block;
+}
+
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+
+[hidden],
+template {
+  display: none;
+}
+
+html {
+  font-family: sans-serif;
+  -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+}
+
+body {
+  margin: 0;
+}
+
+a {
+  background: transparent;
+}
+
+a:focus {
+  outline: thin dotted;
+}
+
+a:active,
+a:hover {
+  outline: 0;
+}
+
+h1 {
+  margin: 0.67em 0;
+  font-size: 2em;
+}
+
+abbr[title] {
+  border-bottom: 1px dotted;
+}
+
+b,
+strong {
+  font-weight: bold;
+}
+
+dfn {
+  font-style: italic;
+}
+
+hr {
+  height: 0;
+  -moz-box-sizing: content-box;
+       box-sizing: content-box;
+}
+
+mark {
+  color: #000;
+  background: #ff0;
+}
+
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, serif;
+  font-size: 1em;
+}
+
+pre {
+  white-space: pre-wrap;
+}
+
+q {
+  quotes: "\201C" "\201D" "\2018" "\2019";
+}
+
+small {
+  font-size: 80%;
+}
+
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+
+sup {
+  top: -0.5em;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+img {
+  border: 0;
+}
+
+svg:not(:root) {
+  overflow: hidden;
+}
+
+figure {
+  margin: 0;
+}
+
+fieldset {
+  padding: 0.35em 0.625em 0.75em;
+  margin: 0 2px;
+  border: 1px solid #c0c0c0;
+}
+
+legend {
+  padding: 0;
+  border: 0;
+}
+
+button,
+input,
+select,
+textarea {
+  margin: 0;
+  font-family: inherit;
+  font-size: 100%;
+}
+
+button,
+input {
+  line-height: normal;
+}
+
+button,
+select {
+  text-transform: none;
+}
+
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  cursor: pointer;
+  -webkit-appearance: button;
+}
+
+button[disabled],
+html input[disabled] {
+  cursor: default;
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+  padding: 0;
+  box-sizing: border-box;
+}
+
+input[type="search"] {
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+  -webkit-appearance: textfield;
+}
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+
+textarea {
+  overflow: auto;
+  vertical-align: top;
+}
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+@media print {
+  * {
+    color: #000 !important;
+    text-shadow: none !important;
+    background: transparent !important;
+    box-shadow: none !important;
+  }
+  a,
+  a:visited {
+    text-decoration: underline;
+  }
+  a[href]:after {
+    content: " (" attr(href) ")";
+  }
+  abbr[title]:after {
+    content: " (" attr(title) ")";
+  }
+  a[href^="javascript:"]:after,
+  a[href^="#"]:after {
+    content: "";
+  }
+  pre,
+  blockquote {
+    border: 1px solid #999;
+    page-break-inside: avoid;
+  }
+  thead {
+    display: table-header-group;
+  }
+  tr,
+  img {
+    page-break-inside: avoid;
+  }
+  img {
+    max-width: 100% !important;
+  }
+  @page  {
+    margin: 2cm .5cm;
+  }
+  p,
+  h2,
+  h3 {
+    orphans: 3;
+    widows: 3;
+  }
+  h2,
+  h3 {
+    page-break-after: avoid;
+  }
+  select {
+    background: #fff !important;
+  }
+  .navbar {
+    display: none;
+  }
+  .table td,
+  .table th {
+    background-color: #fff !important;
+  }
+  .btn > .caret,
+  .dropup > .btn > .caret {
+    border-top-color: #000 !important;
+  }
+  .label {
+    border: 1px solid #000;
+  }
+  .table {
+    border-collapse: collapse !important;
+  }
+  .table-bordered th,
+  .table-bordered td {
+    border: 1px solid #ddd !important;
+  }
+}
+
+*,
+*:before,
+*:after {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+html {
+  font-size: 62.5%;
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+body {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  line-height: 1.428571429;
+  color: #333333;
+  background-color: #ffffff;
+}
+
+input,
+button,
+select,
+textarea {
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+
+a {
+  color: #428bca;
+  text-decoration: none;
+}
+
+a:hover,
+a:focus {
+  color: #2a6496;
+  text-decoration: underline;
+}
+
+a:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+img {
+  vertical-align: middle;
+}
+
+.img-responsive {
+  display: block;
+  height: auto;
+  max-width: 100%;
+}
+
+.img-rounded {
+  border-radius: 6px;
+}
+
+.img-thumbnail {
+  display: inline-block;
+  height: auto;
+  max-width: 100%;
+  padding: 4px;
+  line-height: 1.428571429;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 4px;
+  -webkit-transition: all 0.2s ease-in-out;
+          transition: all 0.2s ease-in-out;
+}
+
+.img-circle {
+  border-radius: 50%;
+}
+
+hr {
+  margin-top: 20px;
+  margin-bottom: 20px;
+  border: 0;
+  border-top: 1px solid #eeeeee;
+}
+
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  border: 0;
+}
+
+p {
+  margin: 0 0 10px;
+}
+
+.lead {
+  margin-bottom: 20px;
+  font-size: 16px;
+  font-weight: 200;
+  line-height: 1.4;
+}
+
+@media (min-width: 768px) {
+  .lead {
+    font-size: 21px;
+  }
+}
+
+small,
+.small {
+  font-size: 85%;
+}
+
+cite {
+  font-style: normal;
+}
+
+.text-muted {
+  color: #999999;
+}
+
+.text-primary {
+  color: #428bca;
+}
+
+.text-primary:hover {
+  color: #3071a9;
+}
+
+.text-warning {
+  color: #c09853;
+}
+
+.text-warning:hover {
+  color: #a47e3c;
+}
+
+.text-danger {
+  color: #b94a48;
+}
+
+.text-danger:hover {
+  color: #953b39;
+}
+
+.text-success {
+  color: #468847;
+}
+
+.text-success:hover {
+  color: #356635;
+}
+
+.text-info {
+  color: #3a87ad;
+}
+
+.text-info:hover {
+  color: #2d6987;
+}
+
+.text-left {
+  text-align: left;
+}
+
+.text-right {
+  text-align: right;
+}
+
+.text-center {
+  text-align: center;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6 {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-weight: 500;
+  line-height: 1.1;
+  color: inherit;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small,
+.h1 small,
+.h2 small,
+.h3 small,
+.h4 small,
+.h5 small,
+.h6 small,
+h1 .small,
+h2 .small,
+h3 .small,
+h4 .small,
+h5 .small,
+h6 .small,
+.h1 .small,
+.h2 .small,
+.h3 .small,
+.h4 .small,
+.h5 .small,
+.h6 .small {
+  font-weight: normal;
+  line-height: 1;
+  color: #999999;
+}
+
+h1,
+h2,
+h3 {
+  margin-top: 20px;
+  margin-bottom: 10px;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h1 .small,
+h2 .small,
+h3 .small {
+  font-size: 65%;
+}
+
+h4,
+h5,
+h6 {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+
+h4 small,
+h5 small,
+h6 small,
+h4 .small,
+h5 .small,
+h6 .small {
+  font-size: 75%;
+}
+
+h1,
+.h1 {
+  font-size: 36px;
+}
+
+h2,
+.h2 {
+  font-size: 30px;
+}
+
+h3,
+.h3 {
+  font-size: 24px;
+}
+
+h4,
+.h4 {
+  font-size: 18px;
+}
+
+h5,
+.h5 {
+  font-size: 14px;
+}
+
+h6,
+.h6 {
+  font-size: 12px;
+}
+
+.page-header {
+  padding-bottom: 9px;
+  margin: 40px 0 20px;
+  border-bottom: 1px solid #eeeeee;
+}
+
+ul,
+ol {
+  margin-top: 0;
+  margin-bottom: 10px;
+}
+
+ul ul,
+ol ul,
+ul ol,
+ol ol {
+  margin-bottom: 0;
+}
+
+.list-unstyled {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-inline {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-inline > li {
+  display: inline-block;
+  padding-right: 5px;
+  padding-left: 5px;
+}
+
+.list-inline > li:first-child {
+  padding-left: 0;
+}
+
+dl {
+  margin-bottom: 20px;
+}
+
+dt,
+dd {
+  line-height: 1.428571429;
+}
+
+dt {
+  font-weight: bold;
+}
+
+dd {
+  margin-left: 0;
+}
+
+@media (min-width: 768px) {
+  .dl-horizontal dt {
+    float: left;
+    width: 160px;
+    overflow: hidden;
+    clear: left;
+    text-align: right;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .dl-horizontal dd {
+    margin-left: 180px;
+  }
+  .dl-horizontal dd:before,
+  .dl-horizontal dd:after {
+    display: table;
+    content: " ";
+  }
+  .dl-horizontal dd:after {
+    clear: both;
+  }
+  .dl-horizontal dd:before,
+  .dl-horizontal dd:after {
+    display: table;
+    content: " ";
+  }
+  .dl-horizontal dd:after {
+    clear: both;
+  }
+}
+
+abbr[title],
+abbr[data-original-title] {
+  cursor: help;
+  border-bottom: 1px dotted #999999;
+}
+
+abbr.initialism {
+  font-size: 90%;
+  text-transform: uppercase;
+}
+
+blockquote {
+  padding: 10px 20px;
+  margin: 0 0 20px;
+  border-left: 5px solid #eeeeee;
+}
+
+blockquote p {
+  font-size: 17.5px;
+  font-weight: 300;
+  line-height: 1.25;
+}
+
+blockquote p:last-child {
+  margin-bottom: 0;
+}
+
+blockquote small {
+  display: block;
+  line-height: 1.428571429;
+  color: #999999;
+}
+
+blockquote small:before {
+  content: '\2014 \00A0';
+}
+
+blockquote.pull-right {
+  padding-right: 15px;
+  padding-left: 0;
+  border-right: 5px solid #eeeeee;
+  border-left: 0;
+}
+
+blockquote.pull-right p,
+blockquote.pull-right small,
+blockquote.pull-right .small {
+  text-align: right;
+}
+
+blockquote.pull-right small:before,
+blockquote.pull-right .small:before {
+  content: '';
+}
+
+blockquote.pull-right small:after,
+blockquote.pull-right .small:after {
+  content: '\00A0 \2014';
+}
+
+blockquote:before,
+blockquote:after {
+  content: "";
+}
+
+address {
+  margin-bottom: 20px;
+  font-style: normal;
+  line-height: 1.428571429;
+}
+
+code,
+kbd,
+pre,
+samp {
+  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+}
+
+code {
+  padding: 2px 4px;
+  font-size: 90%;
+  color: #c7254e;
+  white-space: nowrap;
+  background-color: #f9f2f4;
+  border-radius: 4px;
+}
+
+pre {
+  display: block;
+  padding: 9.5px;
+  margin: 0 0 10px;
+  font-size: 13px;
+  line-height: 1.428571429;
+  color: #333333;
+  word-break: break-all;
+  word-wrap: break-word;
+  background-color: #f5f5f5;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+}
+
+pre code {
+  padding: 0;
+  font-size: inherit;
+  color: inherit;
+  white-space: pre-wrap;
+  background-color: transparent;
+  border-radius: 0;
+}
+
+.pre-scrollable {
+  max-height: 340px;
+  overflow-y: scroll;
+}
+
+.container {
+  padding-right: 15px;
+  padding-left: 15px;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.container:before,
+.container:after {
+  display: table;
+  content: " ";
+}
+
+.container:after {
+  clear: both;
+}
+
+.container:before,
+.container:after {
+  display: table;
+  content: " ";
+}
+
+.container:after {
+  clear: both;
+}
+
+.row {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+.row:before,
+.row:after {
+  display: table;
+  content: " ";
+}
+
+.row:after {
+  clear: both;
+}
+
+.row:before,
+.row:after {
+  display: table;
+  content: " ";
+}
+
+.row:after {
+  clear: both;
+}
+
+.col-xs-1,
+.col-sm-1,
+.col-md-1,
+.col-lg-1,
+.col-xs-2,
+.col-sm-2,
+.col-md-2,
+.col-lg-2,
+.col-xs-3,
+.col-sm-3,
+.col-md-3,
+.col-lg-3,
+.col-xs-4,
+.col-sm-4,
+.col-md-4,
+.col-lg-4,
+.col-xs-5,
+.col-sm-5,
+.col-md-5,
+.col-lg-5,
+.col-xs-6,
+.col-sm-6,
+.col-md-6,
+.col-lg-6,
+.col-xs-7,
+.col-sm-7,
+.col-md-7,
+.col-lg-7,
+.col-xs-8,
+.col-sm-8,
+.col-md-8,
+.col-lg-8,
+.col-xs-9,
+.col-sm-9,
+.col-md-9,
+.col-lg-9,
+.col-xs-10,
+.col-sm-10,
+.col-md-10,
+.col-lg-10,
+.col-xs-11,
+.col-sm-11,
+.col-md-11,
+.col-lg-11,
+.col-xs-12,
+.col-sm-12,
+.col-md-12,
+.col-lg-12 {
+  position: relative;
+  min-height: 1px;
+  padding-right: 15px;
+  padding-left: 15px;
+}
+
+.col-xs-1,
+.col-xs-2,
+.col-xs-3,
+.col-xs-4,
+.col-xs-5,
+.col-xs-6,
+.col-xs-7,
+.col-xs-8,
+.col-xs-9,
+.col-xs-10,
+.col-xs-11 {
+  float: left;
+}
+
+.col-xs-12 {
+  width: 100%;
+}
+
+.col-xs-11 {
+  width: 91.66666666666666%;
+}
+
+.col-xs-10 {
+  width: 83.33333333333334%;
+}
+
+.col-xs-9 {
+  width: 75%;
+}
+
+.col-xs-8 {
+  width: 66.66666666666666%;
+}
+
+.col-xs-7 {
+  width: 58.333333333333336%;
+}
+
+.col-xs-6 {
+  width: 50%;
+}
+
+.col-xs-5 {
+  width: 41.66666666666667%;
+}
+
+.col-xs-4 {
+  width: 33.33333333333333%;
+}
+
+.col-xs-3 {
+  width: 25%;
+}
+
+.col-xs-2 {
+  width: 16.666666666666664%;
+}
+
+.col-xs-1 {
+  width: 8.333333333333332%;
+}
+
+.col-xs-pull-12 {
+  right: 100%;
+}
+
+.col-xs-pull-11 {
+  right: 91.66666666666666%;
+}
+
+.col-xs-pull-10 {
+  right: 83.33333333333334%;
+}
+
+.col-xs-pull-9 {
+  right: 75%;
+}
+
+.col-xs-pull-8 {
+  right: 66.66666666666666%;
+}
+
+.col-xs-pull-7 {
+  right: 58.333333333333336%;
+}
+
+.col-xs-pull-6 {
+  right: 50%;
+}
+
+.col-xs-pull-5 {
+  right: 41.66666666666667%;
+}
+
+.col-xs-pull-4 {
+  right: 33.33333333333333%;
+}
+
+.col-xs-pull-3 {
+  right: 25%;
+}
+
+.col-xs-pull-2 {
+  right: 16.666666666666664%;
+}
+
+.col-xs-pull-1 {
+  right: 8.333333333333332%;
+}
+
+.col-xs-push-12 {
+  left: 100%;
+}
+
+.col-xs-push-11 {
+  left: 91.66666666666666%;
+}
+
+.col-xs-push-10 {
+  left: 83.33333333333334%;
+}
+
+.col-xs-push-9 {
+  left: 75%;
+}
+
+.col-xs-push-8 {
+  left: 66.66666666666666%;
+}
+
+.col-xs-push-7 {
+  left: 58.333333333333336%;
+}
+
+.col-xs-push-6 {
+  left: 50%;
+}
+
+.col-xs-push-5 {
+  left: 41.66666666666667%;
+}
+
+.col-xs-push-4 {
+  left: 33.33333333333333%;
+}
+
+.col-xs-push-3 {
+  left: 25%;
+}
+
+.col-xs-push-2 {
+  left: 16.666666666666664%;
+}
+
+.col-xs-push-1 {
+  left: 8.333333333333332%;
+}
+
+.col-xs-offset-12 {
+  margin-left: 100%;
+}
+
+.col-xs-offset-11 {
+  margin-left: 91.66666666666666%;
+}
+
+.col-xs-offset-10 {
+  margin-left: 83.33333333333334%;
+}
+
+.col-xs-offset-9 {
+  margin-left: 75%;
+}
+
+.col-xs-offset-8 {
+  margin-left: 66.66666666666666%;
+}
+
+.col-xs-offset-7 {
+  margin-left: 58.333333333333336%;
+}
+
+.col-xs-offset-6 {
+  margin-left: 50%;
+}
+
+.col-xs-offset-5 {
+  margin-left: 41.66666666666667%;
+}
+
+.col-xs-offset-4 {
+  margin-left: 33.33333333333333%;
+}
+
+.col-xs-offset-3 {
+  margin-left: 25%;
+}
+
+.col-xs-offset-2 {
+  margin-left: 16.666666666666664%;
+}
+
+.col-xs-offset-1 {
+  margin-left: 8.333333333333332%;
+}
+
+@media (min-width: 768px) {
+  .container {
+    width: 750px;
+  }
+  .col-sm-1,
+  .col-sm-2,
+  .col-sm-3,
+  .col-sm-4,
+  .col-sm-5,
+  .col-sm-6,
+  .col-sm-7,
+  .col-sm-8,
+  .col-sm-9,
+  .col-sm-10,
+  .col-sm-11 {
+    float: left;
+  }
+  .col-sm-12 {
+    width: 100%;
+  }
+  .col-sm-11 {
+    width: 91.66666666666666%;
+  }
+  .col-sm-10 {
+    width: 83.33333333333334%;
+  }
+  .col-sm-9 {
+    width: 75%;
+  }
+  .col-sm-8 {
+    width: 66.66666666666666%;
+  }
+  .col-sm-7 {
+    width: 58.333333333333336%;
+  }
+  .col-sm-6 {
+    width: 50%;
+  }
+  .col-sm-5 {
+    width: 41.66666666666667%;
+  }
+  .col-sm-4 {
+    width: 33.33333333333333%;
+  }
+  .col-sm-3 {
+    width: 25%;
+  }
+  .col-sm-2 {
+    width: 16.666666666666664%;
+  }
+  .col-sm-1 {
+    width: 8.333333333333332%;
+  }
+  .col-sm-pull-12 {
+    right: 100%;
+  }
+  .col-sm-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-sm-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-sm-pull-9 {
+    right: 75%;
+  }
+  .col-sm-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-sm-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-sm-pull-6 {
+    right: 50%;
+  }
+  .col-sm-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-sm-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-sm-pull-3 {
+    right: 25%;
+  }
+  .col-sm-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-sm-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-sm-push-12 {
+    left: 100%;
+  }
+  .col-sm-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-sm-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-sm-push-9 {
+    left: 75%;
+  }
+  .col-sm-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-sm-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-sm-push-6 {
+    left: 50%;
+  }
+  .col-sm-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-sm-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-sm-push-3 {
+    left: 25%;
+  }
+  .col-sm-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-sm-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-sm-offset-12 {
+    margin-left: 100%;
+  }
+  .col-sm-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+  .col-sm-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-sm-offset-9 {
+    margin-left: 75%;
+  }
+  .col-sm-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-sm-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-sm-offset-6 {
+    margin-left: 50%;
+  }
+  .col-sm-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-sm-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-sm-offset-3 {
+    margin-left: 25%;
+  }
+  .col-sm-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-sm-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+}
+
+@media (min-width: 992px) {
+  .container {
+    width: 970px;
+  }
+  .col-md-1,
+  .col-md-2,
+  .col-md-3,
+  .col-md-4,
+  .col-md-5,
+  .col-md-6,
+  .col-md-7,
+  .col-md-8,
+  .col-md-9,
+  .col-md-10,
+  .col-md-11 {
+    float: left;
+  }
+  .col-md-12 {
+    width: 100%;
+  }
+  .col-md-11 {
+    width: 91.66666666666666%;
+  }
+  .col-md-10 {
+    width: 83.33333333333334%;
+  }
+  .col-md-9 {
+    width: 75%;
+  }
+  .col-md-8 {
+    width: 66.66666666666666%;
+  }
+  .col-md-7 {
+    width: 58.333333333333336%;
+  }
+  .col-md-6 {
+    width: 50%;
+  }
+  .col-md-5 {
+    width: 41.66666666666667%;
+  }
+  .col-md-4 {
+    width: 33.33333333333333%;
+  }
+  .col-md-3 {
+    width: 25%;
+  }
+  .col-md-2 {
+    width: 16.666666666666664%;
+  }
+  .col-md-1 {
+    width: 8.333333333333332%;
+  }
+  .col-md-pull-12 {
+    right: 100%;
+  }
+  .col-md-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-md-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-md-pull-9 {
+    right: 75%;
+  }
+  .col-md-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-md-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-md-pull-6 {
+    right: 50%;
+  }
+  .col-md-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-md-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-md-pull-3 {
+    right: 25%;
+  }
+  .col-md-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-md-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-md-push-12 {
+    left: 100%;
+  }
+  .col-md-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-md-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-md-push-9 {
+    left: 75%;
+  }
+  .col-md-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-md-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-md-push-6 {
+    left: 50%;
+  }
+  .col-md-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-md-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-md-push-3 {
+    left: 25%;
+  }
+  .col-md-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-md-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-md-offset-12 {
+    margin-left: 100%;
+  }
+  .col-md-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+  .col-md-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-md-offset-9 {
+    margin-left: 75%;
+  }
+  .col-md-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-md-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-md-offset-6 {
+    margin-left: 50%;
+  }
+  .col-md-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-md-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-md-offset-3 {
+    margin-left: 25%;
+  }
+  .col-md-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-md-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+}
+
+@media (min-width: 1200px) {
+  .container {
+    width: 1170px;
+  }
+  .col-lg-1,
+  .col-lg-2,
+  .col-lg-3,
+  .col-lg-4,
+  .col-lg-5,
+  .col-lg-6,
+  .col-lg-7,
+  .col-lg-8,
+  .col-lg-9,
+  .col-lg-10,
+  .col-lg-11 {
+    float: left;
+  }
+  .col-lg-12 {
+    width: 100%;
+  }
+  .col-lg-11 {
+    width: 91.66666666666666%;
+  }
+  .col-lg-10 {
+    width: 83.33333333333334%;
+  }
+  .col-lg-9 {
+    width: 75%;
+  }
+  .col-lg-8 {
+    width: 66.66666666666666%;
+  }
+  .col-lg-7 {
+    width: 58.333333333333336%;
+  }
+  .col-lg-6 {
+    width: 50%;
+  }
+  .col-lg-5 {
+    width: 41.66666666666667%;
+  }
+  .col-lg-4 {
+    width: 33.33333333333333%;
+  }
+  .col-lg-3 {
+    width: 25%;
+  }
+  .col-lg-2 {
+    width: 16.666666666666664%;
+  }
+  .col-lg-1 {
+    width: 8.333333333333332%;
+  }
+  .col-lg-pull-12 {
+    right: 100%;
+  }
+  .col-lg-pull-11 {
+    right: 91.66666666666666%;
+  }
+  .col-lg-pull-10 {
+    right: 83.33333333333334%;
+  }
+  .col-lg-pull-9 {
+    right: 75%;
+  }
+  .col-lg-pull-8 {
+    right: 66.66666666666666%;
+  }
+  .col-lg-pull-7 {
+    right: 58.333333333333336%;
+  }
+  .col-lg-pull-6 {
+    right: 50%;
+  }
+  .col-lg-pull-5 {
+    right: 41.66666666666667%;
+  }
+  .col-lg-pull-4 {
+    right: 33.33333333333333%;
+  }
+  .col-lg-pull-3 {
+    right: 25%;
+  }
+  .col-lg-pull-2 {
+    right: 16.666666666666664%;
+  }
+  .col-lg-pull-1 {
+    right: 8.333333333333332%;
+  }
+  .col-lg-push-12 {
+    left: 100%;
+  }
+  .col-lg-push-11 {
+    left: 91.66666666666666%;
+  }
+  .col-lg-push-10 {
+    left: 83.33333333333334%;
+  }
+  .col-lg-push-9 {
+    left: 75%;
+  }
+  .col-lg-push-8 {
+    left: 66.66666666666666%;
+  }
+  .col-lg-push-7 {
+    left: 58.333333333333336%;
+  }
+  .col-lg-push-6 {
+    left: 50%;
+  }
+  .col-lg-push-5 {
+    left: 41.66666666666667%;
+  }
+  .col-lg-push-4 {
+    left: 33.33333333333333%;
+  }
+  .col-lg-push-3 {
+    left: 25%;
+  }
+  .col-lg-push-2 {
+    left: 16.666666666666664%;
+  }
+  .col-lg-push-1 {
+    left: 8.333333333333332%;
+  }
+  .col-lg-offset-12 {
+    margin-left: 100%;
+  }
+  .col-lg-offset-11 {
+    margin-left: 91.66666666666666%;
+  }
+  .col-lg-offset-10 {
+    margin-left: 83.33333333333334%;
+  }
+  .col-lg-offset-9 {
+    margin-left: 75%;
+  }
+  .col-lg-offset-8 {
+    margin-left: 66.66666666666666%;
+  }
+  .col-lg-offset-7 {
+    margin-left: 58.333333333333336%;
+  }
+  .col-lg-offset-6 {
+    margin-left: 50%;
+  }
+  .col-lg-offset-5 {
+    margin-left: 41.66666666666667%;
+  }
+  .col-lg-offset-4 {
+    margin-left: 33.33333333333333%;
+  }
+  .col-lg-offset-3 {
+    margin-left: 25%;
+  }
+  .col-lg-offset-2 {
+    margin-left: 16.666666666666664%;
+  }
+  .col-lg-offset-1 {
+    margin-left: 8.333333333333332%;
+  }
+}
+
+table {
+  max-width: 100%;
+  background-color: transparent;
+}
+
+th {
+  text-align: left;
+}
+
+.table {
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.table > thead > tr > th,
+.table > tbody > tr > th,
+.table > tfoot > tr > th,
+.table > thead > tr > td,
+.table > tbody > tr > td,
+.table > tfoot > tr > td {
+  padding: 8px;
+  line-height: 1.428571429;
+  vertical-align: top;
+  border-top: 1px solid #dddddd;
+}
+
+.table > thead > tr > th {
+  vertical-align: bottom;
+  border-bottom: 2px solid #dddddd;
+}
+
+.table > caption + thead > tr:first-child > th,
+.table > colgroup + thead > tr:first-child > th,
+.table > thead:first-child > tr:first-child > th,
+.table > caption + thead > tr:first-child > td,
+.table > colgroup + thead > tr:first-child > td,
+.table > thead:first-child > tr:first-child > td {
+  border-top: 0;
+}
+
+.table > tbody + tbody {
+  border-top: 2px solid #dddddd;
+}
+
+.table .table {
+  background-color: #ffffff;
+}
+
+.table-condensed > thead > tr > th,
+.table-condensed > tbody > tr > th,
+.table-condensed > tfoot > tr > th,
+.table-condensed > thead > tr > td,
+.table-condensed > tbody > tr > td,
+.table-condensed > tfoot > tr > td {
+  padding: 5px;
+}
+
+.table-bordered {
+  border: 1px solid #dddddd;
+}
+
+.table-bordered > thead > tr > th,
+.table-bordered > tbody > tr > th,
+.table-bordered > tfoot > tr > th,
+.table-bordered > thead > tr > td,
+.table-bordered > tbody > tr > td,
+.table-bordered > tfoot > tr > td {
+  border: 1px solid #dddddd;
+}
+
+.table-bordered > thead > tr > th,
+.table-bordered > thead > tr > td {
+  border-bottom-width: 2px;
+}
+
+.table-striped > tbody > tr:nth-child(odd) > td,
+.table-striped > tbody > tr:nth-child(odd) > th {
+  background-color: #f9f9f9;
+}
+
+.table-hover > tbody > tr:hover > td,
+.table-hover > tbody > tr:hover > th {
+  background-color: #f5f5f5;
+}
+
+table col[class*="col-"] {
+  display: table-column;
+  float: none;
+}
+
+table td[class*="col-"],
+table th[class*="col-"] {
+  display: table-cell;
+  float: none;
+}
+
+.table > thead > tr > td.active,
+.table > tbody > tr > td.active,
+.table > tfoot > tr > td.active,
+.table > thead > tr > th.active,
+.table > tbody > tr > th.active,
+.table > tfoot > tr > th.active,
+.table > thead > tr.active > td,
+.table > tbody > tr.active > td,
+.table > tfoot > tr.active > td,
+.table > thead > tr.active > th,
+.table > tbody > tr.active > th,
+.table > tfoot > tr.active > th {
+  background-color: #f5f5f5;
+}
+
+.table > thead > tr > td.success,
+.table > tbody > tr > td.success,
+.table > tfoot > tr > td.success,
+.table > thead > tr > th.success,
+.table > tbody > tr > th.success,
+.table > tfoot > tr > th.success,
+.table > thead > tr.success > td,
+.table > tbody > tr.success > td,
+.table > tfoot > tr.success > td,
+.table > thead > tr.success > th,
+.table > tbody > tr.success > th,
+.table > tfoot > tr.success > th {
+  background-color: #dff0d8;
+}
+
+.table-hover > tbody > tr > td.success:hover,
+.table-hover > tbody > tr > th.success:hover,
+.table-hover > tbody > tr.success:hover > td,
+.table-hover > tbody > tr.success:hover > th {
+  background-color: #d0e9c6;
+}
+
+.table > thead > tr > td.danger,
+.table > tbody > tr > td.danger,
+.table > tfoot > tr > td.danger,
+.table > thead > tr > th.danger,
+.table > tbody > tr > th.danger,
+.table > tfoot > tr > th.danger,
+.table > thead > tr.danger > td,
+.table > tbody > tr.danger > td,
+.table > tfoot > tr.danger > td,
+.table > thead > tr.danger > th,
+.table > tbody > tr.danger > th,
+.table > tfoot > tr.danger > th {
+  background-color: #f2dede;
+}
+
+.table-hover > tbody > tr > td.danger:hover,
+.table-hover > tbody > tr > th.danger:hover,
+.table-hover > tbody > tr.danger:hover > td,
+.table-hover > tbody > tr.danger:hover > th {
+  background-color: #ebcccc;
+}
+
+.table > thead > tr > td.warning,
+.table > tbody > tr > td.warning,
+.table > tfoot > tr > td.warning,
+.table > thead > tr > th.warning,
+.table > tbody > tr > th.warning,
+.table > tfoot > tr > th.warning,
+.table > thead > tr.warning > td,
+.table > tbody > tr.warning > td,
+.table > tfoot > tr.warning > td,
+.table > thead > tr.warning > th,
+.table > tbody > tr.warning > th,
+.table > tfoot > tr.warning > th {
+  background-color: #fcf8e3;
+}
+
+.table-hover > tbody > tr > td.warning:hover,
+.table-hover > tbody > tr > th.warning:hover,
+.table-hover > tbody > tr.warning:hover > td,
+.table-hover > tbody > tr.warning:hover > th {
+  background-color: #faf2cc;
+}
+
+@media (max-width: 767px) {
+  .table-responsive {
+    width: 100%;
+    margin-bottom: 15px;
+    overflow-x: scroll;
+    overflow-y: hidden;
+    border: 1px solid #dddddd;
+    -ms-overflow-style: -ms-autohiding-scrollbar;
+    -webkit-overflow-scrolling: touch;
+  }
+  .table-responsive > .table {
+    margin-bottom: 0;
+  }
+  .table-responsive > .table > thead > tr > th,
+  .table-responsive > .table > tbody > tr > th,
+  .table-responsive > .table > tfoot > tr > th,
+  .table-responsive > .table > thead > tr > td,
+  .table-responsive > .table > tbody > tr > td,
+  .table-responsive > .table > tfoot > tr > td {
+    white-space: nowrap;
+  }
+  .table-responsive > .table-bordered {
+    border: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr > th:first-child,
+  .table-responsive > .table-bordered > tbody > tr > th:first-child,
+  .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+  .table-responsive > .table-bordered > thead > tr > td:first-child,
+  .table-responsive > .table-bordered > tbody > tr > td:first-child,
+  .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+    border-left: 0;
+  }
+  .table-responsive > .table-bordered > thead > tr > th:last-child,
+  .table-responsive > .table-bordered > tbody > tr > th:last-child,
+  .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+  .table-responsive > .table-bordered > thead > tr > td:last-child,
+  .table-responsive > .table-bordered > tbody > tr > td:last-child,
+  .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+    border-right: 0;
+  }
+  .table-responsive > .table-bordered > tbody > tr:last-child > th,
+  .table-responsive > .table-bordered > tfoot > tr:last-child > th,
+  .table-responsive > .table-bordered > tbody > tr:last-child > td,
+  .table-responsive > .table-bordered > tfoot > tr:last-child > td {
+    border-bottom: 0;
+  }
+}
+
+fieldset {
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+
+legend {
+  display: block;
+  width: 100%;
+  padding: 0;
+  margin-bottom: 20px;
+  font-size: 21px;
+  line-height: inherit;
+  color: #333333;
+  border: 0;
+  border-bottom: 1px solid #e5e5e5;
+}
+
+label {
+  display: inline-block;
+  margin-bottom: 5px;
+  font-weight: bold;
+}
+
+input[type="search"] {
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+  margin: 4px 0 0;
+  margin-top: 1px \9;
+  /* IE8-9 */
+
+  line-height: normal;
+}
+
+input[type="file"] {
+  display: block;
+}
+
+select[multiple],
+select[size] {
+  height: auto;
+}
+
+select optgroup {
+  font-family: inherit;
+  font-size: inherit;
+  font-style: inherit;
+}
+
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+input[type="number"]::-webkit-outer-spin-button,
+input[type="number"]::-webkit-inner-spin-button {
+  height: auto;
+}
+
+output {
+  display: block;
+  padding-top: 7px;
+  font-size: 14px;
+  line-height: 1.428571429;
+  color: #555555;
+  vertical-align: middle;
+}
+
+.form-control:-moz-placeholder {
+  color: #999999;
+}
+
+.form-control::-moz-placeholder {
+  color: #999999;
+}
+
+.form-control:-ms-input-placeholder {
+  color: #999999;
+}
+
+.form-control::-webkit-input-placeholder {
+  color: #999999;
+}
+
+.form-control {
+  display: block;
+  width: 100%;
+  height: 34px;
+  padding: 6px 12px;
+  font-size: 14px;
+  line-height: 1.428571429;
+  color: #555555;
+  vertical-align: middle;
+  background-color: #ffffff;
+  background-image: none;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+          transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+}
+
+.form-control:focus {
+  border-color: #66afe9;
+  outline: 0;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
+}
+
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+  cursor: not-allowed;
+  background-color: #eeeeee;
+}
+
+textarea.form-control {
+  height: auto;
+}
+
+.form-group {
+  margin-bottom: 15px;
+}
+
+.radio,
+.checkbox {
+  display: block;
+  min-height: 20px;
+  padding-left: 20px;
+  margin-top: 10px;
+  margin-bottom: 10px;
+  vertical-align: middle;
+}
+
+.radio label,
+.checkbox label {
+  display: inline;
+  margin-bottom: 0;
+  font-weight: normal;
+  cursor: pointer;
+}
+
+.radio input[type="radio"],
+.radio-inline input[type="radio"],
+.checkbox input[type="checkbox"],
+.checkbox-inline input[type="checkbox"] {
+  float: left;
+  margin-left: -20px;
+}
+
+.radio + .radio,
+.checkbox + .checkbox {
+  margin-top: -5px;
+}
+
+.radio-inline,
+.checkbox-inline {
+  display: inline-block;
+  padding-left: 20px;
+  margin-bottom: 0;
+  font-weight: normal;
+  vertical-align: middle;
+  cursor: pointer;
+}
+
+.radio-inline + .radio-inline,
+.checkbox-inline + .checkbox-inline {
+  margin-top: 0;
+  margin-left: 10px;
+}
+
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+.radio[disabled],
+.radio-inline[disabled],
+.checkbox[disabled],
+.checkbox-inline[disabled],
+fieldset[disabled] input[type="radio"],
+fieldset[disabled] input[type="checkbox"],
+fieldset[disabled] .radio,
+fieldset[disabled] .radio-inline,
+fieldset[disabled] .checkbox,
+fieldset[disabled] .checkbox-inline {
+  cursor: not-allowed;
+}
+
+.input-sm {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+select.input-sm {
+  height: 30px;
+  line-height: 30px;
+}
+
+textarea.input-sm {
+  height: auto;
+}
+
+.input-lg {
+  height: 45px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+select.input-lg {
+  height: 45px;
+  line-height: 45px;
+}
+
+textarea.input-lg {
+  height: auto;
+}
+
+.has-warning .help-block,
+.has-warning .control-label,
+.has-warning .radio,
+.has-warning .checkbox,
+.has-warning .radio-inline,
+.has-warning .checkbox-inline {
+  color: #c09853;
+}
+
+.has-warning .form-control {
+  border-color: #c09853;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-warning .form-control:focus {
+  border-color: #a47e3c;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+}
+
+.has-warning .input-group-addon {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #c09853;
+}
+
+.has-error .help-block,
+.has-error .control-label,
+.has-error .radio,
+.has-error .checkbox,
+.has-error .radio-inline,
+.has-error .checkbox-inline {
+  color: #b94a48;
+}
+
+.has-error .form-control {
+  border-color: #b94a48;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-error .form-control:focus {
+  border-color: #953b39;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+}
+
+.has-error .input-group-addon {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #b94a48;
+}
+
+.has-success .help-block,
+.has-success .control-label,
+.has-success .radio,
+.has-success .checkbox,
+.has-success .radio-inline,
+.has-success .checkbox-inline {
+  color: #468847;
+}
+
+.has-success .form-control {
+  border-color: #468847;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-success .form-control:focus {
+  border-color: #356635;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+}
+
+.has-success .input-group-addon {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #468847;
+}
+
+.form-control-static {
+  margin-bottom: 0;
+}
+
+.help-block {
+  display: block;
+  margin-top: 5px;
+  margin-bottom: 10px;
+  color: #737373;
+}
+
+@media (min-width: 768px) {
+  .form-inline .form-group {
+    display: inline-block;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .form-inline .form-control {
+    display: inline-block;
+  }
+  .form-inline .radio,
+  .form-inline .checkbox {
+    display: inline-block;
+    padding-left: 0;
+    margin-top: 0;
+    margin-bottom: 0;
+  }
+  .form-inline .radio input[type="radio"],
+  .form-inline .checkbox input[type="checkbox"] {
+    float: none;
+    margin-left: 0;
+  }
+}
+
+.form-horizontal .control-label,
+.form-horizontal .radio,
+.form-horizontal .checkbox,
+.form-horizontal .radio-inline,
+.form-horizontal .checkbox-inline {
+  padding-top: 7px;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+.form-horizontal .form-group {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after {
+  display: table;
+  content: " ";
+}
+
+.form-horizontal .form-group:after {
+  clear: both;
+}
+
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after {
+  display: table;
+  content: " ";
+}
+
+.form-horizontal .form-group:after {
+  clear: both;
+}
+
+.form-horizontal .form-control-static {
+  padding-top: 7px;
+}
+
+@media (min-width: 768px) {
+  .form-horizontal .control-label {
+    text-align: right;
+  }
+}
+
+.btn {
+  display: inline-block;
+  padding: 6px 12px;
+  margin-bottom: 0;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1.428571429;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: middle;
+  cursor: pointer;
+  background-image: none;
+  border: 1px solid transparent;
+  border-radius: 4px;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+       -o-user-select: none;
+          user-select: none;
+}
+
+.btn:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+.btn:hover,
+.btn:focus {
+  color: #333333;
+  text-decoration: none;
+}
+
+.btn:active,
+.btn.active {
+  background-image: none;
+  outline: 0;
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+  pointer-events: none;
+  cursor: not-allowed;
+  opacity: 0.65;
+  filter: alpha(opacity=65);
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn-default {
+  color: #333333;
+  background-color: #ffffff;
+  border-color: #cccccc;
+}
+
+.btn-default:hover,
+.btn-default:focus,
+.btn-default:active,
+.btn-default.active,
+.open .dropdown-toggle.btn-default {
+  color: #333333;
+  background-color: #ebebeb;
+  border-color: #adadad;
+}
+
+.btn-default:active,
+.btn-default.active,
+.open .dropdown-toggle.btn-default {
+  background-image: none;
+}
+
+.btn-default.disabled,
+.btn-default[disabled],
+fieldset[disabled] .btn-default,
+.btn-default.disabled:hover,
+.btn-default[disabled]:hover,
+fieldset[disabled] .btn-default:hover,
+.btn-default.disabled:focus,
+.btn-default[disabled]:focus,
+fieldset[disabled] .btn-default:focus,
+.btn-default.disabled:active,
+.btn-default[disabled]:active,
+fieldset[disabled] .btn-default:active,
+.btn-default.disabled.active,
+.btn-default[disabled].active,
+fieldset[disabled] .btn-default.active {
+  background-color: #ffffff;
+  border-color: #cccccc;
+}
+
+.btn-primary {
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #357ebd;
+}
+
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary:active,
+.btn-primary.active,
+.open .dropdown-toggle.btn-primary {
+  color: #ffffff;
+  background-color: #3276b1;
+  border-color: #285e8e;
+}
+
+.btn-primary:active,
+.btn-primary.active,
+.open .dropdown-toggle.btn-primary {
+  background-image: none;
+}
+
+.btn-primary.disabled,
+.btn-primary[disabled],
+fieldset[disabled] .btn-primary,
+.btn-primary.disabled:hover,
+.btn-primary[disabled]:hover,
+fieldset[disabled] .btn-primary:hover,
+.btn-primary.disabled:focus,
+.btn-primary[disabled]:focus,
+fieldset[disabled] .btn-primary:focus,
+.btn-primary.disabled:active,
+.btn-primary[disabled]:active,
+fieldset[disabled] .btn-primary:active,
+.btn-primary.disabled.active,
+.btn-primary[disabled].active,
+fieldset[disabled] .btn-primary.active {
+  background-color: #428bca;
+  border-color: #357ebd;
+}
+
+.btn-warning {
+  color: #ffffff;
+  background-color: #f0ad4e;
+  border-color: #eea236;
+}
+
+.btn-warning:hover,
+.btn-warning:focus,
+.btn-warning:active,
+.btn-warning.active,
+.open .dropdown-toggle.btn-warning {
+  color: #ffffff;
+  background-color: #ed9c28;
+  border-color: #d58512;
+}
+
+.btn-warning:active,
+.btn-warning.active,
+.open .dropdown-toggle.btn-warning {
+  background-image: none;
+}
+
+.btn-warning.disabled,
+.btn-warning[disabled],
+fieldset[disabled] .btn-warning,
+.btn-warning.disabled:hover,
+.btn-warning[disabled]:hover,
+fieldset[disabled] .btn-warning:hover,
+.btn-warning.disabled:focus,
+.btn-warning[disabled]:focus,
+fieldset[disabled] .btn-warning:focus,
+.btn-warning.disabled:active,
+.btn-warning[disabled]:active,
+fieldset[disabled] .btn-warning:active,
+.btn-warning.disabled.active,
+.btn-warning[disabled].active,
+fieldset[disabled] .btn-warning.active {
+  background-color: #f0ad4e;
+  border-color: #eea236;
+}
+
+.btn-danger {
+  color: #ffffff;
+  background-color: #d9534f;
+  border-color: #d43f3a;
+}
+
+.btn-danger:hover,
+.btn-danger:focus,
+.btn-danger:active,
+.btn-danger.active,
+.open .dropdown-toggle.btn-danger {
+  color: #ffffff;
+  background-color: #d2322d;
+  border-color: #ac2925;
+}
+
+.btn-danger:active,
+.btn-danger.active,
+.open .dropdown-toggle.btn-danger {
+  background-image: none;
+}
+
+.btn-danger.disabled,
+.btn-danger[disabled],
+fieldset[disabled] .btn-danger,
+.btn-danger.disabled:hover,
+.btn-danger[disabled]:hover,
+fieldset[disabled] .btn-danger:hover,
+.btn-danger.disabled:focus,
+.btn-danger[disabled]:focus,
+fieldset[disabled] .btn-danger:focus,
+.btn-danger.disabled:active,
+.btn-danger[disabled]:active,
+fieldset[disabled] .btn-danger:active,
+.btn-danger.disabled.active,
+.btn-danger[disabled].active,
+fieldset[disabled] .btn-danger.active {
+  background-color: #d9534f;
+  border-color: #d43f3a;
+}
+
+.btn-success {
+  color: #ffffff;
+  background-color: #5cb85c;
+  border-color: #4cae4c;
+}
+
+.btn-success:hover,
+.btn-success:focus,
+.btn-success:active,
+.btn-success.active,
+.open .dropdown-toggle.btn-success {
+  color: #ffffff;
+  background-color: #47a447;
+  border-color: #398439;
+}
+
+.btn-success:active,
+.btn-success.active,
+.open .dropdown-toggle.btn-success {
+  background-image: none;
+}
+
+.btn-success.disabled,
+.btn-success[disabled],
+fieldset[disabled] .btn-success,
+.btn-success.disabled:hover,
+.btn-success[disabled]:hover,
+fieldset[disabled] .btn-success:hover,
+.btn-success.disabled:focus,
+.btn-success[disabled]:focus,
+fieldset[disabled] .btn-success:focus,
+.btn-success.disabled:active,
+.btn-success[disabled]:active,
+fieldset[disabled] .btn-success:active,
+.btn-success.disabled.active,
+.btn-success[disabled].active,
+fieldset[disabled] .btn-success.active {
+  background-color: #5cb85c;
+  border-color: #4cae4c;
+}
+
+.btn-info {
+  color: #ffffff;
+  background-color: #5bc0de;
+  border-color: #46b8da;
+}
+
+.btn-info:hover,
+.btn-info:focus,
+.btn-info:active,
+.btn-info.active,
+.open .dropdown-toggle.btn-info {
+  color: #ffffff;
+  background-color: #39b3d7;
+  border-color: #269abc;
+}
+
+.btn-info:active,
+.btn-info.active,
+.open .dropdown-toggle.btn-info {
+  background-image: none;
+}
+
+.btn-info.disabled,
+.btn-info[disabled],
+fieldset[disabled] .btn-info,
+.btn-info.disabled:hover,
+.btn-info[disabled]:hover,
+fieldset[disabled] .btn-info:hover,
+.btn-info.disabled:focus,
+.btn-info[disabled]:focus,
+fieldset[disabled] .btn-info:focus,
+.btn-info.disabled:active,
+.btn-info[disabled]:active,
+fieldset[disabled] .btn-info:active,
+.btn-info.disabled.active,
+.btn-info[disabled].active,
+fieldset[disabled] .btn-info.active {
+  background-color: #5bc0de;
+  border-color: #46b8da;
+}
+
+.btn-link {
+  font-weight: normal;
+  color: #428bca;
+  cursor: pointer;
+  border-radius: 0;
+}
+
+.btn-link,
+.btn-link:active,
+.btn-link[disabled],
+fieldset[disabled] .btn-link {
+  background-color: transparent;
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn-link,
+.btn-link:hover,
+.btn-link:focus,
+.btn-link:active {
+  border-color: transparent;
+}
+
+.btn-link:hover,
+.btn-link:focus {
+  color: #2a6496;
+  text-decoration: underline;
+  background-color: transparent;
+}
+
+.btn-link[disabled]:hover,
+fieldset[disabled] .btn-link:hover,
+.btn-link[disabled]:focus,
+fieldset[disabled] .btn-link:focus {
+  color: #999999;
+  text-decoration: none;
+}
+
+.btn-lg {
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+.btn-sm,
+.btn-xs {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+.btn-xs {
+  padding: 1px 5px;
+}
+
+.btn-block {
+  display: block;
+  width: 100%;
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.btn-block + .btn-block {
+  margin-top: 5px;
+}
+
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+  width: 100%;
+}
+
+.fade {
+  opacity: 0;
+  -webkit-transition: opacity 0.15s linear;
+          transition: opacity 0.15s linear;
+}
+
+.fade.in {
+  opacity: 1;
+}
+
+.collapse {
+  display: none;
+}
+
+.collapse.in {
+  display: block;
+}
+
+.collapsing {
+  position: relative;
+  height: 0;
+  overflow: hidden;
+  -webkit-transition: height 0.35s ease;
+          transition: height 0.35s ease;
+}
+
+@font-face {
+  font-family: 'Glyphicons Halflings';
+  src: url('../fonts/glyphicons-halflings-regular.eot');
+  src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
+}
+
+.glyphicon {
+  position: relative;
+  top: 1px;
+  display: inline-block;
+  font-family: 'Glyphicons Halflings';
+  -webkit-font-smoothing: antialiased;
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.glyphicon:empty {
+  width: 1em;
+}
+
+.glyphicon-asterisk:before {
+  content: "\2a";
+}
+
+.glyphicon-plus:before {
+  content: "\2b";
+}
+
+.glyphicon-euro:before {
+  content: "\20ac";
+}
+
+.glyphicon-minus:before {
+  content: "\2212";
+}
+
+.glyphicon-cloud:before {
+  content: "\2601";
+}
+
+.glyphicon-envelope:before {
+  content: "\2709";
+}
+
+.glyphicon-pencil:before {
+  content: "\270f";
+}
+
+.glyphicon-glass:before {
+  content: "\e001";
+}
+
+.glyphicon-music:before {
+  content: "\e002";
+}
+
+.glyphicon-search:before {
+  content: "\e003";
+}
+
+.glyphicon-heart:before {
+  content: "\e005";
+}
+
+.glyphicon-star:before {
+  content: "\e006";
+}
+
+.glyphicon-star-empty:before {
+  content: "\e007";
+}
+
+.glyphicon-user:before {
+  content: "\e008";
+}
+
+.glyphicon-film:before {
+  content: "\e009";
+}
+
+.glyphicon-th-large:before {
+  content: "\e010";
+}
+
+.glyphicon-th:before {
+  content: "\e011";
+}
+
+.glyphicon-th-list:before {
+  content: "\e012";
+}
+
+.glyphicon-ok:before {
+  content: "\e013";
+}
+
+.glyphicon-remove:before {
+  content: "\e014";
+}
+
+.glyphicon-zoom-in:before {
+  content: "\e015";
+}
+
+.glyphicon-zoom-out:before {
+  content: "\e016";
+}
+
+.glyphicon-off:before {
+  content: "\e017";
+}
+
+.glyphicon-signal:before {
+  content: "\e018";
+}
+
+.glyphicon-cog:before {
+  content: "\e019";
+}
+
+.glyphicon-trash:before {
+  content: "\e020";
+}
+
+.glyphicon-home:before {
+  content: "\e021";
+}
+
+.glyphicon-file:before {
+  content: "\e022";
+}
+
+.glyphicon-time:before {
+  content: "\e023";
+}
+
+.glyphicon-road:before {
+  content: "\e024";
+}
+
+.glyphicon-download-alt:before {
+  content: "\e025";
+}
+
+.glyphicon-download:before {
+  content: "\e026";
+}
+
+.glyphicon-upload:before {
+  content: "\e027";
+}
+
+.glyphicon-inbox:before {
+  content: "\e028";
+}
+
+.glyphicon-play-circle:before {
+  content: "\e029";
+}
+
+.glyphicon-repeat:before {
+  content: "\e030";
+}
+
+.glyphicon-refresh:before {
+  content: "\e031";
+}
+
+.glyphicon-list-alt:before {
+  content: "\e032";
+}
+
+.glyphicon-lock:before {
+  content: "\e033";
+}
+
+.glyphicon-flag:before {
+  content: "\e034";
+}
+
+.glyphicon-headphones:before {
+  content: "\e035";
+}
+
+.glyphicon-volume-off:before {
+  content: "\e036";
+}
+
+.glyphicon-volume-down:before {
+  content: "\e037";
+}
+
+.glyphicon-volume-up:before {
+  content: "\e038";
+}
+
+.glyphicon-qrcode:before {
+  content: "\e039";
+}
+
+.glyphicon-barcode:before {
+  content: "\e040";
+}
+
+.glyphicon-tag:before {
+  content: "\e041";
+}
+
+.glyphicon-tags:before {
+  content: "\e042";
+}
+
+.glyphicon-book:before {
+  content: "\e043";
+}
+
+.glyphicon-bookmark:before {
+  content: "\e044";
+}
+
+.glyphicon-print:before {
+  content: "\e045";
+}
+
+.glyphicon-camera:before {
+  content: "\e046";
+}
+
+.glyphicon-font:before {
+  content: "\e047";
+}
+
+.glyphicon-bold:before {
+  content: "\e048";
+}
+
+.glyphicon-italic:before {
+  content: "\e049";
+}
+
+.glyphicon-text-height:before {
+  content: "\e050";
+}
+
+.glyphicon-text-width:before {
+  content: "\e051";
+}
+
+.glyphicon-align-left:before {
+  content: "\e052";
+}
+
+.glyphicon-align-center:before {
+  content: "\e053";
+}
+
+.glyphicon-align-right:before {
+  content: "\e054";
+}
+
+.glyphicon-align-justify:before {
+  content: "\e055";
+}
+
+.glyphicon-list:before {
+  content: "\e056";
+}
+
+.glyphicon-indent-left:before {
+  content: "\e057";
+}
+
+.glyphicon-indent-right:before {
+  content: "\e058";
+}
+
+.glyphicon-facetime-video:before {
+  content: "\e059";
+}
+
+.glyphicon-picture:before {
+  content: "\e060";
+}
+
+.glyphicon-map-marker:before {
+  content: "\e062";
+}
+
+.glyphicon-adjust:before {
+  content: "\e063";
+}
+
+.glyphicon-tint:before {
+  content: "\e064";
+}
+
+.glyphicon-edit:before {
+  content: "\e065";
+}
+
+.glyphicon-share:before {
+  content: "\e066";
+}
+
+.glyphicon-check:before {
+  content: "\e067";
+}
+
+.glyphicon-move:before {
+  content: "\e068";
+}
+
+.glyphicon-step-backward:before {
+  content: "\e069";
+}
+
+.glyphicon-fast-backward:before {
+  content: "\e070";
+}
+
+.glyphicon-backward:before {
+  content: "\e071";
+}
+
+.glyphicon-play:before {
+  content: "\e072";
+}
+
+.glyphicon-pause:before {
+  content: "\e073";
+}
+
+.glyphicon-stop:before {
+  content: "\e074";
+}
+
+.glyphicon-forward:before {
+  content: "\e075";
+}
+
+.glyphicon-fast-forward:before {
+  content: "\e076";
+}
+
+.glyphicon-step-forward:before {
+  content: "\e077";
+}
+
+.glyphicon-eject:before {
+  content: "\e078";
+}
+
+.glyphicon-chevron-left:before {
+  content: "\e079";
+}
+
+.glyphicon-chevron-right:before {
+  content: "\e080";
+}
+
+.glyphicon-plus-sign:before {
+  content: "\e081";
+}
+
+.glyphicon-minus-sign:before {
+  content: "\e082";
+}
+
+.glyphicon-remove-sign:before {
+  content: "\e083";
+}
+
+.glyphicon-ok-sign:before {
+  content: "\e084";
+}
+
+.glyphicon-question-sign:before {
+  content: "\e085";
+}
+
+.glyphicon-info-sign:before {
+  content: "\e086";
+}
+
+.glyphicon-screenshot:before {
+  content: "\e087";
+}
+
+.glyphicon-remove-circle:before {
+  content: "\e088";
+}
+
+.glyphicon-ok-circle:before {
+  content: "\e089";
+}
+
+.glyphicon-ban-circle:before {
+  content: "\e090";
+}
+
+.glyphicon-arrow-left:before {
+  content: "\e091";
+}
+
+.glyphicon-arrow-right:before {
+  content: "\e092";
+}
+
+.glyphicon-arrow-up:before {
+  content: "\e093";
+}
+
+.glyphicon-arrow-down:before {
+  content: "\e094";
+}
+
+.glyphicon-share-alt:before {
+  content: "\e095";
+}
+
+.glyphicon-resize-full:before {
+  content: "\e096";
+}
+
+.glyphicon-resize-small:before {
+  content: "\e097";
+}
+
+.glyphicon-exclamation-sign:before {
+  content: "\e101";
+}
+
+.glyphicon-gift:before {
+  content: "\e102";
+}
+
+.glyphicon-leaf:before {
+  content: "\e103";
+}
+
+.glyphicon-fire:before {
+  content: "\e104";
+}
+
+.glyphicon-eye-open:before {
+  content: "\e105";
+}
+
+.glyphicon-eye-close:before {
+  content: "\e106";
+}
+
+.glyphicon-warning-sign:before {
+  content: "\e107";
+}
+
+.glyphicon-plane:before {
+  content: "\e108";
+}
+
+.glyphicon-calendar:before {
+  content: "\e109";
+}
+
+.glyphicon-random:before {
+  content: "\e110";
+}
+
+.glyphicon-comment:before {
+  content: "\e111";
+}
+
+.glyphicon-magnet:before {
+  content: "\e112";
+}
+
+.glyphicon-chevron-up:before {
+  content: "\e113";
+}
+
+.glyphicon-chevron-down:before {
+  content: "\e114";
+}
+
+.glyphicon-retweet:before {
+  content: "\e115";
+}
+
+.glyphicon-shopping-cart:before {
+  content: "\e116";
+}
+
+.glyphicon-folder-close:before {
+  content: "\e117";
+}
+
+.glyphicon-folder-open:before {
+  content: "\e118";
+}
+
+.glyphicon-resize-vertical:before {
+  content: "\e119";
+}
+
+.glyphicon-resize-horizontal:before {
+  content: "\e120";
+}
+
+.glyphicon-hdd:before {
+  content: "\e121";
+}
+
+.glyphicon-bullhorn:before {
+  content: "\e122";
+}
+
+.glyphicon-bell:before {
+  content: "\e123";
+}
+
+.glyphicon-certificate:before {
+  content: "\e124";
+}
+
+.glyphicon-thumbs-up:before {
+  content: "\e125";
+}
+
+.glyphicon-thumbs-down:before {
+  content: "\e126";
+}
+
+.glyphicon-hand-right:before {
+  content: "\e127";
+}
+
+.glyphicon-hand-left:before {
+  content: "\e128";
+}
+
+.glyphicon-hand-up:before {
+  content: "\e129";
+}
+
+.glyphicon-hand-down:before {
+  content: "\e130";
+}
+
+.glyphicon-circle-arrow-right:before {
+  content: "\e131";
+}
+
+.glyphicon-circle-arrow-left:before {
+  content: "\e132";
+}
+
+.glyphicon-circle-arrow-up:before {
+  content: "\e133";
+}
+
+.glyphicon-circle-arrow-down:before {
+  content: "\e134";
+}
+
+.glyphicon-globe:before {
+  content: "\e135";
+}
+
+.glyphicon-wrench:before {
+  content: "\e136";
+}
+
+.glyphicon-tasks:before {
+  content: "\e137";
+}
+
+.glyphicon-filter:before {
+  content: "\e138";
+}
+
+.glyphicon-briefcase:before {
+  content: "\e139";
+}
+
+.glyphicon-fullscreen:before {
+  content: "\e140";
+}
+
+.glyphicon-dashboard:before {
+  content: "\e141";
+}
+
+.glyphicon-paperclip:before {
+  content: "\e142";
+}
+
+.glyphicon-heart-empty:before {
+  content: "\e143";
+}
+
+.glyphicon-link:before {
+  content: "\e144";
+}
+
+.glyphicon-phone:before {
+  content: "\e145";
+}
+
+.glyphicon-pushpin:before {
+  content: "\e146";
+}
+
+.glyphicon-usd:before {
+  content: "\e148";
+}
+
+.glyphicon-gbp:before {
+  content: "\e149";
+}
+
+.glyphicon-sort:before {
+  content: "\e150";
+}
+
+.glyphicon-sort-by-alphabet:before {
+  content: "\e151";
+}
+
+.glyphicon-sort-by-alphabet-alt:before {
+  content: "\e152";
+}
+
+.glyphicon-sort-by-order:before {
+  content: "\e153";
+}
+
+.glyphicon-sort-by-order-alt:before {
+  content: "\e154";
+}
+
+.glyphicon-sort-by-attributes:before {
+  content: "\e155";
+}
+
+.glyphicon-sort-by-attributes-alt:before {
+  content: "\e156";
+}
+
+.glyphicon-unchecked:before {
+  content: "\e157";
+}
+
+.glyphicon-expand:before {
+  content: "\e158";
+}
+
+.glyphicon-collapse-down:before {
+  content: "\e159";
+}
+
+.glyphicon-collapse-up:before {
+  content: "\e160";
+}
+
+.glyphicon-log-in:before {
+  content: "\e161";
+}
+
+.glyphicon-flash:before {
+  content: "\e162";
+}
+
+.glyphicon-log-out:before {
+  content: "\e163";
+}
+
+.glyphicon-new-window:before {
+  content: "\e164";
+}
+
+.glyphicon-record:before {
+  content: "\e165";
+}
+
+.glyphicon-save:before {
+  content: "\e166";
+}
+
+.glyphicon-open:before {
+  content: "\e167";
+}
+
+.glyphicon-saved:before {
+  content: "\e168";
+}
+
+.glyphicon-import:before {
+  content: "\e169";
+}
+
+.glyphicon-export:before {
+  content: "\e170";
+}
+
+.glyphicon-send:before {
+  content: "\e171";
+}
+
+.glyphicon-floppy-disk:before {
+  content: "\e172";
+}
+
+.glyphicon-floppy-saved:before {
+  content: "\e173";
+}
+
+.glyphicon-floppy-remove:before {
+  content: "\e174";
+}
+
+.glyphicon-floppy-save:before {
+  content: "\e175";
+}
+
+.glyphicon-floppy-open:before {
+  content: "\e176";
+}
+
+.glyphicon-credit-card:before {
+  content: "\e177";
+}
+
+.glyphicon-transfer:before {
+  content: "\e178";
+}
+
+.glyphicon-cutlery:before {
+  content: "\e179";
+}
+
+.glyphicon-header:before {
+  content: "\e180";
+}
+
+.glyphicon-compressed:before {
+  content: "\e181";
+}
+
+.glyphicon-earphone:before {
+  content: "\e182";
+}
+
+.glyphicon-phone-alt:before {
+  content: "\e183";
+}
+
+.glyphicon-tower:before {
+  content: "\e184";
+}
+
+.glyphicon-stats:before {
+  content: "\e185";
+}
+
+.glyphicon-sd-video:before {
+  content: "\e186";
+}
+
+.glyphicon-hd-video:before {
+  content: "\e187";
+}
+
+.glyphicon-subtitles:before {
+  content: "\e188";
+}
+
+.glyphicon-sound-stereo:before {
+  content: "\e189";
+}
+
+.glyphicon-sound-dolby:before {
+  content: "\e190";
+}
+
+.glyphicon-sound-5-1:before {
+  content: "\e191";
+}
+
+.glyphicon-sound-6-1:before {
+  content: "\e192";
+}
+
+.glyphicon-sound-7-1:before {
+  content: "\e193";
+}
+
+.glyphicon-copyright-mark:before {
+  content: "\e194";
+}
+
+.glyphicon-registration-mark:before {
+  content: "\e195";
+}
+
+.glyphicon-cloud-download:before {
+  content: "\e197";
+}
+
+.glyphicon-cloud-upload:before {
+  content: "\e198";
+}
+
+.glyphicon-tree-conifer:before {
+  content: "\e199";
+}
+
+.glyphicon-tree-deciduous:before {
+  content: "\e200";
+}
+
+.caret {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  margin-left: 2px;
+  vertical-align: middle;
+  border-top: 4px solid #000000;
+  border-right: 4px solid transparent;
+  border-bottom: 0 dotted;
+  border-left: 4px solid transparent;
+}
+
+.dropdown {
+  position: relative;
+}
+
+.dropdown-toggle:focus {
+  outline: 0;
+}
+
+.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  display: none;
+  float: left;
+  min-width: 160px;
+  padding: 5px 0;
+  margin: 2px 0 0;
+  font-size: 14px;
+  list-style: none;
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  border: 1px solid rgba(0, 0, 0, 0.15);
+  border-radius: 4px;
+  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+          box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+  background-clip: padding-box;
+}
+
+.dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+
+.dropdown-menu .divider {
+  height: 1px;
+  margin: 9px 0;
+  overflow: hidden;
+  background-color: #e5e5e5;
+}
+
+.dropdown-menu > li > a {
+  display: block;
+  padding: 3px 20px;
+  clear: both;
+  font-weight: normal;
+  line-height: 1.428571429;
+  color: #333333;
+  white-space: nowrap;
+}
+
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+  color: #262626;
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+  color: #ffffff;
+  text-decoration: none;
+  background-color: #428bca;
+  outline: 0;
+}
+
+.dropdown-menu > .disabled > a,
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  color: #999999;
+}
+
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  text-decoration: none;
+  cursor: not-allowed;
+  background-color: transparent;
+  background-image: none;
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.open > .dropdown-menu {
+  display: block;
+}
+
+.open > a {
+  outline: 0;
+}
+
+.dropdown-header {
+  display: block;
+  padding: 3px 20px;
+  font-size: 12px;
+  line-height: 1.428571429;
+  color: #999999;
+}
+
+.dropdown-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 990;
+}
+
+.pull-right > .dropdown-menu {
+  right: 0;
+  left: auto;
+}
+
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+  border-top: 0 dotted;
+  border-bottom: 4px solid #000000;
+  content: "";
+}
+
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+  top: auto;
+  bottom: 100%;
+  margin-bottom: 1px;
+}
+
+@media (min-width: 768px) {
+  .navbar-right .dropdown-menu {
+    right: 0;
+    left: auto;
+  }
+}
+
+.btn-default .caret {
+  border-top-color: #333333;
+}
+
+.btn-primary .caret,
+.btn-success .caret,
+.btn-warning .caret,
+.btn-danger .caret,
+.btn-info .caret {
+  border-top-color: #fff;
+}
+
+.dropup .btn-default .caret {
+  border-bottom-color: #333333;
+}
+
+.dropup .btn-primary .caret,
+.dropup .btn-success .caret,
+.dropup .btn-warning .caret,
+.dropup .btn-danger .caret,
+.dropup .btn-info .caret {
+  border-bottom-color: #fff;
+}
+
+.btn-group,
+.btn-group-vertical {
+  position: relative;
+  display: inline-block;
+  vertical-align: middle;
+}
+
+.btn-group > .btn,
+.btn-group-vertical > .btn {
+  position: relative;
+  float: left;
+}
+
+.btn-group > .btn:hover,
+.btn-group-vertical > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group-vertical > .btn:focus,
+.btn-group > .btn:active,
+.btn-group-vertical > .btn:active,
+.btn-group > .btn.active,
+.btn-group-vertical > .btn.active {
+  z-index: 2;
+}
+
+.btn-group > .btn:focus,
+.btn-group-vertical > .btn:focus {
+  outline: none;
+}
+
+.btn-group .btn + .btn,
+.btn-group .btn + .btn-group,
+.btn-group .btn-group + .btn,
+.btn-group .btn-group + .btn-group {
+  margin-left: -1px;
+}
+
+.btn-toolbar:before,
+.btn-toolbar:after {
+  display: table;
+  content: " ";
+}
+
+.btn-toolbar:after {
+  clear: both;
+}
+
+.btn-toolbar:before,
+.btn-toolbar:after {
+  display: table;
+  content: " ";
+}
+
+.btn-toolbar:after {
+  clear: both;
+}
+
+.btn-toolbar .btn-group {
+  float: left;
+}
+
+.btn-toolbar > .btn + .btn,
+.btn-toolbar > .btn-group + .btn,
+.btn-toolbar > .btn + .btn-group,
+.btn-toolbar > .btn-group + .btn-group {
+  margin-left: 5px;
+}
+
+.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+  border-radius: 0;
+}
+
+.btn-group > .btn:first-child {
+  margin-left: 0;
+}
+
+.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.btn-group > .btn:last-child:not(:first-child),
+.btn-group > .dropdown-toggle:not(:first-child) {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.btn-group > .btn-group {
+  float: left;
+}
+
+.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
+  border-radius: 0;
+}
+
+.btn-group > .btn-group:first-child > .btn:last-child,
+.btn-group > .btn-group:first-child > .dropdown-toggle {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.btn-group > .btn-group:last-child > .btn:first-child {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+  outline: 0;
+}
+
+.btn-group-xs > .btn {
+  padding: 5px 10px;
+  padding: 1px 5px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+.btn-group-sm > .btn {
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+.btn-group-lg > .btn {
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+.btn-group > .btn + .dropdown-toggle {
+  padding-right: 8px;
+  padding-left: 8px;
+}
+
+.btn-group > .btn-lg + .dropdown-toggle {
+  padding-right: 12px;
+  padding-left: 12px;
+}
+
+.btn-group.open .dropdown-toggle {
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.btn-group.open .dropdown-toggle.btn-link {
+  -webkit-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn .caret {
+  margin-left: 0;
+}
+
+.btn-lg .caret {
+  border-width: 5px 5px 0;
+  border-bottom-width: 0;
+}
+
+.dropup .btn-lg .caret {
+  border-width: 0 5px 5px;
+}
+
+.btn-group-vertical > .btn,
+.btn-group-vertical > .btn-group {
+  display: block;
+  float: none;
+  width: 100%;
+  max-width: 100%;
+}
+
+.btn-group-vertical > .btn-group:before,
+.btn-group-vertical > .btn-group:after {
+  display: table;
+  content: " ";
+}
+
+.btn-group-vertical > .btn-group:after {
+  clear: both;
+}
+
+.btn-group-vertical > .btn-group:before,
+.btn-group-vertical > .btn-group:after {
+  display: table;
+  content: " ";
+}
+
+.btn-group-vertical > .btn-group:after {
+  clear: both;
+}
+
+.btn-group-vertical > .btn-group > .btn {
+  float: none;
+}
+
+.btn-group-vertical > .btn + .btn,
+.btn-group-vertical > .btn + .btn-group,
+.btn-group-vertical > .btn-group + .btn,
+.btn-group-vertical > .btn-group + .btn-group {
+  margin-top: -1px;
+  margin-left: 0;
+}
+
+.btn-group-vertical > .btn:not(:first-child):not(:last-child) {
+  border-radius: 0;
+}
+
+.btn-group-vertical > .btn:first-child:not(:last-child) {
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.btn-group-vertical > .btn:last-child:not(:first-child) {
+  border-top-right-radius: 0;
+  border-bottom-left-radius: 4px;
+  border-top-left-radius: 0;
+}
+
+.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
+  border-radius: 0;
+}
+
+.btn-group-vertical > .btn-group:first-child > .btn:last-child,
+.btn-group-vertical > .btn-group:first-child > .dropdown-toggle {
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.btn-group-vertical > .btn-group:last-child > .btn:first-child {
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.btn-group-justified {
+  display: table;
+  width: 100%;
+  border-collapse: separate;
+  table-layout: fixed;
+}
+
+.btn-group-justified .btn {
+  display: table-cell;
+  float: none;
+  width: 1%;
+}
+
+[data-toggle="buttons"] > .btn > input[type="radio"],
+[data-toggle="buttons"] > .btn > input[type="checkbox"] {
+  display: none;
+}
+
+.input-group {
+  position: relative;
+  display: table;
+  border-collapse: separate;
+}
+
+.input-group.col {
+  float: none;
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.input-group .form-control {
+  width: 100%;
+  margin-bottom: 0;
+}
+
+.input-group-lg > .form-control,
+.input-group-lg > .input-group-addon,
+.input-group-lg > .input-group-btn > .btn {
+  height: 45px;
+  padding: 10px 16px;
+  font-size: 18px;
+  line-height: 1.33;
+  border-radius: 6px;
+}
+
+select.input-group-lg > .form-control,
+select.input-group-lg > .input-group-addon,
+select.input-group-lg > .input-group-btn > .btn {
+  height: 45px;
+  line-height: 45px;
+}
+
+textarea.input-group-lg > .form-control,
+textarea.input-group-lg > .input-group-addon,
+textarea.input-group-lg > .input-group-btn > .btn {
+  height: auto;
+}
+
+.input-group-sm > .form-control,
+.input-group-sm > .input-group-addon,
+.input-group-sm > .input-group-btn > .btn {
+  height: 30px;
+  padding: 5px 10px;
+  font-size: 12px;
+  line-height: 1.5;
+  border-radius: 3px;
+}
+
+select.input-group-sm > .form-control,
+select.input-group-sm > .input-group-addon,
+select.input-group-sm > .input-group-btn > .btn {
+  height: 30px;
+  line-height: 30px;
+}
+
+textarea.input-group-sm > .form-control,
+textarea.input-group-sm > .input-group-addon,
+textarea.input-group-sm > .input-group-btn > .btn {
+  height: auto;
+}
+
+.input-group-addon,
+.input-group-btn,
+.input-group .form-control {
+  display: table-cell;
+}
+
+.input-group-addon:not(:first-child):not(:last-child),
+.input-group-btn:not(:first-child):not(:last-child),
+.input-group .form-control:not(:first-child):not(:last-child) {
+  border-radius: 0;
+}
+
+.input-group-addon,
+.input-group-btn {
+  width: 1%;
+  white-space: nowrap;
+  vertical-align: middle;
+}
+
+.input-group-addon {
+  padding: 6px 12px;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1;
+  color: #555555;
+  text-align: center;
+  background-color: #eeeeee;
+  border: 1px solid #cccccc;
+  border-radius: 4px;
+}
+
+.input-group-addon.input-sm {
+  padding: 5px 10px;
+  font-size: 12px;
+  border-radius: 3px;
+}
+
+.input-group-addon.input-lg {
+  padding: 10px 16px;
+  font-size: 18px;
+  border-radius: 6px;
+}
+
+.input-group-addon input[type="radio"],
+.input-group-addon input[type="checkbox"] {
+  margin-top: 0;
+}
+
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .dropdown-toggle,
+.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.input-group-addon:first-child {
+  border-right: 0;
+}
+
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .dropdown-toggle,
+.input-group-btn:first-child > .btn:not(:first-child) {
+  border-bottom-left-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.input-group-addon:last-child {
+  border-left: 0;
+}
+
+.input-group-btn {
+  position: relative;
+  white-space: nowrap;
+}
+
+.input-group-btn:first-child > .btn {
+  margin-right: -1px;
+}
+
+.input-group-btn:last-child > .btn {
+  margin-left: -1px;
+}
+
+.input-group-btn > .btn {
+  position: relative;
+}
+
+.input-group-btn > .btn + .btn {
+  margin-left: -4px;
+}
+
+.input-group-btn > .btn:hover,
+.input-group-btn > .btn:active {
+  z-index: 2;
+}
+
+.nav {
+  padding-left: 0;
+  margin-bottom: 0;
+  list-style: none;
+}
+
+.nav:before,
+.nav:after {
+  display: table;
+  content: " ";
+}
+
+.nav:after {
+  clear: both;
+}
+
+.nav:before,
+.nav:after {
+  display: table;
+  content: " ";
+}
+
+.nav:after {
+  clear: both;
+}
+
+.nav > li {
+  position: relative;
+  display: block;
+}
+
+.nav > li > a {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+}
+
+.nav > li > a:hover,
+.nav > li > a:focus {
+  text-decoration: none;
+  background-color: #eeeeee;
+}
+
+.nav > li.disabled > a {
+  color: #999999;
+}
+
+.nav > li.disabled > a:hover,
+.nav > li.disabled > a:focus {
+  color: #999999;
+  text-decoration: none;
+  cursor: not-allowed;
+  background-color: transparent;
+}
+
+.nav .open > a,
+.nav .open > a:hover,
+.nav .open > a:focus {
+  background-color: #eeeeee;
+  border-color: #428bca;
+}
+
+.nav .open > a .caret,
+.nav .open > a:hover .caret,
+.nav .open > a:focus .caret {
+  border-top-color: #2a6496;
+  border-bottom-color: #2a6496;
+}
+
+.nav .nav-divider {
+  height: 1px;
+  margin: 9px 0;
+  overflow: hidden;
+  background-color: #e5e5e5;
+}
+
+.nav > li > a > img {
+  max-width: none;
+}
+
+.nav-tabs {
+  border-bottom: 1px solid #dddddd;
+}
+
+.nav-tabs > li {
+  float: left;
+  margin-bottom: -1px;
+}
+
+.nav-tabs > li > a {
+  margin-right: 2px;
+  line-height: 1.428571429;
+  border: 1px solid transparent;
+  border-radius: 4px 4px 0 0;
+}
+
+.nav-tabs > li > a:hover {
+  border-color: #eeeeee #eeeeee #dddddd;
+}
+
+.nav-tabs > li.active > a,
+.nav-tabs > li.active > a:hover,
+.nav-tabs > li.active > a:focus {
+  color: #555555;
+  cursor: default;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-bottom-color: transparent;
+}
+
+.nav-tabs.nav-justified {
+  width: 100%;
+  border-bottom: 0;
+}
+
+.nav-tabs.nav-justified > li {
+  float: none;
+}
+
+.nav-tabs.nav-justified > li > a {
+  margin-bottom: 5px;
+  text-align: center;
+}
+
+.nav-tabs.nav-justified > .dropdown .dropdown-menu {
+  top: auto;
+  left: auto;
+}
+
+@media (min-width: 768px) {
+  .nav-tabs.nav-justified > li {
+    display: table-cell;
+    width: 1%;
+  }
+  .nav-tabs.nav-justified > li > a {
+    margin-bottom: 0;
+  }
+}
+
+.nav-tabs.nav-justified > li > a {
+  margin-right: 0;
+  border-radius: 4px;
+}
+
+.nav-tabs.nav-justified > .active > a,
+.nav-tabs.nav-justified > .active > a:hover,
+.nav-tabs.nav-justified > .active > a:focus {
+  border: 1px solid #dddddd;
+}
+
+@media (min-width: 768px) {
+  .nav-tabs.nav-justified > li > a {
+    border-bottom: 1px solid #dddddd;
+    border-radius: 4px 4px 0 0;
+  }
+  .nav-tabs.nav-justified > .active > a,
+  .nav-tabs.nav-justified > .active > a:hover,
+  .nav-tabs.nav-justified > .active > a:focus {
+    border-bottom-color: #ffffff;
+  }
+}
+
+.nav-pills > li {
+  float: left;
+}
+
+.nav-pills > li > a {
+  border-radius: 4px;
+}
+
+.nav-pills > li + li {
+  margin-left: 2px;
+}
+
+.nav-pills > li.active > a,
+.nav-pills > li.active > a:hover,
+.nav-pills > li.active > a:focus {
+  color: #ffffff;
+  background-color: #428bca;
+}
+
+.nav-pills > li.active > a .caret,
+.nav-pills > li.active > a:hover .caret,
+.nav-pills > li.active > a:focus .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+.nav-stacked > li {
+  float: none;
+}
+
+.nav-stacked > li + li {
+  margin-top: 2px;
+  margin-left: 0;
+}
+
+.nav-justified {
+  width: 100%;
+}
+
+.nav-justified > li {
+  float: none;
+}
+
+.nav-justified > li > a {
+  margin-bottom: 5px;
+  text-align: center;
+}
+
+.nav-justified > .dropdown .dropdown-menu {
+  top: auto;
+  left: auto;
+}
+
+@media (min-width: 768px) {
+  .nav-justified > li {
+    display: table-cell;
+    width: 1%;
+  }
+  .nav-justified > li > a {
+    margin-bottom: 0;
+  }
+}
+
+.nav-tabs-justified {
+  border-bottom: 0;
+}
+
+.nav-tabs-justified > li > a {
+  margin-right: 0;
+  border-radius: 4px;
+}
+
+.nav-tabs-justified > .active > a,
+.nav-tabs-justified > .active > a:hover,
+.nav-tabs-justified > .active > a:focus {
+  border: 1px solid #dddddd;
+}
+
+@media (min-width: 768px) {
+  .nav-tabs-justified > li > a {
+    border-bottom: 1px solid #dddddd;
+    border-radius: 4px 4px 0 0;
+  }
+  .nav-tabs-justified > .active > a,
+  .nav-tabs-justified > .active > a:hover,
+  .nav-tabs-justified > .active > a:focus {
+    border-bottom-color: #ffffff;
+  }
+}
+
+.tab-content > .tab-pane {
+  display: none;
+}
+
+.tab-content > .active {
+  display: block;
+}
+
+.nav .caret {
+  border-top-color: #428bca;
+  border-bottom-color: #428bca;
+}
+
+.nav a:hover .caret {
+  border-top-color: #2a6496;
+  border-bottom-color: #2a6496;
+}
+
+.nav-tabs .dropdown-menu {
+  margin-top: -1px;
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.navbar {
+  position: relative;
+  min-height: 50px;
+  margin-bottom: 20px;
+  border: 1px solid transparent;
+}
+
+.navbar:before,
+.navbar:after {
+  display: table;
+  content: " ";
+}
+
+.navbar:after {
+  clear: both;
+}
+
+.navbar:before,
+.navbar:after {
+  display: table;
+  content: " ";
+}
+
+.navbar:after {
+  clear: both;
+}
+
+@media (min-width: 768px) {
+  .navbar {
+    border-radius: 4px;
+  }
+}
+
+.navbar-header:before,
+.navbar-header:after {
+  display: table;
+  content: " ";
+}
+
+.navbar-header:after {
+  clear: both;
+}
+
+.navbar-header:before,
+.navbar-header:after {
+  display: table;
+  content: " ";
+}
+
+.navbar-header:after {
+  clear: both;
+}
+
+@media (min-width: 768px) {
+  .navbar-header {
+    float: left;
+  }
+}
+
+.navbar-collapse {
+  max-height: 340px;
+  padding-right: 15px;
+  padding-left: 15px;
+  overflow-x: visible;
+  border-top: 1px solid transparent;
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
+  -webkit-overflow-scrolling: touch;
+}
+
+.navbar-collapse:before,
+.navbar-collapse:after {
+  display: table;
+  content: " ";
+}
+
+.navbar-collapse:after {
+  clear: both;
+}
+
+.navbar-collapse:before,
+.navbar-collapse:after {
+  display: table;
+  content: " ";
+}
+
+.navbar-collapse:after {
+  clear: both;
+}
+
+.navbar-collapse.in {
+  overflow-y: auto;
+}
+
+@media (min-width: 768px) {
+  .navbar-collapse {
+    width: auto;
+    border-top: 0;
+    box-shadow: none;
+  }
+  .navbar-collapse.collapse {
+    display: block !important;
+    height: auto !important;
+    padding-bottom: 0;
+    overflow: visible !important;
+  }
+  .navbar-collapse.in {
+    overflow-y: auto;
+  }
+  .navbar-collapse .navbar-nav.navbar-left:first-child {
+    margin-left: -15px;
+  }
+  .navbar-collapse .navbar-nav.navbar-right:last-child {
+    margin-right: -15px;
+  }
+  .navbar-collapse .navbar-text:last-child {
+    margin-right: 0;
+  }
+}
+
+.container > .navbar-header,
+.container > .navbar-collapse {
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+@media (min-width: 768px) {
+  .container > .navbar-header,
+  .container > .navbar-collapse {
+    margin-right: 0;
+    margin-left: 0;
+  }
+}
+
+.navbar-static-top {
+  z-index: 1000;
+  border-width: 0 0 1px;
+}
+
+@media (min-width: 768px) {
+  .navbar-static-top {
+    border-radius: 0;
+  }
+}
+
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  position: fixed;
+  right: 0;
+  left: 0;
+  z-index: 1030;
+}
+
+@media (min-width: 768px) {
+  .navbar-fixed-top,
+  .navbar-fixed-bottom {
+    border-radius: 0;
+  }
+}
+
+.navbar-fixed-top {
+  top: 0;
+  border-width: 0 0 1px;
+}
+
+.navbar-fixed-bottom {
+  bottom: 0;
+  margin-bottom: 0;
+  border-width: 1px 0 0;
+}
+
+.navbar-brand {
+  float: left;
+  padding: 15px 15px;
+  font-size: 18px;
+  line-height: 20px;
+}
+
+.navbar-brand:hover,
+.navbar-brand:focus {
+  text-decoration: none;
+}
+
+@media (min-width: 768px) {
+  .navbar > .container .navbar-brand {
+    margin-left: -15px;
+  }
+}
+
+.navbar-toggle {
+  position: relative;
+  float: right;
+  padding: 9px 10px;
+  margin-top: 8px;
+  margin-right: 15px;
+  margin-bottom: 8px;
+  background-color: transparent;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+
+.navbar-toggle .icon-bar {
+  display: block;
+  width: 22px;
+  height: 2px;
+  border-radius: 1px;
+}
+
+.navbar-toggle .icon-bar + .icon-bar {
+  margin-top: 4px;
+}
+
+@media (min-width: 768px) {
+  .navbar-toggle {
+    display: none;
+  }
+}
+
+.navbar-nav {
+  margin: 7.5px -15px;
+}
+
+.navbar-nav > li > a {
+  padding-top: 10px;
+  padding-bottom: 10px;
+  line-height: 20px;
+}
+
+@media (max-width: 767px) {
+  .navbar-nav .open .dropdown-menu {
+    position: static;
+    float: none;
+    width: auto;
+    margin-top: 0;
+    background-color: transparent;
+    border: 0;
+    box-shadow: none;
+  }
+  .navbar-nav .open .dropdown-menu > li > a,
+  .navbar-nav .open .dropdown-menu .dropdown-header {
+    padding: 5px 15px 5px 25px;
+  }
+  .navbar-nav .open .dropdown-menu > li > a {
+    line-height: 20px;
+  }
+  .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-nav .open .dropdown-menu > li > a:focus {
+    background-image: none;
+  }
+}
+
+@media (min-width: 768px) {
+  .navbar-nav {
+    float: left;
+    margin: 0;
+  }
+  .navbar-nav > li {
+    float: left;
+  }
+  .navbar-nav > li > a {
+    padding-top: 15px;
+    padding-bottom: 15px;
+  }
+}
+
+@media (min-width: 768px) {
+  .navbar-left {
+    float: left !important;
+  }
+  .navbar-right {
+    float: right !important;
+  }
+}
+
+.navbar-form {
+  padding: 10px 15px;
+  margin-top: 8px;
+  margin-right: -15px;
+  margin-bottom: 8px;
+  margin-left: -15px;
+  border-top: 1px solid transparent;
+  border-bottom: 1px solid transparent;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+}
+
+@media (min-width: 768px) {
+  .navbar-form .form-group {
+    display: inline-block;
+    margin-bottom: 0;
+    vertical-align: middle;
+  }
+  .navbar-form .form-control {
+    display: inline-block;
+  }
+  .navbar-form .radio,
+  .navbar-form .checkbox {
+    display: inline-block;
+    padding-left: 0;
+    margin-top: 0;
+    margin-bottom: 0;
+  }
+  .navbar-form .radio input[type="radio"],
+  .navbar-form .checkbox input[type="checkbox"] {
+    float: none;
+    margin-left: 0;
+  }
+}
+
+@media (max-width: 767px) {
+  .navbar-form .form-group {
+    margin-bottom: 5px;
+  }
+}
+
+@media (min-width: 768px) {
+  .navbar-form {
+    width: auto;
+    padding-top: 0;
+    padding-bottom: 0;
+    margin-right: 0;
+    margin-left: 0;
+    border: 0;
+    -webkit-box-shadow: none;
+            box-shadow: none;
+  }
+}
+
+.navbar-nav > li > .dropdown-menu {
+  margin-top: 0;
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.navbar-nav.pull-right > li > .dropdown-menu,
+.navbar-nav > li > .dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+
+.navbar-btn {
+  margin-top: 8px;
+  margin-bottom: 8px;
+}
+
+.navbar-text {
+  float: left;
+  margin-top: 15px;
+  margin-bottom: 15px;
+}
+
+@media (min-width: 768px) {
+  .navbar-text {
+    margin-right: 15px;
+    margin-left: 15px;
+  }
+}
+
+.navbar-default {
+  background-color: #f8f8f8;
+  border-color: #e7e7e7;
+}
+
+.navbar-default .navbar-brand {
+  color: #777777;
+}
+
+.navbar-default .navbar-brand:hover,
+.navbar-default .navbar-brand:focus {
+  color: #5e5e5e;
+  background-color: transparent;
+}
+
+.navbar-default .navbar-text {
+  color: #777777;
+}
+
+.navbar-default .navbar-nav > li > a {
+  color: #777777;
+}
+
+.navbar-default .navbar-nav > li > a:hover,
+.navbar-default .navbar-nav > li > a:focus {
+  color: #333333;
+  background-color: transparent;
+}
+
+.navbar-default .navbar-nav > .active > a,
+.navbar-default .navbar-nav > .active > a:hover,
+.navbar-default .navbar-nav > .active > a:focus {
+  color: #555555;
+  background-color: #e7e7e7;
+}
+
+.navbar-default .navbar-nav > .disabled > a,
+.navbar-default .navbar-nav > .disabled > a:hover,
+.navbar-default .navbar-nav > .disabled > a:focus {
+  color: #cccccc;
+  background-color: transparent;
+}
+
+.navbar-default .navbar-toggle {
+  border-color: #dddddd;
+}
+
+.navbar-default .navbar-toggle:hover,
+.navbar-default .navbar-toggle:focus {
+  background-color: #dddddd;
+}
+
+.navbar-default .navbar-toggle .icon-bar {
+  background-color: #cccccc;
+}
+
+.navbar-default .navbar-collapse,
+.navbar-default .navbar-form {
+  border-color: #e7e7e7;
+}
+
+.navbar-default .navbar-nav > .dropdown > a:hover .caret,
+.navbar-default .navbar-nav > .dropdown > a:focus .caret {
+  border-top-color: #333333;
+  border-bottom-color: #333333;
+}
+
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .open > a:hover,
+.navbar-default .navbar-nav > .open > a:focus {
+  color: #555555;
+  background-color: #e7e7e7;
+}
+
+.navbar-default .navbar-nav > .open > a .caret,
+.navbar-default .navbar-nav > .open > a:hover .caret,
+.navbar-default .navbar-nav > .open > a:focus .caret {
+  border-top-color: #555555;
+  border-bottom-color: #555555;
+}
+
+.navbar-default .navbar-nav > .dropdown > a .caret {
+  border-top-color: #777777;
+  border-bottom-color: #777777;
+}
+
+@media (max-width: 767px) {
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a {
+    color: #777777;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
+    color: #333333;
+    background-color: transparent;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a,
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
+    color: #555555;
+    background-color: #e7e7e7;
+  }
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+    color: #cccccc;
+    background-color: transparent;
+  }
+}
+
+.navbar-default .navbar-link {
+  color: #777777;
+}
+
+.navbar-default .navbar-link:hover {
+  color: #333333;
+}
+
+.navbar-inverse {
+  background-color: #222222;
+  border-color: #080808;
+}
+
+.navbar-inverse .navbar-brand {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-brand:hover,
+.navbar-inverse .navbar-brand:focus {
+  color: #ffffff;
+  background-color: transparent;
+}
+
+.navbar-inverse .navbar-text {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-nav > li > a {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-nav > li > a:hover,
+.navbar-inverse .navbar-nav > li > a:focus {
+  color: #ffffff;
+  background-color: transparent;
+}
+
+.navbar-inverse .navbar-nav > .active > a,
+.navbar-inverse .navbar-nav > .active > a:hover,
+.navbar-inverse .navbar-nav > .active > a:focus {
+  color: #ffffff;
+  background-color: #080808;
+}
+
+.navbar-inverse .navbar-nav > .disabled > a,
+.navbar-inverse .navbar-nav > .disabled > a:hover,
+.navbar-inverse .navbar-nav > .disabled > a:focus {
+  color: #444444;
+  background-color: transparent;
+}
+
+.navbar-inverse .navbar-toggle {
+  border-color: #333333;
+}
+
+.navbar-inverse .navbar-toggle:hover,
+.navbar-inverse .navbar-toggle:focus {
+  background-color: #333333;
+}
+
+.navbar-inverse .navbar-toggle .icon-bar {
+  background-color: #ffffff;
+}
+
+.navbar-inverse .navbar-collapse,
+.navbar-inverse .navbar-form {
+  border-color: #101010;
+}
+
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .open > a:hover,
+.navbar-inverse .navbar-nav > .open > a:focus {
+  color: #ffffff;
+  background-color: #080808;
+}
+
+.navbar-inverse .navbar-nav > .dropdown > a:hover .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .navbar-nav > .dropdown > a .caret {
+  border-top-color: #999999;
+  border-bottom-color: #999999;
+}
+
+.navbar-inverse .navbar-nav > .open > a .caret,
+.navbar-inverse .navbar-nav > .open > a:hover .caret,
+.navbar-inverse .navbar-nav > .open > a:focus .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+@media (max-width: 767px) {
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
+    border-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
+    color: #999999;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {
+    color: #ffffff;
+    background-color: transparent;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {
+    color: #ffffff;
+    background-color: #080808;
+  }
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+    color: #444444;
+    background-color: transparent;
+  }
+}
+
+.navbar-inverse .navbar-link {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-link:hover {
+  color: #ffffff;
+}
+
+.breadcrumb {
+  padding: 8px 15px;
+  margin-bottom: 20px;
+  list-style: none;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+}
+
+.breadcrumb > li {
+  display: inline-block;
+}
+
+.breadcrumb > li + li:before {
+  padding: 0 5px;
+  color: #cccccc;
+  content: "/\00a0";
+}
+
+.breadcrumb > .active {
+  color: #999999;
+}
+
+.pagination {
+  display: inline-block;
+  padding-left: 0;
+  margin: 20px 0;
+  border-radius: 4px;
+}
+
+.pagination > li {
+  display: inline;
+}
+
+.pagination > li > a,
+.pagination > li > span {
+  position: relative;
+  float: left;
+  padding: 6px 12px;
+  margin-left: -1px;
+  line-height: 1.428571429;
+  text-decoration: none;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+}
+
+.pagination > li:first-child > a,
+.pagination > li:first-child > span {
+  margin-left: 0;
+  border-bottom-left-radius: 4px;
+  border-top-left-radius: 4px;
+}
+
+.pagination > li:last-child > a,
+.pagination > li:last-child > span {
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 4px;
+}
+
+.pagination > li > a:hover,
+.pagination > li > span:hover,
+.pagination > li > a:focus,
+.pagination > li > span:focus {
+  background-color: #eeeeee;
+}
+
+.pagination > .active > a,
+.pagination > .active > span,
+.pagination > .active > a:hover,
+.pagination > .active > span:hover,
+.pagination > .active > a:focus,
+.pagination > .active > span:focus {
+  z-index: 2;
+  color: #ffffff;
+  cursor: default;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+.pagination > .disabled > span,
+.pagination > .disabled > span:hover,
+.pagination > .disabled > span:focus,
+.pagination > .disabled > a,
+.pagination > .disabled > a:hover,
+.pagination > .disabled > a:focus {
+  color: #999999;
+  cursor: not-allowed;
+  background-color: #ffffff;
+  border-color: #dddddd;
+}
+
+.pagination-lg > li > a,
+.pagination-lg > li > span {
+  padding: 10px 16px;
+  font-size: 18px;
+}
+
+.pagination-lg > li:first-child > a,
+.pagination-lg > li:first-child > span {
+  border-bottom-left-radius: 6px;
+  border-top-left-radius: 6px;
+}
+
+.pagination-lg > li:last-child > a,
+.pagination-lg > li:last-child > span {
+  border-top-right-radius: 6px;
+  border-bottom-right-radius: 6px;
+}
+
+.pagination-sm > li > a,
+.pagination-sm > li > span {
+  padding: 5px 10px;
+  font-size: 12px;
+}
+
+.pagination-sm > li:first-child > a,
+.pagination-sm > li:first-child > span {
+  border-bottom-left-radius: 3px;
+  border-top-left-radius: 3px;
+}
+
+.pagination-sm > li:last-child > a,
+.pagination-sm > li:last-child > span {
+  border-top-right-radius: 3px;
+  border-bottom-right-radius: 3px;
+}
+
+.pager {
+  padding-left: 0;
+  margin: 20px 0;
+  text-align: center;
+  list-style: none;
+}
+
+.pager:before,
+.pager:after {
+  display: table;
+  content: " ";
+}
+
+.pager:after {
+  clear: both;
+}
+
+.pager:before,
+.pager:after {
+  display: table;
+  content: " ";
+}
+
+.pager:after {
+  clear: both;
+}
+
+.pager li {
+  display: inline;
+}
+
+.pager li > a,
+.pager li > span {
+  display: inline-block;
+  padding: 5px 14px;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 15px;
+}
+
+.pager li > a:hover,
+.pager li > a:focus {
+  text-decoration: none;
+  background-color: #eeeeee;
+}
+
+.pager .next > a,
+.pager .next > span {
+  float: right;
+}
+
+.pager .previous > a,
+.pager .previous > span {
+  float: left;
+}
+
+.pager .disabled > a,
+.pager .disabled > a:hover,
+.pager .disabled > a:focus,
+.pager .disabled > span {
+  color: #999999;
+  cursor: not-allowed;
+  background-color: #ffffff;
+}
+
+.label {
+  display: inline;
+  padding: .2em .6em .3em;
+  font-size: 75%;
+  font-weight: bold;
+  line-height: 1;
+  color: #ffffff;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  border-radius: .25em;
+}
+
+.label[href]:hover,
+.label[href]:focus {
+  color: #ffffff;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.label:empty {
+  display: none;
+}
+
+.label-default {
+  background-color: #999999;
+}
+
+.label-default[href]:hover,
+.label-default[href]:focus {
+  background-color: #808080;
+}
+
+.label-primary {
+  background-color: #428bca;
+}
+
+.label-primary[href]:hover,
+.label-primary[href]:focus {
+  background-color: #3071a9;
+}
+
+.label-success {
+  background-color: #5cb85c;
+}
+
+.label-success[href]:hover,
+.label-success[href]:focus {
+  background-color: #449d44;
+}
+
+.label-info {
+  background-color: #5bc0de;
+}
+
+.label-info[href]:hover,
+.label-info[href]:focus {
+  background-color: #31b0d5;
+}
+
+.label-warning {
+  background-color: #f0ad4e;
+}
+
+.label-warning[href]:hover,
+.label-warning[href]:focus {
+  background-color: #ec971f;
+}
+
+.label-danger {
+  background-color: #d9534f;
+}
+
+.label-danger[href]:hover,
+.label-danger[href]:focus {
+  background-color: #c9302c;
+}
+
+.badge {
+  display: inline-block;
+  min-width: 10px;
+  padding: 3px 7px;
+  font-size: 12px;
+  font-weight: bold;
+  line-height: 1;
+  color: #ffffff;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  background-color: #999999;
+  border-radius: 10px;
+}
+
+.badge:empty {
+  display: none;
+}
+
+a.badge:hover,
+a.badge:focus {
+  color: #ffffff;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.btn .badge {
+  position: relative;
+  top: -1px;
+}
+
+a.list-group-item.active > .badge,
+.nav-pills > .active > a > .badge {
+  color: #428bca;
+  background-color: #ffffff;
+}
+
+.nav-pills > li > a > .badge {
+  margin-left: 3px;
+}
+
+.jumbotron {
+  padding: 30px;
+  margin-bottom: 30px;
+  font-size: 21px;
+  font-weight: 200;
+  line-height: 2.1428571435;
+  color: inherit;
+  background-color: #eeeeee;
+}
+
+.jumbotron h1 {
+  line-height: 1;
+  color: inherit;
+}
+
+.jumbotron p {
+  line-height: 1.4;
+}
+
+.container .jumbotron {
+  border-radius: 6px;
+}
+
+@media screen and (min-width: 768px) {
+  .jumbotron {
+    padding-top: 48px;
+    padding-bottom: 48px;
+  }
+  .container .jumbotron {
+    padding-right: 60px;
+    padding-left: 60px;
+  }
+  .jumbotron h1 {
+    font-size: 63px;
+  }
+}
+
+.thumbnail {
+  display: inline-block;
+  display: block;
+  height: auto;
+  max-width: 100%;
+  padding: 4px;
+  margin-bottom: 20px;
+  line-height: 1.428571429;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-radius: 4px;
+  -webkit-transition: all 0.2s ease-in-out;
+          transition: all 0.2s ease-in-out;
+}
+
+.thumbnail > img {
+  display: block;
+  height: auto;
+  max-width: 100%;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+a.thumbnail:hover,
+a.thumbnail:focus,
+a.thumbnail.active {
+  border-color: #428bca;
+}
+
+.thumbnail .caption {
+  padding: 9px;
+  color: #333333;
+}
+
+.alert {
+  padding: 15px;
+  margin-bottom: 20px;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+
+.alert h4 {
+  margin-top: 0;
+  color: inherit;
+}
+
+.alert .alert-link {
+  font-weight: bold;
+}
+
+.alert > p,
+.alert > ul {
+  margin-bottom: 0;
+}
+
+.alert > p + p {
+  margin-top: 5px;
+}
+
+.alert-dismissable {
+  padding-right: 35px;
+}
+
+.alert-dismissable .close {
+  position: relative;
+  top: -2px;
+  right: -21px;
+  color: inherit;
+}
+
+.alert-success {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+
+.alert-success hr {
+  border-top-color: #c9e2b3;
+}
+
+.alert-success .alert-link {
+  color: #356635;
+}
+
+.alert-info {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+
+.alert-info hr {
+  border-top-color: #a6e1ec;
+}
+
+.alert-info .alert-link {
+  color: #2d6987;
+}
+
+.alert-warning {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #faebcc;
+}
+
+.alert-warning hr {
+  border-top-color: #f7e1b5;
+}
+
+.alert-warning .alert-link {
+  color: #a47e3c;
+}
+
+.alert-danger {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #ebccd1;
+}
+
+.alert-danger hr {
+  border-top-color: #e4b9c0;
+}
+
+.alert-danger .alert-link {
+  color: #953b39;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@-moz-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@-o-keyframes progress-bar-stripes {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: 40px 0;
+  }
+}
+
+@keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+.progress {
+  height: 20px;
+  margin-bottom: 20px;
+  overflow: hidden;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.progress-bar {
+  float: left;
+  width: 0;
+  height: 100%;
+  font-size: 12px;
+  line-height: 20px;
+  color: #ffffff;
+  text-align: center;
+  background-color: #428bca;
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -webkit-transition: width 0.6s ease;
+          transition: width 0.6s ease;
+}
+
+.progress-striped .progress-bar {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-size: 40px 40px;
+}
+
+.progress.active .progress-bar {
+  -webkit-animation: progress-bar-stripes 2s linear infinite;
+          animation: progress-bar-stripes 2s linear infinite;
+}
+
+.progress-bar-success {
+  background-color: #5cb85c;
+}
+
+.progress-striped .progress-bar-success {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-info {
+  background-color: #5bc0de;
+}
+
+.progress-striped .progress-bar-info {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-warning {
+  background-color: #f0ad4e;
+}
+
+.progress-striped .progress-bar-warning {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-danger {
+  background-color: #d9534f;
+}
+
+.progress-striped .progress-bar-danger {
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.media,
+.media-body {
+  overflow: hidden;
+  zoom: 1;
+}
+
+.media,
+.media .media {
+  margin-top: 15px;
+}
+
+.media:first-child {
+  margin-top: 0;
+}
+
+.media-object {
+  display: block;
+}
+
+.media-heading {
+  margin: 0 0 5px;
+}
+
+.media > .pull-left {
+  margin-right: 10px;
+}
+
+.media > .pull-right {
+  margin-left: 10px;
+}
+
+.media-list {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-group {
+  padding-left: 0;
+  margin-bottom: 20px;
+}
+
+.list-group-item {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+  margin-bottom: -1px;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+}
+
+.list-group-item:first-child {
+  border-top-right-radius: 4px;
+  border-top-left-radius: 4px;
+}
+
+.list-group-item:last-child {
+  margin-bottom: 0;
+  border-bottom-right-radius: 4px;
+  border-bottom-left-radius: 4px;
+}
+
+.list-group-item > .badge {
+  float: right;
+}
+
+.list-group-item > .badge + .badge {
+  margin-right: 5px;
+}
+
+a.list-group-item {
+  color: #555555;
+}
+
+a.list-group-item .list-group-item-heading {
+  color: #333333;
+}
+
+a.list-group-item:hover,
+a.list-group-item:focus {
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+
+a.list-group-item.active,
+a.list-group-item.active:hover,
+a.list-group-item.active:focus {
+  z-index: 2;
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+a.list-group-item.active .list-group-item-heading,
+a.list-group-item.active:hover .list-group-item-heading,
+a.list-group-item.active:focus .list-group-item-heading {
+  color: inherit;
+}
+
+a.list-group-item.active .list-group-item-text,
+a.list-group-item.active:hover .list-group-item-text,
+a.list-group-item.active:focus .list-group-item-text {
+  color: #e1edf7;
+}
+
+.list-group-item-heading {
+  margin-top: 0;
+  margin-bottom: 5px;
+}
+
+.list-group-item-text {
+  margin-bottom: 0;
+  line-height: 1.3;
+}
+
+.panel {
+  margin-bottom: 20px;
+  background-color: #ffffff;
+  border: 1px solid transparent;
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+          box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.panel-body {
+  padding: 15px;
+}
+
+.panel-body:before,
+.panel-body:after {
+  display: table;
+  content: " ";
+}
+
+.panel-body:after {
+  clear: both;
+}
+
+.panel-body:before,
+.panel-body:after {
+  display: table;
+  content: " ";
+}
+
+.panel-body:after {
+  clear: both;
+}
+
+.panel > .list-group {
+  margin-bottom: 0;
+}
+
+.panel > .list-group .list-group-item {
+  border-width: 1px 0;
+}
+
+.panel > .list-group .list-group-item:first-child {
+  border-top-right-radius: 0;
+  border-top-left-radius: 0;
+}
+
+.panel > .list-group .list-group-item:last-child {
+  border-bottom: 0;
+}
+
+.panel-heading + .list-group .list-group-item:first-child {
+  border-top-width: 0;
+}
+
+.panel > .table,
+.panel > .table-responsive {
+  margin-bottom: 0;
+}
+
+.panel > .panel-body + .table,
+.panel > .panel-body + .table-responsive {
+  border-top: 1px solid #dddddd;
+}
+
+.panel > .table-bordered,
+.panel > .table-responsive > .table-bordered {
+  border: 0;
+}
+
+.panel > .table-bordered > thead > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,
+.panel > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-bordered > thead > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,
+.panel > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-bordered > tfoot > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+  border-left: 0;
+}
+
+.panel > .table-bordered > thead > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,
+.panel > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-bordered > thead > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,
+.panel > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-bordered > tfoot > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+  border-right: 0;
+}
+
+.panel > .table-bordered > thead > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > thead > tr:last-child > th,
+.panel > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-bordered > tfoot > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th,
+.panel > .table-bordered > thead > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > thead > tr:last-child > td,
+.panel > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-bordered > tfoot > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td {
+  border-bottom: 0;
+}
+
+.panel-heading {
+  padding: 10px 15px;
+  border-bottom: 1px solid transparent;
+  border-top-right-radius: 3px;
+  border-top-left-radius: 3px;
+}
+
+.panel-heading > .dropdown .dropdown-toggle {
+  color: inherit;
+}
+
+.panel-title {
+  margin-top: 0;
+  margin-bottom: 0;
+  font-size: 16px;
+}
+
+.panel-title > a {
+  color: inherit;
+}
+
+.panel-footer {
+  padding: 10px 15px;
+  background-color: #f5f5f5;
+  border-top: 1px solid #dddddd;
+  border-bottom-right-radius: 3px;
+  border-bottom-left-radius: 3px;
+}
+
+.panel-group .panel {
+  margin-bottom: 0;
+  overflow: hidden;
+  border-radius: 4px;
+}
+
+.panel-group .panel + .panel {
+  margin-top: 5px;
+}
+
+.panel-group .panel-heading {
+  border-bottom: 0;
+}
+
+.panel-group .panel-heading + .panel-collapse .panel-body {
+  border-top: 1px solid #dddddd;
+}
+
+.panel-group .panel-footer {
+  border-top: 0;
+}
+
+.panel-group .panel-footer + .panel-collapse .panel-body {
+  border-bottom: 1px solid #dddddd;
+}
+
+.panel-default {
+  border-color: #dddddd;
+}
+
+.panel-default > .panel-heading {
+  color: #333333;
+  background-color: #f5f5f5;
+  border-color: #dddddd;
+}
+
+.panel-default > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #dddddd;
+}
+
+.panel-default > .panel-heading > .dropdown .caret {
+  border-color: #333333 transparent;
+}
+
+.panel-default > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #dddddd;
+}
+
+.panel-primary {
+  border-color: #428bca;
+}
+
+.panel-primary > .panel-heading {
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #428bca;
+}
+
+.panel-primary > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #428bca;
+}
+
+.panel-primary > .panel-heading > .dropdown .caret {
+  border-color: #ffffff transparent;
+}
+
+.panel-primary > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #428bca;
+}
+
+.panel-success {
+  border-color: #d6e9c6;
+}
+
+.panel-success > .panel-heading {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+
+.panel-success > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #d6e9c6;
+}
+
+.panel-success > .panel-heading > .dropdown .caret {
+  border-color: #468847 transparent;
+}
+
+.panel-success > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #d6e9c6;
+}
+
+.panel-warning {
+  border-color: #faebcc;
+}
+
+.panel-warning > .panel-heading {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #faebcc;
+}
+
+.panel-warning > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #faebcc;
+}
+
+.panel-warning > .panel-heading > .dropdown .caret {
+  border-color: #c09853 transparent;
+}
+
+.panel-warning > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #faebcc;
+}
+
+.panel-danger {
+  border-color: #ebccd1;
+}
+
+.panel-danger > .panel-heading {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #ebccd1;
+}
+
+.panel-danger > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #ebccd1;
+}
+
+.panel-danger > .panel-heading > .dropdown .caret {
+  border-color: #b94a48 transparent;
+}
+
+.panel-danger > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #ebccd1;
+}
+
+.panel-info {
+  border-color: #bce8f1;
+}
+
+.panel-info > .panel-heading {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+
+.panel-info > .panel-heading + .panel-collapse .panel-body {
+  border-top-color: #bce8f1;
+}
+
+.panel-info > .panel-heading > .dropdown .caret {
+  border-color: #3a87ad transparent;
+}
+
+.panel-info > .panel-footer + .panel-collapse .panel-body {
+  border-bottom-color: #bce8f1;
+}
+
+.well {
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.well blockquote {
+  border-color: #ddd;
+  border-color: rgba(0, 0, 0, 0.15);
+}
+
+.well-lg {
+  padding: 24px;
+  border-radius: 6px;
+}
+
+.well-sm {
+  padding: 9px;
+  border-radius: 3px;
+}
+
+.close {
+  float: right;
+  font-size: 21px;
+  font-weight: bold;
+  line-height: 1;
+  color: #000000;
+  text-shadow: 0 1px 0 #ffffff;
+  opacity: 0.2;
+  filter: alpha(opacity=20);
+}
+
+.close:hover,
+.close:focus {
+  color: #000000;
+  text-decoration: none;
+  cursor: pointer;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+button.close {
+  padding: 0;
+  cursor: pointer;
+  background: transparent;
+  border: 0;
+  -webkit-appearance: none;
+}
+
+.modal-open {
+  overflow: hidden;
+}
+
+.modal {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1040;
+  display: none;
+  overflow: auto;
+  overflow-y: scroll;
+}
+
+.modal.fade .modal-dialog {
+  -webkit-transform: translate(0, -25%);
+      -ms-transform: translate(0, -25%);
+          transform: translate(0, -25%);
+  -webkit-transition: -webkit-transform 0.3s ease-out;
+     -moz-transition: -moz-transform 0.3s ease-out;
+       -o-transition: -o-transform 0.3s ease-out;
+          transition: transform 0.3s ease-out;
+}
+
+.modal.in .modal-dialog {
+  -webkit-transform: translate(0, 0);
+      -ms-transform: translate(0, 0);
+          transform: translate(0, 0);
+}
+
+.modal-dialog {
+  position: relative;
+  z-index: 1050;
+  width: auto;
+  padding: 10px;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.modal-content {
+  position: relative;
+  background-color: #ffffff;
+  border: 1px solid #999999;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 6px;
+  outline: none;
+  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
+          box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
+  background-clip: padding-box;
+}
+
+.modal-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1030;
+  background-color: #000000;
+}
+
+.modal-backdrop.fade {
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+
+.modal-backdrop.in {
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+.modal-header {
+  min-height: 16.428571429px;
+  padding: 15px;
+  border-bottom: 1px solid #e5e5e5;
+}
+
+.modal-header .close {
+  margin-top: -2px;
+}
+
+.modal-title {
+  margin: 0;
+  line-height: 1.428571429;
+}
+
+.modal-body {
+  position: relative;
+  padding: 20px;
+}
+
+.modal-footer {
+  padding: 19px 20px 20px;
+  margin-top: 15px;
+  text-align: right;
+  border-top: 1px solid #e5e5e5;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  content: " ";
+}
+
+.modal-footer:after {
+  clear: both;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  content: " ";
+}
+
+.modal-footer:after {
+  clear: both;
+}
+
+.modal-footer .btn + .btn {
+  margin-bottom: 0;
+  margin-left: 5px;
+}
+
+.modal-footer .btn-group .btn + .btn {
+  margin-left: -1px;
+}
+
+.modal-footer .btn-block + .btn-block {
+  margin-left: 0;
+}
+
+@media screen and (min-width: 768px) {
+  .modal-dialog {
+    width: 600px;
+    padding-top: 30px;
+    padding-bottom: 30px;
+  }
+  .modal-content {
+    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+  }
+}
+
+.tooltip {
+  position: absolute;
+  z-index: 1030;
+  display: block;
+  font-size: 12px;
+  line-height: 1.4;
+  opacity: 0;
+  filter: alpha(opacity=0);
+  visibility: visible;
+}
+
+.tooltip.in {
+  opacity: 0.9;
+  filter: alpha(opacity=90);
+}
+
+.tooltip.top {
+  padding: 5px 0;
+  margin-top: -3px;
+}
+
+.tooltip.right {
+  padding: 0 5px;
+  margin-left: 3px;
+}
+
+.tooltip.bottom {
+  padding: 5px 0;
+  margin-top: 3px;
+}
+
+.tooltip.left {
+  padding: 0 5px;
+  margin-left: -3px;
+}
+
+.tooltip-inner {
+  max-width: 200px;
+  padding: 3px 8px;
+  color: #ffffff;
+  text-align: center;
+  text-decoration: none;
+  background-color: #000000;
+  border-radius: 4px;
+}
+
+.tooltip-arrow {
+  position: absolute;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+
+.tooltip.top .tooltip-arrow {
+  bottom: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-top-color: #000000;
+  border-width: 5px 5px 0;
+}
+
+.tooltip.top-left .tooltip-arrow {
+  bottom: 0;
+  left: 5px;
+  border-top-color: #000000;
+  border-width: 5px 5px 0;
+}
+
+.tooltip.top-right .tooltip-arrow {
+  right: 5px;
+  bottom: 0;
+  border-top-color: #000000;
+  border-width: 5px 5px 0;
+}
+
+.tooltip.right .tooltip-arrow {
+  top: 50%;
+  left: 0;
+  margin-top: -5px;
+  border-right-color: #000000;
+  border-width: 5px 5px 5px 0;
+}
+
+.tooltip.left .tooltip-arrow {
+  top: 50%;
+  right: 0;
+  margin-top: -5px;
+  border-left-color: #000000;
+  border-width: 5px 0 5px 5px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+  top: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-bottom-color: #000000;
+  border-width: 0 5px 5px;
+}
+
+.tooltip.bottom-left .tooltip-arrow {
+  top: 0;
+  left: 5px;
+  border-bottom-color: #000000;
+  border-width: 0 5px 5px;
+}
+
+.tooltip.bottom-right .tooltip-arrow {
+  top: 0;
+  right: 5px;
+  border-bottom-color: #000000;
+  border-width: 0 5px 5px;
+}
+
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1010;
+  display: none;
+  max-width: 276px;
+  padding: 1px;
+  text-align: left;
+  white-space: normal;
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 6px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+          box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  background-clip: padding-box;
+}
+
+.popover.top {
+  margin-top: -10px;
+}
+
+.popover.right {
+  margin-left: 10px;
+}
+
+.popover.bottom {
+  margin-top: 10px;
+}
+
+.popover.left {
+  margin-left: -10px;
+}
+
+.popover-title {
+  padding: 8px 14px;
+  margin: 0;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 18px;
+  background-color: #f7f7f7;
+  border-bottom: 1px solid #ebebeb;
+  border-radius: 5px 5px 0 0;
+}
+
+.popover-content {
+  padding: 9px 14px;
+}
+
+.popover .arrow,
+.popover .arrow:after {
+  position: absolute;
+  display: block;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+
+.popover .arrow {
+  border-width: 11px;
+}
+
+.popover .arrow:after {
+  border-width: 10px;
+  content: "";
+}
+
+.popover.top .arrow {
+  bottom: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-top-color: #999999;
+  border-top-color: rgba(0, 0, 0, 0.25);
+  border-bottom-width: 0;
+}
+
+.popover.top .arrow:after {
+  bottom: 1px;
+  margin-left: -10px;
+  border-top-color: #ffffff;
+  border-bottom-width: 0;
+  content: " ";
+}
+
+.popover.right .arrow {
+  top: 50%;
+  left: -11px;
+  margin-top: -11px;
+  border-right-color: #999999;
+  border-right-color: rgba(0, 0, 0, 0.25);
+  border-left-width: 0;
+}
+
+.popover.right .arrow:after {
+  bottom: -10px;
+  left: 1px;
+  border-right-color: #ffffff;
+  border-left-width: 0;
+  content: " ";
+}
+
+.popover.bottom .arrow {
+  top: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-bottom-color: #999999;
+  border-bottom-color: rgba(0, 0, 0, 0.25);
+  border-top-width: 0;
+}
+
+.popover.bottom .arrow:after {
+  top: 1px;
+  margin-left: -10px;
+  border-bottom-color: #ffffff;
+  border-top-width: 0;
+  content: " ";
+}
+
+.popover.left .arrow {
+  top: 50%;
+  right: -11px;
+  margin-top: -11px;
+  border-left-color: #999999;
+  border-left-color: rgba(0, 0, 0, 0.25);
+  border-right-width: 0;
+}
+
+.popover.left .arrow:after {
+  right: 1px;
+  bottom: -10px;
+  border-left-color: #ffffff;
+  border-right-width: 0;
+  content: " ";
+}
+
+.carousel {
+  position: relative;
+}
+
+.carousel-inner {
+  position: relative;
+  width: 100%;
+  overflow: hidden;
+}
+
+.carousel-inner > .item {
+  position: relative;
+  display: none;
+  -webkit-transition: 0.6s ease-in-out left;
+          transition: 0.6s ease-in-out left;
+}
+
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+  display: block;
+  height: auto;
+  max-width: 100%;
+  line-height: 1;
+}
+
+.carousel-inner > .active,
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  display: block;
+}
+
+.carousel-inner > .active {
+  left: 0;
+}
+
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+
+.carousel-inner > .next {
+  left: 100%;
+}
+
+.carousel-inner > .prev {
+  left: -100%;
+}
+
+.carousel-inner > .next.left,
+.carousel-inner > .prev.right {
+  left: 0;
+}
+
+.carousel-inner > .active.left {
+  left: -100%;
+}
+
+.carousel-inner > .active.right {
+  left: 100%;
+}
+
+.carousel-control {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 15%;
+  font-size: 20px;
+  color: #ffffff;
+  text-align: center;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+.carousel-control.left {
+  background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001)));
+  background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0), color-stop(rgba(0, 0, 0, 0.0001) 100%));
+  background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
+  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);
+}
+
+.carousel-control.right {
+  right: 0;
+  left: auto;
+  background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5)));
+  background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0), color-stop(rgba(0, 0, 0, 0.5) 100%));
+  background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
+  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);
+}
+
+.carousel-control:hover,
+.carousel-control:focus {
+  color: #ffffff;
+  text-decoration: none;
+  opacity: 0.9;
+  filter: alpha(opacity=90);
+}
+
+.carousel-control .icon-prev,
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-left,
+.carousel-control .glyphicon-chevron-right {
+  position: absolute;
+  top: 50%;
+  z-index: 5;
+  display: inline-block;
+}
+
+.carousel-control .icon-prev,
+.carousel-control .glyphicon-chevron-left {
+  left: 50%;
+}
+
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-right {
+  right: 50%;
+}
+
+.carousel-control .icon-prev,
+.carousel-control .icon-next {
+  width: 20px;
+  height: 20px;
+  margin-top: -10px;
+  margin-left: -10px;
+  font-family: serif;
+}
+
+.carousel-control .icon-prev:before {
+  content: '\2039';
+}
+
+.carousel-control .icon-next:before {
+  content: '\203a';
+}
+
+.carousel-indicators {
+  position: absolute;
+  bottom: 10px;
+  left: 50%;
+  z-index: 15;
+  width: 60%;
+  padding-left: 0;
+  margin-left: -30%;
+  text-align: center;
+  list-style: none;
+}
+
+.carousel-indicators li {
+  display: inline-block;
+  width: 10px;
+  height: 10px;
+  margin: 1px;
+  text-indent: -999px;
+  cursor: pointer;
+  background-color: #000 \9;
+  background-color: rgba(0, 0, 0, 0);
+  border: 1px solid #ffffff;
+  border-radius: 10px;
+}
+
+.carousel-indicators .active {
+  width: 12px;
+  height: 12px;
+  margin: 0;
+  background-color: #ffffff;
+}
+
+.carousel-caption {
+  position: absolute;
+  right: 15%;
+  bottom: 20px;
+  left: 15%;
+  z-index: 10;
+  padding-top: 20px;
+  padding-bottom: 20px;
+  color: #ffffff;
+  text-align: center;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
+}
+
+.carousel-caption .btn {
+  text-shadow: none;
+}
+
+@media screen and (min-width: 768px) {
+  .carousel-control .glyphicons-chevron-left,
+  .carousel-control .glyphicons-chevron-right,
+  .carousel-control .icon-prev,
+  .carousel-control .icon-next {
+    width: 30px;
+    height: 30px;
+    margin-top: -15px;
+    margin-left: -15px;
+    font-size: 30px;
+  }
+  .carousel-caption {
+    right: 20%;
+    left: 20%;
+    padding-bottom: 30px;
+  }
+  .carousel-indicators {
+    bottom: 20px;
+  }
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: " ";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.center-block {
+  display: block;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.pull-right {
+  float: right !important;
+}
+
+.pull-left {
+  float: left !important;
+}
+
+.hide {
+  display: none !important;
+}
+
+.show {
+  display: block !important;
+}
+
+.invisible {
+  visibility: hidden;
+}
+
+.text-hide {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+.hidden {
+  display: none !important;
+  visibility: hidden !important;
+}
+
+.affix {
+  position: fixed;
+}
+
+@-ms-viewport {
+  width: device-width;
+}
+
+.visible-xs,
+tr.visible-xs,
+th.visible-xs,
+td.visible-xs {
+  display: none !important;
+}
+
+@media (max-width: 767px) {
+  .visible-xs {
+    display: block !important;
+  }
+  tr.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-xs,
+  td.visible-xs {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-xs.visible-sm {
+    display: block !important;
+  }
+  tr.visible-xs.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-xs.visible-sm,
+  td.visible-xs.visible-sm {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-xs.visible-md {
+    display: block !important;
+  }
+  tr.visible-xs.visible-md {
+    display: table-row !important;
+  }
+  th.visible-xs.visible-md,
+  td.visible-xs.visible-md {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .visible-xs.visible-lg {
+    display: block !important;
+  }
+  tr.visible-xs.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-xs.visible-lg,
+  td.visible-xs.visible-lg {
+    display: table-cell !important;
+  }
+}
+
+.visible-sm,
+tr.visible-sm,
+th.visible-sm,
+td.visible-sm {
+  display: none !important;
+}
+
+@media (max-width: 767px) {
+  .visible-sm.visible-xs {
+    display: block !important;
+  }
+  tr.visible-sm.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-sm.visible-xs,
+  td.visible-sm.visible-xs {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-sm {
+    display: block !important;
+  }
+  tr.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-sm,
+  td.visible-sm {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-sm.visible-md {
+    display: block !important;
+  }
+  tr.visible-sm.visible-md {
+    display: table-row !important;
+  }
+  th.visible-sm.visible-md,
+  td.visible-sm.visible-md {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .visible-sm.visible-lg {
+    display: block !important;
+  }
+  tr.visible-sm.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-sm.visible-lg,
+  td.visible-sm.visible-lg {
+    display: table-cell !important;
+  }
+}
+
+.visible-md,
+tr.visible-md,
+th.visible-md,
+td.visible-md {
+  display: none !important;
+}
+
+@media (max-width: 767px) {
+  .visible-md.visible-xs {
+    display: block !important;
+  }
+  tr.visible-md.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-md.visible-xs,
+  td.visible-md.visible-xs {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-md.visible-sm {
+    display: block !important;
+  }
+  tr.visible-md.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-md.visible-sm,
+  td.visible-md.visible-sm {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-md {
+    display: block !important;
+  }
+  tr.visible-md {
+    display: table-row !important;
+  }
+  th.visible-md,
+  td.visible-md {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .visible-md.visible-lg {
+    display: block !important;
+  }
+  tr.visible-md.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-md.visible-lg,
+  td.visible-md.visible-lg {
+    display: table-cell !important;
+  }
+}
+
+.visible-lg,
+tr.visible-lg,
+th.visible-lg,
+td.visible-lg {
+  display: none !important;
+}
+
+@media (max-width: 767px) {
+  .visible-lg.visible-xs {
+    display: block !important;
+  }
+  tr.visible-lg.visible-xs {
+    display: table-row !important;
+  }
+  th.visible-lg.visible-xs,
+  td.visible-lg.visible-xs {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .visible-lg.visible-sm {
+    display: block !important;
+  }
+  tr.visible-lg.visible-sm {
+    display: table-row !important;
+  }
+  th.visible-lg.visible-sm,
+  td.visible-lg.visible-sm {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .visible-lg.visible-md {
+    display: block !important;
+  }
+  tr.visible-lg.visible-md {
+    display: table-row !important;
+  }
+  th.visible-lg.visible-md,
+  td.visible-lg.visible-md {
+    display: table-cell !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .visible-lg {
+    display: block !important;
+  }
+  tr.visible-lg {
+    display: table-row !important;
+  }
+  th.visible-lg,
+  td.visible-lg {
+    display: table-cell !important;
+  }
+}
+
+.hidden-xs {
+  display: block !important;
+}
+
+tr.hidden-xs {
+  display: table-row !important;
+}
+
+th.hidden-xs,
+td.hidden-xs {
+  display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+  .hidden-xs,
+  tr.hidden-xs,
+  th.hidden-xs,
+  td.hidden-xs {
+    display: none !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-xs.hidden-sm,
+  tr.hidden-xs.hidden-sm,
+  th.hidden-xs.hidden-sm,
+  td.hidden-xs.hidden-sm {
+    display: none !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-xs.hidden-md,
+  tr.hidden-xs.hidden-md,
+  th.hidden-xs.hidden-md,
+  td.hidden-xs.hidden-md {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .hidden-xs.hidden-lg,
+  tr.hidden-xs.hidden-lg,
+  th.hidden-xs.hidden-lg,
+  td.hidden-xs.hidden-lg {
+    display: none !important;
+  }
+}
+
+.hidden-sm {
+  display: block !important;
+}
+
+tr.hidden-sm {
+  display: table-row !important;
+}
+
+th.hidden-sm,
+td.hidden-sm {
+  display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+  .hidden-sm.hidden-xs,
+  tr.hidden-sm.hidden-xs,
+  th.hidden-sm.hidden-xs,
+  td.hidden-sm.hidden-xs {
+    display: none !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-sm,
+  tr.hidden-sm,
+  th.hidden-sm,
+  td.hidden-sm {
+    display: none !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-sm.hidden-md,
+  tr.hidden-sm.hidden-md,
+  th.hidden-sm.hidden-md,
+  td.hidden-sm.hidden-md {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .hidden-sm.hidden-lg,
+  tr.hidden-sm.hidden-lg,
+  th.hidden-sm.hidden-lg,
+  td.hidden-sm.hidden-lg {
+    display: none !important;
+  }
+}
+
+.hidden-md {
+  display: block !important;
+}
+
+tr.hidden-md {
+  display: table-row !important;
+}
+
+th.hidden-md,
+td.hidden-md {
+  display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+  .hidden-md.hidden-xs,
+  tr.hidden-md.hidden-xs,
+  th.hidden-md.hidden-xs,
+  td.hidden-md.hidden-xs {
+    display: none !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-md.hidden-sm,
+  tr.hidden-md.hidden-sm,
+  th.hidden-md.hidden-sm,
+  td.hidden-md.hidden-sm {
+    display: none !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-md,
+  tr.hidden-md,
+  th.hidden-md,
+  td.hidden-md {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .hidden-md.hidden-lg,
+  tr.hidden-md.hidden-lg,
+  th.hidden-md.hidden-lg,
+  td.hidden-md.hidden-lg {
+    display: none !important;
+  }
+}
+
+.hidden-lg {
+  display: block !important;
+}
+
+tr.hidden-lg {
+  display: table-row !important;
+}
+
+th.hidden-lg,
+td.hidden-lg {
+  display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+  .hidden-lg.hidden-xs,
+  tr.hidden-lg.hidden-xs,
+  th.hidden-lg.hidden-xs,
+  td.hidden-lg.hidden-xs {
+    display: none !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .hidden-lg.hidden-sm,
+  tr.hidden-lg.hidden-sm,
+  th.hidden-lg.hidden-sm,
+  td.hidden-lg.hidden-sm {
+    display: none !important;
+  }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+  .hidden-lg.hidden-md,
+  tr.hidden-lg.hidden-md,
+  th.hidden-lg.hidden-md,
+  td.hidden-lg.hidden-md {
+    display: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .hidden-lg,
+  tr.hidden-lg,
+  th.hidden-lg,
+  td.hidden-lg {
+    display: none !important;
+  }
+}
+
+.visible-print,
+tr.visible-print,
+th.visible-print,
+td.visible-print {
+  display: none !important;
+}
+
+@media print {
+  .visible-print {
+    display: block !important;
+  }
+  tr.visible-print {
+    display: table-row !important;
+  }
+  th.visible-print,
+  td.visible-print {
+    display: table-cell !important;
+  }
+  .hidden-print,
+  tr.hidden-print,
+  th.hidden-print,
+  td.hidden-print {
+    display: none !important;
+  }
+}

File diff suppressed because it is too large
+ 8 - 0
Applications/Chat/Web/css/bootstrap.min.css


+ 118 - 0
Applications/Chat/Web/css/jquery-sinaEmotion-2.1.0.css

@@ -0,0 +1,118 @@
+#sinaEmotion{
+	z-index: 999;
+	width: 373px;
+	padding: 10px;
+	display: none;
+	font-size: 12px;
+	background: #fff;
+	overflow: hidden;
+	position: absolute;
+	border: 1px solid #E8E8E8;
+}
+
+#sinaEmotion .right{
+	float: right;
+}
+
+#sinaEmotion .prev,
+#sinaEmotion .next{
+	float: left;
+	color: #555;
+	width: 22px;
+	height: 22px;
+	font-size: 20px;
+	margin-left: 5px;
+	line-height: 22px;
+	text-align: center;
+	background: #f8f8f8;
+	text-decoration: none;
+}
+
+#sinaEmotion .item{
+	float: left;
+	list-style: none;
+}
+
+#sinaEmotion .categories,
+#sinaEmotion .faces,
+#sinaEmotion .pages{
+	margin: 0;
+	padding: 0;
+	overflow: hidden;
+	_zoom: 1;
+}
+
+#sinaEmotion .category{
+	float: left;
+	color: #0a8cd2;
+	cursor: pointer;
+	padding: 0 8px;
+	line-height: 22px;
+	border-radius: 4px;
+	white-space: nowrap;
+}
+
+#sinaEmotion .category:hover{
+	text-decoration: underline;
+}
+
+#sinaEmotion .categories .current,
+#sinaEmotion .categories .current:hover{
+	color: #333;
+	cursor: default;
+	background: #e6e6e6;
+	text-decoration: none;
+}
+
+#sinaEmotion .faces{
+	width: 372px;
+	padding: 11px 0 0 1px;
+}
+
+#sinaEmotion .face{
+	z-index: 1;
+	float: left;
+	width: 26px;
+	height: 22px;
+	cursor: pointer;
+	overflow: hidden;
+	padding: 4px 2px;
+	position: relative;
+	text-align: center;
+	margin: -1px 0 0 -1px;
+	border: 1px solid #e8e8e8;
+	_display: inline;
+}
+
+#sinaEmotion .face:hover{
+	z-index: 2;
+	border: 1px solid #0095cd;
+}
+
+#sinaEmotion .pages{
+	float: right;
+	margin-top: 8px;
+}
+
+#sinaEmotion .page{
+	float: left;
+	height: 22px;
+	padding: 0 8px;
+	color: #0a8cd2;
+	margin-left: 5px;
+	line-height: 22px;
+	border-radius: 1px;
+	background: #f2f2f2;
+	text-decoration: none;
+}
+
+#sinaEmotion .pages .current{
+	color: #333;
+	cursor: default;
+	background: #fff;
+}
+
+.sina-emotion{
+	border: 0;
+	vertical-align: text-bottom;
+}

+ 1 - 0
Applications/Chat/Web/css/jquery-sinaEmotion-2.1.0.min.css

@@ -0,0 +1 @@
+#sinaEmotion{z-index:999;width:373px;padding:10px;display:none;font-size:12px;background:#fff;overflow:hidden;position:absolute;border:1px solid #e8e8e8}#sinaEmotion .right{float:right}#sinaEmotion .prev,#sinaEmotion .next{float:left;color:#555;width:22px;height:22px;font-size:20px;margin-left:5px;line-height:22px;text-align:center;background:#f8f8f8;text-decoration:none}#sinaEmotion .item{float:left;list-style:none}#sinaEmotion .categories,#sinaEmotion .faces,#sinaEmotion .pages{margin:0;padding:0;overflow:hidden;_zoom:1}#sinaEmotion .category{float:left;color:#0a8cd2;cursor:pointer;padding:0 8px;line-height:22px;border-radius:4px;white-space:nowrap}#sinaEmotion .category:hover{text-decoration:underline}#sinaEmotion .categories .current,#sinaEmotion .categories .current:hover{color:#333;cursor:default;background:#e6e6e6;text-decoration:none}#sinaEmotion .faces{width:372px;padding:11px 0 0 1px}#sinaEmotion .face{z-index:1;float:left;width:26px;height:22px;cursor:pointer;overflow:hidden;padding:4px 2px;position:relative;text-align:center;margin:-1px 0 0 -1px;border:1px solid #e8e8e8;_display:inline}#sinaEmotion .face:hover{z-index:2;border:1px solid #0095cd}#sinaEmotion .pages{float:right;margin-top:8px}#sinaEmotion .page{float:left;height:22px;padding:0 8px;color:#0a8cd2;margin-left:5px;line-height:22px;border-radius:1px;background:#f2f2f2;text-decoration:none}#sinaEmotion .pages .current{color:#333;cursor:default;background:#fff}.sina-emotion{border:0;vertical-align:text-bottom}

+ 139 - 0
Applications/Chat/Web/css/style.css

@@ -0,0 +1,139 @@
+body
+{
+	background:#FCFCFC;
+}
+.say-btn
+{
+	text-align:right;
+	margin-top:12px;
+}
+
+#dialog
+{
+	min-height:600px;
+	background:#EEEEEE;
+	max-height: 80vh;
+	overflow: scroll;
+}
+
+.textarea
+{
+    height:6em;
+	width:100%;
+}
+
+#userlist
+{
+	min-height:600px;
+	background:#EEEEEE;
+}
+
+#userlist > li
+{
+	color:#1372A2;
+	list-style:none;
+	margin-left:12px;
+}
+
+#userlist > h4
+{
+    text-align:center;
+	font-size:14px;
+	font-weight:nold;
+}
+
+.words
+{
+	margin:8px;
+}
+
+
+.triangle-isosceles {
+    position:relative;
+    padding:10px;
+    margin:10px 0 15px;
+    color:#000;
+    background:#D3FF93; /* default background for browsers without gradient support */
+    background:-webkit-gradient(linear, 0 0, 0 100%, from(#EFFFD7), to(#D3FF93));
+    background:-moz-linear-gradient(#EFFFD7, #D3FF93);
+    background:-o-linear-gradient(#EFFFD7, #D3FF93);
+    background:linear-gradient(#EFFFD7, #D3FF93);
+    -webkit-border-radius:10px;
+    -moz-border-radius:10px;
+    border-radius:10px;
+    -moz-box-shadow:1px 1px 2px hsla(0, 0%, 0%, 0.3);  
+    -webkit-box-shadow:1px 1px 2px hsla(0, 0%, 0%, 0.3);  
+     box-shadow:1px 1px 2px hsla(0, 0%, 0%, 0.3);  
+}
+
+    .triangle-isosceles:hover{  
+        top:-2px;  
+        left:-2px;  
+        -moz-box-shadow:3px 3px 2px hsla(0, 0%, 0%, 0.3);  
+        -webkit-box-shadow:3px 3px 2px hsla(0, 0%, 0%, 0.3);  
+        box-shadow:3px 3px 2x hsla(0, 0%, 0%, 0.3);  
+    }  
+
+.triangle-isosceles.top {
+    background:-webkit-gradient(linear, 0 0, 0 100%, from(#D3FF93), to(#EFFFD7));
+    background:-moz-linear-gradient(#D3FF93, #EFFFD7);
+    background:-o-linear-gradient(#D3FF93, #EFFFD7);
+    background:linear-gradient(#D3FF93, #EFFFD7);
+}
+
+.triangle-isosceles:after {
+    content:"";
+    position:absolute;
+    bottom:-9px;
+    left:15px;
+    border-width:9px 21px 0; 
+    border-style:solid;
+    border-color:#D3FF93 transparent;
+    display:block; 
+    width:0;
+}
+.triangle-isosceles.top:after {
+    top:-9px;
+    left:15px;
+    bottom:auto;
+    border-width:0 9px 9px;
+    border-color:#D3FF93 transparent;
+}
+
+.speech_item img{
+    max-height: 300px;
+    display: block;
+    margin-top: 5px;
+    margin-bottom: 10px;
+}
+
+.user_icon
+{
+	float:left;border:1px solid #DDDDDD;padding:2px;margin:0 5px 0 5px;
+}
+
+.cp
+{
+	color:#888888;
+	text-align:center;
+	font-size:11px;
+}
+
+.thumbnail
+{
+	border:1px solid #CCCCCC;
+}
+
+#sinaEmotion .item{
+    margin: 2px;
+    height: 25px;
+}
+
+#sinaEmotion .face{
+    padding: 1px 2px;
+    height: 25px;
+}
+
+img.sina-emotion{
+    display: inline;
+}

BIN
Applications/Chat/Web/img/workerman-todpole.png


+ 212 - 0
Applications/Chat/Web/index.php

@@ -0,0 +1,212 @@
+<html><head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <title>workerman-chat PHP聊天室 Websocket(HTLM5/Flash)+PHP多进程socket实时推送技术</title>
+  <link href="/css/bootstrap.min.css" rel="stylesheet">
+    <link href="/css/jquery-sinaEmotion-2.1.0.min.css" rel="stylesheet">
+    <link href="/css/style.css" rel="stylesheet">
+	
+  <script type="text/javascript" src="/js/swfobject.js"></script>
+  <script type="text/javascript" src="/js/web_socket.js"></script>
+  <script type="text/javascript" src="/js/jquery.min.js"></script>
+    <script type="text/javascript" src="/js/jquery-sinaEmotion-2.1.0.min.js"></script>
+
+  <script type="text/javascript">
+    if (typeof console == "undefined") {    this.console = { log: function (msg) {  } };}
+    // 如果浏览器不支持websocket,会使用这个flash自动模拟websocket协议,此过程对开发者透明
+    WEB_SOCKET_SWF_LOCATION = "/swf/WebSocketMain.swf";
+    // 开启flash的websocket debug
+    WEB_SOCKET_DEBUG = true;
+    var ws, name, client_list={};
+
+    // 连接服务端
+    function connect() {
+       // 创建websocket
+       ws = new WebSocket("ws://127.0.0.1:7272");
+       // 当socket连接打开时,输入用户名
+       ws.onopen = onopen;
+       // 当有消息时根据消息类型显示不同信息
+       ws.onmessage = onmessage; 
+       ws.onclose = function() {
+    	  console.log("连接关闭,定时重连");
+          connect();
+       };
+       ws.onerror = function() {
+     	  console.log("出现错误");
+       };
+    }
+
+    // 连接建立时发送登录信息
+    function onopen()
+    {
+        if(!name)
+        {
+            show_prompt();
+        }
+        // 登录
+        var login_data = '{"type":"login","client_name":"'+name.replace(/"/g, '\\"')+'","room_id":"<?php echo isset($_GET['room_id']) ? $_GET['room_id'] : 1?>"}';
+        console.log("websocket握手成功,发送登录数据:"+login_data);
+        ws.send(login_data);
+    }
+
+    // 服务端发来消息时
+    function onmessage(e)
+    {
+        console.log(e.data);
+        var data = JSON.parse(e.data);
+        switch(data['type']){
+            // 服务端ping客户端
+            case 'ping':
+                ws.send('{"type":"pong"}');
+                break;;
+            // 登录 更新用户列表
+            case 'login':
+                //{"type":"login","client_id":xxx,"client_name":"xxx","client_list":"[...]","time":"xxx"}
+                say(data['client_id'], data['client_name'],  data['client_name']+' 加入了聊天室', data['time']);
+                if(data['client_list'])
+                {
+                    client_list = data['client_list'];
+                }
+                else
+                {
+                    client_list[data['client_id']] = data['client_name']; 
+                }
+                flush_client_list();
+                console.log(data['client_name']+"登录成功");
+                break;
+            // 发言
+            case 'say':
+                //{"type":"say","from_client_id":xxx,"to_client_id":"all/client_id","content":"xxx","time":"xxx"}
+                say(data['from_client_id'], data['from_client_name'], data['content'], data['time']);
+                break;
+            // 用户退出 更新用户列表
+            case 'logout':
+                //{"type":"logout","client_id":xxx,"time":"xxx"}
+                say(data['from_client_id'], data['from_client_name'], data['from_client_name']+' 退出了', data['time']);
+                delete client_list[data['from_client_id']];
+                flush_client_list();
+        }
+    }
+
+    // 输入姓名
+    function show_prompt(){  
+        name = prompt('输入你的名字:', '');
+        if(!name || name=='null'){  
+            name = '游客';
+        }
+    }  
+
+    // 提交对话
+    function onSubmit() {
+      var input = document.getElementById("textarea");
+      var to_client_id = $("#client_list option:selected").attr("value");
+      var to_client_name = $("#client_list option:selected").text();
+      ws.send('{"type":"say","to_client_id":"'+to_client_id+'","to_client_name":"'+to_client_name+'","content":"'+input.value.replace(/"/g, '\\"').replace(/\n/g,'\\n').replace(/\r/g, '\\r')+'"}');
+      input.value = "";
+      input.focus();
+    }
+
+    // 刷新用户列表框
+    function flush_client_list(){
+    	var userlist_window = $("#userlist");
+    	var client_list_slelect = $("#client_list");
+    	userlist_window.empty();
+    	client_list_slelect.empty();
+    	userlist_window.append('<h4>在线用户</h4><ul>');
+    	client_list_slelect.append('<option value="all" id="cli_all">所有人</option>');
+    	for(var p in client_list){
+            userlist_window.append('<li id="'+p+'">'+client_list[p]+'</li>');
+            client_list_slelect.append('<option value="'+p+'">'+client_list[p]+'</option>');
+        }
+    	$("#client_list").val(select_client_id);
+    	userlist_window.append('</ul>');
+    }
+
+    // 发言
+    function say(from_client_id, from_client_name, content, time){
+        //解析新浪微博图片
+        content = content.replace(/(http|https):\/\/[\w]+.sinaimg.cn[\S]+(jpg|png|gif)/gi, function(img){
+            return "<a target='_blank' href='"+img+"'>"+"<img src='"+img+"'>"+"</a>";}
+        );
+
+        //解析url
+        content = content.replace(/(http|https):\/\/[\S]+/gi, function(url){
+            if(url.indexOf(".sinaimg.cn/") < 0)
+                return "<a target='_blank' href='"+url+"'>"+url+"</a>";
+            else
+                return url;
+        }
+        );
+
+    	$("#dialog").append('<div class="speech_item"><img src="http://lorempixel.com/38/38/?'+from_client_id+'" class="user_icon" /> '+from_client_name+' <br> '+time+'<div style="clear:both;"></div><p class="triangle-isosceles top">'+content+'</p> </div>').parseEmotion();
+    }
+
+    $(function(){
+    	select_client_id = 'all';
+	    $("#client_list").change(function(){
+	         select_client_id = $("#client_list option:selected").attr("value");
+	    });
+        $('.face').click(function(event){
+            $(this).sinaEmotion();
+            event.stopPropagation();
+        });
+    });
+
+
+  </script>
+</head>
+<body onload="connect();">
+    <div class="container">
+	    <div class="row clearfix">
+	        <div class="col-md-1 column">
+	        </div>
+	        <div class="col-md-6 column">
+	           <div class="thumbnail">
+	               <div class="caption" id="dialog"></div>
+	           </div>
+	           <form onsubmit="onSubmit(); return false;">
+	                <select style="margin-bottom:8px" id="client_list">
+                        <option value="all">所有人</option>
+                    </select>
+                    <textarea class="textarea thumbnail" id="textarea"></textarea>
+                    <div class="say-btn">
+                        <input type="button" class="btn btn-default face pull-left" value="表情" />
+                        <input type="submit" class="btn btn-default" value="发表" />
+                    </div>
+               </form>
+               <div>
+               &nbsp;&nbsp;&nbsp;&nbsp;<b>房间列表:</b>(当前在&nbsp;房间<?php echo isset($_GET['room_id'])&&intval($_GET['room_id'])>0 ? intval($_GET['room_id']):1; ?>)<br>
+               &nbsp;&nbsp;&nbsp;&nbsp;<a href="/?room_id=1">房间1</a>&nbsp;&nbsp;&nbsp;&nbsp;<a href="/?room_id=2">房间2</a>&nbsp;&nbsp;&nbsp;&nbsp;<a href="/?room_id=3">房间3</a>&nbsp;&nbsp;&nbsp;&nbsp;<a href="/?room_id=4">房间4</a>
+               <br><br>
+               </div>
+               <p class="cp">PHP多进程+Websocket(HTML5/Flash)+PHP Socket实时推送技术&nbsp;&nbsp;&nbsp;&nbsp;Powered by <a href="http://www.workerman.net/workerman-chat" target="_blank">workerman-chat</a></p>
+	        </div>
+	        <div class="col-md-3 column">
+	           <div class="thumbnail">
+                   <div class="caption" id="userlist"></div>
+               </div>
+               <a href="http://workerman.net:8383" target="_blank"><img style="width:252px;margin-left:5px;" src="/img/workerman-todpole.png"></a>
+	        </div>
+	    </div>
+    </div>
+    <script type="text/javascript">var _bdhmProtocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cscript src='" + _bdhmProtocol + "hm.baidu.com/h.js%3F7b1919221e89d2aa5711e4deb935debd' type='text/javascript'%3E%3C/script%3E"));</script>
+    <script type="text/javascript">
+      // 动态自适应屏幕
+      document.write('<meta name="viewport" content="width=device-width,initial-scale=1">');
+      $("textarea").on("keydown", function(e) {
+          // 按enter键自动提交
+          if(e.keyCode === 13 && !e.ctrlKey) {
+              e.preventDefault();
+              $('form').submit();
+              return false;
+          }
+
+          // 按ctrl+enter组合键换行
+          if(e.keyCode === 13 && e.ctrlKey) {
+              $(this).val(function(i,val){
+                  return val + "\n";
+              });
+          }
+      });
+    </script>
+</body>
+</html>

+ 280 - 0
Applications/Chat/Web/js/jquery-sinaEmotion-2.1.0.js

@@ -0,0 +1,280 @@
+/*!
+ * jQuery Sina Emotion v2.1.0
+ * http://www.clanfei.com/
+ *
+ * Copyright 2012-2014 Lanfei
+ * Released under the MIT license
+ *
+ * Date: 2014-05-19T20:10:23+0800
+ */
+(function($) {
+
+	var $target;
+
+	var options;
+
+	var emotions;
+
+	var categories;
+
+	var emotionsMap;
+
+	var parsingArray = [];
+
+	var defCategory = '默认';
+
+	var initEvents = function() {
+		$('body').bind({
+			click: function() {
+				$('#sinaEmotion').hide();
+			}
+		});
+
+		$('#sinaEmotion').bind({
+			click: function(event) {
+				event.stopPropagation();
+			}
+		}).delegate('.prev', {
+			click: function(event) {
+				var page = $('#sinaEmotion .categories').data('page');
+				showCatPage(page - 1);
+				event.preventDefault();
+			}
+		}).delegate('.next', {
+			click: function(event) {
+				var page = $('#sinaEmotion .categories').data('page');
+				showCatPage(page + 1);
+				event.preventDefault();
+			}
+		}).delegate('.category', {
+			click: function(event) {
+				$('#sinaEmotion .categories .current').removeClass('current');
+				showCategory($.trim($(this).addClass('current').text()));
+				event.preventDefault();
+			}
+		}).delegate('.page', {
+			click: function(event) {
+				$('#sinaEmotion .pages .current').removeClass('current');
+				var page = parseInt($(this).addClass('current').text() - 1);
+				showFacePage(page);
+				event.preventDefault();
+			}
+		}).delegate('.face', {
+			click: function(event) {
+				$('#sinaEmotion').hide();
+				$target.insertText($(this).children('img').prop('alt'));
+				event.preventDefault();
+			}
+		});
+	};
+
+	var loadEmotions = function(callback) {
+
+		if(emotions){
+			callback && callback();
+			return;
+		}
+
+		if (!options) {
+			options = $.fn.sinaEmotion.options;
+		}
+
+		emotions = {};
+		categories = [];
+		emotionsMap = {};
+
+		$('body').append('<div id="sinaEmotion">正在加载,请稍后...</div>');
+
+		initEvents();
+
+		$.getJSON('https://api.weibo.com/2/emotions.json?callback=?', {
+			source: options.appKey,
+			language: options.language
+		}, function(json) {
+
+			var item, category;
+			var data = json.data;
+
+			$('#sinaEmotion').html('<div class="right"><a href="#" class="prev">&laquo;</a><a href="#" class="next">&raquo;</a></div><ul class="categories"></ul><ul class="faces"></ul><ul class="pages"></ul>');
+
+			for (var i = 0, l = data.length; i < l; ++i) {
+				item = data[i];
+				category = item.category || defCategory;
+
+				if (!emotions[category]) {
+					emotions[category] = [];
+					categories.push(category);
+				}
+
+				emotions[category].push({
+					icon: item.icon,
+					phrase: item.phrase
+				});
+
+				emotionsMap[item.phrase] = item.icon;
+			}
+
+			$(parsingArray).parseEmotion();
+			parsingArray = null;
+
+			callback && callback();
+		});
+	};
+
+	var showCatPage = function(page) {
+
+		var html = '';
+		var length = categories.length;
+		var maxPage = Math.ceil(length / 5);
+		var $categories = $('#sinaEmotion .categories');
+		var category = $categories.data('category') || defCategory;
+
+		page = (page + maxPage) % maxPage;
+
+		for (var i = page * 5; i < length && i < (page + 1) * 5; ++i) {
+			html += '<li class="item"><a href="#" class="category' + (category == categories[i] ? ' current' : '') + '">' + categories[i] + '</a></li>';
+		}
+
+		$categories.data('page', page).html(html);
+	};
+
+	var showCategory = function(category) {
+		$('#sinaEmotion .categories').data('category', category);
+		showFacePage(0);
+		showPages();
+	};
+
+	var showFacePage = function(page) {
+
+		var face;
+		var html = '';
+		var pageHtml = '';
+		var rows = options.rows;
+		var category = $('#sinaEmotion .categories').data('category');
+		var faces = emotions[category];
+		page = page || 0;
+
+		for (var i = page * rows, l = faces.length; i < l && i < (page + 1) * rows; ++i) {
+			face = faces[i];
+			html += '<li class="item"><a href="#" class="face"><img class="sina-emotion" src="' + face.icon + '" alt="' + face.phrase + '" /></a></li>';
+		}
+
+		$('#sinaEmotion .faces').html(html);
+	};
+
+	var showPages = function() {
+
+		var html = '';
+		var rows = options.rows;
+		var category = $('#sinaEmotion .categories').data('category');
+		var faces = emotions[category];
+		var length = faces.length;
+
+		if (length > rows) {
+			for (var i = 0, l = Math.ceil(length / rows); i < l; ++i) {
+				html += '<li class="item"><a href="#" class="page' + (i == 0 ? ' current' : '') + '">' + (i + 1) + '</a></li>';
+			}
+			$('#sinaEmotion .pages').html(html).show();
+		} else {
+			$('#sinaEmotion .pages').hide();
+		}
+	};
+
+	/**
+	 * 为某个元素设置点击事件,点击弹出表情选择窗口
+	 * @param  {[type]} target [description]
+	 * @return {[type]}        [description]
+	 */
+	$.fn.sinaEmotion = function(target) {
+
+		target = target || function(){
+			return $(this).parents('form').find('textarea,input[type=text]').eq(0);
+		};
+
+		var $that = $(this).last();
+		var offset = $that.offset();
+
+		if($that.is(':visible')){
+			if(typeof target == 'function'){
+				$target = target.call($that);
+			}else{
+				$target = $(target);
+			}
+
+			loadEmotions(function(){
+				showCategory(defCategory);
+				showCatPage(0);
+			});
+			$('#sinaEmotion').css({
+				top: offset.top + $that.outerHeight() + 5,
+				left: offset.left
+			}).show();
+		}
+
+		return this;
+	};
+
+	$.fn.parseEmotion = function() {
+
+		if(! categories){
+			parsingArray = $(this);
+			loadEmotions();
+		}else if(categories.length == 0){
+			parsingArray = parsingArray.add($(this));
+		}else{
+			$(this).each(function() {
+
+				var $this = $(this);
+				var html = $this.html();
+
+				html = html.replace(/<.*?>/g, function($1) {
+					$1 = $1.replace('[', '&#91;');
+					$1 = $1.replace(']', '&#93;');
+					return $1;
+				}).replace(/\[[^\[\]]*?\]/g, function($1) {
+					var url = emotionsMap[$1];
+					if (url) {
+						return '<img class="sina-emotion" src="' + url + '" alt="' + $1 + '" />';
+					}
+					return $1;
+				});
+
+				$this.html(html);
+			});
+		}
+
+		return this;
+	};
+
+	$.fn.insertText = function(text) {
+
+		this.each(function() {
+
+			if (this.tagName !== 'INPUT' && this.tagName !== 'TEXTAREA') {
+				return;
+			}
+			if (document.selection) {
+				this.focus();
+				var cr = document.selection.createRange();
+				cr.text = text;
+				cr.collapse();
+				cr.select();
+			} else if (this.selectionStart !== undefined) {
+				var start = this.selectionStart;
+				var end = this.selectionEnd;
+				this.value = this.value.substring(0, start) + text + this.value.substring(end, this.value.length);
+				this.selectionStart = this.selectionEnd = start + text.length;
+			} else {
+				this.value += text;
+			}
+		});
+
+		return this;
+	}
+
+	$.fn.sinaEmotion.options = {
+		rows: 72,				// 每页显示的表情数
+		language: 'cnname',		// 简体(cnname)、繁体(twname)
+		appKey: '1362404091'	// 新浪微博开放平台的应用ID
+	};
+})(jQuery);

File diff suppressed because it is too large
+ 9 - 0
Applications/Chat/Web/js/jquery-sinaEmotion-2.1.0.min.js


File diff suppressed because it is too large
+ 1 - 0
Applications/Chat/Web/js/jquery.min.js


File diff suppressed because it is too large
+ 3 - 0
Applications/Chat/Web/js/swfobject.js


+ 398 - 0
Applications/Chat/Web/js/web_socket.js

@@ -0,0 +1,398 @@
+// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
+// License: New BSD License
+// Reference: http://dev.w3.org/html5/websockets/
+// Reference: http://tools.ietf.org/html/rfc6455
+
+(function() {
+  
+  if (window.WEB_SOCKET_FORCE_FLASH) {
+    // Keeps going.
+  } else if (window.WebSocket) {
+    return;
+  } else if (window.MozWebSocket) {
+    // Firefox.
+    window.WebSocket = MozWebSocket;
+    return;
+  }
+  
+  var logger;
+  if (window.WEB_SOCKET_LOGGER) {
+    logger = WEB_SOCKET_LOGGER;
+  } else if (window.console && window.console.log && window.console.error) {
+    // In some environment, console is defined but console.log or console.error is missing.
+    logger = window.console;
+  } else {
+    logger = {log: function(){ }, error: function(){ }};
+  }
+  
+  // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
+  if (swfobject.getFlashPlayerVersion().major < 10) {
+    logger.error("Flash Player >= 10.0.0 is required.");
+    return;
+  }
+  if (location.protocol == "file:") {
+    logger.error(
+      "WARNING: web-socket-js doesn't work in file:///... URL " +
+      "unless you set Flash Security Settings properly. " +
+      "Open the page via Web server i.e. http://...");
+  }
+
+  /**
+   * Our own implementation of WebSocket class using Flash.
+   * @param {string} url
+   * @param {array or string} protocols
+   * @param {string} proxyHost
+   * @param {int} proxyPort
+   * @param {string} headers
+   */
+  window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
+    var self = this;
+    self.__id = WebSocket.__nextId++;
+    WebSocket.__instances[self.__id] = self;
+    self.readyState = WebSocket.CONNECTING;
+    self.bufferedAmount = 0;
+    self.__events = {};
+    if (!protocols) {
+      protocols = [];
+    } else if (typeof protocols == "string") {
+      protocols = [protocols];
+    }
+    // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
+    // Otherwise, when onopen fires immediately, onopen is called before it is set.
+    self.__createTask = setTimeout(function() {
+      WebSocket.__addTask(function() {
+        self.__createTask = null;
+        WebSocket.__flash.create(
+            self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
+      });
+    }, 0);
+  };
+
+  /**
+   * Send data to the web socket.
+   * @param {string} data  The data to send to the socket.
+   * @return {boolean}  True for success, false for failure.
+   */
+  WebSocket.prototype.send = function(data) {
+    if (this.readyState == WebSocket.CONNECTING) {
+      throw "INVALID_STATE_ERR: Web Socket connection has not been established";
+    }
+    // We use encodeURIComponent() here, because FABridge doesn't work if
+    // the argument includes some characters. We don't use escape() here
+    // because of this:
+    // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
+    // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
+    // preserve all Unicode characters either e.g. "\uffff" in Firefox.
+    // Note by wtritch: Hopefully this will not be necessary using ExternalInterface.  Will require
+    // additional testing.
+    var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
+    if (result < 0) { // success
+      return true;
+    } else {
+      this.bufferedAmount += result;
+      return false;
+    }
+  };
+
+  /**
+   * Close this web socket gracefully.
+   */
+  WebSocket.prototype.close = function() {
+    if (this.__createTask) {
+      clearTimeout(this.__createTask);
+      this.__createTask = null;
+      this.readyState = WebSocket.CLOSED;
+      return;
+    }
+    if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
+      return;
+    }
+    this.readyState = WebSocket.CLOSING;
+    WebSocket.__flash.close(this.__id);
+  };
+
+  /**
+   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+   *
+   * @param {string} type
+   * @param {function} listener
+   * @param {boolean} useCapture
+   * @return void
+   */
+  WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
+    if (!(type in this.__events)) {
+      this.__events[type] = [];
+    }
+    this.__events[type].push(listener);
+  };
+
+  /**
+   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+   *
+   * @param {string} type
+   * @param {function} listener
+   * @param {boolean} useCapture
+   * @return void
+   */
+  WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
+    if (!(type in this.__events)) return;
+    var events = this.__events[type];
+    for (var i = events.length - 1; i >= 0; --i) {
+      if (events[i] === listener) {
+        events.splice(i, 1);
+        break;
+      }
+    }
+  };
+
+  /**
+   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+   *
+   * @param {Event} event
+   * @return void
+   */
+  WebSocket.prototype.dispatchEvent = function(event) {
+    var events = this.__events[event.type] || [];
+    for (var i = 0; i < events.length; ++i) {
+      events[i](event);
+    }
+    var handler = this["on" + event.type];
+    if (handler) handler.apply(this, [event]);
+  };
+
+  /**
+   * Handles an event from Flash.
+   * @param {Object} flashEvent
+   */
+  WebSocket.prototype.__handleEvent = function(flashEvent) {
+    
+    if ("readyState" in flashEvent) {
+      this.readyState = flashEvent.readyState;
+    }
+    if ("protocol" in flashEvent) {
+      this.protocol = flashEvent.protocol;
+    }
+    
+    var jsEvent;
+    if (flashEvent.type == "open" || flashEvent.type == "error") {
+      jsEvent = this.__createSimpleEvent(flashEvent.type);
+    } else if (flashEvent.type == "close") {
+      jsEvent = this.__createSimpleEvent("close");
+      jsEvent.wasClean = flashEvent.wasClean ? true : false;
+      jsEvent.code = flashEvent.code;
+      jsEvent.reason = flashEvent.reason;
+    } else if (flashEvent.type == "message") {
+      var data = decodeURIComponent(flashEvent.message);
+      jsEvent = this.__createMessageEvent("message", data);
+    } else {
+      throw "unknown event type: " + flashEvent.type;
+    }
+    
+    this.dispatchEvent(jsEvent);
+    
+  };
+  
+  WebSocket.prototype.__createSimpleEvent = function(type) {
+    if (document.createEvent && window.Event) {
+      var event = document.createEvent("Event");
+      event.initEvent(type, false, false);
+      return event;
+    } else {
+      return {type: type, bubbles: false, cancelable: false};
+    }
+  };
+  
+  WebSocket.prototype.__createMessageEvent = function(type, data) {
+    if (window.MessageEvent && typeof(MessageEvent) == "function" && !window.opera) {
+      return new MessageEvent("message", {
+        "view": window,
+        "bubbles": false,
+        "cancelable": false,
+        "data": data
+      });
+    } else if (document.createEvent && window.MessageEvent && !window.opera) {
+      var event = document.createEvent("MessageEvent");
+    	event.initMessageEvent("message", false, false, data, null, null, window, null);
+      return event;
+    } else {
+      // Old IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
+      return {type: type, data: data, bubbles: false, cancelable: false};
+    }
+  };
+  
+  /**
+   * Define the WebSocket readyState enumeration.
+   */
+  WebSocket.CONNECTING = 0;
+  WebSocket.OPEN = 1;
+  WebSocket.CLOSING = 2;
+  WebSocket.CLOSED = 3;
+
+  // Field to check implementation of WebSocket.
+  WebSocket.__isFlashImplementation = true;
+  WebSocket.__initialized = false;
+  WebSocket.__flash = null;
+  WebSocket.__instances = {};
+  WebSocket.__tasks = [];
+  WebSocket.__nextId = 0;
+  
+  /**
+   * Load a new flash security policy file.
+   * @param {string} url
+   */
+  WebSocket.loadFlashPolicyFile = function(url){
+    WebSocket.__addTask(function() {
+      WebSocket.__flash.loadManualPolicyFile(url);
+    });
+  };
+
+  /**
+   * Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
+   */
+  WebSocket.__initialize = function() {
+    
+    if (WebSocket.__initialized) return;
+    WebSocket.__initialized = true;
+    
+    if (WebSocket.__swfLocation) {
+      // For backword compatibility.
+      window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
+    }
+    if (!window.WEB_SOCKET_SWF_LOCATION) {
+      logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
+      return;
+    }
+    if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
+        !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
+        WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
+      var swfHost = RegExp.$1;
+      if (location.host != swfHost) {
+        logger.error(
+            "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
+            "('" + location.host + "' != '" + swfHost + "'). " +
+            "See also 'How to host HTML file and SWF file in different domains' section " +
+            "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
+            "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
+      }
+    }
+    var container = document.createElement("div");
+    container.id = "webSocketContainer";
+    // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
+    // Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
+    // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
+    // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
+    // the best we can do as far as we know now.
+    container.style.position = "absolute";
+    if (WebSocket.__isFlashLite()) {
+      container.style.left = "0px";
+      container.style.top = "0px";
+    } else {
+      container.style.left = "-100px";
+      container.style.top = "-100px";
+    }
+    var holder = document.createElement("div");
+    holder.id = "webSocketFlash";
+    container.appendChild(holder);
+    document.body.appendChild(container);
+    // See this article for hasPriority:
+    // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
+    swfobject.embedSWF(
+      WEB_SOCKET_SWF_LOCATION,
+      "webSocketFlash",
+      "1" /* width */,
+      "1" /* height */,
+      "10.0.0" /* SWF version */,
+      null,
+      null,
+      {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
+      null,
+      function(e) {
+        if (!e.success) {
+          logger.error("[WebSocket] swfobject.embedSWF failed");
+        }
+      }
+    );
+    
+  };
+  
+  /**
+   * Called by Flash to notify JS that it's fully loaded and ready
+   * for communication.
+   */
+  WebSocket.__onFlashInitialized = function() {
+    // We need to set a timeout here to avoid round-trip calls
+    // to flash during the initialization process.
+    setTimeout(function() {
+      WebSocket.__flash = document.getElementById("webSocketFlash");
+      WebSocket.__flash.setCallerUrl(location.href);
+      WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
+      for (var i = 0; i < WebSocket.__tasks.length; ++i) {
+        WebSocket.__tasks[i]();
+      }
+      WebSocket.__tasks = [];
+    }, 0);
+  };
+  
+  /**
+   * Called by Flash to notify WebSockets events are fired.
+   */
+  WebSocket.__onFlashEvent = function() {
+    setTimeout(function() {
+      try {
+        // Gets events using receiveEvents() instead of getting it from event object
+        // of Flash event. This is to make sure to keep message order.
+        // It seems sometimes Flash events don't arrive in the same order as they are sent.
+        var events = WebSocket.__flash.receiveEvents();
+        for (var i = 0; i < events.length; ++i) {
+          WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
+        }
+      } catch (e) {
+        logger.error(e);
+      }
+    }, 0);
+    return true;
+  };
+  
+  // Called by Flash.
+  WebSocket.__log = function(message) {
+    logger.log(decodeURIComponent(message));
+  };
+  
+  // Called by Flash.
+  WebSocket.__error = function(message) {
+    logger.error(decodeURIComponent(message));
+  };
+  
+  WebSocket.__addTask = function(task) {
+    if (WebSocket.__flash) {
+      task();
+    } else {
+      WebSocket.__tasks.push(task);
+    }
+  };
+  
+  /**
+   * Test if the browser is running flash lite.
+   * @return {boolean} True if flash lite is running, false otherwise.
+   */
+  WebSocket.__isFlashLite = function() {
+    if (!window.navigator || !window.navigator.mimeTypes) {
+      return false;
+    }
+    var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
+    if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
+      return false;
+    }
+    return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
+  };
+  
+  if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
+    // NOTE:
+    //   This fires immediately if web_socket.js is dynamically loaded after
+    //   the document is loaded.
+    swfobject.addDomLoadEvent(function() {
+      WebSocket.__initialize();
+    });
+  }
+  
+})();

BIN
Applications/Chat/Web/swf/WebSocketMain.swf


+ 34 - 0
Applications/Chat/start_businessworker.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * This file is part of workerman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+use \Workerman\Worker;
+use \GatewayWorker\BusinessWorker;
+use \Workerman\Autoloader;
+
+require_once __DIR__ . '/../../vendor/autoload.php';
+
+// bussinessWorker 进程
+$worker = new BusinessWorker();
+// worker名称
+$worker->name = 'ChatBusinessWorker';
+// bussinessWorker进程数量
+$worker->count = 4;
+// 服务注册地址
+$worker->registerAddress = '127.0.0.1:2345';
+
+// 如果不是在根目录启动,则运行runAll方法
+if(!defined('GLOBAL_START'))
+{
+    Worker::runAll();
+}
+

+ 61 - 0
Applications/Chat/start_gateway.php

@@ -0,0 +1,61 @@
+<?php 
+/**
+ * This file is part of workerman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+use \Workerman\Worker;
+use \GatewayWorker\Gateway;
+use \Workerman\Autoloader;
+
+require_once __DIR__ . '/../../vendor/autoload.php';
+
+// gateway 进程
+$gateway = new Gateway("tcp://0.0.0.0:2346");
+// 设置名称,方便status时查看
+$gateway->name = 'ChatGateway';
+// 设置进程数,gateway进程数建议与cpu核数相同
+$gateway->count = 4;
+// 分布式部署时请设置成内网ip(非127.0.0.1)
+$gateway->lanIp = '127.0.0.1';
+// 内部通讯起始端口。假如$gateway->count=4,起始端口为2300
+// 则一般会使用2300 2301 2302 2303 4个端口作为内部通讯端口 
+$gateway->startPort = 2347;
+// 心跳间隔
+$gateway->pingInterval = 10;
+// 心跳数据
+$gateway->pingData = '{"type":"ping"}';
+// 服务注册地址
+$gateway->registerAddress = '127.0.0.1:2345';
+
+/* 
+// 当客户端连接上来时,设置连接的onWebSocketConnect,即在websocket握手时的回调
+$gateway->onConnect = function($connection)
+{
+    $connection->onWebSocketConnect = function($connection , $http_header)
+    {
+        // 可以在这里判断连接来源是否合法,不合法就关掉连接
+        // $_SERVER['HTTP_ORIGIN']标识来自哪个站点的页面发起的websocket链接
+        if($_SERVER['HTTP_ORIGIN'] != 'http://chat.workerman.net')
+        {
+            $connection->close();
+        }
+        // onWebSocketConnect 里面$_GET $_SERVER是可用的
+        // var_dump($_GET, $_SERVER);
+    };
+}; 
+*/
+
+// 如果不是在根目录启动,则运行runAll方法
+if(!defined('GLOBAL_START'))
+{
+    Worker::runAll();
+}
+

+ 27 - 0
Applications/Chat/start_register.php

@@ -0,0 +1,27 @@
+<?php 
+/**
+ * This file is part of workerman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+use \Workerman\Worker;
+use \GatewayWorker\Register;
+
+require_once __DIR__ . '/../../vendor/autoload.php';
+
+// register 服务必须是text协议
+$register = new Register('text://0.0.0.0:2345');
+
+// 如果不是在根目录启动,则运行runAll方法
+if(!defined('GLOBAL_START'))
+{
+    Worker::runAll();
+}
+

+ 79 - 0
Applications/Chat/start_web.php

@@ -0,0 +1,79 @@
+<?php 
+/**
+ * This file is part of workerman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link http://www.workerman.net/
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+use Workerman\Worker;
+use Workerman\Protocols\Http\Request;
+use Workerman\Protocols\Http\Response;
+use Workerman\Connection\TcpConnection;
+
+require_once __DIR__ . '/../../vendor/autoload.php';
+
+// WebServer
+$web = new Worker("http://0.0.0.0:55151");
+// WebServer进程数量
+$web->count = 2;
+
+define('WEBROOT', __DIR__ . DIRECTORY_SEPARATOR .  'Web');
+
+$web->onMessage = function (TcpConnection $connection, Request $request) {
+    $_GET = $request->get();
+    $path = $request->path();
+    if ($path === '/') {
+        $connection->send(exec_php_file(WEBROOT.'/index.php'));
+        return;
+    }
+    $file = realpath(WEBROOT. $path);
+    if (false === $file) {
+        $connection->send(new Response(404, array(), '<h3>404 Not Found</h3>'));
+        return;
+    }
+    // Security check! Very important!!!
+    if (strpos($file, WEBROOT) !== 0) {
+        $connection->send(new Response(400));
+        return;
+    }
+    if (\pathinfo($file, PATHINFO_EXTENSION) === 'php') {
+        $connection->send(exec_php_file($file));
+        return;
+    }
+
+    $if_modified_since = $request->header('if-modified-since');
+    if (!empty($if_modified_since)) {
+        // Check 304.
+        $info = \stat($file);
+        $modified_time = $info ? \date('D, d M Y H:i:s', $info['mtime']) . ' ' . \date_default_timezone_get() : '';
+        if ($modified_time === $if_modified_since) {
+            $connection->send(new Response(304));
+            return;
+        }
+    }
+    $connection->send((new Response())->withFile($file));
+};
+
+function exec_php_file($file) {
+    \ob_start();
+    // Try to include php file.
+    try {
+        include $file;
+    } catch (\Exception $e) {
+        echo $e;
+    }
+    return \ob_get_clean();
+}
+
+// 如果不是在根目录启动,则运行runAll方法
+if(!defined('GLOBAL_START'))
+{
+    Worker::runAll();
+}
+

+ 1 - 0
application/api/controller/Noble.php

@@ -5,6 +5,7 @@ namespace app\api\controller;
 
 use app\common\controller\Api;
 use think\Db;
+vendor('workerman.GatewayClient.Gateway');
 use GatewayClient\Gateway;
 /**
  * 贵族接口

+ 25 - 0
server.php

@@ -1,5 +1,30 @@
 <?php
+
+use Workerman\Worker;
+
+//重定向目录
+$cur_dir = dirname(__FILE__);
+chdir($cur_dir);
+//php版本判断
+$ver = '7.0.0';
+if(version_compare(PHP_VERSION, $ver) < 0) {
+    die('PHP版本最低为:'.$ver.'(当前为:'.PHP_VERSION.')');
+}
+/*
+由于TP5自带composer库的加载器.
+如果直接使用vendor的autoload会导致函数重定义错误
+所以直接从入口处引入tp文件,即可加载workerman
+*/
+define('GLOBAL_START', 1);
+ini_set('memory_limit', '10000M');
+
 define('APP_PATH', __DIR__ . '/application/');
 define('BIND_MODULE','push/Worker');
+
+$file_name = basename(__FILE__);
+//日志
+Worker::$stdoutFile = RUNTIME_PATH."workerman.{$file_name}.stdout.log";
+Worker::$logFile = RUNTIME_PATH."workerman.{$file_name}.log";
+Worker::$pidFile = RUNTIME_PATH."workerman.{$file_name}.pid";
 // 加载框架引导文件
 require __DIR__ . '/thinkphp/start.php';

+ 21 - 0
vendor/workerman/gateway-worker/MIT-LICENSE.txt

@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2009-2015 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/workerman/contributors)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 38 - 0
vendor/workerman/gateway-worker/README.md

@@ -0,0 +1,38 @@
+GatewayWorker 
+=================
+
+GatewayWorker基于[Workerman](https://github.com/walkor/Workerman)开发的一个项目框架,用于快速开发长连接应用,例如app推送服务端、即时IM服务端、游戏服务端、物联网、智能家居等等。
+
+GatewayWorker使用经典的Gateway和Worker进程模型。Gateway进程负责维持客户端连接,并转发客户端的数据给Worker进程处理;Worker进程负责处理实际的业务逻辑,并将结果推送给对应的客户端。Gateway服务和Worker服务可以分开部署在不同的服务器上,实现分布式集群。
+
+GatewayWorker提供非常方便的API,可以全局广播数据、可以向某个群体广播数据、也可以向某个特定客户端推送数据。配合Workerman的定时器,也可以定时推送数据。
+
+快速开始
+======
+开发者可以从一个简单的demo开始(demo中包含了GatewayWorker内核,以及start_gateway.php start_business.php等启动入口文件)<br>
+[点击这里下载demo](http://www.workerman.net/download/GatewayWorker.zip)。<br>
+demo说明见源码readme。
+
+手册
+=======
+http://www.workerman.net/gatewaydoc/
+
+安装内核
+=======
+
+只安装GatewayWorker内核文件(不包含start_gateway.php start_businessworker.php等启动入口文件)
+```
+composer require workerman/gateway-worker
+```
+
+使用GatewayWorker开发的项目
+=======
+## [tadpole](http://kedou.workerman.net/)  
+[Live demo](http://kedou.workerman.net/)  
+[Source code](https://github.com/walkor/workerman)  
+![workerman todpole](http://www.workerman.net/img/workerman-todpole.png)   
+
+## [chat room](http://chat.workerman.net/)  
+[Live demo](http://chat.workerman.net/)  
+[Source code](https://github.com/walkor/workerman-chat)  
+![workerman-chat](http://www.workerman.net/img/workerman-chat.png)  

+ 12 - 0
vendor/workerman/gateway-worker/composer.json

@@ -0,0 +1,12 @@
+{
+    "name"  : "workerman/gateway-worker",
+    "keywords": ["distributed","communication"],
+    "homepage": "http://www.workerman.net",
+    "license" : "MIT",
+    "require": {
+        "workerman/workerman" : ">=3.5.0"
+    },
+    "autoload": {
+        "psr-4": {"GatewayWorker\\": "./src"}
+    }
+}

+ 565 - 0
vendor/workerman/gateway-worker/src/BusinessWorker.php

@@ -0,0 +1,565 @@
+<?php
+/**
+ * This file is part of workerman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace GatewayWorker;
+
+use Workerman\Connection\TcpConnection;
+
+use Workerman\Worker;
+use Workerman\Lib\Timer;
+use Workerman\Connection\AsyncTcpConnection;
+use GatewayWorker\Protocols\GatewayProtocol;
+use GatewayWorker\Lib\Context;
+
+/**
+ *
+ * BusinessWorker 用于处理Gateway转发来的数据
+ *
+ * @author walkor<walkor@workerman.net>
+ *
+ */
+class BusinessWorker extends Worker
+{
+    /**
+     * 保存与 gateway 的连接 connection 对象
+     *
+     * @var array
+     */
+    public $gatewayConnections = array();
+
+    /**
+     * 注册中心地址
+     *
+     * @var string|array
+     */
+    public $registerAddress = '127.0.0.1:1236';
+
+    /**
+     * 事件处理类,默认是 Event 类
+     *
+     * @var string
+     */
+    public $eventHandler = 'Events';
+
+    /**
+     * 业务超时时间,可用来定位程序卡在哪里
+     *
+     * @var int
+     */
+    public $processTimeout = 30;
+
+    /**
+     * 业务超时时间,可用来定位程序卡在哪里
+     *
+     * @var callable
+     */
+    public $processTimeoutHandler = '\\Workerman\\Worker::log';
+    
+    /**
+     * 秘钥
+     *
+     * @var string
+     */
+    public $secretKey = '';
+
+    /**
+     * businessWorker进程将消息转发给gateway进程的发送缓冲区大小
+     *
+     * @var int
+     */
+    public $sendToGatewayBufferSize = 10240000;
+
+    /**
+     * 保存用户设置的 worker 启动回调
+     *
+     * @var callback
+     */
+    protected $_onWorkerStart = null;
+
+    /**
+     * 保存用户设置的 workerReload 回调
+     *
+     * @var callback
+     */
+    protected $_onWorkerReload = null;
+    
+    /**
+     * 保存用户设置的 workerStop 回调
+     *
+     * @var callback
+     */
+    protected $_onWorkerStop= null;
+
+    /**
+     * 到注册中心的连接
+     *
+     * @var AsyncTcpConnection
+     */
+    protected $_registerConnection = null;
+
+    /**
+     * 处于连接状态的 gateway 通讯地址
+     *
+     * @var array
+     */
+    protected $_connectingGatewayAddresses = array();
+
+    /**
+     * 所有 geteway 内部通讯地址
+     *
+     * @var array
+     */
+    protected $_gatewayAddresses = array();
+
+    /**
+     * 等待连接个 gateway 地址
+     *
+     * @var array
+     */
+    protected $_waitingConnectGatewayAddresses = array();
+
+    /**
+     * Event::onConnect 回调
+     *
+     * @var callback
+     */
+    protected $_eventOnConnect = null;
+
+    /**
+     * Event::onMessage 回调
+     *
+     * @var callback
+     */
+    protected $_eventOnMessage = null;
+
+    /**
+     * Event::onClose 回调
+     *
+     * @var callback
+     */
+    protected $_eventOnClose = null;
+
+    /**
+     * websocket回调
+     *
+     * @var null
+     */
+    protected $_eventOnWebSocketConnect = null;
+
+    /**
+     * SESSION 版本缓存
+     *
+     * @var array
+     */
+    protected $_sessionVersion = array();
+
+    /**
+     * 用于保持长连接的心跳时间间隔
+     *
+     * @var int
+     */
+    const PERSISTENCE_CONNECTION_PING_INTERVAL = 25;
+
+    /**
+     * 构造函数
+     *
+     * @param string $socket_name
+     * @param array  $context_option
+     */
+    public function __construct($socket_name = '', $context_option = array())
+    {
+        parent::__construct($socket_name, $context_option);
+        $backrace                = debug_backtrace();
+        $this->_autoloadRootPath = dirname($backrace[0]['file']);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function run()
+    {
+        $this->_onWorkerStart  = $this->onWorkerStart;
+        $this->_onWorkerReload = $this->onWorkerReload;
+        $this->_onWorkerStop = $this->onWorkerStop;
+        $this->onWorkerStop   = array($this, 'onWorkerStop');
+        $this->onWorkerStart   = array($this, 'onWorkerStart');
+        $this->onWorkerReload  = array($this, 'onWorkerReload');
+        parent::run();
+    }
+
+    /**
+     * 当进程启动时一些初始化工作
+     *
+     * @return void
+     */
+    protected function onWorkerStart()
+    {
+        if (function_exists('opcache_reset')) {
+            opcache_reset();
+        }
+
+        if (!class_exists('\Protocols\GatewayProtocol')) {
+            class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol');
+        }
+
+        if (!is_array($this->registerAddress)) {
+            $this->registerAddress = array($this->registerAddress);
+        }
+        $this->connectToRegister();
+
+        \GatewayWorker\Lib\Gateway::setBusinessWorker($this);
+        \GatewayWorker\Lib\Gateway::$secretKey = $this->secretKey;
+        if ($this->_onWorkerStart) {
+            call_user_func($this->_onWorkerStart, $this);
+        }
+        
+        if (is_callable($this->eventHandler . '::onWorkerStart')) {
+            call_user_func($this->eventHandler . '::onWorkerStart', $this);
+        }
+
+        if (function_exists('pcntl_signal')) {
+            // 业务超时信号处理
+            pcntl_signal(SIGALRM, array($this, 'timeoutHandler'), false);
+        } else {
+            $this->processTimeout = 0;
+        }
+
+        // 设置回调
+        if (is_callable($this->eventHandler . '::onConnect')) {
+            $this->_eventOnConnect = $this->eventHandler . '::onConnect';
+        }
+
+        if (is_callable($this->eventHandler . '::onMessage')) {
+            $this->_eventOnMessage = $this->eventHandler . '::onMessage';
+        } else {
+            echo "Waring: {$this->eventHandler}::onMessage is not callable\n";
+        }
+
+        if (is_callable($this->eventHandler . '::onClose')) {
+            $this->_eventOnClose = $this->eventHandler . '::onClose';
+        }
+
+        if (is_callable($this->eventHandler . '::onWebSocketConnect')) {
+            $this->_eventOnWebSocketConnect = $this->eventHandler . '::onWebSocketConnect';
+        }
+
+    }
+
+    /**
+     * onWorkerReload 回调
+     *
+     * @param Worker $worker
+     */
+    protected function onWorkerReload($worker)
+    {
+        // 防止进程立刻退出
+        $worker->reloadable = false;
+        // 延迟 0.05 秒退出,避免 BusinessWorker 瞬间全部退出导致没有可用的 BusinessWorker 进程
+        Timer::add(0.05, array('Workerman\Worker', 'stopAll'));
+        // 执行用户定义的 onWorkerReload 回调
+        if ($this->_onWorkerReload) {
+            call_user_func($this->_onWorkerReload, $this);
+        }
+    }
+    
+    /**
+     * 当进程关闭时一些清理工作
+     *
+     * @return void
+     */
+    protected function onWorkerStop()
+    {
+        if ($this->_onWorkerStop) {
+            call_user_func($this->_onWorkerStop, $this);
+        }
+        if (is_callable($this->eventHandler . '::onWorkerStop')) {
+            call_user_func($this->eventHandler . '::onWorkerStop', $this);
+        }
+    }
+
+    /**
+     * 连接服务注册中心
+     * 
+     * @return void
+     */
+    public function connectToRegister()
+    {
+        foreach ($this->registerAddress as $register_address) {
+            $register_connection = new AsyncTcpConnection("text://{$register_address}");
+            $secret_key = $this->secretKey;
+            $register_connection->onConnect = function () use ($register_connection, $secret_key, $register_address) {
+                $register_connection->send('{"event":"worker_connect","secret_key":"' . $secret_key . '"}');
+                // 如果Register服务器不在本地服务器,则需要保持心跳
+                if (strpos($register_address, '127.0.0.1') !== 0) {
+                    $register_connection->ping_timer = Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, function () use ($register_connection) {
+                        $register_connection->send('{"event":"ping"}');
+                    });
+                }
+            };
+            $register_connection->onClose = function ($register_connection) {
+                if(!empty($register_connection->ping_timer)) {
+                    Timer::del($register_connection->ping_timer);
+                }
+                $register_connection->reconnect(1);
+            };
+            $register_connection->onMessage = array($this, 'onRegisterConnectionMessage');
+            $register_connection->connect();
+        }
+    }
+
+
+    /**
+     * 当注册中心发来消息时
+     *
+     * @return void
+     */
+    public function onRegisterConnectionMessage($register_connection, $data)
+    {
+        $data = json_decode($data, true);
+        if (!isset($data['event'])) {
+            echo "Received bad data from Register\n";
+            return;
+        }
+        $event = $data['event'];
+        switch ($event) {
+            case 'broadcast_addresses':
+                if (!is_array($data['addresses'])) {
+                    echo "Received bad data from Register. Addresses empty\n";
+                    return;
+                }
+                $addresses               = $data['addresses'];
+                $this->_gatewayAddresses = array();
+                foreach ($addresses as $addr) {
+                    $this->_gatewayAddresses[$addr] = $addr;
+                }
+                $this->checkGatewayConnections($addresses);
+                break;
+            default:
+                echo "Receive bad event:$event from Register.\n";
+        }
+    }
+
+    /**
+     * 当 gateway 转发来数据时
+     *
+     * @param TcpConnection $connection
+     * @param mixed         $data
+     */
+    public function onGatewayMessage($connection, $data)
+    {
+        $cmd = $data['cmd'];
+        if ($cmd === GatewayProtocol::CMD_PING) {
+            return;
+        }
+        // 上下文数据
+        Context::$client_ip     = $data['client_ip'];
+        Context::$client_port   = $data['client_port'];
+        Context::$local_ip      = $data['local_ip'];
+        Context::$local_port    = $data['local_port'];
+        Context::$connection_id = $data['connection_id'];
+        Context::$client_id     = Context::addressToClientId($data['local_ip'], $data['local_port'],
+            $data['connection_id']);
+        // $_SERVER 变量
+        $_SERVER = array(
+            'REMOTE_ADDR'       => long2ip($data['client_ip']),
+            'REMOTE_PORT'       => $data['client_port'],
+            'GATEWAY_ADDR'      => long2ip($data['local_ip']),
+            'GATEWAY_PORT'      => $data['gateway_port'],
+            'GATEWAY_CLIENT_ID' => Context::$client_id,
+        );
+        // 检查session版本,如果是过期的session数据则拉取最新的数据
+        if ($cmd !== GatewayProtocol::CMD_ON_CLOSE && isset($this->_sessionVersion[Context::$client_id]) && $this->_sessionVersion[Context::$client_id] !== crc32($data['ext_data'])) {
+            $_SESSION = Context::$old_session = \GatewayWorker\Lib\Gateway::getSession(Context::$client_id);
+            $this->_sessionVersion[Context::$client_id] = crc32($data['ext_data']);
+        } else {
+            if (!isset($this->_sessionVersion[Context::$client_id])) {
+                $this->_sessionVersion[Context::$client_id] = crc32($data['ext_data']);
+            }
+            // 尝试解析 session
+            if ($data['ext_data'] != '') {
+                Context::$old_session = $_SESSION = Context::sessionDecode($data['ext_data']);
+            } else {
+                Context::$old_session = $_SESSION = null;
+            }
+        }
+
+        if ($this->processTimeout) {
+            pcntl_alarm($this->processTimeout);
+        }
+        // 尝试执行 Event::onConnection、Event::onMessage、Event::onClose
+        switch ($cmd) {
+            case GatewayProtocol::CMD_ON_CONNECT:
+                if ($this->_eventOnConnect) {
+                    call_user_func($this->_eventOnConnect, Context::$client_id);
+                }
+                break;
+            case GatewayProtocol::CMD_ON_MESSAGE:
+                if ($this->_eventOnMessage) {
+                    call_user_func($this->_eventOnMessage, Context::$client_id, $data['body']);
+                }
+                break;
+            case GatewayProtocol::CMD_ON_CLOSE:
+                unset($this->_sessionVersion[Context::$client_id]);
+                if ($this->_eventOnClose) {
+                    call_user_func($this->_eventOnClose, Context::$client_id);
+                }
+                break;
+            case GatewayProtocol::CMD_ON_WEBSOCKET_CONNECT:
+                if ($this->_eventOnWebSocketConnect) {
+                    call_user_func($this->_eventOnWebSocketConnect, Context::$client_id, $data['body']);
+                }
+                break;
+        }
+        if ($this->processTimeout) {
+            pcntl_alarm(0);
+        }
+        
+        // session 必须是数组
+        if ($_SESSION !== null && !is_array($_SESSION)) {
+            throw new \Exception('$_SESSION must be an array. But $_SESSION=' . var_export($_SESSION, true) . ' is not array.');
+        }
+
+        // 判断 session 是否被更改
+        if ($_SESSION !== Context::$old_session && $cmd !== GatewayProtocol::CMD_ON_CLOSE) {
+            $session_str_now = $_SESSION !== null ? Context::sessionEncode($_SESSION) : '';
+            \GatewayWorker\Lib\Gateway::setSocketSession(Context::$client_id, $session_str_now);
+            $this->_sessionVersion[Context::$client_id] = crc32($session_str_now);
+        }
+
+        Context::clear();
+    }
+
+    /**
+     * 当与 Gateway 的连接断开时触发
+     *
+     * @param TcpConnection $connection
+     * @return  void
+     */
+    public function onGatewayClose($connection)
+    {
+        $addr = $connection->remoteAddress;
+        unset($this->gatewayConnections[$addr], $this->_connectingGatewayAddresses[$addr]);
+        if (isset($this->_gatewayAddresses[$addr]) && !isset($this->_waitingConnectGatewayAddresses[$addr])) {
+            Timer::add(1, array($this, 'tryToConnectGateway'), array($addr), false);
+            $this->_waitingConnectGatewayAddresses[$addr] = $addr;
+        }
+    }
+
+    /**
+     * 尝试连接 Gateway 内部通讯地址
+     *
+     * @param string $addr
+     */
+    public function tryToConnectGateway($addr)
+    {
+        if (!isset($this->gatewayConnections[$addr]) && !isset($this->_connectingGatewayAddresses[$addr]) && isset($this->_gatewayAddresses[$addr])) {
+            $gateway_connection                    = new AsyncTcpConnection("GatewayProtocol://$addr");
+            $gateway_connection->remoteAddress     = $addr;
+            $gateway_connection->onConnect         = array($this, 'onConnectGateway');
+            $gateway_connection->onMessage         = array($this, 'onGatewayMessage');
+            $gateway_connection->onClose           = array($this, 'onGatewayClose');
+            $gateway_connection->onError           = array($this, 'onGatewayError');
+            $gateway_connection->maxSendBufferSize = $this->sendToGatewayBufferSize;
+            if (TcpConnection::$defaultMaxSendBufferSize == $gateway_connection->maxSendBufferSize) {
+                $gateway_connection->maxSendBufferSize = 50 * 1024 * 1024;
+            }
+            $gateway_data         = GatewayProtocol::$empty;
+            $gateway_data['cmd']  = GatewayProtocol::CMD_WORKER_CONNECT;
+            $gateway_data['body'] = json_encode(array(
+                'worker_key' =>"{$this->name}:{$this->id}", 
+                'secret_key' => $this->secretKey,
+            ));
+            $gateway_connection->send($gateway_data);
+            $gateway_connection->connect();
+            $this->_connectingGatewayAddresses[$addr] = $addr;
+        }
+        unset($this->_waitingConnectGatewayAddresses[$addr]);
+    }
+
+    /**
+     * 检查 gateway 的通信端口是否都已经连
+     * 如果有未连接的端口,则尝试连接
+     *
+     * @param array $addresses_list
+     */
+    public function checkGatewayConnections($addresses_list)
+    {
+        if (empty($addresses_list)) {
+            return;
+        }
+        foreach ($addresses_list as $addr) {
+            if (!isset($this->_waitingConnectGatewayAddresses[$addr])) {
+                $this->tryToConnectGateway($addr);
+            }
+        }
+    }
+
+    /**
+     * 当连接上 gateway 的通讯端口时触发
+     * 将连接 connection 对象保存起来
+     *
+     * @param TcpConnection $connection
+     * @return void
+     */
+    public function onConnectGateway($connection)
+    {
+        $this->gatewayConnections[$connection->remoteAddress] = $connection;
+        unset($this->_connectingGatewayAddresses[$connection->remoteAddress], $this->_waitingConnectGatewayAddresses[$connection->remoteAddress]);
+    }
+
+    /**
+     * 当与 gateway 的连接出现错误时触发
+     *
+     * @param TcpConnection $connection
+     * @param int           $error_no
+     * @param string        $error_msg
+     */
+    public function onGatewayError($connection, $error_no, $error_msg)
+    {
+        echo "GatewayConnection Error : $error_no ,$error_msg\n";
+    }
+
+    /**
+     * 获取所有 Gateway 内部通讯地址
+     *
+     * @return array
+     */
+    public function getAllGatewayAddresses()
+    {
+        return $this->_gatewayAddresses;
+    }
+
+    /**
+     * 业务超时回调
+     *
+     * @param int $signal
+     * @throws \Exception
+     */
+    public function timeoutHandler($signal)
+    {
+        switch ($signal) {
+            // 超时时钟
+            case SIGALRM:
+                // 超时异常
+                $e         = new \Exception("process_timeout", 506);
+                $trace_str = $e->getTraceAsString();
+                // 去掉第一行timeoutHandler的调用栈
+                $trace_str = $e->getMessage() . ":\n" . substr($trace_str, strpos($trace_str, "\n") + 1) . "\n";
+                // 开发者没有设置超时处理函数,或者超时处理函数返回空则执行退出
+                if (!$this->processTimeoutHandler || !call_user_func($this->processTimeoutHandler, $trace_str, $e)) {
+                    Worker::stopAll();
+                }
+                break;
+        }
+    }
+}

+ 1028 - 0
vendor/workerman/gateway-worker/src/Gateway.php

@@ -0,0 +1,1028 @@
+<?php
+/**
+ * This file is part of workerman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace GatewayWorker;
+
+use GatewayWorker\Lib\Context;
+
+use Workerman\Connection\TcpConnection;
+
+use Workerman\Worker;
+use Workerman\Lib\Timer;
+use Workerman\Autoloader;
+use Workerman\Connection\AsyncTcpConnection;
+use GatewayWorker\Protocols\GatewayProtocol;
+
+/**
+ *
+ * Gateway,基于Worker 开发
+ * 用于转发客户端的数据给Worker处理,以及转发Worker的数据给客户端
+ *
+ * @author walkor<walkor@workerman.net>
+ *
+ */
+class Gateway extends Worker
+{
+    /**
+     * 版本
+     *
+     * @var string
+     */
+    const VERSION = '3.0.19';
+
+    /**
+     * 本机 IP
+     *  单机部署默认 127.0.0.1,如果是分布式部署,需要设置成本机 IP
+     *
+     * @var string
+     */
+    public $lanIp = '127.0.0.1';
+
+    /**
+     * 本机端口
+     *
+     * @var string
+     */
+    public $lanPort = 0;
+
+    /**
+     * gateway 内部通讯起始端口,每个 gateway 实例应该都不同,步长1000
+     *
+     * @var int
+     */
+    public $startPort = 2000;
+
+    /**
+     * 注册服务地址,用于注册 Gateway BusinessWorker,使之能够通讯
+     *
+     * @var string|array
+     */
+    public $registerAddress = '127.0.0.1:1236';
+
+    /**
+     * 是否可以平滑重启,gateway 不能平滑重启,否则会导致连接断开
+     *
+     * @var bool
+     */
+    public $reloadable = false;
+
+    /**
+     * 心跳时间间隔
+     *
+     * @var int
+     */
+    public $pingInterval = 0;
+
+    /**
+     * $pingNotResponseLimit * $pingInterval 时间内,客户端未发送任何数据,断开客户端连接
+     *
+     * @var int
+     */
+    public $pingNotResponseLimit = 0;
+
+    /**
+     * 服务端向客户端发送的心跳数据
+     *
+     * @var string
+     */
+    public $pingData = '';
+    
+    /**
+     * 秘钥
+     *
+     * @var string
+     */
+    public $secretKey = '';
+
+    /**
+     * 路由函数
+     *
+     * @var callback
+     */
+    public $router = null;
+
+
+    /**
+     * gateway进程转发给businessWorker进程的发送缓冲区大小
+     *
+     * @var int
+     */
+    public $sendToWorkerBufferSize = 10240000;
+
+    /**
+     * gateway进程将数据发给客户端时每个客户端发送缓冲区大小
+     *
+     * @var int
+     */
+    public $sendToClientBufferSize = 1024000;
+
+    /**
+     * 协议加速
+     *
+     * @var bool
+     */
+    public $protocolAccelerate = false;
+
+    /**
+     * 保存客户端的所有 connection 对象
+     *
+     * @var array
+     */
+    protected $_clientConnections = array();
+
+    /**
+     * uid 到 connection 的映射,一对多关系
+     */
+    protected $_uidConnections = array();
+
+    /**
+     * group 到 connection 的映射,一对多关系
+     *
+     * @var array
+     */
+    protected $_groupConnections = array();
+
+    /**
+     * 保存所有 worker 的内部连接的 connection 对象
+     *
+     * @var array
+     */
+    protected $_workerConnections = array();
+
+    /**
+     * gateway 内部监听 worker 内部连接的 worker
+     *
+     * @var Worker
+     */
+    protected $_innerTcpWorker = null;
+
+    /**
+     * 当 worker 启动时
+     *
+     * @var callback
+     */
+    protected $_onWorkerStart = null;
+
+    /**
+     * 当有客户端连接时
+     *
+     * @var callback
+     */
+    protected $_onConnect = null;
+
+    /**
+     * 当客户端发来消息时
+     *
+     * @var callback
+     */
+    protected $_onMessage = null;
+
+    /**
+     * 当客户端连接关闭时
+     *
+     * @var callback
+     */
+    protected $_onClose = null;
+
+    /**
+     * 当 worker 停止时
+     *
+     * @var callback
+     */
+    protected $_onWorkerStop = null;
+
+    /**
+     * 进程启动时间
+     *
+     * @var int
+     */
+    protected $_startTime = 0;
+
+    /**
+     * gateway 监听的端口
+     *
+     * @var int
+     */
+    protected $_gatewayPort = 0;
+    
+    /**
+     * connectionId 记录器
+     * @var int
+     */
+    protected static $_connectionIdRecorder = 0;
+
+    /**
+     * 用于保持长连接的心跳时间间隔
+     *
+     * @var int
+     */
+    const PERSISTENCE_CONNECTION_PING_INTERVAL = 25;
+
+    /**
+     * 构造函数
+     *
+     * @param string $socket_name
+     * @param array  $context_option
+     */
+    public function __construct($socket_name, $context_option = array())
+    {
+        parent::__construct($socket_name, $context_option);
+		$this->_gatewayPort = substr(strrchr($socket_name,':'),1);
+        $this->router = array("\\GatewayWorker\\Gateway", 'routerBind');
+
+        $backtrace               = debug_backtrace();
+        $this->_autoloadRootPath = dirname($backtrace[0]['file']);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function run()
+    {
+        // 保存用户的回调,当对应的事件发生时触发
+        $this->_onWorkerStart = $this->onWorkerStart;
+        $this->onWorkerStart  = array($this, 'onWorkerStart');
+        // 保存用户的回调,当对应的事件发生时触发
+        $this->_onConnect = $this->onConnect;
+        $this->onConnect  = array($this, 'onClientConnect');
+
+        // onMessage禁止用户设置回调
+        $this->onMessage = array($this, 'onClientMessage');
+
+        // 保存用户的回调,当对应的事件发生时触发
+        $this->_onClose = $this->onClose;
+        $this->onClose  = array($this, 'onClientClose');
+        // 保存用户的回调,当对应的事件发生时触发
+        $this->_onWorkerStop = $this->onWorkerStop;
+        $this->onWorkerStop  = array($this, 'onWorkerStop');
+
+        if (!is_array($this->registerAddress)) {
+            $this->registerAddress = array($this->registerAddress);
+        }
+
+        // 记录进程启动的时间
+        $this->_startTime = time();
+        // 运行父方法
+        parent::run();
+    }
+
+    /**
+     * 当客户端发来数据时,转发给worker处理
+     *
+     * @param TcpConnection $connection
+     * @param mixed         $data
+     */
+    public function onClientMessage($connection, $data)
+    {
+        $connection->pingNotResponseCount = -1;
+        $this->sendToWorker(GatewayProtocol::CMD_ON_MESSAGE, $connection, $data);
+    }
+
+    /**
+     * 当客户端连接上来时,初始化一些客户端的数据
+     * 包括全局唯一的client_id、初始化session等
+     *
+     * @param TcpConnection $connection
+     */
+    public function onClientConnect($connection)
+    {
+        $connection->id = self::generateConnectionId();
+        // 保存该连接的内部通讯的数据包报头,避免每次重新初始化
+        $connection->gatewayHeader = array(
+            'local_ip'      => ip2long($this->lanIp),
+            'local_port'    => $this->lanPort,
+            'client_ip'     => ip2long($connection->getRemoteIp()),
+            'client_port'   => $connection->getRemotePort(),
+            'gateway_port'  => $this->_gatewayPort,
+            'connection_id' => $connection->id,
+            'flag'          => 0,
+        );
+        // 连接的 session
+        $connection->session                       = '';
+        // 该连接的心跳参数
+        $connection->pingNotResponseCount          = -1;
+        // 该链接发送缓冲区大小
+        $connection->maxSendBufferSize             = $this->sendToClientBufferSize;
+        // 保存客户端连接 connection 对象
+        $this->_clientConnections[$connection->id] = $connection;
+
+        // 如果用户有自定义 onConnect 回调,则执行
+        if ($this->_onConnect) {
+            call_user_func($this->_onConnect, $connection);
+            if (isset($connection->onWebSocketConnect)) {
+                $connection->_onWebSocketConnect = $connection->onWebSocketConnect;
+            }
+        }
+        if ($connection->protocol === '\Workerman\Protocols\Websocket' || $connection->protocol === 'Workerman\Protocols\Websocket') {
+            $connection->onWebSocketConnect = array($this, 'onWebsocketConnect');
+        }
+
+        $this->sendToWorker(GatewayProtocol::CMD_ON_CONNECT, $connection);
+    }
+
+    /**
+     * websocket握手时触发
+     *
+     * @param $connection
+     * @param $http_buffer
+     */
+    public function onWebsocketConnect($connection, $http_buffer)
+    {
+        if (isset($connection->_onWebSocketConnect)) {
+            call_user_func($connection->_onWebSocketConnect, $connection, $http_buffer);
+            unset($connection->_onWebSocketConnect);
+        }
+        $this->sendToWorker(GatewayProtocol::CMD_ON_WEBSOCKET_CONNECT, $connection, array('get' => $_GET, 'server' => $_SERVER, 'cookie' => $_COOKIE));
+    }
+    
+    /**
+     * 生成connection id
+     * @return int
+     */
+    protected function generateConnectionId()
+    {
+        $max_unsigned_int = 4294967295;
+        if (self::$_connectionIdRecorder >= $max_unsigned_int) {
+            self::$_connectionIdRecorder = 0;
+        }
+        while(++self::$_connectionIdRecorder <= $max_unsigned_int) {
+            if(!isset($this->_clientConnections[self::$_connectionIdRecorder])) {
+                break;
+            }
+        }
+        return self::$_connectionIdRecorder;
+    }
+
+    /**
+     * 发送数据给 worker 进程
+     *
+     * @param int           $cmd
+     * @param TcpConnection $connection
+     * @param mixed         $body
+     * @return bool
+     */
+    protected function sendToWorker($cmd, $connection, $body = '')
+    {
+        $gateway_data             = $connection->gatewayHeader;
+        $gateway_data['cmd']      = $cmd;
+        $gateway_data['body']     = $body;
+        $gateway_data['ext_data'] = $connection->session;
+        if ($this->_workerConnections) {
+            // 调用路由函数,选择一个worker把请求转发给它
+            /** @var TcpConnection $worker_connection */
+            $worker_connection = call_user_func($this->router, $this->_workerConnections, $connection, $cmd, $body);
+            if (false === $worker_connection->send($gateway_data)) {
+                $msg = "SendBufferToWorker fail. May be the send buffer are overflow. See http://doc2.workerman.net/send-buffer-overflow.html";
+                static::log($msg);
+                return false;
+            }
+        } // 没有可用的 worker
+        else {
+            // gateway 启动后 1-2 秒内 SendBufferToWorker fail 是正常现象,因为与 worker 的连接还没建立起来,
+            // 所以不记录日志,只是关闭连接
+            $time_diff = 2;
+            if (time() - $this->_startTime >= $time_diff) {
+                $msg = 'SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://doc2.workerman.net/send-buffer-to-worker-fail.html';
+                static::log($msg);
+            }
+            $connection->destroy();
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 随机路由,返回 worker connection 对象
+     *
+     * @param array         $worker_connections
+     * @param TcpConnection $client_connection
+     * @param int           $cmd
+     * @param mixed         $buffer
+     * @return TcpConnection
+     */
+    public static function routerRand($worker_connections, $client_connection, $cmd, $buffer)
+    {
+        return $worker_connections[array_rand($worker_connections)];
+    }
+
+    /**
+     * client_id 与 worker 绑定
+     *
+     * @param array         $worker_connections
+     * @param TcpConnection $client_connection
+     * @param int           $cmd
+     * @param mixed         $buffer
+     * @return TcpConnection
+     */
+    public static function routerBind($worker_connections, $client_connection, $cmd, $buffer)
+    {
+        if (!isset($client_connection->businessworker_address) || !isset($worker_connections[$client_connection->businessworker_address])) {
+            $client_connection->businessworker_address = array_rand($worker_connections);
+        }
+        return $worker_connections[$client_connection->businessworker_address];
+    }
+
+    /**
+     * 当客户端关闭时
+     *
+     * @param TcpConnection $connection
+     */
+    public function onClientClose($connection)
+    {
+        // 尝试通知 worker,触发 Event::onClose
+        $this->sendToWorker(GatewayProtocol::CMD_ON_CLOSE, $connection);
+        unset($this->_clientConnections[$connection->id]);
+        // 清理 uid 数据
+        if (!empty($connection->uid)) {
+            $uid = $connection->uid;
+            unset($this->_uidConnections[$uid][$connection->id]);
+            if (empty($this->_uidConnections[$uid])) {
+                unset($this->_uidConnections[$uid]);
+            }
+        }
+        // 清理 group 数据
+        if (!empty($connection->groups)) {
+            foreach ($connection->groups as $group) {
+                unset($this->_groupConnections[$group][$connection->id]);
+                if (empty($this->_groupConnections[$group])) {
+                    unset($this->_groupConnections[$group]);
+                }
+            }
+        }
+        // 触发 onClose
+        if ($this->_onClose) {
+            call_user_func($this->_onClose, $connection);
+        }
+    }
+
+    /**
+     * 当 Gateway 启动的时候触发的回调函数
+     *
+     * @return void
+     */
+    public function onWorkerStart()
+    {
+        // 分配一个内部通讯端口
+        $this->lanPort = $this->startPort + $this->id;
+
+        // 如果有设置心跳,则定时执行
+        if ($this->pingInterval > 0) {
+            $timer_interval = $this->pingNotResponseLimit > 0 ? $this->pingInterval / 2 : $this->pingInterval;
+            Timer::add($timer_interval, array($this, 'ping'));
+        }
+
+        // 如果BusinessWorker ip不是127.0.0.1,则需要加gateway到BusinessWorker的心跳
+        if ($this->lanIp !== '127.0.0.1') {
+            Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, array($this, 'pingBusinessWorker'));
+        }
+
+        if (!class_exists('\Protocols\GatewayProtocol')) {
+            class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol');
+        }
+
+        // 初始化 gateway 内部的监听,用于监听 worker 的连接已经连接上发来的数据
+        $this->_innerTcpWorker = new Worker("GatewayProtocol://{$this->lanIp}:{$this->lanPort}");
+        $this->_innerTcpWorker->reusePort = false;
+        $this->_innerTcpWorker->listen();
+        $this->_innerTcpWorker->name = 'GatewayInnerWorker';
+
+        // 重新设置自动加载根目录
+        Autoloader::setRootPath($this->_autoloadRootPath);
+
+        // 设置内部监听的相关回调
+        $this->_innerTcpWorker->onMessage = array($this, 'onWorkerMessage');
+
+        $this->_innerTcpWorker->onConnect = array($this, 'onWorkerConnect');
+        $this->_innerTcpWorker->onClose   = array($this, 'onWorkerClose');
+
+        // 注册 gateway 的内部通讯地址,worker 去连这个地址,以便 gateway 与 worker 之间建立起 TCP 长连接
+        $this->registerAddress();
+
+        if ($this->_onWorkerStart) {
+            call_user_func($this->_onWorkerStart, $this);
+        }
+    }
+
+
+    /**
+     * 当 worker 通过内部通讯端口连接到 gateway 时
+     *
+     * @param TcpConnection $connection
+     */
+    public function onWorkerConnect($connection)
+    {
+        $connection->maxSendBufferSize = $this->sendToWorkerBufferSize;
+        $connection->authorized = $this->secretKey ? false : true;
+    }
+
+    /**
+     * 当 worker 发来数据时
+     *
+     * @param TcpConnection $connection
+     * @param mixed         $data
+     * @throws \Exception
+     *
+     * @return void
+     */
+    public function onWorkerMessage($connection, $data)
+    {
+        $cmd = $data['cmd'];
+        if (empty($connection->authorized) && $cmd !== GatewayProtocol::CMD_WORKER_CONNECT && $cmd !== GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT) {
+            self::log("Unauthorized request from " . $connection->getRemoteIp() . ":" . $connection->getRemotePort());
+            $connection->close();
+            return;
+        }
+        switch ($cmd) {
+            // BusinessWorker连接Gateway
+            case GatewayProtocol::CMD_WORKER_CONNECT:
+                $worker_info = json_decode($data['body'], true);
+                if ($worker_info['secret_key'] !== $this->secretKey) {
+                    self::log("Gateway: Worker key does not match ".var_export($this->secretKey, true)." !== ". var_export($this->secretKey));
+                    $connection->close();
+                    return;
+                }
+                $key = $connection->getRemoteIp() . ':' . $worker_info['worker_key'];
+                // 在一台服务器上businessWorker->name不能相同
+                if (isset($this->_workerConnections[$key])) {
+                    self::log("Gateway: Worker->name conflict. Key:{$key}");
+		            $connection->close();
+                    return;
+                }
+		        $connection->key = $key;
+                $this->_workerConnections[$key] = $connection;
+                $connection->authorized = true;
+                return;
+            // GatewayClient连接Gateway
+            case GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT:
+                $worker_info = json_decode($data['body'], true);
+                if ($worker_info['secret_key'] !== $this->secretKey) {
+                    self::log("Gateway: GatewayClient key does not match ".var_export($this->secretKey, true)." !== ".var_export($this->secretKey, true));
+                    $connection->close();
+                    return;
+                }
+                $connection->authorized = true;
+                return;
+            // 向某客户端发送数据,Gateway::sendToClient($client_id, $message);
+            case GatewayProtocol::CMD_SEND_TO_ONE:
+                if (isset($this->_clientConnections[$data['connection_id']])) {
+                    $this->_clientConnections[$data['connection_id']]->send($data['body']);
+                }
+                return;
+            // 踢出用户,Gateway::closeClient($client_id, $message);
+            case GatewayProtocol::CMD_KICK:
+                if (isset($this->_clientConnections[$data['connection_id']])) {
+                    $this->_clientConnections[$data['connection_id']]->close($data['body']);
+                }
+                return;
+            // 立即销毁用户连接, Gateway::destroyClient($client_id);
+            case GatewayProtocol::CMD_DESTROY:
+                if (isset($this->_clientConnections[$data['connection_id']])) {
+                    $this->_clientConnections[$data['connection_id']]->destroy();
+                }
+                return;
+            // 广播, Gateway::sendToAll($message, $client_id_array)
+            case GatewayProtocol::CMD_SEND_TO_ALL:
+                $raw = (bool)($data['flag'] & GatewayProtocol::FLAG_NOT_CALL_ENCODE);
+                $body = $data['body'];
+                if (!$raw && $this->protocolAccelerate && $this->protocol) {
+                    $body = $this->preEncodeForClient($body);
+                    $raw = true;
+                }
+                $ext_data = $data['ext_data'] ? json_decode($data['ext_data'], true) : '';
+                // $client_id_array 不为空时,只广播给 $client_id_array 指定的客户端
+                if (isset($ext_data['connections'])) {
+                    foreach ($ext_data['connections'] as $connection_id) {
+                        if (isset($this->_clientConnections[$connection_id])) {
+                            $this->_clientConnections[$connection_id]->send($body, $raw);
+                        }
+                    }
+                } // $client_id_array 为空时,广播给所有在线客户端
+                else {
+                    $exclude_connection_id = !empty($ext_data['exclude']) ? $ext_data['exclude'] : null;
+                    foreach ($this->_clientConnections as $client_connection) {
+                        if (!isset($exclude_connection_id[$client_connection->id])) {
+                            $client_connection->send($body, $raw);
+                        }
+                    }
+                }
+                return;
+            case GatewayProtocol::CMD_SELECT:
+                $client_info_array = array();
+                $ext_data = json_decode($data['ext_data'], true);
+                if (!$ext_data) {
+                    echo 'CMD_SELECT ext_data=' . var_export($data['ext_data'], true) . '\r\n';
+                    $buffer = serialize($client_info_array);
+                    $connection->send(pack('N', strlen($buffer)) . $buffer, true);
+                    return;
+                }
+                $fields = $ext_data['fields'];
+                $where  = $ext_data['where'];
+                if ($where) {
+                    $connection_box_map = array(
+                        'groups'        => $this->_groupConnections,
+                        'uid'           => $this->_uidConnections
+                    );
+                    // $where = ['groups'=>[x,x..], 'uid'=>[x,x..], 'connection_id'=>[x,x..]]
+                    foreach ($where as $key => $items) {
+                        if ($key !== 'connection_id') {
+                            $connections_box = $connection_box_map[$key];
+                            foreach ($items as $item) {
+                                if (isset($connections_box[$item])) {
+                                    foreach ($connections_box[$item] as $connection_id => $client_connection) {
+                                        if (!isset($client_info_array[$connection_id])) {
+                                            $client_info_array[$connection_id] = array();
+                                            // $fields = ['groups', 'uid', 'session']
+                                            foreach ($fields as $field) {
+                                                $client_info_array[$connection_id][$field] = isset($client_connection->$field) ? $client_connection->$field : null;
+                                            }
+                                        }
+                                    }
+
+                                }
+                            }
+                        } else {
+                            foreach ($items as $connection_id) {
+                                if (isset($this->_clientConnections[$connection_id])) {
+                                    $client_connection = $this->_clientConnections[$connection_id];
+                                    $client_info_array[$connection_id] = array();
+                                    // $fields = ['groups', 'uid', 'session']
+                                    foreach ($fields as $field) {
+                                        $client_info_array[$connection_id][$field] = isset($client_connection->$field) ? $client_connection->$field : null;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    foreach ($this->_clientConnections as $connection_id => $client_connection) {
+                        foreach ($fields as $field) {
+                            $client_info_array[$connection_id][$field] = isset($client_connection->$field) ? $client_connection->$field : null;
+                        }
+                    }
+                }
+                $buffer = serialize($client_info_array);
+                $connection->send(pack('N', strlen($buffer)) . $buffer, true);
+                return;
+            // 获取在线群组列表
+            case GatewayProtocol::CMD_GET_GROUP_ID_LIST:
+                $buffer = serialize(array_keys($this->_groupConnections));
+                $connection->send(pack('N', strlen($buffer)) . $buffer, true);
+                return;
+            // 重新赋值 session
+            case GatewayProtocol::CMD_SET_SESSION:
+                if (isset($this->_clientConnections[$data['connection_id']])) {
+                    $this->_clientConnections[$data['connection_id']]->session = $data['ext_data'];
+                }
+                return;
+            // session合并
+            case GatewayProtocol::CMD_UPDATE_SESSION:
+                if (!isset($this->_clientConnections[$data['connection_id']])) {
+                    return;
+                } else {
+                    if (!$this->_clientConnections[$data['connection_id']]->session) {
+                        $this->_clientConnections[$data['connection_id']]->session = $data['ext_data'];
+                        return;
+                    }
+                    $session = Context::sessionDecode($this->_clientConnections[$data['connection_id']]->session);
+                    $session_for_merge = Context::sessionDecode($data['ext_data']);
+                    $session = array_replace_recursive($session, $session_for_merge);
+                    $this->_clientConnections[$data['connection_id']]->session = Context::sessionEncode($session);
+                }
+                return;
+            case GatewayProtocol::CMD_GET_SESSION_BY_CLIENT_ID:
+                if (!isset($this->_clientConnections[$data['connection_id']])) {
+                    $session = serialize(null);
+                } else {
+                    if (!$this->_clientConnections[$data['connection_id']]->session) {
+                        $session = serialize(array());
+                    } else {
+                        $session = $this->_clientConnections[$data['connection_id']]->session;
+                    }
+                }
+                $connection->send(pack('N', strlen($session)) . $session, true);
+                return;
+            // 获得客户端sessions
+            case GatewayProtocol::CMD_GET_ALL_CLIENT_SESSIONS:
+                $client_info_array = array();
+                foreach ($this->_clientConnections as $connection_id => $client_connection) {
+                    $client_info_array[$connection_id] = $client_connection->session;
+                }
+                $buffer = serialize($client_info_array);
+                $connection->send(pack('N', strlen($buffer)) . $buffer, true);
+                return;
+            // 判断某个 client_id 是否在线 Gateway::isOnline($client_id)
+            case GatewayProtocol::CMD_IS_ONLINE:
+                $buffer = serialize((int)isset($this->_clientConnections[$data['connection_id']]));
+                $connection->send(pack('N', strlen($buffer)) . $buffer, true);
+                return;
+            // 将 client_id 与 uid 绑定
+            case GatewayProtocol::CMD_BIND_UID:
+                $uid = $data['ext_data'];
+                if (empty($uid)) {
+                    echo "bindUid(client_id, uid) uid empty, uid=" . var_export($uid, true);
+                    return;
+                }
+                $connection_id = $data['connection_id'];
+                if (!isset($this->_clientConnections[$connection_id])) {
+                    return;
+                }
+                $client_connection = $this->_clientConnections[$connection_id];
+                if (isset($client_connection->uid)) {
+                    $current_uid = $client_connection->uid;
+                    unset($this->_uidConnections[$current_uid][$connection_id]);
+                    if (empty($this->_uidConnections[$current_uid])) {
+                        unset($this->_uidConnections[$current_uid]);
+                    }
+                }
+                $client_connection->uid                      = $uid;
+                $this->_uidConnections[$uid][$connection_id] = $client_connection;
+                return;
+            // client_id 与 uid 解绑 Gateway::unbindUid($client_id, $uid);
+            case GatewayProtocol::CMD_UNBIND_UID:
+                $connection_id = $data['connection_id'];
+                if (!isset($this->_clientConnections[$connection_id])) {
+                    return;
+                }
+                $client_connection = $this->_clientConnections[$connection_id];
+                if (isset($client_connection->uid)) {
+                    $current_uid = $client_connection->uid;
+                    unset($this->_uidConnections[$current_uid][$connection_id]);
+                    if (empty($this->_uidConnections[$current_uid])) {
+                        unset($this->_uidConnections[$current_uid]);
+                    }
+                    $client_connection->uid_info = '';
+                    $client_connection->uid      = null;
+                }
+                return;
+            // 发送数据给 uid Gateway::sendToUid($uid, $msg);
+            case GatewayProtocol::CMD_SEND_TO_UID:
+                $uid_array = json_decode($data['ext_data'], true);
+                foreach ($uid_array as $uid) {
+                    if (!empty($this->_uidConnections[$uid])) {
+                        foreach ($this->_uidConnections[$uid] as $connection) {
+                            /** @var TcpConnection $connection */
+                            $connection->send($data['body']);
+                        }
+                    }
+                }
+                return;
+            // 将 $client_id 加入用户组 Gateway::joinGroup($client_id, $group);
+            case GatewayProtocol::CMD_JOIN_GROUP:
+                $group = $data['ext_data'];
+                if (empty($group)) {
+                    echo "join(group) group empty, group=" . var_export($group, true);
+                    return;
+                }
+                $connection_id = $data['connection_id'];
+                if (!isset($this->_clientConnections[$connection_id])) {
+                    return;
+                }
+                $client_connection = $this->_clientConnections[$connection_id];
+                if (!isset($client_connection->groups)) {
+                    $client_connection->groups = array();
+                }
+                $client_connection->groups[$group]               = $group;
+                $this->_groupConnections[$group][$connection_id] = $client_connection;
+                return;
+            // 将 $client_id 从某个用户组中移除 Gateway::leaveGroup($client_id, $group);
+            case GatewayProtocol::CMD_LEAVE_GROUP:
+                $group = $data['ext_data'];
+                if (empty($group)) {
+                    echo "leave(group) group empty, group=" . var_export($group, true);
+                    return;
+                }
+                $connection_id = $data['connection_id'];
+                if (!isset($this->_clientConnections[$connection_id])) {
+                    return;
+                }
+                $client_connection = $this->_clientConnections[$connection_id];
+                if (!isset($client_connection->groups[$group])) {
+                    return;
+                }
+                unset($client_connection->groups[$group], $this->_groupConnections[$group][$connection_id]);
+                if (empty($this->_groupConnections[$group])) {
+                    unset($this->_groupConnections[$group]);
+                }
+                return;
+            // 解散分组
+            case GatewayProtocol::CMD_UNGROUP:
+                $group = $data['ext_data'];
+                if (empty($group)) {
+                    echo "leave(group) group empty, group=" . var_export($group, true);
+                    return;
+                }
+                if (empty($this->_groupConnections[$group])) {
+                    return;
+                }
+                foreach ($this->_groupConnections[$group] as $client_connection) {
+                    unset($client_connection->groups[$group]);
+                }
+                unset($this->_groupConnections[$group]);
+                return;
+            // 向某个用户组发送消息 Gateway::sendToGroup($group, $msg);
+            case GatewayProtocol::CMD_SEND_TO_GROUP:
+                $raw = (bool)($data['flag'] & GatewayProtocol::FLAG_NOT_CALL_ENCODE);
+                $body = $data['body'];
+                if (!$raw && $this->protocolAccelerate && $this->protocol) {
+                    $body = $this->preEncodeForClient($body);
+                    $raw = true;
+                }
+                $ext_data = json_decode($data['ext_data'], true);
+                $group_array = $ext_data['group'];
+                $exclude_connection_id = $ext_data['exclude'];
+
+                foreach ($group_array as $group) {
+                    if (!empty($this->_groupConnections[$group])) {
+                        foreach ($this->_groupConnections[$group] as $connection) {
+                            if(!isset($exclude_connection_id[$connection->id]))
+                            {
+                                /** @var TcpConnection $connection */
+                                $connection->send($body, $raw);
+                            }
+                        }
+                    }
+                }
+                return;
+            // 获取某用户组成员信息 Gateway::getClientSessionsByGroup($group);
+            case GatewayProtocol::CMD_GET_CLIENT_SESSIONS_BY_GROUP:
+                $group = $data['ext_data'];
+                if (!isset($this->_groupConnections[$group])) {
+                    $buffer = serialize(array());
+                    $connection->send(pack('N', strlen($buffer)) . $buffer, true);
+                    return;
+                }
+                $client_info_array = array();
+                foreach ($this->_groupConnections[$group] as $connection_id => $client_connection) {
+                    $client_info_array[$connection_id] = $client_connection->session;
+                }
+                $buffer = serialize($client_info_array);
+                $connection->send(pack('N', strlen($buffer)) . $buffer, true);
+                return;
+            // 获取用户组成员数 Gateway::getClientCountByGroup($group);
+            case GatewayProtocol::CMD_GET_CLIENT_COUNT_BY_GROUP:
+                $group = $data['ext_data'];
+                $count = 0;
+                if ($group !== '') {
+                    if (isset($this->_groupConnections[$group])) {
+                        $count = count($this->_groupConnections[$group]);
+                    }
+                } else {
+                    $count = count($this->_clientConnections);
+                }
+                $buffer = serialize($count);
+                $connection->send(pack('N', strlen($buffer)) . $buffer, true);
+                return;
+            // 获取与某个 uid 绑定的所有 client_id Gateway::getClientIdByUid($uid);
+            case GatewayProtocol::CMD_GET_CLIENT_ID_BY_UID:
+                $uid = $data['ext_data'];
+                if (empty($this->_uidConnections[$uid])) {
+                    $buffer = serialize(array());
+                } else {
+                    $buffer = serialize(array_keys($this->_uidConnections[$uid]));
+                }
+                $connection->send(pack('N', strlen($buffer)) . $buffer, true);
+                return;
+            default :
+                $err_msg = "gateway inner pack err cmd=$cmd";
+                echo $err_msg;
+        }
+    }
+
+
+    /**
+     * 当worker连接关闭时
+     *
+     * @param TcpConnection $connection
+     */
+    public function onWorkerClose($connection)
+    {
+        if (isset($connection->key)) {
+            unset($this->_workerConnections[$connection->key]);
+        }
+    }
+
+    /**
+     * 存储当前 Gateway 的内部通信地址
+     *
+     * @return bool
+     */
+    public function registerAddress()
+    {
+        $address = $this->lanIp . ':' . $this->lanPort;
+        foreach ($this->registerAddress as $register_address) {
+            $register_connection = new AsyncTcpConnection("text://{$register_address}");
+            $secret_key = $this->secretKey;
+            $register_connection->onConnect = function($register_connection) use ($address, $secret_key, $register_address){
+                $register_connection->send('{"event":"gateway_connect", "address":"' . $address . '", "secret_key":"' . $secret_key . '"}');
+                // 如果Register服务器不在本地服务器,则需要保持心跳
+                if (strpos($register_address, '127.0.0.1') !== 0) {
+                    $register_connection->ping_timer = Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, function () use ($register_connection) {
+                        $register_connection->send('{"event":"ping"}');
+                    });
+                }
+            };
+            $register_connection->onClose = function ($register_connection) {
+                if(!empty($register_connection->ping_timer)) {
+                    Timer::del($register_connection->ping_timer);
+                }
+                $register_connection->reconnect(1);
+            };
+            $register_connection->connect();
+        }
+    }
+
+
+    /**
+     * 心跳逻辑
+     *
+     * @return void
+     */
+    public function ping()
+    {
+        $ping_data = $this->pingData ? (string)$this->pingData : null;
+        $raw = false;
+        if ($this->protocolAccelerate && $ping_data && $this->protocol) {
+            $ping_data = $this->preEncodeForClient($ping_data);
+            $raw = true;
+        }
+        // 遍历所有客户端连接
+        foreach ($this->_clientConnections as $connection) {
+            // 上次发送的心跳还没有回复次数大于限定值就断开
+            if ($this->pingNotResponseLimit > 0 &&
+                $connection->pingNotResponseCount >= $this->pingNotResponseLimit * 2
+            ) {
+                $connection->destroy();
+                continue;
+            }
+            // $connection->pingNotResponseCount 为 -1 说明最近客户端有发来消息,则不给客户端发送心跳
+            $connection->pingNotResponseCount++;
+            if ($ping_data) {
+                if ($connection->pingNotResponseCount === 0 ||
+                    ($this->pingNotResponseLimit > 0 && $connection->pingNotResponseCount % 2 === 1)
+                ) {
+                    continue;
+                }
+                $connection->send($ping_data, $raw);
+            }
+        }
+    }
+
+    /**
+     * 向 BusinessWorker 发送心跳数据,用于保持长连接
+     *
+     * @return void
+     */
+    public function pingBusinessWorker()
+    {
+        $gateway_data        = GatewayProtocol::$empty;
+        $gateway_data['cmd'] = GatewayProtocol::CMD_PING;
+        foreach ($this->_workerConnections as $connection) {
+            $connection->send($gateway_data);
+        }
+    }
+
+    /**
+     * @param mixed $data
+     *
+     * @return string
+     */
+    protected function preEncodeForClient($data)
+    {
+        foreach ($this->_clientConnections as $client_connection) {
+            return call_user_func(array($client_connection->protocol, 'encode'), $data, $client_connection);
+        }
+    }
+
+    /**
+     * 当 gateway 关闭时触发,清理数据
+     *
+     * @return void
+     */
+    public function onWorkerStop()
+    {
+        // 尝试触发用户设置的回调
+        if ($this->_onWorkerStop) {
+            call_user_func($this->_onWorkerStop, $this);
+        }
+    }
+
+    /**
+     * Log.
+     * @param string $msg
+     */
+    public static function log($msg){
+        Timer::add(1, function() use ($msg) {
+            Worker::log($msg);
+        }, null, false);
+    }
+}

+ 136 - 0
vendor/workerman/gateway-worker/src/Lib/Context.php

@@ -0,0 +1,136 @@
+<?php
+/**
+ * This file is part of workerman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace GatewayWorker\Lib;
+
+use Exception;
+
+/**
+ * 上下文 包含当前用户 uid, 内部通信 local_ip local_port socket_id,以及客户端 client_ip client_port
+ */
+class Context
+{
+    /**
+     * 内部通讯 id
+     *
+     * @var string
+     */
+    public static $local_ip;
+
+    /**
+     * 内部通讯端口
+     *
+     * @var int
+     */
+    public static $local_port;
+
+    /**
+     * 客户端 ip
+     *
+     * @var string
+     */
+    public static $client_ip;
+
+    /**
+     * 客户端端口
+     *
+     * @var int
+     */
+    public static $client_port;
+
+    /**
+     * client_id
+     *
+     * @var string
+     */
+    public static $client_id;
+
+    /**
+     * 连接 connection->id
+     *
+     * @var int
+     */
+    public static $connection_id;
+    
+    /**
+     * 旧的session
+     * 
+     * @var string
+     */
+    public static $old_session;
+
+    /**
+     * 编码 session
+     *
+     * @param mixed $session_data
+     * @return string
+     */
+    public static function sessionEncode($session_data = '')
+    {
+        if ($session_data !== '') {
+            return serialize($session_data);
+        }
+        return '';
+    }
+
+    /**
+     * 解码 session
+     *
+     * @param string $session_buffer
+     * @return mixed
+     */
+    public static function sessionDecode($session_buffer)
+    {
+        return unserialize($session_buffer);
+    }
+
+    /**
+     * 清除上下文
+     *
+     * @return void
+     */
+    public static function clear()
+    {
+        self::$local_ip = self::$local_port = self::$client_ip = self::$client_port =
+        self::$client_id = self::$connection_id  = self::$old_session = null;
+    }
+
+    /**
+     * 通讯地址到 client_id 的转换
+     *
+     * @param int $local_ip
+     * @param int $local_port
+     * @param int $connection_id
+     * @return string
+     */
+    public static function addressToClientId($local_ip, $local_port, $connection_id)
+    {
+        return bin2hex(pack('NnN', $local_ip, $local_port, $connection_id));
+    }
+
+    /**
+     * client_id 到通讯地址的转换
+     *
+     * @param string $client_id
+     * @return array
+     * @throws Exception
+     */
+    public static function clientIdToAddress($client_id)
+    {
+        if (strlen($client_id) !== 20) {
+            echo new Exception("client_id $client_id is invalid");
+            return false;
+        }
+        return unpack('Nlocal_ip/nlocal_port/Nconnection_id', pack('H*', $client_id));
+    }
+}

+ 76 - 0
vendor/workerman/gateway-worker/src/Lib/Db.php

@@ -0,0 +1,76 @@
+<?php
+/**
+ * This file is part of workerman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace GatewayWorker\Lib;
+
+use Config\Db as DbConfig;
+use Exception;
+
+/**
+ * 数据库类
+ */
+class Db
+{
+    /**
+     * 实例数组
+     *
+     * @var array
+     */
+    protected static $instance = array();
+
+    /**
+     * 获取实例
+     *
+     * @param string $config_name
+     * @return DbConnection
+     * @throws Exception
+     */
+    public static function instance($config_name)
+    {
+        if (!isset(DbConfig::$$config_name)) {
+            echo "\\Config\\Db::$config_name not set\n";
+            throw new Exception("\\Config\\Db::$config_name not set\n");
+        }
+
+        if (empty(self::$instance[$config_name])) {
+            $config                       = DbConfig::$$config_name;
+            self::$instance[$config_name] = new DbConnection($config['host'], $config['port'],
+                $config['user'], $config['password'], $config['dbname'],$config['charset']);
+        }
+        return self::$instance[$config_name];
+    }
+
+    /**
+     * 关闭数据库实例
+     *
+     * @param string $config_name
+     */
+    public static function close($config_name)
+    {
+        if (isset(self::$instance[$config_name])) {
+            self::$instance[$config_name]->closeConnection();
+            self::$instance[$config_name] = null;
+        }
+    }
+
+    /**
+     * 关闭所有数据库实例
+     */
+    public static function closeAll()
+    {
+        foreach (self::$instance as $connection) {
+            $connection->closeConnection();
+        }
+        self::$instance = array();
+    }
+}

+ 1977 - 0
vendor/workerman/gateway-worker/src/Lib/DbConnection.php

@@ -0,0 +1,1977 @@
+<?php
+namespace GatewayWorker\Lib;
+
+use Exception;
+use PDO;
+use PDOException;
+
+/**
+ * 数据库连接类,依赖 PDO_MYSQL 扩展
+ * 在 https://github.com/auraphp/Aura.SqlQuery 的基础上修改而成
+ */
+class DbConnection
+{
+    /**
+     * SELECT
+     *
+     * @var array
+     */
+    protected $union = array();
+
+    /**
+     * 是否是更新
+     *
+     * @var bool
+     */
+    protected $for_update = false;
+
+    /**
+     * 选择的列
+     *
+     * @var array
+     */
+    protected $cols = array();
+
+    /**
+     * 从哪些表里面 SELECT
+     *
+     * @var array
+     */
+    protected $from = array();
+
+    /**
+     * $from 当前的 key
+     *
+     * @var int
+     */
+    protected $from_key = -1;
+
+    /**
+     * GROUP BY 的列
+     *
+     * @var array
+     */
+    protected $group_by = array();
+
+    /**
+     * HAVING 条件数组.
+     *
+     * @var array
+     */
+    protected $having = array();
+
+    /**
+     * HAVING 语句中绑定的值.
+     *
+     * @var array
+     */
+    protected $bind_having = array();
+
+    /**
+     * 每页多少条记录
+     *
+     * @var int
+     */
+    protected $paging = 10;
+
+    /**
+     * sql 中绑定的值
+     *
+     * @var array
+     */
+    protected $bind_values = array();
+
+    /**
+     * WHERE 条件.
+     *
+     * @var array
+     */
+    protected $where = array();
+
+    /**
+     * WHERE 语句绑定的值
+     *
+     * @var array
+     */
+    protected $bind_where = array();
+
+    /**
+     * ORDER BY 的列
+     *
+     * @var array
+     */
+    protected $order_by = array();
+
+    /**
+     * ORDER BY 的排序方式,默认为升序
+     *
+     * @var bool
+     */
+    protected $order_asc = true;
+    /**
+     * SELECT 多少记录
+     *
+     * @var int
+     */
+    protected $limit = 0;
+
+    /**
+     * 返回记录的游标
+     *
+     * @var int
+     */
+    protected $offset = 0;
+
+    /**
+     * flags 列表
+     *
+     * @var array
+     */
+    protected $flags = array();
+
+    /**
+     * 操作哪个表
+     *
+     * @var string
+     */
+    protected $table;
+
+    /**
+     * 表.列 和 last-insert-id 映射
+     *
+     * @var array
+     */
+    protected $last_insert_id_names = array();
+
+    /**
+     * INSERT 或者 UPDATE 的列
+     *
+     * @param array
+     */
+    protected $col_values;
+
+    /**
+     * 返回的列
+     *
+     * @var array
+     */
+    protected $returning = array();
+
+    /**
+     * sql 的类型 SELECT INSERT DELETE UPDATE
+     *
+     * @var string
+     */
+    protected $type = '';
+
+    /**
+     * pdo 实例
+     *
+     * @var PDO
+     */
+    protected $pdo;
+
+    /**
+     * PDOStatement 实例
+     *
+     * @var \PDOStatement
+     */
+    protected $sQuery;
+
+    /**
+     * 数据库用户名密码等配置
+     *
+     * @var array
+     */
+    protected $settings = array();
+
+    /**
+     * sql 的参数
+     *
+     * @var array
+     */
+    protected $parameters = array();
+
+    /**
+     * 最后一条直行的 sql
+     *
+     * @var string
+     */
+    protected $lastSql = '';
+
+    /**
+     * 是否执行成功
+     *
+     * @var bool
+     */
+    protected $success = false;
+
+    /**
+     * 选择哪些列
+     *
+     * @param string|array $cols
+     * @return self
+     */
+    public function select($cols = '*')
+    {
+        $this->type = 'SELECT';
+        if (!is_array($cols)) {
+            $cols = array($cols);
+        }
+        $this->cols($cols);
+        return $this;
+    }
+
+    /**
+     * 从哪个表删除
+     *
+     * @param string $table
+     * @return self
+     */
+    public function delete($table)
+    {
+        $this->type  = 'DELETE';
+        $this->table = $this->quoteName($table);
+        $this->fromRaw($this->quoteName($table));
+        return $this;
+    }
+
+    /**
+     * 更新哪个表
+     *
+     * @param string $table
+     * @return self
+     */
+    public function update($table)
+    {
+        $this->type  = 'UPDATE';
+        $this->table = $this->quoteName($table);
+        return $this;
+    }
+
+    /**
+     * 向哪个表插入
+     *
+     * @param string $table
+     * @return self
+     */
+    public function insert($table)
+    {
+        $this->type  = 'INSERT';
+        $this->table = $this->quoteName($table);
+        return $this;
+    }
+
+    /**
+     *
+     * 设置 SQL_CALC_FOUND_ROWS 标记.
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function calcFoundRows($enable = true)
+    {
+        $this->setFlag('SQL_CALC_FOUND_ROWS', $enable);
+        return $this;
+    }
+
+    /**
+     * 设置 SQL_CACHE 标记
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function cache($enable = true)
+    {
+        $this->setFlag('SQL_CACHE', $enable);
+        return $this;
+    }
+
+    /**
+     * 设置 SQL_NO_CACHE 标记
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function noCache($enable = true)
+    {
+        $this->setFlag('SQL_NO_CACHE', $enable);
+        return $this;
+    }
+
+    /**
+     * 设置 STRAIGHT_JOIN 标记.
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function straightJoin($enable = true)
+    {
+        $this->setFlag('STRAIGHT_JOIN', $enable);
+        return $this;
+    }
+
+    /**
+     * 设置 HIGH_PRIORITY 标记
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function highPriority($enable = true)
+    {
+        $this->setFlag('HIGH_PRIORITY', $enable);
+        return $this;
+    }
+
+    /**
+     * 设置 SQL_SMALL_RESULT 标记
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function smallResult($enable = true)
+    {
+        $this->setFlag('SQL_SMALL_RESULT', $enable);
+        return $this;
+    }
+
+    /**
+     * 设置 SQL_BIG_RESULT 标记
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function bigResult($enable = true)
+    {
+        $this->setFlag('SQL_BIG_RESULT', $enable);
+        return $this;
+    }
+
+    /**
+     * 设置 SQL_BUFFER_RESULT 标记
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function bufferResult($enable = true)
+    {
+        $this->setFlag('SQL_BUFFER_RESULT', $enable);
+        return $this;
+    }
+
+    /**
+     * 设置 FOR UPDATE 标记
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function forUpdate($enable = true)
+    {
+        $this->for_update = (bool)$enable;
+        return $this;
+    }
+
+    /**
+     * 设置 DISTINCT 标记
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function distinct($enable = true)
+    {
+        $this->setFlag('DISTINCT', $enable);
+        return $this;
+    }
+
+    /**
+     * 设置 LOW_PRIORITY 标记
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function lowPriority($enable = true)
+    {
+        $this->setFlag('LOW_PRIORITY', $enable);
+        return $this;
+    }
+
+    /**
+     * 设置 IGNORE 标记
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function ignore($enable = true)
+    {
+        $this->setFlag('IGNORE', $enable);
+        return $this;
+    }
+
+    /**
+     * 设置 QUICK 标记
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function quick($enable = true)
+    {
+        $this->setFlag('QUICK', $enable);
+        return $this;
+    }
+
+    /**
+     * 设置 DELAYED 标记
+     *
+     * @param bool $enable
+     * @return self
+     */
+    public function delayed($enable = true)
+    {
+        $this->setFlag('DELAYED', $enable);
+        return $this;
+    }
+
+    /**
+     * 序列化
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        $union = '';
+        if ($this->union) {
+            $union = implode(' ', $this->union) . ' ';
+        }
+        return $union . $this->build();
+    }
+
+    /**
+     * 设置每页多少条记录
+     *
+     * @param int $paging
+     * @return self
+     */
+    public function setPaging($paging)
+    {
+        $this->paging = (int)$paging;
+        return $this;
+    }
+
+    /**
+     * 获取每页多少条记录
+     *
+     * @return int
+     */
+    public function getPaging()
+    {
+        return $this->paging;
+    }
+
+    /**
+     * 获取绑定在占位符上的值
+     */
+    public function getBindValues()
+    {
+        switch ($this->type) {
+            case 'SELECT':
+                return $this->getBindValuesSELECT();
+            case 'DELETE':
+            case 'UPDATE':
+            case 'INSERT':
+                return $this->getBindValuesCOMMON();
+            default :
+                throw new Exception("type err");
+        }
+    }
+
+    /**
+     * 获取绑定在占位符上的值
+     *
+     * @return array
+     */
+    public function getBindValuesSELECT()
+    {
+        $bind_values = $this->bind_values;
+        $i           = 1;
+        foreach ($this->bind_where as $val) {
+            $bind_values[$i] = $val;
+            $i++;
+        }
+        foreach ($this->bind_having as $val) {
+            $bind_values[$i] = $val;
+            $i++;
+        }
+        return $bind_values;
+    }
+
+    /**
+     *
+     * SELECT选择哪些列
+     *
+     * @param mixed  $key
+     * @param string $val
+     * @return void
+     */
+    protected function addColSELECT($key, $val)
+    {
+        if (is_string($key)) {
+            $this->cols[$val] = $key;
+        } else {
+            $this->addColWithAlias($val);
+        }
+    }
+
+    /**
+     * SELECT 增加选择的列
+     *
+     * @param string $spec
+     */
+    protected function addColWithAlias($spec)
+    {
+        $parts = explode(' ', $spec);
+        $count = count($parts);
+        if ($count == 2) {
+            $this->cols[$parts[1]] = $parts[0];
+        } elseif ($count == 3 && strtoupper($parts[1]) == 'AS') {
+            $this->cols[$parts[2]] = $parts[0];
+        } else {
+            $this->cols[] = $spec;
+        }
+    }
+
+    /**
+     * from 哪个表
+     *
+     * @param string $table
+     * @return self
+     */
+    public function from($table)
+    {
+        return $this->fromRaw($this->quoteName($table));
+    }
+
+    /**
+     * from的表
+     *
+     * @param string $table
+     * @return self
+     */
+    public function fromRaw($table)
+    {
+        $this->from[] = array($table);
+        $this->from_key++;
+        return $this;
+    }
+
+    /**
+     *
+     * 子查询
+     *
+     * @param string $table
+     * @param string $name The alias name for the sub-select.
+     * @return self
+     */
+    public function fromSubSelect($table, $name)
+    {
+        $this->from[] = array("($table) AS " . $this->quoteName($name));
+        $this->from_key++;
+        return $this;
+    }
+
+
+    /**
+     * 增加 join 语句
+     *
+     * @param string $table
+     * @param string $cond
+     * @param string $type
+     * @return self
+     * @throws Exception
+     */
+    public function join($table, $cond = null, $type = '')
+    {
+        return $this->joinInternal($type, $table, $cond);
+    }
+
+    /**
+     * 增加 join 语句
+     *
+     * @param string $join inner, left, natural
+     * @param string $table
+     * @param string $cond
+     * @return self
+     * @throws Exception
+     */
+    protected function joinInternal($join, $table, $cond = null)
+    {
+        if (!$this->from) {
+            throw new Exception('Cannot join() without from()');
+        }
+
+        $join                          = strtoupper(ltrim("$join JOIN"));
+        $table                         = $this->quoteName($table);
+        $cond                          = $this->fixJoinCondition($cond);
+        $this->from[$this->from_key][] = rtrim("$join $table $cond");
+        return $this;
+    }
+
+    /**
+     * quote
+     *
+     * @param string $cond
+     * @return string
+     *
+     */
+    protected function fixJoinCondition($cond)
+    {
+        if (!$cond) {
+            return '';
+        }
+
+        $cond = $this->quoteNamesIn($cond);
+
+        if (strtoupper(substr(ltrim($cond), 0, 3)) == 'ON ') {
+            return $cond;
+        }
+
+        if (strtoupper(substr(ltrim($cond), 0, 6)) == 'USING ') {
+            return $cond;
+        }
+
+        return 'ON ' . $cond;
+    }
+
+    /**
+     * inner join
+     *
+     * @param string $table
+     * @param string $cond
+     * @return self
+     * @throws Exception
+     */
+    public function innerJoin($table, $cond = null)
+    {
+        return $this->joinInternal('INNER', $table, $cond);
+    }
+
+    /**
+     * left join
+     *
+     * @param string $table
+     * @param string $cond
+     * @return self
+     * @throws Exception
+     */
+    public function leftJoin($table, $cond = null)
+    {
+        return $this->joinInternal('LEFT', $table, $cond);
+    }
+
+    /**
+     * right join
+     *
+     * @param string $table
+     * @param string $cond
+     * @return self
+     * @throws Exception
+     */
+    public function rightJoin($table, $cond = null)
+    {
+        return $this->joinInternal('RIGHT', $table, $cond);
+    }
+
+    /**
+     * joinSubSelect
+     *
+     * @param string $join inner, left, natural
+     * @param string $spec
+     * @param string $name sub-select 的别名
+     * @param string $cond
+     * @return self
+     * @throws Exception
+     */
+    public function joinSubSelect($join, $spec, $name, $cond = null)
+    {
+        if (!$this->from) {
+            throw new \Exception('Cannot join() without from() first.');
+        }
+
+        $join                          = strtoupper(ltrim("$join JOIN"));
+        $name                          = $this->quoteName($name);
+        $cond                          = $this->fixJoinCondition($cond);
+        $this->from[$this->from_key][] = rtrim("$join ($spec) AS $name $cond");
+        return $this;
+    }
+
+    /**
+     * group by 语句
+     *
+     * @param array $cols
+     * @return self
+     */
+    public function groupBy(array $cols)
+    {
+        foreach ($cols as $col) {
+            $this->group_by[] = $this->quoteNamesIn($col);
+        }
+        return $this;
+    }
+
+    /**
+     * having 语句
+     *
+     * @param string $cond
+     * @return self
+     */
+    public function having($cond)
+    {
+        $this->addClauseCondWithBind('having', 'AND', func_get_args());
+        return $this;
+    }
+
+    /**
+     * or having 语句
+     *
+     * @param string $cond The HAVING condition.
+     * @return self
+     */
+    public function orHaving($cond)
+    {
+        $this->addClauseCondWithBind('having', 'OR', func_get_args());
+        return $this;
+    }
+
+    /**
+     * 设置每页的记录数量
+     *
+     * @param int $page
+     * @return self
+     */
+    public function page($page)
+    {
+        $this->limit  = 0;
+        $this->offset = 0;
+
+        $page = (int)$page;
+        if ($page > 0) {
+            $this->limit  = $this->paging;
+            $this->offset = $this->paging * ($page - 1);
+        }
+        return $this;
+    }
+
+    /**
+     * union
+     *
+     * @return self
+     */
+    public function union()
+    {
+        $this->union[] = $this->build() . ' UNION';
+        $this->reset();
+        return $this;
+    }
+
+    /**
+     * unionAll
+     *
+     * @return self
+     */
+    public function unionAll()
+    {
+        $this->union[] = $this->build() . ' UNION ALL';
+        $this->reset();
+        return $this;
+    }
+
+    /**
+     * 重置
+     */
+    protected function reset()
+    {
+        $this->resetFlags();
+        $this->cols       = array();
+        $this->from       = array();
+        $this->from_key   = -1;
+        $this->where      = array();
+        $this->group_by   = array();
+        $this->having     = array();
+        $this->order_by   = array();
+        $this->limit      = 0;
+        $this->offset     = 0;
+        $this->for_update = false;
+    }
+
+    /**
+     * 清除所有数据
+     */
+    protected function resetAll()
+    {
+        $this->union                = array();
+        $this->for_update           = false;
+        $this->cols                 = array();
+        $this->from                 = array();
+        $this->from_key             = -1;
+        $this->group_by             = array();
+        $this->having               = array();
+        $this->bind_having          = array();
+        $this->paging               = 10;
+        $this->bind_values          = array();
+        $this->where                = array();
+        $this->bind_where           = array();
+        $this->order_by             = array();
+        $this->limit                = 0;
+        $this->offset               = 0;
+        $this->flags                = array();
+        $this->table                = '';
+        $this->last_insert_id_names = array();
+        $this->col_values           = array();
+        $this->returning            = array();
+        $this->parameters           = array();
+    }
+
+    /**
+     * 创建 SELECT SQL
+     *
+     * @return string
+     */
+    protected function buildSELECT()
+    {
+        return 'SELECT'
+        . $this->buildFlags()
+        . $this->buildCols()
+        . $this->buildFrom()
+        . $this->buildWhere()
+        . $this->buildGroupBy()
+        . $this->buildHaving()
+        . $this->buildOrderBy()
+        . $this->buildLimit()
+        . $this->buildForUpdate();
+    }
+
+    /**
+     * 创建 DELETE SQL
+     */
+    protected function buildDELETE()
+    {
+        return 'DELETE'
+        . $this->buildFlags()
+        . $this->buildFrom()
+        . $this->buildWhere()
+        . $this->buildOrderBy()
+        . $this->buildLimit()
+        . $this->buildReturning();
+    }
+
+    /**
+     * 生成 SELECT 列语句
+     *
+     * @return string
+     * @throws Exception
+     */
+    protected function buildCols()
+    {
+        if (!$this->cols) {
+            throw new Exception('No columns in the SELECT.');
+        }
+
+        $cols = array();
+        foreach ($this->cols as $key => $val) {
+            if (is_int($key)) {
+                $cols[] = $this->quoteNamesIn($val);
+            } else {
+                $cols[] = $this->quoteNamesIn("$val AS $key");
+            }
+        }
+
+        return $this->indentCsv($cols);
+    }
+
+    /**
+     * 生成 FROM 语句.
+     *
+     * @return string
+     */
+    protected function buildFrom()
+    {
+        if (!$this->from) {
+            return '';
+        }
+
+        $refs = array();
+        foreach ($this->from as $from) {
+            $refs[] = implode(' ', $from);
+        }
+        return ' FROM' . $this->indentCsv($refs);
+    }
+
+    /**
+     * 生成 GROUP BY 语句.
+     *
+     * @return string
+     */
+    protected function buildGroupBy()
+    {
+        if (!$this->group_by) {
+            return '';
+        }
+        return ' GROUP BY' . $this->indentCsv($this->group_by);
+    }
+
+    /**
+     * 生成 HAVING 语句.
+     *
+     * @return string
+     */
+    protected function buildHaving()
+    {
+        if (!$this->having) {
+            return '';
+        }
+        return ' HAVING' . $this->indent($this->having);
+    }
+
+    /**
+     * 生成 FOR UPDATE 语句
+     *
+     * @return string
+     */
+    protected function buildForUpdate()
+    {
+        if (!$this->for_update) {
+            return '';
+        }
+        return ' FOR UPDATE';
+    }
+
+    /**
+     * where
+     *
+     * @param string|array $cond
+     * @return self
+     */
+    public function where($cond)
+    {
+        if (is_array($cond)) {
+            foreach ($cond as $key => $val) {
+                if (is_string($key)) {
+                    $this->addWhere('AND', array($key, $val));
+                } else {
+                    $this->addWhere('AND', array($val));
+                }
+            }
+        } else {
+            $this->addWhere('AND', func_get_args());
+        }
+        return $this;
+    }
+
+    /**
+     * or where
+     *
+     * @param string|array $cond
+     * @return self
+     */
+    public function orWhere($cond)
+    {
+        if (is_array($cond)) {
+            foreach ($cond as $key => $val) {
+                if (is_string($key)) {
+                    $this->addWhere('OR', array($key, $val));
+                } else {
+                    $this->addWhere('OR', array($val));
+                }
+            }
+        } else {
+            $this->addWhere('OR', func_get_args());
+        }
+        return $this;
+    }
+
+    /**
+     * limit
+     *
+     * @param int $limit
+     * @return self
+     */
+    public function limit($limit)
+    {
+        $this->limit = (int)$limit;
+        return $this;
+    }
+
+    /**
+     * limit offset
+     *
+     * @param int $offset
+     * @return self
+     */
+    public function offset($offset)
+    {
+        $this->offset = (int)$offset;
+        return $this;
+    }
+
+    /**
+     * orderby.
+     *
+     * @param array $cols
+     * @return self
+     */
+    public function orderBy(array $cols)
+    {
+        return $this->addOrderBy($cols);
+    }
+
+    /**
+     * order by ASC OR DESC
+     *
+     * @param array $cols
+     * @param bool  $order_asc
+     * @return self
+     */
+    public function orderByASC(array $cols, $order_asc = true)
+    {
+        $this->order_asc = $order_asc;
+        return $this->addOrderBy($cols);
+    }
+
+    /**
+     * order by DESC
+     *
+     * @param array $cols
+     * @return self
+     */
+    public function orderByDESC(array $cols)
+    {
+        $this->order_asc = false;
+        return $this->addOrderBy($cols);
+    }
+
+    // -------------abstractquery----------
+    /**
+     * 返回逗号分隔的字符串
+     *
+     * @param array $list
+     * @return string
+     */
+    protected function indentCsv(array $list)
+    {
+        return ' ' . implode(',', $list);
+    }
+
+    /**
+     * 返回空格分隔的字符串
+     *
+     * @param array $list
+     * @return string
+     */
+    protected function indent(array $list)
+    {
+        return ' ' . implode(' ', $list);
+    }
+
+    /**
+     * 批量为占位符绑定值
+     *
+     * @param array $bind_values
+     * @return self
+     *
+     */
+    public function bindValues(array $bind_values)
+    {
+        foreach ($bind_values as $key => $val) {
+            $this->bindValue($key, $val);
+        }
+        return $this;
+    }
+
+    /**
+     * 单个为占位符绑定值
+     *
+     * @param string $name
+     * @param mixed  $value
+     * @return self
+     */
+    public function bindValue($name, $value)
+    {
+        $this->bind_values[$name] = $value;
+        return $this;
+    }
+
+    /**
+     * 生成 flag
+     *
+     * @return string
+     */
+    protected function buildFlags()
+    {
+        if (!$this->flags) {
+            return '';
+        }
+        return ' ' . implode(' ', array_keys($this->flags));
+    }
+
+    /**
+     * 设置 flag.
+     *
+     * @param string $flag
+     * @param bool   $enable
+     */
+    protected function setFlag($flag, $enable = true)
+    {
+        if ($enable) {
+            $this->flags[$flag] = true;
+        } else {
+            unset($this->flags[$flag]);
+        }
+    }
+
+    /**
+     * 重置 flag
+     */
+    protected function resetFlags()
+    {
+        $this->flags = array();
+    }
+
+    /**
+     *
+     * 添加 where 语句
+     *
+     * @param string $andor 'AND' or 'OR
+     * @param array  $conditions
+     * @return self
+     *
+     */
+    protected function addWhere($andor, $conditions)
+    {
+        $this->addClauseCondWithBind('where', $andor, $conditions);
+        return $this;
+    }
+
+    /**
+     * 添加条件和绑定值
+     *
+     * @param string $clause where 、having等
+     * @param string $andor  AND、OR等
+     * @param array  $conditions
+     */
+    protected function addClauseCondWithBind($clause, $andor, $conditions)
+    {
+        $cond = array_shift($conditions);
+        $cond = $this->quoteNamesIn($cond);
+
+        $bind =& $this->{"bind_{$clause}"};
+        foreach ($conditions as $value) {
+            $bind[] = $value;
+        }
+
+        $clause =& $this->$clause;
+        if ($clause) {
+            $clause[] = "$andor $cond";
+        } else {
+            $clause[] = $cond;
+        }
+    }
+
+    /**
+     * 生成 where 语句
+     *
+     * @return string
+     */
+    protected function buildWhere()
+    {
+        if (!$this->where) {
+            return '';
+        }
+        return ' WHERE' . $this->indent($this->where);
+    }
+
+    /**
+     * 增加 order by
+     *
+     * @param array $spec The columns and direction to order by.
+     * @return self
+     */
+    protected function addOrderBy(array $spec)
+    {
+        foreach ($spec as $col) {
+            $this->order_by[] = $this->quoteNamesIn($col);
+        }
+        return $this;
+    }
+
+    /**
+     * 生成 order by 语句
+     *
+     * @return string
+     */
+    protected function buildOrderBy()
+    {
+        if (!$this->order_by) {
+            return '';
+        }
+
+        if ($this->order_asc) {
+            return ' ORDER BY' . $this->indentCsv($this->order_by) . ' ASC';
+        } else {
+            return ' ORDER BY' . $this->indentCsv($this->order_by) . ' DESC';
+        }
+    }
+
+    /**
+     * 生成 limit 语句
+     *
+     * @return string
+     */
+    protected function buildLimit()
+    {
+        $has_limit  = $this->type == 'DELETE' || $this->type == 'UPDATE';
+        $has_offset = $this->type == 'SELECT';
+
+        if ($has_offset && $this->limit) {
+            $clause = " LIMIT {$this->limit}";
+            if ($this->offset) {
+                $clause .= " OFFSET {$this->offset}";
+            }
+            return $clause;
+        } elseif ($has_limit && $this->limit) {
+            return " LIMIT {$this->limit}";
+        }
+        return '';
+    }
+
+    /**
+     * Quotes
+     *
+     * @param string $spec
+     * @return string|array
+     */
+    public function quoteName($spec)
+    {
+        $spec = trim($spec);
+        $seps = array(' AS ', ' ', '.');
+        foreach ($seps as $sep) {
+            $pos = strripos($spec, $sep);
+            if ($pos) {
+                return $this->quoteNameWithSeparator($spec, $sep, $pos);
+            }
+        }
+        return $this->replaceName($spec);
+    }
+
+    /**
+     * 指定分隔符的 Quotes
+     *
+     * @param string $spec
+     * @param string $sep
+     * @param int    $pos
+     * @return string
+     */
+    protected function quoteNameWithSeparator($spec, $sep, $pos)
+    {
+        $len   = strlen($sep);
+        $part1 = $this->quoteName(substr($spec, 0, $pos));
+        $part2 = $this->replaceName(substr($spec, $pos + $len));
+        return "{$part1}{$sep}{$part2}";
+    }
+
+    /**
+     * Quotes "table.col" 格式的字符串
+     *
+     * @param string $text
+     * @return string|array
+     */
+    public function quoteNamesIn($text)
+    {
+        $list = $this->getListForQuoteNamesIn($text);
+        $last = count($list) - 1;
+        $text = null;
+        foreach ($list as $key => $val) {
+            if (($key + 1) % 3) {
+                $text .= $this->quoteNamesInLoop($val, $key == $last);
+            }
+        }
+        return $text;
+    }
+
+    /**
+     * 返回 quote 元素列表
+     *
+     * @param string $text
+     * @return array
+     */
+    protected function getListForQuoteNamesIn($text)
+    {
+        $apos = "'";
+        $quot = '"';
+        return preg_split(
+            "/(($apos+|$quot+|\\$apos+|\\$quot+).*?\\2)/",
+            $text,
+            -1,
+            PREG_SPLIT_DELIM_CAPTURE
+        );
+    }
+
+    /**
+     * 循环 quote
+     *
+     * @param string $val
+     * @param bool   $is_last
+     * @return string
+     */
+    protected function quoteNamesInLoop($val, $is_last)
+    {
+        if ($is_last) {
+            return $this->replaceNamesAndAliasIn($val);
+        }
+        return $this->replaceNamesIn($val);
+    }
+
+    /**
+     * 替换成别名
+     *
+     * @param string $val
+     * @return string
+     */
+    protected function replaceNamesAndAliasIn($val)
+    {
+        $quoted = $this->replaceNamesIn($val);
+        $pos    = strripos($quoted, ' AS ');
+        if ($pos) {
+            $alias  = $this->replaceName(substr($quoted, $pos + 4));
+            $quoted = substr($quoted, 0, $pos) . " AS $alias";
+        }
+        return $quoted;
+    }
+
+    /**
+     * Quotes name
+     *
+     * @param string $name
+     * @return string
+     */
+    protected function replaceName($name)
+    {
+        $name = trim($name);
+        if ($name == '*') {
+            return $name;
+        }
+        return '`' . $name . '`';
+    }
+
+    /**
+     * Quotes
+     *
+     * @param string $text
+     * @return string|array
+     */
+    protected function replaceNamesIn($text)
+    {
+        $is_string_literal = strpos($text, "'") !== false
+            || strpos($text, '"') !== false;
+        if ($is_string_literal) {
+            return $text;
+        }
+
+        $word = '[a-z_][a-z0-9_]+';
+
+        $find = "/(\\b)($word)\\.($word)(\\b)/i";
+
+        $repl = '$1`$2`.`$3`$4';
+
+        $text = preg_replace($find, $repl, $text);
+
+        return $text;
+    }
+
+    // ---------- insert --------------
+    /**
+     * 设置 `table.column` 与 last-insert-id 的映射
+     *
+     * @param array $last_insert_id_names
+     */
+    public function setLastInsertIdNames(array $last_insert_id_names)
+    {
+        $this->last_insert_id_names = $last_insert_id_names;
+    }
+
+    /**
+     * insert into.
+     *
+     * @param string $table
+     * @return self
+     */
+    public function into($table)
+    {
+        $this->table = $this->quoteName($table);
+        return $this;
+    }
+
+    /**
+     * 生成 INSERT 语句
+     *
+     * @return string
+     */
+    protected function buildINSERT()
+    {
+        return 'INSERT'
+        . $this->buildFlags()
+        . $this->buildInto()
+        . $this->buildValuesForInsert()
+        . $this->buildReturning();
+    }
+
+    /**
+     * 生成 INTO 语句
+     *
+     * @return string
+     */
+    protected function buildInto()
+    {
+        return " INTO " . $this->table;
+    }
+
+    /**
+     * PDO::lastInsertId()
+     *
+     * @param string $col
+     * @return mixed
+     */
+    public function getLastInsertIdName($col)
+    {
+        $key = str_replace('`', '', $this->table) . '.' . $col;
+        if (isset($this->last_insert_id_names[$key])) {
+            return $this->last_insert_id_names[$key];
+        }
+
+        return null;
+    }
+
+    /**
+     * 设置一列,如果有第二各参数,则把第二个参数绑定在占位符上
+     *
+     * @param string $col
+     * @return self
+     */
+    public function col($col)
+    {
+        return call_user_func_array(array($this, 'addCol'), func_get_args());
+    }
+
+    /**
+     * 设置多列
+     *
+     * @param array $cols
+     * @return self
+     */
+    public function cols(array $cols)
+    {
+        if ($this->type == 'SELECT') {
+            foreach ($cols as $key => $val) {
+                $this->addColSELECT($key, $val);
+            }
+            return $this;
+        }
+        return $this->addCols($cols);
+    }
+
+    /**
+     * 直接设置列的值
+     *
+     * @param string $col
+     * @param string $value
+     * @return self
+     */
+    public function set($col, $value)
+    {
+        return $this->setCol($col, $value);
+    }
+
+    /**
+     * 为 INSERT 语句绑定值
+     *
+     * @return string
+     */
+    protected function buildValuesForInsert()
+    {
+        return ' (' . $this->indentCsv(array_keys($this->col_values)) . ') VALUES (' .
+        $this->indentCsv(array_values($this->col_values)) . ')';
+    }
+
+    // ------update-------
+    /**
+     * 更新哪个表
+     *
+     * @param string $table
+     * @return self
+     */
+    public function table($table)
+    {
+        $this->table = $this->quoteName($table);
+        return $this;
+    }
+
+    /**
+     * 生成完整 SQL 语句
+     *
+     * @return string
+     * @throws Exception
+     */
+    protected function build()
+    {
+        switch ($this->type) {
+            case 'DELETE':
+                return $this->buildDELETE();
+            case 'INSERT':
+                return $this->buildINSERT();
+            case 'UPDATE':
+                return $this->buildUPDATE();
+            case 'SELECT':
+                return $this->buildSELECT();
+        }
+        throw new Exception("type empty");
+    }
+
+    /**
+     * 生成更新的 SQL 语句
+     */
+    protected function buildUPDATE()
+    {
+        return 'UPDATE'
+        . $this->buildFlags()
+        . $this->buildTable()
+        . $this->buildValuesForUpdate()
+        . $this->buildWhere()
+        . $this->buildOrderBy()
+        . $this->buildLimit()
+        . $this->buildReturning();
+    }
+
+    /**
+     * 哪个表
+     *
+     * @return string
+     */
+    protected function buildTable()
+    {
+        return " {$this->table}";
+    }
+
+    /**
+     * 为更新语句绑定值
+     *
+     * @return string
+     */
+    protected function buildValuesForUpdate()
+    {
+        $values = array();
+        foreach ($this->col_values as $col => $value) {
+            $values[] = "{$col} = {$value}";
+        }
+        return ' SET' . $this->indentCsv($values);
+    }
+
+    // ----------Dml---------------
+    /**
+     * 获取绑定的值
+     *
+     * @return array
+     */
+    public function getBindValuesCOMMON()
+    {
+        $bind_values = $this->bind_values;
+        $i           = 1;
+        foreach ($this->bind_where as $val) {
+            $bind_values[$i] = $val;
+            $i++;
+        }
+        return $bind_values;
+    }
+
+    /**
+     * 设置列
+     *
+     * @param string $col
+     * @return self
+     */
+    protected function addCol($col)
+    {
+        $key                    = $this->quoteName($col);
+        $this->col_values[$key] = ":$col";
+        $args                   = func_get_args();
+        if (count($args) > 1) {
+            $this->bindValue($col, $args[1]);
+        }
+        return $this;
+    }
+
+    /**
+     * 设置多个列
+     *
+     * @param array $cols
+     * @return self
+     */
+    protected function addCols(array $cols)
+    {
+        foreach ($cols as $key => $val) {
+            if (is_int($key)) {
+                $this->addCol($val);
+            } else {
+                $this->addCol($key, $val);
+            }
+        }
+        return $this;
+    }
+
+    /**
+     * 设置单列的值
+     *
+     * @param string $col .
+     * @param string $value
+     * @return self
+     */
+    protected function setCol($col, $value)
+    {
+        if ($value === null) {
+            $value = 'NULL';
+        }
+
+        $key                    = $this->quoteName($col);
+        $value                  = $this->quoteNamesIn($value);
+        $this->col_values[$key] = $value;
+        return $this;
+    }
+
+    /**
+     * 增加返回的列
+     *
+     * @param array $cols
+     * @return self
+     *
+     */
+    protected function addReturning(array $cols)
+    {
+        foreach ($cols as $col) {
+            $this->returning[] = $this->quoteNamesIn($col);
+        }
+        return $this;
+    }
+
+    /**
+     * 生成 RETURNING 语句
+     *
+     * @return string
+     */
+    protected function buildReturning()
+    {
+        if (!$this->returning) {
+            return '';
+        }
+        return ' RETURNING' . $this->indentCsv($this->returning);
+    }
+
+    /**
+     * 构造函数
+     *
+     * @param string $host
+     * @param int    $port
+     * @param string $user
+     * @param string $password
+     * @param string $db_name
+     * @param string $charset
+     */
+    public function __construct($config)
+    {
+        /*$this->settings = array(
+            'host'     => $host,
+            'port'     => $port,
+            'user'     => $user,
+            'password' => $password,
+            'dbname'   => $db_name,
+            'charset'  => $charset,
+        );*/
+        $this->settings = $config;
+        $this->connect();
+    }
+
+    /**
+     * 创建 PDO 实例
+     */
+    protected function connect()
+    {
+        $dsn       = 'mysql:dbname=' . $this->settings["dbname"] . ';host=' .
+            $this->settings["host"] . ';port=' . $this->settings['port'];
+        $this->pdo = new PDO($dsn, $this->settings["user"], $this->settings["password"],
+            array(
+                PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . (!empty($this->settings['charset']) ?
+                        $this->settings['charset'] : 'utf8')
+            ));
+        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+        $this->pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
+        $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+    }
+
+   /**
+    * 关闭连接
+    */
+    public function closeConnection()
+    {
+        $this->pdo = null;
+    }
+
+    /**
+     * 执行
+     *
+     * @param string $query
+     * @param string $parameters
+     * @throws PDOException
+     */
+    protected function execute($query, $parameters = "")
+    {
+        try {
+            $this->sQuery = @$this->pdo->prepare($query);
+            $this->bindMore($parameters);
+            if (!empty($this->parameters)) {
+                foreach ($this->parameters as $param) {
+                    $parameters = explode("\x7F", $param);
+                    $this->sQuery->bindParam($parameters[0], $parameters[1]);
+                }
+            }
+            $this->success = $this->sQuery->execute();
+        } catch (PDOException $e) {
+            // 服务端断开时重连一次
+            if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
+                $this->closeConnection();
+                $this->connect();
+
+                try {
+                    $this->sQuery = $this->pdo->prepare($query);
+                    $this->bindMore($parameters);
+                    if (!empty($this->parameters)) {
+                        foreach ($this->parameters as $param) {
+                            $parameters = explode("\x7F", $param);
+                            $this->sQuery->bindParam($parameters[0], $parameters[1]);
+                        }
+                    }
+                    $this->success = $this->sQuery->execute();
+                } catch (PDOException $ex) {
+                    $this->rollBackTrans();
+                    throw $ex;
+                }
+            } else {
+                $this->rollBackTrans();
+                $msg = $e->getMessage();
+                $err_msg = "SQL:".$this->lastSQL()." ".$msg;
+                $exception = new \PDOException($err_msg, (int)$e->getCode());
+                throw $exception;
+            }
+        }
+        $this->parameters = array();
+    }
+
+    /**
+     * 绑定
+     *
+     * @param string $para
+     * @param string $value
+     */
+    public function bind($para, $value)
+    {
+        if (is_string($para)) {
+            $this->parameters[sizeof($this->parameters)] = ":" . $para . "\x7F" . $value;
+        } else {
+            $this->parameters[sizeof($this->parameters)] = $para . "\x7F" . $value;
+        }
+    }
+
+    /**
+     * 绑定多个
+     *
+     * @param array $parray
+     */
+    public function bindMore($parray)
+    {
+        if (empty($this->parameters) && is_array($parray)) {
+            $columns = array_keys($parray);
+            foreach ($columns as $i => &$column) {
+                $this->bind($column, $parray[$column]);
+            }
+        }
+    }
+
+    /**
+     * 执行 SQL
+     *
+     * @param string $query
+     * @param array  $params
+     * @param int    $fetchmode
+     * @return mixed
+     */
+    public function query($query = '', $params = null, $fetchmode = PDO::FETCH_ASSOC)
+    {
+        $query = trim($query);
+        if (empty($query)) {
+            $query = $this->build();
+            if (!$params) {
+                $params = $this->getBindValues();
+            }
+        }
+
+        $this->resetAll();
+        $this->lastSql = $query;
+
+        $this->execute($query, $params);
+
+        $rawStatement = explode(" ", $query);
+
+        $statement = strtolower(trim($rawStatement[0]));
+        if ($statement === 'select' || $statement === 'show') {
+            return $this->sQuery->fetchAll($fetchmode);
+        } elseif ($statement === 'update' || $statement === 'delete') {
+            return $this->sQuery->rowCount();
+        } elseif ($statement === 'insert') {
+            if ($this->sQuery->rowCount() > 0) {
+                return $this->lastInsertId();
+            }
+        } else {
+            return null;
+        }
+
+        return null;
+    }
+
+    /**
+     * 返回一列
+     *
+     * @param  string $query
+     * @param  array  $params
+     * @return array
+     */
+    public function column($query = '', $params = null)
+    {
+        $query = trim($query);
+        if (empty($query)) {
+            $query = $this->build();
+            if (!$params) {
+                $params = $this->getBindValues();
+            }
+        }
+
+        $this->resetAll();
+        $this->lastSql = $query;
+
+        $this->execute($query, $params);
+        $columns = $this->sQuery->fetchAll(PDO::FETCH_NUM);
+        $column  = null;
+        foreach ($columns as $cells) {
+            $column[] = $cells[0];
+        }
+        return $column;
+    }
+
+    /**
+     * 返回一行
+     *
+     * @param  string $query
+     * @param  array  $params
+     * @param  int    $fetchmode
+     * @return array
+     */
+    public function row($query = '', $params = null, $fetchmode = PDO::FETCH_ASSOC)
+    {
+        $query = trim($query);
+        if (empty($query)) {
+            $query = $this->build();
+            if (!$params) {
+                $params = $this->getBindValues();
+            }
+        }
+
+        $this->resetAll();
+        $this->lastSql = $query;
+
+        $this->execute($query, $params);
+        return $this->sQuery->fetch($fetchmode);
+    }
+
+    /**
+     * 返回单个值
+     *
+     * @param  string $query
+     * @param  array  $params
+     * @return string
+     */
+    public function single($query = '', $params = null)
+    {
+        $query = trim($query);
+        if (empty($query)) {
+            $query = $this->build();
+            if (!$params) {
+                $params = $this->getBindValues();
+            }
+        }
+
+        $this->resetAll();
+        $this->lastSql = $query;
+
+        $this->execute($query, $params);
+        return $this->sQuery->fetchColumn();
+    }
+
+    /**
+     * 返回 lastInsertId
+     *
+     * @return string
+     */
+    public function lastInsertId()
+    {
+        return $this->pdo->lastInsertId();
+    }
+
+    /**
+     * 返回最后一条执行的 sql
+     *
+     * @return  string
+     */
+    public function lastSQL()
+    {
+        return $this->lastSql;
+    }
+
+    /**
+     * 开始事务
+     */
+    public function beginTrans()
+    {
+        try {
+            $this->pdo->beginTransaction();
+        } catch (PDOException $e) {
+            // 服务端断开时重连一次
+            if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) {
+                $this->pdo->beginTransaction();
+            } else {
+                throw $e;
+            }
+        }
+    }
+
+    /**
+     * 提交事务
+     */
+    public function commitTrans()
+    {
+        $this->pdo->commit();
+    }
+
+    /**
+     * 事务回滚
+     */
+    public function rollBackTrans()
+    {
+        if ($this->pdo->inTransaction()) {
+            $this->pdo->rollBack();
+        }
+    }
+}

+ 1361 - 0
vendor/workerman/gateway-worker/src/Lib/Gateway.php

@@ -0,0 +1,1361 @@
+<?php
+/**
+ * This file is part of workerman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace GatewayWorker\Lib;
+
+use Exception;
+use GatewayWorker\Protocols\GatewayProtocol;
+use Workerman\Connection\TcpConnection;
+
+/**
+ * 数据发送相关
+ */
+class Gateway
+{
+    /**
+     * gateway 实例
+     *
+     * @var object
+     */
+    protected static $businessWorker = null;
+
+    /**
+     * 注册中心地址
+     *
+     * @var string|array
+     */
+    public static $registerAddress = '127.0.0.1:1236';
+
+    /**
+     * 秘钥
+     * @var string
+     */
+    public static $secretKey = '';
+
+    /**
+     * 链接超时时间
+     * @var int
+     */
+    public static $connectTimeout = 3;
+
+    /**
+     * 与Gateway是否是长链接
+     * @var bool
+     */
+    public static $persistentConnection = false;
+    
+    /**
+     * 向所有客户端连接(或者 client_id_array 指定的客户端连接)广播消息
+     *
+     * @param string $message           向客户端发送的消息
+     * @param array  $client_id_array   客户端 id 数组
+     * @param array  $exclude_client_id 不给这些client_id发
+     * @param bool   $raw               是否发送原始数据(即不调用gateway的协议的encode方法)
+     * @return void
+     * @throws Exception
+     */
+    public static function sendToAll($message, $client_id_array = null, $exclude_client_id = null, $raw = false)
+    {
+        $gateway_data         = GatewayProtocol::$empty;
+        $gateway_data['cmd']  = GatewayProtocol::CMD_SEND_TO_ALL;
+        $gateway_data['body'] = $message;
+        if ($raw) {
+            $gateway_data['flag'] |= GatewayProtocol::FLAG_NOT_CALL_ENCODE;
+        }
+
+        if ($exclude_client_id) {
+            if (!is_array($exclude_client_id)) {
+                $exclude_client_id = array($exclude_client_id);
+            }
+            if ($client_id_array) {
+                $exclude_client_id = array_flip($exclude_client_id);
+            }
+        }
+
+        if ($client_id_array) {
+            if (!is_array($client_id_array)) {
+                echo new \Exception('bad $client_id_array:'.var_export($client_id_array, true));
+                return;
+            }
+            $data_array = array();
+            foreach ($client_id_array as $client_id) {
+                if (isset($exclude_client_id[$client_id])) {
+                    continue;
+                }
+                $address = Context::clientIdToAddress($client_id);
+                if ($address) {
+                    $key                                         = long2ip($address['local_ip']) . ":{$address['local_port']}";
+                    $data_array[$key][$address['connection_id']] = $address['connection_id'];
+                }
+            }
+            foreach ($data_array as $addr => $connection_id_list) {
+                $the_gateway_data             = $gateway_data;
+                $the_gateway_data['ext_data'] = json_encode(array('connections' => $connection_id_list));
+                static::sendToGateway($addr, $the_gateway_data);
+            }
+            return;
+        } elseif (empty($client_id_array) && is_array($client_id_array)) {
+            return;
+        }
+
+        if (!$exclude_client_id) {
+            return static::sendToAllGateway($gateway_data);
+        }
+
+        $address_connection_array = static::clientIdArrayToAddressArray($exclude_client_id);
+
+        // 如果有businessWorker实例,说明运行在workerman环境中,通过businessWorker中的长连接发送数据
+        if (static::$businessWorker) {
+            foreach (static::$businessWorker->gatewayConnections as $address => $gateway_connection) {
+                $gateway_data['ext_data'] = isset($address_connection_array[$address]) ?
+                    json_encode(array('exclude'=> $address_connection_array[$address])) : '';
+                /** @var TcpConnection $gateway_connection */
+                $gateway_connection->send($gateway_data);
+            }
+        } // 运行在其它环境中,通过注册中心得到gateway地址
+        else {
+            $all_addresses = static::getAllGatewayAddressesFromRegister();
+            foreach ($all_addresses as $address) {
+                $gateway_data['ext_data'] = isset($address_connection_array[$address]) ?
+                    json_encode(array('exclude'=> $address_connection_array[$address])) : '';
+                static::sendToGateway($address, $gateway_data);
+            }
+        }
+
+    }
+
+    /**
+     * 向某个client_id对应的连接发消息
+     *
+     * @param int    $client_id
+     * @param string $message
+     * @return void
+     */
+    public static function sendToClient($client_id, $message)
+    {
+        return static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_SEND_TO_ONE, $message);
+    }
+
+    /**
+     * 向当前客户端连接发送消息
+     *
+     * @param string $message
+     * @return bool
+     */
+    public static function sendToCurrentClient($message)
+    {
+        return static::sendCmdAndMessageToClient(null, GatewayProtocol::CMD_SEND_TO_ONE, $message);
+    }
+
+    /**
+     * 判断某个uid是否在线
+     *
+     * @param string $uid
+     * @return int 0|1
+     */
+    public static function isUidOnline($uid)
+    {
+        return (int)static::getClientIdByUid($uid);
+    }
+    
+    /**
+     * 判断client_id对应的连接是否在线
+     *
+     * @param int $client_id
+     * @return int 0|1
+     */
+    public static function isOnline($client_id)
+    {
+        $address_data = Context::clientIdToAddress($client_id);
+        if (!$address_data) {
+            return 0;
+        }
+        $address      = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}";
+        if (isset(static::$businessWorker)) {
+            if (!isset(static::$businessWorker->gatewayConnections[$address])) {
+                return 0;
+            }
+        }
+        $gateway_data                  = GatewayProtocol::$empty;
+        $gateway_data['cmd']           = GatewayProtocol::CMD_IS_ONLINE;
+        $gateway_data['connection_id'] = $address_data['connection_id'];
+        return (int)static::sendAndRecv($address, $gateway_data);
+    }
+
+    /**
+     * 获取所有在线用户的session,client_id为 key(弃用,请用getAllClientSessions代替)
+     *
+     * @param string $group
+     * @return array
+     */
+    public static function getAllClientInfo($group = '')
+    {
+        echo "Warning: Gateway::getAllClientInfo is deprecated and will be removed in a future, please use Gateway::getAllClientSessions instead.";
+        return static::getAllClientSessions($group);
+    }
+
+    /**
+     * 获取所有在线client_id的session,client_id为 key
+     *
+     * @param string $group
+     * @return array
+     */
+    public static function getAllClientSessions($group = '')
+    {
+        $gateway_data = GatewayProtocol::$empty;
+        if (!$group) {
+            $gateway_data['cmd']      = GatewayProtocol::CMD_GET_ALL_CLIENT_SESSIONS;
+        } else {
+            $gateway_data['cmd']      = GatewayProtocol::CMD_GET_CLIENT_SESSIONS_BY_GROUP;
+            $gateway_data['ext_data'] = $group;
+        }
+        $status_data      = array();
+        $all_buffer_array = static::getBufferFromAllGateway($gateway_data);
+        foreach ($all_buffer_array as $local_ip => $buffer_array) {
+            foreach ($buffer_array as $local_port => $data) {
+                if ($data) {
+                    foreach ($data as $connection_id => $session_buffer) {
+                        $client_id = Context::addressToClientId($local_ip, $local_port, $connection_id);
+                        if ($client_id === Context::$client_id) {
+                            $status_data[$client_id] = (array)$_SESSION;
+                        } else {
+                            $status_data[$client_id] = $session_buffer ? Context::sessionDecode($session_buffer) : array();
+                        }
+                    }
+                }
+            }
+        }
+        return $status_data;
+    }
+
+    /**
+     * 获取某个组的连接信息(弃用,请用getClientSessionsByGroup代替)
+     *
+     * @param string $group
+     * @return array
+     */
+    public static function getClientInfoByGroup($group)
+    {
+        echo "Warning: Gateway::getClientInfoByGroup is deprecated and will be removed in a future, please use Gateway::getClientSessionsByGroup instead.";
+        return static::getAllClientSessions($group);
+    }
+
+    /**
+     * 获取某个组的所有client_id的session信息
+     *
+     * @param string $group
+     *
+     * @return array
+     */
+    public static function getClientSessionsByGroup($group)
+    {
+        if (static::isValidGroupId($group)) {
+            return static::getAllClientSessions($group);
+        }
+        return array();
+    }
+
+    /**
+     * 获取所有在线client_id数
+     *
+     * @return int
+     */
+    public static function getAllClientIdCount()
+    {
+        return static::getClientCountByGroup();
+    }
+
+    /**
+     * 获取所有在线client_id数(getAllClientIdCount的别名)
+     *
+     * @return int
+     */
+    public static function getAllClientCount()
+    {
+        return static::getAllClientIdCount();
+    }
+
+    /**
+     * 获取某个组的在线client_id数
+     *
+     * @param string $group
+     * @return int
+     */
+    public static function getClientIdCountByGroup($group = '')
+    {
+        $gateway_data             = GatewayProtocol::$empty;
+        $gateway_data['cmd']      = GatewayProtocol::CMD_GET_CLIENT_COUNT_BY_GROUP;
+        $gateway_data['ext_data'] = $group;
+        $total_count              = 0;
+        $all_buffer_array         = static::getBufferFromAllGateway($gateway_data);
+        foreach ($all_buffer_array as $local_ip => $buffer_array) {
+            foreach ($buffer_array as $local_port => $count) {
+                if ($count) {
+                    $total_count += $count;
+                }
+            }
+        }
+        return $total_count;
+    }
+
+    /**
+     * getClientIdCountByGroup 函数的别名
+     *
+     * @param string $group
+     * @return int
+     */
+    public static function getClientCountByGroup($group = '')
+    {
+        return static::getClientIdCountByGroup($group);
+    }
+
+    /**
+     * 获取某个群组在线client_id列表
+     *
+     * @param string $group
+     * @return array
+     */
+    public static function getClientIdListByGroup($group)
+    {
+        if (!static::isValidGroupId($group)) {
+            return array();
+        }
+
+        $data = static::select(array('uid'), array('groups' => is_array($group) ? $group : array($group)));
+        $client_id_map = array();
+        foreach ($data as $local_ip => $buffer_array) {
+            foreach ($buffer_array as $local_port => $items) {
+                //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..];
+                foreach ($items as $connection_id => $info) {
+                    $client_id = Context::addressToClientId($local_ip, $local_port, $connection_id);
+                    $client_id_map[$client_id] = $client_id;
+                }
+            }
+        }
+        return $client_id_map;
+    }
+
+    /**
+     * 获取集群所有在线client_id列表
+     *
+     * @return array
+     */
+    public static function getAllClientIdList()
+    {
+        return static::formatClientIdFromGatewayBuffer(static::select(array('uid')));
+    }
+
+    /**
+     * 格式化client_id
+     *
+     * @param $data
+     * @return array
+     */
+    protected static function formatClientIdFromGatewayBuffer($data)
+    {
+        $client_id_list = array();
+        foreach ($data as $local_ip => $buffer_array) {
+            foreach ($buffer_array as $local_port => $items) {
+                //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..];
+                foreach ($items as $connection_id => $info) {
+                    $client_id = Context::addressToClientId($local_ip, $local_port, $connection_id);
+                    $client_id_list[$client_id] = $client_id;
+                }
+            }
+        }
+        return $client_id_list;
+    }
+
+
+    /**
+     * 获取与 uid 绑定的 client_id 列表
+     *
+     * @param string $uid
+     * @return array
+     */
+    public static function getClientIdByUid($uid)
+    {
+        $gateway_data             = GatewayProtocol::$empty;
+        $gateway_data['cmd']      = GatewayProtocol::CMD_GET_CLIENT_ID_BY_UID;
+        $gateway_data['ext_data'] = $uid;
+        $client_list              = array();
+        $all_buffer_array         = static::getBufferFromAllGateway($gateway_data);
+        foreach ($all_buffer_array as $local_ip => $buffer_array) {
+            foreach ($buffer_array as $local_port => $connection_id_array) {
+                if ($connection_id_array) {
+                    foreach ($connection_id_array as $connection_id) {
+                        $client_list[] = Context::addressToClientId($local_ip, $local_port, $connection_id);
+                    }
+                }
+            }
+        }
+        return $client_list;
+    }
+
+    /**
+     * 获取某个群组在线uid列表
+     *
+     * @param string $group
+     * @return array
+     */
+    public static function getUidListByGroup($group)
+    {
+        if (!static::isValidGroupId($group)) {
+            return array();
+        }
+
+        $group = is_array($group) ? $group : array($group);
+        $data = static::select(array('uid'), array('groups' => $group));
+        $uid_map = array();
+        foreach ($data as $local_ip => $buffer_array) {
+            foreach ($buffer_array as $local_port => $items) {
+                //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..];
+                foreach ($items as $connection_id => $info) {
+                    if (!empty($info['uid'])) {
+                        $uid_map[$info['uid']] = $info['uid'];
+                    }
+                }
+            }
+        }
+        return $uid_map;
+    }
+
+    /**
+     * 获取某个群组在线uid数
+     *
+     * @param string $group
+     * @return int
+     */
+    public static function getUidCountByGroup($group)
+    {
+        if (static::isValidGroupId($group)) {
+            return count(static::getUidListByGroup($group));
+        }
+        return 0;
+    }
+
+    /**
+     * 获取全局在线uid列表
+     *
+     * @return array
+     */
+    public static function getAllUidList()
+    {
+        $data = static::select(array('uid'));
+        $uid_map = array();
+        foreach ($data as $local_ip => $buffer_array) {
+            foreach ($buffer_array as $local_port => $items) {
+                //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..];
+                foreach ($items as $connection_id => $info) {
+                    if (!empty($info['uid'])) {
+                        $uid_map[$info['uid']] = $info['uid'];
+                    }
+                }
+            }
+        }
+        return $uid_map;
+    }
+
+    /**
+     * 获取全局在线uid数
+     * @return int
+     */
+    public static function getAllUidCount()
+    {
+        return count(static::getAllUidList());
+    }
+
+    /**
+     * 通过client_id获取uid
+     *
+     * @param $client_id
+     * @return mixed
+     */
+    public static function getUidByClientId($client_id)
+    {
+        $data = static::select(array('uid'), array('client_id'=>array($client_id)));
+        foreach ($data as $local_ip => $buffer_array) {
+            foreach ($buffer_array as $local_port => $items) {
+                //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..];
+                foreach ($items as $info) {
+                    return $info['uid'];
+                }
+            }
+        }
+    }
+
+    /**
+     * 获取所有在线的群组id
+     *
+     * @return array
+     */
+    public static function getAllGroupIdList()
+    {
+        $gateway_data             = GatewayProtocol::$empty;
+        $gateway_data['cmd']      = GatewayProtocol::CMD_GET_GROUP_ID_LIST;
+        $group_id_list            = array();
+        $all_buffer_array         = static::getBufferFromAllGateway($gateway_data);
+        foreach ($all_buffer_array as $local_ip => $buffer_array) {
+            foreach ($buffer_array as $local_port => $group_id_array) {
+                if (is_array($group_id_array)) {
+                    foreach ($group_id_array as $group_id) {
+                        if (!isset($group_id_list[$group_id])) {
+                            $group_id_list[$group_id] = $group_id;
+                        }
+                    }
+                }
+            }
+        }
+        return $group_id_list;
+    }
+
+
+    /**
+     * 获取所有在线分组的uid数量,也就是每个分组的在线用户数
+     *
+     * @return array
+     */
+    public static function getAllGroupUidCount()
+    {
+        $group_uid_map = static::getAllGroupUidList();
+        $group_uid_count_map = array();
+        foreach ($group_uid_map as $group_id => $uid_list) {
+            $group_uid_count_map[$group_id] = count($uid_list);
+        }
+        return $group_uid_count_map;
+    }
+
+
+
+    /**
+     * 获取所有分组uid在线列表
+     *
+     * @return array
+     */
+    public static function getAllGroupUidList()
+    {
+        $data = static::select(array('uid','groups'));
+        $group_uid_map = array();
+        foreach ($data as $local_ip => $buffer_array) {
+            foreach ($buffer_array as $local_port => $items) {
+                //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..];
+                foreach ($items as $connection_id => $info) {
+                    if (empty($info['uid']) || empty($info['groups'])) {
+                        break;
+                    }
+                    $uid = $info['uid'];
+                    foreach ($info['groups'] as $group_id) {
+                        if(!isset($group_uid_map[$group_id])) {
+                            $group_uid_map[$group_id] = array();
+                        }
+                        $group_uid_map[$group_id][$uid] = $uid;
+                    }
+                }
+            }
+        }
+        return $group_uid_map;
+    }
+
+    /**
+     * 获取所有群组在线client_id列表
+     *
+     * @return array
+     */
+    public static function getAllGroupClientIdList()
+    {
+        $data = static::select(array('groups'));
+        $group_client_id_map = array();
+        foreach ($data as $local_ip => $buffer_array) {
+            foreach ($buffer_array as $local_port => $items) {
+                //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..];
+                foreach ($items as $connection_id => $info) {
+                    if (empty($info['groups'])) {
+                        break;
+                    }
+                    $client_id = Context::addressToClientId($local_ip, $local_port, $connection_id);
+                    foreach ($info['groups'] as $group_id) {
+                        if(!isset($group_client_id_map[$group_id])) {
+                            $group_client_id_map[$group_id] = array();
+                        }
+                        $group_client_id_map[$group_id][$client_id] = $client_id;
+                    }
+                }
+            }
+        }
+        return $group_client_id_map;
+    }
+
+    /**
+     * 获取所有群组在线client_id数量,也就是获取每个群组在线连接数
+     *
+     * @return array
+     */
+    public static function getAllGroupClientIdCount()
+    {
+        $group_client_map = static::getAllGroupClientIdList();
+        $group_client_count_map = array();
+        foreach ($group_client_map as $group_id => $client_id_list) {
+            $group_client_count_map[$group_id] = count($client_id_list);
+        }
+        return $group_client_count_map;
+    }
+
+
+    /**
+     * 根据条件到gateway搜索数据
+     *
+     * @param array $fields
+     * @param array $where
+     * @return array
+     */
+    protected static function select($fields = array('session','uid','groups'), $where = array())
+    {
+        $t = microtime(true);
+        $gateway_data             = GatewayProtocol::$empty;
+        $gateway_data['cmd']      = GatewayProtocol::CMD_SELECT;
+        $gateway_data['ext_data'] = array('fields' => $fields, 'where' => $where);
+        $gateway_data_list   = array();
+        // 有client_id,能计算出需要和哪些gateway通讯,只和必要的gateway通讯能降低系统负载
+        if (isset($where['client_id'])) {
+            $client_id_list = $where['client_id'];
+            unset($gateway_data['ext_data']['where']['client_id']);
+            $gateway_data['ext_data']['where']['connection_id'] = array();
+            foreach ($client_id_list as $client_id) {
+                $address_data = Context::clientIdToAddress($client_id);
+                if (!$address_data) {
+                    continue;
+                }
+                $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}";
+                if (!isset($gateway_data_list[$address])) {
+                    $gateway_data_list[$address] = $gateway_data;
+                }
+                $gateway_data_list[$address]['ext_data']['where']['connection_id'][$address_data['connection_id']] = $address_data['connection_id'];
+            }
+            foreach ($gateway_data_list as $address => $item) {
+                $gateway_data_list[$address]['ext_data'] = json_encode($item['ext_data']);
+            }
+            // 有其它条件,则还是需要向所有gateway发送
+            if (count($where) !== 1) {
+                $gateway_data['ext_data'] = json_encode($gateway_data['ext_data']);
+                foreach (static::getAllGatewayAddress() as $address) {
+                    if (!isset($gateway_data_list[$address])) {
+                        $gateway_data_list[$address] = $gateway_data;
+                    }
+                }
+            }
+            $data = static::getBufferFromSomeGateway($gateway_data_list);
+        } else {
+            $gateway_data['ext_data'] = json_encode($gateway_data['ext_data']);
+            $data = static::getBufferFromAllGateway($gateway_data);
+        }
+
+        return $data;
+    }
+
+    /**
+     * 生成验证包,用于验证此客户端的合法性
+     * 
+     * @return string
+     */
+    protected static function generateAuthBuffer()
+    {
+        $gateway_data         = GatewayProtocol::$empty;
+        $gateway_data['cmd']  = GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT;
+        $gateway_data['body'] = json_encode(array(
+            'secret_key' => static::$secretKey,
+        ));
+        return GatewayProtocol::encode($gateway_data);
+    }
+
+    /**
+     * 批量向某些gateway发包,并得到返回数组
+     *
+     * @param array $gateway_data_array
+     * @return array
+     * @throws Exception
+     */
+    protected static function getBufferFromSomeGateway($gateway_data_array)
+    {
+        $gateway_buffer_array = array();
+        $auth_buffer = static::$secretKey ? static::generateAuthBuffer() : '';
+        foreach ($gateway_data_array as $address => $gateway_data) {
+            if ($auth_buffer) {
+                $gateway_buffer_array[$address] = $auth_buffer.GatewayProtocol::encode($gateway_data);
+            } else {
+                $gateway_buffer_array[$address] = GatewayProtocol::encode($gateway_data);
+            }
+        }
+        return static::getBufferFromGateway($gateway_buffer_array);
+    }
+
+    /**
+     * 批量向所有 gateway 发包,并得到返回数组
+     *
+     * @param string $gateway_data
+     * @return array
+     * @throws Exception
+     */
+    protected static function getBufferFromAllGateway($gateway_data)
+    {
+        $addresses = static::getAllGatewayAddress();
+        $gateway_buffer_array = array();
+        $gateway_buffer = GatewayProtocol::encode($gateway_data);
+        $gateway_buffer = static::$secretKey ? static::generateAuthBuffer() . $gateway_buffer : $gateway_buffer;
+        foreach ($addresses as $address) {
+            $gateway_buffer_array[$address] = $gateway_buffer;
+        }
+
+        return static::getBufferFromGateway($gateway_buffer_array);
+    }
+
+    /**
+     * 获取所有gateway内部通讯地址
+     *
+     * @return array
+     * @throws Exception
+     */
+    protected static function getAllGatewayAddress()
+    {
+        if (isset(static::$businessWorker)) {
+            $addresses = static::$businessWorker->getAllGatewayAddresses();
+            if (empty($addresses)) {
+                throw new Exception('businessWorker::getAllGatewayAddresses return empty');
+            }
+        } else {
+            $addresses = static::getAllGatewayAddressesFromRegister();
+            if (empty($addresses)) {
+                return array();
+            }
+        }
+        return $addresses;
+    }
+
+    /**
+     * 批量向gateway发送并获取数据
+     * @param $gateway_buffer_array
+     * @return array
+     */
+    protected static function getBufferFromGateway($gateway_buffer_array)
+    {
+        $client_array = $status_data = $client_address_map = $receive_buffer_array = $recv_length_array = array();
+        // 批量向所有gateway进程发送请求数据
+        foreach ($gateway_buffer_array as $address => $gateway_buffer) {
+            $client = stream_socket_client("tcp://$address", $errno, $errmsg, static::$connectTimeout);
+            if ($client && strlen($gateway_buffer) === stream_socket_sendto($client, $gateway_buffer)) {
+                $socket_id                        = (int)$client;
+                $client_array[$socket_id]         = $client;
+                $client_address_map[$socket_id]   = explode(':', $address);
+                $receive_buffer_array[$socket_id] = '';
+            }
+        }
+        // 超时5秒
+        $timeout    = 5;
+        $time_start = microtime(true);
+        // 批量接收请求
+        while (count($client_array) > 0) {
+            $write = $except = array();
+            $read  = $client_array;
+            if (@stream_select($read, $write, $except, $timeout)) {
+                foreach ($read as $client) {
+                    $socket_id = (int)$client;
+                    $buffer    = stream_socket_recvfrom($client, 65535);
+                    if ($buffer !== '' && $buffer !== false) {
+                        $receive_buffer_array[$socket_id] .= $buffer;
+                        $receive_length = strlen($receive_buffer_array[$socket_id]);
+                        if (empty($recv_length_array[$socket_id]) && $receive_length >= 4) {
+                            $recv_length_array[$socket_id] = current(unpack('N', $receive_buffer_array[$socket_id]));
+                        }
+                        if (!empty($recv_length_array[$socket_id]) && $receive_length >= $recv_length_array[$socket_id] + 4) {
+                            unset($client_array[$socket_id]);
+                        }
+                    } elseif (feof($client)) {
+                        unset($client_array[$socket_id]);
+                    }
+                }
+            }
+            if (microtime(true) - $time_start > $timeout) {
+                break;
+            }
+        }
+        $format_buffer_array = array();
+        foreach ($receive_buffer_array as $socket_id => $buffer) {
+            $local_ip                                    = ip2long($client_address_map[$socket_id][0]);
+            $local_port                                  = $client_address_map[$socket_id][1];
+            $format_buffer_array[$local_ip][$local_port] = unserialize(substr($buffer, 4));
+        }
+        return $format_buffer_array;
+    }
+
+    /**
+     * 踢掉某个客户端,并以$message通知被踢掉客户端
+     *
+     * @param string $client_id
+     * @param string $message
+     * @return void
+     */
+    public static function closeClient($client_id, $message = null)
+    {
+        if ($client_id === Context::$client_id) {
+            return static::closeCurrentClient($message);
+        } // 不是发给当前用户则使用存储中的地址
+        else {
+            $address_data = Context::clientIdToAddress($client_id);
+            if (!$address_data) {
+                return false;
+            }
+            $address      = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}";
+            return static::kickAddress($address, $address_data['connection_id'], $message);
+        }
+    }
+
+    /**
+     * 踢掉当前客户端,并以$message通知被踢掉客户端
+     *
+     * @param string $message
+     * @return bool
+     * @throws Exception
+     */
+    public static function closeCurrentClient($message = null)
+    {
+        if (!Context::$connection_id) {
+            throw new Exception('closeCurrentClient can not be called in async context');
+        }
+        $address = long2ip(Context::$local_ip) . ':' . Context::$local_port;
+        return static::kickAddress($address, Context::$connection_id, $message);
+    }
+
+    /**
+     * 踢掉某个客户端并直接立即销毁相关连接
+     *
+     * @param int $client_id
+     * @return bool
+     */
+    public static function destoryClient($client_id)
+    {
+        if ($client_id === Context::$client_id) {
+            return static::destoryCurrentClient();
+        } // 不是发给当前用户则使用存储中的地址
+        else {
+            $address_data = Context::clientIdToAddress($client_id);
+            if (!$address_data) {
+                return false;
+            }
+            $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}";
+            return static::destroyAddress($address, $address_data['connection_id']);
+        }
+    }
+
+    /**
+     * 踢掉当前客户端并直接立即销毁相关连接
+     *
+     * @return bool
+     * @throws Exception
+     */
+    public static function destoryCurrentClient()
+    {
+        if (!Context::$connection_id) {
+            throw new Exception('destoryCurrentClient can not be called in async context');
+        }
+        $address = long2ip(Context::$local_ip) . ':' . Context::$local_port;
+        return static::destroyAddress($address, Context::$connection_id);
+    }
+
+    /**
+     * 将 client_id 与 uid 绑定
+     *
+     * @param int        $client_id
+     * @param int|string $uid
+     * @return void
+     */
+    public static function bindUid($client_id, $uid)
+    {
+        static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_BIND_UID, '', $uid);
+    }
+
+    /**
+     * 将 client_id 与 uid 解除绑定
+     *
+     * @param int        $client_id
+     * @param int|string $uid
+     * @return void
+     */
+    public static function unbindUid($client_id, $uid)
+    {
+        static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_UNBIND_UID, '', $uid);
+    }
+
+    /**
+     * 将 client_id 加入组
+     *
+     * @param int        $client_id
+     * @param int|string $group
+     * @return void
+     */
+    public static function joinGroup($client_id, $group)
+    {
+
+        static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_JOIN_GROUP, '', $group);
+    }
+
+    /**
+     * 将 client_id 离开组
+     *
+     * @param int        $client_id
+     * @param int|string $group
+     *
+     * @return void
+     */
+    public static function leaveGroup($client_id, $group)
+    {
+        static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_LEAVE_GROUP, '', $group);
+    }
+
+    /**
+     * 取消分组
+     *
+     * @param int|string $group
+     *
+     * @return void
+     */
+    public static function ungroup($group)
+    {
+        if (!static::isValidGroupId($group)) {
+            return false;
+        }
+        $gateway_data             = GatewayProtocol::$empty;
+        $gateway_data['cmd']      = GatewayProtocol::CMD_UNGROUP;
+        $gateway_data['ext_data'] = $group;
+        return static::sendToAllGateway($gateway_data);
+
+    }
+
+    /**
+     * 向所有 uid 发送
+     *
+     * @param int|string|array $uid
+     * @param string           $message
+     *
+     * @return void
+     */
+    public static function sendToUid($uid, $message)
+    {
+        $gateway_data         = GatewayProtocol::$empty;
+        $gateway_data['cmd']  = GatewayProtocol::CMD_SEND_TO_UID;
+        $gateway_data['body'] = $message;
+
+        if (!is_array($uid)) {
+            $uid = array($uid);
+        }
+
+        $gateway_data['ext_data'] = json_encode($uid);
+
+        static::sendToAllGateway($gateway_data);
+    }
+
+    /**
+     * 向 group 发送
+     *
+     * @param int|string|array $group             组(不允许是 0 '0' false null array()等为空的值)
+     * @param string           $message           消息
+     * @param array            $exclude_client_id 不给这些client_id发
+     * @param bool             $raw               发送原始数据(即不调用gateway的协议的encode方法)
+     *
+     * @return void
+     */
+    public static function sendToGroup($group, $message, $exclude_client_id = null, $raw = false)
+    {
+        if (!static::isValidGroupId($group)) {
+            return false;
+        }
+        $gateway_data         = GatewayProtocol::$empty;
+        $gateway_data['cmd']  = GatewayProtocol::CMD_SEND_TO_GROUP;
+        $gateway_data['body'] = $message;
+        if ($raw) {
+            $gateway_data['flag'] |= GatewayProtocol::FLAG_NOT_CALL_ENCODE;
+        }
+
+        if (!is_array($group)) {
+            $group = array($group);
+        }
+
+        // 分组发送,没有排除的client_id,直接发送
+        $default_ext_data_buffer = json_encode(array('group'=> $group, 'exclude'=> null));
+        if (empty($exclude_client_id)) {
+            $gateway_data['ext_data'] = $default_ext_data_buffer;
+            return static::sendToAllGateway($gateway_data);
+        }
+
+        // 分组发送,有排除的client_id,需要将client_id转换成对应gateway进程内的connectionId
+        if (!is_array($exclude_client_id)) {
+            $exclude_client_id = array($exclude_client_id);
+        }
+
+        $address_connection_array = static::clientIdArrayToAddressArray($exclude_client_id);
+        // 如果有businessWorker实例,说明运行在workerman环境中,通过businessWorker中的长连接发送数据
+        if (static::$businessWorker) {
+            foreach (static::$businessWorker->gatewayConnections as $address => $gateway_connection) {
+                $gateway_data['ext_data'] = isset($address_connection_array[$address]) ?
+                    json_encode(array('group'=> $group, 'exclude'=> $address_connection_array[$address])) :
+                    $default_ext_data_buffer;
+                /** @var TcpConnection $gateway_connection */
+                $gateway_connection->send($gateway_data);
+            }
+        } // 运行在其它环境中,通过注册中心得到gateway地址
+        else {
+            $addresses = static::getAllGatewayAddressesFromRegister();
+            foreach ($addresses as $address) {
+                $gateway_data['ext_data'] = isset($address_connection_array[$address]) ?
+                    json_encode(array('group'=> $group, 'exclude'=> $address_connection_array[$address])) :
+                    $default_ext_data_buffer;
+                static::sendToGateway($address, $gateway_data);
+            }
+        }
+    }
+
+    /**
+     * 更新 session,框架自动调用,开发者不要调用
+     *
+     * @param int    $client_id
+     * @param string $session_str
+     * @return bool
+     */
+    public static function setSocketSession($client_id, $session_str)
+    {
+        return static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_SET_SESSION, '', $session_str);
+    }
+
+    /**
+     * 设置 session,原session值会被覆盖
+     *
+     * @param int   $client_id
+     * @param array $session
+     *
+     * @return void
+     */
+    public static function setSession($client_id, array $session)
+    {
+        if (Context::$client_id === $client_id) {
+            $_SESSION = $session;
+            Context::$old_session = $_SESSION;
+        }
+        static::setSocketSession($client_id, Context::sessionEncode($session));
+    }
+    
+    /**
+     * 更新 session,实际上是与老的session合并
+     *
+     * @param int   $client_id
+     * @param array $session
+     *
+     * @return void
+     */
+    public static function updateSession($client_id, array $session)
+    {
+        if (Context::$client_id === $client_id) {
+            $_SESSION = array_replace_recursive((array)$_SESSION, $session);
+            Context::$old_session = $_SESSION;
+        }
+        static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_UPDATE_SESSION, '', Context::sessionEncode($session));
+    }
+    
+    /**
+     * 获取某个client_id的session
+     *
+     * @param int   $client_id
+     * @return mixed false表示出错、null表示用户不存在、array表示具体的session信息 
+     */
+    public static function getSession($client_id)
+    {
+        $address_data = Context::clientIdToAddress($client_id);
+        if (!$address_data) {
+            return false;
+        }
+        $address      = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}";
+        if (isset(static::$businessWorker)) {
+            if (!isset(static::$businessWorker->gatewayConnections[$address])) {
+                return null;
+            }
+        }
+        $gateway_data                  = GatewayProtocol::$empty;
+        $gateway_data['cmd']           = GatewayProtocol::CMD_GET_SESSION_BY_CLIENT_ID;
+        $gateway_data['connection_id'] = $address_data['connection_id'];
+        return static::sendAndRecv($address, $gateway_data);
+    }
+
+    /**
+     * 向某个用户网关发送命令和消息
+     *
+     * @param int    $client_id
+     * @param int    $cmd
+     * @param string $message
+     * @param string $ext_data
+     * @return boolean
+     */
+    protected static function sendCmdAndMessageToClient($client_id, $cmd, $message, $ext_data = '')
+    {
+        // 如果是发给当前用户则直接获取上下文中的地址
+        if ($client_id === Context::$client_id || $client_id === null) {
+            $address       = long2ip(Context::$local_ip) . ':' . Context::$local_port;
+            $connection_id = Context::$connection_id;
+        } else {
+            $address_data  = Context::clientIdToAddress($client_id);
+            if (!$address_data) {
+                return false;
+            }
+            $address       = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}";
+            $connection_id = $address_data['connection_id'];
+        }
+        $gateway_data                  = GatewayProtocol::$empty;
+        $gateway_data['cmd']           = $cmd;
+        $gateway_data['connection_id'] = $connection_id;
+        $gateway_data['body']          = $message;
+        if (!empty($ext_data)) {
+            $gateway_data['ext_data'] = $ext_data;
+        }
+
+        return static::sendToGateway($address, $gateway_data);
+    }
+
+    /**
+     * 发送数据并返回
+     *
+     * @param int   $address
+     * @param mixed $data
+     * @return bool
+     * @throws Exception
+     */
+    protected static function sendAndRecv($address, $data)
+    {
+        $buffer = GatewayProtocol::encode($data);
+        $buffer = static::$secretKey ? static::generateAuthBuffer() . $buffer : $buffer;
+        $client = stream_socket_client("tcp://$address", $errno, $errmsg, static::$connectTimeout);
+        if (!$client) {
+            throw new Exception("can not connect to tcp://$address $errmsg");
+        }
+        if (strlen($buffer) === stream_socket_sendto($client, $buffer)) {
+            $timeout = 5;
+            // 阻塞读
+            stream_set_blocking($client, 1);
+            // 1秒超时
+            stream_set_timeout($client, 1);
+            $all_buffer = '';
+            $time_start = microtime(true);
+            $pack_len = 0;
+            while (1) {
+                $buf = stream_socket_recvfrom($client, 655350);
+                if ($buf !== '' && $buf !== false) {
+                    $all_buffer .= $buf;
+                } else {
+                    if (feof($client)) {
+                        throw new Exception("connection close tcp://$address");
+                    } elseif (microtime(true) - $time_start > $timeout) {
+                        break;
+                    }
+                    continue;
+                }
+                $recv_len = strlen($all_buffer);
+                if (!$pack_len && $recv_len >= 4) {
+                    $pack_len= current(unpack('N', $all_buffer));
+                }
+                // 回复的数据都是以\n结尾
+                if (($pack_len && $recv_len >= $pack_len + 4) || microtime(true) - $time_start > $timeout) {
+                    break;
+                }
+            }
+            // 返回结果
+            return unserialize(substr($all_buffer, 4));
+        } else {
+            throw new Exception("sendAndRecv($address, \$bufer) fail ! Can not send data!", 502);
+        }
+    }
+
+    /**
+     * 发送数据到网关
+     *
+     * @param string $address
+     * @param array  $gateway_data
+     * @return bool
+     */
+    protected static function sendToGateway($address, $gateway_data)
+    {
+        return static::sendBufferToGateway($address, GatewayProtocol::encode($gateway_data));
+    }
+
+    /**
+     * 发送buffer数据到网关
+     * @param string $address
+     * @param string $gateway_buffer
+     * @return bool
+     */
+    protected static function sendBufferToGateway($address, $gateway_buffer)
+    {
+        // 有$businessWorker说明是workerman环境,使用$businessWorker发送数据
+        if (static::$businessWorker) {
+            if (!isset(static::$businessWorker->gatewayConnections[$address])) {
+                return false;
+            }
+            return static::$businessWorker->gatewayConnections[$address]->send($gateway_buffer, true);
+        }
+        // 非workerman环境
+        $gateway_buffer = static::$secretKey ? static::generateAuthBuffer() . $gateway_buffer : $gateway_buffer;
+        $flag           = static::$persistentConnection ? STREAM_CLIENT_PERSISTENT | STREAM_CLIENT_CONNECT : STREAM_CLIENT_CONNECT;
+        $client         = stream_socket_client("tcp://$address", $errno, $errmsg, static::$connectTimeout, $flag);
+        return strlen($gateway_buffer) == stream_socket_sendto($client, $gateway_buffer);
+    }
+
+    /**
+     * 向所有 gateway 发送数据
+     *
+     * @param string $gateway_data
+     * @throws Exception
+     *
+     * @return void
+     */
+    protected static function sendToAllGateway($gateway_data)
+    {
+        $buffer = GatewayProtocol::encode($gateway_data);
+        // 如果有businessWorker实例,说明运行在workerman环境中,通过businessWorker中的长连接发送数据
+        if (static::$businessWorker) {
+            foreach (static::$businessWorker->gatewayConnections as $gateway_connection) {
+                /** @var TcpConnection $gateway_connection */
+                $gateway_connection->send($buffer, true);
+            }
+        } // 运行在其它环境中,通过注册中心得到gateway地址
+        else {
+            $all_addresses = static::getAllGatewayAddressesFromRegister();
+            foreach ($all_addresses as $address) {
+                static::sendBufferToGateway($address, $buffer);
+            }
+        }
+    }
+
+    /**
+     * 踢掉某个网关的 socket
+     *
+     * @param string $address
+     * @param int    $connection_id
+     * @return bool
+     */
+    protected static function kickAddress($address, $connection_id, $message)
+    {
+        $gateway_data                  = GatewayProtocol::$empty;
+        $gateway_data['cmd']           = GatewayProtocol::CMD_KICK;
+        $gateway_data['connection_id'] = $connection_id;
+        $gateway_data['body'] = $message;
+        return static::sendToGateway($address, $gateway_data);
+    }
+
+    /**
+     * 销毁某个网关的 socket
+     *
+     * @param string $address
+     * @param int    $connection_id
+     * @return bool
+     */
+    protected static function destroyAddress($address, $connection_id)
+    {
+        $gateway_data                  = GatewayProtocol::$empty;
+        $gateway_data['cmd']           = GatewayProtocol::CMD_DESTROY;
+        $gateway_data['connection_id'] = $connection_id;
+        return static::sendToGateway($address, $gateway_data);
+    }
+
+    /**
+     * 将clientid数组转换成address数组
+     *
+     * @param array $client_id_array
+     * @return array
+     */
+    protected static function clientIdArrayToAddressArray(array $client_id_array)
+    {
+        $address_connection_array = array();
+        foreach ($client_id_array as $client_id) {
+            $address_data = Context::clientIdToAddress($client_id);
+            if ($address_data) {
+                $address                                                            = long2ip($address_data['local_ip']) .
+                    ":{$address_data['local_port']}";
+                $address_connection_array[$address][$address_data['connection_id']] = $address_data['connection_id'];
+            }
+        }
+        return $address_connection_array;
+    }
+
+    /**
+     * 设置 gateway 实例
+     *
+     * @param \GatewayWorker\BusinessWorker $business_worker_instance
+     */
+    public static function setBusinessWorker($business_worker_instance)
+    {
+        static::$businessWorker = $business_worker_instance;
+    }
+
+    /**
+     * 获取通过注册中心获取所有 gateway 通讯地址
+     *
+     * @return array
+     * @throws Exception
+     */
+    protected static function getAllGatewayAddressesFromRegister()
+    {
+        static $addresses_cache, $last_update;
+        $time_now = time();
+        $expiration_time = 1;
+        $register_addresses = (array)static::$registerAddress;
+        if(empty($addresses_cache) || $time_now - $last_update > $expiration_time) {
+            foreach ($register_addresses as $register_address) {
+                $client = stream_socket_client('tcp://' . $register_address, $errno, $errmsg, static::$connectTimeout);
+                if ($client) {
+                    break;
+                }
+            }
+            if (!$client) {
+                throw new Exception('Can not connect to tcp://' . $register_address . ' ' . $errmsg);
+            }
+
+            fwrite($client, '{"event":"worker_connect","secret_key":"' . static::$secretKey . '"}' . "\n");
+            stream_set_timeout($client, 5);
+            $ret = fgets($client, 655350);
+            if (!$ret || !$data = json_decode(trim($ret), true)) {
+                throw new Exception('getAllGatewayAddressesFromRegister fail. tcp://' .
+                    $register_address . ' return ' . var_export($ret, true));
+            }
+            $last_update = $time_now;
+            $addresses_cache = $data['addresses'];
+        }
+        if (!$addresses_cache) {
+            throw new Exception('Gateway::getAllGatewayAddressesFromRegister() with registerAddress:' .
+                json_encode(static::$registerAddress) . '  return ' . var_export($addresses_cache, true));
+        }
+        return $addresses_cache;
+    }
+
+    /**
+     * 检查群组id是否合法
+     *
+     * @param $group
+     * @return bool
+     */
+    protected static function isValidGroupId($group)
+    {
+        if (empty($group)) {
+            echo new \Exception('group('.var_export($group, true).') empty');
+            return false;
+        }
+        return true;
+    }
+}
+
+if (!class_exists('\Protocols\GatewayProtocol')) {
+    class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol');
+}

+ 216 - 0
vendor/workerman/gateway-worker/src/Protocols/GatewayProtocol.php

@@ -0,0 +1,216 @@
+<?php
+/**
+ * This file is part of workerman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace GatewayWorker\Protocols;
+
+/**
+ * Gateway 与 Worker 间通讯的二进制协议
+ *
+ * struct GatewayProtocol
+ * {
+ *     unsigned int        pack_len,
+ *     unsigned char       cmd,//命令字
+ *     unsigned int        local_ip,
+ *     unsigned short      local_port,
+ *     unsigned int        client_ip,
+ *     unsigned short      client_port,
+ *     unsigned int        connection_id,
+ *     unsigned char       flag,
+ *     unsigned short      gateway_port,
+ *     unsigned int        ext_len,
+ *     char[ext_len]       ext_data,
+ *     char[pack_length-HEAD_LEN] body//包体
+ * }
+ * NCNnNnNCnN
+ */
+class GatewayProtocol
+{
+    // 发给worker,gateway有一个新的连接
+    const CMD_ON_CONNECT = 1;
+
+    // 发给worker的,客户端有消息
+    const CMD_ON_MESSAGE = 3;
+
+    // 发给worker上的关闭链接事件
+    const CMD_ON_CLOSE = 4;
+
+    // 发给gateway的向单个用户发送数据
+    const CMD_SEND_TO_ONE = 5;
+
+    // 发给gateway的向所有用户发送数据
+    const CMD_SEND_TO_ALL = 6;
+
+    // 发给gateway的踢出用户
+    // 1、如果有待发消息,将在发送完后立即销毁用户连接
+    // 2、如果无待发消息,将立即销毁用户连接
+    const CMD_KICK = 7;
+
+    // 发给gateway的立即销毁用户连接
+    const CMD_DESTROY = 8;
+
+    // 发给gateway,通知用户session更新
+    const CMD_UPDATE_SESSION = 9;
+
+    // 获取在线状态
+    const CMD_GET_ALL_CLIENT_SESSIONS = 10;
+
+    // 判断是否在线
+    const CMD_IS_ONLINE = 11;
+
+    // client_id绑定到uid
+    const CMD_BIND_UID = 12;
+
+    // 解绑
+    const CMD_UNBIND_UID = 13;
+
+    // 向uid发送数据
+    const CMD_SEND_TO_UID = 14;
+
+    // 根据uid获取绑定的clientid
+    const CMD_GET_CLIENT_ID_BY_UID = 15;
+
+    // 加入组
+    const CMD_JOIN_GROUP = 20;
+
+    // 离开组
+    const CMD_LEAVE_GROUP = 21;
+
+    // 向组成员发消息
+    const CMD_SEND_TO_GROUP = 22;
+
+    // 获取组成员
+    const CMD_GET_CLIENT_SESSIONS_BY_GROUP = 23;
+
+    // 获取组在线连接数
+    const CMD_GET_CLIENT_COUNT_BY_GROUP = 24;
+
+    // 按照条件查找
+    const CMD_SELECT = 25;
+
+    // 获取在线的群组ID
+    const CMD_GET_GROUP_ID_LIST = 26;
+
+    // 取消分组
+    const CMD_UNGROUP = 27;
+
+    // worker连接gateway事件
+    const CMD_WORKER_CONNECT = 200;
+
+    // 心跳
+    const CMD_PING = 201;
+
+    // GatewayClient连接gateway事件
+    const CMD_GATEWAY_CLIENT_CONNECT = 202;
+
+    // 根据client_id获取session
+    const CMD_GET_SESSION_BY_CLIENT_ID = 203;
+
+    // 发给gateway,覆盖session
+    const CMD_SET_SESSION = 204;
+
+    // 当websocket握手时触发,只有websocket协议支持此命令字
+    const CMD_ON_WEBSOCKET_CONNECT = 205;
+
+    // 包体是标量
+    const FLAG_BODY_IS_SCALAR = 0x01;
+
+    // 通知gateway在send时不调用协议encode方法,在广播组播时提升性能
+    const FLAG_NOT_CALL_ENCODE = 0x02;
+
+    /**
+     * 包头长度
+     *
+     * @var int
+     */
+    const HEAD_LEN = 28;
+
+    public static $empty = array(
+        'cmd'           => 0,
+        'local_ip'      => 0,
+        'local_port'    => 0,
+        'client_ip'     => 0,
+        'client_port'   => 0,
+        'connection_id' => 0,
+        'flag'          => 0,
+        'gateway_port'  => 0,
+        'ext_data'      => '',
+        'body'          => '',
+    );
+
+    /**
+     * 返回包长度
+     *
+     * @param string $buffer
+     * @return int return current package length
+     */
+    public static function input($buffer)
+    {
+        if (strlen($buffer) < self::HEAD_LEN) {
+            return 0;
+        }
+
+        $data = unpack("Npack_len", $buffer);
+        return $data['pack_len'];
+    }
+
+    /**
+     * 获取整个包的 buffer
+     *
+     * @param mixed $data
+     * @return string
+     */
+    public static function encode($data)
+    {
+        $flag = (int)is_scalar($data['body']);
+        if (!$flag) {
+            $data['body'] = serialize($data['body']);
+        }
+        $data['flag'] |= $flag;
+        $ext_len      = strlen($data['ext_data']);
+        $package_len  = self::HEAD_LEN + $ext_len + strlen($data['body']);
+        return pack("NCNnNnNCnN", $package_len,
+            $data['cmd'], $data['local_ip'],
+            $data['local_port'], $data['client_ip'],
+            $data['client_port'], $data['connection_id'],
+            $data['flag'], $data['gateway_port'],
+            $ext_len) . $data['ext_data'] . $data['body'];
+    }
+
+    /**
+     * 从二进制数据转换为数组
+     *
+     * @param string $buffer
+     * @return array
+     */
+    public static function decode($buffer)
+    {
+        $data = unpack("Npack_len/Ccmd/Nlocal_ip/nlocal_port/Nclient_ip/nclient_port/Nconnection_id/Cflag/ngateway_port/Next_len",
+            $buffer);
+        if ($data['ext_len'] > 0) {
+            $data['ext_data'] = substr($buffer, self::HEAD_LEN, $data['ext_len']);
+            if ($data['flag'] & self::FLAG_BODY_IS_SCALAR) {
+                $data['body'] = substr($buffer, self::HEAD_LEN + $data['ext_len']);
+            } else {
+                $data['body'] = unserialize(substr($buffer, self::HEAD_LEN + $data['ext_len']));
+            }
+        } else {
+            $data['ext_data'] = '';
+            if ($data['flag'] & self::FLAG_BODY_IS_SCALAR) {
+                $data['body'] = substr($buffer, self::HEAD_LEN);
+            } else {
+                $data['body'] = unserialize(substr($buffer, self::HEAD_LEN));
+            }
+        }
+        return $data;
+    }
+}

+ 193 - 0
vendor/workerman/gateway-worker/src/Register.php

@@ -0,0 +1,193 @@
+<?php
+/**
+ * This file is part of workerman.
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the MIT-LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @author    walkor<walkor@workerman.net>
+ * @copyright walkor<walkor@workerman.net>
+ * @link      http://www.workerman.net/
+ * @license   http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace GatewayWorker;
+
+use Workerman\Worker;
+use Workerman\Lib\Timer;
+
+/**
+ *
+ * 注册中心,用于注册 Gateway 和 BusinessWorker
+ *
+ * @author walkor<walkor@workerman.net>
+ *
+ */
+class Register extends Worker
+{
+    /**
+     * {@inheritdoc}
+     */
+    public $name = 'Register';
+
+    /**
+     * {@inheritdoc}
+     */
+    public $reloadable = false;
+    
+    /**
+     * 秘钥
+     * @var string
+     */
+    public $secretKey = '';
+
+    /**
+     * 所有 gateway 的连接
+     *
+     * @var array
+     */
+    protected $_gatewayConnections = array();
+
+    /**
+     * 所有 worker 的连接
+     *
+     * @var array
+     */
+    protected $_workerConnections = array();
+
+    /**
+     * 进程启动时间
+     *
+     * @var int
+     */
+    protected $_startTime = 0;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function run()
+    {
+        // 设置 onMessage 连接回调
+        $this->onConnect = array($this, 'onConnect');
+
+        // 设置 onMessage 回调
+        $this->onMessage = array($this, 'onMessage');
+
+        // 设置 onClose 回调
+        $this->onClose = array($this, 'onClose');
+
+        // 记录进程启动的时间
+        $this->_startTime = time();
+        
+        // 强制使用text协议
+        $this->protocol = '\Workerman\Protocols\Text';
+
+        // reusePort
+        $this->reusePort = false;
+        
+        // 运行父方法
+        parent::run();
+    }
+
+    /**
+     * 设置个定时器,将未及时发送验证的连接关闭
+     *
+     * @param \Workerman\Connection\ConnectionInterface $connection
+     * @return void
+     */
+    public function onConnect($connection)
+    {
+        $connection->timeout_timerid = Timer::add(10, function () use ($connection) {
+            Worker::log("Register auth timeout (".$connection->getRemoteIp()."). See http://doc2.workerman.net/register-auth-timeout.html");
+            $connection->close();
+        }, null, false);
+    }
+
+    /**
+     * 设置消息回调
+     *
+     * @param \Workerman\Connection\ConnectionInterface $connection
+     * @param string                                    $buffer
+     * @return void
+     */
+    public function onMessage($connection, $buffer)
+    {
+        // 删除定时器
+        Timer::del($connection->timeout_timerid);
+        $data       = @json_decode($buffer, true);
+        if (empty($data['event'])) {
+            $error = "Bad request for Register service. Request info(IP:".$connection->getRemoteIp().", Request Buffer:$buffer). See http://doc2.workerman.net/register-auth-timeout.html";
+            Worker::log($error);
+            return $connection->close($error);
+        }
+        $event      = $data['event'];
+        $secret_key = isset($data['secret_key']) ? $data['secret_key'] : '';
+        // 开始验证
+        switch ($event) {
+            // 是 gateway 连接
+            case 'gateway_connect':
+                if (empty($data['address'])) {
+                    echo "address not found\n";
+                    return $connection->close();
+                }
+                if ($secret_key !== $this->secretKey) {
+                    Worker::log("Register: Key does not match ".var_export($secret_key, true)." !== ".var_export($this->secretKey, true));
+                    return $connection->close();
+                }
+                $this->_gatewayConnections[$connection->id] = $data['address'];
+                $this->broadcastAddresses();
+                break;
+            // 是 worker 连接
+            case 'worker_connect':
+                if ($secret_key !== $this->secretKey) {
+                    Worker::log("Register: Key does not match ".var_export($secret_key, true)." !== ".var_export($this->secretKey, true));
+                    return $connection->close();
+                }
+                $this->_workerConnections[$connection->id] = $connection;
+                $this->broadcastAddresses($connection);
+                break;
+            case 'ping':
+                break;
+            default:
+                Worker::log("Register unknown event:$event IP: ".$connection->getRemoteIp()." Buffer:$buffer. See http://doc2.workerman.net/register-auth-timeout.html");
+                $connection->close();
+        }
+    }
+
+    /**
+     * 连接关闭时
+     *
+     * @param \Workerman\Connection\ConnectionInterface $connection
+     */
+    public function onClose($connection)
+    {
+        if (isset($this->_gatewayConnections[$connection->id])) {
+            unset($this->_gatewayConnections[$connection->id]);
+            $this->broadcastAddresses();
+        }
+        if (isset($this->_workerConnections[$connection->id])) {
+            unset($this->_workerConnections[$connection->id]);
+        }
+    }
+
+    /**
+     * 向 BusinessWorker 广播 gateway 内部通讯地址
+     *
+     * @param \Workerman\Connection\ConnectionInterface $connection
+     */
+    public function broadcastAddresses($connection = null)
+    {
+        $data   = array(
+            'event'     => 'broadcast_addresses',
+            'addresses' => array_unique(array_values($this->_gatewayConnections)),
+        );
+        $buffer = json_encode($data);
+        if ($connection) {
+            $connection->send($buffer);
+            return;
+        }
+        foreach ($this->_workerConnections as $con) {
+            $con->send($buffer);
+        }
+    }
+}

Some files were not shown because too many files changed in this diff