728x90

법정공휴일은 '관공서의 공휴일에 관한 규정'(대통령령)에 의해 공휴일이 된 날을 말한다.
법령에 따르면 법정공휴일은 일요일, 국경일, 1월 1일, 음력 1월 1일(설날)과 전후 이틀, 석가탄신일(음력 4월 8일),
어린이날(5월 5일), 현충일(6월 6일), 음력 8월 15일(추석)과 전후 이틀, 성탄절(12월 25일),
보궐선거를 제외한 각종 선거투표일 등 정부에서 수시로 정하는 날 등이다.
국경일은 ‘국경일에 관한 법률’에 의한 3ㆍ1절, 제헌절, 광복절, 개천절, 한글날을 말한다.

대체공휴일제의 도입 (안 제3조)
– 설날, 추석 연휴가 다른 공휴일과 겹치는 경우 그 날 다음의 첫 번째 비공휴일을 공휴일로 함
– 어린이날이 토요일 또는 다른 공휴일과 겹치는 경우 그 날 다음의 첫 번째 비공휴일을 공휴일로 함
(어린이날 외의 토요일은 대체공휴일에 포함되지 않는다)


음력기준으로 법정공휴일인 설날, 추석과 어린이날은 대체 공휴일을 고려하여 코딩해야 한다.



블로그 이미지

Link2Me

,
728x90

PHP로 만들어진 달력을 활용해보고자 많은 자료를 검색해봤지만 결국에는 기본적인 개념이 충실한 PHP 달력 만들기 소스코드를 좀 더 정교하게 가다듬어 정리를 하는 것이 답인 거 같다.


달력은 일정관리, 음력일정, 음력 기념일 등을 관리할 수 있어야 하므로 외국 소스를 활용하는 것은 큰 도움이 안되더라.


bootstrap 기반으로 포팅하고 약간 수정해서 정리한 걸 적어둔다.


양력기반으로 일정추가가 잘된 소스는 https://phppot.com/php/php-calendar-event-management-using-fullcalendar-javascript-library/ 를 참조하면 도움이 될 것이다.

그리고 https://www.phpzag.com/create-event-calendar-with-jquery-php-and-mysql/ 자료도 도움이 된다.



음력 기념일, 일정 등이 포함된 버전으로 만들기 위해서는 좀 더 정리가 필요하다.

음력 일정까지 정리되는 달력은 다른 소스를 가지고 수정 작업을 하고 있는 중이다.


<?php
//---- 오늘 날짜
$thisyear = date('Y'); // 4자리 연도
$thismonth = date('n'); // 0을 포함하지 않는 월
$today = date('j'); // 0을 포함하지 않는 일

//------ $year, $month 값이 없으면 현재 날짜
$year = isset($_GET['year']) ? $_GET['year'] : $thisyear;
$month = isset($_GET['month']) ? $_GET['month'] : $thismonth;
$day = isset($_GET['day']) ? $_GET['day'] : $today;

$prev_month = $month - 1;
$next_month = $month + 1;
$prev_year = $next_year = $year;
if ($month == 1) {
    $prev_month = 12;
    $prev_year = $year - 1;
} else if ($month == 12) {
    $next_month = 1;
    $next_year = $year + 1;
}
$preyear = $year - 1;
$nextyear = $year + 1;

$predate = date("Y-m-d", mktime(0, 0, 0, $month - 1, 1, $year));
$nextdate = date("Y-m-d", mktime(0, 0, 0, $month + 1, 1, $year));

// 1. 총일수 구하기
$max_day = date('t', mktime(0, 0, 0, $month, 1, $year)); // 해당월의 마지막 날짜
//echo '총요일수'.$max_day.'<br />';

// 2. 시작요일 구하기
$start_week = date("w", mktime(0, 0, 0, $month, 1, $year)); // 일요일 0, 토요일 6

// 3. 총 몇 주인지 구하기
$total_week = ceil(($max_day + $start_week) / 7);

