本类包含了微信官方公布的所有可使用的接口,包括了普通接口及高级接口,具体哪些接口可用还是要看您的账号级别。
具体接口如下:

  • 基础支持
  • 接收消息
  • 发送消息
  • 用户管理
  • 自定义菜单
  • 推广支持
  • Weixin JS接口

代码如下:

<?php

/**
 * chenzhidong
 * 2013-6-6
 */
class WeiXin {
    const MSG_TYPE_TEXT = 'text';
    const MSG_TYPE_IMAGE = 'image';
    const MSG_TYPE_VOICE = 'voice';
    const MSG_TYPE_VIDEO = 'video';
    const MSG_TYPE_LOCATION = 'location';
    const MSG_TYPE_LINK = 'link';
    const MSG_TYPE_EVENT = 'event';

    const MSG_EVENT_SUBSCRIBE = 'subscribe';
    const MSG_EVENT_UNSUBSCRIBE = 'unsubscribe';
    const MSG_EVENT_SCAN = 'scan';
    const MSG_EVENT_LOCATION = 'LOCATION';
    const MSG_EVENT_CLICK = 'CLICK';

    const REPLY_TYPE_TEXT = 'text';
    const REPLY_TYPE_IMAGE = 'image';
    const REPLY_TYPE_VOICE = 'voice';
    const REPLY_TYPE_VIDEO = 'video';
    const REPLY_TYPE_MUSIC = 'music';
    const REPLY_TYPE_NEWS = 'news';

    const MEDIA_TYPE_IMAGE = "image";
    const MEDIA_TYPE_VOICE = 'voice';
    const MEDIA_TYPE_VIDEO = 'video';
    const MEDIA_TYPE_THUMB = 'thumb';

    const SCOPE_REDIRECT = "snsapi_base";
    const SCOPE_POP = "snsapi_userinfo";

    private static $links = array(
            'message' => "https://api.weixin.qq.com/cgi-bin/message/custom/send",
            'group_create' => "https://api.weixin.qq.com/cgi-bin/groups/create",
            'group_get' => "https://api.weixin.qq.com/cgi-bin/groups/get",
            'group_getid' => "https://api.weixin.qq.com/cgi-bin/groups/getid",
            'group_rename' => "https://api.weixin.qq.com/cgi-bin/groups/update",
            'group_move' => "https://api.weixin.qq.com/cgi-bin/groups/members/update",
            'user_info' => "https://api.weixin.qq.com/cgi-bin/user/info",
            'user_get' => 'https://api.weixin.qq.com/cgi-bin/user/get',
            'menu_create' => 'https://api.weixin.qq.com/cgi-bin/menu/create',
            'menu_get' => 'https://api.weixin.qq.com/cgi-bin/menu/get',
            'menu_delete' => 'https://api.weixin.qq.com/cgi-bin/menu/delete',
            'qrcode' => 'https://api.weixin.qq.com/cgi-bin/qrcode/create',
            'showqrcode' => 'https://mp.weixin.qq.com/cgi-bin/showqrcode',
            'media_download' => 'http://file.api.weixin.qq.com/cgi-bin/media/get',
            'media_upload' => 'http://file.api.weixin.qq.com/cgi-bin/media/upload',
            'oauth_code' => 'https://open.weixin.qq.com/connect/oauth2/authorize',
            'oauth_access_token' => 'https://api.weixin.qq.com/sns/oauth2/access_token',
            'oauth_refresh' => 'https://api.weixin.qq.com/sns/oauth2/refresh_token',
            'oauth_userinfo' => 'https://api.weixin.qq.com/sns/userinfo'
    );

