<?php
namespace imcat;
class extWework{
public $reqs = [];
public $wxcpt = null;
public $acfg = [];
# ================================
function __construct($appId){
// params
$reqs['sMsgSign'] = req('msg_signature');
$reqs['sTimeStamp'] = req('timestamp');
$reqs['sNonce'] = req('nonce');
$this->reqs = $reqs;
// cfgs
$this->wecfg = read('wework', 'ex');
if(empty($this->wecfg['isOpen'])){
die('请配置:[ex_wework.php]:isOpen=1');
}else{
include_once(DIR_WEKIT."/sv-api/callback/WXBizMsgCrypt.php");
$this->wxcpt = new \WXBizMsgCrypt($this->acfg['Token'], $this->acfg['EncodingAESKey'], $this->wecfg['CorpId']);
}
if(!isset($this->wecfg['AppsConfig'][$appId])){
die('Error AppID!');
}else{
$this->acfg = $this->wecfg['AppsConfig'][$appId];
}
}
# Edu:家校沟通-Start -------
static function getEdutPid($ures){
if(!empty($ures['parent_userid'])){
return $ures['parent_userid'];
}
if(!empty($ures['UserId'])){
$uin = self::getUser($ures['UserId']);
if(isset($uin['mobile'])){
//return $uin['mobile'];
$rdb = db()->table('exd_edu')->field('pid')->where("mob='$uin[mobile]'")->find();
if(!empty($rdb['pid'])){ return $rdb['pid']; }
}
}
return '';
}
// 更新班级:学生-家长:对应关系;cid=班级id
static function updEduClass($cid='0'){
$cdata = is_int($cid) ? self::getEduTabs($cid) : $cid;
if(is_array($cdata)){
foreach($cdata as $cr){
$sid = $cr['student_userid'];
foreach($cr['parents'] as $pr){
$pid = $pr['parent_userid'];
$eid = empty($pr['external_userid']) ? '' : $pr['external_userid'];
$rr = ['sid'=>$sid, 'pid'=>$pid, 'mob'=>$pr['mobile'], 'eid'=>$eid];
db()->table('exd_edu')->data($rr)->replace(0);
//echo "$sid - $pid - $mob - $eid<br>\n";
}
} }
}
// 从缓存获取:学校:部门/用户列表数据
static function getEduTabs($act='deps', $upd=0){ // deps,utab
$key = $act=='deps' ? 'departments' : 'students';
$fp = "/dtmp/wework/edu_$act.cac_tab";
if(!file_exists(DIR_VARS.$fp) || $upd){
self::updEduTab($act);
}
$data = comFiles::get(DIR_VARS.$fp);
return json_decode($data,1);
}
// 更新:学校:部门/用户列表数据 > 保存到缓存
static function updEduTab($act='deps'){ // deps,utab
$key = $act=='deps' ? 'departments' : 'students';
$fp = "/dtmp/wework/edu_$act.cac_tab";
$res = ['errno'=>'','errmsg'=>'更新成功'];
$wecfg = read('wework', 'ex');
$jiaSecret = $wecfg['JiaSecret'];
if(empty($wecfg['isOpen'])){
return ['errno'=>'!isOpen','errmsg'=>'请配置:[ex_wework.php]:isOpen=1'];
}
// getData
$api = new \CorpAPI($wecfg['CorpId'], $jiaSecret);
if($act=='deps'){
$data = $api->EduDepartLists();
$res = self::fmtDepts($data);
}else{ // if($act=='utab')
$res = $api->EduDepartUsers($act); //dump($res);
}
// save
if(!empty($res[$key])){
$data = comParse::jsonEncode($res[$key]);
comFiles::put(DIR_VARS.$fp,$data); //dump($data);
unset($res[$key]);
}
return $res;
}
static function fmtDepts($data){
$res = $old = []; $t34 = $t2 = $t1 = [];
foreach($data['departments'] as $row){
$old[$row['id']] = $row['name'];
if($row['type']==4 || $row['type']==3){ // $row['type']==4 || // 3表示学段,4表示校区
//$row['name'] = $old[$row['parentid']].$row['name'];
$t34[] = $row;
continue;
}
if($row['type']==2){ // 2表示年级
if(!empty($row['is_graduated'])){ continue; } // 已毕业
$t2[] = $row;
continue;
}
if($row['type']==1){ $t1[] = $row; } // 1表示班级
}
// 按入学年龄倒序排列
$ordcol = array_column($t2, 'register_year');
array_multisort($ordcol, SORT_DESC, $t2);
//dump($t34); //dump($t2); dump($t1);
$deps = array_merge($t34, $t2, $t1);
return ['departments'=>$deps];
}
// 从缓存获取:家校:用户信息
static function getEduUser($uid, $upd=0){ // deps,utab
$fp = "/dtmp/wework/eu_$uid.cac_tab";
if(!file_exists(DIR_VARS.$fp) || $upd){
$res = ['errno'=>'','errmsg'=>'Get.成功'];
$wecfg = read('wework', 'ex');
$jiaSecret = $wecfg['JiaSecret'];
if(empty($wecfg['isOpen'])){
return ['errno'=>'!isOpen','errmsg'=>'请配置:[ex_wework.php]:isOpen=1'];
}
// getData
$api = new \CorpAPI($wecfg['CorpId'], $jiaSecret);
$res = $api->EdutUserInfo($uid);
//
$type = empty($res['user_type']) ? 0 : $res['user_type'];
$tabs = ['','student','parent']; $key = $tabs[$type];
if(!empty($res[$key])){
$tmp = ['type'=>$key] + $res[$key];
$data = comParse::jsonEncode($tmp);
comFiles::put(DIR_VARS.$fp,$data); //dump($data);
unset($res['user_type']);
}
}
$data = comFiles::get(DIR_VARS.$fp);
return json_decode($data,1);
}
// 从缓存获取:外部:用户信息
static function getExtUser($uid, $upd=0){ // deps,utab
$fp = "/dtmp/wework/out_$uid.cac_tab";
if(!file_exists(DIR_VARS.$fp) || $upd){
$res = ['errno'=>'','errmsg'=>'Get.成功'];
$wecfg = read('wework', 'ex');
$jiaSecret = $wecfg['JiaSecret'];
if(empty($wecfg['isOpen'])){
return ['errno'=>'!isOpen','errmsg'=>'请配置:[ex_wework.php]:isOpen=1'];
}
// getData
$api = new \CorpAPI($wecfg['CorpId'], $jiaSecret);
$res = $api->EXTUserInfo($uid);
if(!empty($res['external_contact'])){
$data = comParse::jsonEncode($res['external_contact']);
comFiles::put(DIR_VARS.$fp,$data); //dump($data);
}
}
$data = comFiles::get(DIR_VARS.$fp);
return json_decode($data,1);
}
# Edu:家校沟通-End -------
function getMsg($encMesg){
$res = ['errCode'=>0, 'strMsg'=>''];
$reqs = $this->reqs;
//$encMesg = file_get_contents("php://input")
// "<xml><ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt>encStr</Encrypt><AgentID><![CDATA[218]]></AgentID></xml>";
$strMsg = ""; // 解析之后的明文
$errCode = $this->wxcpt->DecryptMsg($reqs['sMsgSign'], $reqs['sTimeStamp'], $reqs['sNonce'], $encMesg, $strMsg);
if ($errCode == 0) { // 解密成功,sMsg即为xml格式的明文
basDebug::bugLogs("getMsg-check", $strMsg, "msgOK.log", 'db');
$res = ['errCode'=>0, 'strMsg'=>$strMsg]; // var_dump($strMsg); // TODO: 对明文的处理
} else {
basDebug::bugLogs("getMsg-check", $encMesg, "errMsg.log", 'db');
//print("ERROR: " . $errCode . "\n\n");
$res = ['errCode'=>$errCode, 'strMsg'=>$strMsg];
}
return $res;
}
function chkUrl($sVerifyEchoStr){
$reqs = $this->reqs;
$sEchoStr = "";
//dump([$this->acfg['Token'], $this->acfg['EncodingAESKey'], $receiveid]);
$errCode = $this->wxcpt->VerifyURL($reqs['sMsgSign'], $reqs['sTimeStamp'], $reqs['sNonce'], $sVerifyEchoStr, $sEchoStr);
$data = [$errCode, $sVerifyEchoStr, $sEchoStr];
if(empty($errCode)){ // ==0
basDebug::bugLogs("chkUrl-check", $data, "chkUrl-urlOK.log", 'db');
echo $sEchoStr;
} else {
basDebug::bugLogs("chkUrl-check", $data, "chkUrl-errUrl.log", 'db');
print("ERROR: " . $errCode . "\n\n");
}
}
# ================================
static function smsgExtNews($agentId, $arts=[], $to=[]){
$wecfg = read('wework', 'ex');
//
$to['pids'] = empty($to['pids']) ? [] : array_unique(array_filter($to['pids']));
$to['sids'] = empty($to['sids']) ? [] : array_unique(array_filter($to['sids']));
$to['part'] = empty($to['part']) ? [] : array_unique(array_filter($to['part']));
if(empty($to['pids']) && empty($to['sids']) && empty($to['part'])){
basDebug::bugLogs("smsgExtNews", $to, "smsgExtNews-null.log", 'file');
return ['errcode'=>'82001', 'errmsg'=>"指定的发送对象"];
}
$api = new \CorpAPI($wecfg['CorpId'], $agentId); // $agentId,$wecfg['JiaSecret']
//
$data = [
'to_external_user' => [],
'to_parent_userid' => $to['pids'],
'to_student_userid' => $to['sids'],
'to_party' => $to['part'],
'toall' => 0,
'msgtype' => 'news',
'agentid' => $wecfg['AppsConfig'][$agentId]['AgentId'],
'news' => ['articles' => $arts],
'enable_id_trans' => 0,
'enable_duplicate_check' => 0,
'duplicate_check_interval' => 1800
];
try {
$errPlist = $errSlist = $errDlist = [];
$api->ExtMessageSend($data, $errPlist, $errSlist, $errDlist);
if(!empty($errPlist) || !empty($errSlist) || !empty($errDlist)){
$dlog = [$errPlist, $errSlist, $errDlist];
basDebug::bugLogs("smsgExtNews", $dlog, "smsgExtNews-nul2.log", 'file');
}
return [$errPlist, $errSlist, $errDlist];
} catch (Exception $ex) {
return ['errcode'=>'smsgExtNews', 'errmsg'=>$ex->getMessage()];
}
}
//static function smsgNewsArticle($agentId){}
static function smsgCard($agentId, $msg=[], $to=[]){
$wecfg = read('wework', 'ex');
$to['uids'] = array_unique(array_filter($to['uids']));
if(empty($to['uids']) && empty($to['party']) && empty($to['tag'])){
basDebug::bugLogs("msg-send", $to, "msg-null.log", 'file');
return ['errcode'=>'82001', 'errmsg'=>"指定的成员/部门/标签全部为空"];
}
$api = new \CorpAPI($wecfg['CorpId'], $wecfg['AppsConfig'][$agentId]['Secret']);
try {
$message = new \Message();
{
$message->sendToAll = false;
if(!empty($to['uids'])) $message->touser = $to['uids']; // array("PeaceXie", "ShengbenZhu");
if(!empty($to['party'])) $message->toparty = $to['party']; // array(1, 2, 1111, 3333);
if(!empty($to['tag'])) $message->totag = $to['tag']; // array(3, 4, 22233332, 33334444);
$message->agentid = $wecfg['AppsConfig'][$agentId]['AgentId'];
$message->safe = 0;
$message->messageContent = new \TextCardMessageContent(
$msg['title'], //"售后单[$row[did]]{$acmsg}", // 请(加急)处理,提到了您,完成了...
$msg['des'], //"<div class=\"gray\">".date('Y-m-d H:i:s')."</div> <div class=\"normal\">$row[title]</div><div class=\"highlight\">单号 $row[did] $mfmsg</div>",
$msg['url'], //$url,
$msg['btntxt'] //"查看详情"
);
}
$invalidUserIdList = null;
$invalidPartyIdList = null;
$invalidTagIdList = null;
$api->MessageSend($message, $invalidUserIdList, $invalidPartyIdList, $invalidTagIdList);
//var_dump($agentId, $invalidUserIdList, $invalidPartyIdList, $invalidTagIdList);
$dlog = [$message, $invalidUserIdList, $invalidPartyIdList, $invalidTagIdList];
basDebug::bugLogs("msg-send", $dlog, "msg-end.log", 'file');
return [$invalidUserIdList, $invalidPartyIdList, $invalidTagIdList];
} catch (Exception $ex) {
//var_dump($invalidPartyIdList);
return ['errcode'=>'errMsgSend', 'errmsg'=>$ex->getMessage()];
}
}
# ================================
// 精简用户数据
static function userMin(&$uinfo, $cut=1){
$skip = ['extattr','order','external_profile','is_leader_in_dept','errcode','errmsg'];
$keep = ['userid','name','mobile','email','avatar'];
if($cut){
foreach ($skip as $key) {
unset($uinfo[$key]);
}
}else{
foreach ($uinfo as $key=>$val) {
if(!in_array($key,$keep)){
unset($uinfo[$key]);
}
}
}
}
// 从缓存获取:单个用户数据
static function getUser($UserId='', $agentId=''){ // deps,utab,uone
$wecfgs = read('wework', 'ex'); // DefAppID
$agentId = $agentId ?: $wecfgs['DefAppID'];
$fp = "/dtmp/wework/$UserId.cac_tab";
if(!$UserId){
$data = $wecfgs['utab']['(null)'];
}elseif(isset($wecfgs['utab'][$UserId])){
$data = $wecfgs['utab'][$UserId];
}else{
if(!file_exists(DIR_VARS.$fp)){
$utmp = self::updUser($UserId, $agentId);
if(!empty($utmp['errcode'])){
return $utmp;
}
}
$data = comFiles::get(DIR_VARS.$fp);
}
$uinfo = json_decode($data,1);
if(!empty($uinfo)){ // 默认头像,调试权限
if(empty($uinfo['avatar'])){ $uinfo['avatar']=PATH_STATIC.'/icons/basic/nouser2.png'; }
$wecfgs = read('wework', 'ex');
//$uinfo['pdebug'] = $uinfo['userid'] && strstr($wecfgs['ucfg']['debug'],$uinfo['userid']);
}
return $uinfo;
}
// 更新:单个用户数据 > 保存到缓存
static function updUser($UserId='', $agentId=''){ // deps,utab,uone
$wecfg = read('wework', 'ex');
$CorpId = $wecfg['CorpId']; //$agentId = 'AppCS';
if(empty($wecfg['isOpen'])){
return ['errno'=>'!isOpen','errmsg'=>'请配置:[ex_wework.php]:isOpen=1'];
}
$agentId = $agentId ?: $wecfg['DefAppID'];
$fp = "/dtmp/wework/$UserId.cac_tab";
$api = new \CorpAPI($CorpId, $agentId);
$uinfo = $api->GetUserById($UserId);
// save
if(!empty($uinfo['errcode'])){
$uinfo = ['uname'=>$UserId, 'mname'=>"($UserId)", 'mpic'=>''] + $uinfo;
}elseif(!empty($uinfo['userid'])){
extWework::userMin($uinfo);
$data = comParse::jsonEncode($uinfo);
comFiles::put(DIR_VARS.$fp,$data);
//unset($res[$key]);
}
return $uinfo;
}
// 从缓存获取:部门/用户列表数据
static function getContacts($act='deps', $secret='AppAB'){ // deps,utab
$key = $act=='deps' ? 'department' : 'userlist';
$fp = "/dtmp/wework/_$key.cac_tab";
if(!file_exists(DIR_VARS.$fp)){
self::updContacts($act, $secret);
}
$data = comFiles::get(DIR_VARS.$fp);
return empty($data) ? [] : json_decode($data,1);
}
static function getDpuids($dpid=1){
$utab = self::getContacts('utab');
$res = [];
foreach($utab as $uk => $ur) {
if(in_array($dpid,$ur['department'])){
$res[$uk] = $ur['name'];
}
}
return $res;
}
// 更新:部门/用户列表数据 > 保存到缓存
static function updContacts($act='deps', $secret='AppAB'){ // deps,utab
$key = $act=='deps' ? 'department' : 'userlist';
$fp = "/dtmp/wework/_$key.cac_tab";
$res = ['errno'=>'','errmsg'=>'更新成功'];
$wecfg = read('wework', 'ex');
if(empty($wecfg['isOpen'])){
return ['errno'=>'!isOpen','errmsg'=>'请配置:[ex_wework.php]:isOpen=1'];
}
// getData
$api = new \CorpAPI($wecfg['CorpId'], ($secret?$secret:$wecfg['TxlSecret']));
if($act=='deps'){
$res = $api->DepartmentList(null, 1);
}elseif($act=='utab'){
$res = $api->userSimpleList(1, 1, 1);
$tmp = [];
if(!empty($res[$key])){
foreach ($res[$key] as $no=>$row) {
$tmp[$row['userid']] = $row;
}
}
$res[$key] = $tmp;
}
// save
if(!empty($res[$key])){
$data = comParse::jsonEncode($res[$key]);
comFiles::put(DIR_VARS.$fp,$data); //dump($data);
unset($res[$key]);
}
return $res;
}
// 判断是否企业微信
static function isWework($ver=0){
//return 1;
$wxpos = strpos(basEnv::userAG(), 'wxwork/');
if($ver){
preg_match('/.*?(wxwork\/([0-9.]+))\s*/', $uagent, $matches);
return $wxpos ? $matches[2] : '';
}else{
return $wxpos;
}
}
// 判断...是否iOS-act
static function fixAct(){
return 'onclick';
//$isMob = basEnv::isMobile();
$isiOS = strpos($_SERVER['HTTP_USER_AGENT'],'iPhone') || strpos($_SERVER['HTTP_USER_AGENT'],'iPad');
$act = $isiOS ? 'ontouchstart' : 'onclick';
return $act;
}
// if(strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone')||strpos($_SERVER['HTTP_USER_AGENT'], 'iPad')){
static function mapLink($pos=''){
$tmp = explode(',',$pos);
$title = '打卡位置';
$url = "https://map.qq.com/?type=marker&isopeninfowin=1&markertype=1&pointx={$tmp[0]}&pointy={$tmp[1]}&name=$title&zoomLevel=16";
return $url;
}
static function oauth2Link($redirect, $scope='', $state='imcat_wxwork_login'){
//$reuri = $iss.urlencode($iss.surl('user-login','',1));
$redirect = str_replace(array("?&","&","#"),array("?","%26","%23"),$redirect);
$scope || $scope = 'snsapi_base';
$CorpId = read('wework.CorpId', 'ex');
$urlBase = '';
$url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=$CorpId&redirect_uri=$redirect&response_type=code&scope=$scope&state=$state#wechat_redirect";
return $url;
}
static function wecfgs(){
$wecfgs = read('wework', 'ex');
$tab1 = ['CorpId','TxlSecret','CHECKIN_APP_SECRET','APPROVAL_APP_SECRET'];
$tab2 = ['Secret','Token','EncodingAESKey'];
foreach($tab1 as $dek){ unset($wecfgs[$dek]); }
foreach($wecfgs['AppsConfig'] as $ak=>&$av){
foreach($tab2 as $dek){ unset($wecfgs['AppsConfig'][$ak][$dek]); }
}
return $wecfgs;
}
// 按pid格式化
static function lv1Dept($data, &$res, $pid=0, $deep=0){
#$cr = $data[1];
#$res[1] = ['name'=>$cr['name'], 'deep'=>0, 'pid'=>0];
foreach($data as $ck=>$cr){
if($cr['parentid']==$pid){
$res[$ck] = ['name'=>$cr['name'], 'deep'=>$deep, 'pid'=>$pid];
self::lv1Dept($data, $res, $ck, $deep+1);
}
}
}
// 部门结构格式化:健值是班级id
static function fmtDept($class=''){
$data = $class ? self::getEduTabs('deps') : self::getContacts('deps');
$res = [];
foreach($data as $ck=>$cr){ // .':'.$cr['id']
$res[$cr['id']] = $cr; //['name'];
}
return $res;
}
// xxx
static function fmtMychs($deps, $userp){
if(empty($userp['children'])){ return []; }
$tcs = $userp['children'];
$chs = [];
foreach($tcs as $tk=>$tr){
$chs[$tk]['sid'] = $sid = $tr['student_userid']; $ti = extWework::getEduUser($sid);
$chs[$tk]['sid'] = $sid;
$chs[$tk]['rel'] = $tr['relation'];
$chs[$tk]['name'] = $ti['name'];
$chs[$tk]['cid'] = $cid = $ti['department'][0];
$chs[$tk]['pid'] = $pid = $deps[$cid]['parentid'];
$chs[$tk]['cname'] = $deps[$pid]['name'].$deps[$cid]['name'];
$aid = '';
foreach($deps[$cid]['department_admins'] as $ar){
if($ar['type']==3){
$chs[$tk]['aid'] = $aid = $ar['userid'];
$au = extWework::getUser($aid); //dump($au);
$chs[$tk]['aname'] = $au['name'];
break;
}
}
}
return $chs;
}
static function fmtMyprs($ustu){
if(is_string($ustu)){
$ustu = extWework::getEduUser($ustu);
}
$prtab = $ustu['parents'];
$prs = [];
foreach($prtab as $tk=>$tr){
$prs[$tk]['pid'] = $tr['parent_userid'];
$prs[$tk]['rel'] = $tr['relation'];
$prs[$tk]['mtel'] = $tr['mobile'];
$prs[$tk]['isub'] = $tr['is_subscribe'];
}
return $prs;
}
//parents
}
/*
PC-Chrome:
- "(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"
PC-Qiye
- "(Windows NT 6.2; WOW64) Chrome/53.0.2785.116 Safari/537.36 wxwork/3.0.16 (MicroMessenger/6.2) WindowsWechat"
PC-Wechat
- "(Windows NT 10.0; WOW64) Chrome/53.0.2875.116 Safari/537.36 NetType/WIFI MicroMessenger/7.0.5 WindowsWechat"
Mob-Wechat
- "(Linux; Android 7.0; SLA-TL10 Build/HUAWEISLA-TL10; wv) MMWEBID/4318 MicroMessenger/7.0.12.1620(0x27000C34) Process/tools NetType/WIFI Language/zh_CN ABI/arm32"
Mob-Qiye
- "(Linux; Android 7.0; SLA-TL10 Build/HUAWEISLA-TL10; wv) wxwork/3.0.14 MicroMessenger/7.0.1 NetType/WIFI Language/zh"
*/