token = isset($options['token']) ? $options['token'] : ''; $this->appid = isset($options['appid']) ? $options['appid'] : ''; $this->appsecret = isset($options['appsecret']) ? $options['appsecret'] : ''; $this->debug = isset($options['debug']) ? $options['debug'] : false; $this->_logcallback = isset($options['logcallback']) ? $options['logcallback'] : false; } /** * For weixin server validation */ private function checkSignature() { $signature = isset($_GET["signature"]) ? $_GET["signature"] : ''; $timestamp = isset($_GET["timestamp"]) ? $_GET["timestamp"] : ''; $nonce = isset($_GET["nonce"]) ? $_GET["nonce"] : ''; $token = $this->token; $tmpArr = array($token, $timestamp, $nonce); sort($tmpArr, SORT_STRING); $tmpStr = implode($tmpArr); $tmpStr = sha1($tmpStr); if ($tmpStr == $signature) { return true; } else { return false; } } /** * For weixin server validation * @param bool $return 是否返回 */ public function valid($return = false) { $echoStr = isset($_GET["echostr"]) ? $_GET["echostr"] : ''; if ($return) { if ($echoStr) { if ($this->checkSignature()) return $echoStr; else return false; } else return $this->checkSignature(); } else { if ($echoStr) { if ($this->checkSignature()) die($echoStr); else die('no access'); } else { if ($this->checkSignature()) return true; else die('no access'); } } return false; } /** * 设置发送消息 * @param array $msg 消息数组 * @param bool $append 是否在原消息数组追加 */ public function Message($msg = '', $append = false) { if (is_null($msg)) { $this->_msg = array(); } elseif (is_array($msg)) { if ($append) $this->_msg = array_merge($this->_msg, $msg); else $this->_msg = $msg; //return $this->_msg; } else { //return $this->_msg; } return $this->iconvUtf($this->_msg); } public function setFuncFlag($flag) { $this->_funcflag = $flag; return $this; } private function log($log) { if ($this->debug && function_exists($this->_logcallback)) { if (is_array($log)) $log = print_r($log, true); return call_user_func($this->_logcallback, $log); } } /** * 获取微信服务器发来的信息 */ public function getRev() { if ($this->_receive) return $this; //$postStr = $GLOBALS["HTTP_RAW_POST_DATA"];// $postStr = file_get_contents("php://input"); $this->log($postStr); if (!empty($postStr)) { $this->_receive = (array) simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA); $this->_receive = $this->iconvUtf($this->_receive); } return $this; } /** * 获取微信服务器发来的信息 */ public function getRevData() { return $this->iconvUtf($this->_receive); } /** * 获取消息发送者 */ public function getRevFrom() { if (isset($this->_receive['FromUserName'])) return $this->_receive['FromUserName']; else return false; } /** * 获取消息接受者 */ public function getRevTo() { if (isset($this->_receive['ToUserName'])) return $this->_receive['ToUserName']; else return false; } /** * 获取接收消息的类型 */ public function getRevType() { if (isset($this->_receive['MsgType'])) return $this->_receive['MsgType']; else return false; } /** * 获取消息ID */ public function getRevID() { if (isset($this->_receive['MsgId'])) return $this->_receive['MsgId']; else return false; } /** * 获取消息发送时间 */ public function getRevCtime() { if (isset($this->_receive['CreateTime'])) return $this->_receive['CreateTime']; else return false; } /** * 获取接收消息内容正文 */ public function getRevContent() { if (isset($this->_receive['Content'])) return $this->_receive['Content']; else if (isset($this->_receive['Recognition'])) //获取语音识别文字内容,需申请开通 return $this->_receive['Recognition']; else return false; } /** * 获取接收消息图片 */ public function getRevPic() { if (isset($this->_receive['PicUrl'])) return $this->_receive['PicUrl']; else return false; } /** * 获取接收消息链接 */ public function getRevLink() { if (isset($this->_receive['Url'])) { return array( 'url' => $this->_receive['Url'], 'title' => $this->_receive['Title'], 'description' => $this->_receive['Description'] ); } else return false; } /** * 获取接收地理位置 */ public function getRevGeo() { if (isset($this->_receive['Location_X'])) { return array( 'x' => $this->_receive['Location_X'], 'y' => $this->_receive['Location_Y'], 'scale' => $this->_receive['Scale'], 'label' => $this->_receive['Label'] ); } else return false; } /** * 获取接收事件推送 */ public function getRevEvent() { if (isset($this->_receive['Event'])) { return array( 'event' => $this->_receive['Event'], 'key' => isset($this->_receive['EventKey'])?$this->_receive['EventKey']:'', 'Latitude' => isset($this->_receive['Latitude'])?$this->_receive['Latitude']:'', 'Longitude' => isset($this->_receive['Longitude'])?$this->_receive['Longitude']:'', 'Precision' => isset($this->_receive['Precision'])?$this->_receive['Precision']:'', ); } else return false; } /** * 获取接收语言推送 */ public function getRevVoice() { if (isset($this->_receive['MediaId'])) { return array( 'mediaid' => $this->_receive['MediaId'], 'format' => $this->_receive['Format'], ); } else return false; } /** * 获取接收视频推送 */ public function getRevVideo() { if (isset($this->_receive['MediaId'])) { return array( 'mediaid' => $this->_receive['MediaId'], 'thumbmediaid' => $this->_receive['ThumbMediaId'] ); } else return false; } /** * 获取接收TICKET */ public function getRevTicket() { if (isset($this->_receive['Ticket'])) { return $this->_receive['Ticket']; } else return false; } public static function xmlSafeStr($str) { return ''; } /** * 数据XML编码 * @param mixed $data 数据 * @return string */ public static function data_to_xml($data) { $xml = ''; foreach ($data as $key => $val) { is_numeric($key) && $key = "item id=\"$key\""; $xml .= "<$key>"; $xml .= ( is_array($val) || is_object($val)) ? self::data_to_xml($val) : self::xmlSafeStr($val); list($key, ) = explode(' ', $key); $xml .= ""; } return $xml; } /** * XML编码 * @param mixed $data 数据 * @param string $root 根节点名 * @param string $item 数字索引的子节点名 * @param string $attr 根节点属性 * @param string $id 数字索引子节点key转换的属性名 * @param string $encoding 数据编码 * @return string */ public function xml_encode($data, $root = 'xml', $item = 'item', $attr = '', $id = 'id', $encoding = 'utf-8') { if (is_array($attr)) { $_attr = array(); foreach ($attr as $key => $value) { $_attr[] = "{$key}=\"{$value}\""; } $attr = implode(' ', $_attr); } $attr = trim($attr); $attr = empty($attr) ? '' : " {$attr}"; $xml = "<{$root}{$attr}>"; $xml .= self::data_to_xml($data, $item, $id); $xml .= ""; return $xml; } /** * 设置回复消息 * Examle: $obj->text('hello')->reply(); * @param string $text */ public function text($text = '') { $FuncFlag = $this->_funcflag ? 1 : 0; $msg = array( 'ToUserName' => $this->getRevFrom(), 'FromUserName' => $this->getRevTo(), 'CreateTime' => TIMESTAMP, 'MsgType' => self::MSGTYPE_TEXT, 'Content' => $text, 'FuncFlag' => $FuncFlag ); $this->Message($msg); return $this; } /** * 设置回复音乐 * @param string $title * @param string $desc * @param string $musicurl * @param string $hgmusicurl */ public function music($title, $desc, $musicurl, $hgmusicurl = '') { $FuncFlag = $this->_funcflag ? 1 : 0; $msg = array( 'ToUserName' => $this->getRevFrom(), 'FromUserName' => $this->getRevTo(), 'CreateTime' => TIMESTAMP, 'MsgType' => self::MSGTYPE_MUSIC, 'Music' => array( 'Title' => $title, 'Description' => $desc, 'MusicUrl' => $musicurl, 'HQMusicUrl' => $hgmusicurl ), 'FuncFlag' => $FuncFlag ); $this->Message($msg); return $this; } /** * 设置回复图文 * @param array $newsData * 数组结构: * array( * [0]=>array( * 'Title'=>'msg title', * 'Description'=>'summary text', * 'PicUrl'=>'http://www.domain.com/1.jpg', * 'Url'=>'http://www.domain.com/1.html' * ), * [1]=>.... * ) */ public function news($newsData = array()) { $FuncFlag = $this->_funcflag ? 1 : 0; $count = count($newsData); $msg = array( 'ToUserName' => $this->getRevFrom(), 'FromUserName' => $this->getRevTo(), 'MsgType' => self::MSGTYPE_NEWS, 'CreateTime' => TIMESTAMP, 'ArticleCount' => $count, 'Articles' => $newsData, 'FuncFlag' => $FuncFlag ); $this->Message($msg); return $this; } /** * * 回复微信服务器, 此函数支持链式操作 * @example $this->text('msg tips')->reply(); * @param string $msg 要发送的信息, 默认取$this->_msg * @param bool $return 是否返回信息而不抛出到浏览器 默认:否 */ public function reply($msg = array(), $return = false) { if (empty($msg)) $msg = $this->_msg; $xmldata = $this->xml_encode($msg); $this->log($xmldata); if ($return) return $xmldata; else echo $xmldata; } /** * GET 请求 * @param string $url */ private function http_get($url) { $oCurl = curl_init(); if (stripos($url, "https://") !== FALSE) { curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE); } curl_setopt($oCurl, CURLOPT_URL, $url); curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1); $sContent = curl_exec($oCurl); $aStatus = curl_getinfo($oCurl); curl_close($oCurl); if (intval($aStatus["http_code"]) == 200) { return $sContent; } else { return false; } } /** * POST 请求 * @param string $url * @param array $param * @return string content */ function http_post($url, $param) { $oCurl = curl_init(); if (stripos($url, "https://") !== FALSE) { curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false); } if (is_string($param)) { $strPOST = $param; } else { $aPOST = array(); foreach ($param as $key => $val) { $aPOST[] = $key . "=" . urlencode($val); } $strPOST = join("&", $aPOST); } curl_setopt($oCurl, CURLOPT_URL, $url); curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($oCurl, CURLOPT_POST, true); curl_setopt($oCurl, CURLOPT_POSTFIELDS, $strPOST); $sContent = curl_exec($oCurl); $aStatus = curl_getinfo($oCurl); curl_close($oCurl); if (intval($aStatus["http_code"]) == 200) { return $sContent; } else { return false; } } /** * 通用auth验证方法,暂时仅用于菜单更新操作 * @param string $appid * @param string $appsecret */ public function checkAuth($appid = '', $appsecret = '') { if (!$appid || !$appsecret) { $appid = $this->appid; $appsecret = $this->appsecret; } //TODO: get the cache access_token $result = $this->http_get(self::API_URL_PREFIX . self::AUTH_URL . 'appid=' . $appid . '&secret=' . $appsecret); if ($result) { $json = json_decode($result, true); if (!$json || isset($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } $this->access_token = $json['access_token']; $expire = $json['expires_in'] ? intval($json['expires_in']) - 100 : 3600; //TODO: cache access_token return $this->access_token; } return false; } /** * 删除验证数据 * @param string $appid */ public function resetAuth($appid = '') { $this->access_token = ''; //TODO: remove cache return true; } /** * 微信api不支持中文转义的json结构 * @param array $arr */ static function json_encode($arr) { $parts = array(); $is_list = false; //Find out if the given array is a numerical array $keys = array_keys($arr); $max_length = count($arr) - 1; if (isset($keys[0])&&($keys[0] === 0) && ($keys[$max_length] === $max_length )) { //See if the first key is 0 and last key is length - 1 $is_list = true; for ($i = 0; $i < count($keys); $i++) { //See if each key correspondes to its position if ($i != $keys [$i]) { //A key fails at position check. $is_list = false; //It is an associative array. break; } } } foreach ($arr as $key => $value) { if (is_array($value)) { //Custom handling for arrays if ($is_list) $parts [] = self::json_encode($value); /* :RECURSION: */ else $parts [] = '"' . $key . '":' . self::json_encode($value); /* :RECURSION: */ } else { $str = ''; if (!$is_list) $str = '"' . $key . '":'; //Custom handling for multiple data types if (is_numeric($value) && $value < 2000000000) $str .= $value; //Numbers elseif ($value === false) $str .= 'false'; //The booleans elseif ($value === true) $str .= 'true'; else $str .= '"' . addslashes($value) . '"'; //All other things // :TODO: Is there any more datatype we should be in the lookout for? (Object?) $parts [] = $str; } } $json = implode(',', $parts); if ($is_list) return '[' . $json . ']'; //Return numerical JSON return '{' . $json . '}'; //Return associative JSON } /** * 创建菜单 * @param array $data 菜单数组数据 * example: { "button":[ { "type":"click", "name":"今日歌曲", "key":"MENU_KEY_MUSIC" }, { "type":"view", "name":"歌手简介", "url":"http://www.qq.com/" }, { "name":"菜单", "sub_button":[ { "type":"click", "name":"hello word", "key":"MENU_KEY_MENU" }, { "type":"click", "name":"赞一下我们", "key":"MENU_KEY_GOOD" }] }] } */ public function createMenu($data) { if (!$this->access_token && !$this->checkAuth()) return false; $result = $this->http_post(self::API_URL_PREFIX . self::MENU_CREATE_URL . 'access_token=' . $this->access_token, self::json_encode($data)); if ($result) { $json = json_decode($result, true); if (!$json || $json['errcode']!='0') { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } return true; } return false; } /** * 获取菜单 * @return array('menu'=>array(....s)) */ public function getMenu() { if (!$this->access_token && !$this->checkAuth()) return false; $result = $this->http_get(self::API_URL_PREFIX . self::MENU_GET_URL . 'access_token=' . $this->access_token); if ($result) { $json = json_decode($result, true); if (!$json || isset($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } return $json; } return false; } /** * 删除菜单 * @return boolean */ public function deleteMenu() { if (!$this->access_token && !$this->checkAuth()) return false; $result = $this->http_get(self::API_URL_PREFIX . self::MENU_DELETE_URL . 'access_token=' . $this->access_token); if ($result) { $json = json_decode($result, true); if (!$json || !empty($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } return true; } return false; } /** * 根据媒体文件ID获取媒体文件 * @param string $media_id 媒体文件id * @return raw data */ public function getMedia($media_id) { if (!$this->access_token && !$this->checkAuth()) return false; $result = $this->http_get(self::API_URL_PREFIX . self::MEDIA_GET_URL . 'access_token=' . $this->access_token . '&media_id=' . $media_id); if ($result) { $json = json_decode($result, true); if (isset($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } return $json; } return false; } /** * 创建二维码ticket * @param int $scene_id 自定义追踪id * @param int $type 0:临时二维码;1:永久二维码(此时expire参数无效) * @param int $expire 临时二维码有效期,最大为1800秒 * @return array('ticket'=>'qrcode字串','expire_seconds'=>1800) */ public function getQRCode($scene_id, $type = 0, $expire = 1800) { if (!$this->access_token && !$this->checkAuth()) return false; $data = array( 'action_name' => $type ? "QR_LIMIT_SCENE" : "QR_SCENE", 'expire_seconds' => $expire, 'action_info' => array('scene' => array('scene_id' => $scene_id)) ); if ($type == 1) { unset($data['expire_seconds']); } $result = $this->http_post(self::API_URL_PREFIX . self::QRCODE_CREATE_URL . 'access_token=' . $this->access_token, self::json_encode($data)); if ($result) { $json = json_decode($result, true); if (!$json || !empty($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } return $json; } return false; } /** * 获取二维码图片 * @param string $ticket 传入由getQRCode方法生成的ticket参数 * @return string url 返回http地址 */ public function getQRUrl($ticket) { return self::QRCODE_IMG_URL . $ticket; } /** * 批量获取关注用户列表 * @param unknown $next_openid */ public function getUserList($next_openid = '') { if (!$this->access_token && !$this->checkAuth()) return false; $result = $this->http_get(self::API_URL_PREFIX . self::USER_GET_URL . 'access_token=' . $this->access_token . '&next_openid=' . $next_openid); if ($result) { $json = json_decode($result, true); if (isset($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } return $json; } return false; } /** * 获取关注者详细信息 * @param string $openid * @return array */ public function getwxUserInfo($openid) { if (!$this->access_token && !$this->checkAuth()) return false; $result = $this->http_get(self::API_URL_PREFIX . self::USER_INFO_URL . 'access_token=' . $this->access_token . '&openid=' . $openid); if ($result) { $json = json_decode($result, true); if (isset($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } $json['access_token'] = $this->access_token; return $json; } return false; } /** * 获取用户分组列表 * @return boolean|array */ public function getGroup() { if (!$this->access_token && !$this->checkAuth()) return false; $result = $this->http_get(self::API_URL_PREFIX . self::GROUP_GET_URL . 'access_token=' . $this->access_token); if ($result) { $json = json_decode($result, true); if (isset($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } return $json; } return false; } /** * 新增自定分组 * @param string $name 分组名称 * @return boolean|array */ public function createGroup($name) { if (!$this->access_token && !$this->checkAuth()) return false; $data = array( 'group' => array('name' => $name) ); $result = $this->http_post(self::API_URL_PREFIX . self::GROUP_CREATE_URL . 'access_token=' . $this->access_token, self::json_encode($data)); if ($result) { $json = json_decode($result, true); if (!$json || !empty($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } return $json; } return false; } /** * 更改分组名称 * @param int $groupid 分组id * @param string $name 分组名称 * @return boolean|array */ public function updateGroup($groupid, $name) { if (!$this->access_token && !$this->checkAuth()) return false; $data = array( 'group' => array('id' => $groupid, 'name' => $name) ); $result = $this->http_post(self::API_URL_PREFIX . self::GROUP_UPDATE_URL . 'access_token=' . $this->access_token, self::json_encode($data)); if ($result) { $json = json_decode($result, true); if (!$json || !empty($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } return $json; } return false; } /** * 移动用户分组 * @param int $groupid 分组id * @param string $openid 用户openid * @return boolean|array */ public function updateGroupMembers($groupid, $openid) { if (!$this->access_token && !$this->checkAuth()) return false; $data = array( 'openid' => $openid, 'to_groupid' => $groupid ); $result = $this->http_post(self::API_URL_PREFIX . self::GROUP_MEMBER_UPDATE_URL . 'access_token=' . $this->access_token, self::json_encode($data)); if ($result) { $json = json_decode($result, true); if (!$json || !empty($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } return $json; } return false; } /** * 发送客服消息 * @param array $data 消息结构{"touser":"OPENID","msgtype":"news","news":{...}} * @return boolean|array */ public function sendCustomMessage($data) { if(!$this->access_token && !$this->checkAuth()) { return false; } $result = $this->http_post(self::API_URL_PREFIX . self::CUSTOM_SEND_URL . 'access_token=' . $this->access_token, self::json_encode($data)); if ($result) { $json = json_decode($result, true); //file_put_contents('send.xml',$result); if (!$json || $json['errcode']!='0') { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; }else { return $json; } } return false; } //消息群发 function massSend($data){ if(!$this->access_token && !$this->checkAuth()) { return false; } $result = $this->http_post(self::API_URL_PREFIX . self::MASS_SEND_URL . 'access_token=' . $this->access_token, self::json_encode($data)); if ($result) { $json = json_decode($result, true); //file_put_contents('send.xml',$result); if (!$json || $json['errcode']!='0') { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; }else { return $json; } } return false; } /** * oauth 授权跳转接口 * @param string $callback 回调URI * @return string */ public function getOauthRedirect($callback, $state = '', $scope = 'snsapi_userinfo') { return self::OAUTH_PREFIX . self::OAUTH_AUTHORIZE_URL . 'appid=' . $this->appid . '&redirect_uri=' . urlencode($callback) . '&response_type=code&scope=' . $scope . '&state=' . $state . '#wechat_redirect'; } /* * 通过code获取Access Token * @return array {access_token,expires_in,refresh_token,openid,scope} */ public function getOauthAccessToken() { $code = isset($_GET['code']) ? $_GET['code'] : ''; if (!$code) return false; $result = $this->http_get(self::OAUTH_TOKEN_PREFIX . self::OAUTH_TOKEN_URL . 'appid=' . $this->appid . '&secret=' . $this->appsecret . '&code=' . $code . '&grant_type=authorization_code'); if ($result) { $json = json_decode($result, true); if (!$json || !empty($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } $this->user_token = $json['access_token']; return $json; } return false; } /** * 刷新access token并续期 * @param string $refresh_token * @return boolean|mixed */ public function getOauthRefreshToken($refresh_token) { $result = $this->http_get(self::OAUTH_TOKEN_PREFIX . self::OAUTH_REFRESH_URL . 'appid=' . $this->appid . '&grant_type=refresh_token&refresh_token=' . $refresh_token); if ($result) { $json = json_decode($result, true); if (!$json || !empty($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } $this->user_token = $json['access_token']; return $json; } return false; } /** * 获取授权后的用户资料 * @param string $access_token * @param string $openid * @return array {openid,nickname,sex,province,city,country,headimgurl,privilege} */ public function getOauthUserinfo($access_token, $openid) { $result = $this->http_get(self::OAUTH_USERINFO_URL . 'access_token=' . $access_token . '&openid=' . $openid); if ($result) { $json = json_decode($result, true); if (!$json || !empty($json['errcode'])) { $this->errCode = $json['errcode']; $this->errMsg = $json['errmsg']; return false; } return $json; } return false; } /** * 进入客服模式 * Examle: $obj->kefu()->reply(); * @param string $text */ public function kefu() { $msg = array( 'ToUserName' => $this->getRevFrom(), 'FromUserName' => $this->getRevTo(), 'MsgType' => 'transfer_customer_service', 'CreateTime' => TIMESTAMP ); $this->Message($msg); return $this; } //编码转换 function iconvUtf($content) { if ($this->_charset != 'utf-8') { $content = serialize($content); $content = iconv($this->_charset, 'UTF-8', $content); $content = unserialize($content); } return $content; } //获取用户上报的地理信息 function getUserLocation() { if (empty($this->_receive['Latitude'])) return false; return array( 'FromUserName' => $this->_receive['FromUserName'], 'Latitude' => $this->_receive['Latitude'], 'Longitude' => $this->_receive['Longitude'], 'Precision' => $this->_receive['Precision'], ); } }