코드이그나이터 기반 PHP 오픈소스 게시판 : 씨아이보드

smslib 장문 문자 발송 수정

  • 하마쿵
  • 0
  • 3,814
  • 글주소
  • 04-11

현재 사용되는 Smslib.php  (아이코드) 모듈은 단문(80byte)만 가능합니다.


그래서 장문 발송가능한 아이코드 모듈을 토대로 수정했습니다.( sms_module4_php_v3.1.2_b25 )


기존 smslib 클래스에 멤버함수를 추가하고 수정하는 형태로 변경했습니다.

따라서 구버젼 함수, 신규 함수등이 모두 포함되어 있습니다. 


(아이코드의 구 모듈에 신규모듈을 사용하는 방법을 상세히? 알려주는 pdf 파일을 참고.)


크게 바뀐부분

1. 아이코드 사이트에서 신규 토큰을 발급받아야 합니다.

2. 기존에 사용하던 id/pw 는 관리자와 연동되므로 계속 사용.

3. $this->Add() 멤버함수가 신규 함수로 교체.

4. 기타 .. (기억이 가믈가믈)

5. 관리자 sms 문자발송하기 테스트 할때

 (views\admin\basic\sms\smssend\index.php) 자바스크립트 80byte 제약 해제필요.

  -> byte_check() 함수 line 481 쯤에 return; if (cnt > 80) {..}  
     if문 앞에 return; 추가로 스킵하거나 다른게 변경하시면 됩니다.


(아래 코드는 신규 모듈 교체가 아닌  구버전을 하용해 장문발송이 가능하도록 수정한 소스입니다. )

필요하신분은 수정해서 사용하시면 됩니다.



<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * Smslib class
 *
 * Copyright (c) CIBoard <www.ciboard.co.kr>
 *
 * @author CIBoard (develop@ciboard.co.kr)
 */

/**
 * 문자 관련 class 입니다.
 * 그누보드 (http://www.sir.co.kr) 의 소스 참조
 *
 * 아이코드 소켓 규격 v3.2.14 b45 (2018.1.31) 으로 대체 적용 - 하마쿵.2019.03.08.
 * php용 sms_module4_php_v3.1.2_b25
 */
class Smslib extends CI_Controller
{
   
    private $CI;
    var $Log = array();
    var $SMS_Server;
    var $SMS_Port;
    var $SMS_Key;
   
