Selaa lähdekoodia

Merge pull request #136 from binsee/master

公众号类库 增加对消息体签名加密方案 的支持
dodge 10 vuotta sitten
vanhempi
commit
3beafc7d05
10 muutettua tiedostoa jossa 843 lisäystä ja 137 poistoa
  1. 0 5
      .buildpath
  2. 2 1
      .gitignore
  3. 5 1
      README.md
  4. 286 29
      Thinkphp/Wechat.class.php
  5. 132 37
      Thinkphp/qywechat.class.php
  6. 1 0
      errCode.php
  7. 132 37
      qywechat.class.php
  8. 281 27
      wechat.class.php
  9. 3 0
      wiki/企业号API类库.md
  10. 1 0
      wiki/官方API类库.md

+ 0 - 5
.buildpath

@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<buildpath>
-	<buildpathentry kind="src" path=""/>
-	<buildpathentry kind="con" path="org.eclipse.php.core.LANGUAGE"/>
-</buildpath>

+ 2 - 1
.gitignore

@@ -1,3 +1,4 @@
 /nbproject/private/
 .buildpath
-/pages/
+/pages/
+/my/

+ 5 - 1
README.md

@@ -56,6 +56,7 @@ https://mp.weixin.qq.com/cgi-bin/readtemplate?t=business/course2_tmpl&lang=zh_CN
 ```php
  $options = array(
 	'token'=>'tokenaccesskey', //填写你设定的key
+	'encodingaeskey'=>'encodingaeskey', //填写加密用的EncodingAESKey
 	'appid'=>'wxdk1234567890', //填写高级调用功能的app id, 请在微信开发模式后台查询
 	'appsecret'=>'xxxxxxxxxxxxxxxxxxx', //填写高级调用功能的密钥
 	'partnerid'=>'88888888', //财付通商户身份标识,支付权限专用,没有可不填
@@ -239,7 +240,10 @@ $options = array(
 * getRevPic() 返回图片信息(图片型信息) 返回数组{'mediaid'=>'','picurl'=>''}
 * getRevGeo() 返回地理位置(位置型信息) 返回数组{'x'=>'','y'=>'','scale'=>'','label'=>''}
 * getRevEventGeo() 返回事件地理位置(事件型信息) 返回数组{'x'=>'','y'=>'','precision'=>''}
-* getRevEvent() 返回事件类型(事件型信息) 返回数组{'event'=>'','key'=>''}
+* getRevEvent() 返回事件类型(事件型信息) 返回数组{'event'=>'','key'=>''}
+* getRevScanInfo() 获取自定义菜单的扫码推事件信息,事件类型为`scancode_push`或`scancode_waitmsg` 返回数组array ('ScanType'=>'qrcode','ScanResult'=>'123123')
+* getRevSendPicsInfo() 获取自定义菜单的图片发送事件信息,事件类型为`pic_sysphoto`或`pic_photo_or_album`或`pic_weixin` 数组结构见php文件内方法说明
+* getRevSendGeoInfo() 获取自定义菜单的地理位置选择器事件推送,事件类型为`location_select` 数组结构见php文件内方法说明
 * getRevVoice() 返回语音信息(语音型信息) 返回数组{'mediaid'=>'','format'=>''}
 * getRevVoice() 返回语音信息(语音型信息) 返回数组{'mediaid'=>'','format'=>''}
 * getRevVideo() 返回视频信息(视频型信息) 返回数组{'mediaid'=>'','thumbmediaid'=>''}

+ 286 - 29
Thinkphp/Wechat.class.php

@@ -7,6 +7,7 @@
  *  usage:
  *   $options = array(
  *			'token'=>'tokenaccesskey', //填写你设定的key
+ *			'encodingaeskey'=>'encodingaeskey', //填写加密用的EncodingAESKey
  *			'appid'=>'wxdk1234567890', //填写高级调用功能的app id
  *			'appsecret'=>'xxxxxxxxxxxxxxxxxxx', //填写高级调用功能的密钥
  *			'partnerid'=>'88888888', //财付通商户身份标识
@@ -96,6 +97,8 @@ class Wechat
 	const SEMANTIC_API_URL= 'https://api.weixin.qq.com/semantic/semproxy/search?';
 	
 	private $token;
+	private $encodingAesKey;
+	private $encrypt_type;
 	private $appid;
 	private $appsecret;
 	private $access_token;
@@ -103,6 +106,7 @@ class Wechat
 	private $partnerid;
 	private $partnerkey;
 	private $paysignkey;
+	private $postxml;
 	private $_msg;
 	private $_funcflag = false;
 	private $_receive;
@@ -115,6 +119,7 @@ class Wechat
 	public function __construct($options)
 	{
 		$this->token = isset($options['token'])?$options['token']:'';
+		$this->encodingAesKey = isset($options['encodingaeskey'])?$options['encodingaeskey']:'';
 		$this->appid = isset($options['appid'])?$options['appid']:'';
 		$this->appsecret = isset($options['appsecret'])?$options['appsecret']:'';
 		$this->partnerid = isset($options['partnerid'])?$options['partnerid']:'';
@@ -127,14 +132,15 @@ class Wechat
 	/**
 	 * For weixin server validation 
 	 */	
-	private function checkSignature()
+	private function checkSignature($str='')
 	{
         $signature = isset($_GET["signature"])?$_GET["signature"]:'';
+	    $signature = isset($_GET["msg_signature"])?$_GET["msg_signature"]:$signature; //如果存在加密验证则用加密验证段
         $timestamp = isset($_GET["timestamp"])?$_GET["timestamp"]:'';
         $nonce = isset($_GET["nonce"])?$_GET["nonce"]:'';
         		
 		$token = $this->token;
-		$tmpArr = array($token, $timestamp, $nonce);
+		$tmpArr = array($token, $timestamp, $nonce,$str);
 		sort($tmpArr, SORT_STRING);
 		$tmpStr = implode( $tmpArr );
 		$tmpStr = sha1( $tmpStr );
@@ -152,23 +158,46 @@ class Wechat
 	 */
 	public function valid($return=false)
     {
-        $echoStr = isset($_GET["echostr"]) ? $_GET["echostr"]: '';
+        $encryptStr="";
+        if ($_SERVER['REQUEST_METHOD'] == "POST") {
+            $postStr = file_get_contents("php://input");
+            $array = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
+            $this->encrypt_type = isset($_GET["encrypt_type"]) ? $_GET["encrypt_type"]: '';
+            if ($this->encrypt_type == 'aes') { //aes加密
+                $this->log($postStr);
+            	$encryptStr = $array['Encrypt'];
+            	$pc = new Prpcrypt($this->encodingAesKey);
+            	$array = $pc->decrypt($encryptStr,$this->appid);
+            	if (!isset($array[0]) || ($array[0] != 0)) {
+            	    if (!$return) {
+            	        die('解密失败!');
+            	    } else {
+            	        return false;
+            	    }
+            	}
+            	$this->postxml = $array[1];
+            } else {
+                $this->postxml = $postStr;
+            }
+        } else {
+            $echoStr = isset($_GET["echostr"]) ? $_GET["echostr"]: '';
+        }
         if ($return) {
         		if ($echoStr) {
-        			if ($this->checkSignature()) 
+        			if ($this->checkSignature($encryptStr)) 
         				return $echoStr;
         			else
         				return false;
         		} else 
-        			return $this->checkSignature();
+        			return $this->checkSignature($encryptStr);
         } else {
 	        	if ($echoStr) {
-	        		if ($this->checkSignature())
+	        		if ($this->checkSignature($encryptStr))
 	        			die($echoStr);
 	        		else 
 	        			die('no access');
 	        	}  else {
-	        		if ($this->checkSignature())
+	        		if ($this->checkSignature($encryptStr))
 	        			return true;
 	        		else
 	        			die('no access');
@@ -219,7 +248,8 @@ class Wechat
 	public function getRev()
 	{
 		if ($this->_receive) return $this;
-		$postStr = file_get_contents("php://input");
+		$postStr = !empty($this->postxml)?$this->postxml:file_get_contents("php://input");
+		//兼顾使用明文又不想调用valid()方法的情况
 		$this->log($postStr);
 		if (!empty($postStr)) {
 			$this->_receive = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
@@ -410,19 +440,11 @@ class Wechat
 	 * @return: array | false
 	 * array (
 	 *   'Count' => '2',
-	 *   'PicList' => 
-	 *   array (
-	 *     'item' => 
-	 *     array (
-	 *       0 => 
-	 *       array (
-	 *         'PicMd5Sum' => 'aaae42617cf2a14342d96005af53624c',
-	 *       ),
-	 *       1 => 
-	 *       array (
-	 *         'PicMd5Sum' => '149bd39e296860a2adc2f1bb81616ff8',
-	 *       ),
-	 *     ),
+	 *   'PicList' =>array (
+	 *         'item' =>array (
+	 *             0 =>array ('PicMd5Sum' => 'aaae42617cf2a14342d96005af53624c'),
+	 *             1 =>array ('PicMd5Sum' => '149bd39e296860a2adc2f1bb81616ff8'),
+	 *         ),
 	 *   ),
 	 * )
 	 * 
@@ -653,6 +675,7 @@ class Wechat
 		$this->Message($msg);
 		return $this;
 	}
+	
 	/**
 	 * 设置回复消息
 	 * Examle: $obj->voice('media_id')->reply();
@@ -672,6 +695,7 @@ class Wechat
 		$this->Message($msg);
 		return $this;
 	}
+	
 	/**
 	 * 设置回复消息
 	 * Examle: $obj->video('media_id','title','description')->reply();
@@ -767,11 +791,44 @@ class Wechat
 			$msg = $this->_msg;
 		$xmldata=  $this->xml_encode($msg);
 		$this->log($xmldata);
+		if ($this->encrypt_type == 'aes') { //如果来源消息为加密方式
+		    $pc = new Prpcrypt($this->encodingAesKey);
+		    $array = $pc->encrypt($xmldata, $this->appid);
+		    $ret = $array[0];
+		    if ($ret != 0) {
+		        $this->log('encrypt err!');
+		        return false;
+		    }
+		    $timestamp = time();
+		    $nonce = rand(77,999)*rand(605,888)*rand(11,99);
+		    $encrypt = $array[1];
+		    $tmpArr = array($this->token, $timestamp, $nonce,$encrypt);//比普通公众平台多了一个加密的密文
+		    sort($tmpArr, SORT_STRING);
+		    $signature = implode($tmpArr);
+		    $signature = sha1($signature);
+		    $xmldata = $this->generate($encrypt, $signature, $timestamp, $nonce);
+		    $this->log($xmldata);
+		}
 		if ($return)
 			return $xmldata;
 		else
 			echo $xmldata;
 	}
+
+    /**
+     * xml格式加密,仅请求为加密方式时再用
+     */
+	private function generate($encrypt, $signature, $timestamp, $nonce)
+	{
+	    //格式化加密信息
+	    $format = "<xml>
+<Encrypt><![CDATA[%s]]></Encrypt>
+<MsgSignature><![CDATA[%s]]></MsgSignature>
+<TimeStamp>%s</TimeStamp>
+<Nonce><![CDATA[%s]]></Nonce>
+</xml>";
+	    return sprintf($format, $encrypt, $signature, $timestamp, $nonce);
+	}
 	
 	/**
 	 * GET 请求
@@ -940,13 +997,11 @@ class Wechat
      * 	              'type' => 'scancode_waitmsg',
      * 	              'name' => '扫码带提示',
      * 	              'key' => 'rselfmenu_0_0',
-     * 	              'sub_button' => ''
      * 	            ),
      * 	            1 => array (
      * 	              'type' => 'scancode_push',
      * 	              'name' => '扫码推事件',
      * 	              'key' => 'rselfmenu_0_1',
-     * 	              'sub_button' => ''
      * 	            ),
      * 	        ),
      * 	      ),
@@ -957,21 +1012,18 @@ class Wechat
      * 	              'type' => 'pic_sysphoto',
      * 	              'name' => '系统拍照发图',
      * 	              'key' => 'rselfmenu_1_0',
-     * 	              'sub_button' => ''
      * 	            ),
      * 	            1 => array (
      * 	              'type' => 'pic_photo_or_album',
      * 	              'name' => '拍照或者相册发图',
      * 	              'key' => 'rselfmenu_1_1',
-     * 	              'sub_button' => ''
      * 	            )
      * 	        ),
      * 	      ),
      * 	      2 => array (
      * 	        'type' => 'location_select',
      * 	        'name' => '发送位置',
-     * 	        'key' => 'rselfmenu_2_0',
-     * 	        'sub_button' => ''
+     * 	        'key' => 'rselfmenu_2_0'
      * 	      ),
      * 	    ),
      * 	)
@@ -1925,11 +1977,12 @@ class Wechat
 	            'uid' => ''
 	    );
 	    //地理坐标或城市名称二选一
-	    if (isset($latitude)) {
+	    if ($latitude) {
 	        $data['latitude'] = $latitude;
 	        $data['longitude'] = $longitude;
-	    } elseif (isset($city)) {
+	    } elseif ($city) {
 	        $data['city'] = $city;
+	    } elseif ($region) {
 	        $data['region'] = $region;
 	    }
 	    $result = $this->http_post(self::SEMANTIC_API_URL.'access_token='.$this->access_token,self::json_encode($data));
@@ -1946,3 +1999,207 @@ class Wechat
 	    return false;
 	}
 }
+
+
+
+/**
+ * PKCS7Encoder class
+ *
+ * 提供基于PKCS7算法的加解密接口.
+ */
+class PKCS7Encoder
+{
+    public static $block_size = 32;
+
+    /**
+     * 对需要加密的明文进行填充补位
+     * @param $text 需要进行填充补位操作的明文
+     * @return 补齐明文字符串
+     */
+    function encode($text)
+    {
+        $block_size = PKCS7Encoder::$block_size;
+        $text_length = strlen($text);
+        //计算需要填充的位数
+        $amount_to_pad = PKCS7Encoder::$block_size - ($text_length % PKCS7Encoder::$block_size);
+        if ($amount_to_pad == 0) {
+            $amount_to_pad = PKCS7Encoder::block_size;
+        }
+        //获得补位所用的字符
+        $pad_chr = chr($amount_to_pad);
+        $tmp = "";
+        for ($index = 0; $index < $amount_to_pad; $index++) {
+            $tmp .= $pad_chr;
+        }
+        return $text . $tmp;
+    }
+
+    /**
+     * 对解密后的明文进行补位删除
+     * @param decrypted 解密后的明文
+     * @return 删除填充补位后的明文
+     */
+    function decode($text)
+    {
+
+        $pad = ord(substr($text, -1));
+        if ($pad < 1 || $pad > PKCS7Encoder::$block_size) {
+            $pad = 0;
+        }
+        return substr($text, 0, (strlen($text) - $pad));
+    }
+
+}
+
+/**
+ * Prpcrypt class
+ *
+ * 提供接收和推送给公众平台消息的加解密接口.
+ */
+class Prpcrypt
+{
+    public $key;
+
+    function Prpcrypt($k)
+    {
+        $this->key = base64_decode($k . "=");
+    }
+
+    /**
+     * 对明文进行加密
+     * @param string $text 需要加密的明文
+     * @return string 加密后的密文
+     */
+    public function encrypt($text, $appid)
+    {
+
+        try {
+            //获得16位随机字符串,填充到明文之前
+            $random = "aaaabbbbccccdddd"; //$this->getRandomStr();
+            $text = $random . pack("N", strlen($text)) . $text . $appid;
+            // 网络字节序
+            $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
+            $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
+            $iv = substr($this->key, 0, 16);
+            //使用自定义的填充方式对明文进行补位填充
+            $pkc_encoder = new PKCS7Encoder;
+            $text = $pkc_encoder->encode($text);
+            mcrypt_generic_init($module, $this->key, $iv);
+            //加密
+            $encrypted = mcrypt_generic($module, $text);
+            mcrypt_generic_deinit($module);
+            mcrypt_module_close($module);
+
+            //			print(base64_encode($encrypted));
+            //使用BASE64对加密后的字符串进行编码
+            return array(ErrorCode::$OK, base64_encode($encrypted));
+        } catch (Exception $e) {
+            print $e;
+            return array(ErrorCode::$EncryptAESError, null);
+        }
+    }
+
+    /**
+     * 对密文进行解密
+     * @param string $encrypted 需要解密的密文
+     * @return string 解密得到的明文
+     */
+    public function decrypt($encrypted, $appid)
+    {
+
+        try {
+            //使用BASE64对需要解密的字符串进行解码
+            $ciphertext_dec = base64_decode($encrypted);
+            $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
+            $iv = substr($this->key, 0, 16);
+            mcrypt_generic_init($module, $this->key, $iv);
+            //解密
+            $decrypted = mdecrypt_generic($module, $ciphertext_dec);
+            mcrypt_generic_deinit($module);
+            mcrypt_module_close($module);
+        } catch (Exception $e) {
+            return array(ErrorCode::$DecryptAESError, null);
+        }
+
+
+        try {
+            //去除补位字符
+            $pkc_encoder = new PKCS7Encoder;
+            $result = $pkc_encoder->decode($decrypted);
+            //去除16位随机字符串,网络字节序和AppId
+            if (strlen($result) < 16)
+                return "";
+            $content = substr($result, 16, strlen($result));
+            $len_list = unpack("N", substr($content, 0, 4));
+            $xml_len = $len_list[1];
+            $xml_content = substr($content, 4, $xml_len);
+            $from_appid = substr($content, $xml_len + 4);
+        } catch (Exception $e) {
+            print $e;
+            return array(ErrorCode::$IllegalBuffer, null);
+        }
+        if ($from_appid != $appid)
+            return array(ErrorCode::$ValidateAppidError, null);
+        return array(0, $xml_content);
+
+    }
+
+
+    /**
+     * 随机生成16位字符串
+     * @return string 生成的字符串
+     */
+    function getRandomStr()
+    {
+
+        $str = "";
+        $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
+        $max = strlen($str_pol) - 1;
+        for ($i = 0; $i < 16; $i++) {
+            $str .= $str_pol[mt_rand(0, $max)];
+        }
+        return $str;
+    }
+
+}
+
+/**
+ * error code
+ * 仅用作类内部使用,不用于官方API接口的errCode码
+ */
+class ErrorCode
+{
+    public static $OK = 0;
+    public static $ValidateSignatureError = 40001;
+    public static $ParseXmlError = 40002;
+    public static $ComputeSignatureError = 40003;
+    public static $IllegalAesKey = 40004;
+    public static $ValidateAppidError = 40005;
+    public static $EncryptAESError = 40006;
+    public static $DecryptAESError = 40007;
+    public static $IllegalBuffer = 40008;
+    public static $EncodeBase64Error = 40009;
+    public static $DecodeBase64Error = 40010;
+    public static $GenReturnXmlError = 40011;
+    public static $errCode=array(
+            '0' => '处理成功',
+            '40001' => '校验签名失败',
+            '40002' => '解析xml失败',
+            '40003' => '计算签名失败',
+            '40004' => '不合法的AESKey',
+            '40005' => '校验AppID失败',
+            '40006' => 'AES加密失败',
+            '40007' => 'AES解密失败',
+            '40008' => '公众平台发送的xml不合法',
+            '40009' => 'Base64编码失败',
+            '40010' => 'Base64解码失败',
+            '40011' => '公众帐号生成回包xml失败'
+    );
+    public static function getErrText($err) {
+        if (isset(self::$errCode[$err])) {
+            return self::$errCode[$err];
+        }else {
+            return false;
+        };
+    }
+}

+ 132 - 37
Thinkphp/qywechat.class.php

@@ -493,6 +493,117 @@ class Wechat
 			return false;
 		}
 	}
+
+	/**
+	 * 获取自定义菜单的扫码推事件信息
+	 *
+	 * 事件类型为以下两种时则调用此方法有效
+	 * Event	 事件类型,scancode_push
+	 * Event	 事件类型,scancode_waitmsg
+	 *
+	 * @return: array | false
+	 * array (
+	 *     'ScanType'=>'qrcode',
+	 *     'ScanResult'=>'123123'
+	 * )
+	 */
+	public function getRevScanInfo(){
+	    if (isset($this->_receive['ScanCodeInfo'])){
+	        if (!is_array($this->_receive['SendPicsInfo'])) {
+	            $array=(array)$this->_receive['ScanCodeInfo'];
+	            $this->_receive['ScanCodeInfo']=$array;
+	        }else {
+	            $array=$this->_receive['ScanCodeInfo'];
+	        }
+	    }
+	    if (isset($array) && count($array) > 0) {
+	        return $array;
+	    } else {
+	        return false;
+	    }
+	}
+	
+	/**
+	 * 获取自定义菜单的图片发送事件信息
+	 *
+	 * 事件类型为以下三种时则调用此方法有效
+	 * Event	 事件类型,pic_sysphoto        弹出系统拍照发图的事件推送
+	 * Event	 事件类型,pic_photo_or_album  弹出拍照或者相册发图的事件推送
+	 * Event	 事件类型,pic_weixin          弹出微信相册发图器的事件推送
+	 *
+	 * @return: array | false
+	 * array (
+	 *   'Count' => '2',
+	 *   'PicList' =>array (
+	 *         'item' =>array (
+	 *             0 =>array ('PicMd5Sum' => 'aaae42617cf2a14342d96005af53624c'),
+	 *             1 =>array ('PicMd5Sum' => '149bd39e296860a2adc2f1bb81616ff8'),
+	 *         ),
+	 *   ),
+	 * )
+	 *
+	 */
+	public function getRevSendPicsInfo(){
+	    if (isset($this->_receive['SendPicsInfo'])){
+	        if (!is_array($this->_receive['SendPicsInfo'])) {
+	            $array=(array)$this->_receive['SendPicsInfo'];
+	            if (isset($array['PicList'])){
+	                $array['PicList']=(array)$array['PicList'];
+	                $item=$array['PicList']['item'];
+	                $array['PicList']['item']=array();
+	                foreach ( $item as $key => $value ){
+	                    $array['PicList']['item'][$key]=(array)$value;
+	                }
+	            }
+	            $this->_receive['SendPicsInfo']=$array;
+	        } else {
+	            $array=$this->_receive['SendPicsInfo'];
+	        }
+	    }
+	    if (isset($array) && count($array) > 0) {
+	        return $array;
+	    } else {
+	        return false;
+	    }
+	}
+	
+	/**
+	 * 获取自定义菜单的地理位置选择器事件推送
+	 *
+	 * 事件类型为以下时则可以调用此方法有效
+	 * Event	 事件类型,location_select        弹出系统拍照发图的事件推送
+	 *
+	 * @return: array | false
+	 * array (
+	 *   'Location_X' => '33.731655000061',
+	 *   'Location_Y' => '113.29955200008047',
+	 *   'Scale' => '16',
+	 *   'Label' => '某某市某某区某某路',
+	 *   'Poiname' => '',
+	 * )
+	 *
+	 */
+	public function getRevSendGeoInfo(){
+	    if (isset($this->_receive['SendLocationInfo'])){
+	        if (!is_array($this->_receive['SendLocationInfo'])) {
+	            $array=(array)$this->_receive['SendLocationInfo'];
+	            if (empty($array['Poiname'])) {
+	                $array['Poiname']="";
+	            }
+	            if (empty($array['Label'])) {
+	                $array['Label']="";
+	            }
+	            $this->_receive['SendLocationInfo']=$array;
+	        } else {
+	            $array=$this->_receive['SendLocationInfo'];
+	        }
+	    }
+	    if (isset($array) && count($array) > 0) {
+	        return $array;
+	    } else {
+	        return false;
+	    }
+	}
 	
 	/**
 	 * 获取接收语音推送
@@ -760,13 +871,11 @@ class Wechat
      * 	              'type' => 'scancode_waitmsg',
      * 	              'name' => '扫码带提示',
      * 	              'key' => 'rselfmenu_0_0',
-     * 	              'sub_button' => ''
      * 	            ),
      * 	            1 => array (
      * 	              'type' => 'scancode_push',
      * 	              'name' => '扫码推事件',
      * 	              'key' => 'rselfmenu_0_1',
-     * 	              'sub_button' => ''
      * 	            ),
      * 	        ),
      * 	      ),
@@ -777,25 +886,23 @@ class Wechat
      * 	              'type' => 'pic_sysphoto',
      * 	              'name' => '系统拍照发图',
      * 	              'key' => 'rselfmenu_1_0',
-     * 	              'sub_button' => ''
      * 	            ),
      * 	            1 => array (
      * 	              'type' => 'pic_photo_or_album',
      * 	              'name' => '拍照或者相册发图',
      * 	              'key' => 'rselfmenu_1_1',
-     * 	              'sub_button' => ''
      * 	            )
      * 	        ),
      * 	      ),
      * 	      2 => array (
      * 	        'type' => 'location_select',
      * 	        'name' => '发送位置',
-     * 	        'key' => 'rselfmenu_2_0',
-     * 	        'sub_button' => ''
+     * 	        'key' => 'rselfmenu_2_0'
      * 	      ),
      * 	    ),
      * 	)
-     * type可以选择为以下几种,只是目前企业号3-8的类型不能收到事件消息,但可以达到效果。
+     * type可以选择为以下几种,会收到相应类型的事件推送。请注意,3到8的所有事件,仅支持微信iPhone5.4.1以上版本,
+     * 和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。
      * 1、click:点击推事件
      * 2、view:跳转URL
      * 3、scancode_push:扫码推事件
@@ -1485,41 +1592,29 @@ class Wechat
 	 *         ),
 	 * 
 	 *         "news" => array(			//不支持保密
-	 *                 "articles":[
-	 *                 array(
-	 *                         "title" => "Title",
-	 *                         "description" => "Description",
-	 *                         "url" => "URL",
-	 *                         "picurl" => "PIC_URL",
-	 *                 ),
-	 *                 array(
-	 *                         "title" => "Title",
-	 *                         "description" => "Description",
-	 *                         "url" => "URL",
-	 *                         "picurl" => "PIC_URL",
+	 *                 "articles" => array(    //articles  图文消息,一个图文消息支持1到10个图文
+	 *                     array(
+	 *                         "title" => "Title",             //标题
+	 *                         "description" => "Description", //描述
+	 *                         "url" => "URL",                 //点击后跳转的链接。可根据url里面带的code参数校验员工的真实身份。
+	 *                         "picurl" => "PIC_URL",          //图文消息的图片链接,支持JPG、PNG格式,较好的效果为大图640*320,
+	 *                                                         //小图80*80。如不填,在客户端不显示图片
+	 *                     ),
 	 *                 )
-	 *                 ]
 	 *         ),
 	 * 
 	 *         "mpnews" => array(
-	 *                 "articles":[
-	 *                 array(
-	 *                         "thumb_media_id" => "id",
-	 *                         "author" => "Author",
-	 *                         "content_source_url" => "URL",
-	 *                         "content" => "Content"
-	 *                         "digest" => "Digest description",
-	 *                         "show_cover_pic" => "0"
-	 *                 ),
-	 *                 array(
-	 *                         "thumb_media_id" => "id",
-	 *                         "author" => "Author",
-	 *                         "content_source_url" => "URL",
-	 *                         "content" => "Content"
-	 *                         "digest" => "Digest description",
-	 *                         "show_cover_pic" => "0"
+	 *                 "articles" => array(    //articles  图文消息,一个图文消息支持1到10个图文
+	 *                     array(
+	 *                         "title" => "Title",             //图文消息的标题
+	 *                         "thumb_media_id" => "id",       //图文消息缩略图的media_id
+	 *                         "author" => "Author",           //图文消息的作者(可空)
+	 *                         "content_source_url" => "URL",  //图文消息点击“阅读原文”之后的页面链接(可空)
+	 *                         "content" => "Content"          //图文消息的内容,支持html标签
+	 *                         "digest" => "Digest description",   //图文消息的描述
+	 *                         "show_cover_pic" => "0"         //是否显示封面,1为显示,0为不显示(可空)
+	 *                     ),
 	 *                 )
-	 *                 ]
 	 *         )
 	 * )
 	 * 请查看官方开发文档中的 发送消息 -> 消息类型及数据格式

+ 1 - 0
errCode.php

@@ -89,6 +89,7 @@ class ErrCode
 	    '45016'=>'系统分组,不允许修改',
 	    '45017'=>'分组名字过长',
 	    '45018'=>'分组数量超过上限',
+	    '45024'=>'账号数量超过上限',
 	    '46001'=>'不存在媒体数据',
 	    '46002'=>'不存在的菜单版本',
 	    '46003'=>'不存在的菜单数据',

+ 132 - 37
qywechat.class.php

@@ -488,6 +488,117 @@ class Wechat
 			return false;
 		}
 	}
+
+	/**
+	 * 获取自定义菜单的扫码推事件信息
+	 *
+	 * 事件类型为以下两种时则调用此方法有效
+	 * Event	 事件类型,scancode_push
+	 * Event	 事件类型,scancode_waitmsg
+	 *
+	 * @return: array | false
+	 * array (
+	 *     'ScanType'=>'qrcode',
+	 *     'ScanResult'=>'123123'
+	 * )
+	 */
+	public function getRevScanInfo(){
+	    if (isset($this->_receive['ScanCodeInfo'])){
+	        if (!is_array($this->_receive['SendPicsInfo'])) {
+	            $array=(array)$this->_receive['ScanCodeInfo'];
+	            $this->_receive['ScanCodeInfo']=$array;
+	        }else {
+	            $array=$this->_receive['ScanCodeInfo'];
+	        }
+	    }
+	    if (isset($array) && count($array) > 0) {
+	        return $array;
+	    } else {
+	        return false;
+	    }
+	}
+	
+	/**
+	 * 获取自定义菜单的图片发送事件信息
+	 *
+	 * 事件类型为以下三种时则调用此方法有效
+	 * Event	 事件类型,pic_sysphoto        弹出系统拍照发图的事件推送
+	 * Event	 事件类型,pic_photo_or_album  弹出拍照或者相册发图的事件推送
+	 * Event	 事件类型,pic_weixin          弹出微信相册发图器的事件推送
+	 *
+	 * @return: array | false
+	 * array (
+	 *   'Count' => '2',
+	 *   'PicList' =>array (
+	 *         'item' =>array (
+	 *             0 =>array ('PicMd5Sum' => 'aaae42617cf2a14342d96005af53624c'),
+	 *             1 =>array ('PicMd5Sum' => '149bd39e296860a2adc2f1bb81616ff8'),
+	 *         ),
+	 *   ),
+	 * )
+	 *
+	 */
+	public function getRevSendPicsInfo(){
+	    if (isset($this->_receive['SendPicsInfo'])){
+	        if (!is_array($this->_receive['SendPicsInfo'])) {
+	            $array=(array)$this->_receive['SendPicsInfo'];
+	            if (isset($array['PicList'])){
+	                $array['PicList']=(array)$array['PicList'];
+	                $item=$array['PicList']['item'];
+	                $array['PicList']['item']=array();
+	                foreach ( $item as $key => $value ){
+	                    $array['PicList']['item'][$key]=(array)$value;
+	                }
+	            }
+	            $this->_receive['SendPicsInfo']=$array;
+	        } else {
+	            $array=$this->_receive['SendPicsInfo'];
+	        }
+	    }
+	    if (isset($array) && count($array) > 0) {
+	        return $array;
+	    } else {
+	        return false;
+	    }
+	}
+	
+	/**
+	 * 获取自定义菜单的地理位置选择器事件推送
+	 *
+	 * 事件类型为以下时则可以调用此方法有效
+	 * Event	 事件类型,location_select        弹出系统拍照发图的事件推送
+	 *
+	 * @return: array | false
+	 * array (
+	 *   'Location_X' => '33.731655000061',
+	 *   'Location_Y' => '113.29955200008047',
+	 *   'Scale' => '16',
+	 *   'Label' => '某某市某某区某某路',
+	 *   'Poiname' => '',
+	 * )
+	 *
+	 */
+	public function getRevSendGeoInfo(){
+	    if (isset($this->_receive['SendLocationInfo'])){
+	        if (!is_array($this->_receive['SendLocationInfo'])) {
+	            $array=(array)$this->_receive['SendLocationInfo'];
+	            if (empty($array['Poiname'])) {
+	                $array['Poiname']="";
+	            }
+	            if (empty($array['Label'])) {
+	                $array['Label']="";
+	            }
+	            $this->_receive['SendLocationInfo']=$array;
+	        } else {
+	            $array=$this->_receive['SendLocationInfo'];
+	        }
+	    }
+	    if (isset($array) && count($array) > 0) {
+	        return $array;
+	    } else {
+	        return false;
+	    }
+	}
 	
 	/**
 	 * 获取接收语音推送
@@ -751,13 +862,11 @@ class Wechat
      * 	              'type' => 'scancode_waitmsg',
      * 	              'name' => '扫码带提示',
      * 	              'key' => 'rselfmenu_0_0',
-     * 	              'sub_button' => ''
      * 	            ),
      * 	            1 => array (
      * 	              'type' => 'scancode_push',
      * 	              'name' => '扫码推事件',
      * 	              'key' => 'rselfmenu_0_1',
-     * 	              'sub_button' => ''
      * 	            ),
      * 	        ),
      * 	      ),
@@ -768,25 +877,23 @@ class Wechat
      * 	              'type' => 'pic_sysphoto',
      * 	              'name' => '系统拍照发图',
      * 	              'key' => 'rselfmenu_1_0',
-     * 	              'sub_button' => ''
      * 	            ),
      * 	            1 => array (
      * 	              'type' => 'pic_photo_or_album',
      * 	              'name' => '拍照或者相册发图',
      * 	              'key' => 'rselfmenu_1_1',
-     * 	              'sub_button' => ''
      * 	            )
      * 	        ),
      * 	      ),
      * 	      2 => array (
      * 	        'type' => 'location_select',
      * 	        'name' => '发送位置',
-     * 	        'key' => 'rselfmenu_2_0',
-     * 	        'sub_button' => ''
+     * 	        'key' => 'rselfmenu_2_0'
      * 	      ),
      * 	    ),
      * 	)
-     * type可以选择为以下几种,只是目前企业号3-8的类型不能收到事件消息,但可以达到效果。
+     * type可以选择为以下几种,会收到相应类型的事件推送。请注意,3到8的所有事件,仅支持微信iPhone5.4.1以上版本,
+     * 和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。
      * 1、click:点击推事件
      * 2、view:跳转URL
      * 3、scancode_push:扫码推事件
@@ -1476,41 +1583,29 @@ class Wechat
 	 *         ),
 	 * 
 	 *         "news" => array(			//不支持保密
-	 *                 "articles":[
-	 *                 array(
-	 *                         "title" => "Title",
-	 *                         "description" => "Description",
-	 *                         "url" => "URL",
-	 *                         "picurl" => "PIC_URL",
-	 *                 ),
-	 *                 array(
-	 *                         "title" => "Title",
-	 *                         "description" => "Description",
-	 *                         "url" => "URL",
-	 *                         "picurl" => "PIC_URL",
+	 *                 "articles" => array(    //articles  图文消息,一个图文消息支持1到10个图文
+	 *                     array(
+	 *                         "title" => "Title",             //标题
+	 *                         "description" => "Description", //描述
+	 *                         "url" => "URL",                 //点击后跳转的链接。可根据url里面带的code参数校验员工的真实身份。
+	 *                         "picurl" => "PIC_URL",          //图文消息的图片链接,支持JPG、PNG格式,较好的效果为大图640*320,
+	 *                                                         //小图80*80。如不填,在客户端不显示图片
+	 *                     ),
 	 *                 )
-	 *                 ]
 	 *         ),
 	 * 
 	 *         "mpnews" => array(
-	 *                 "articles":[
-	 *                 array(
-	 *                         "thumb_media_id" => "id",
-	 *                         "author" => "Author",
-	 *                         "content_source_url" => "URL",
-	 *                         "content" => "Content"
-	 *                         "digest" => "Digest description",
-	 *                         "show_cover_pic" => "0"
-	 *                 ),
-	 *                 array(
-	 *                         "thumb_media_id" => "id",
-	 *                         "author" => "Author",
-	 *                         "content_source_url" => "URL",
-	 *                         "content" => "Content"
-	 *                         "digest" => "Digest description",
-	 *                         "show_cover_pic" => "0"
+	 *                 "articles" => array(    //articles  图文消息,一个图文消息支持1到10个图文
+	 *                     array(
+	 *                         "title" => "Title",             //图文消息的标题
+	 *                         "thumb_media_id" => "id",       //图文消息缩略图的media_id
+	 *                         "author" => "Author",           //图文消息的作者(可空)
+	 *                         "content_source_url" => "URL",  //图文消息点击“阅读原文”之后的页面链接(可空)
+	 *                         "content" => "Content"          //图文消息的内容,支持html标签
+	 *                         "digest" => "Digest description",   //图文消息的描述
+	 *                         "show_cover_pic" => "0"         //是否显示封面,1为显示,0为不显示(可空)
+	 *                     ),
 	 *                 )
-	 *                 ]
 	 *         )
 	 * )
 	 * 请查看官方开发文档中的 发送消息 -> 消息类型及数据格式

+ 281 - 27
wechat.class.php

@@ -7,6 +7,7 @@
  *  usage:
  *   $options = array(
  *			'token'=>'tokenaccesskey', //填写你设定的key
+ *			'encodingaeskey'=>'encodingaeskey', //填写加密用的EncodingAESKey
  *			'appid'=>'wxdk1234567890', //填写高级调用功能的app id
  *			'appsecret'=>'xxxxxxxxxxxxxxxxxxx', //填写高级调用功能的密钥
  *			'partnerid'=>'88888888', //财付通商户身份标识
@@ -96,6 +97,8 @@ class Wechat
 	const SEMANTIC_API_URL= 'https://api.weixin.qq.com/semantic/semproxy/search?';
 	
 	private $token;
+	private $encodingAesKey;
+	private $encrypt_type;
 	private $appid;
 	private $appsecret;
 	private $access_token;
@@ -103,6 +106,7 @@ class Wechat
 	private $partnerid;
 	private $partnerkey;
 	private $paysignkey;
+	private $postxml;
 	private $_msg;
 	private $_funcflag = false;
 	private $_receive;
@@ -115,6 +119,7 @@ class Wechat
 	public function __construct($options)
 	{
 		$this->token = isset($options['token'])?$options['token']:'';
+		$this->encodingAesKey = isset($options['encodingaeskey'])?$options['encodingaeskey']:'';
 		$this->appid = isset($options['appid'])?$options['appid']:'';
 		$this->appsecret = isset($options['appsecret'])?$options['appsecret']:'';
 		$this->partnerid = isset($options['partnerid'])?$options['partnerid']:'';
@@ -127,14 +132,15 @@ class Wechat
 	/**
 	 * For weixin server validation 
 	 */	
-	private function checkSignature()
+	private function checkSignature($str='')
 	{
         $signature = isset($_GET["signature"])?$_GET["signature"]:'';
+	    $signature = isset($_GET["msg_signature"])?$_GET["msg_signature"]:$signature; //如果存在加密验证则用加密验证段
         $timestamp = isset($_GET["timestamp"])?$_GET["timestamp"]:'';
         $nonce = isset($_GET["nonce"])?$_GET["nonce"]:'';
         		
 		$token = $this->token;
-		$tmpArr = array($token, $timestamp, $nonce);
+		$tmpArr = array($token, $timestamp, $nonce,$str);
 		sort($tmpArr, SORT_STRING);
 		$tmpStr = implode( $tmpArr );
 		$tmpStr = sha1( $tmpStr );
@@ -152,23 +158,46 @@ class Wechat
 	 */
 	public function valid($return=false)
     {
-        $echoStr = isset($_GET["echostr"]) ? $_GET["echostr"]: '';
+        $encryptStr="";
+        if ($_SERVER['REQUEST_METHOD'] == "POST") {
+            $postStr = file_get_contents("php://input");
+            $array = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
+            $this->encrypt_type = isset($_GET["encrypt_type"]) ? $_GET["encrypt_type"]: '';
+            if ($this->encrypt_type == 'aes') { //aes加密
+                $this->log($postStr);
+            	$encryptStr = $array['Encrypt'];
+            	$pc = new Prpcrypt($this->encodingAesKey);
+            	$array = $pc->decrypt($encryptStr,$this->appid);
+            	if (!isset($array[0]) || ($array[0] != 0)) {
+            	    if (!$return) {
+            	        die('解密失败!');
+            	    } else {
+            	        return false;
+            	    }
+            	}
+            	$this->postxml = $array[1];
+            } else {
+                $this->postxml = $postStr;
+            }
+        } else {
+            $echoStr = isset($_GET["echostr"]) ? $_GET["echostr"]: '';
+        }
         if ($return) {
         		if ($echoStr) {
-        			if ($this->checkSignature()) 
+        			if ($this->checkSignature($encryptStr)) 
         				return $echoStr;
         			else
         				return false;
         		} else 
-        			return $this->checkSignature();
+        			return $this->checkSignature($encryptStr);
         } else {
 	        	if ($echoStr) {
-	        		if ($this->checkSignature())
+	        		if ($this->checkSignature($encryptStr))
 	        			die($echoStr);
 	        		else 
 	        			die('no access');
 	        	}  else {
-	        		if ($this->checkSignature())
+	        		if ($this->checkSignature($encryptStr))
 	        			return true;
 	        		else
 	        			die('no access');
@@ -214,7 +243,8 @@ class Wechat
 	public function getRev()
 	{
 		if ($this->_receive) return $this;
-		$postStr = file_get_contents("php://input");
+		$postStr = !empty($this->postxml)?$this->postxml:file_get_contents("php://input");
+		//兼顾使用明文又不想调用valid()方法的情况
 		$this->log($postStr);
 		if (!empty($postStr)) {
 			$this->_receive = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
@@ -405,19 +435,11 @@ class Wechat
 	 * @return: array | false
 	 * array (
 	 *   'Count' => '2',
-	 *   'PicList' => 
-	 *   array (
-	 *     'item' => 
-	 *     array (
-	 *       0 => 
-	 *       array (
-	 *         'PicMd5Sum' => 'aaae42617cf2a14342d96005af53624c',
-	 *       ),
-	 *       1 => 
-	 *       array (
-	 *         'PicMd5Sum' => '149bd39e296860a2adc2f1bb81616ff8',
-	 *       ),
-	 *     ),
+	 *   'PicList' =>array (
+	 *         'item' =>array (
+	 *             0 =>array ('PicMd5Sum' => 'aaae42617cf2a14342d96005af53624c'),
+	 *             1 =>array ('PicMd5Sum' => '149bd39e296860a2adc2f1bb81616ff8'),
+	 *         ),
 	 *   ),
 	 * )
 	 * 
@@ -764,11 +786,44 @@ class Wechat
 			$msg = $this->_msg;
 		$xmldata=  $this->xml_encode($msg);
 		$this->log($xmldata);
+		if ($this->encrypt_type == 'aes') { //如果来源消息为加密方式
+		    $pc = new Prpcrypt($this->encodingAesKey);
+		    $array = $pc->encrypt($xmldata, $this->appid);
+		    $ret = $array[0];
+		    if ($ret != 0) {
+		        $this->log('encrypt err!');
+		        return false;
+		    }
+		    $timestamp = time();
+		    $nonce = rand(77,999)*rand(605,888)*rand(11,99);
+		    $encrypt = $array[1];
+		    $tmpArr = array($this->token, $timestamp, $nonce,$encrypt);//比普通公众平台多了一个加密的密文
+		    sort($tmpArr, SORT_STRING);
+		    $signature = implode($tmpArr);
+		    $signature = sha1($signature);
+		    $xmldata = $this->generate($encrypt, $signature, $timestamp, $nonce);
+		    $this->log($xmldata);
+		}
 		if ($return)
 			return $xmldata;
 		else
 			echo $xmldata;
 	}
+
+    /**
+     * xml格式加密,仅请求为加密方式时再用
+     */
+	private function generate($encrypt, $signature, $timestamp, $nonce)
+	{
+	    //格式化加密信息
+	    $format = "<xml>
+<Encrypt><![CDATA[%s]]></Encrypt>
+<MsgSignature><![CDATA[%s]]></MsgSignature>
+<TimeStamp>%s</TimeStamp>
+<Nonce><![CDATA[%s]]></Nonce>
+</xml>";
+	    return sprintf($format, $encrypt, $signature, $timestamp, $nonce);
+	}
 	
 	/**
 	 * GET 请求
@@ -932,13 +987,11 @@ class Wechat
      * 	              'type' => 'scancode_waitmsg',
      * 	              'name' => '扫码带提示',
      * 	              'key' => 'rselfmenu_0_0',
-     * 	              'sub_button' => ''
      * 	            ),
      * 	            1 => array (
      * 	              'type' => 'scancode_push',
      * 	              'name' => '扫码推事件',
      * 	              'key' => 'rselfmenu_0_1',
-     * 	              'sub_button' => ''
      * 	            ),
      * 	        ),
      * 	      ),
@@ -949,21 +1002,18 @@ class Wechat
      * 	              'type' => 'pic_sysphoto',
      * 	              'name' => '系统拍照发图',
      * 	              'key' => 'rselfmenu_1_0',
-     * 	              'sub_button' => ''
      * 	            ),
      * 	            1 => array (
      * 	              'type' => 'pic_photo_or_album',
      * 	              'name' => '拍照或者相册发图',
      * 	              'key' => 'rselfmenu_1_1',
-     * 	              'sub_button' => ''
      * 	            )
      * 	        ),
      * 	      ),
      * 	      2 => array (
      * 	        'type' => 'location_select',
      * 	        'name' => '发送位置',
-     * 	        'key' => 'rselfmenu_2_0',
-     * 	        'sub_button' => ''
+     * 	        'key' => 'rselfmenu_2_0'
      * 	      ),
      * 	    ),
      * 	)
@@ -1939,3 +1989,207 @@ class Wechat
 	    return false;
 	}
 }
+
+
+
+/**
+ * PKCS7Encoder class
+ *
+ * 提供基于PKCS7算法的加解密接口.
+ */
+class PKCS7Encoder
+{
+    public static $block_size = 32;
+
+    /**
+     * 对需要加密的明文进行填充补位
+     * @param $text 需要进行填充补位操作的明文
+     * @return 补齐明文字符串
+     */
+    function encode($text)
+    {
+        $block_size = PKCS7Encoder::$block_size;
+        $text_length = strlen($text);
+        //计算需要填充的位数
+        $amount_to_pad = PKCS7Encoder::$block_size - ($text_length % PKCS7Encoder::$block_size);
+        if ($amount_to_pad == 0) {
+            $amount_to_pad = PKCS7Encoder::block_size;
+        }
+        //获得补位所用的字符
+        $pad_chr = chr($amount_to_pad);
+        $tmp = "";
+        for ($index = 0; $index < $amount_to_pad; $index++) {
+            $tmp .= $pad_chr;
+        }
+        return $text . $tmp;
+    }
+
+    /**
+     * 对解密后的明文进行补位删除
+     * @param decrypted 解密后的明文
+     * @return 删除填充补位后的明文
+     */
+    function decode($text)
+    {
+
+        $pad = ord(substr($text, -1));
+        if ($pad < 1 || $pad > PKCS7Encoder::$block_size) {
+            $pad = 0;
+        }
+        return substr($text, 0, (strlen($text) - $pad));
+    }
+
+}
+
+/**
+ * Prpcrypt class
+ *
+ * 提供接收和推送给公众平台消息的加解密接口.
+ */
+class Prpcrypt
+{
+    public $key;
+
+    function Prpcrypt($k)
+    {
+        $this->key = base64_decode($k . "=");
+    }
+
+    /**
+     * 对明文进行加密
+     * @param string $text 需要加密的明文
+     * @return string 加密后的密文
+     */
+    public function encrypt($text, $appid)
+    {
+
+        try {
+            //获得16位随机字符串,填充到明文之前
+            $random = "aaaabbbbccccdddd"; //$this->getRandomStr();
+            $text = $random . pack("N", strlen($text)) . $text . $appid;
+            // 网络字节序
+            $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
+            $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
+            $iv = substr($this->key, 0, 16);
+            //使用自定义的填充方式对明文进行补位填充
+            $pkc_encoder = new PKCS7Encoder;
+            $text = $pkc_encoder->encode($text);
+            mcrypt_generic_init($module, $this->key, $iv);
+            //加密
+            $encrypted = mcrypt_generic($module, $text);
+            mcrypt_generic_deinit($module);
+            mcrypt_module_close($module);
+
+            //			print(base64_encode($encrypted));
+            //使用BASE64对加密后的字符串进行编码
+            return array(ErrorCode::$OK, base64_encode($encrypted));
+        } catch (Exception $e) {
+            print $e;
+            return array(ErrorCode::$EncryptAESError, null);
+        }
+    }
+
+    /**
+     * 对密文进行解密
+     * @param string $encrypted 需要解密的密文
+     * @return string 解密得到的明文
+     */
+    public function decrypt($encrypted, $appid)
+    {
+
+        try {
+            //使用BASE64对需要解密的字符串进行解码
+            $ciphertext_dec = base64_decode($encrypted);
+            $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
+            $iv = substr($this->key, 0, 16);
+            mcrypt_generic_init($module, $this->key, $iv);
+            //解密
+            $decrypted = mdecrypt_generic($module, $ciphertext_dec);
+            mcrypt_generic_deinit($module);
+            mcrypt_module_close($module);
+        } catch (Exception $e) {
+            return array(ErrorCode::$DecryptAESError, null);
+        }
+
+
+        try {
+            //去除补位字符
+            $pkc_encoder = new PKCS7Encoder;
+            $result = $pkc_encoder->decode($decrypted);
+            //去除16位随机字符串,网络字节序和AppId
+            if (strlen($result) < 16)
+                return "";
+            $content = substr($result, 16, strlen($result));
+            $len_list = unpack("N", substr($content, 0, 4));
+            $xml_len = $len_list[1];
+            $xml_content = substr($content, 4, $xml_len);
+            $from_appid = substr($content, $xml_len + 4);
+        } catch (Exception $e) {
+            print $e;
+            return array(ErrorCode::$IllegalBuffer, null);
+        }
+        if ($from_appid != $appid)
+            return array(ErrorCode::$ValidateAppidError, null);
+        return array(0, $xml_content);
+
+    }
+
+
+    /**
+     * 随机生成16位字符串
+     * @return string 生成的字符串
+     */
+    function getRandomStr()
+    {
+
+        $str = "";
+        $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
+        $max = strlen($str_pol) - 1;
+        for ($i = 0; $i < 16; $i++) {
+            $str .= $str_pol[mt_rand(0, $max)];
+        }
+        return $str;
+    }
+
+}
+
+/**
+ * error code
+ * 仅用作类内部使用,不用于官方API接口的errCode码
+ */
+class ErrorCode
+{
+    public static $OK = 0;
+    public static $ValidateSignatureError = 40001;
+    public static $ParseXmlError = 40002;
+    public static $ComputeSignatureError = 40003;
+    public static $IllegalAesKey = 40004;
+    public static $ValidateAppidError = 40005;
+    public static $EncryptAESError = 40006;
+    public static $DecryptAESError = 40007;
+    public static $IllegalBuffer = 40008;
+    public static $EncodeBase64Error = 40009;
+    public static $DecodeBase64Error = 40010;
+    public static $GenReturnXmlError = 40011;
+    public static $errCode=array(
+            '0' => '处理成功',
+            '40001' => '校验签名失败',
+            '40002' => '解析xml失败',
+            '40003' => '计算签名失败',
+            '40004' => '不合法的AESKey',
+            '40005' => '校验AppID失败',
+            '40006' => 'AES加密失败',
+            '40007' => 'AES解密失败',
+            '40008' => '公众平台发送的xml不合法',
+            '40009' => 'Base64编码失败',
+            '40010' => 'Base64解码失败',
+            '40011' => '公众帐号生成回包xml失败'
+    );
+    public static function getErrText($err) {
+        if (isset(self::$errCode[$err])) {
+            return self::$errCode[$err];
+        }else {
+            return false;
+        };
+    }
+}

+ 3 - 0
wiki/企业号API类库.md

@@ -48,6 +48,9 @@ $options = array(
 * getRevGeo() 返回地理位置(位置型信息) 返回数组{'x'=>'','y'=>'','scale'=>'','label'=>''}
 * getRevEventGeo() 返回事件地理位置(事件型信息) 返回数组{'x'=>'','y'=>'','precision'=>''}
 * getRevEvent() 返回事件类型(事件型信息) 返回数组{'event'=>'','key'=>''}
+* getRevScanInfo() 获取自定义菜单的扫码推事件信息,事件类型为`scancode_push`或`scancode_waitmsg` 返回数组array ('ScanType'=>'qrcode','ScanResult'=>'123123')
+* getRevSendPicsInfo() 获取自定义菜单的图片发送事件信息,事件类型为`pic_sysphoto`或`pic_photo_or_album`或`pic_weixin` 数组结构见php文件内方法说明
+* getRevSendGeoInfo() 获取自定义菜单的地理位置选择器事件推送,事件类型为`location_select` 数组结构见php文件内方法说明
 * getRevVoice() 返回语音信息(语音型信息) 返回数组{'mediaid'=>'','format'=>''}
 * getRevVoice() 返回语音信息(语音型信息) 返回数组{'mediaid'=>'','format'=>''}
 * getRevVideo() 返回视频信息(视频型信息) 返回数组{'mediaid'=>'','thumbmediaid'=>''}

+ 1 - 0
wiki/官方API类库.md

@@ -27,6 +27,7 @@
 ```php
  $options = array(
 	'token'=>'tokenaccesskey', //填写你设定的key
+	'encodingaeskey'=>'encodingaeskey', //填写加密用的EncodingAESKey
 	'appid'=>'wxdk1234567890', //填写高级调用功能的app id, 请在微信开发模式后台查询
 	'appsecret'=>'xxxxxxxxxxxxxxxxxxx', //填写高级调用功能的密钥
 	'partnerid'=>'88888888', //财付通商户身份标识,支付权限专用,没有可不填