<?php
class Weixin {
private $receive;
private $token;
private $appid;
private $appsecret;
private $access_token;
public $debug = false;
public $error = false;
public $errcode = array(
'-1' => '系统繁忙',
'0' => '请求成功',
'40001' => '获取access_token时AppSecret错误,或者access_token无效',
'40002' => '不合法的凭证类型',
'40003' => '不合法的OpenID',
'40004' => '不合法的媒体文件类型',
'40005' => '不合法的文件类型',
'40006' => '不合法的文件大小',
'40007' => '不合法的媒体文件id',
'40008' => '不合法的消息类型',
'40009' => '不合法的图片文件大小',
'40010' => '不合法的语音文件大小',
'40011' => '不合法的视频文件大小',
'40012' => '不合法的缩略图文件大小',
'40013' => '不合法的APPID',
'40014' => '不合法的access_token',
'40015' => '不合法的菜单类型',
'40016' => '不合法的按钮个数',
'40017' => '不合法的按钮个数',
'40018' => '不合法的按钮名字长度',
'40019' => '不合法的按钮KEY长度',
'40020' => '不合法的按钮URL长度',
'40021' => '不合法的菜单版本号',
'40022' => '不合法的子菜单级数',
'40023' => '不合法的子菜单按钮个数',
'40024' => '不合法的子菜单按钮类型',
'40025' => '不合法的子菜单按钮名字长度',
'40026' => '不合法的子菜单按钮KEY长度',
'40027' => '不合法的子菜单按钮URL长度',
'40028' => '不合法的自定义菜单使用用户',
'40029' => '不合法的oauth_code',
'40030' => '不合法的refresh_token',
'40031' => '不合法的openid列表',
'40032' => '不合法的openid列表长度',
'40033' => '不合法的请求字符,不能包含\uxxxx格式的字符',
'40035' => '不合法的参数',
'40038' => '不合法的请求格式',
'40039' => '不合法的URL长度',
'40050' => '不合法的分组id',
'40051' => '分组名字不合法',
'41001' => '缺少access_token参数',
'41002' => '缺少appid参数',
'41003' => '缺少refresh_token参数',
'41004' => '缺少secret参数',
'41005' => '缺少多媒体文件数据',
'41006' => '缺少media_id参数',
'41007' => '缺少子菜单数据',
'41008' => '缺少oauth code',
'41009' => '缺少openid',
'42001' => 'access_token超时',
'42002' => 'refresh_token超时',
'42003' => 'oauth_code超时',
'43001' => '需要GET请求',
'43002' => '需要POST请求',
'43003' => '需要HTTPS请求',
'43004' => '需要接收者关注',
'43005' => '需要好友关系',
'44001' => '多媒体文件为空',
'44002' => 'POST的数据包为空',
'44003' => '图文消息内容为空',
'44004' => '文本消息内容为空',
'45001' => '多媒体文件大小超过限制',
'45002' => '消息内容超过限制',
'45003' => '标题字段超过限制',
'45004' => '描述字段超过限制',
'45005' => '链接字段超过限制',
'45006' => '图片链接字段超过限制',
'45007' => '语音播放时间超过限制',
'45008' => '图文消息超过限制',
'45009' => '接口调用超过限制',
'45010' => '创建菜单个数超过限制',
'45015' => '回复时间超过限制',
'45016' => '系统分组,不允许修改',
'45017' => '分组名字过长',
'45018' => '分组数量超过上限',
'46001' => '不存在媒体数据',
'46002' => '不存在的菜单版本',
'46003' => '不存在的菜单数据',
'46004' => '不存在的用户',
'47001' => '解析JSON/XML内容错误',
'48001' => 'api功能未授权',
'50001' => '用户未授权该api'
);
public function __construct($options) {
$this->token = isset($options['token']) ? $options['token'] : '';
$this->receive = isset($options['receive']) ? $options['receive'] : '';
$this->appid = isset($options['appid']) ? $options['appid'] : '';
$this->appsecret = isset($options['appsecret']) ? $options['appsecret'] : '';
$this->debug = isset($options['debug']) ? $options['debug'] : false;
}
public function get_error() {
return $this->error;
}
//==============================接收消息===================================
/**
* 获取微信服务器发来的信息
*/
public function getRev() {
$postStr = file_get_contents("php://input");
$this->log($postStr);
if (!empty($postStr)) {
$this->receive = (array) simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
}
return $this;
}
/**
* 获取微信服务器发来的信息
*/
public function getRevData() {
return $this->receive;
}
/**
* 获取消息发送者
*/
public function getRevFrom() {
if ($this->receive) {
return $this->receive['FromUserName'];
}
return false;
}
/**
* 获取接收消息的类型
*/
public function getRevType() {
if (isset($this->receive['MsgType'])) {
return $this->receive['MsgType'];
}
return false;
}
/**
* 获取消息ID
*/
public function getRevID() {
if (isset($this->receive['MsgId'])) {
return $this->receive['MsgId'];
}
return false;
}
/**
* 获取消息发送时间
*/
public function getRevCtime() {
if (isset($this->receive['CreateTime'])) {
return $this->receive['CreateTime'];
}
return false;
}
/**
* 获取接收消息内容正文
*/
public function getRevContent() {
if (isset($this->receive['Content'])) {
return $this->receive['Content'];
} elseif (isset($this->receive['Recognition'])) {
//获取语音识别文字内容,需申请开通
return $this->receive['Recognition'];
}
return false;
}
/**
* 获取接收消息图片
*/
public function getRevPic() {
if (isset($this->receive['PicUrl'])) {
return $this->receive['PicUrl'];
}
return false;
}
/**
* 获取接收消息链接
*/
public function getRevLink() {
if (isset($this->receive['Url'])) {
return array(
'url' => $this->receive['Url'],
'title' => $this->receive['Title'],
'description' => $this->receive['Description']
);
}
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']
);
}
return false;
}
/**
* 获取接收事件推送
*/
public function getRevEvent() {
if (isset($this->receive['Event'])) {
return array(
'event' => $this->receive['Event'],
'key' => $this->receive['EventKey'],
);
}
return false;
}
/**
* 获取接收语言推送
*/
public function getRevVoice() {
if (isset($this->receive['MediaId'])) {
return array(
'mediaid' => $this->receive['MediaId'],
'format' => $this->receive['Format'],
);
}
return false;
}
/**
* 获取接收视频推送
*/
public function getRevVideo() {
if (isset($this->receive['MediaId'])) {
return array(
'mediaid' => $this->receive['MediaId'],
'thumbmediaid' => $this->receive['ThumbMediaId']
);
}
return false;
}
/**
* 获取消息接受者
*/
public function getRevTo() {
if ($this->receive) {
return $this->receive['ToUserName'];
}
return false;
}
/**
* 获取access token 2000次/天
* @param type $appid 第三方用户唯一凭证
* @param type $secret 第三方用户唯一凭证密钥,既appsecret
*/
private function get_access_token() {
if (isset($_COOKIE['access_token'])) {
$this->access_token = $_COOKIE['access_token'];
}
if (empty($this->access_token)) {
$data = $this->http_get('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' . $this->appid . '&secret=' . $this->appsecret);
$this->access_token = $data['access_token'];
setcookie("access_token", $data['access_token'], time() + 7200);
}
return $this->access_token;
}
/**
* 回复文本消息
* @param string $content
* @return \Core_Helper_Weixin
*/
public function send_text($content) {
$msg = array(
'ToUserName' => $this->getRevFrom(),
'FromUserName' => $this->getRevTo(),
'CreateTime' => time(),
'MsgType' => 'text',
'Content' => $content
);
return $this->send($msg);
}
/**
* 回复图片消息
* @param int $id
* @return \Core_Helper_Weixin
*/
public function send_image($id) {
$msg = array(
'ToUserName' => $this->getRevFrom(),
'FromUserName' => $this->getRevTo(),
'CreateTime' => time(),
'MsgType' => 'image',
'Image' => array('MediaId' => $id)
);
return $this->send($msg);
}
/**
* 回复语音消息
* @param int $id
* @return \Core_Helper_Weixin
*/
public function send_voice($id) {
$msg = array(
'ToUserName' => $this->getRevFrom(),
'FromUserName' => $this->getRevTo(),
'CreateTime' => time(),
'MsgType' => 'voice',
'Voice' => array('MediaId' => $id)
);
return $this->send($msg);
}
/**
* 回复视频消息
* @param int $id
* @param string $title
* @param string $desc
* @return \Core_Helper_Weixin
*/
public function send_video($id, $title, $desc) {
$msg = array(
'ToUserName' => $this->getRevFrom(),
'FromUserName' => $this->getRevTo(),
'CreateTime' => time(),
'MsgType' => 'video',
'Video' => array(
'MediaId' => $id,
'Title' => $title,
'Description' => $desc
)
);
return $this->send($msg);
}
/**
* 回复音乐消息
* @param string $title 音乐标题
* @param string $url 音乐链接
* @param string $desc 音乐描述
* @param string $hq_url 高质量音乐链接,WIFI环境优先使用该链接播放音乐
* @return \Core_Helper_Weixin
*/
public function send_music($title, $url = '', $desc = '', $hq_url = '', $id = '') {
$msg = array(
'ToUserName' => $this->getRevFrom(),
'FromUserName' => $this->getRevTo(),
'CreateTime' => time(),
'MsgType' => 'music',
'Music' => array(
'Title' => $title,
'Description' => $desc,
'MusicUrl' => $url,
'HQMusicUrl' => empty($hq_url) ? $url : $hq_url,
"ThumbMediaId" => $id
)
);
return $this->send($msg);
}
/**
* 回复图文消息
* @param array $news
* 数组结构:
* 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 send_news(array $news = array()) {
$count = count($news);
$msg = array(
'ToUserName' => $this->getRevFrom(),
'FromUserName' => $this->getRevTo(),
'CreateTime' => time(),
'MsgType' => 'news',
'ArticleCount' => $count,
'Articles' => $news
);
return $this->send($msg);
}
private function send($msg, $type = 'XML') {
if ($type == 'XML') {
echo self::xml_encode($msg);
} else {
echo self::json_encode($msg);
}
}
//==============================发送消息===================================
public function custom_send_text($fromuser, $content) {
$msg = array(
'touser' => $fromuser,
'msgtype' => 'text',
'text' => array(
'content' => $content
)
);
return $this->custom_send($msg);
}
public function custom_send_image($fromuser, $id) {
$msg = array(
'touser' => $fromuser,
'msgtype' => 'image',
'image' => array(
"media_id" => $id
)
);
return $this->custom_send($msg);
}
public function custom_send_voice($fromuser, $id) {
$msg = array(
'touser' => $fromuser,
'msgtype' => 'voice',
'voice' => array(
"media_id" => $id
)
);
return $this->custom_send($msg);
}
public function custom_send_video($fromuser, $id) {
$msg = array(
'touser' => $fromuser,
'msgtype' => 'video',
'video' => array(
"media_id" => $id
)
);
return $this->custom_send($msg);
}
public function custom_send_music($fromuser, $title, $url = '', $desc = '', $hq_url = '', $id = '') {
$msg = array(
'touser' => $fromuser,
'msgtype' => 'music',
'music' => array(
"title" => $title,
"description" => $desc,
"musicurl" => $url,
"hqmusicurl" => empty($hq_url) ? $url : $hq_url,
"thumb_media_id" => $id
)
);
return $this->custom_send($msg);
}
private function custom_send($msg) {
return $this->http_post('https://api.weixin.qq.com/cgi-bin/message/custom?access_token=' . $this->get_access_token(), self::json_encode($msg));
}
/**
* 查询分组
*/
public function groups_get() {
return $this->http_get('https://api.weixin.qq.com/cgi-bin/groups/get?access_token=' . $this->get_access_token());
}
/**
* 创建分组
* 最多支持创建500个分组
* {"group":{"name":"test"}}
*/
public function groups_create($name) {
$data = array('group' => array('name' => $name));
return $this->http_post('https://api.weixin.qq.com/cgi-bin/groups/create?access_token=' . $this->get_access_token(), self::json_encode($data));
}
/**
* 修改分组名
* {"group":{"id":108,"name":"test2_modify2"}}
*/
public function groups_update($id, $name) {
$data = array('group' => array("id" => $id, 'name' => $name));
return $this->http_post('https://api.weixin.qq.com/cgi-bin/groups/update?access_token=' . $this->get_access_token(), self::json_encode($data));
}
/**
* 移动用户分组
* {"openid":"oDF3iYx0ro3_7jD4HFRDfrjdCM58","to_groupid":108}
*/
public function groups_members_update($openid, $to_groupid) {
$data = array('group' => array("openid" => $openid, 'to_groupid' => $to_groupid));
return $this->http_post('https://api.weixin.qq.com/cgi-bin/groups/members/update?access_token=' . $this->get_access_token(), self::json_encode($data));
}
/**
* 获取用户基本信息
* @param string $openid 普通用户的标识,对当前公众号唯一
* @return array "subscribe" 用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。
* "openid" 用户的标识,对当前公众号唯一
* "nickname" 用户的昵称
* "sex": 1 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
* "language" 用户的语言,简体中文为zh_CN
* "city" 用户所在城市
* "province" 用户所在省份
* "country" 用户所在国家
* "headimgurl" 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
* "subscribe_time" 用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间
*/
public function user_info($openid) {
return $this->http_get('https://api.weixin.qq.com/cgi-bin/user/info?access_token=' . $this->get_access_token() . '&openid=' . $openid);
}
/**
* 获取关注者列表
* @param type $next_openid $next_openid 第一个拉取的OPENID,不填默认从头开始拉取
* @return {
* "total":23000, //关注该公众账号的总用户数
* "count":10000, //拉取的OPENID个数,最大值为10000
* "data":{" //列表数据,OPENID的列表
* openid":[
* "OPENID1",
* "OPENID2",
* ...,
* "OPENID10000"
* ]
* },
* "next_openid":"NEXT_OPENID1" //拉取列表的后一个用户的OPENID
* }
*/
public function user_get($next_openid = '') {
return $this->http_get('http://api.weixin.qq.com/cgi-bin/user/get?access_token=' . $this->get_access_token() . '&next_openid=' . $next_openid);
}
/**
* 创建菜单
* @param array $data 菜单数组数据
* example:
* array(
* "button"=>
* array(
* array('type'=>'click','name'=>'最新消息','key'=>'MENU_KEY_NEWS'),
* array('type'=>'view','name'=>'我要搜索','url'=>'http://www.baidu.com'),
* )
* );
*/
public function create_menu($data) {
$this->http_post('https://api.weixin.qq.com/cgi-bin/groups/members/update?access_token=' . $this->get_access_token(), self::json_encode($data));
return TRUE;
}
/**
* 自定义菜单查询接口
* @return array
*/
public function menu_get() {
return $this->http_get('https://api.weixin.qq.com/cgi-bin/menu/get?access_token=' . $this->get_access_token());
}
/**
* 自定义菜单删除接口
* @return boolean
*/
public function menu_delete() {
$this->http_get('https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=' . $this->get_access_token());
return true;
}
/**
* 创建二维码ticket
* @param int $scene_id 自定义追踪id,临时二维码时为32位整型,永久二维码时最大值为1000
* @param type $action_name 二维码类型,
* QR_SCENE为临时 https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
* QR_LIMIT_SCENE为永久 https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
* @param type $expire_seconds 该二维码有效时间,以秒为单位。 最大不超过1800。
* @return string $ticket
*/
public function qrcode_create($id, $type = TRUE, $expire = 1800) {
$param = array(
'action_name' => $type ? "QR_LIMIT_SCENE" : "QR_SCENE",
'expire_seconds' => $expire,
'action_info' => array('scene' => array('scene_id' => $id))
);
$data = $this->http_post('https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=' . $this->get_access_token(), $param);
return $data['ticket'];
}
/**
* 通过ticket换取二维码
* @return string $image 图片
*/
public function qrcode_show() {
return $this->http_get('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=' . $this->token);
}
private function http_get($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //设置是否返回信息
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$result = curl_exec($ch);
if (curl_errno($ch)) {
$this->error = curl_error($ch);
return FALSE;
}
$data = json_decode($result, TRUE);
if (isset($data['errcode']) && $data['errcode'] !== 0) {
$this->error = $this->errcode[$data['errcode']];
return FALSE;
}
curl_close($ch);
return $data;
}
private function http_post($url, $param) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //设置是否返回信息
curl_setopt($ch, CURLOPT_POST, 1); //设置为POST方式
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_POSTFIELDS, self::json_encode($param)); //POST数据
$result = curl_exec($ch);
if (curl_errno($ch)) {
$this->error = curl_error($ch);
return FALSE;
}
$data = json_decode($result, TRUE);
if (isset($data['errcode']) && $data['errcode'] !== 0) {
$this->error = $this->errcode[$data['errcode']];
return FALSE;
}
curl_close($ch);
return $data;
}
/**
* 微信api不支持中文转义的json结构
* @param array $arr
*/
private static function json_encode($arr) {
$parts = array();
$is_list = false;
$keys = array_keys($arr);
$max_length = count($arr) - 1;
if (($keys [0] === 0) && ($keys [$max_length] === $max_length )) {
$is_list = true;
for ($i = 0; $i < count($keys); $i ++) {
if ($i != $keys [$i]) {
$is_list = false;
break;
}
}
}
foreach ($arr as $key => $value) {
if (is_array($value)) {
if ($is_list)
$parts [] = self::json_encode($value);
else
$parts [] = '"' . $key . '":' . self::json_encode($value);
} else {
$str = '';
if (!$is_list)
$str = '"' . $key . '":';
if (is_numeric($value) && $value < 2000000000)
$str .= $value;
elseif ($value === false)
$str .= 'false';
elseif ($value === true)
$str .= 'true';
else
$str .= '"' . addslashes($value) . '"';
$parts [] = $str;
}
}
$json = implode(',', $parts);
if ($is_list)
return '[' . $json . ']';
return '{' . $json . '}';
}
/**
* XML编码
* @param mixed $data 数据
* @param string $root 根节点名
* @param string $item 数字索引的子节点名
* @param string $attr 根节点属性
* @param string $id 数字索引子节点key转换的属性名
* @param string $encoding 数据编码
* @return string
*/
private 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 .= "</{$root}>";
return $xml;
}
/**
* 数据XML编码
* @param mixed $data 数据
* @return string
*/
private 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 .= "</$key>";
}
return $xml;
}
private static function xmlSafeStr($str) {
return '<![CDATA[' . preg_replace("/[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]/", '', $str) . ']]>';
}
private function log($log) {
if ($this->debug) {
}
}
/**
* 验证消息真实性
* @return boolean
*/
public function checkSignature() {
$signature = isset($_GET["signature"]) ? $_GET["signature"] : '';
$timestamp = isset($_GET["timestamp"]) ? $_GET["timestamp"] : '';
$nonce = isset($_GET["nonce"]) ? $_GET["nonce"] : '';
$tmpArr = array($this->token, $timestamp, $nonce);
sort($tmpArr);
$tmpStr = sha1(implode($tmpArr));
if ($tmpStr == $signature) {
return true;
} else {
return false;
}
}
/**
* 公众平台校验
* @return string
*/
public function valid() {
$echoStr = isset($_GET["echostr"]) ? $_GET["echostr"] : '';
if ($this->checkSignature()) {
return $echoStr;
}
}
}