    function __construct()
    {
        $this->CI = & get_instance();
        $this->CI->load->helper( array('array'));
    }
   
   
    public function Init()
    {
        $this->Data = '';
        $this->Result = '';
    }
   
   
    public function get_icode_info($id, $pw)
    {
        $result = get_sock('http://www.icodekorea.com/res/userinfo.php?userid=' . $id . '&userpw=' . $pw);
        $res = explode(';', $result);
        $userinfo = array(
            'code' => $res[0], // 결과코드
            'coin' => $res[1], // 고객 잔액 (충전제만 해당)
            'gpay' => $res[2], // 고객의 건수 별 차감액 표시 (충전제만 해당)
            'payment' => $res[3] // 요금제 표시, A:충전제, C:정액제
        );
       
        return $userinfo;
    }
   
   
    /**
     * SMS 문자발송
     * @param string $receiver - 수신번호
     * @param string $sender - 발신번호
     * @param string $content - 내용
     * @param string $date - 예약일시
     * @param string $memo - 제목
     * @return string
     */
    public function send($receiver = '', $sender = '', $content = '', $date = '', $memo = '')
    {
        $this->SMS_con();
        $date2 = $date ? str_replace(array(' ', '-', ':'), '', $date) : '';
        if (element('phone', $receiver)) {
            $list[0] = $receiver;
        } else {
            $list = $receiver;
        }
        if ($list) {
            foreach ($list as $key => $val) {
                $list[$key]['phone'] = str_replace('-', '', element('phone', $val));
            }
            if (empty($list[$key]['phone'])) unset($list[$key]);
        }
        $send_phone = (element('phone', $sender)) ? element('phone', $sender) : '0000';
        $send_mem_id = (element('mem_id', $sender)) ? element('mem_id', $sender) : '0';
        $total = count($list);
        if (empty($total)) {
            $return = array(
                'result' => 'fail',
                'message' => '발송할 대상이 없습니다.'
            );
            return json_encode($return, JSON_UNESCAPED_UNICODE);
        }
        //$result = $this->Add($list, $send_phone, '', '', $content, $date2, $total);
        $result = $this->Add($list, $send_phone, $content, $memo, $date2, $total);
       
        if ($result) {
            $result = $this->SendSMS();
           
            $this->CI->load->model(array('Sms_send_content_model', 'Sms_send_history_model'));
           
            if ($result) { //SMS 서버에 접속했습니다.
                $contentdata = array(
                    'ssc_content' => $content,
                    'send_mem_id' => $send_mem_id,
                    'ssc_send_phone' => $send_phone,
                    'ssc_total' => $total,
                    'ssc_datetime' => cdate('Y-m-d H:i:s'),
                    'ssc_memo' => $memo,
                   
                );
                if ($date) {
                    $contentdata['ssc_booking'] = cdate('Y-m-d H:i:s', strtotime($date));
                }
                $ssc_id = $this->CI->Sms_send_content_model->insert($contentdata);
               
                $success = 0;
                $fail = 0;
                $count = 0;
               
                $ssh_memo = '';
               
                if ($this->Result) {
                    foreach ($this->Result as $result) {
                        $ssh_memo = '';
                        list($phone, $code) = explode(':', $result);
                       
                        if (substr($code,0,5) === 'Error') {
                            $hs_code = substr($code,6,2);
                           
                            switch ($hs_code) {
                                case '02': // '02:형식오류'
                                    $ssh_memo = '형식이 잘못되어 전송이 실패하였습니다.';
                                    break;
                                case '23': // '23:인증실패,데이터오류,전송날짜오류'
                                    $ssh_memo = '데이터를 다시 확인해 주시기바랍니다.';
                                    break;
                                   
                                    // 아래의 사유들은 발송진행이 중단됨.
                                case '85':   // "85:발송번호 미등록"
                                    echo "등록되지 않는 발송번호 입니다.<br />";
                                    break;
                                case '87':   // "87:인증실패"
                                    echo "(정액제-계약확인)인증 받지 못하였습니다.<br />";
                                    break;
                                case '88':   // "88:연동모듈 발송불가"
                                    echo "연동모듈 사용이 불가능합니다. 아이코드로 문의하세요.<br />";
                                    break;
                                   
                                case '96':   // "96:토큰 검사 실패"
                                    echo "사용할 수 없는 토큰키입니다.<br />";
                                    break;
                                case '97': // '97:잔여코인부족'
                                    $ssh_memo = '잔여코인이 부족합니다.';
                                    break;
                                case '98': // '98:사용기간만료'
                                    $ssh_memo = '사용기간이 만료되었습니다.';
                                    break;
                                case '99': // '99:인증실패'
                                    $ssh_memo = '인증 받지 못하였습니다. 계정을 다시 확인해 주세요.';
                                    break;
                                default: // '미 확인 오류'
                                    $ssh_memo = $hs_code.'알 수 없는 오류로 전송이 실패하였습니다.';
                                    break;
                            }
                            $fail++;
                            $ssh_success = 0;
                        } else {
                            $hs_code = $code;
                            switch (substr($code,0,2)) {
                                case '17':   // "17: 접수(발송)대기 처리. 지연해소시 발송됨."
                                    //echo "접수(발송)대기처리 되었습니다.";
                                    $ssh_memo = $phone . '접수(발송)대기처리 되었습니다.';
                                    break;
                                default:   // "00: 전송완료."
                                    $ssh_memo = $phone . '로 전송했습니다.';
                                    break;
                            }
                           
                            $success++;
                            $ssh_success = 1;
                        }
                       
                        $row = array_shift($list);
                        $row['phone'] = $row['phone'];
                       
                        $log = array_shift($this->Log);
                        //$log = @iconv('UTF-8', 'UTF-8//IGNORE', $log);  //주석처리.19.03.28.
                       
                        $insertdata = array(
                            'ssc_id' => $ssc_id,
                            'send_mem_id' => $send_mem_id,
                            'recv_mem_id' => empty(element('mem_id', $row)) ? 0: element('mem_id', $row),
                            'ssh_name' => element('name', $row),
                            'ssh_phone' => element('phone', $row),
                            'ssh_success' => $ssh_success,
                            'ssh_datetime' => cdate('Y-m-d H:i:s'),
                            'ssh_memo' => $ssh_memo,
                            'ssh_log' => $log,
                        );
                        $this->CI->Sms_send_history_model->insert($insertdata);
                    }
                }
                $this->Init(); // 보관하고 있던 결과값을 지웁니다.
               
                $updatedata = array(
                    'ssc_success' => $success,
                    'ssc_fail' => $fail,
                );
                $this->CI->Sms_send_content_model->update($ssc_id, $updatedata);
                $return = array(
                    'result' => 'success',
                    'message' => '발송이 완료되었습니다.'
                );
               
                return json_encode($return, JSON_UNESCAPED_UNICODE);
               
            } else {
                $return = array(
                    'result' => 'fail',
                    'message' => '에러: SMS 서버와 통신이 불안정합니다.'
                );
               
                return json_encode($return, JSON_UNESCAPED_UNICODE);
               
            }
        } else {
            $return = array(
                'result' => 'fail',
                'message' => '에러: SMS 데이터 입력도중 에러가 발생하였습니다.'
            );
            return json_encode($return, JSON_UNESCAPED_UNICODE);
        }
    }
   