// 4. 마지막 요일 구하기
$last_week = date('w', mktime(0, 0, 0, $month, $max_day, $year));
?>

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Bootstrap Example</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <style>
    font.holy {font-family: tahoma;font-size: 20px;color: #FF6C21;}
    font.blue {font-family: tahoma;font-size: 20px;color: #0000FF;}
    font.black {font-family: tahoma;font-size: 20px;color: #000000;}
  </style>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script>
  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<table class="table table-bordered table-responsive">
  <tr align="center" >
    <td>
        <a href=<?php echo 'index.php?year='.$preyear.'&month='.$month . '&day=1'; ?>>◀◀</a>
    </td>
    <td>
        <a href=<?php echo 'index.php?year='.$prev_year.'&month='.$prev_month . '&day=1'; ?>>◀</a>
    </td>
    <td height="50" bgcolor="#FFFFFF" colspan="3">
        <a href=<?php echo 'index.php?year=' . $thisyear . '&month=' . $thismonth . '&day=1'; ?>>
        <?php echo "&nbsp;&nbsp;" . $year . '년 ' . $month . '월 ' . "&nbsp;&nbsp;"; ?></a>
    </td>
    <td>
        <a href=<?php echo 'index.php?year='.$next_year.'&month='.$next_month.'&day=1'; ?>>▶</a>
    </td>
    <td>
        <a href=<?php echo 'index.php?year='.$nextyear.'&month='.$month.'&day=1'; ?>>▶▶</a>
    </td>
  </tr>
  <tr class="info">
    <th hight="30">일</td>
    <th>월</th>
    <th>화</th>
    <th>수</th>
    <th>목</th>
    <th>금</th>
    <th>토</th>
  </tr>

  <?php
    // 5. 화면에 표시할 화면의 초기값을 1로 설정
    $day=1;

    // 6. 총 주 수에 맞춰서 세로줄 만들기
    for($i=1; $i <= $total_week; $i++){?>
  <tr>
    <?php
    // 7. 총 가로칸 만들기
    for ($j = 0; $j < 7; $j++) {
        // 8. 첫번째 주이고 시작요일보다 $j가 작거나 마지막주이고 $j가 마지막 요일보다 크면 표시하지 않음
        echo '<td height="50" valign="top">';
        if (!(($i == 1 && $j < $start_week) || ($i == $total_week && $j > $last_week))) {

            if ($j == 0) {
                // 9. $j가 0이면 일요일이므로 빨간색
                $style = "holy";
            } else if ($j == 6) {
                // 10. $j가 0이면 토요일이므로 파란색
                $style = "blue";
            } else {
                // 11. 그외는 평일이므로 검정색
                $style = "black";
            }

            // 12. 오늘 날짜면 굵은 글씨
            if ($year == $thisyear && $month == $thismonth && $day == date("j")) {
                // 13. 날짜 출력
                echo '<font class='.$style.'>';
                echo $day;
                echo '</font>';
            } else {
                echo '<font class='.$style.'>';
                echo $day;
                echo '</font>';
            }
            // 14. 날짜 증가
            $day++;
        }
        echo '</td>';
    }
 ?>
  </tr>
  <?php } ?>
</table>
</div>

</body>
</html>


음력을 지원하는 달력의 모습


음력 달력에 대한 소스 코드 설명은 http://link2me.tistory.com/1545 를 참조하면 된다.

'Web 프로그램 > PHP 응용 및 활용' 카테고리의 다른 글

PHP 달력 소스 코드 (음력, 간지, 기념일, 일정등록)  (9) 2018.11.05
법정공휴일  (0) 2018.11.03
날짜 선택  (0) 2018.10.26
접속단말 및 IP주소  (0) 2018.06.11
PHP 글자수 줄임 표시  (0) 2018.06.09
블로그 이미지

Link2Me

,
728x90

PHP에서 날짜 선택을 깔끔하게 하는 코드를 적어둔다.


<?php
//---- 오늘 날짜
$thisyear = date("Y");
$thismonth = date("m");
$today = date("d"); // 1, 2, 3, ..., 31

//------ $year, $month 값이 없으면 현재 날짜
$year = isset($_GET['year']) ? $_GET['year'] : $thisyear;
$month = isset($_GET['month']) ? $_GET['month'] : $thismonth;
$day = isset($_GET['day']) ? $_GET['day'] : $today;

$start_date = date("Y", mktime(0, 0, 0, date("m"), date("d"), date("Y") - 1));
$end_date = date("Y", mktime(0, 0, 0, date("m"), date("d"), date("Y") + 3));
?>


<TR>
    <TD >시작날짜</TD>
    <TD colspan="2" rowspan="1">
    <select name="start_date1">
    <?php for($i=$start_date;$i<$end_date;$i++):?><option value="<?php echo $i?>"<?php if($year==$i):?> selected="selected"<?php endif?>><?php echo $i?>년</option><?php endfor?>
    </select>
    <select name="start_date2" >
    <?php for($i=1;$i<13;$i++):?><option value="<?php echo sprintf('%02d',$i)?>"<?php if($month==$i):?> selected="selected"<?php endif?>><?php echo sprintf('%02d',$i)?>월</option><?php endfor?>
    </select>
    <select name="start_date3" >
    <?php for($i=1;$i<32;$i++):?><option value="<?php echo sprintf('%02d',$i)?>"<?php if($day==$i):?> selected="selected"<?php endif?>><?php echo sprintf('%02d',$i)?>일</option><?php endfor?>
    </select>

    </TD>
</TR>

'Web 프로그램 > PHP 응용 및 활용' 카테고리의 다른 글

법정공휴일  (0) 2018.11.03
PHP 달력 만들기 소스  (0) 2018.10.30
접속단말 및 IP주소  (0) 2018.06.11
PHP 글자수 줄임 표시  (0) 2018.06.09
PHP to JSP  (0) 2018.04.28
블로그 이미지

Link2Me

,
728x90

PHP 서버와 안드로이드 클라이언트간에 연동 테스트를 하는데 결과가 표시되지 않는다.

서버를 직접 실행해서 메시지를 확인해보니...

Warning: mysql_fetch_array() expects parameter 1 to be resource, boolean given

와 같은 메시지가 나온다.


이 메시지 나오는 원인은 SQL 문의 칼럼이 잘못된 곳이 있을 경우에 나온다.

echo $sql.'<br />'; 로 결과를 화면에 찍어보고 잘못 오타가 난 칼럼 또는 칼럼에 전혀 없는 명칭을 사용하고 있지는 않는지 확인해보는 것이 좋다.

블로그 이미지

Link2Me

,
728x90

https://gist.github.com/joashp/a1ae9cb30fa533f4ad94 를 참조하여 AES 암호화, 복호화 알고리즘을 테스트 했다.


PHP에서 문자열을 암호화할 때 보다 더 강력하게 암호화할 수 있는 salt(솔트), hash 256를 사용한 AES 암호화, 복호화 알고리즘이다.

가장 중요한 것은 secret_key 를 관리하는 방법이다.

web 접속 경로상에 없도록 하거나, 다른 서버에 관리하는 것이 좋다.

아래 예제에서는 $salt 를 이용하여 생성한 값을 가지고 임의로 $secret_key 를 만들었다.


윈도우 기반 autoset9 에서는 openssl_encrypt 에러가 발생하여 리눅스 기반에서 테스트를 했다.


사용법 예제

<?php
include_once 'dbController.php';
require_once 'loginClass.php';
$c = new LoginClass();

$salt = sha1(mt_rand());
echo $salt.'<br />';
$secret_key = "f9a90bfa9e8ddd9965fecc0052e6786cf59f3131";
echo $secret_key.'<br />';
$secret_iv = substr($salt, 0, 20);
echo $secret_iv.'<br />';

$str = '안녕하세요? 홍길동입니다.';
$encrypted = $c->AES_encrypt($str);
echo $encrypted.'<br />';
echo 'encrypted message length : '.strlen($encrypted).'<br />';

$decrypted = $c->AES_decrypt($encrypted);
echo $decrypted.'<br />';
?>



Class

<?php
class DBController {
    private $host = 'localhost';
    private $database = 'test';
    private $userid = 'root';
    private $password = 'autoset';
    protected $db; // 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.

    public function __construct() {
        $this->db = $this->connectDB();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }

    function __destruct(){
        mysqli_close($this->connectDB());
        //mysqli_close($this->db);
    }

    private function connectDB() {
        $dbconn = mysqli_connect($this->host, $this->userid, $this->password, $this->database);
        mysqli_set_charset($dbconn, "utf8"); // DB설정이 잘못되어 euc-kr 로 되어 있으면 문제가 됨
        if (mysqli_connect_errno()) {
           printf("Connect failed: %s\n", mysqli_connect_error());
           exit();
        } else {
          return $dbconn;
        }
    }
}//end dbClass

?>

<?php
class LoginClass extends DBController {
    // class 자식클래스 extends 부모클래스
    // override : 부모 클래스와 자식 클래스가 같은 메소드를 정의했을 경우 자식 클래스가 우선시된다.
    // AES 암호화
    function AES_encrypt($plain_text){
        global $secret_key, $secret_iv;
        $encrypt_method = "AES-256-CBC";
        $key = hash('sha256', $secret_key);
        $iv = substr(hash('sha256', $secret_iv), 0, 16);
        $output = openssl_encrypt($plain_text, $encrypt_method, $key, true, $iv);
        return base64_encode($output);
    }

    // AES 복호화
    function AES_decrypt($encrypted_string){
        global $secret_key, $secret_iv;
        $encrypt_method = "AES-256-CBC";
        $key = hash('sha256', $secret_key);
        $iv = substr(hash('sha256', $secret_iv), 0, 16);
        $output = openssl_decrypt(base64_decode($encrypted_string), $encrypt_method, $key, true, $iv);
        return $output;
    }

}//end class LoginClass
?>


https://gist.github.com/kijin/8404004 를 참조하여 AES 암호화, 복호화를 해도 좋을 거 같다.


'Web 프로그램 > 암호화 처리' 카테고리의 다른 글

phpMyAdmin 해킹시도 흔적  (0) 2018.06.11
[PHP] SHA384 패스워드 만들기  (0) 2017.01.27
[보안] PHP AES 암호화 예제  (0) 2016.10.16
[보안] SEED PHP 사용법  (0) 2016.10.15
블로그 이미지

Link2Me

,
728x90

<?php
class DBController {
    private $host = 'localhost';
    private $database = 'test';
    private $userid = 'root';
    private $password = 'autoset';
    protected $db; // 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.

    public function __construct() {
        $this->db = $this->connectDB();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }

    function __destruct(){
        mysqli_close($this->connectDB());
        //mysqli_close($this->db);
    }

    private function connectDB() {
        $dbconn = mysqli_connect($this->host, $this->userid, $this->password, $this->database);
        mysqli_set_charset($dbconn, "utf8"); // DB설정이 잘못되어 euc-kr 로 되어 있으면 문제가 됨
        if (mysqli_connect_errno()) {
           printf("Connect failed: %s\n", mysqli_connect_error());
           exit();
        } else {
          return $dbconn;
        }
    }

}//end dbClass
?>

<?php
class LoginClass extends DBController {
// class 자식클래스 extends 부모클래스
    // 회원 가입 여부 체크
    public function isUserExisted($u) {
        if(!isset($u) || empty($u)) {
            return '-1';
        } else {
            $sql ="SELECT count(userID) from members WHERE userID='".$u."'";
            $result = mysqli_query($this->db, $sql);
            if($row = mysqli_fetch_array($result)){
                return $row[0]; // 미가입이면 0 반환, 가입이면 1 반환
            } else {
                return -1;
            }
        }
    }
}//end class LoginClass
?>


PHP 함수를 만들어서 사용하는 경우 개수를 체크하기 위해 count(*)를 사용한다.
이 경우 반환값은 정수일까? String(문자열)일까?

아래 코드를 직접 실행해보면 결과가 나온다.
변수 타입 확인은 getttype 함수 또는 var_dump 함수로 확인할 수 있다.
<?php
$userID ='jsk005@naver.com';
include_once 'dbController.php';
require_once 'loginClass.php';
$c = new LoginClass();
$rs = $c->isUserExisted($userID);

if($rs === 1){
    echo 'Variable Type is Integer<br />';
} else if($rs == '1'){
    echo '
Variable Type is string <br />';
    echo gettype($rs);
    //var_dump($rs);
}
?>

결과는 정수 타입이 아니라 String 이다.


블로그 이미지

Link2Me

,
728x90

MySQLi 객체지향 접속방식으로 코드를 작성한 회원가입과 로그인 예제를 만들었다.

모두 꼼꼼하게 테스트하여 잘 동작함을 확인했다.


안전한 비밀번호 작성 규칙

- 영문자, 숫자, 특수문자 중 2가지 종류를 조합할 때에는 10자리 이상 / 3가지 종류를 조합할 때에는 8자리 이상으로 설정

- 비밀번호는 최소 6개월에 한번씩 변경

- 누구나 쉽게 알 수 있는 비밀번호 사용은 자제


<?php
// DB 정보 입력
define("DB_HOST", "localhost");
define("DB_DATABASE", "test");
define("DB_USER", "root");
define("DB_PASSWORD", "autoset");
?>

<?php
class DBController {
    protected $db; // 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.

    // 생성자
    function __construct() {
        $this->db = $this->connectDB();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }

    // 소멸자(destructor)
    function __destruct() {
        mysqli_close($this->connectDB());
    }

    private function connectDB() {
        require_once 'config.php';
        // MySQLi 객체지향 DB 연결
        $conn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE);
        return $conn; // return database handler
    }

}
?>

<?php
class LoginClass extends DBController {
    // class 자식클래스 extends 부모클래스
    // override : 부모 클래스와 자식 클래스가 같은 메소드를 정의했을 경우 자식 클래스가 우선시된다.

    public function hashSSHA($password) {

        $salt = sha1(rand());
        $salt = substr($salt, 0, 10);
        $encrypted = base64_encode(sha1($password . $salt, true) . $salt);
        $hash = array("salt" => $salt, "encrypted" => $encrypted);
        return $hash;
    }

    public function checkhashSSHA($salt, $password) {
        $hash = base64_encode(sha1($password . $salt, true) . $salt);
        return $hash;
    }

    // 회원 정보 신규 입력
    public function storeUser($userID, $userNM, $password, $mobileNO) {
        $hash = $this->hashSSHA($password);
        $encrypted_password = $hash['encrypted']; // encrypted password
        $salt = $hash['salt']; // salt
        $regdate = date("YmdHis");

        $stmt = $this->db->prepare("INSERT INTO members(userID, userNM, passwd, salt, regdate) VALUES(?, ?, ?, ?, ?)");
        $stmt->bind_param("sssss", $userID, $userNM, $encrypted_password, $salt,$regdate);
        $result = $stmt->execute();
        $stmt->close();

        // check for successful store
        if ($result) {
            $user = $this->getUser($userID, $password);
            //echo '<pre>';print_r($user);echo '</pre>';
            $rs = $this->storeUserDetail($user['uid'],$userNM,$userID,$mobileNO);
            if($rs == 1){
                return $user;
            } else {
                return -1;
            }
        } else {
            return -1; // 0
        }
    }

    // 회원 세부 정보 입력
    public function storeUserDetail($relateduid,$userNM,$email,$mobileNO){
        $mobileNO = preg_replace("/[^0-9]/", "", $mobileNO); //전화번호 숫자만 남기고 제거
        $sql ="INSERT INTO member_data(relateduid,userNM,email,mobileNO) VALUES(?, ?, ?, ?)";
        $stmt = $this->db->prepare($sql);
        $stmt->bind_param("isss", $relateduid, $userNM, $email, $mobileNO);
        $result = $stmt->execute();
        $stmt->close();
        if($result){
            return 1;
        } else {
            return 0;
        }
    }


    // 로그인 체크
    public function getUser($userID, $password) {
        $stmt = $this->db->prepare("SELECT * FROM members WHERE userID = ?");
        $stmt->bind_param("s", $userID);

        if ($stmt->execute()) {
            $user = $stmt->get_result()->fetch_assoc();
            $stmt->close();

            // verifying user password
            $salt = $user['salt'];
            $encrypted_password = $user['passwd'];
            $hash = $this->checkhashSSHA($salt, $password);
            // check for password equality
            if ($encrypted_password == $hash) {
                // user authentication details are correct
                return $user;
            }
        } else {
            return NULL;
        }
    }

    // 안드로이드/아이폰 로그인 체크
    public function LoginUserChk($userID,$password,$deviceID){
        if(empty($userID) || empty($password)){
            return 0;
        } else {
            $user = $this->getUser($userID, $password);
            if($user['uid']>0){ // 가입자 정보가 있다면
                // 장치 일련번호 체크
                if($user['phoneSE'] == NULL){
                    // 신규 장치번호 입력(최초 로그인)
                    $this->LoginUserEquipInput($userID,$deviceID);
                    return 1;
                } else {
                    if($user['phoneSE'] === $deviceID){
                        return 1; // 일련번호 일치
                    } else {
                        return -1; //일련번호 불일치
                    }
                }
            } else {
                return 0; // 로그인 실패
            }
        }

    }

    // 장치번호 업데이트
    public function LoginUserEquipInput($userID,$deviceID){
        if(strlen($deviceID)>0 && is_numeric($deviceID)){ // 안드로이드폰
            $ostype = 2;
        } else if(strlen($deviceID)>30){ // 아이폰
            $ostype = 1;
        } else { // 기타
            $ostype = 0;
        }

        $sql='update members set phoneSE=?, OStype=? where userID=?';
        $stmt = $this->db->prepare($sql);
        $stmt->bind_param("sss", $deviceID, $ostype, $userID);
        $status = $stmt->execute();
        if($status == true){
            return 1;
        } else {
            return 0;
        }
    }//end

    // 장치번호 초기화 (관리자용)
    public function EquipReset($userID){
        $ostype = 0;
        $sql='update members set phoneSE=NULL, OStype=? where userID=?';
        $stmt = $this->db->prepare($sql);
        $stmt->bind_param("ss", $ostype, $userID);
        $status = $stmt->execute();
        if($status == true){
            return 1;
        } else {
            return 0;
        }
    }//end

    // 회원 가입 여부 체크
    public function isUserExisted($userID) {
        $stmt = $this->db->prepare("SELECT userID from members WHERE userID = ?");

        $stmt->bind_param("s", $userID);
        $stmt->execute();
        //$stmt->store_result();
        $result = $stmt->get_result();

        if ($result->num_rows > 0) {
            // user existed
            $stmt->free_result();
            $stmt->close();
            return true;
        } else {
            // user not existed
            $stmt->free_result();
            $stmt->close();
            return false;
        }
    }

    // 회원 정보 삭제
    public function deleteUser($userID){
        $stmt = $this->db->prepare("delete FROM members WHERE userID = ?");
        $stmt->bind_param("s", $userID);
        $stmt->execute();
        $stmt->close();
    }

    // 인증이 성공한 상태에서 직접 접속을 시도하는 외부 접속으로부터 보호
    function checkReferer($url) {
        $referer = $_SERVER['HTTP_REFERER'];
        $res = strpos($referer,$url);
        return $res == 0 ? false : true;
    }
}
?>

 <?php
if (!isset($_SESSION)) {
    session_start();
}

// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    //echo '<pre>';print_r($_POST);echo '</pre>'; // 전송받은 배열을 확인하고 싶을 때 사용
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        include_once 'config/dbconnect.php';
        require_once 'config/loginClass.php';
        $c = new LoginClass();
        if ($c->isUserExisted($userID) === true) {
            echo '{"result":"0"}'; // echo json_encode(array('result' => '0')); 과 동일
        } else {
            $mobileNO = preg_replace("/[^0-9]/", "", $mobileNO); //전화번호 숫자만 남기고 제거
            // 회원 등록
            $user = $c->storeUser($userID, $userNM, $password, $mobileNO);
            if($user) {// 회원 등록 성공
                $_SESSION['userID'] = $user['userID'];
                $_SESSION['userNM'] = $user['userNM'];
                $_SESSION['admin'] = $user['admin'];
                echo json_encode(array('result' => '1'));
            } else {
                // 회원 등록 실패
                echo json_encode(array('result' => '-1'));
            }
        }
    }else {// 입력받은 데이터에 문제가 있을 경우
        echo json_encode(array('result' => '-2'));
    }
}
?>

 <?php
