├── README.md ├── ROADMAP.md ├── composer.json ├── doc ├── CLIENT.md └── SERVER.md ├── examples ├── Menu.php ├── Server.php └── TextMessage.php ├── src └── Jenner │ └── Wechat │ ├── Client │ ├── Card │ │ ├── AbstractCard.php │ │ ├── BoardingPass.php │ │ ├── Card.php │ │ ├── Code.php │ │ ├── Color.php │ │ ├── Location.php │ │ ├── LuckyMoney.php │ │ ├── MemberCard.php │ │ ├── MovieTicket.php │ │ ├── QrCode.php │ │ └── WhiteList.php │ ├── Client.php │ ├── CustomService.php │ ├── JsTicket.php │ ├── Media.php │ ├── Menu.php │ ├── MenuStructure │ │ ├── Button.php │ │ ├── ButtonMenu.php │ │ ├── ClickButton.php │ │ ├── EventButton.php │ │ ├── Factory.php │ │ └── ViewButton.php │ ├── Merchant │ │ ├── AbstractMerchant.php │ │ ├── Category.php │ │ ├── Common.php │ │ ├── Express.php │ │ ├── Good.php │ │ ├── Merchant.php │ │ ├── Order.php │ │ ├── Shelf.php │ │ └── Stock.php │ ├── Message │ │ ├── AbstractMessage.php │ │ ├── Image.php │ │ ├── Music.php │ │ ├── News.php │ │ ├── NewsItem.php │ │ ├── Text.php │ │ ├── Video.php │ │ └── Voice.php │ ├── QrCode.php │ ├── Semantic.php │ ├── ShortUrl.php │ ├── System.php │ ├── User.php │ └── UserGroup.php │ ├── Coding │ ├── ErrorCode.php │ ├── PKCS7Encoder.php │ ├── Prpcrypt.php │ ├── SHA1.php │ └── WXBizMsgCrypt.php │ ├── Config │ ├── Event.php │ └── URI.php │ ├── Exception │ ├── ResponseErrorException.php │ └── WechatException.php │ ├── Redirect.php │ ├── Request │ └── XmlRequest.php │ ├── Response │ ├── AbstractXml.php │ ├── Image.php │ ├── Media.php │ ├── Music.php │ ├── News.php │ ├── NewsItem.php │ ├── Text.php │ └── Voice.php │ ├── Server.php │ └── Tool │ └── Http.php └── tests ├── composer.json ├── composer.lock └── vendor ├── autoload.php ├── composer ├── ClassLoader.php ├── autoload_classmap.php ├── autoload_namespaces.php ├── autoload_psr4.php ├── autoload_real.php └── installed.json └── jenner └── tools ├── README.md ├── composer.json └── src └── Jenner └── Zebra └── Tools ├── CommonArray.php ├── Http.php └── Server.php /README.md: -------------------------------------------------------------------------------- 1 | Zebra-Wechat 2 | =================== 3 | 4 | 微信SDK 5 | 目前处于开发状态,目前实现了以下功能: 6 | * 接收微信服务器推送信息,对推送信息类型进行识别 7 | * 微信API客户端封装(用户管理、用户组管理、客服管理、自定义菜单管理、系统管理等) 8 | * 微信跳转验证封装 9 | * 微信卡卷支持 10 | 11 | [博客地址:www.huyanping.cn](http://www.huyanping.cn/ "程序猿始终不够") 12 | 13 | **接收微信推送示例** 14 | ```php 15 | use \Jenner\Zebra\Wechat\WechatServer; 16 | use \Jenner\Zebra\Wechat\Response\TextResponse; 17 | 18 | $token = 'you wechat token'; 19 | $server = new WechatServer($token); 20 | 21 | //处理事件前调用,无论是否有注册事件处理器 22 | $server->on('before', function(WechatServer $server, $request){ 23 | //do something 24 | }); 25 | 26 | //处理事件后调用,$result为事件处理器的返回值 27 | $server->on('after', function(WechatServer $server, $result){ 28 | //do something 29 | }); 30 | 31 | //未知消息处理器 32 | $server->on('unknown_message', function(WechatServer $server, $request){ 33 | //do something 34 | }); 35 | 36 | //未知时间处理器 37 | $server->on('unknown_event', function(WechatServer $server, $request){ 38 | //do something 39 | }); 40 | 41 | //处理微信文本消息推送 42 | $server->on('text', function(WechatServer $server, $request){ 43 | $to_user = $server->getFromUserName(); 44 | $from_user = $server->getToUserName(); 45 | $response = new TextResponse($to_user, $from_user, 'hello'); 46 | $server->send($response); 47 | $result = 'success'; 48 | 49 | //如果你定义了after的回调,这个返回值将作为参数传递给after函数 50 | return $result; 51 | }); 52 | 53 | 54 | //处理微信关注推送 55 | $server->on('subscribe', function(WechatServer $server, $request){ 56 | $to_user = $server->getFromUserName(); 57 | $from_user = $server->getToUserName(); 58 | $response = new TextResponse($to_user, $from_user, 'thx'); 59 | $server->send($response); 60 | }); 61 | 62 | ``` 63 | 64 | **主动向微信发送消息** 65 | ```php 66 | define('WECHAT_APP_ID', 'your app id'); 67 | define('WECHAT_SECRET', 'your secret'); 68 | 69 | $to_user = 'to_user_open_id'; 70 | $text = 'hello'; 71 | $text_message = new \Jenner\Zebra\Wechat\Client\Message\TextMessage($to_user, $text); 72 | $text_message->send(); 73 | ``` 74 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | Zebra-Wechat线路图 2 | ================== 3 | 4 | ### 0.1 基础框架搭建,用户接口设计 5 | * 用户接口设计 6 | * 微信推送接收服务设计 7 | * 通信底层框架搭建 8 | 9 | ### 0.3 完善工作 10 | * 补充完善微信接口 11 | * 代码重构 -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jenner/wechat", 3 | "description": "wechat api client", 4 | "license": "MIT", 5 | "keywords": ["wechat", "api"], 6 | "version": "v1.0.0", 7 | "authors": [ 8 | { 9 | "name": "Jenner", 10 | "email": "hypxm@qq.com" 11 | } 12 | ], 13 | "require": { 14 | "jenner/tools": "2.1.0", 15 | "php": ">=5.3.0" 16 | }, 17 | 18 | "autoload": { 19 | "psr-0": { 20 | "Jenner\\Zebra\\Wechat": "src/" 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /doc/CLIENT.md: -------------------------------------------------------------------------------- 1 | 微信客户端 2 | ================ -------------------------------------------------------------------------------- /doc/SERVER.md: -------------------------------------------------------------------------------- 1 | 微信推送接收器 2 | ================ -------------------------------------------------------------------------------- /examples/Menu.php: -------------------------------------------------------------------------------- 1 | 12 | array ( 13 | 'name' => '功能列表', 14 | 'type' => NULL, 15 | 'url' => '', 16 | 'key' => '1', 17 | 'sub_button' => 18 | array ( 19 | 0 => 20 | array ( 21 | 'id' => 6, 22 | 'shop_id' => 1, 23 | 'type' => 'view', 24 | 'name' => '百度', 25 | 'key' => '1', 26 | 'url' => 'http://www.baidu.com', 27 | 'pid' => 1, 28 | 'rank' => 0, 29 | 'created_at' => '2014-12-19 16:57:04', 30 | 'updated_at' => '2014-12-19 16:57:04', 31 | 'text' => '', 32 | ), 33 | 1 => 34 | array ( 35 | 'id' => 7, 36 | 'shop_id' => 1, 37 | 'type' => 'click', 38 | 'name' => '测试', 39 | 'key' => '1', 40 | 'url' => '', 41 | 'pid' => 1, 42 | 'rank' => 1, 43 | 'created_at' => '2014-12-19 16:57:42', 44 | 'updated_at' => '2014-12-19 16:57:42', 45 | 'text' => 'dddd', 46 | ), 47 | ), 48 | ), 49 | ); 50 | 51 | $button = \Jenner\Zebra\Wechat\Client\MenuStructure\Factory::create($layered_menus); 52 | 53 | 54 | define('WECHAT_APP_ID', 'your wechat app_id'); 55 | define('WECHAT_SECRET', 'your wechat secret'); 56 | 57 | $menu = new \Jenner\Zebra\Wechat\Client\Menu(); 58 | $menu->create($button); -------------------------------------------------------------------------------- /examples/Server.php: -------------------------------------------------------------------------------- 1 | on('before', function(WechatServer $server, $request){ 16 | //do something 17 | }); 18 | 19 | //处理事件后调用,$result为事件处理器的返回值 20 | $server->on('after', function(WechatServer $server, $result){ 21 | //do something 22 | }); 23 | 24 | //未知消息处理器 25 | $server->on('unknown_message', function(WechatServer $server, $request){ 26 | //do something 27 | }); 28 | 29 | //未知时间处理器 30 | $server->on('unknown_event', function(WechatServer $server, $request){ 31 | //do something 32 | }); 33 | 34 | //处理微信文本消息推送 35 | $server->on('text', function(WechatServer $server, $request){ 36 | $to_user = $server->getFromUserName(); 37 | $from_user = $server->getToUserName(); 38 | $response = new TextResponse($to_user, $from_user, 'hello'); 39 | $server->send($response); 40 | $result = 'success'; 41 | 42 | //如果你定义了after的回调,这个返回值将作为参数传递给after函数 43 | return $result; 44 | }); 45 | 46 | 47 | //处理微信关注推送 48 | $server->on('subscribe', function(WechatServer $server, $request){ 49 | $to_user = $server->getFromUserName(); 50 | $from_user = $server->getToUserName(); 51 | $response = new TextResponse($to_user, $from_user, 'thx'); 52 | $server->send($response); 53 | }); 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /examples/TextMessage.php: -------------------------------------------------------------------------------- 1 | send(); 19 | 20 | 21 | /** 22 | * 或者 你可以这样 23 | */ 24 | 25 | \Jenner\Zebra\Wechat\Client\WechatClient::registerAuthInfo('your app_id', 'your secret'); 26 | $to_user = 'to_user_open_id'; 27 | $text = 'hello'; 28 | $text_message = new \Jenner\Zebra\Wechat\Client\Message\TextMessage($to_user, $text); 29 | $text_message->send(); -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Card/AbstractCard.php: -------------------------------------------------------------------------------- 1 | card_uri_prefix = URI::CARD_PREFIX; 21 | } 22 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Card/BoardingPass.php: -------------------------------------------------------------------------------- 1 | card_uri_prefix . URI::CARD_BOARDING_PASS_CHECK_IN; 24 | return $this->request_post($uri, $card); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Card/Card.php: -------------------------------------------------------------------------------- 1 | card_uri_prefix . URI::CARD_CREATE; 23 | return $this->request_post($uri, $card); 24 | } 25 | 26 | 27 | /** 28 | * 查询卡券详情 29 | * @param $card_id 30 | * @return bool|mixed 31 | */ 32 | public function get($card_id) 33 | { 34 | $uri = $this->card_uri_prefix . URI::CARD_GET; 35 | return $this->request_post($uri, compact('card_id')); 36 | } 37 | 38 | public function update($card) 39 | { 40 | $uri = $this->card_uri_prefix . URI::CARD_UPDATE; 41 | return $this->request_post($uri, $card); 42 | } 43 | 44 | public function batchGet($offset = 0, $count = 50) 45 | { 46 | $uri = $this->card_uri_prefix . URI::CARD_BATCH_GET; 47 | return $this->request_post($uri, compact('offset', 'count')); 48 | } 49 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Card/Code.php: -------------------------------------------------------------------------------- 1 | card_uri_prefix . URI::CARD_CODE_CONSUME; 25 | return $this->request_post($uri, compact('code', 'card_id')); 26 | } 27 | 28 | /** 29 | * code解码接口 30 | * 通过 choose_card_info 获取的加密字符串 31 | * @param $encrypt_code 32 | * @return bool|mixed 33 | */ 34 | public function decrypt($encrypt_code) 35 | { 36 | $uri = $this->card_uri_prefix . URI::CARD_CODE_DECRYPT; 37 | return $this->request_post($uri, compact('encrypt_code')); 38 | } 39 | 40 | /** 41 | * 查询code 42 | * @param $code 43 | * @return bool|mixed 44 | */ 45 | public function get($code) 46 | { 47 | $uri = $this->card_uri_prefix . URI::CARD_CODE_GET; 48 | return $this->request_post($uri, compact('code')); 49 | } 50 | 51 | /** 52 | * 批量查询卡列表 53 | * @param $offset 54 | * @param $count 55 | * @return bool|mixed 56 | */ 57 | public function batchGet($offset, $count) 58 | { 59 | $uri = $this->card_uri_prefix . URI::CARD_CODE_BATCH_GET; 60 | return $this->request_post($uri, compact('offset', 'count')); 61 | } 62 | 63 | /** 64 | * 更改code 65 | * @param $code 66 | * @param $card_id 67 | * @param $new_code 68 | * @return bool|mixed 69 | */ 70 | public function update($code, $card_id, $new_code) 71 | { 72 | $uri = $this->card_uri_prefix . URI::CARD_CODE_UPDATE; 73 | return $this->request_post($uri, compact('code', 'card_id', 'new_code')); 74 | } 75 | 76 | /** 77 | * 设置卡券失效接口 78 | * @param $code 79 | * @param null $card_id 80 | * @return bool|mixed 81 | */ 82 | public function unavailable($code, $card_id = null) 83 | { 84 | $uri = $this->card_uri_prefix . URI::CARD_CODE_UNAVAILABLE; 85 | $params['code'] = $code; 86 | if (!is_null($card_id)) $params['card_id'] = $card_id; 87 | return $this->request_post($uri, $params); 88 | } 89 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Card/Color.php: -------------------------------------------------------------------------------- 1 | card_uri_prefix . URI::CARD_COLOR_GET; 19 | return $this->request_get($uri); 20 | } 21 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Card/Location.php: -------------------------------------------------------------------------------- 1 | card_uri_prefix . URI::CARD_LOCATION_BATCH_ADD; 19 | return $this->request_post($uri, $location); 20 | } 21 | 22 | public function batchGet($offset, $count) 23 | { 24 | $uri = $this->card_uri_prefix . URI::CARD_LOCATION_BATCH_GET; 25 | return $this->request_post($uri, compact('offset', 'count')); 26 | } 27 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Card/LuckyMoney.php: -------------------------------------------------------------------------------- 1 | card_uri_prefix . URI::CARD_LUCKY_MONEY_UPDATE_USER_BALANCE; 24 | return $this->request_post($uri, $card); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Card/MemberCard.php: -------------------------------------------------------------------------------- 1 | card_uri_prefix . URI::CARD_MEMBER_ACTIVATE; 24 | return $this->request_post($uri, $card); 25 | } 26 | 27 | /** 28 | * 会员卡交易 会员卡交易后每次积分及余额变更需通过接口通知微信,便于后续消息通知及其他扩展功能。 29 | * @param $member_card 30 | * @return bool|mixed 31 | */ 32 | public function updateUser($member_card) 33 | { 34 | $uri = $this->card_uri_prefix . URI::CARD_MEMBER_UPDATE; 35 | return $this->request_post($uri, $member_card); 36 | } 37 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Card/MovieTicket.php: -------------------------------------------------------------------------------- 1 | card_uri_prefix . URI::CARD_MOVIE_TICKET; 24 | return $this->request_post($uri, $card); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Card/QrCode.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/white-poto/Wechat_PHP_SDK/329c03ba617edd348ce3d8c259c81c45cf9780c5/src/Jenner/Wechat/Client/Card/QrCode.php -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Card/WhiteList.php: -------------------------------------------------------------------------------- 1 | card_uri_prefix . URI::CARD_TEST_WHITE_LIST; 24 | return $this->request_post($uri, $list); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Client.php: -------------------------------------------------------------------------------- 1 | app_id = $app_id; 57 | $this->secret = $secret; 58 | } 59 | 60 | /** 61 | * @param $callback 62 | */ 63 | public static function registerGetAccessTokenCallback($callback) 64 | { 65 | self::$get_access_token_callback = $callback; 66 | } 67 | 68 | /** 69 | * @param $callback 70 | */ 71 | public static function registerSetAccessTokenCallback($callback) 72 | { 73 | self::$set_access_token_callback = $callback; 74 | } 75 | 76 | /** 77 | * 发起普通GET请求 78 | * @param $uri 79 | * @param null $params 80 | * @return bool|mixed 81 | */ 82 | public function request_get($uri, $params = null) 83 | { 84 | return $this->request($uri, null, $params); 85 | } 86 | 87 | /** 88 | * 发起POST请求 89 | * @param $uri 90 | * @param $post_params 91 | * @param bool $file_upload 是否上传文件 92 | * @return bool|mixed 93 | */ 94 | public function request_post($uri, $post_params, $file_upload = false) 95 | { 96 | return $this->request($uri, $post_params, $file_upload); 97 | } 98 | 99 | /** 100 | * 发起带有GET参数的POST请求 101 | * @param $uri 102 | * @param null $post_params POST参数 103 | * @param null $get_params GET参数 104 | * @param bool $file_upload 是否上传文件 105 | * @return bool|mixed 106 | */ 107 | public function request($uri, $post_params = null, $get_params = null, $file_upload = false) 108 | { 109 | $access_token = $this->getAccessToken(); 110 | $get_params['access_token'] = $access_token; 111 | $query_string = http_build_query($get_params); 112 | $post_params = json_encode($post_params, JSON_UNESCAPED_UNICODE); 113 | $http = new \Jenner\Wechat\Tool\Http($uri . '?' . $query_string); 114 | $result_json = $http->POST($post_params, $file_upload); 115 | 116 | //存在errcode并且errcode不为0时,为错误返回 117 | return $this->checkResponse($result_json); 118 | } 119 | 120 | /** 121 | * 获取微信access_key 122 | * @throws \Exception 123 | * @internal param $app_id 124 | * @internal param $secret 125 | * @return mixed 126 | */ 127 | public function getAccessToken() 128 | { 129 | if ($cache = $this->getAccessTokenAndCheckExpiresIn()) { 130 | return $cache['access_token']; 131 | } 132 | 133 | $params = [ 134 | 'grant_type' => 'client_credential', 135 | 'appid' => $this->app_id, 136 | 'secret' => $this->secret, 137 | ]; 138 | $http = new \Jenner\Wechat\Tool\Http(self::API_AUTH_TOKEN); 139 | $response_json = $http->GET($params); 140 | $this->checkResponse($response_json); 141 | $result = json_decode($response_json, true); 142 | 143 | $cache['access_token'] = $result['access_token']; 144 | $cache['expires_in'] = $result['expires_in']; 145 | $cache['create_time'] = time(); 146 | if (!empty(self::$set_access_token_callback) && is_callable(self::$set_access_token_callback)) { 147 | call_user_func(self::$set_access_token_callback, $cache); 148 | } 149 | 150 | return $result['access_token']; 151 | } 152 | 153 | /** 154 | * 检查access_key是否过期 155 | * @return bool 156 | */ 157 | public function getAccessTokenAndCheckExpiresIn() 158 | { 159 | if (empty(self::$get_access_token_callback) || !is_callable(self::$get_access_token_callback)) 160 | return false; 161 | 162 | $cache = call_user_func(self::$get_access_token_callback); 163 | 164 | if (empty($cache)) return false; 165 | $now = time(); 166 | if ($now - $cache['create_time'] > $cache['expires_in']) { 167 | return false; 168 | } 169 | 170 | return $cache; 171 | } 172 | 173 | /** 174 | * 检查微信响应是否出错,如果出错,抛出异常 175 | * @param $response_json 176 | * @return mixed 177 | * @throws \Jenner\Wechat\Exception\ResponseErrorException 178 | */ 179 | public function checkResponse($response_json) 180 | { 181 | $response = json_decode($response_json, true); 182 | if (isset($response['errcode']) && $response['errcode'] != 0) { 183 | throw new ResponseErrorException($response['errmsg'], $response['errcode']); 184 | } 185 | 186 | return $response; 187 | } 188 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/CustomService.php: -------------------------------------------------------------------------------- 1 | request_get(self::API_LIST); 36 | } 37 | 38 | /** 39 | * 获取在线客服接待信息 40 | * @return bool|mixed 41 | */ 42 | public function getOnline() 43 | { 44 | return $this->request_get(self::API_ONLINE_LIST); 45 | } 46 | 47 | /** 48 | * 添加客服账号 49 | * @param $account 50 | * @param $nickname 51 | * @param $password 52 | * @return bool|mixed 53 | */ 54 | public function add($account, $nickname, $password) 55 | { 56 | $params = ['account' => $account, 'nickname' => $nickname, 'password' => $password]; 57 | return $this->request_post(self::API_ADD, $params); 58 | } 59 | 60 | /** 61 | * 设置客服信息 62 | * @param $account 63 | * @param $nickname 64 | * @param $password 65 | * @return bool|mixed 66 | */ 67 | public function update($account, $nickname, $password) 68 | { 69 | $params = ['account' => $account, 'nickname' => $nickname, 'password' => $password]; 70 | return $this->request_post(self::API_UPDATE, $params); 71 | } 72 | 73 | /** 74 | * 上传客服头像 75 | * @param $account 76 | * @param string $img_with_full_path 图片地址,绝对路径 77 | * @return bool|mixed 78 | */ 79 | public function uploadHeadImg($account, $img_with_full_path) 80 | { 81 | $get_params = ['kf_account' => $account]; 82 | $post_params = ['media' => '@' . $img_with_full_path]; 83 | return $this->request(self::API_UPLOAD_HEAD_IMG, $post_params, $get_params, true); 84 | } 85 | 86 | /** 87 | * 删除客服账号 88 | * @param $account 89 | * @return bool|mixed 90 | */ 91 | public function delete($account) 92 | { 93 | $params = ['kf_account' => $account]; 94 | return $this->request_get(self::API_DELETE, $params); 95 | } 96 | 97 | /** 98 | * 获取客服聊天记录 99 | * @param $start_time 查询开始时间,UNIX时间戳 100 | * @param $end_time 查询结束时间,UNIX时间戳,每次查询不能跨日查询 101 | * @param $open_id 普通用户的标识,对当前公众号唯一 102 | * @param $page_size 每页大小,每页最多拉取1000条 103 | * @param $page_index 查询第几页,从1开始 104 | * @throws \Jenner\Wechat\Exception\WechatException 105 | * @return bool|mixed 106 | */ 107 | public function getRecord($start_time, $end_time, $open_id, $page_size, $page_index) 108 | { 109 | if ($page_size > 1000) { 110 | throw new WechatException('page_size out of range'); 111 | } 112 | 113 | $start_date = date('Y-m-d', $start_time); 114 | $end_date = date('Y-m-d', $end_time); 115 | if ($start_date != $end_date) { 116 | throw new WechatException('param start_time and end_time cannot span multiple days'); 117 | } 118 | 119 | $params = [ 120 | 'starttime' => $start_time, 121 | 'endtime' => $end_time, 122 | 'openid' => $open_id, 123 | 'pagesize' => $page_size, 124 | 'pageindex' => $page_index, 125 | ]; 126 | 127 | return $this->request_post(self::API_RECORD, $params); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/JsTicket.php: -------------------------------------------------------------------------------- 1 | getJsApiTicket()['ticket']; 21 | } 22 | $url = "http://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"; 23 | $timestamp = time(); 24 | $nonce_str = $this->createNonceStr(); 25 | 26 | // 这里参数的顺序要按照 key 值 ASCII 码升faf序排序 27 | $string = "jsapi_ticket={$js_api_ticket}&noncestr={$nonce_str}×tamp={$timestamp}&url={$url}"; 28 | 29 | $signature = sha1($string); 30 | 31 | $signPackage = array( 32 | "appId" => $this->app_id, 33 | "nonceStr" => $nonce_str, 34 | "timestamp" => $timestamp, 35 | "url" => $url, 36 | "signature" => $signature, 37 | "rawString" => $string, 38 | 'jsapi_ticket' => $js_api_ticket, 39 | ); 40 | return $signPackage; 41 | } 42 | 43 | private function createNonceStr($length = 16) 44 | { 45 | $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 46 | $str = ""; 47 | for ($i = 0; $i < $length; $i++) { 48 | $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); 49 | } 50 | return $str; 51 | } 52 | 53 | public function getCardTicket() 54 | { 55 | return $this->getTicket('wx_card'); 56 | } 57 | 58 | public function getJsApiTicket() 59 | { 60 | return $this->getTicket('jsapi'); 61 | } 62 | 63 | public function getTicket($type) 64 | { 65 | $params = ['type' => $type]; 66 | return $this->request_get(self::API_GET, $params); 67 | } 68 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Media.php: -------------------------------------------------------------------------------- 1 | '@' . $absolute_file]; 29 | return $this->request(self::API_UPLOAD, $post_params, $get_params, true); 30 | } 31 | 32 | public function uploadImage($absolute_file) 33 | { 34 | 35 | } 36 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Menu.php: -------------------------------------------------------------------------------- 1 | request_get(self::API_GET); 27 | } 28 | 29 | /** 30 | * 删除微信菜单 31 | * @return bool|mixed 32 | */ 33 | public function delete() 34 | { 35 | return $this->request_get(self::API_DELETE); 36 | } 37 | 38 | /** 39 | * 创建微信菜单,格式如: 40 | * {"button":[ 41 | * {"type":"click","name":"今日歌曲","key":"V1001_TODAY_MUSIC"}, 42 | * {"name":"菜单","sub_button":[{"type":"view","name":"搜索","url":"http:\/\/www.soso.com\/"}, 43 | * {"type":"view","name":"视频","url":"http:\/\/v.qq.com\/"}, 44 | * {"type":"click","name":"赞一下我们","key":"V1001_GOOD"}]} 45 | * ]} 46 | * @param $menu 47 | * @return bool|mixed 48 | */ 49 | public function create($menu) 50 | { 51 | return $this->request_post(self::API_CREATE, $menu); 52 | } 53 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/MenuStructure/Button.php: -------------------------------------------------------------------------------- 1 | 40) 26 | throw new WechatException('Illegal name size'); 27 | 28 | $this->button['name'] = $name; 29 | if (!in_array($type, ['click', 'view', 'scancode_push', 'scancode_waitmsg', 'pic_sysphoto', 'pic_photo_or_album', 'pic_weixin', 'location_select']) && !is_null($type)) 30 | throw new WechatException('button type error'); 31 | 32 | if (!is_null($type)) { 33 | $this->button['type'] = $type; 34 | } 35 | } 36 | 37 | /** 38 | * @param $key 39 | * @throws \Jenner\Wechat\Exception\WechatException 40 | */ 41 | public function setKey($key) 42 | { 43 | if (empty($key)) { 44 | throw new WechatException('Illegal empty button key'); 45 | } 46 | if (strlen($key) > 128) { 47 | throw new WechatException('Illegal button key size'); 48 | } 49 | 50 | $this->button['key'] = $key; 51 | } 52 | 53 | /** 54 | * @param $url 55 | * @throws \Jenner\Wechat\Exception\WechatException 56 | */ 57 | public function setUrl($url) 58 | { 59 | if (empty($url)) { 60 | throw new WechatException('Illegal empty button url'); 61 | } 62 | if (strlen($url) > 256) { 63 | throw new WechatException('Illegal button key url'); 64 | } 65 | 66 | $this->button['url'] = $url; 67 | } 68 | 69 | /** 70 | * @param Button $button 71 | * @throws \Jenner\Wechat\Exception\WechatException 72 | */ 73 | public function addSubButton(Button $button) 74 | { 75 | if (!isset($this->button['sub_button'])) 76 | $this->button['sub_button'] = []; 77 | 78 | if (count($this->button) == 5) { 79 | throw new WechatException('too many sub buttons'); 80 | } 81 | 82 | $this->button['sub_button'][] = $button->getButton(); 83 | } 84 | 85 | public function getButton() 86 | { 87 | return $this->button; 88 | } 89 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/MenuStructure/ButtonMenu.php: -------------------------------------------------------------------------------- 1 | buttons[] = $button->getButton(); 21 | } 22 | 23 | public function create() 24 | { 25 | if (count($this->buttons) > 3 || count($this->buttons) < 1) { 26 | throw new WechatException('Illegal button size'); 27 | } 28 | foreach ($this->buttons as $button) { 29 | if (strlen($button['name']) > 16) 30 | throw new WechatException('Illegal button name size'); 31 | } 32 | return ['button' => $this->buttons]; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/MenuStructure/ClickButton.php: -------------------------------------------------------------------------------- 1 | setKey($key); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/MenuStructure/EventButton.php: -------------------------------------------------------------------------------- 1 | setKey($key); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/MenuStructure/Factory.php: -------------------------------------------------------------------------------- 1 | addSubButton($sub_button_obj); 32 | } 33 | } 34 | $button_menu->addButton($button); 35 | } 36 | 37 | return $button_menu->create(); 38 | } 39 | 40 | protected static function CreateButton($menu) 41 | { 42 | // 父级按钮创建 43 | if (empty($menu['type'])) { 44 | return new Button($menu['name']); 45 | } 46 | 47 | //点击事件类型推送按钮创建 48 | if ($menu['type'] == 'click') { 49 | if (empty($menu['key'])) 50 | throw new WechatException('button key cannot be empty'); 51 | return new ClickButton($menu['name'], $menu['key']); 52 | } //跳转页面类型按钮创建 53 | elseif ($menu['type'] == 'view') { 54 | if (empty($menu['url'])) 55 | throw new WechatException('button url cannot be empty'); 56 | return new ViewButton($menu['name'], $menu['url']); 57 | } //事件类型按钮创建 58 | else { 59 | if (empty($menu['key'])) 60 | throw new WechatException('button key cannot be empty'); 61 | return new EventButton($menu['name'], $menu['type'], $menu['key']); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/MenuStructure/ViewButton.php: -------------------------------------------------------------------------------- 1 | setUrl($url); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Merchant/AbstractMerchant.php: -------------------------------------------------------------------------------- 1 | merchant_uri_prefix = URI::MERCHANT_PREFIX; 23 | } 24 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Merchant/Category.php: -------------------------------------------------------------------------------- 1 | merchant_uri_prefix . URI::MERCHANT_CATEGORY_GET_SUB; 24 | return $this->request_post($uri, ['cate_id' => $category_id]); 25 | } 26 | 27 | /** 28 | * 获取指定子分类的所有SKU 29 | * @param $category_id 30 | * @return bool|mixed 31 | */ 32 | public function getSku($category_id) 33 | { 34 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_CATEGORY_GET_SKU; 35 | return $this->request_post($uri, ['cate_id' => $category_id]); 36 | } 37 | 38 | /** 39 | * 获取指定分类的所有属性 40 | * @param $category_id 41 | * @return bool|mixed 42 | */ 43 | public function getProperty($category_id) 44 | { 45 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_CATEGORY_GET_PROPERTY; 46 | return $this->request_post($uri, ['cate_id' => $category_id]); 47 | } 48 | 49 | 50 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Merchant/Common.php: -------------------------------------------------------------------------------- 1 | merchant_uri_prefix . URI::MERCHANT_COMMON_UPLOAD_IMG; 19 | $file_name = basename($filename_with_full_path); 20 | return $this->request($uri, ['filename' => $file_name], file_get_contents($filename_with_full_path), true); 21 | } 22 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Merchant/Express.php: -------------------------------------------------------------------------------- 1 | merchant_uri_prefix . URI::MERCHANT_EXPRESS_ADD; 24 | return $this->request_post($uri, $express); 25 | } 26 | 27 | /** 28 | * 删除邮费模板 29 | * @param $template_id 30 | * @return bool|mixed 31 | */ 32 | public function del($template_id) 33 | { 34 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_EXPRESS_DEL; 35 | return $this->request_post($uri, compact('template_id')); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Merchant/Good.php: -------------------------------------------------------------------------------- 1 | merchant_uri_prefix . URI::MERCHANT_GOOD_CREATE; 25 | return $this->request_post($uri, $product); 26 | } 27 | 28 | /** 29 | * 删除商品 30 | * @param $product_id 31 | * @return bool|mixed 32 | */ 33 | public function del($product_id) 34 | { 35 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_GOOD_DEL; 36 | return $this->request_post($uri, ['product_id' => $product_id]); 37 | } 38 | 39 | /** 40 | * 修改商品 41 | * @param $product 42 | * @return bool|mixed 43 | */ 44 | public function update($product) 45 | { 46 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_GOOD_UPDATE; 47 | return $this->request_post($uri, $product); 48 | } 49 | 50 | /** 51 | * 查询商品 52 | * @param $product_id 53 | * @return bool|mixed 54 | */ 55 | public function get($product_id) 56 | { 57 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_GOOD_GET; 58 | return $this->request_post($uri, ['product_id' => $product_id]); 59 | } 60 | 61 | /** 62 | * 获取指定状态的所有商品 63 | * @param $status 64 | * @return bool|mixed 65 | */ 66 | public function getByStatus($status) 67 | { 68 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_GOOD_GET_BY_STATUS; 69 | return $this->request_post($uri, ['status' => $status]); 70 | } 71 | 72 | /** 73 | * 商品上下架 74 | * @param $product_id 75 | * @param $status 76 | * @return bool|mixed 77 | */ 78 | public function modStatus($product_id, $status) 79 | { 80 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_GOOD_MOD_STATUS; 81 | return $this->request_post($uri, ['product_id' => $product_id, 'status' => $status]); 82 | } 83 | 84 | 85 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Merchant/Merchant.php: -------------------------------------------------------------------------------- 1 | uri_prefix = URI::MERCHANT_PREFIX; 21 | } 22 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Merchant/Order.php: -------------------------------------------------------------------------------- 1 | merchant_uri_prefix . URI::MERCHANT_ORDER_GET_BY_ID; 24 | return $this->request_post($uri, ['order_id' => $order_id]); 25 | } 26 | 27 | /** 28 | * 根据订单状态/创建时间获取订单详情 29 | */ 30 | public function getByFilter($status = null, $begin_time = null, $end_time = null) 31 | { 32 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_ORDER_GET_BY_FILTER; 33 | is_null($status) ? '' : $params['status'] = $status; 34 | is_null($begin_time) ? '' : $params['begintime'] = $begin_time; 35 | is_null($end_time) ? '' : $params['endtime'] = $end_time; 36 | 37 | return $this->request_post($uri, $params); 38 | } 39 | 40 | /** 41 | * 设置订单发货信息 42 | * @param $order_id 订单ID 43 | * @param $delivery_company 物流公司ID(参考《物流公司ID》 44 | * @param $delivery_track_no 运单ID 45 | * @param $need_delivery 商品是否需要物流(0-不需要,1-需要,无该字段默认为需要物流) 46 | * @param $is_others 是否为6.4.5表之外的其它物流公司(0-否,1-是,无该字段默认为不是其它物流公司) 47 | * @return bool|mixed 48 | */ 49 | public function setDelivery($order_id, $delivery_company, $delivery_track_no, $need_delivery, $is_others) 50 | { 51 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_ORDER_SET_DELIVERY; 52 | $params = compact('order_id', 'delivery_company', 'delivery_track_no', 'need_delivery', 'is_others'); 53 | return $this->request_post($uri, $params); 54 | } 55 | 56 | /** 57 | * 关闭订单 58 | * @param $order_id 订单ID 59 | * @return bool|mixed 60 | */ 61 | public function close($order_id) 62 | { 63 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_ORDER_CLOSE; 64 | return $this->request_post($uri, ['order_id' => $order_id]); 65 | } 66 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Merchant/Shelf.php: -------------------------------------------------------------------------------- 1 | merchant_uri_prefix . URI::MERCHANT_SHELF_ADD; 26 | return $this->request_post($uri, $shelf); 27 | } 28 | 29 | /** 30 | * 删除货架 31 | * @param $shelf_id 货架ID 32 | * @return bool|mixed 33 | */ 34 | public function del($shelf_id) 35 | { 36 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_SHELF_DEL; 37 | return $this->request_post($uri, compact('shelf_id')); 38 | } 39 | 40 | /** 41 | * 修改货架 42 | * @param $shelf 43 | * @return bool|mixed 44 | */ 45 | public function update($shelf) 46 | { 47 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_SHELF_MOD; 48 | return $this->request_post($uri, $shelf); 49 | } 50 | 51 | /** 52 | * 获取所有货架 53 | * @return bool|mixed 54 | */ 55 | public function getAll() 56 | { 57 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_SHELF_GET_ALL; 58 | return $this->request_get($uri); 59 | } 60 | 61 | /** 62 | * 根据货架ID获取货架信息 63 | * @param $shelf_id 64 | * @return bool|mixed 65 | */ 66 | public function getById($shelf_id) 67 | { 68 | $uri = $this->merchant_uri_prefix . URI::MERCHANT_SHELF_GET_BY_ID; 69 | return $this->request_post($uri, compact('shelf_id')); 70 | } 71 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Merchant/Stock.php: -------------------------------------------------------------------------------- 1 | merchant_uri_prefix . URI::MERCHANT_STOCK_ADD; 26 | $params = [ 27 | 'product_id' => $product_id, 28 | 'quantity' => $quantity, 29 | 'sku_info' => $sku_info, 30 | ]; 31 | return $this->request_post($uri, $params); 32 | } 33 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Message/AbstractMessage.php: -------------------------------------------------------------------------------- 1 | params['touser'] = $to_user; 41 | $this->uri = $this->uri_prefix . URI::MESSAGE_SEND; 42 | } 43 | 44 | /** 45 | * 获取请求微信的参数数组 46 | * @return mixed 47 | */ 48 | public function getParams() 49 | { 50 | return $this->params; 51 | } 52 | 53 | /** 54 | * 向微信发起请求 55 | * @return bool|mixed 56 | */ 57 | public function send() 58 | { 59 | $params = $this->getParams(); 60 | return $this->request($this->uri, $params); 61 | } 62 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Message/Image.php: -------------------------------------------------------------------------------- 1 | params['msgtype'] = 'image'; 24 | $this->params['image'] = []; 25 | $this->params['image']['media_id'] = $media_id; 26 | } 27 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Message/Music.php: -------------------------------------------------------------------------------- 1 | params['msgtype'] = 'music'; 27 | $this->params['music'] = []; 28 | $this->params['music']['title'] = $title; 29 | $this->params['music']['description'] = $description; 30 | $this->params['music']['musicurl'] = $music_url; 31 | $this->params['music']['hqmusicurl'] = $hq_music_url; 32 | $this->params['music']['thumb_media_id'] = $thumb_media_id; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Message/News.php: -------------------------------------------------------------------------------- 1 | params['msgtype'] = 'news'; 23 | $this->params['news'] = []; 24 | $this->params['news']['articles'] = []; 25 | foreach ($news_items as $item) { 26 | $this->params['news']['articles'][] = $item->getParams(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Message/NewsItem.php: -------------------------------------------------------------------------------- 1 | params['title'] = $title; 23 | $this->params['description'] = $description; 24 | $this->params['url'] = $url; 25 | $this->params['picurl'] = $pic_url; 26 | } 27 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Message/Text.php: -------------------------------------------------------------------------------- 1 | params['msgtype'] = 'text'; 24 | $this->params['text'] = []; 25 | $this->params['text']['content'] = $text; 26 | } 27 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Message/Video.php: -------------------------------------------------------------------------------- 1 | params['msgtype'] = 'video'; 26 | $this->params['video'] = []; 27 | $this->params['video']['media_id'] = $media_id; 28 | $this->params['video']['thumb_media_id'] = $thumb_media_id; 29 | $this->params['video']['title'] = $title; 30 | $this->params['video']['description'] = $description; 31 | 32 | } 33 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Message/Voice.php: -------------------------------------------------------------------------------- 1 | params['msgtype'] = 'voice'; 23 | $this->params['voice'] = []; 24 | $this->params['voice']['content'] = $media_id; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/QrCode.php: -------------------------------------------------------------------------------- 1 | $expire_seconds, 30 | 'action_name' => 'QR_LIMIT_SCENE', 31 | 'scene' => ['scene_id' => $scene_id] 32 | ]; 33 | return $this->request_post(self::API_CREATE, $params); 34 | } 35 | 36 | /** 37 | * 创建临时二维码 38 | * @param $expire_seconds 39 | * @param $scene_id 40 | * @return bool|mixed 41 | */ 42 | public function createTemp($expire_seconds, $scene_id) 43 | { 44 | $params = [ 45 | 'expire_seconds' => $expire_seconds, 46 | 'action_name' => 'QR_SCENE', 47 | 'scene' => ['scene_id' => $scene_id] 48 | ]; 49 | return $this->request_post(self::API_CREATE, $params); 50 | } 51 | 52 | /** 53 | * 下载二维码 54 | * @param $ticket 55 | * @return mixed 56 | * @throws \Jenner\Wechat\Exception\WechatException 57 | */ 58 | public function download($ticket) 59 | { 60 | $ticket = urlencode($ticket); 61 | $http = new Http(self::API_DOWNLOAD); 62 | $image = $http->GET(compact('ticket')); 63 | if ($http->getStatus() != 200) { 64 | $message = 'download qrcode image failed. the ticket param error'; 65 | throw new WechatException($message); 66 | } 67 | 68 | return $image; 69 | } 70 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/Semantic.php: -------------------------------------------------------------------------------- 1 | WECHAT_APP_ID, 17 | 'uid' => $uid, 18 | 'category' => $category, 19 | 20 | ]; 21 | } 22 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/ShortUrl.php: -------------------------------------------------------------------------------- 1 | 'long2short', 26 | 'long_url' => $long_url, 27 | ]; 28 | return $this->request_post(self::API_URL, $param); 29 | } 30 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/System.php: -------------------------------------------------------------------------------- 1 | request_get(self::API_CALLBACK_IP); 21 | 22 | return $result; 23 | } 24 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/User.php: -------------------------------------------------------------------------------- 1 | $openid, 'remark' => $remark]; 27 | $result = $this->request_get(self::API_UPDATE_REMARK, $params); 28 | 29 | return $result; 30 | } 31 | 32 | /** 33 | * 获取用户基本信息 34 | * @param $openid 35 | * @param string $lang 36 | * @return bool|mixed 37 | */ 38 | public function info($openid, $lang = 'zh_CN') 39 | { 40 | $params = ['openid' => $openid, 'lang' => $lang]; 41 | $response = $this->request_get(self::API_INFO, $params); 42 | 43 | return $response; 44 | } 45 | 46 | /** 47 | * 获取全部关注列表的OPEN_ID 48 | * @return mixed 49 | */ 50 | public function getAll() 51 | { 52 | $data = $openid_list = []; 53 | while (true) { 54 | if ($data) { 55 | $data = $this->request_get(self::API_GET, ['next_openid' => $data['next_openid']]); 56 | } else { 57 | $data = $this->request_get(self::API_GET); 58 | } 59 | $openid_list = array_merge($openid_list, $data['data']['openid']); 60 | if (empty($data['next_openid'])) { 61 | break; 62 | } 63 | } 64 | $result['count'] = count($openid_list); 65 | $result['total'] = $data['total']; 66 | 67 | return $result; 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/Jenner/Wechat/Client/UserGroup.php: -------------------------------------------------------------------------------- 1 | request_post(self::API_CREATE, $group); 33 | } 34 | 35 | /** 36 | * 获取分组 37 | * @return bool|mixed 38 | */ 39 | public function get() 40 | { 41 | return $this->request_get(self::API_GET); 42 | } 43 | 44 | /** 45 | * 根据OPEN_ID获取用户所属分组 46 | * @param $open_id 47 | * @return bool|mixed 48 | */ 49 | public function getByOpenId($open_id) 50 | { 51 | return $this->request_post(self::API_GET_ID, ['openid' => $open_id]); 52 | } 53 | 54 | /** 55 | * 更新分组名称 56 | * @param $group_id 57 | * @param $name 58 | * @return bool|mixed 59 | */ 60 | public function update($group_id, $name) 61 | { 62 | $params = ['group' => ['id' => $group_id, 'name' => $name]]; 63 | return $this->request_post(self::API_UPDATE, $params); 64 | } 65 | 66 | /** 67 | * 移动用户分组 68 | * @param $open_id 69 | * @param $to_group_id 70 | * @return bool|mixed 71 | */ 72 | public function userGroupUpdate($open_id, $to_group_id) 73 | { 74 | $params = ['openid' => $open_id, 'to_group_id' => $to_group_id]; 75 | return $this->request_post(self::API_MEMBER_UPDATE, $params); 76 | } 77 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Coding/ErrorCode.php: -------------------------------------------------------------------------------- 1 | 15 | *
  • -40001: 签名验证错误
  • 16 | *
  • -40002: xml解析失败
  • 17 | *
  • -40003: sha加密生成签名失败
  • 18 | *
  • -40004: encodingAesKey 非法
  • 19 | *
  • -40005: appid 校验错误
  • 20 | *
  • -40006: aes 加密失败
  • 21 | *
  • -40007: aes 解密失败
  • 22 | *
  • -40008: 解密后得到的buffer非法
  • 23 | *
  • -40009: base64加密失败
  • 24 | *
  • -40010: base64解密失败
  • 25 | *
  • -40011: 生成xml失败
  • 26 | * 27 | */ 28 | class ErrorCode 29 | { 30 | public static $OK = 0; 31 | public static $ValidateSignatureError = -40001; 32 | public static $ParseXmlError = -40002; 33 | public static $ComputeSignatureError = -40003; 34 | public static $IllegalAesKey = -40004; 35 | public static $ValidateAppidError = -40005; 36 | public static $EncryptAESError = -40006; 37 | public static $DecryptAESError = -40007; 38 | public static $IllegalBuffer = -40008; 39 | public static $EncodeBase64Error = -40009; 40 | public static $DecodeBase64Error = -40010; 41 | public static $GenReturnXmlError = -40011; 42 | } 43 | -------------------------------------------------------------------------------- /src/Jenner/Wechat/Coding/PKCS7Encoder.php: -------------------------------------------------------------------------------- 1 | 32) { 53 | $pad = 0; 54 | } 55 | return substr($text, 0, (strlen($text) - $pad)); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Coding/Prpcrypt.php: -------------------------------------------------------------------------------- 1 | key = base64_decode($k . "="); 24 | } 25 | 26 | /** 27 | * 对明文进行加密 28 | * @param string $text 需要加密的明文 29 | * @return string 加密后的密文 30 | */ 31 | public function encrypt($text, $appid) 32 | { 33 | 34 | try { 35 | //获得16位随机字符串,填充到明文之前 36 | $random = $this->getRandomStr(); 37 | $text = $random . pack("N", strlen($text)) . $text . $appid; 38 | // 网络字节序 39 | $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); 40 | $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); 41 | $iv = substr($this->key, 0, 16); 42 | //使用自定义的填充方式对明文进行补位填充 43 | $pkc_encoder = new PKCS7Encoder; 44 | $text = $pkc_encoder->encode($text); 45 | mcrypt_generic_init($module, $this->key, $iv); 46 | //加密 47 | $encrypted = mcrypt_generic($module, $text); 48 | mcrypt_generic_deinit($module); 49 | mcrypt_module_close($module); 50 | 51 | //print(base64_encode($encrypted)); 52 | //使用BASE64对加密后的字符串进行编码 53 | return array(ErrorCode::$OK, base64_encode($encrypted)); 54 | } catch (Exception $e) { 55 | //print $e; 56 | return array(ErrorCode::$EncryptAESError, null); 57 | } 58 | } 59 | 60 | /** 61 | * 对密文进行解密 62 | * @param string $encrypted 需要解密的密文 63 | * @return string 解密得到的明文 64 | */ 65 | public function decrypt($encrypted, $appid) 66 | { 67 | 68 | try { 69 | //使用BASE64对需要解密的字符串进行解码 70 | $ciphertext_dec = base64_decode($encrypted); 71 | $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); 72 | $iv = substr($this->key, 0, 16); 73 | mcrypt_generic_init($module, $this->key, $iv); 74 | 75 | //解密 76 | $decrypted = mdecrypt_generic($module, $ciphertext_dec); 77 | mcrypt_generic_deinit($module); 78 | mcrypt_module_close($module); 79 | } catch (Exception $e) { 80 | return array(ErrorCode::$DecryptAESError, null); 81 | } 82 | 83 | 84 | try { 85 | //去除补位字符 86 | $pkc_encoder = new PKCS7Encoder; 87 | $result = $pkc_encoder->decode($decrypted); 88 | //去除16位随机字符串,网络字节序和AppId 89 | if (strlen($result) < 16) 90 | return ""; 91 | $content = substr($result, 16, strlen($result)); 92 | $len_list = unpack("N", substr($content, 0, 4)); 93 | $xml_len = $len_list[1]; 94 | $xml_content = substr($content, 4, $xml_len); 95 | $from_appid = substr($content, $xml_len + 4); 96 | } catch (Exception $e) { 97 | //print $e; 98 | return array(ErrorCode::$IllegalBuffer, null); 99 | } 100 | if ($from_appid != $appid) 101 | return array(ErrorCode::$ValidateAppidError, null); 102 | return array(0, $xml_content); 103 | 104 | } 105 | 106 | 107 | /** 108 | * 随机生成16位字符串 109 | * @return string 生成的字符串 110 | */ 111 | function getRandomStr() 112 | { 113 | 114 | $str = ""; 115 | $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; 116 | $max = strlen($str_pol) - 1; 117 | for ($i = 0; $i < 16; $i++) { 118 | $str .= $str_pol[mt_rand(0, $max)]; 119 | } 120 | return $str; 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Coding/SHA1.php: -------------------------------------------------------------------------------- 1 | token = $token; 31 | $this->encodingAesKey = $encodingAesKey; 32 | $this->appId = $appId; 33 | } 34 | 35 | /** 36 | * 将公众平台回复用户的消息加密打包. 37 | *
      38 | *
    1. 对要发送的消息进行AES-CBC加密
    2. 39 | *
    3. 生成安全签名
    4. 40 | *
    5. 将消息密文和安全签名打包成xml格式
    6. 41 | *
    42 | * 43 | * @param $replyMsg string 公众平台待回复用户的消息,xml格式的字符串 44 | * @param $timeStamp string 时间戳,可以自己生成,也可以用URL参数的timestamp 45 | * @param $nonce string 随机串,可以自己生成,也可以用URL参数的nonce 46 | * @param &$encryptMsg string 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串, 47 | * 当return返回0时有效 48 | * 49 | * @return int 成功0,失败返回对应的错误码 50 | */ 51 | public function encryptMsg($replyMsg, $timeStamp, $nonce, &$encryptMsg) 52 | { 53 | $pc = new Prpcrypt($this->encodingAesKey); 54 | 55 | //加密 56 | $array = $pc->encrypt($replyMsg, $this->appId); 57 | $ret = $array[0]; 58 | if ($ret != 0) { 59 | return $ret; 60 | } 61 | 62 | if ($timeStamp == null) { 63 | $timeStamp = time(); 64 | } 65 | $encrypt = $array[1]; 66 | 67 | //生成安全签名 68 | $sha1 = new SHA1; 69 | $array = $sha1->getSHA1($this->token, $timeStamp, $nonce, $encrypt); 70 | $ret = $array[0]; 71 | if ($ret != 0) { 72 | return $ret; 73 | } 74 | $signature = $array[1]; 75 | 76 | //生成发送的xml 77 | $xmlparse = new XMLParse; 78 | $encryptMsg = $xmlparse->generate($encrypt, $signature, $timeStamp, $nonce); 79 | return ErrorCode::$OK; 80 | } 81 | 82 | 83 | /** 84 | * 检验消息的真实性,并且获取解密后的明文. 85 | *
      86 | *
    1. 利用收到的密文生成安全签名,进行签名验证
    2. 87 | *
    3. 若验证通过,则提取xml中的加密消息
    4. 88 | *
    5. 对消息进行解密
    6. 89 | *
    90 | * 91 | * @param $msgSignature string 签名串,对应URL参数的msg_signature 92 | * @param $timestamp string 时间戳 对应URL参数的timestamp 93 | * @param $nonce string 随机串,对应URL参数的nonce 94 | * @param $postData string 密文,对应POST请求的数据 95 | * @param &$msg string 解密后的原文,当return返回0时有效 96 | * 97 | * @return int 成功0,失败返回对应的错误码 98 | */ 99 | public function decryptMsg($msgSignature, $timestamp = null, $nonce, $postData, &$msg) 100 | { 101 | if (strlen($this->encodingAesKey) != 43) { 102 | return ErrorCode::$IllegalAesKey; 103 | } 104 | 105 | $pc = new Prpcrypt($this->encodingAesKey); 106 | 107 | //提取密文 108 | $xmlparse = new XMLParse; 109 | $array = $xmlparse->extract($postData); 110 | $ret = $array[0]; 111 | 112 | if ($ret != 0) { 113 | return $ret; 114 | } 115 | 116 | if ($timestamp == null) { 117 | $timestamp = time(); 118 | } 119 | 120 | $encrypt = $array[1]; 121 | $touser_name = $array[2]; 122 | 123 | //验证安全签名 124 | $sha1 = new SHA1; 125 | $array = $sha1->getSHA1($this->token, $timestamp, $nonce, $encrypt); 126 | $ret = $array[0]; 127 | 128 | if ($ret != 0) { 129 | return $ret; 130 | } 131 | 132 | $signature = $array[1]; 133 | if ($signature != $msgSignature) { 134 | return ErrorCode::$ValidateSignatureError; 135 | } 136 | 137 | $result = $pc->decrypt($encrypt, $this->appId); 138 | if ($result[0] != 0) { 139 | return $result[0]; 140 | } 141 | $msg = $result[1]; 142 | 143 | return ErrorCode::$OK; 144 | } 145 | 146 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Config/Event.php: -------------------------------------------------------------------------------- 1 | redirectToWechat($redirect_uri, 'snsapi_base'); 40 | } 41 | 42 | /** 43 | * 跳转到用户认证页面 44 | * @param $redirect_uri 45 | */ 46 | public function userInfoRedirect($redirect_uri) 47 | { 48 | $this->redirectToWechat($redirect_uri, 'snsapi_userinfo'); 49 | } 50 | 51 | //跳转到微信认证 52 | protected function redirectToWechat($redirect_uri, $scope = 'snsapi_base') 53 | { 54 | $response_type = 'code'; 55 | $redirect_uri = WechatConfig::REDIRECT_AUTH . '?appid=' 56 | . WECHAT_APP_ID . '&redirect_uri=' . urlencode($redirect_uri) 57 | . '&response_type=' . $response_type 58 | . '&scope=' . $scope . '#wechat_redirect'; 59 | 60 | header('Location:' . $redirect_uri); 61 | } 62 | 63 | public function baseInfo($code) 64 | { 65 | $params = [ 66 | 'appid' => WECHAT_APP_ID, 67 | 'secret' => WECHAT_SECRET, 68 | 'code' => $code, 69 | 'grant_type' => 'authorization_code', 70 | ]; 71 | $uri = WechatConfig::REDIRECT_TOKEN; 72 | $http = new Http($uri); 73 | $response_json = $http->GET($params); 74 | 75 | return $this->checkResponse($response_json); 76 | } 77 | 78 | public function userInfo($access_token, $openid, $lang = 'zh_CN') 79 | { 80 | $params = [ 81 | 'access_token' => $access_token, 82 | 'openid' => $openid, 83 | 'lang' => $lang, 84 | ]; 85 | $uri = WechatConfig::REDIRECT_USER_INFO; 86 | $http = new Http($uri); 87 | $response_json = $http->GET($params); 88 | 89 | $response = json_decode($response_json, true); 90 | if (isset($response['errcode'])) { 91 | throw new ResponseErrorException($response['errmsg'], $response['errcode']); 92 | } 93 | 94 | return $response; 95 | } 96 | 97 | /** 98 | * 检查微信响应是否出错,如果出错,抛出异常 99 | * @param $response_json 100 | * @return mixed 101 | * @throws \Jenner\Wechat\Exception\ResponseErrorException 102 | */ 103 | public function checkResponse($response_json) 104 | { 105 | $response = json_decode($response_json, true); 106 | if (isset($response['errcode'])) { 107 | throw new ResponseErrorException($response['errmsg'], $response['errcode']); 108 | } 109 | 110 | return $response; 111 | } 112 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Request/XmlRequest.php: -------------------------------------------------------------------------------- 1 | $val) { 26 | $val = (is_array($val)) || is_object($val) ? self::object_to_array($val) : $val; 27 | $arr[$key] = $val; 28 | } 29 | 30 | return $_arr; 31 | } 32 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Response/AbstractXml.php: -------------------------------------------------------------------------------- 1 | replace['ToUserName'] = $to_user; 35 | $this->replace['FromUserName'] = $from_user; 36 | $this->initTemplate(); 37 | } 38 | 39 | /** 40 | * 生成响应主体 41 | * @return mixed 42 | */ 43 | abstract protected function initTemplate(); 44 | 45 | /** 46 | * @return mixed 47 | */ 48 | public function create() 49 | { 50 | $this->replace['CreateTime'] = time(); 51 | $replace_keys = array_keys($this->replace); 52 | $response = $this->template; 53 | foreach ($replace_keys as $key) { 54 | //字符串转换,使其支持下划线、中划线写法 55 | if (strstr($key, '-') || strstr($key, '_')) { 56 | $search = '{' . $this->studlyCase($key) . '}'; 57 | } else { 58 | $search = '{' . $key . '}'; 59 | } 60 | if (!strstr($response, $search)) continue; 61 | $response = str_replace($search, $this->replace[$key], $response); 62 | } 63 | 64 | return $response; 65 | } 66 | 67 | /** 68 | * 将foo_bar foo-bar格式的字符串转换为FooBar 69 | * @param $value 70 | * @return mixed 71 | */ 72 | protected function studlyCase($value) 73 | { 74 | $value = ucwords(str_replace(array('-', '_'), ' ', $value)); 75 | 76 | return str_replace(' ', '', $value); 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Response/Image.php: -------------------------------------------------------------------------------- 1 | replace['MediaId'] = $media_id; 19 | } 20 | 21 | protected function initTemplate() 22 | { 23 | $this->template = << 25 | 26 | 27 | {CreateTime} 28 | 29 | 30 | 31 | 32 | 33 | XML; 34 | } 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/Jenner/Wechat/Response/Media.php: -------------------------------------------------------------------------------- 1 | replace['MediaId'] = $media_id; 19 | } 20 | 21 | protected function initTemplate() 22 | { 23 | $this->template = << 25 | 26 | 27 | {CreateTime} 28 | 29 | 30 | 31 | 32 | 33 | XML; 34 | } 35 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Response/Music.php: -------------------------------------------------------------------------------- 1 | replace['Title'] = $title; 19 | $this->replace['Description'] = $description; 20 | $this->replace['MusicUrl'] = $music_url; 21 | $this->replace['HQMusicUrl'] = $hq_music_url; 22 | $this->replace['ThumbMediaId'] = $thumb_media_id; 23 | } 24 | 25 | protected function initTemplate() 26 | { 27 | $this->template = << 29 | 30 | 31 | {CreateTime} 32 | 33 | 34 | <![CDATA[{Title}]]> 35 | 36 | 37 | 38 | 39 | 40 | 41 | XML; 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Response/News.php: -------------------------------------------------------------------------------- 1 | items = $items; 24 | } 25 | 26 | public function create() 27 | { 28 | $response = $this->create(); 29 | $item_response = ''; 30 | foreach ($this->items as $item) { 31 | $item_response .= $item->create(); 32 | } 33 | $response = str_replace('{item}', $item_response, $response); 34 | 35 | return $response; 36 | } 37 | 38 | protected function initTemplate() 39 | { 40 | $this->template = << 42 | 43 | 44 | {CreateTime} 45 | 46 | {ArticleCount} 47 | 48 | {item} 49 | 50 | 51 | XML; 52 | } 53 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Response/NewsItem.php: -------------------------------------------------------------------------------- 1 | replace['Title'] = $title; 18 | $this->replace['Description'] = $description; 19 | $this->replace['PicUrl'] = $pic_url; 20 | $this->replace['Url'] = $url; 21 | } 22 | 23 | /** 24 | * @return mixed 25 | */ 26 | protected function initTemplate() 27 | { 28 | $this->item_template = << 30 | <![CDATA[{Title}]]> 31 | 32 | 33 | 34 | 35 | XML; 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Response/Text.php: -------------------------------------------------------------------------------- 1 | replace['Content'] = $content; 18 | } 19 | 20 | protected function initTemplate() 21 | { 22 | $this->template = << 24 | 25 | 26 | {CreateTime} 27 | 28 | 29 | 30 | XML; 31 | } 32 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Response/Voice.php: -------------------------------------------------------------------------------- 1 | replace['MediaId'] = $media_id; 18 | } 19 | 20 | protected function initTemplate() 21 | { 22 | $this->template = << 24 | 25 | 26 | {CreateTime} 27 | 28 | 29 | 30 | 31 | 32 | XML; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Jenner/Wechat/Server.php: -------------------------------------------------------------------------------- 1 | send方法向微信返回消息 26 | * $request是微信请求的信息, 27 | * 同时你也可以通过$server->getRequest方法读取request中的信息,另种方式任你选择 28 | * after函数接收两个参数,WechatServer $server, $result,$result为事件处理函数的返回值 29 | * 30 | * 微信消息推送列表(不区分大小写): 31 | * text 32 | * image 33 | * voice 34 | * location 35 | * link 36 | * 37 | * 38 | * 微信事件推送列表 39 | * subscribe 关注事件 40 | * unsubscribe 取消关注事件 41 | * SCAN 扫描带参数二维码事件 42 | * LOCATION 上报地理位置事件 43 | * CLICK 点击菜单拉取消息时的事件推送 44 | * VIEW 点击菜单跳转链接时的事件推送 45 | * scancode_push 扫码推事件的事件推送 46 | * scancode_waitmsg 扫码推事件且弹出“消息接收中”提示框的事件推送 47 | * pic_sysphoto 弹出系统拍照发图的事件推送 48 | * pic_photo_or_album 弹出拍照或者相册发图的事件推送 49 | * pic_weixin 弹出微信相册发图器的事件推送 50 | * location_select 弹出地理位置选择器的事件推送 51 | * merchant_order 订单付款时间 52 | * 53 | * card_pass_check 生成的卡券通过审核 54 | * card_not_pass_check 卡券未通过审核 55 | * user_get_card 用户领取卡券 56 | * user_del_card 用户删除卡券 57 | * 58 | * 自定义事件 59 | * unknown_event 未知事件推送 60 | * unknown_message 未知消息推送 61 | * 62 | * Class WechatServer 63 | * @package Jenner\Wechat 64 | */ 65 | class WechatServer 66 | { 67 | 68 | const UNKNOWN_EVENT = 'unknown_event'; //未知事件推送 69 | const UNKNOWN_MESSAGE = 'unknown_message'; //未知消息推送 70 | 71 | const SUBSCRIBE = 'subscribe'; //关注事件 72 | const UNSUBSCRIBE = 'unsubscribe'; //取消关注事件 73 | const SCAN = 'SCAN'; //扫描带参数二维码事件 74 | const LOCATION = 'LOCATION'; //上报地理位置事件 75 | const CLICK = 'CLICK'; //点击菜单拉取消息时的事件推送 76 | const VIEW = 'VIEW'; //点击菜单跳转链接时的事件推送 77 | const SCANCODE_PUSH = 'scancode_push'; //扫码推事件的事件推送 78 | const SCANCODE_WAITMSG = 'scancode_waitmsg'; //扫码推事件且弹出“消息接收中”提示框的事件推送 79 | const PIC_SYSPHOTO = 'pic_sysphoto'; //弹出系统拍照发图的事件推送 80 | const PIC_PHOTO_OR_ALBUM = 'pic_photo_or_album'; //弹出拍照或者相册发图的事件推送 81 | const PIC_WEIXIN = 'pic_weixin'; //弹出微信相册发图器的事件推送 82 | const LOCATION_SELECT = 'location_select'; //弹出地理位置选择器的事件推送 83 | const MERCHANT_ORDER = 'merchant_order'; //订单付款时间 84 | const CARD_PASS_CHECK = 'card_pass_check'; //生成的卡券通过审核 85 | const CARD_NOT_PASS_CHECK = 'card_not_pass_check'; //卡券未通过审核 86 | const USER_GET_CARD = 'user_get_card'; //用户领取卡券 87 | const USER_DEL_CARD = 'user_del_card'; //用户删除卡券 88 | 89 | /** 90 | * 微信账号的token,验证消息真实性时需要用到 91 | */ 92 | protected $token; 93 | 94 | /** 95 | * 微信发送的请求包,数组格式,下标统一转换为了小写 96 | */ 97 | protected $request; 98 | 99 | 100 | /** 101 | * 微信推送回调函数数组 102 | */ 103 | protected $callback; 104 | 105 | /** 106 | * 构造函数 107 | * @param $token 微信账号的token 108 | */ 109 | public function __construct($token) 110 | { 111 | $this->token = $token; 112 | } 113 | 114 | /** 115 | * 注册推送事件回调函数 116 | * @param $event 117 | * @param $callback 118 | */ 119 | public function on($event, $callback) 120 | { 121 | $event = strtolower($event); 122 | $this->callback[$event] = $callback; 123 | } 124 | 125 | /** 126 | * 解除推送事件回调函数 127 | * @param $event 128 | */ 129 | public function off($event) 130 | { 131 | $event = strtolower($event); 132 | unset($this->callback[$event]); 133 | } 134 | 135 | /** 136 | * 微信消息、时间推送执行入口 137 | */ 138 | public function start() 139 | { 140 | //验证是否为微信验证服务器 141 | if ($this->isValid()) { 142 | echo $_GET['echostr']; 143 | return; 144 | } 145 | 146 | //验证是否是微信发来的请求 147 | if (!$this->validateSignature($this->token)) { 148 | throw new WechatException('wechat request Illegal'); 149 | } 150 | 151 | if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) { 152 | throw new WechatException('HTTP_RAW_POST_DATA empty'); 153 | } 154 | 155 | $request_xml = $GLOBALS['HTTP_RAW_POST_DATA']; 156 | $request = XmlRequest::toArray($request_xml); 157 | //将下标统一转换为小写,获取信息统一使用$this->getRequest('field_name'); 158 | $this->request = array_change_key_case($request, CASE_LOWER); 159 | 160 | //调用before回调 161 | if (!empty($this->callback['before']) && is_callable($this->callback['before'])) { 162 | $result = call_user_func($this->callback['before'], $this, $request); 163 | } 164 | 165 | //处理事件推送 166 | if ($this->getMsgType() == 'event') { 167 | 168 | //处理全局事件回调 169 | if (!empty($this->callback['before_event']) && is_callable($this->callback['before_event'])) { 170 | $result = call_user_func($this->callback['before_event'], $this, $request); 171 | } 172 | 173 | //处理事件回调 174 | $event_type = $this->getEvent(); 175 | $event_type = strtolower($event_type); 176 | if (!empty($this->callback[$event_type]) && is_callable($this->callback[$event_type])) { 177 | $result = call_user_func($this->callback[$event_type], $this, $request); 178 | } else { 179 | //未定义时间回调处理 180 | if (!empty($this->callback['unknown_event']) && is_callable($this->callback['unknown_event'])) { 181 | $result = call_user_func($this->callback['unknown_event'], $this, $request); 182 | } 183 | } 184 | } else { 185 | 186 | //处理全局消息推送回调 187 | if (!empty($this->callback['before_message']) && is_callable($this->callback['before_message'])) { 188 | $result = call_user_func($this->callback['before_message'], $this, $request); 189 | } 190 | 191 | //处理消息推送回调 192 | $message_type = $this->getMsgType(); 193 | $message_type = strtolower($message_type); 194 | if (!empty($this->callback[$message_type]) && is_callable($this->callback[$message_type])) { 195 | $result = call_user_func($this->callback[$message_type], $this, $request); 196 | } else { 197 | //处理未知消息推送回调 198 | if (!empty($this->callback['unknown_message']) && is_callable($this->callback['unknown_message'])) { 199 | $result = call_user_func($this->callback['unknown_message'], $this, $request); 200 | } 201 | } 202 | } 203 | 204 | //全局处理结束回调 205 | if (!empty($this->callback['after']) && is_callable($this->callback['after'])) { 206 | call_user_func($this->callback['after'], $this, $result); 207 | } 208 | 209 | } 210 | 211 | /** 212 | * 向服务器发送消息 213 | * @param AbstractXml $response 214 | */ 215 | public function send(AbstractXml $response) 216 | { 217 | $message = $response->create(); 218 | echo $message; 219 | return; 220 | } 221 | 222 | /** 223 | * 获取本次请求中的参数,不区分大小 224 | * 225 | * @param bool|string $param 参数名,默认为无参 226 | * @return mixed 227 | */ 228 | public function getRequest($param = FALSE) 229 | { 230 | if ($param === false) { 231 | return $this->request; 232 | } 233 | $param = strtolower($param); 234 | if (isset($this->request[$param])) { 235 | return $this->request[$param]; 236 | } 237 | return null; 238 | } 239 | 240 | /** 241 | * 获取消息发送者open_id 242 | * @return mixed 243 | */ 244 | public function getFromUserName() 245 | { 246 | return $this->getRequest('FromUserName'); 247 | } 248 | 249 | /** 250 | * 获取消息接受者(一般是自己)的open_id 251 | * @return mixed 252 | */ 253 | public function getToUserName() 254 | { 255 | return $this->getRequest('ToUserName'); 256 | } 257 | 258 | /** 259 | * 获取消息创建时间 260 | * @return mixed 261 | */ 262 | public function getCreateTime() 263 | { 264 | return $this->getRequest('CreateTime'); 265 | } 266 | 267 | /** 268 | * 获取消息类型 269 | * @return mixed 270 | */ 271 | public function getMsgType() 272 | { 273 | return $this->getRequest('MsgType'); 274 | } 275 | 276 | /** 277 | * 获取事件名称 278 | * @return mixed 279 | */ 280 | public function getEvent() 281 | { 282 | return $this->getRequest('Event'); 283 | } 284 | 285 | /** 286 | * 获取消息ID 287 | * @return mixed 288 | */ 289 | public function getMsgId() 290 | { 291 | return $this->getRequest('MsgId'); 292 | } 293 | 294 | /** 295 | * 验证消息真实性 296 | * 297 | * @param string $token 验证信息 298 | * @return boolean 299 | */ 300 | public function validateSignature($token) 301 | { 302 | if (!(isset($_GET['signature']) && isset($_GET['timestamp']) && isset($_GET['nonce']))) { 303 | return false; 304 | } 305 | 306 | $signature = $_GET['signature']; 307 | $timestamp = $_GET['timestamp']; 308 | $nonce = $_GET['nonce']; 309 | $signatureArray = [$token, $timestamp, $nonce]; 310 | sort($signatureArray, SORT_STRING); 311 | return sha1(implode($signatureArray)) == $signature; 312 | } 313 | 314 | /** 315 | * 修改微信API配置时,微信会发送验证到URL 316 | * 判断此次请求是否为验证请求 317 | * 如果是,你需要在上层直接输出原始的echostr并返回 318 | * 319 | * @return boolean 320 | */ 321 | public function isValid() 322 | { 323 | return isset($_GET['echostr']); 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/Jenner/Wechat/Tool/Http.php: -------------------------------------------------------------------------------- 1 | url = $url; 40 | $this->timeOut = $timeOut; 41 | $this->connectTimeout = $connectTimeout; 42 | //代理服务器I的设置 43 | if ($proxyIp) { 44 | $this->proxyIp = $proxyIp; 45 | $this->proxyPort = $proxyPort ? $proxyPort : 80; 46 | } 47 | } 48 | 49 | /** 50 | * 设置代理服务器 51 | * @param $ip 52 | * @param int $port 53 | */ 54 | public function setProxy($ip, $port = 80) 55 | { 56 | if ($ip) { 57 | $this->proxyIp = $ip; 58 | $this->proxyPort = $port; 59 | } 60 | } 61 | 62 | /** 63 | * GET请求 64 | * @param null $params 65 | * @return mixed 66 | */ 67 | public function GET($params = null) 68 | { 69 | //组合带参数的URL 70 | $url = &$this->url; 71 | if ($params && is_array($params)) { 72 | $url .= '?'; 73 | $amp = ''; 74 | foreach ($params as $paramKey => $paramValue) { 75 | $url .= $amp . $paramKey . '=' . urlencode($paramValue); 76 | $amp = '&'; 77 | } 78 | } 79 | //初始化curl 80 | $curl = curl_init(); 81 | $this->initProxy($curl); 82 | $this->initCurlParam($curl); 83 | $content = curl_exec($curl); 84 | $this->httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE); 85 | curl_close($curl); 86 | return $content; 87 | } 88 | 89 | /** 90 | * POST请求,支持文件上传 91 | * 文件上传的params格式['key'=>'@file_path/filename'] 92 | * @param null $params 93 | * @param bool $fileUpload 94 | * @return mixed 95 | */ 96 | public function POST($params = null, $fileUpload = false) 97 | { 98 | //初始化curl 99 | $curl = curl_init(); 100 | $this->initProxy($curl); 101 | $this->initCurlParam($curl); 102 | //设置POST参数 103 | curl_setopt($curl, CURLOPT_POST, 1); 104 | if ($params && is_array($params)) { 105 | if ($fileUpload) { 106 | curl_setopt($curl, CURLOPT_POSTFIELDS, $params); 107 | } else { 108 | $amp = ''; 109 | $postFields = ''; 110 | foreach ($params as $paramKey => $paramValue) { 111 | $postFields .= $amp . $paramKey . '=' . urlencode($paramValue); 112 | $amp = '&'; 113 | } 114 | curl_setopt($curl, CURLOPT_POSTFIELDS, $postFields); 115 | } 116 | } elseif ($params) { 117 | curl_setopt($curl, CURLOPT_POSTFIELDS, $params); 118 | } 119 | $content = curl_exec($curl); 120 | $this->httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE); 121 | curl_close($curl); 122 | return $content; 123 | } 124 | 125 | /** 126 | * 获取HTTP状态码 127 | * @return mixed 128 | */ 129 | public function getStatus() 130 | { 131 | return $this->httpStatus; 132 | } 133 | 134 | /** 135 | * 初始化代理 136 | * @param $curl 137 | */ 138 | private function initProxy($curl) 139 | { 140 | if ($this->proxyIp && $this->proxyPort) { 141 | $proxy = "http://{$this->proxyIp}:{$this->proxyPort}"; 142 | curl_setopt($curl, CURLOPT_PROXY, $proxy); 143 | } 144 | } 145 | 146 | /** 147 | * 初始化CURL参数 148 | * @param $curl 149 | */ 150 | private function initCurlParam($curl) 151 | { 152 | curl_setopt($curl, CURLOPT_URL, $this->url); 153 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 154 | curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); 155 | curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $this->connectTimeout); 156 | curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeOut); 157 | } 158 | } -------------------------------------------------------------------------------- /tests/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | { 4 | "name": "Jenner", 5 | "email": "hypxm@qq.com", 6 | "homepage" : "http://www.huyanping.cn" 7 | } 8 | ], 9 | "require": { 10 | "jenner/wechat": "dev-master", 11 | "php": ">=5.3.0" 12 | } 13 | } -------------------------------------------------------------------------------- /tests/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "0527fd011fa5d183f1a458882cb8af21", 8 | "packages": [ 9 | { 10 | "name": "jenner/tools", 11 | "version": "v1.2.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/huyanping/Zebra-Tools.git", 15 | "reference": "bfd0533f8071eaf12778f3e1a47a3bab9dfd7e09" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/huyanping/Zebra-Tools/zipball/bfd0533f8071eaf12778f3e1a47a3bab9dfd7e09", 20 | "reference": "bfd0533f8071eaf12778f3e1a47a3bab9dfd7e09", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=5.3.0" 25 | }, 26 | "type": "library", 27 | "autoload": { 28 | "psr-0": { 29 | "Jenner\\Zebra\\Tools": "src/" 30 | } 31 | }, 32 | "notification-url": "https://packagist.org/downloads/", 33 | "license": [ 34 | "MIT" 35 | ], 36 | "authors": [ 37 | { 38 | "name": "Jenner", 39 | "email": "hypxm@qq.com" 40 | } 41 | ], 42 | "description": "common static functions", 43 | "keywords": [ 44 | "tools", 45 | "utils" 46 | ], 47 | "time": "2014-11-17 08:15:54" 48 | }, 49 | { 50 | "name": "jenner/wechat", 51 | "version": "dev-master", 52 | "source": { 53 | "type": "git", 54 | "url": "git@github.com:huyanping/Zebra-Wechat.git", 55 | "reference": "cde2c63f90c976dd3be4611cabb1948448708dc9" 56 | }, 57 | "dist": { 58 | "type": "zip", 59 | "url": "https://api.github.com/repos/huyanping/Zebra-Wechat/zipball/cde2c63f90c976dd3be4611cabb1948448708dc9", 60 | "reference": "cde2c63f90c976dd3be4611cabb1948448708dc9", 61 | "shasum": "" 62 | }, 63 | "require": { 64 | "jenner/tools": "1.2.0", 65 | "php": ">=5.3.0" 66 | }, 67 | "type": "library", 68 | "autoload": { 69 | "psr-0": { 70 | "Jenner\\Zebra\\Wechat": "src/" 71 | } 72 | }, 73 | "notification-url": "https://packagist.org/downloads/", 74 | "license": [ 75 | "MIT" 76 | ], 77 | "authors": [ 78 | { 79 | "name": "Jenner", 80 | "email": "hypxm@qq.com" 81 | } 82 | ], 83 | "description": "wechat api client", 84 | "keywords": [ 85 | "api", 86 | "wechat" 87 | ], 88 | "time": "2014-12-14 04:06:52" 89 | } 90 | ], 91 | "packages-dev": [], 92 | "aliases": [], 93 | "minimum-stability": "stable", 94 | "stability-flags": { 95 | "jenner/wechat": 20 96 | }, 97 | "prefer-stable": false, 98 | "platform": { 99 | "php": ">=5.3.0" 100 | }, 101 | "platform-dev": [] 102 | } 103 | -------------------------------------------------------------------------------- /tests/vendor/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0 class loader 17 | * 18 | * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md 19 | * 20 | * $loader = new \Composer\Autoload\ClassLoader(); 21 | * 22 | * // register classes with namespaces 23 | * $loader->add('Symfony\Component', __DIR__.'/component'); 24 | * $loader->add('Symfony', __DIR__.'/framework'); 25 | * 26 | * // activate the autoloader 27 | * $loader->register(); 28 | * 29 | * // to enable searching the include path (eg. for PEAR packages) 30 | * $loader->setUseIncludePath(true); 31 | * 32 | * In this example, if you try to use a class in the Symfony\Component 33 | * namespace or one of its children (Symfony\Component\Console for instance), 34 | * the autoloader will first look for the class under the component/ 35 | * directory, and it will then fallback to the framework/ directory if not 36 | * found before giving up. 37 | * 38 | * This class is loosely based on the Symfony UniversalClassLoader. 39 | * 40 | * @author Fabien Potencier 41 | * @author Jordi Boggiano 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | 57 | public function getPrefixes() 58 | { 59 | return call_user_func_array('array_merge', $this->prefixesPsr0); 60 | } 61 | 62 | public function getPrefixesPsr4() 63 | { 64 | return $this->prefixDirsPsr4; 65 | } 66 | 67 | public function getFallbackDirs() 68 | { 69 | return $this->fallbackDirsPsr0; 70 | } 71 | 72 | public function getFallbackDirsPsr4() 73 | { 74 | return $this->fallbackDirsPsr4; 75 | } 76 | 77 | public function getClassMap() 78 | { 79 | return $this->classMap; 80 | } 81 | 82 | /** 83 | * @param array $classMap Class to filename map 84 | */ 85 | public function addClassMap(array $classMap) 86 | { 87 | if ($this->classMap) { 88 | $this->classMap = array_merge($this->classMap, $classMap); 89 | } else { 90 | $this->classMap = $classMap; 91 | } 92 | } 93 | 94 | /** 95 | * Registers a set of PSR-0 directories for a given prefix, either 96 | * appending or prepending to the ones previously set for this prefix. 97 | * 98 | * @param string $prefix The prefix 99 | * @param array|string $paths The PSR-0 root directories 100 | * @param bool $prepend Whether to prepend the directories 101 | */ 102 | public function add($prefix, $paths, $prepend = false) 103 | { 104 | if (!$prefix) { 105 | if ($prepend) { 106 | $this->fallbackDirsPsr0 = array_merge( 107 | (array) $paths, 108 | $this->fallbackDirsPsr0 109 | ); 110 | } else { 111 | $this->fallbackDirsPsr0 = array_merge( 112 | $this->fallbackDirsPsr0, 113 | (array) $paths 114 | ); 115 | } 116 | 117 | return; 118 | } 119 | 120 | $first = $prefix[0]; 121 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 122 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 123 | 124 | return; 125 | } 126 | if ($prepend) { 127 | $this->prefixesPsr0[$first][$prefix] = array_merge( 128 | (array) $paths, 129 | $this->prefixesPsr0[$first][$prefix] 130 | ); 131 | } else { 132 | $this->prefixesPsr0[$first][$prefix] = array_merge( 133 | $this->prefixesPsr0[$first][$prefix], 134 | (array) $paths 135 | ); 136 | } 137 | } 138 | 139 | /** 140 | * Registers a set of PSR-4 directories for a given namespace, either 141 | * appending or prepending to the ones previously set for this namespace. 142 | * 143 | * @param string $prefix The prefix/namespace, with trailing '\\' 144 | * @param array|string $paths The PSR-0 base directories 145 | * @param bool $prepend Whether to prepend the directories 146 | * 147 | * @throws \InvalidArgumentException 148 | */ 149 | public function addPsr4($prefix, $paths, $prepend = false) 150 | { 151 | if (!$prefix) { 152 | // Register directories for the root namespace. 153 | if ($prepend) { 154 | $this->fallbackDirsPsr4 = array_merge( 155 | (array) $paths, 156 | $this->fallbackDirsPsr4 157 | ); 158 | } else { 159 | $this->fallbackDirsPsr4 = array_merge( 160 | $this->fallbackDirsPsr4, 161 | (array) $paths 162 | ); 163 | } 164 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 165 | // Register directories for a new namespace. 166 | $length = strlen($prefix); 167 | if ('\\' !== $prefix[$length - 1]) { 168 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 169 | } 170 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 171 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 172 | } elseif ($prepend) { 173 | // Prepend directories for an already registered namespace. 174 | $this->prefixDirsPsr4[$prefix] = array_merge( 175 | (array) $paths, 176 | $this->prefixDirsPsr4[$prefix] 177 | ); 178 | } else { 179 | // Append directories for an already registered namespace. 180 | $this->prefixDirsPsr4[$prefix] = array_merge( 181 | $this->prefixDirsPsr4[$prefix], 182 | (array) $paths 183 | ); 184 | } 185 | } 186 | 187 | /** 188 | * Registers a set of PSR-0 directories for a given prefix, 189 | * replacing any others previously set for this prefix. 190 | * 191 | * @param string $prefix The prefix 192 | * @param array|string $paths The PSR-0 base directories 193 | */ 194 | public function set($prefix, $paths) 195 | { 196 | if (!$prefix) { 197 | $this->fallbackDirsPsr0 = (array) $paths; 198 | } else { 199 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 200 | } 201 | } 202 | 203 | /** 204 | * Registers a set of PSR-4 directories for a given namespace, 205 | * replacing any others previously set for this namespace. 206 | * 207 | * @param string $prefix The prefix/namespace, with trailing '\\' 208 | * @param array|string $paths The PSR-4 base directories 209 | * 210 | * @throws \InvalidArgumentException 211 | */ 212 | public function setPsr4($prefix, $paths) 213 | { 214 | if (!$prefix) { 215 | $this->fallbackDirsPsr4 = (array) $paths; 216 | } else { 217 | $length = strlen($prefix); 218 | if ('\\' !== $prefix[$length - 1]) { 219 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 220 | } 221 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 222 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 223 | } 224 | } 225 | 226 | /** 227 | * Turns on searching the include path for class files. 228 | * 229 | * @param bool $useIncludePath 230 | */ 231 | public function setUseIncludePath($useIncludePath) 232 | { 233 | $this->useIncludePath = $useIncludePath; 234 | } 235 | 236 | /** 237 | * Can be used to check if the autoloader uses the include path to check 238 | * for classes. 239 | * 240 | * @return bool 241 | */ 242 | public function getUseIncludePath() 243 | { 244 | return $this->useIncludePath; 245 | } 246 | 247 | /** 248 | * Registers this instance as an autoloader. 249 | * 250 | * @param bool $prepend Whether to prepend the autoloader or not 251 | */ 252 | public function register($prepend = false) 253 | { 254 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 255 | } 256 | 257 | /** 258 | * Unregisters this instance as an autoloader. 259 | */ 260 | public function unregister() 261 | { 262 | spl_autoload_unregister(array($this, 'loadClass')); 263 | } 264 | 265 | /** 266 | * Loads the given class or interface. 267 | * 268 | * @param string $class The name of the class 269 | * @return bool|null True if loaded, null otherwise 270 | */ 271 | public function loadClass($class) 272 | { 273 | if ($file = $this->findFile($class)) { 274 | includeFile($file); 275 | 276 | return true; 277 | } 278 | } 279 | 280 | /** 281 | * Finds the path to the file where the class is defined. 282 | * 283 | * @param string $class The name of the class 284 | * 285 | * @return string|false The path if found, false otherwise 286 | */ 287 | public function findFile($class) 288 | { 289 | // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 290 | if ('\\' == $class[0]) { 291 | $class = substr($class, 1); 292 | } 293 | 294 | // class map lookup 295 | if (isset($this->classMap[$class])) { 296 | return $this->classMap[$class]; 297 | } 298 | 299 | $file = $this->findFileWithExtension($class, '.php'); 300 | 301 | // Search for Hack files if we are running on HHVM 302 | if ($file === null && defined('HHVM_VERSION')) { 303 | $file = $this->findFileWithExtension($class, '.hh'); 304 | } 305 | 306 | if ($file === null) { 307 | // Remember that this class does not exist. 308 | return $this->classMap[$class] = false; 309 | } 310 | 311 | return $file; 312 | } 313 | 314 | private function findFileWithExtension($class, $ext) 315 | { 316 | // PSR-4 lookup 317 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 318 | 319 | $first = $class[0]; 320 | if (isset($this->prefixLengthsPsr4[$first])) { 321 | foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { 322 | if (0 === strpos($class, $prefix)) { 323 | foreach ($this->prefixDirsPsr4[$prefix] as $dir) { 324 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { 325 | return $file; 326 | } 327 | } 328 | } 329 | } 330 | } 331 | 332 | // PSR-4 fallback dirs 333 | foreach ($this->fallbackDirsPsr4 as $dir) { 334 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 335 | return $file; 336 | } 337 | } 338 | 339 | // PSR-0 lookup 340 | if (false !== $pos = strrpos($class, '\\')) { 341 | // namespaced class name 342 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 343 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 344 | } else { 345 | // PEAR-like class name 346 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 347 | } 348 | 349 | if (isset($this->prefixesPsr0[$first])) { 350 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 351 | if (0 === strpos($class, $prefix)) { 352 | foreach ($dirs as $dir) { 353 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 354 | return $file; 355 | } 356 | } 357 | } 358 | } 359 | } 360 | 361 | // PSR-0 fallback dirs 362 | foreach ($this->fallbackDirsPsr0 as $dir) { 363 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 364 | return $file; 365 | } 366 | } 367 | 368 | // PSR-0 include paths. 369 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 370 | return $file; 371 | } 372 | } 373 | } 374 | 375 | /** 376 | * Scope isolated include. 377 | * 378 | * Prevents access to $this/self from included files. 379 | */ 380 | function includeFile($file) 381 | { 382 | include $file; 383 | } 384 | -------------------------------------------------------------------------------- /tests/vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/jenner/wechat/src'), 10 | 'Jenner\\Zebra\\Tools' => array($vendorDir . '/jenner/tools/src'), 11 | ); 12 | -------------------------------------------------------------------------------- /tests/vendor/composer/autoload_psr4.php: -------------------------------------------------------------------------------- 1 | $path) { 28 | $loader->set($namespace, $path); 29 | } 30 | 31 | $map = require __DIR__ . '/autoload_psr4.php'; 32 | foreach ($map as $namespace => $path) { 33 | $loader->setPsr4($namespace, $path); 34 | } 35 | 36 | $classMap = require __DIR__ . '/autoload_classmap.php'; 37 | if ($classMap) { 38 | $loader->addClassMap($classMap); 39 | } 40 | 41 | $loader->register(true); 42 | 43 | return $loader; 44 | } 45 | } 46 | 47 | function composerRequire7b361b6bc0dd4fff94980c130273a18b($file) 48 | { 49 | require $file; 50 | } 51 | -------------------------------------------------------------------------------- /tests/vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "jenner/tools", 4 | "version": "v1.2.0", 5 | "version_normalized": "1.2.0.0", 6 | "source": { 7 | "type": "git", 8 | "url": "https://github.com/huyanping/Zebra-Tools.git", 9 | "reference": "bfd0533f8071eaf12778f3e1a47a3bab9dfd7e09" 10 | }, 11 | "dist": { 12 | "type": "zip", 13 | "url": "https://api.github.com/repos/huyanping/Zebra-Tools/zipball/bfd0533f8071eaf12778f3e1a47a3bab9dfd7e09", 14 | "reference": "bfd0533f8071eaf12778f3e1a47a3bab9dfd7e09", 15 | "shasum": "" 16 | }, 17 | "require": { 18 | "php": ">=5.3.0" 19 | }, 20 | "time": "2014-11-17 08:15:54", 21 | "type": "library", 22 | "installation-source": "dist", 23 | "autoload": { 24 | "psr-0": { 25 | "Jenner\\Zebra\\Tools": "src/" 26 | } 27 | }, 28 | "notification-url": "https://packagist.org/downloads/", 29 | "license": [ 30 | "MIT" 31 | ], 32 | "authors": [ 33 | { 34 | "name": "Jenner", 35 | "email": "hypxm@qq.com" 36 | } 37 | ], 38 | "description": "common static functions", 39 | "keywords": [ 40 | "tools", 41 | "utils" 42 | ] 43 | }, 44 | { 45 | "name": "jenner/wechat", 46 | "version": "dev-master", 47 | "version_normalized": "9999999-dev", 48 | "source": { 49 | "type": "git", 50 | "url": "git@github.com:huyanping/Zebra-Wechat.git", 51 | "reference": "cde2c63f90c976dd3be4611cabb1948448708dc9" 52 | }, 53 | "dist": { 54 | "type": "zip", 55 | "url": "https://api.github.com/repos/huyanping/Zebra-Wechat/zipball/cde2c63f90c976dd3be4611cabb1948448708dc9", 56 | "reference": "cde2c63f90c976dd3be4611cabb1948448708dc9", 57 | "shasum": "" 58 | }, 59 | "require": { 60 | "jenner/tools": "1.2.0", 61 | "php": ">=5.3.0" 62 | }, 63 | "time": "2014-12-14 04:06:52", 64 | "type": "library", 65 | "installation-source": "source", 66 | "autoload": { 67 | "psr-0": { 68 | "Jenner\\Zebra\\Wechat": "src/" 69 | } 70 | }, 71 | "notification-url": "https://packagist.org/downloads/", 72 | "license": [ 73 | "MIT" 74 | ], 75 | "authors": [ 76 | { 77 | "name": "Jenner", 78 | "email": "hypxm@qq.com" 79 | } 80 | ], 81 | "description": "wechat api client", 82 | "keywords": [ 83 | "api", 84 | "wechat" 85 | ] 86 | } 87 | ] 88 | -------------------------------------------------------------------------------- /tests/vendor/jenner/tools/README.md: -------------------------------------------------------------------------------- 1 | Zebra-Tools 2 | =================== 3 | 常用行数库 4 | [博客地址:www.huyanping.cn](http://www.huyanping.cn/ "程序猿始终不够") -------------------------------------------------------------------------------- /tests/vendor/jenner/tools/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jenner/tools", 3 | "description": "common static functions", 4 | "license": "MIT", 5 | "keywords": ["tools", "utils"], 6 | "version": "v1.2.0", 7 | "authors": [ 8 | { 9 | "name": "Jenner", 10 | "email": "hypxm@qq.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.3.0" 15 | }, 16 | 17 | "autoload": { 18 | "psr-0": { 19 | "Jenner\\Zebra\\Tools": "src/" 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /tests/vendor/jenner/tools/src/Jenner/Zebra/Tools/CommonArray.php: -------------------------------------------------------------------------------- 1 | $filed) ? false : $value->$filed; 44 | } 45 | return $result; 46 | } 47 | 48 | /** 49 | * 类似SQL ORDER BY 的多为数组排序函数 50 | * example: $sorted = array_orderby($data, 'volume', SORT_DESC, 'edition', SORT_ASC); 51 | * 52 | * @return mixed 53 | */ 54 | public static function arrayOrderby() 55 | { 56 | $args = \func_get_args(); 57 | $data = \array_shift($args); 58 | foreach ($args as $n => $field) { 59 | if (\is_string($field)) { 60 | $tmp = array(); 61 | foreach ($data as $key => $row) 62 | $tmp[$key] = $row[$field]; 63 | $args[$n] = $tmp; 64 | } 65 | } 66 | $args[] = & $data; 67 | \call_user_func_array('array_multisort', $args); 68 | return \array_pop($args); 69 | } 70 | 71 | /** 72 | * object 转 array 73 | */ 74 | function objectToArray($obj){ 75 | $_arr = is_object($obj)? get_object_vars($obj) : $obj; 76 | foreach ($_arr as $key => $val) { 77 | $val = (is_array($val)) || is_object($val) ? object_to_array($val) : $val; 78 | $arr[$key] = $val; 79 | } 80 | 81 | return $arr; 82 | } 83 | } -------------------------------------------------------------------------------- /tests/vendor/jenner/tools/src/Jenner/Zebra/Tools/Http.php: -------------------------------------------------------------------------------- 1 | url = $url; 25 | $this->timeOut = $timeOut; 26 | $this->transferTimeOut = $transferTimeOut; 27 | 28 | //代理服务器I的设置 29 | if ($proxyIp) { 30 | $this->proxyIp = $proxyIp; 31 | $this->proxyPort = $proxyPort ? $proxyPort : 80; 32 | } 33 | 34 | } 35 | 36 | public function setProxy($ip, $port = 80) { 37 | if ($ip) { 38 | $this->proxyIp = $ip; 39 | $this->proxyPort = $port; 40 | } 41 | } 42 | 43 | public function GET($params = null) { 44 | 45 | //组合带参数的URL 46 | $url = $this->url; 47 | if ($params && is_array($params)) { 48 | $url .= '?'; 49 | $amp = ''; 50 | foreach ($params as $paramKey => $paramValue) { 51 | $url .= $amp . $paramKey . '=' . urlencode($paramValue); 52 | $amp = '&'; 53 | } 54 | } 55 | 56 | //初始化curl 57 | $curl = curl_init(); 58 | if ($this->proxyIp && $this->proxyPort) { 59 | $proxy = "http://{$this->proxyIp}:{$this->proxyPort}"; 60 | curl_setopt($curl, CURLOPT_PROXY, $proxy); 61 | } 62 | curl_setopt($curl, CURLOPT_URL, $url); 63 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 64 | curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); 65 | curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $this->timeOut); 66 | curl_setopt($curl, CURLOPT_TIMEOUT, $this->transferTimeOut); 67 | 68 | $content = curl_exec($curl); 69 | curl_close($curl); 70 | 71 | return $content; 72 | 73 | } 74 | 75 | public function POST($params = null, $fileUpload = false) { 76 | 77 | //初始化curl 78 | $curl = curl_init(); 79 | if ($this->proxyIp && $this->proxyPort) { 80 | $proxy = "http://{$this->proxyIp}:{$this->proxyPort}"; 81 | curl_setopt($curl, CURLOPT_PROXY, $proxy); 82 | } 83 | curl_setopt($curl, CURLOPT_URL, $this->url); 84 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 85 | curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); 86 | curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $this->timeOut); 87 | curl_setopt($curl, CURLOPT_TIMEOUT, $this->transferTimeOut); 88 | 89 | //设置POST参数 90 | curl_setopt($curl, CURLOPT_POST, 1); 91 | if ($params && is_array($params)) { 92 | if ($fileUpload) { 93 | curl_setopt($curl, CURLOPT_POSTFIELDS, $params); 94 | } else { 95 | $amp = ''; 96 | $postFields = ''; 97 | foreach ($params as $paramKey => $paramValue) { 98 | $postFields .= $amp . $paramKey . '=' . urlencode($paramValue); 99 | $amp = '&'; 100 | } 101 | curl_setopt($curl, CURLOPT_POSTFIELDS, $postFields); 102 | } 103 | } 104 | 105 | $content = curl_exec($curl); 106 | curl_close($curl); 107 | 108 | return $content; 109 | 110 | } 111 | 112 | } -------------------------------------------------------------------------------- /tests/vendor/jenner/tools/src/Jenner/Zebra/Tools/Server.php: -------------------------------------------------------------------------------- 1 |