    // SMS 서버 접속
    public function SMS_con()
    {
        $this->SMS_Server = '211.172.232.124';
        $this->SMS_Port = $this->CI->cbconfig->item('sms_icode_port'); //ciboard에 설정되있음. (port: 9201)
        $this->SMS_Key = $this->CI->cbconfig->item('sms_icode_key');  //관리자 sms 설정에 연동시킴. (발급받은 키값을 직접 넣어도됩니다.)
    }
   
   
    /**
     * 발송번호의 값이 정확한 값인지 확인합니다.
     *
     * @param strDest 발송번호 배열입니다.
     *    nCount 배열의 크기입니다.
     * @return 처리결과입니다.
     */
    public function CheckCommonTypeDest($strDest, $nCount)
    {
        for ($i = 0; $i < $nCount; $i++) {
            $hp_number = preg_replace("/[^0-9]/", '', $strDest[$i]['phone']);
           
            if (strlen($hp_number) < 10 OR strlen($hp_number) > 11) {
                return '휴대폰 번호가 틀렸습니다';
            }
           
            $CID = substr($hp_number,0,3);
           
            if ( preg_match("/[^0-9]/", $CID) OR ($CID !== '010' && $CID !== '011' && $CID !== '016' && $CID !== '017' && $CID !== '018' && $CID !== '019')) {
                return '휴대폰 앞자리 번호가 잘못되었습니다';
            }
        }
    }
   
   
    /**
     * 회신번호의 값이 정확한 값인지 확인합니다.
     */
    public function CheckCommonTypeCallBack($strCallBack)
    {
        if (preg_match("/[^0-9]/", $strCallBack)) {
            return '회신 전화번호가 잘못되었습니다';
        }
    }
   
   
    /**
     * 예약날짜의 값이 정확한 값인지 확인합니다.
     */
    public function CheckCommonTypeDate($strDate)
    {
        $strDate = preg_replace("/[^0-9]/", '', $strDate);
       
        if ($strDate) {
            if (checkdate(substr($strDate,4,2), substr($strDate,6,2), substr($strDate,0,4)) === false) {
                return '예약날짜가 잘못되었습니다';
            }
            if (substr($strDate,8,2) >23 OR substr($strDate,10,2) >59) {
                return '예약시간이 잘못되었습니다';
            }
        }
    }
   
   
    /**
     * URL콜백용으로 메세지 크기를 수정합니다.
     *
     * @param url  URL 내용입니다.
     *   msg  결과메시지입니다.
     *   desk 문자내용입니다.
     */
    public function CheckCallCenter($url, $dest, $data)
    {
        switch (substr($dest,0,3)) {
            case '010': //20바이트
                return $this->cut_char($data,20);
                break;
            case '011': //80바이트
                return $this->cut_char($data,80);
                break;
            case '016': // 80바이트
                return $this->cut_char($data,80);
                break;
            case '017': // URL 포함 80바이트
                return $this->cut_char($data,80 - strlen($url));
                break;
            case '018': // 20바이트
                return $this->cut_char($data,20);
                break;
            case '019': // 20바이트
                return $this->cut_char($data,20);
                break;
            default:
                return $this->cut_char($data,80);
                break;
        }
    }
   