// 파일을 직접 실행하는 비정상적 동작을 방지 목적
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        include_once 'config/dbconnect.php';
        require_once 'config/loginClass.php';
        $c = new LoginClass();
        $user = $c->getUser($userID, $password);
        //echo '<pre>'; print_r($user);echo '</pre>';
        if ($user != false) {
            $_SESSION['userID'] = $user['userID'];
            $_SESSION['userNM'] = $user['userNM'];
            $_SESSION['admin'] = $user['admin'];
            echo json_encode(array('result' => '1'));
        } else {
            echo json_encode(array('result' => '0'));
        }
    }else {// 입력받은 데이터에 문제가 있을 경우
        echo json_encode(array('result' => '-2'));
    }
} else { // 비정상적인 접속인 경우
    echo 0; // a.loginChk.php 파일을 직접 실행할 경우에는 화면에 0을 찍어준다.
    exit;
}
?>


직접 실행해 볼 수 있는 테이블 구조 및 관련 파일을 모두 첨부한다.

안드로이드에서 로그인하는 코드 샘플도 포함되어 있다.


mysqlI_login.zip



블로그 이미지

Link2Me

,
728x90

폴더안에 있는 파일들의 파일명을 배열(array)로 저장하고 출력해보자.

basename($_FILES['b_file']['name']); // basename() 메소드는 경로를 제외하고 파일명만 추출

pathinfo($filename, PATHINFO_FILENAME); //확장자를 제외한 파일명
pathinfo($filename, PATHINFO_EXTENSION); // 파일 확장자 구하기


<?php
$dir ='./photos'; // 폴더명 지정

$fileList = getCurrentFileList($dir);
echo count($fileList).'개 검색됨<br />';
foreach($fileList as $f){
    echo $f.'<br />';
}

function getCurrentFileList($dir){
    $valid_formats = array("jpg","png");
    $handle = opendir($dir); // 디렉토리의 핸들을 얻어옴
    // 지정한 디렉토리에 있는 디렉토리와 파일들의 이름을 배열로 읽어들임
    $R = array(); // 결과 담을 변수 생성
    while ($filename = readdir($handle)) {
        if($filename == '.' || $filename == '..') continue;
        $filepath = $dir.'/'.$filename;
        if(is_file($filepath)){ // 파일인 경우에만
            $getExt = pathinfo($filename, PATHINFO_EXTENSION); // 파일 확장자 구하기
            if(in_array($getExt, $valid_formats)){
                array_push($R,basename($filename,'.jpg')); // 파일이름만 선택하여 배열에 넣는다.
            }
        }
    }
    closedir($handle);
    sort($R); // 가나다순으로 정렬하기 위해
    return $R;
}
?>


블로그 이미지

Link2Me

,
728x90

PHP 에서 배열의 결과를 json 으로 반환하는 코드다.

네이버지식인에 간혹 화면에 결과가 없다고 질의가 올라오는 코드라서 아주 간단하게 적어본다.

json_encode 는 UTF-8로 된 것만 인식한다고 이해하면 된다.

DB, 서버와의 통신, 파일 인코딩 모드를 모두 UTF-8로 기본 설정해야 한다.


아래 코드를 파일로 첨부한다. 친절하게 테스트해보라고 테이블 구조까지 포함했다.


JSON.zip



<?php
extract($_GET);
$userID ="jsk005@naver.com"; // $_GET 배열로 넘어온 데이터라고 가정한다.

include_once 'dbconnect.php';

$sql = "SELECT * FROM members WHERE userID='".$userID."'";

$result = mysqli_query($dbconn,$sql) or die(mysqli_error($dbconn));
$R = array();
while($row = mysqli_fetch_array($result)){
array_push($R, array("userID"=>$row['userID'], "userNM"=>$row['userNM'], "OSType"=>$row['OStype']));
}

//echo '<pre>';print_r($R);echo '</pre>'; // 데이터가 제대로 들어있는지 확인 용도

echo json_encode(array("result"=>$R)); // 출력은 파일 Encoding 모드가 UTF-8 이어야 한다.
mysqli_close($dbconn);
?>

<?php
$dbconn = isConnectDb(); // DB 연결 함수

function isConnectDb(){
    $dbhost = 'localhost';
    $database = 'test';
    $dbuser = 'root';
    $dbpass = 'autoset';
    $dbport = "3306";
    $conn = mysqli_connect($dbhost,$dbuser,$dbpass,$database,$dbport);
    mysqli_set_charset($conn, "utf8");  // DB설정이 잘못되어 euc-kr 로 되어 있으면 문제가 됨
    if (mysqli_connect_errno()) {
        echo "Failed to connect to MySQL: " . mysqli_connect_error();
        exit;
    } else {
        return $conn;
    }
}
?>



블로그 이미지

Link2Me

,
728x90

http://brackets.io/ 에서 설치 파일을 다운로드 한다.

Adobe에서 제공하는 브라켓은 오픈 소스로 만들어진 텍스트 편집기로 웹 개발에 필요한 기능을 포함하고 있는 웹 개발 도구다.

확장기능을 설치하면 편리하게 사용할 수 있는 text editor 인거 같다.

브라켓 에디터를 설치한 이유는 NodeJS 코드 연습을 위한 목적이다.


일반적인 기능 설명은 https://opentutorials.org/course/2418/13455 생활코딩 사이트의 유투브 강좌에 잘 설명되어 있다.




설치 폴더는 Autoset9 에 설치되도록 변경했다.


프로그램을 실행하여 보기 --> 테마를 선택하여 [테마 설정]에서 아래와 같이 글꼴 크기를 조정한다.


실시간 미리보기를 누르면 크롬브라우저가 자동으로 연동되어 코드를 수정하면 실시간으로 크롬브라우저에서 변경된 사항을 확인할 수 있다.


확장기능 설치 : 파일 -> 확장기능 관리자

- Emmet : 편리한 자동완성 기능

- Custom Work : 파일을 탭 형식으로 보여주기

- Number Tabs

- Brackets Outline List  : 한 파일에 함수들을 한번에 볼 수 있는 플러그인

- NodeJS integration : 브라켓에서 노드 프로그램 실행하기



NodeJS 확장기능을 설치하고 실행하면 이런 에러메시지가 나온다.



파일을 선택하지 않아서 생기는 현상이다.



파일을 선택하고 실행버튼을 누르면 결과가 나온다.


실행할 파일을 제거하는 방법은



파일을 제거 또는 추가해주면 리스트에 보인다.

블로그 이미지

Link2Me

,
728x90

코드는 Morden PHP 라고 보기에는 좀 약할 수 있다. 하지만 참고하면 도움이 될 코드가 엄청 많다.

회원가입과 로그인, 최신 패스워드 권장 적용방식, 중복체크가 가능하고 방식은 Ajax 방식으로 처리한다.

jQuery, CSS 코드, PHP Class 함수는 안드로이드폰 로그인과의 연동부분까지 고려되어 있으니 입문 개발자에게는 훌륭한 샘플이 될 것이다.

메인 화면 Layout 구성은 초간단으로 기능 동작 중심이다. 회원가입과 로그인만 제대로 되는지 체크하는 용도라고 보면 된다.