    private static $errors = 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'
    );

    private static $debug = false;

    private $token;
    private $postStr;
    private $postObj;

    private $appid;
    private $appsecret;
    private $access_token;
    private $valid = 0;

    /**
     * @param      $token
     * @param null $appid
     * @param null $appsecret
     * @param bool $debug
     */
    public function __construct($token, $appid = null, $appsecret = null, $debug = false) {
        $this->token = $token;
        if (!empty($_GET) && $this->checkSignature())
            $this->handleRequest();
        $this->appid = $appid;
        $this->appsecret = $appsecret;
        self::$debug = $debug;
    }

    /**
     * 检查签名
     *
     * @return bool
     */
    private function checkSignature() {
        if (!isset($_GET['signature']) || !isset($_GET['timestamp']) || !isset($_GET['nonce']))
            return false;
        $signature = $_GET["signature"];
        $timestamp = $_GET["timestamp"];
        $nonce = $_GET["nonce"];

        $tmpArr = array($this->token, $timestamp, $nonce);
        sort($tmpArr, SORT_STRING);
        $tmpStr = implode($tmpArr);
        $tmpStr = sha1($tmpStr);

        if ($tmpStr == $signature)
            return true;
        else
            return false;
    }

    /**
     * 处理请求
     */
    private function handleRequest() {
        if (isset($_GET['echostr']))
        {
            echo $_GET['echostr'];
            exit;
        }
        else
        {
            $this->postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
            if (!empty($this->postStr))
            {
                $this->postObj = simplexml_load_string($this->postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
            }
            else
            {
                Log::simpleappend('fail', $this->postStr);
                exit;
            }
        }
    }

    /**
     * 获取高级接口的access_token
     *
     * @return bool
     */
    private function getAccessToken() {
        if ($this->appid && $this->appsecret)
        {
            if ($this->valid <= time())
            {
                $access = json_decode(Tools::curl("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appid}&secret={$this->appsecret}"));
                if (isset($access->access_token) && isset($access->expires_in))
                {
                    $this->access_token = $access->access_token;
                    $this->valid = time() + $access->expires_in;
                }
                else
                    return false;
            }

            return true;
        }

        return false;
    }

    /**
     * 获取请求中的收信人OpenId,一般为公众账号自身
     *
     * @return mixed
     */
    public function getToUserName() {
        return $this->postObj->ToUserName;
    }

    /**
     * 请求中的发信人OpenId
     *
     * @return mixed
     */
    public function getFromUserName() {
        return $this->postObj->FromUserName;
    }

    /**
     * 获取Object格式的消息内容
     *
     * @return mixed
     */
    public function getPostObj() {
        return $this->postObj;
    }

    /**
     * 获取字符串格式的消息内容
     *
     * @return mixed
     */
    public function getPostStr() {
        return $this->postStr;
    }

    /**
     * 获取消息ID
     *
     * @return mixed
     */
    public function getMsgId() {
        return $this->postObj->MsgId;
    }

    /**
     * 消息创建时间
     *
     * @return string
     */
    public function getCreateTime() {
        return strval($this->postObj->CreateTime);
    }

    //获取消息类型
    public function getMsgType() {
        return $this->postObj->MsgType;
    }

    /**
     * 获取事件推送的时间类型,非事件的消息返回false
     *
     * @return mixed
     */
    public function getEvent() {
        if ($this->postObj->MsgType == self::MSG_TYPE_EVENT)
        {
            return $this->postObj->Event;
        }

        return false;
    }

    /**
     * 是否为文本消息
     *
     * @return bool
     */
    public function isTextMessage() {
        return $this->postObj->MsgType == self::MSG_TYPE_TEXT;
    }

    /**
     * 是否为图片消息
     *
     * @return bool
     */
    public function isImageMessage() {
        return $this->postObj->MsgType == self::MSG_TYPE_IMAGE;
    }

    /**
     * 是否为语音消息
     *
     * @return bool
     */
    public function isVoiceMessage() {
        return $this->postObj->MsgType == self::MSG_TYPE_VOICE;
    }

    /**
     * 是否为视频消息
     *
     * @return bool
     */
    public function isVideoMessage() {
        return $this->postObj->MsgType == self::MSG_TYPE_VIDEO;
    }

    /**
     * 是否为地理位置消息
     *
     * @return bool
     */
    public function isLocationMessage() {
        return $this->postObj->MsgType == self::MSG_TYPE_LOCATION;
    }

    /**
     * 是否为链接消息
     *
     * @return bool
     */
    public function isLinkMessage() {
        return $this->postObj->MsgType == self::MSG_TYPE_LINK;
    }

    /**
     * 是否为普通关注事件
     *
     * @return bool
     */
    public function isEventSubscribe() {
        return $this->postObj->MsgType == self::MSG_TYPE_EVENT && $this->postObj->Event == self::MSG_EVENT_SUBSCRIBE && !isset($this->postObj->EventKey);
    }

    /**
     * 是否为取消关注事件
     *
     * @return bool
     */
    public function isEventUnSubscribe() {
        return $this->postObj->MsgType == self::MSG_TYPE_EVENT && $this->postObj->Event == self::MSG_EVENT_UNSUBSCRIBE;
    }

    /**
     * 是否为扫描二维码关注事件
     *
     * @return bool
     */
    public function isEventScanSubscript() {
        return $this->postObj->MsgType == self::MSG_TYPE_EVENT && $this->postObj->Event == self::MSG_EVENT_SUBSCRIBE && isset($this->postObj->EventKey);
    }

    /**
     * 是否为已关注扫描二维码事件
     *
     * @return bool
     */
    public function isEventScan() {
        return $this->postObj->MsgType == self::MSG_TYPE_EVENT && $this->postObj->Event == self::MSG_EVENT_SCAN;
    }

    /**
     * 是否为上报地理位置事件
     *
     * @return bool
     */
    public function isEventLocation() {
        return $this->postObj->MsgType == self::MSG_TYPE_EVENT && $this->postObj->Event == self::MSG_EVENT_LOCATION;
    }

    /**
     * 是否为菜单点击事件
     *
     * @return bool
     */
    public function isEventClick() {
        return $this->postObj->MsgType == self::MSG_TYPE_EVENT && $this->postObj->Event == self::MSG_EVENT_CLICK;
    }

    /**
     * 获取文本消息内容
     *
     * @return string
     */
    public function requestText() {
        return strval($this->postObj->Content);
    }

    /**
     * 获取图片消息内容
     *
     * @return array
     */
    public function requestImage() {
        $image = array();
        $image['PicUrl'] = strval($this->postObj->PicUrl);
        $image['MediaId'] = strval($this->postObj->MediaId);

        return $image;
    }

    /**
     * 获取语音消息内容,可能包含语音识别结果
     *
     * @return array
     */
    public function requestVoice() {
        $voice = array();
        $voice['MediaId'] = $this->postObj->MediaId;
        $voice['Format'] = $this->postObj->Format;
        if (isset($this->postObj->Recognition))
            $voice['Recognition'] = $this->postObj->Recognition;

        return $voice;
    }

    /**
     * 获取视频消息内容
     *
     * @return array
     */
    public function requestVideo() {
        $video = array();
        $video['MediaId'] = $this->postObj->MediaId;
        $video['ThumbMediaId'] = $this->postObj->ThumbMediaId;

        return $video;
    }

    /**
     * 获取地理位置消息内容
     *
     * @return array
     */
    public function requestLocation() {
        $location = array();
        $location['Location_X'] = strval($this->postObj->Location_X);
        $location['Location_Y'] = strval($this->postObj->Location_Y);
        $location['Scale'] = strval($this->postObj->Scale);
        $location['Label'] = strval($this->postObj->Label);

        return $location;
    }

    /**
     * 获取链接消息内容
     *
     * @return array
     */
    public function requestLink() {
        $link = array();
        $link['Title'] = strval($this->postObj->Title);
        $link['Description'] = strval($this->postObj->Description);
        $link['Url'] = strval($this->postObj->Url);

        return $link;
    }

    /**
     * 获取扫描二维码事件内容
     *
     * @return array
     */
    public function requestEventScan() {
        $info = array();
        $info['EventKey'] = $this->postObj->EventKey;
        $info['Ticket'] = $this->postObj->Ticket;
        $info['Scene_Id'] = str_replace('qrscene_', '', $this->postObj->EventKey);

        return $info;
    }

    /**
     * 获取上报地理位置事件内容
     *
     * @return array
     */
    public function requestEventLocation() {
        $location = array();
        $location['Latitude'] = $this->postObj->Latitude;
        $location['Longitude'] = $this->postObj->Longitude;
        $location['Precision'] = $this->postObj->Precision;

        return $location;
    }

    /**
     * 获取菜单点击事件内容
     *
     * @return string
     */
    public function requestEventClick() {
        return strval($this->postObj->EventKey);
    }

    /**
     * GET方法
     *
     * @param $link
     *
     * @return mixed
     */
    private static function get($link) {
        if (self::$debug)
            Log::out("weixin_debug", 'I', "get:" . $link);

        return json_decode(Tools::curl($link));
    }

    /**
     * POST方法
     *
     * @param $link
     * @param $data
     *
     * @return mixed
     */
    private static function post($link, $data) {
        if (self::$debug)
            Log::out("weixin_debug", 'I', "post:", $link . ":" . serialize($data));

        return json_decode(Tools::curl($link, 'POST', $data));
    }

    /**
     * 向微信服务器ECHO内容
     *
     * @param $content
     */
    private static function response($content) {
        if (self::$debug)
            Log::out("weixin_debug", 'I', 'echo:' . $content);
        echo $content;
    }

    /**
     * 获取错误代码中文描述
     *
     * @param $errorcode
     *
     * @return mixed
     */
    public static function error($errorcode) {
        return self::$errors[$errorcode];
    }

    /**
     * 回复文本消息
     *
     * @param $content
     */
    public function responseTextMessage($content) {
        $textTpl = "<xml>
        <ToUserName><![CDATA[%s]]></ToUserName>
        <FromUserName><![CDATA[%s]]></FromUserName>
        <CreateTime>%s</CreateTime>
        <MsgType><![CDATA[%s]]></MsgType>
        <Content><![CDATA[%s]]></Content>
        </xml>";
        $resultStr = sprintf($textTpl, $this->postObj->FromUserName, $this->postObj->ToUserName, time(), self::REPLY_TYPE_TEXT, $content);
        if (!headers_sent())
            header('Content-Type: application/xml; charset=utf-8');
        self::response($resultStr);
    }

    /**
     * 回复图片消息
     *
     * @param $mediaid
     */
    public function responseImageMessage($mediaid) {
        $textTpl = "<xml>
        <ToUserName><![CDATA[%s]]></ToUserName>
        <FromUserName><![CDATA[%s]]></FromUserName>
        <CreateTime>%s</CreateTime>
        <MsgType><![CDATA[%s]]></MsgType>
        <Image>
        <MediaId><![CDATA[%s]]></MediaId>
        </Image>
        </xml>";
        $resultStr = sprintf($textTpl, $this->postObj->FromUserName, $this->postObj->ToUserName, time(), self::REPLY_TYPE_IMAGE, $mediaid);
        if (!headers_sent())
            header('Content-Type: application/xml; charset=utf-8');
        self::response($resultStr);
    }

    /**
     * 回复语音消息
     *
     * @param $mediaid
     */
    public function responseVoiceMessage($mediaid) {
        $textTpl = "<xml>
        <ToUserName><![CDATA[%s]]></ToUserName>
        <FromUserName><![CDATA[%s]]></FromUserName>
        <CreateTime>%s</CreateTime>
        <MsgType><![CDATA[%s]]></MsgType>
        <Voice>
        <MediaId><![CDATA[%s]]></MediaId>
        </Voice>
        </xml>";
        $resultStr = sprintf($textTpl, $this->postObj->FromUserName, $this->postObj->ToUserName, time(), self::REPLY_TYPE_VOICE, $mediaid);
        if (!headers_sent())
            header('Content-Type: application/xml; charset=utf-8');
        self::response($resultStr);
    }

    /**
     * 回复视频消息
     *
     * @param        $mediaid
     * @param string $title
     * @param string $description
     */
    public function responseVideoMessage($mediaid, $title = "", $description = "") {
        $textTpl = "<xml>
        <ToUserName><![CDATA[%s]]></ToUserName>
        <FromUserName><![CDATA[%s]]></FromUserName>
        <CreateTime>%s</CreateTime>
        <MsgType><![CDATA[%s]]></MsgType>
        <Video>
        <MediaId><![CDATA[%s]]></MediaId>
        <Title><![CDATA[%s]]></Title>
        <Description><![CDATA[%s]]></Description>
        </Video>
        </xml>";
        $resultStr = sprintf($textTpl, $this->postObj->FromUserName, $this->postObj->ToUserName, time(), self::REPLY_TYPE_VIDEO, $mediaid, $title, $description);
        if (!headers_sent())
            header('Content-Type: application/xml; charset=utf-8');
        self::response($resultStr);
    }

    /**
     * 回复音乐消息
     *
     * @param string $title
     * @param string $description
     * @param string $url
     * @param string $hq_url
     */
    public function responseMusicMessage($title = '', $description = '', $url = '', $hq_url = '') {
        $textTpl = '<xml>
        <ToUserName><![CDATA[%s]]></ToUserName>
        <FromUserName><![CDATA[%s]]></FromUserName>
        <CreateTime>%s</CreateTime>
        <MsgType><![CDATA[%s]]></MsgType>
        <Music>
        <Title><![CDATA[%s]]></Title>
        <Description><![CDATA[%s]]></Description>
        <MusicUrl><![CDATA[%s]]></MusicUrl>
        <HQMusicUrl><![CDATA[%s]]></HQMusicUrl>
        </Music>
        </xml>';
        $resultStr = sprintf($textTpl, $this->postObj->FromUserName, $this->postObj->ToUserName, time(), self::REPLY_TYPE_MUSIC, $title, $description, $url, $hq_url);
        if (!headers_sent())
            header('Content-Type: application/xml; charset=utf-8');
        self::response($resultStr);
    }

    /**
     * 回复图文消息
     *
     * @param $items
     *
     * @throws Exception
     */
    public function responseNewsMessage($items) {
        $textTpl = '<xml>
        <ToUserName><![CDATA[%s]]></ToUserName>
        <FromUserName><![CDATA[%s]]></FromUserName>
        <CreateTime>%s</CreateTime>
        <MsgType><![CDATA[%s]]></MsgType>
        <ArticleCount>%d</ArticleCount>
        <Articles>%s</Articles>
        </xml>';

        $itemTpl = '<item>
        <Title><![CDATA[%s]]></Title>
        <Description><![CDATA[%s]]></Description>
        <PicUrl><![CDATA[%s]]></PicUrl>
        <Url><![CDATA[%s]]></Url>
        </item>';

        $articles = '';
        if ($items && is_array($items))
        {
            foreach ($items as $item)
            {
                if (is_array($item) && (isset($item['Title']) || isset($item['Description']) || isset($item['PicUrl']) || isset($item['Url'])))
                    $articles .= sprintf($itemTpl, $item['Title'], $item['Description'], $item['PicUrl'], $item['Url']);
                else
                    throw new Exception("item => array('Title'=>'','Description'=>'','PicUrl'=>'','Url'=>'')");
            }
        }

        $resultStr = sprintf($textTpl, $this->postObj->FromUserName, $this->postObj->ToUserName, time(), self::REPLY_TYPE_NEWS, count($items), $articles);
        if (!headers_sent())
            header('Content-Type: application/xml; charset=utf-8');
        self::response($resultStr);
    }

    /**发送文本客服消息,需要access_token
     *
     * @param $openid
     * @param $content
     *
     * @return bool|mixed
     */
    public function sendTextMessage($openid, $content) {
        if ($this->getAccessToken())
        {
            $message = array();
            $message['touser'] = $openid;
            $message['msgtype'] = "text";
            $message['text']['content'] = $content;

            return self::post(self::$links['message'] . "?access_token={$this->access_token}", json_encode($message, JSON_UNESCAPED_UNICODE));
        }

        return false;
    }

    /**
     * 发送图片客服消息,需要access_token
     *
     * @param $openid
     * @param $mediaid
     *
     * @return bool|mixed
     */
    public function sendImageMessage($openid, $mediaid) {
        if ($this->getAccessToken())
        {
            $message = array();
            $message['touser'] = $openid;
            $message['msgtype'] = "image";
            $message['image']['media_id'] = $mediaid;

            return self::post(self::$links['message'] . "?access_token={$this->access_token}", json_encode($message, JSON_UNESCAPED_UNICODE));
        }

        return false;
    }

    /**
     * 发送语音客服消息,需要access_token
     *
     * @param $openid
     * @param $media_id
     *
     * @return bool|mixed
     */
    public function sendVoiceMessage($openid, $media_id) {
        if ($this->getAccessToken())
        {
            $message = array();
            $message['touser'] = $openid;
            $message['msgtype'] = "voice";
            $message['voice']['media_id'] = $media_id;

            return self::post(self::$links['message'] . "?access_token={$this->access_token}", json_encode($message, JSON_UNESCAPED_UNICODE));
        }

        return false;
    }

    /**
     * 发送视频客服消息,需要access_token
     *
     * @param        $openid
     * @param        $media_id
     * @param string $title
     * @param string $description
     *
     * @return bool|mixed
     */
    public function sendVideoMessage($openid, $media_id, $title = "", $description = "") {
        if ($this->getAccessToken())
        {
            $message = array();
            $message['touser'] = $openid;
            $message['msgtype'] = "video";
            $message['video']['media_id'] = $media_id;
            $message['video']['title'] = $title;
            $message['video']['description'] = $description;

            return self::post(self::$links['message'] . "?access_token={$this->access_token}", json_encode($message, JSON_UNESCAPED_UNICODE));
        }

        return false;
    }

    /**
     * 发送音乐客服消息,需要access_token
     *
     * @param        $openid
     * @param        $url
     * @param        $hq_url
     * @param        $media_id
     * @param string $title
     * @param string $description
     *
     * @return bool|mixed
     */
    public function sendMusicMessage($openid, $url, $hq_url, $media_id, $title = "", $description = "") {
        if ($this->getAccessToken())
        {
            $message = array();
            $message['touser'] = $openid;
            $message['msgtype'] = "music";
            $message['music']['title'] = $title;
            $message['music']['description'] = $description;
            $message['music']['musicurl'] = $url;
            $message['music']['hqmusicurl'] = $hq_url;
            $message['music']['thumb_media_id'] = $media_id;

            return self::post(self::$links['message'] . "?access_token={$this->access_token}", json_encode($message, JSON_UNESCAPED_UNICODE));
        }

        return false;
    }

    /**
     * 发送图文客服消息,需要access_token
     *
     * @param $openid
     * @param $items
     *
     * @return bool|mixed
     * @throws Exception
     */
    public function sendNewsMessage($openid, $items) {
        if ($this->getAccessToken())
        {
            $message = array();
            $message['touser'] = $openid;
            $message['msgtype'] = "news";
            if ($items && is_array($items))
            {
                foreach ($items as $item)
                {
                    if (is_array($item) && (isset($item['Title']) || isset($item['Description']) || isset($item['PicUrl']) || isset($item['Url'])))
                    {
                        $it['title'] = isset($item['Title']) ? $item['Title'] : "";
                        $it['description'] = isset($item['Description']) ? $item['Description'] : "";
                        $it['url'] = isset($item['Url']) ? $item['Url'] : "";
                        $it['picurl'] = isset($item['PicUrl']) ? $item['PicUrl'] : "";
                        if ($it['title'] && $it['description'] && $it['url'] && $it['picurl'])
                            $message['news']['articles'][] = $it;
                    }
                    else
                        throw new Exception("item => array('Title'=>'','Description'=>'','PicUrl'=>'','Url'=>'')");
                }
            }

            return self::post(self::$links['message'] . "?access_token={$this->access_token}", json_encode($message, JSON_UNESCAPED_UNICODE));
        }

        return false;
    }

    /**
     * 创建分组
     *
     * @param $name
     *
     * @return bool|mixed
     */
    public function groupCreate($name) {
        if ($this->getAccessToken())
        {
            $message = array();
            $message['name'] = $name;

            return self::post(self::$links['group_create'] . "?access_token={$this->access_token}", json_encode($message, JSON_UNESCAPED_UNICODE));
        }

        return false;
    }

    /**
     * 查询所有分组
     *
     * @return bool|mixed
     */
    public function groupGetAll() {
        if ($this->getAccessToken())
        {
            return self::get(self::$links['group_get'] . "?access_token={$this->access_token}");
        }

        return false;
    }

    /**
     * 查询用户所在分组
     *
     * @param $openid
     *
     * @return bool|mixed
     */
    public function groupGet($openid) {
        if ($this->getAccessToken())
        {
            $message = array();
            $message['openid'] = $openid;

            return self::post(self::$links['group_getid'] . "?access_token={$this->access_token}", json_encode($message, JSON_UNESCAPED_UNICODE));
        }

        return false;
    }

    /**
     * 修改分组名
     *
     * @param $groupid
     * @param $name
     *
     * @return bool|mixed
     */
    public function groupRename($groupid, $name) {
        if ($this->getAccessToken())
        {
            $message = array();
            $message['id'] = $groupid;
            $message['name'] = $name;

            return self::post(self::$links['group_rename'] . "?access_token={$this->access_token}", json_encode($message, JSON_UNESCAPED_UNICODE));
        }

        return false;
    }

    /**
     * 移动用户分组
     *
     * @param $openid
     * @param $to_groupid
     *
     * @return bool|mixed
     */
    public function groupMove($openid, $to_groupid) {
        if ($this->getAccessToken())
        {
            $message = array();
            $message['openid'] = $openid;
            $message['to_groupid'] = $to_groupid;

            return self::post(self::$links['group_move'] . "?access_token={$this->access_token}", json_encode($message, JSON_UNESCAPED_UNICODE));
        }

        return false;
    }

    /**
     * 获取用户基本信息
     *
     * @param $openid
     *
     * @return bool|mixed
     */
    public function userInfo($openid) {
        if ($this->getAccessToken())
        {
            return self::get(self::$links['user_info'] . "?access_token={$this->access_token}&openid={$openid}");
        }

        return false;
    }

    /**
     * 获取关注者列表
     *
     * @param null $next_openid
     *
     * @return bool|mixed
     */
    public function userGet($next_openid = null) {
        if ($this->getAccessToken())
        {
            if ($next_openid)
                return self::get(self::$links['user_get'] . "?access_token={$this->access_token}&openid=");
            else
                return self::get(self::$links['user_get'] . "?access_token={$this->access_token}&next_openid={$next_openid}");
        }

        return false;
    }

    /**
     * 创建自定义菜单
     *
     * @param $menus
     *
     * @return bool|mixed
     */
    public function menuCreate($menus) {
        if ($this->getAccessToken())
        {
            $message = array();
            $message['button'] = $menus;

            return self::post(self::$links['menu_create'] . "?access_token={$this->access_token}", json_encode($message, JSON_UNESCAPED_UNICODE));
        }

        return false;
    }

    /**
     * 获取自定义菜单
     *
     * @return bool|mixed
     */
    public function menuGet() {
        if ($this->getAccessToken())
        {
            return self::get(self::$links['menu_get'] . "?access_token={$this->access_token}");
        }

        return false;
    }

    /**
     * 删除自定义菜单
     *
     * @return bool|mixed
     */
    public function menuDelete() {
        if ($this->getAccessToken())
        {
            return self::get(self::$links['menu_delete'] . "?access_token={$this->access_token}");
        }

        return false;
    }

    /**
     * 生成带参数的二维码,通过expire_seconds可以设置临时二维码或永久二维码
     *
     * @param     $sceneid
     * @param int $expire_seconds
     *
     * @return bool|mixed
     */
    public function qrcodeCreate($sceneid, $expire_seconds = 0) {
        if ($this->getAccessToken())
        {
            $message = array();
            if ($expire_seconds > 0)
            {
                if ($expire_seconds > 1800)
                    return false;
                $message['expire_seconds'] = $expire_seconds;
                $message['action_name'] = 'QR_SCENE';
            }
            else
                $message['action_name'] = 'QR_LIMIT_SCENE';
            $message['action_info']['scene']['scene_id'] = $sceneid;

            return self::post(self::$links['qrcode'] . "?access_token={$this->access_token}", json_encode($message, JSON_UNESCAPED_UNICODE));
        }

        return false;
    }

    /**
     * 用ticket换取二维码图片,返回结果未图片,可以直接展示或者下载
     *
     * @param $ticket
     *
     * @return mixed
     */
    public static function qrcodeGet($ticket) {
        return self::get(self::$links['showqrcode'] . "?tocket=" . urlencode($ticket));
    }

    /**
     * 下载多媒体文件
     *
     * @param $mediaid
     *
     * @return mixed
     */
    public function mediaDownload($mediaid) {
        return self::get(self::$links['media_download'] . "?access_token={$this->access_token}&media_id=" . $mediaid);
    }

    /**
     * 上传多媒体文件
     * 图片(image): 128K,支持JPG格式
     * 语音(voice):256K,播放长度不超过60s,支持AMR\MP3格式
     * 视频(video):1MB,支持MP4格式
     * 缩略图(thumb):64KB,支持JPG格式
     *
     * @param $filetype
     * @param $filepath
     *
     * @return bool|mixed
     */
    public function mediaUpload($filetype, $filepath) {
        if ($this->getAccessToken() && file_exists($filepath))
        {
            $fileext = strtolower(Tools::getFileExtension($filepath));
            $filesize = filesize($filepath);
            switch ($filetype)
            {
                case self::MEDIA_TYPE_IMAGE:
                    if ($fileext != 'jpg' || $filesize > 128 * 1024)
                        return false;
                    break;
                case self::MEDIA_TYPE_VOICE:
                    if (!in_array($fileext, array('amr', 'mp3')) || $filesize > 256 * 1024)
                        return false;
                    break;
                case self::MEDIA_TYPE_VIDEO:
                    if ($fileext != 'mp4' || $filesize > 1024 * 1024)
                        return false;
                    break;
                case self::MEDIA_TYPE_THUMB:
                    if ($fileext != 'jpg' || $filesize > 64 * 1024)
                        return false;
                    break;
                default:
                    return false;
                    break;
            }
            $media = array();
            $media['access_token'] = $this->access_token;
            $media['type'] = $filetype;
            $media['media'] = '@' . $filepath;

            return self::post(self::$links['media_upload'], $media);
        }

        return false;
    }

    /**
     * 第三方网页通过Oauth2.0获取用户授权
     * 获取code
     */
    public function oauthGetCode($redirect, $scope, $state) {
        return self::$links['oauth_code'] . "?appid={$this->appid}&redirect_uri={$redirect}&response_type=code&scope={$scope}&state={$state}#wechat_redirect";
    }

    /**
     * 第三方网页通过Oauth2.0获取用户授权
     * 通过code获取access_token
     */
    public function oauthGetAccessToken($code) {
        return self::get(self::$links['oauth_access_token'] . "?appid={$this->appid}&secret={$this->appsecret}&code={$code}&grant_type=authorization_code");
    }

    /**
     * 第三方网页通过Oauth2.0获取用户授权
     * 刷新access_token
     */
    public function oauthRefreshAccessToken($refresh_token) {
        return self::get(self::$links['oauth_refresh'] . "?appid={$this->appid}&grant_type=refresh_token&refresh_token={$refresh_token}");
    }

    /**
     * 第三方网页通过Oauth2.0获取用户权限
     * 获取用户信息,仅限scope为SCOPE_POP
     */
    public function oauthUserInfo($access_token, $openid) {
        return self::get(self::$links['oauth_userinfo'] . "?access_token={$access_token}&openid={$openid}");
    }

    /**
     * 返回js代码,隐藏微信中网页右上角按钮
     *
     * @return string
     */
    public static function hideOptionMenu() {
        return <<<EOF
<script type="text/javascript">
document.addEventListener('WeixinJSBridgeReady', function onBridgeReady() {
    WeixinJSBridge.call('hideOptionMenu');
});
</script>
EOF;
    }

    /**
     * 返回js代码,隐藏微信中网页底部导航栏
     *
     * @return string
     */
    public static function hideToolbar() {
        return <<<EOF
<script type="text/javascript">
document.addEventListener('WeixinJSBridgeReady', function onBridgeReady() {
    WeixinJSBridge.call('hideToolbar');
});
</script>
EOF;
    }

    /**
     * 返回js代码,网页获取用户网络状态
     *
     * @return string
     */
    public static function getNetworkType() {
        return <<<EOF
<script type="text/javascript">
WeixinJSBridge.invoke('getNetworkType',{},
    function(e){
        WeixinJSBridge.log(e.err_msg);
    }
);
</script>
EOF;
    }

    /**
     * 返回微信支付的JS代码
     */
    public static function getPayment($params,$account){
        global $_W;
        $wOpt['appId'] = $_W['account']['key'];
        $wOpt['timeStamp'] = TIMESTAMP;
        $wOpt['nonceStr'] = random(8);
        $package = array();
        $package['bank_type'] = 'WX';
        $package['body'] = $params['title'];
        $package['attach'] = $_W['weid'];
        $package['partner'] = $wechat['partner'];
        $package['out_trade_no'] = $params['tid'];
        $package['total_fee'] = $params['fee'] * 100;
        $package['fee_type'] = '1';
        $package['notify_url'] = $_W['siteroot'] . 'payment/wechat/notify.php';
        $package['spbill_create_ip'] = CLIENT_IP;
        $package['time_start'] = date('YmdHis', TIMESTAMP);
        $package['time_expire'] = date('YmdHis', TIMESTAMP + 600);
        $package['input_charset'] = 'UTF-8';
        ksort($package);
        $string1 = '';
        foreach ($package as $key => $v)
        {
            $string1 .= "{$key}={$v}&";
        }
        $string1 .= "key={$wechat['key']}";
        $sign = strtoupper(md5($string));

        $string2 = '';
        foreach ($package as $key => $v)
        {
            $v = urlencode($v);
            $string2 .= "{$key}={$v}&";
        }
        $string2 .= "sign={$sign}";

        $string = '';
        $keys = array('appId', 'timeStamp', 'nonceStr', 'package', 'appKey');
        sort($keys);
        foreach ($keys as $key)
        {
            $v = $wOpt[$key];
            if ($key == 'appKey')
            {
                $v = $wechat['signkey'];
            }
            $key = strtolower($key);
            $string .= "{$key}={$v}&";
        }
        $string = rtrim($string, '&');

        $wOpt['package'] = $string2;
        $wOpt['signType'] = 'SHA1';
        $wOpt['paySign'] = sha1($string);

        return $wOpt;
    }
}