    /**
     * 발송 패킷 생성
     * Add(수신번호목록(배열), 발신번호, 발송내용(2000자이내), 제목(옵션, 30자이내), 예약일자(옵션, 12자리)
     */
    public function Add($strDest, $strCallBack, $strMessage, $strSubject='', $strDate='', $nCount)
    {
        $strCallBack = str_replace('-', '', $strCallBack);
        if(empty($strSubject)) $strSubject = $this->CI->cbconfig->item('site_title');
       
        $Error = $this->CheckCommonTypeDest($strDest, $nCount);   // 번호 검사
        $Error = $this->CheckCommonTypeCallBack($strCallBack);
        $Error = $this->CheckCommonTypeDate($strDate);
       
        // 개행치환
        $strData = preg_replace_callback("/\r\n/","fn_sms_chcr",$strMessage);
        $strData = preg_replace_callback("/\r/","fn_sms_chcr",$strData);
       
        // 문자 타입별 Port 설정.
        $sendType = strlen($strData)>90 ? 1 : 0; // 0: SMS / 1: LMS
        if($sendType==0) $strSubject = "";
        $strCallBack = CutChar($strCallBack, 11);       // 회신번호
       
        /** LMS 제목 **/
        /*
         제목필드의 값이 없을 경우 단말기 기종및 설정에 따라 표기 방법이 다름
         1.설정에서 제목필드보기 설정 Disable -> 제목필드값을 넣어도 미표기
         2.설정에서 제목필드보기 설정 Enable  -> 제목을 넣지 않을 경우 제목없음으로 자동표시
        
         제목의 첫글자에 "<",">", 개행문자가 있을경우 단말기종류 및 통신사에 따라 메세지 전송실패 -> 글자를 체크하거나 취환처리요망
         $strSubject = str_replace("\r\n", " ", $strSubject);
         $strSubject = str_replace("<", "[", $strSubject);
         $strSubject = str_replace(">", "]", $strSubject);
         */
       
        $strSubject = CutCharUtf8($strSubject,30);
        $strData    = CutCharUtf8($strData,2000);
       
        /* 필수 항목에 대해 정상적인 코드인지 검사 과정.
         개발 방식에 따라 활용*/
        //$Error = CheckCommonTypeDest($strDest); // 번호 검사
        //$Error = IsVaildCallback($strCallBack);
        //$Error = CheckCommonTypeDate($strDate);
       
       
        for ($i = 0; $i < $nCount; $i++) {
            $hp_number = $this->spacing($strDest[$i]['phone'],11);
           
            if ( ! empty($strDest[$i]['name'])) {
                $strData = str_replace('{이름}', $strDest[$i]['name'], $strData);
            }
           
            $list = array(
                "key" => $this->SMS_Key,
                "tel" => $hp_number,
                "cb" => $strCallBack,
                "msg" => $strData,
                "charset" => 'utf-8'
            );
            if(!empty($strSubject)) $list['title'] = $strSubject;
            if(!empty($strDate)) $list['date'] = $strDate;
            $packet = json_encode($list, JSON_UNESCAPED_UNICODE);
           
            $this->Data[$i] = '06'.str_pad(strlen($packet), 4, "0", STR_PAD_LEFT).$packet;
        }
        return true; // 수정대기
    }
   
   
    public function SendSMS()
    {
        $count = 1;
       
        $is_test = false;
       
        if ($is_test) {
            if ($this->Data) {
                foreach ($this->Data as $puts) {
                    if (rand(0,10)) {
                        $phone = substr($puts,26,11);
                        $code = '47022497 ';
                    } else {
                        $phone = substr($puts,26,11);
                        $code = 'Error(02)';
                    }
                    $this->Result[] = "$phone:$code";
                    $this->Log[] = $puts;
                }
            }
            $this->Data = '';
           
            return true;
        }
       
       
        $fsocket = fsockopen($this->SMS_Server, $this->SMS_Port, $errno, $errstr, 2);
        if (empty($fsocket)) {
            return false;
        }
        set_time_limit(300);
       
        foreach ($this->Data as $puts) {
           
            fputs($fsocket, $puts);
            while(!$gets) { $gets = fgets($fsocket,32); }
           
            $chk = preg_match("/\"tel\":\"([0-9]*)\"/", substr($puts,6), $matches);
            $desc = $matches[1];
            $resultCode = substr($gets,6,2);
            if ($resultCode == '00' || $resultCode == '17') { // 17은 접수(발송)대기.
                $this->Result[] = $resultCode.":".substr($gets,8,12).":".substr($gets,20,11);
                $this->Log[] = '성공'.$puts;
            } else {
                $this->Result[] = $desc.":Error(".substr($gets,6,2).")";
                $this->Log[] = '실패'.$puts;
                if(substr($gets,6,2) >= "80") break;
            }
            $gets = "";
           
            // 1천건씩 전송 후 5초 쉼
            if ($count++%1000 === 0) {
                sleep(5);
            }
        }
        fclose($fsocket);
        $this->Data = '';
        return true;
    }
   
   
    public function spacing($text, $size)
    {
        for ($i = 0; $i < $size; $i++) {
            $text .= ' ';
        }
        $text = substr($text,0, $size);
        return $text;
    }
   
   
    public function cut_char($word, $cut)
    {
        $word = substr($word,0, $cut);      // 필요한 길이만큼 취함.
        for ($k = $cut-1; $k > 1; $k--) {
            if (ord(substr($word, $k,1)) < 128) {
                break;  // 한글값은 160 이상.
            }
        }
        $word = substr($word,0, $cut-($cut-$k+1)%2);
        return $word;
    }
   
   
    public function CheckCommonType($dest, $rsvTime)
    {
        $dest = preg_replace("/[^0-9]/i", '', $dest);
        if (strlen($dest) < 10 OR strlen($dest) >11) {
            return '휴대폰 번호가 틀렸습니다';
        }
        $CID = substr($dest,0,3);
        if ( preg_match("/[^0-9]/i", $CID) OR ($CID !== '010' && $CID !== '011' && $CID !== '016' && $CID !== '017' && $CID !== '018' && $CID !== '019')) {
            return '휴대폰 앞자리 번호가 잘못되었습니다';
        }
        $rsvTime = preg_replace("/[^0-9]/i", '', $rsvTime);
        if ($rsvTime) {
            if (checkdate(substr($rsvTime,4,2), substr($rsvTime,6,2), substr($rsvTime,0,4)) === false) {
                return '예약날짜가 잘못되었습니다';
            }
            if (substr($rsvTime,8,2) >23 OR substr($rsvTime,10,2) >59) {
                return '예약시간이 잘못되었습니다';
            }
        }
    }
}