== 회원가입 폼 ===

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="form.css"  />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
    // 회원가입처리
    $('#join-submit').click(function(e){
        e.preventDefault();
        var userNM = $("input[name='userNM']");
        if( userNM.val() =='') {
            alert("성명을 입력하세요");
            userNM.focus();
            return false;
        }

        var email = $("input[name='email']");
        if(email.val() == ''){
            alert('이메일을 입력하세요');
            email.focus();
            return false;
        } else {
            var emailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!emailRegex.test(email.val())) {
                alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
                email.focus();
                return false;
            }
        }

        var mobileNO = $("input[name='mobileNO']");
        if(mobileNO.val() ==''){
            alert('휴대폰 번호를 입력하세요');
            mobileNO.focus();
            return false;
        } else if(!/^[0-9]{10,11}$/.test(mobileNO.val())){
            alert("휴대폰 번호는 숫자만 10~11자리 입력하세요.");
            return false;
        } else if(!/^(01[016789]{1}|02|0[3-9]{1}[0-9]{1})([0-9]{3,4})([0-9]{4})$/.test(mobileNO.val())){
            alert("유효하지 않은 전화번호 입니다.");
            return false;
        }

        var password = $("input[name='Password']");
        var repassword = $("input[name='rePassword']");
        if(password.val() =='') {
            alert("비밀번호를 입력하세요!");
            password.focus();
            return false;
        }
        if(password.val().search(/\s/) != -1){
            alert("비밀번호는 공백없이 입력해주세요.");
            return false;
        }
        if(!/^[a-zA-Z0-9!@#$%^&*()?_~]{8,20}$/.test(password.val())){
            alert("비밀번호는 숫자, 영문, 특수문자(!@$%^&*?_~ 만 허용) 조합으로 8~20자리를 사용해야 합니다.");
            return false;
        }
        // 영문, 숫자, 특수문자 2종 이상 혼용
        var chk=0;
        if(password.val().search(/[0-9]/g) != -1 ) chk ++;
        if(password.val().search(/[a-z]/ig)  != -1 ) chk ++;
        if(password.val().search(/[!@#$%^&*()?_~]/g) != -1) chk ++;
        if(chk < 2){
            alert("비밀번호는 숫자, 영문, 특수문자를 두가지이상 혼용하여야 합니다.");
            return false;
        }
        // email이 아닌 userID 인 경우에는 체크하면 유용. email은 특수 허용문자에서 걸러진다.
        /*
        if(password.val().search(userID.val())>-1){
            alert("userID가 포함된 비밀번호는 사용할 수 없습니다.");
            return false;
        }
        */
        if(repassword.val() =='') {
            alert("비밀번호를 다시 한번 더 입력하세요!");
            repassword.focus();
            return false;
        }
        if(password.val()!== repassword.val()){
            alert('입력한 두 개의 비밀번호가 일치하지 않습니다');
            return false;
        }

        //var loginpath =$("#ajaxPath").attr('data-path');
        $.ajax({
            url: 'a.register.php',
            type: 'POST',
            data: {
                userNM:userNM.val(),
                userID:email.val(),
                password:password.val(),
                mobileNO:mobileNO.val()
            },
            dataType: "json", // json, text
            success: function (response) {
                //alert(response); //text 로 하고 a.register.php 에서 print_r을 사용하면 넘어간 데이터를 확인 가능
                if(response.result == 1){
                    alert('가입 완료');
                    location.replace('index.php'); // 화면 갱신
                } else if(response.result == 0){
                    alert('이미 가입된 아이디입니다');
                } else if(response.result == -2){
                    alert('입력 데이터를 확인하세요');
                } else {
                    //alert('등록중에 에러가 발생했습니다' + response);
                    alert('등록중에 에러가 발생했습니다');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });

    });

    // userID(e-mail) 가입여부 검사
    $("#checkid").click(function(e){
        e.preventDefault();
        var email = $("input[name='email']");
        if(email.val() == ''){
            alert('이메일을 입력하세요');
            email.focus();
            return false;
        } else {
            var emailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!emailRegex.test(email.val())) {
                alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
                email.focus();
                return false;
            }
        }

        $.ajax({
            url: 'a.joinChk.php',
            type: 'POST',
            data: {userID:email.val()},
            dataType: "json",
            success: function (msg) {
                //alert(msg); // 확인하고 싶으면 dataType: "text" 로 변경한 후 확인 가능
                if(msg.result == 1){
                    alert('사용 가능합니다');
                } else if(msg.result == 0){
                     alert('이미 가입된 아이디입니다');
                    email.val('');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });
    });

});
</script>
</head>
<body>
<div class="container">
<h2>회원가입 Form 예제</h2>
<form method="post" action="a.register.php">
    <table>
        <tr>
            <td style='width:100px'>이름</td>
            <td><input type="text" size=37 name="userNM" value=""></td>
        </tr>
        <tr>
            <td>E-Mail</td>
            <td>
                <input type="text" size=25 name="email" value="">
                <input type="button" id="checkid" value="중복체크">
            </td>
        </tr>
        <tr>
            <td>휴대폰번호</td>
            <td><input type="text" size=37 name="mobileNO" value=""></td>
        </tr>
        <tr>
            <td>비밀번호</td>
            <td><input type="password" size=37 name="Password"></td>
        </tr>
        <tr>
            <td>비밀번호(확인)</td>
            <td><input type="password" size=37 name="rePassword"></td>
        </tr>
        <tr>
            <td colspan='2' align='center'>
                <input type="button" id="join-submit" value="회원가입">
            </td>
        </tr>
    </table>
</fom>
</div>
</body>
</html>

 === 회원가입 a.register.php ===

<?php
if (!isset($_SESSION)) {
    session_start();
}

// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    //echo '<pre>';print_r($_POST);echo '</pre>'; // 전송받은 배열을 확인하고 싶을 때 사용
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        include_once 'dbController.php';
        require_once 'loginClass.php';
        $c = new LoginClass();
        if ($c->isUserExisted($userID)==1) {
            echo '{"result":"0"}'; // echo json_encode(array('result' => '0')); 과 동일
        } else {
            $mobileNO = preg_replace("/[^0-9]/", "", $mobileNO); //전화번호 숫자만 남기고 제거
            // 회원 등록
            $user = $c->storeUser($userID, $userNM, $password, $mobileNO);
            if ($user) {// 회원 등록 성공
                $_SESSION['userID'] = $user['userID'];
                $_SESSION['admin'] = $user['admin'];
                echo json_encode(array('result' => '1'));
            } else {
                // 회원 등록 실패
                echo json_encode(array('result' => '-1'));
            }
        }
    }else {// 입력받은 데이터에 문제가 있을 경우
        echo json_encode(array('result' => '-2'));
    }
}
?>

 === 로그인 폼 ===

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="form.css"  />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
    $('#login-submit').click(function(e){
        var userID = $("input[name='userID']");
        if( userID.val() =='') {
            alert("아이디를 입력하세요");
            userID.focus();
            return false;
        }

        var password = $("input[name='password']");
        if(password.val() =='') {
            alert("비밀번호를 입력하세요!");
            password.focus();
            return false;
        }
        $.ajax({
            url: 'a.loginChk.php',
            type: 'POST',
            data: {userID:userID.val(), password:password.val()},
            dataType: "json",
            success: function (response) {
                if(response.result == 1){
                    //alert('로그인 성공');
                    location.href='index.php';
                } else if(response.result == -2){
                     alert('입력한 값에 문제가 있습니다');
                } else {
                    alert('로그인 실패');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });
    });
});
</script>
</head>
<body>
<div class="container">
<h2>로그인 Form 예제</h2>
<form method="post" action="a.loginChk.php">
    <table>
        <tr>
            <td style="font-size:14px">로그인 ID</td>
            <td><input type="text" name="userID" value=""></td>
        </tr>
        <tr>
            <td style="font-size:14px">패스워드</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan='2' align=left>
                <input type="button" id="login-submit" value="로그인">
            </td>
        </tr>
        <tr>
            <td colspan='2'><p>Don't have an account? <a href="joinForm.php">Sign up now</a>.</p></td>
        </tr>
    </table>
</fom>
</div>
</body>
</html>

 === 로그인 체크 ===

<?php
// 파일을 직접 실행하는 비정상적 동작을 방지 하기 위한 목적
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    if(isset($userID) && !empty($userID) && isset($password) && !empty($password)) {
        include_once 'dbController.php';
        require_once 'loginClass.php';
        $c = new LoginClass();
        $user = $c->getUser($userID, $password);
        if ($user != false) {
            $_SESSION['userID'] = $user['userID'];
            $_SESSION['userNM'] = $user['userNM'];
            $_SESSION['admin'] = $user['admin'];
            echo json_encode(array('result' => '1'));
        } else {
            echo json_encode(array('result' => '0'));
        }
    }else {// 입력받은 데이터에 문제가 있을 경우
        echo json_encode(array('result' => '-2'));
    }
} else { // 비정상적인 접속인 경우
    echo 0; // a.loginChk.php 파일을 직접 실행할 경우에는 화면에 0을 찍어준다.
    exit;
}
?>

 == 가입여부 체크 ===

<?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    // db 접속파일 include 및 정상 로그인 여부 체크하는 함수 실행후 결과 반환처리 하면 된다.
    include_once 'dbController.php';
    require_once 'loginClass.php';
    $d = new DBController();
    $c = new LoginClass();
    $rs = $c->isUserExisted($userID);
    if($rs == '0'){
        echo '{"result":"1"}';
    } else {
        echo '{"result":"0"}'; // echo json_encode(array('result' => '0')); 과 동일
    }
}
?>


주요코드는 위에 열거하였고 나머지 코드에 대해서는 첨부파일을 참조하면 된다.

첨부파일에는 SQL 테이블 구조까지 포함되어 있고, 100% 동작되는지 여부를 확인한 코드다.

login.zip


인터넷에 올려진 코드가 잘 동작하지 않는 것은 변수명을 살짝 바꾸거나, 한두개 핵심적인 것을 빼먹어서 그런 경우가 참 많다. 코드가 제대로 동작 안되는 것은 본문에 있는 내용과 어떤 부분이 약간 다른지 확인하면 해결될 문제인데, 일단 시도를 안해보는 거 같다. 첨부한 예제는 오래된 책에 나온 구닥다리 PHP 와는 비교되지 않는 좋은 샘플 예제다.

무조건 실행해서 결과가 OK 되는 걸 원하는 건 초보자로서 당연할 수 있다.

하지만 개념 이해를 위한 공부를 해야 내 것이 된다.

login.zip 파일과 login_2.zip 파일 내용을 비교하면서 뭐가 틀려서 안되었던 걸까 하고 이해를 해야 한다.

이해를 하기 위해서는 파일 비교하는 방법을 알아야 한다.

https://link2me.tistory.com/1327 에 AcroDiff.exe 라는 파일이 있다.

최근에 개발자들이 Visual Studio Code 를 선호하여 AcroEdit 라는 툴이 있는지 모를 수 있지만, 이 에디터에 포함된 AcroDiff.exe 파일은 파일의 내용 비교에는 최고의 툴이다.

이걸 이용해서 파일을 서로 비교해보면 어디가 달라져 있고, 무엇 때문에 질문을 했는지 이해가 될 것이다.

login_2.zip


블로그 이미지

Link2Me

,
728x90

회원가입폼과 기가입여부를 체크하는 기능이 포함된 Form 예제다.

jQuery, Ajax 를 사용하여 입력 필드 체크와 휴대폰번호 및 비밀번호 유효성 검사, 이미 가입된 ID인지 여부를 체크하는 기능, 회원가입 자료 저장, 로그인 처리 기능이 포함되어 있다.

첨부파일에는 테이블 구조 SQL 까지 포함되어 있으므로 윈도우 기반 Autoset9 에서 직접 테스트해볼 수 있다.