// 개행치환
function fn_sms_chcr() { return "\n"; }

/**
 * 원하는 문자열의 길이를 원하는 길이만큼 공백을 넣어 맞추도록 합니다.
 *
 * @param   text  원하는 문자열입니다.
 *          size  원하는 길이입니다.
 * @return        변경된 문자열을 넘깁니다.
 */
function FillSpace($text,$size) {
    for ($i=0; $i<$size; $i++) $text.= " ";
    $text = substr($text,0,$size);
    return $text;
}

/**
 * 원하는 문자열을 원하는 길에 맞는지 확인해서 조정하는 기능을 합니다.
 *
 * @param   word  원하는 문자열입니다.
 *          cut   원하는 길이입니다.
 * @return        변경된 문자열입니다.
 */
function CutChar($word, $cut) {
    $word=substr($word,0,$cut); // 필요한 길이만큼 취함.
    for ($k = $cut-1; $k > 1; $k--) {
        if (ord(substr($word,$k,1))<128) break; // 한글값은 160 이상.
    }
    $word = substr($word, 0, $cut-($cut-$k+1)%2);
    return $word;
}

function CutCharUtf8($word, $cut) {
    preg_match_all('/[\xE0-\xFF][\x80-\xFF]{2}|./', $word, $match); // target for BMP
   
    $m = $match[0];
    $slen = strlen($word); // length of source string
    if ($slen <= $cut) return $word;
   
    $ret = array();
    $count = 0;
    for ($i=0; $i < $cut; $i++) {
        $count += (strlen($m[$i]) > 1)?2:1;
        if ($count > $cut) break;
        $ret[] = $m[$i];
    }
   
    return join('', $ret);
}