간략하게 사용법 순서를 적어보면....

1. 테이블 구조 SQL 문을 autoset9 에서 테이블 생성한다.

    members 테이블과 member_data 테이블 두개의 테이블로 나누었고 PHP에서 JOIN 으로 데이터를 처리한다.

 CREATE TABLE IF NOT EXISTS `members` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `userID` varchar(50) NOT NULL DEFAULT '',
  `userNM` varchar(20) NOT NULL,
  `passwd` varchar(80) NOT NULL,
  `salt` varchar(10) NOT NULL,
  `admin` tinyint(2) NOT NULL DEFAULT '0',
  `phoneSE` varchar(80) DEFAULT NULL,
  `OStype` tinyint(2) NOT NULL DEFAULT '0',
  `regdate` char(14) NOT NULL DEFAULT '',
  `curdate` char(14) DEFAULT NULL COMMENT '최근접속',
  PRIMARY KEY (`uid`),
  UNIQUE KEY `userID` (`userID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

 CREATE TABLE IF NOT EXISTS `member_data` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `relateduid` int(11) NOT NULL DEFAULT '0',
  `level` int(11) NOT NULL DEFAULT '0',
  `userNM` varchar(60) NOT NULL COMMENT '이름',
  `email` varchar(60) NOT NULL COMMENT '메일',
  `mobileNO` varchar(20) NOT NULL,
  `officeNO` varchar(20) DEFAULT NULL,
  `homeNO` varchar(20) DEFAULT NULL,
  `home_addr1` varchar(100) DEFAULT NULL,
  `home_addr2` varchar(50) DEFAULT NULL,
  `office_addr1` varchar(100) DEFAULT NULL,
  `office_addr2` varchar(50) DEFAULT NULL,
  `sex` tinyint(2) NOT NULL DEFAULT '0',
  `point` int(11) NOT NULL DEFAULT '0',
  `smart` tinyint(2) NOT NULL DEFAULT '0',
  `modifydate` char(14) NOT NULL DEFAULT '',
  PRIMARY KEY (`uid`),
  KEY `relateduid` (`relateduid`),
  KEY `mobileNO` (`mobileNO`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;


2. 회원가입 폼

    - E-Mail 을 userID 로 사용하고 가입여부를 체크하는 중복체크 jQuery가 포함되어 있다.

    - E-Mail 주소 유효성 체크 기능이 포함되어 있다.

    - 휴대폰 번호 유효성 체크 기능이 포함되어 있다.

    - 비밀번호는 영문, 숫자, 특수문자 등을 혼용하여 가입하도록 했다.



3. 회원가입 처리 로직은 직접 구현해보도록 하여 입력값이 넘어오는지 여부만 체크하는 걸 주석처리했다.

    회원가입 결과를 확인할 수 있도록 내용을 수정 보완했다.

4. LoginForm.php 에서는 로그인 화면을 구현했다.

    로그인 체크 jQuery 와 Ajax 처리하는 걸 추가했다.

5. 회원가입/로그인이 성공하면 index.php 에서 SESSION을 검사하여 main.php 파일로 이동하도록 했다.


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="form.css"  />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
    // 회원가입처리
    $("form").submit(function(){
        var userNM = $("input[name='userNM']");
        if( userNM.val() =='') {
            alert("성명을 입력하세요");
            userNM.focus();
            return false;
        }

        var email = $("input[name='email']");
        if(email.val() == ''){
            alert('이메일을 입력하세요');
            email.focus();
            return false;
        } else {
            var emailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!emailRegex.test(email.val())) {
                alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
                email.focus();
                return false;
            }
        }

        var mobileNO = $("input[name='mobileNO']");
        if(mobileNO.val() ==''){
            alert('휴대폰 번호를 입력하세요');
            mobileNO.focus();
            return false;
        } else if(!/^[0-9]{10,11}$/.test(mobileNO.val())){
            alert("휴대폰 번호는 숫자만 10~11자리 입력하세요.");
            return false;
        } else if(!/^(01[016789]{1}|02|0[3-9]{1}[0-9]{1})([0-9]{3,4})([0-9]{4})$/.test(mobileNO.val())){
            alert("유효하지 않은 전화번호 입니다.");
            return false;
        }

        var password = $("input[name='Password']");
        var repassword = $("input[name='rePassword']");
        if(password.val() =='') {
            alert("비밀번호를 입력하세요!");
            password.focus();
            return false;
        }
        if(password.val().search(/\s/) != -1){
            alert("비밀번호는 공백없이 입력해주세요.");
            return false;
        }
        if(!/^[a-zA-Z0-9!@#$%^&*()?_~]{8,20}$/.test(password.val())){
            alert("비밀번호는 숫자, 영문, 특수문자(!@$%^&*?_~ 만 허용) 조합으로 8~20자리를 사용해야 합니다.");
            return false;
        }
        // 영문, 숫자, 특수문자 2종 이상 혼용
        var chk=0;
        if(password.val().search(/[0-9]/g) != -1 ) chk ++;
        if(password.val().search(/[a-z]/ig)  != -1 ) chk ++;
        if(password.val().search(/[!@#$%^&*()?_~]/g) != -1) chk ++;
        if(chk < 2){
            alert("비밀번호는 숫자, 영문, 특수문자를 두가지이상 혼용하여야 합니다.");
            return false;
        }
        // email이 아닌 userID 인 경우에는 체크하면 유용. email은 특수 허용문자에서 걸러진다.
        /*
        if(password.val().search(userID.val())>-1){
            alert("userID가 포함된 비밀번호는 사용할 수 없습니다.");
            return false;
        }
        */
        if(repassword.val() =='') {
            alert("비밀번호를 다시 한번 더 입력하세요!");
            repassword.focus();
            return false;
        }
        if(password.val()!== repassword.val()){
            alert('입력한 두 개의 비밀번호가 일치하지 않습니다');
            return false;
        }

    });

    // userID(e-mail) 가입여부 검사
    $("#checkid").click(function(e){
        e.preventDefault();
        var email = $("input[name='email']");
        if(email.val() == ''){
            alert('이메일을 입력하세요');
            email.focus();
            return false;
        } else {
            var emailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
            if (!emailRegex.test(email.val())) {
                alert('이메일 주소가 유효하지 않습니다. ex)abc@gmail.com');
                email.focus();
                return false;
            }
        }

        $.ajax({
            url: 'a.joinChk.php',
            type: 'POST',
            data: {userID:email.val()},
            dataType: "json",
            success: function (msg) {
                //alert(msg); // 확인하고 싶으면 dataType: "text" 로 변경한 후 확인 가능
                if(msg.result == 1){
                    alert('사용 가능합니다');
                } else if(msg.result == 0){
                     alert('이미 가입된 아이디입니다');
                    email.val('');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });
    });

});
</script>
</head>
<body>
<div class="container">
<h2>회원가입 Form 예제</h2>
<form method="post" action="a.register.php">
    <table>
        <tr>
            <td style='width:100px'>이름</td>
            <td><input type="text" size=37 name="userNM" value=""></td>
        </tr>
        <tr>
            <td>E-Mail</td>
            <td>
                <input type="text" size=25 name="email" value="">
                <input type="button" id="checkid" value="중복체크">
            </td>
        </tr>
        <tr>
            <td>휴대폰번호</td>
            <td><input type="text" size=37 name="mobileNO" value=""></td>
        </tr>
        <tr>
            <td>비밀번호</td>
            <td><input type="password" size=37 name="Password"></td>
        </tr>
        <tr>
            <td>비밀번호(확인)</td>
            <td><input type="password" size=37 name="rePassword"></td>
        </tr>
        <tr>
            <td colspan='2' align='center'><input type="submit" value="회원가입"></td>
        </tr>
    </table>
</fom>
</div>
</body>
</html>

 body {    
    font-family: 나눔바른고딕, NanumBarunGothic, 맑은고딕, "Malgun Gothic", 돋움, Dotum, "Apple SD Gothic Neo", sans-serif;
    font-size: 12px;
    color: rgb(33, 33, 33);
    letter-spacing: 0.03em;
}

table {
    width: 600px;
}

tr {
    height: 50px;
}

input[type=text], input[type=password] {
    padding: 5px 10px; /* 상하 우좌 */
    margin: 3px 0; /* 상하 우좌 */
    font-family: inherit; /* 폰트 상속 */
    border: 1px solid #ccc; /* 999가 약간 더 진한 색 */
    font-size:14pt;
    box-sizing: border-box;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    -webkit-transition: width 0.4s ease-in-out;
    transition: width 0.4s ease-in-out;
}

input[type=text]:focus, input[type=password]:focus {
    border: 2px solid #555;
}

input[type=submit],input[type=button] {
    margin-top: 10px;
    width:100px;
    height:40px;
    line-height: 22px;
    padding: 5px, 10px; /* 상하 우좌 */
    background: #E6E6E6;
    color: #000000;
    font-size: 15px;
    font-weight: normal;
    letter-spacing: 1px;
    border: none;
    cursor: pointer;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
}

input[type=submit]:hover {
    background-color: #FFBF00;
}

 <?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    // db 접속파일 include 및 정상 로그인 여부 체크하는 함수 실행후 결과 반환처리 하면 된다.
    include_once 'dbController.php'; // http://link2me.tistory.com/1478 참조
    require_once 'loginClass.php';
    $c = new LoginClass();
    $rs = $c->isUserExisted($userID);
    if($rs == '0'){
        echo '{"result":"1"}';
    } else {
        echo '{"result":"0"}'; // echo json_encode(array('result' => '0')); 과 동일
    }
}
?>

<?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    echo '<pre>';print_r($_POST);echo '</pre>';
}
?>


LoginClass 함수 만든 예제다.

<?php
class LoginClass extends DBController {
    // 회원 가입 여부 체크
    public function isUserExisted($u) {
        if(!isset($u) || empty($u)) {
            return '-1';
        } else {
            $sql ="SELECT count(userID) from members WHERE userID='".$u."'";
            $result = mysqli_query($this->db, $sql);
            if($row = mysqli_fetch_array($result)){
                return $row[0]; // 미가입이면 0 반환, 가입이면 1 반환
            } else {
                return -1;
            }
        }
    }

}//end class LoginClass

?> 


직접 테스트해보고 싶은 개발자의 타이핑 노고를 덜어주고자 테스트한 파일을 첨부한다.

Last Update : 2019.04.12 첨부파일 내용 보강(초보자를 위한 배려)


ex02.zip


https://link2me.tistory.com/1481 읽어보고 첨부된 예제를 받아서 분석하고 이해하면 많은 도움이 될 것이다.


도움되신 분은 공감 팍팍 눌러주세요.

블로그 이미지

Link2Me

,
728x90

PHP 에서 내용을 다른 파일로 전달하는 방법으로 가장 기본 예제가 로그인 예제다.

네이버 지식인에 올라오는 공통적인 질의사항에 대한 개념이해를 돕기 위함이다.

HTML5 기반 jQuery 입력 필드 체크, 모바일 환경을 고려했다.

<meta name="viewport" content="width=device-width, initial-scale=1"> 이 부분이 모바일 환경을 고려한 부분이다.

bootstrap 폼 예제는 아니다. 기본 Form 에 대한 이해를 하고 나면 bootstrap Form으로 변경하는 것도 쉽다.

Form 을 작성하려면 <form> 태그를 사용한다.

<form> 태그는 두가지 주요 속성을 가진다.

- action 속성 : 어디에 데이터를 보낼지의 URL을 지정

- method 속성 : 어떻게 데이터를 전송할지의 방법을 지정. method 속성값에는 POST와 GET의 두가지 방법이 있다.


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="form1.css"  />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function(){
    $("form").submit(function(){
        var loginID = $("input[name='loginID']").val();
        var loginPW = $("input[name='loginPW']").val();

        if( loginID =='') {
            alert("아이디를 입력하세요");
            $("input[name='loginID']").focus();
            return false;
        }

        if(loginPW =='') {
            alert("패스워드를 입력하세요!");
            $("input[name='loginPW']").focus();
            return false;
        }
    });
});
</script>
</head>
<body>
<div class="container">
<h2>Form 예제1</h2>
<form method="post" action="form.action.php">
    <table>
        <tr>
            <td>로그인 ID</td>
            <td><input type="text" name="loginID" value=""></td>
        </tr>
        <tr>
            <td>패스워드</td>
            <td><input type="password" name="loginPW"></td>
        </tr>
        <tr>
            <td colspan='2' align='center'><input type="submit" value="전송"></td>
        </tr>
    </table>
</fom>
</div>
</body>
</html>

 body {    
    font-family: 나눔바른고딕, NanumBarunGothic, 맑은고딕, "Malgun Gothic", 돋움, Dotum, "Apple SD Gothic Neo", sans-serif;
    font-size: 12px;
    color: rgb(33, 33, 33);
    letter-spacing: 0.03em;
}

table {
    width: 300px;
}

tr {
    height: 50px;
}

input[type=text], input[type=password] {
    width: 100%;
    padding: 5px 10px; /* 상하 우좌 */
    margin: 3px 0; /* 상하 우좌 */
    font-family: inherit; /* 폰트 상속 */
    border: 1px solid #ccc; /* 999가 약간 더 진한 색 */
    font-size:14pt;
    box-sizing: border-box;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    -webkit-transition: width 0.4s ease-in-out;
    transition: width 0.4s ease-in-out;
}

input[type=text]:focus, input[type=password]:focus {
    border: 2px solid #555;
}

input[type=submit] {
    margin-top: 10px;
    width:100px;
    height:40px;
    line-height: 22px;
    padding: 5px, 10px; /* 상하 우좌 */
    background: #E6E6E6;
    color: #000000;
    font-size: 15px;
    font-weight: normal;
    letter-spacing: 1px;
    border: none;
    cursor: pointer;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
}

input[type=submit]:hover {
    background-color: #FFBF00;
}

<?php
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // $_POST['loginID'] 라고 쓰지 않고, $loginID 라고 써도 인식되게 함
    // POST 배열로 넘어온 값 확인 목적
    echo '<pre>';print_r($_POST);echo '</pre>';
    // db 접속파일 include 및 정상 로그인 여부 체크하는 함수 실행후 결과 반환처리 하면 된다.

    $ref = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER']:'';
    // 만약 사용자가 이전 페이지의 링크를 거치지 않고 브라우저에 URL을 직접 입력하여 접속한 경우는 ''
} else { // 비정상적인 접속인 경우
    echo 0; // form.action.php 파일을 직접 실행할 경우에는 화면에 0을 찍어준다.
    exit;
}
?>


클라이언트에서 Javascript 로 검증하고 있으면 서버에서 재검증할 필요가 없다고 생각할 수도 있지만 이것은 크게 잘못된 생각이다. 공격자는 모든 요청을 자유롭게 서버에 전송할 수 있으므로, Javascript에서의 검증을 쉽게 빠져나갈 수 있다.


만약 사용자가 이전 페이지의 링크를 거지치 않고 직접 접속한 경우에는 브라우저에 URL을 직접 입력하여 접속한 경우에는 참조원 정보뿐만아니라 $_SERVER['HTTP_REFERER'] 도 없다.


테스트한 파일을 첨부한다.

form-01.zip


블로그 이미지

Link2Me

,
728x90

PHP 에서 MySQL 접속 dbController Class 만드는 코드다.

절차지향방식의 생성자, 소멸자 만드는 방법과 class MySQLDbClass extends DBController { } 예시다.


사용법

require_once '../phpclass/dbController.php';

$d = new DBController();


<?php
class DBController {
    private $host = 'localhost';
    private $database = 'survey';
    private $userid = 'root';
    private $password = 'autoset';
    protected $db;

    public function __construct() {
        $this->db = $this->connectDB();
    }

    function __destruct(){
        mysqli_close($this->connectDB());
        //mysqli_close($this->db);
    }

    private function connectDB() {
        $dbconn = mysqli_connect($this->host, $this->userid, $this->password, $this->database);
        mysqli_set_charset($dbconn, "utf8"); // DB설정이 잘못되어 euc-kr 로 되어 있으면 문제가 됨
        if (mysqli_connect_errno()) {
           printf("Connect failed: %s\n", mysqli_connect_error());
           exit();
        } else {
          return $dbconn;
        }
    }

    //DB-UID데이터
    function getUidData($table,$uid){
        return $this->getDbData($table,'uid='.(int)$uid,'*');
    }

    // DB Query Cutom 함수
    function getDbData($table,$where,$column) {
        $result = mysqli_query($this->db,'select '.$column.' from '.$table.($where?' where '.$this->getSqlFilter($where):''));
        $row = mysqli_fetch_array($result);
        return $row;
    }

    // DB Query result 함수
    function getDbresult($table,$where,$column) {
        $result = mysqli_query($this->db,'select '.$column.' from '.$table.($where?' where '.$this->getSqlFilter($where):''));
        return $result;
    }

    //DB데이터 ARRAY -> 테이블에 출력할 데이터 배열
    function getDbArray($table,$where,$flddata,$orderby,$rowsPage,$curPage){
        $sql = 'select '.$flddata.' from '.$table.($where?' where '.$this->getSqlFilter($where):'').($orderby?' order by '.$orderby:'').($rowsPage?' limit '.(($curPage-1)*$rowsPage).', '.$rowsPage:'');
        if($result = mysqli_query($this->db,$sql)){
            return $result;
        }
    }

    //DB데이터 레코드 총 개수
    function getDbRows($table,$where){
        $sql = 'select count(*) from '.$table.($where?' where '.$this->getSqlFilter($where):'');
        if($result = mysqli_query($this->db,$sql)){
            $rows = mysqli_fetch_row($result);
            return $rows[0] ? $rows[0] : 0;
        }
    }

    //DB삽입
    function getDbInsert($table,$key,$val){
        mysqli_query($this->db,"insert into ".$table." (".$key.")values(".$val.")");
    }

    //DB업데이트
    function getDbUpdate($table,$set,$where){
        mysqli_query('set names utf8');
        mysqli_query('set sql_mode=\'\'');
        mysqli_query($this->db,"update ".$table." set ".$set.($where?' where '.$this->getSqlFilter($where):''));
    }

    //DB삭제
    function getDbDelete($table,$where)    {
        mysqli_query($this->db,"delete from ".$table.($where?' where '.$this->getSqlFilter($where):''));
    }

    //SQL필터링
    function getSqlFilter($sql){
        return $sql;
    }

}//end dbClass

?>


만약 2개의 파일로 나누어서 만들었다면 첨부파일처럼 코드를 작성하면 된다.

dbClass.zip


블로그 이미지

Link2Me

,
728x90

접속한 단말을 구분하고 IP주소를 확인하는 코드다.

echo $_SERVER['HTTP_USER_AGENT'].'<br />';

이 한줄로 접속한 단말의 정보를 알 수 있다. 직접 확인해보라.

스마트폰 단말에서 접속이 글자가 너무 작아지는 걸 방지하기 위해서 헤더정보를 포함했다.


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex,nofollow"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<?php
echo $_SERVER['HTTP_USER_AGENT'].'<br />';
if(strpos($_SERVER['HTTP_USER_AGENT'],"iPhone")>0){
    echo '<br />'."접속단말 : 아이폰";
} else if(strpos($_SERVER['HTTP_USER_AGENT'],"Android")>0) {
    echo '<br />'."접속단말 : 안드로이드";
}

$ipAddress = getClientIP();
echo '<br />';
echo '<br />'.$ipAddress;

    function getClientIP() {
        if (isset($_SERVER)) {
            if (isset($_SERVER["HTTP_X_FORWARDED_FOR"]))
                return $_SERVER["HTTP_X_FORWARDED_FOR"];

            if (isset($_SERVER["HTTP_CLIENT_IP"]))
                return $_SERVER["HTTP_CLIENT_IP"];

            return $_SERVER["REMOTE_ADDR"];
        }

        if (getenv('HTTP_X_FORWARDED_FOR'))
            return getenv('HTTP_X_FORWARDED_FOR');

        if (getenv('HTTP_CLIENT_IP'))
            return getenv('HTTP_CLIENT_IP');

        return getenv('REMOTE_ADDR');
    }

?>
</body>
</html>


접속단말이 Android 인 경우 Android 버전 정보를 알고 싶다면 어떻게 하면 될까?

if(strstr($_SERVER['HTTP_USER_AGENT'], 'Android')){
    preg_match('/Android (\d+(?:\.\d+)+)[;)]/', $_SERVER['HTTP_USER_AGENT'], $matches);
    echo $matches[1].'<br />';
}
로 하면 버전 정보를 추출할 수 있다.


'Web 프로그램 > PHP 응용 및 활용' 카테고리의 다른 글

PHP 달력 만들기 소스  (0) 2018.10.30
날짜 선택  (0) 2018.10.26
PHP 글자수 줄임 표시  (0) 2018.06.09
PHP to JSP  (0) 2018.04.28
PHP Form 다중 체크박스 값 전달 및 POST  (0) 2018.04.02
블로그 이미지

Link2Me

,
728x90

Apache AccessLog 를 분석하다보니 이런 패턴이 보인다. (시도한 IP주소는 삭제했음)



[01/Jun/2018:06:46:41 +0900] "GET /phpmyadmin/index.php HTTP/1.1" 404 218
[01/Jun/2018:06:46:41 +0900] "GET /phpMyAdmin/index.php HTTP/1.1" 404 218
[01/Jun/2018:06:46:41 +0900] "GET /pmd/index.php HTTP/1.1" 404 211
[01/Jun/2018:06:46:41 +0900] "GET /pma/index.php HTTP/1.1" 200 1
[01/Jun/2018:06:46:43 +0900] "GET /pma/index.php HTTP/1.1" 200 1
[01/Jun/2018:06:46:43 +0900] "GET /PMA/index.php HTTP/1.1" 404 211
[01/Jun/2018:06:46:43 +0900] "GET /PMA2/index.php HTTP/1.1" 404 212
[01/Jun/2018:06:46:43 +0900] "GET /pmamy/index.php HTTP/1.1" 404 213
[01/Jun/2018:06:46:44 +0900] "GET /pmamy2/index.php HTTP/1.1" 404 214
[01/Jun/2018:06:46:44 +0900] "GET /mysql/index.php HTTP/1.1" 404 213
[01/Jun/2018:06:46:44 +0900] "GET /admin/index.php HTTP/1.1" 404 213
[01/Jun/2018:06:46:44 +0900] "GET /db/index.php HTTP/1.1" 404 210
[01/Jun/2018:06:46:44 +0900] "GET /dbadmin/index.php HTTP/1.1" 404 215
[01/Jun/2018:06:46:44 +0900] "GET /web/phpMyAdmin/index.php HTTP/1.1" 404 222
[01/Jun/2018:06:46:44 +0900] "GET /admin/pma/index.php HTTP/1.1" 404 217
[01/Jun/2018:06:46:44 +0900] "GET /admin/PMA/index.php HTTP/1.1" 404 217
[01/Jun/2018:06:46:44 +0900] "GET /admin/mysql/index.php HTTP/1.1" 404 219
[01/Jun/2018:06:46:44 +0900] "GET /admin/mysql2/index.php HTTP/1.1" 404 220
[01/Jun/2018:06:46:44 +0900] "GET /admin/phpmyadmin/index.php HTTP/1.1" 404 224
[01/Jun/2018:06:46:44 +0900] "GET /admin/phpMyAdmin/index.php HTTP/1.1" 404 224
[01/Jun/2018:06:46:44 +0900] "GET /admin/phpmyadmin2/index.php HTTP/1.1" 404 225
[01/Jun/2018:06:46:44 +0900] "GET /mysqladmin/index.php HTTP/1.1" 404 218
[01/Jun/2018:06:46:44 +0900] "GET /mysql-admin/index.php HTTP/1.1" 404 219
[01/Jun/2018:06:46:44 +0900] "GET /phpadmin/index.php HTTP/1.1" 404 216
[01/Jun/2018:06:46:44 +0900] "GET /phpmyadmin0/index.php HTTP/1.1" 404 219
[01/Jun/2018:06:46:44 +0900] "GET /phpmyadmin1/index.php HTTP/1.1" 404 219
[01/Jun/2018:06:46:44 +0900] "GET /phpmyadmin2/index.php HTTP/1.1" 404 219
[01/Jun/2018:06:46:44 +0900] "GET /myadmin/index.php HTTP/1.1" 404 215
[01/Jun/2018:06:46:44 +0900] "GET /myadmin2/index.php HTTP/1.1" 404 216
[01/Jun/2018:06:46:44 +0900] "GET /xampp/phpmyadmin/index.php HTTP/1.1" 404 224
[01/Jun/2018:06:46:44 +0900] "GET /phpMyadmin_bak/index.php HTTP/1.1" 404 222
[01/Jun/2018:06:46:44 +0900] "GET /www/phpMyAdmin/index.php HTTP/1.1" 404 222
[01/Jun/2018:06:46:44 +0900] "GET /tools/phpMyAdmin/index.php HTTP/1.1" 404 224
[01/Jun/2018:06:46:44 +0900] "GET /phpmyadmin-old/index.php HTTP/1.1" 404 222
[01/Jun/2018:06:46:44 +0900] "GET /phpMyAdminold/index.php HTTP/1.1" 404 221
[01/Jun/2018:06:46:44 +0900] "GET /phpMyAdmin.old/index.php HTTP/1.1" 404 222
[01/Jun/2018:06:46:44 +0900] "GET /pma-old/index.php HTTP/1.1" 404 215
[01/Jun/2018:06:46:44 +0900] "GET /claroline/phpMyAdmin/index.php HTTP/1.1" 404 228
[01/Jun/2018:06:46:44 +0900] "GET /typo3/phpmyadmin/index.php HTTP/1.1" 404 224
[01/Jun/2018:06:46:44 +0900] "GET /phpma/index.php HTTP/1.1" 404 213
[01/Jun/2018:06:46:44 +0900] "GET /phpmyadmin/phpmyadmin/index.php HTTP/1.1" 404 229
[01/Jun/2018:06:46:44 +0900] "GET /phpMyAdmin/phpMyAdmin/index.php HTTP/1.1" 404 229



404는 파일이 존재하지 않는다는 의미이고 200은 파일은 존재한다는 의미다.

phpMyAdmin 사용을 매우 주의해야겠다는 생각이 들었다.

특정한 IP에서만 접속 가능하도록 ipFiltering을 걸어두었기에 brute force 공격(무차별 root 패스워드 대입공격)은 불가능하다.

하지만 ipFiltering 을 걸어두지 않고 MySQL 패스워드를 간단하게 사용하는 사이트가 있다면 사이트가 털릴 가능성이 매우 높다.

이런 로그를 잘 살펴보지 않으면 무슨 문제가 발생하고 있는지 조차 모를 수 있다.

1. MySQL root 패스워드는 복잡하게 생성해야 한다.

2. 특정 IP 에서만 접속 허용하는 ipFiltering.php 를 만들어서 incluce_once 시켜서 접속 IP를 확인하게 만든다.


블로그 이미지

Link2Me

,
728x90

PHP 테이블에서 글자수가 너무 길어서 2줄로 보이거나 테이블 구조가 이상하게 보이는 증상이 있어서 찾아봤더니 mb_strimwidth 함수를 이용하면 편하게 글자수 줄임이 가능하다.


mb_strimwidth ( string $str , int $start , int $width [, string $trimmarker [, string $encoding ]] )

mb_strimwidth('텍스트','시작위치','끝위치',"끝에붙일말","언어코드");


사용 예제


mb_strimwidth("Hello World", 0, 10, "...");


echo "<td class='bl'>".mb_strimwidth($row['positionNM'],'0','18','...','utf-8')."</td>";


표의 셀에서 글자수가 길어서 글줄임 ... 으로 표시하기도 하지만

echo "<td class='bl' style='font-size:0.8vw'>".$row['userNM']."</td>";

와 같이 style='font-size:0.8vw' 를 사용하면 글자 크기를 줄여서 보여줄 수 있다.


https://stackoverflow.com/questions/16056591/font-scaling-based-on-width-of-container
에 나온 내용을 참고했다.
The values are:
    vw (% of the viewport width)
    vh (% of the viewport height)
    vi (1% of the viewport size in the direction of the root element's inline axis)
    vb (1% of the viewport size in the direction of the root element's block axis)
    vmin (the smaller of vw or vh)
    vmax (the larger or vw or vh)

1 v* is equal to 1% of the initial containing block.


나중에 살펴보려고 검색한 링크를 적어둔다.

Responsive fonts - auto-scaling root font-size

https://codepen.io/2kool2/pen/ZpLYYo


'Web 프로그램 > PHP 응용 및 활용' 카테고리의 다른 글

날짜 선택  (0) 2018.10.26
접속단말 및 IP주소  (0) 2018.06.11
PHP to JSP  (0) 2018.04.28
PHP Form 다중 체크박스 값 전달 및 POST  (0) 2018.04.02
PHP 배열 합집합, 교집합, 차집합, 대칭차집합  (0) 2018.04.01
블로그 이미지

Link2Me

,
728x90

반응형웹은 다양한 디바이스(PC, 노트북, 테블릿, 스마트폰 등)에 최적화된 UI를 제공하게 할 수 있다.
웹사이트의 레이아웃을 만들 때 방문자가 사용하는 모니터의 화면 해상도를 고려해야 한다.
너무 크게 가로폭을 만들면 작은 해상도의 모니터로 접속했을 때 가로 스크롤이 생겨 컨텐츠를 보는 게 불편하기 때문이다.
해상도에 따라 가로폭이나 배치를 변경하여 가독성을 높인다.

이러한 작업을 할 수 있게 해주는 것이 @media 이다. media 쿼리는 장치 특성에 따라 브라우저가 해석해야 할 CSS 코드를 분기 처리하는 규칙이다. 보통 해상도 또는 해상력을 판별하여 분기 처리한다.


CSS3 @media 쿼리는 IE6~8 브라우저에서는 지원되지 않는다. 그래서 Javascript로 구현된 respond.js를 이용해서 이 문제를 해결해야한다. respond.min.js 라이브러리를 사용하면 IE6~8 브라우저도 media 쿼리를 해석할 수 있다.

html5shiv.js 파일을 문서 head 요소 내부에 삽입하면 IE6~8 브라우저도 HTML5 요소를 화면에 표시하게 된다.

하지만 html5shiv.js 파일을 추가해도 새로운 요소들 때문에 화면이 깨지는 증상을 발견할 수 있다. 이유는 display:block 으로 표시를 해야 할 블럭 요소들이 display:inline 으로 랜더링 되기 때문이다.

header,footer,section,article,aside,nav,hgroup,details,menu,figure,figcaption{
  display:block
}

인라인 형태로 표시되던 새로운 HTML5 요소를 블럭 요소로 변경해 준다. 이제 IE 6~8 브라우저도 HTML5 요소를 화면에 제대로 표시하게 된다.

최근에는 IE6~8 사용 거의 안하므로 이런 건 무시하자.


고정으로 보일 영역과 가변으로 보일 영역을 지정한다.
고정 영역에서도 padding 처리, 글꼴 크기 등을 조절하여 디바이스 기기에 따라 변하게 작성한다.


A 영역에는 공용으로 사용할 기본 CSS를 모두 적어준다.

B/C/D/E 영역에는 기본 사항과 공통 요소 값을 다르게 적어주면 해당 해상도에 따라 덮어쓰기가 된다.


  A

/* ########### 599px 이하 ########### */
@media (max-width: 599px) {
  B
}

/* ########### 600px 이상 ~ 767px 이하 ########### */
@media (min-width: 600px) and (max-width: 767px) {
  C
}

/* ########### 768px 이상 ########### */
@media ( min-width: 768px ) {
  D
}

/* ########### 1040px 이상 ########### */
@media ( min-width: 1024px ) {
  E
}



Layout 구성하는 예제를 작성해보자.

가장 먼저 전체 Layout 에서 헤더 중심으로 파일을 살펴본다.

<!doctype html>
<html lang="en">
<head>
<title>반응형 웹 예제</title>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./css/main.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>

</body>
</html>


<meta name="viewport" content="width=device-width, initial-scale=1">

webkit 계열 iOS, Android 모바일 브라우저에서 이코드를 해석한다.


jQuery 를 사용하기 위해 https://www.w3schools.com/ 를 참조하여 Header 를 구성하면 편하다.


<link rel="stylesheet" href="./css/main.css">

이제 main.css 파일 내용은 @media 를 사용해서 작성할 것이다.


<body> </body> 사이의 구성할 Layout 은 <div></div> 를 사용한다.

div 태그는 기본 속성이 세로로 하나씩 추가되므로 container 에 가로로 정렬되도록 하려면 별도 처리를 해야 한다.


<body>
<div class="header"></div>
<div class="container">
    <div class="sidemenu"></div>
    <div class="content"></div>
</div>
<div class="footer"></div>
</body>


div 태그 : float 속성으로 가로 정렬 한다.

/* ########### 768px 이상 ########### */
@media ( min-width: 768px ) {
    .container:after {content:"";display:both;clear:both;}
    .sidemenu { float:left; width:160px;}
    .content {float:none; width:auto; margin-left:160px;}
    /* sidemenu 와 content 의 왼쪽 오른쪽의 간격 */
    .sidemenu    {
        padding-right: 10px;
        -moz-box-sizing: border-box;
        -webkit-box-sizing: border-box;
        box-sizing: border-box;
    }
    .footer { 

        clear:both; /* float 속성 적용한 것을 해제하겠다. */

        width:100%;

    }
}

<div class="header"></div>

에는 상단 가로정렬 메뉴를 만들어보자.

<div class="header">
    <nav class="topmenu">
        <ul>
            <li><a href="#">Home</a></li>
            <li><a href="#">메뉴1</a></li>
            <li><a href="#">메뉴2</a></li>
            <li><a href="#">메뉴3</a></li>
            <li><a href="#">메뉴4</a></li>
            <li><a href="#">메뉴5</a></li>
        </ul>
    </nav>
</div>


위와 같은 코드만 추가하면 메뉴는 세로 정렬로 추가된다.

가로 정렬로 추가되도록 하려면 CSS 코드를 추가해줘야 한다.

/* 메뉴(가로 정렬) */
.topmenu ul {    margin: 0; padding: 0; list-style: none; }
.topmenu li a    {
    display: block;
    padding: 7px 10px;
    color: #000000;
    font-size: 14px;
    text-decoration: none;
}
.topmenu li a:hover {background-color: #F7D358;}
.topmenu ul:after    {content: ""; display: block; clear: both;}
.topmenu li {float: left; width: auto;}


header를 바의 형태로 디자인 하려면

.header    {background-color: #58D3F7;}



사이드 메뉴 만들기

    <div class="sidemenu">
        <ul>
            <li><a href="#">Home</a></li>
            <li><a href="#">Side 메뉴1</a></li>
            <li><a href="#">Side 메뉴2</a></li>
            <li><a href="#">Side 메뉴3</a></li>
            <li><a href="#">Side 메뉴4</a></li>
            <li><a href="#">Side 메뉴5</a></li>
        </ul>
    </div> 

 /* 사이드 메뉴(sidemenu) */
.sidemenu ul {margin: 0; padding: 0; list-style: none;}
.sidemenu li a    {
    display: block;
    padding: 5px;
    color: #000000;
    font-size: 14px;
    text-decoration: none;
}
.sidemenu li a:hover {background-color: #eeeeee;}


content 부분

    <div class="content">
        <article class="story">
            <h2>본문 제목</h2>
            <p><img src="img/img_sample.png" alt=""></p>
            <p>본문 내용을 여기에 작성한다.</p>
            <p>김남길과 김아중이 연기한 명불허전은 조선시대 침술대가 허임을 소재로......</p>
        </article>
    </div>

 /* 본문 기사 */
.story h1    {margin-top: 0;    margin-bottom: 10px;font-size: 26px;}
.story p    {margin-top: 0;    margin-bottom: 10px;}
.story img    {max-width: 100%; height: auto}


연습한 Layout 샘플 파일을 첨부한다.

구글에서 "반응형 css 예제" 로 검색하면 참고할 수 있는 자료가 많이 나온다.

div 화면 구성하는 것을 많이 다뤄보면 도움된다.

블로그 이미지

Link2Me

,
728x90

php.ini 에서 extension=php_gd2.dll 가 앞에 주석처리되어 있다면 ;(주석)을 제거하고 apache 를 재시작한다.


생활코딩(https://opentutorials.org/course/62/5137) 사이트에 나온 걸 기준으로 주석을 좀 더 달았다.

<?php
header("Content-type: image/png"); // 콘텐츠의 타입을 png image 로 선언
$string = isset($_GET['text'])? $_GET['text']:'default';
$im = imagecreatefrompng("image/btn.png"); // 버튼 배경으로 사용할 이미지를 지정
$backgroundColor  = imagecolorallocate($im, 60, 87, 156); // 컬러를 지정
$px = (imagesx($im) - 7.5 * strlen($string)) / 2;
imagestring($im, 4, $px, 9, $string, $backgroundColor );
imagepng($im); // 이미지 파일 만들기
imagedestroy($im); // 정보삭제
?>


심플한 워터마크 만들기

디지털 파일에 저작자를 밝히기 위해 흐릿하게 이미지를 박아넣는 디지털 워터마크.
유료 이미지 소스 제공 사이트들에는 샘플 이미지에 모두 워터마크 처리를 한다.
샘플로 공개할 때 화질을 낮추거나 워터마크 처리를 하여 도용을 방지한다.


http://itrooms.tistory.com/304 에 나온 워터마크 코드를 용어를 좀 더 정리했다.


watermark.zip

사용법 : <img src="./watermark.php?path=image/sandal-01.png">

<?php
$ALIGN_CENTER = true;
$image_source =  $_GET['path']; // 원본 이미지 경로
$watermark_Path = './image/watermark.png'; // 워터마크 사용할 이미지
$Image_Type = strtolower(substr($image_source, strlen($image_source)-4, 4));
$watermark_Type = strtolower(substr($watermark_Path, strlen($watermark_Path)-4, 4));
if($Image_Type == '.bmp') $image = imagecreatefromwbmp($image_source);
if($Image_Type == '.gif') $image = imagecreatefromgif($image_source);
if($Image_Type == '.jpg') $image = imagecreatefromjpeg($image_source);
if($Image_Type == '.png') $image = imagecreatefrompng($image_source);
if($image) {
    if($watermark_Type == '.bmp') $watermark = imagecreatefromwbmp($watermark_Path);
    if($watermark_Type == '.gif') $watermark = imagecreatefromgif($watermark_Path);
    if($watermark_Type == '.jpg') $watermark = imagecreatefromjpeg($watermark_Path);
    if($watermark_Type == '.png') $watermark = imagecreatefrompng($watermark_Path);
    if($watermark) {
        list($imagewidth, $imageheight) = getimagesize($image_source);
        list($watermarkwidth, $watermarkheight) = getimagesize($watermark_Path);
        if($ALIGN_CENTER) { // Center
            $startwidth = (($imagewidth - $watermarkwidth)/2);
            $startheight = (($imageheight - $watermarkheight)/2);
        }
        else {
            $startwidth = ($imagewidth - $watermarkwidth);
            $startheight = ($imageheight - $watermarkheight);
        }
        imagealphablending($image, true);
        imagecopy($image, $watermark, $startwidth, $startheight, 0, 0, $watermarkwidth, $watermarkheight);
        header("Content-type: image/png");
        imagepng($image); // 이미지 파일 만들기
        imagedestroy($image);
        imagedestroy($watermark);
    }
}
// <img src="./watermark.php?path=원본이미지경로">
?>


블로그 이미지

Link2Me

,
728x90

[
    {
    "userID": 1383458400,
    "icon": "partly-cloudy-day",
    "sunriseTime": 1383491266,
    "sunsetTime": 1383523844,
    "temperatureMin": -3.46,
    "temperatureMinTime": 1383544800,
    "temperatureMax": -1.12,
    "temperatureMaxTime": 1383458400
    },
    {
    "userID": 1383458400,
    "icon": "partly-cloudy-day",
    "sunriseTime": 1383491266,
    "sunsetTime": 1383523844,
    "temperatureMin": -3.46,
    "temperatureMinTime": 1383544800,
    "temperatureMax": -1.12,
    "temperatureMaxTime": 1383458400
    }
] 



<?php
// Local JSON 파일 읽어오기

$url ='test.json';

if(!file_exists($url)) {
    echo '파일이 없습니다.';
    exit;
}

$json_string = file_get_contents($url);
$R = json_decode($json_string, true);
// json_decode : JSON 문자열을 PHP 배열로 바꾼다
// json_decode 함수의 두번째 인자를 true 로 설정하면 무조건 array로 변환된다.
// $R : array data
echo '<pre>';
print_r($R);
echo '</pre>';


// 자료 파싱처리

foreach ($R as $key => $value) {
    if (!is_array($value)) {
        echo $key . '=>' . $value . '<br/>';
    } else {
        foreach ($value as $key => $val) {
            echo $key . '=>' . $val . '<br/>';
        }
        echo '<br />';
    }
}
?>


위의 코드의 경우에는 이중 배열이므로 간단하게

foreach ($R as $row) {
    $userID= $row['userID'];
    $sunriseTime = $row['sunriseTime'];
}
로 원하는 데이터를 뽑아서 처리하면 된다.



JSON 데이터 형식이 다음과 같을 때

 { "Sample_Data" :
  [{
    "userID": 1383458400,
    "icon": "partly-cloudy-day",
    "sunriseTime": 1383491266,
    "sunsetTime": 1383523844,
    "temperatureMin": -3.46,
    "temperatureMinTime": 1383544800,
    "temperatureMax": -1.12,
    "temperatureMaxTime": 1383458400
    },
    {
    "userID": 1383458400,
    "icon": "partly-cloudy-day",
    "sunriseTime": 1383491266,
    "sunsetTime": 1383523844,
    "temperatureMin": -3.46,
    "temperatureMinTime": 1383544800,
    "temperatureMax": -1.12,
    "temperatureMaxTime": 1383458400
  }]
}


foreach ($R['Sample_Data'] as $row) {
    echo '<pre>'; print_r($row);echo '</pre>';
    echo $userID= $row['userID'].'<br />';
    echo $sunriseTime = $row['sunriseTime'].'<br />';
}


로 처리하면 해결된다.

'Web 프로그램 > JSON, 파싱 다루기' 카테고리의 다른 글

jQuery 파싱 예제  (0) 2018.12.16
PHP json_encode  (0) 2018.06.19
네이버 동네 날씨정보 파싱  (0) 2017.12.24
기상청 지역 날씨정보 파싱  (0) 2017.12.23
Parse JSON with PHP (JSON 파싱)  (2) 2017.11.27
블로그 이미지

Link2Me

,