/**
 * 잘못된 수신번호 목록을 리턴합니다.
 *
 * @param   strTelList  발송번호 배열.
 * @return              잘못된 수신번호 목록.
 */
function CheckCommonTypeDest($strTelList) {
    $result = '';
    foreach ($strTelList as $tel) {
        $tel = preg_replace("/[^0-9]/","",$tel);
        if(!preg_match("/^(0[173][0136789])([0-9]{3,4})([0-9]{4})$/", $tel)) $result .= $tel.',';
    }
    return $result;
}


/**
 * 회신번호 유효성 여부조회
 * 한국인터넷진흥원 권고사항
 *
 * @param  string callback  회신번호
 * @return                  처리결과입니다
 */
function IsVaildCallback($callback){
    $_callback = preg_replace('/[^0-9]/', '', $callback);
    if (!preg_match("/^(02|0[3-6]\d|01(0|1|3|5|6|7|8|9)|070|080|007)\-?\d{3,4}\-?\d{4,5}$/", $_callback) &&
        !preg_match("/^(15|16|18)\d{2}\-?\d{4,5}$/", $_callback)) return "회신번호오류";
        if (preg_match("/^(02|0[3-6]\d|01(0|1|3|5|6|7|8|9)|070|080)\-?0{3,4}\-?\d{4}$/", $_callback)) return "회신번호오류";
        return '';
}

/**
 * 문자열을 JSON 사용가능 타입으로 변환한다.
 */
function EscapeJsonString($value) {
    $escapers =     array('\\',  '"');
    $replacements = array('\\\\', '\"');
    $result = str_replace($escapers, $replacements, $value);
    return $result;
}

/**
 * 예약날짜의 값이 정확한 값인지 확인합니다.
 *
 * @param   string strDate  예약시간
 * @return                  처리결과입니다
 */
function CheckCommonTypeDate($strDate) {
    $strDate = preg_replace("/[^0-9]/", "", $strDate);
    if ($strDate){
        if(strlen($strDate) != 12) return '예약날짜오류';
        if (!checkdate(substr($strDate,4,2),substr($strDate,6,2),substr($rsvTime,0,4))) return "예약날짜오류";
        if (substr($strDate,8,2)>23 || substr($strDate,10,2)>59) return "예약시간오류";
    }
    return '';
}