728x90

File Upload 공격

설명
악의적인 스크립트 파일을 웹 서버에 업로드하여 접근할 경우 웹 서버 사용자 권한으로 실행이 되는 취약점이다.
 발생원인 업로드할 파일이 안전한지 검사하지 않아 발생한다.

파일이 업로드되는 페이지(게시판, SNS 등)에 악성파일(웹셀)을 업로드가 가능한 경우에 발생한다.
 위험성 환경마다 권한이 다를 수 있지만, 서버를 직접 컨트롤할 수 있어 서비스에 치명적인 영향을 줄 수 있다.
 대응 ㅇ업로드되는 파일의 크기, 개수, 종류를 제한한다.
ㅇ업로드한 파일을 외부에서 접근할 수 없는 경로에 저장한다.
ㅇ업로드한 파일의 저장 경로와 파일명을 외부에서 알 수 없도록 한다.

ㅇ실제 저장되는 파일명은 난수를 이용해 유추 불가능하도록 생성하여

   외부로부터 직접적인 접근 불가능하게 구현한다.

ㅇ업로드한 파일에 대한 웹서버의 실행권한을 제거하고 저장한다.

 

 

PHP File Upload 공격이 가능하려면?
1) php 파일을 서버에 업로드할 수 있어야 한다.
2) 웹 브라우저를 통해서 접근이 가능해야 한다.

 

이를 방지하려면

. 업로드되는 파일의 확장자를 검사하여 허락된 타입만 저장한다.

. File Upload 디렉토리를 Web root 디렉토리를 벗어난 위치에 설정한다.

. 해커가 파일을 찾을 수 없도록 업로드 파일의 이름과 확장자를 난수화하여 저장한다.

. 저장되는 파일은 실행권한이 없도록 권한 변경하여 저장한다.

. 실제 저장되는 파일명과 View로 보여주는 파일명을 다르게 한다.

  DB에 보여줄 파일명과 실제 저장된 파일명을 모두 저장하고, View에는 업로드시의 파일명으로 보여주고

  다운로드를 누르면 실제 파일명을 View 파일명으로 변경하여 저장되도록 구현한다.

. 특수문자가 포함된 경우 업로드를 막는다.

 

'Web 프로그램 > Web Security' 카테고리의 다른 글

HTML Purifier 사용법 예시  (0) 2023.02.28
remote IP address  (0) 2021.03.24
파일 다운로드 공격 방지  (0) 2019.07.05
Cross-Site Scripting (XSS) 방지  (0) 2019.06.20
SQL Injection 공격 방지  (1) 2019.06.19
블로그 이미지

Link2Me

,
728x90

Cross-Site Scripting (XSS)

설명

공격자가 웹 페이지에 악성 스크립트를 삽입할 수 있는 취약점이다.

사용자가 입력한 정보를 출력할 때 스크립트가 실행되도록 하는 공격기법이다.

다른 사이트로 어떤 정보를 전송하는 행위가 주로 일어나기 때문에 사이트간 스크립팅이라는 이름을 가지고 있다.

 발생원인

웹 페이지에서 사용자로부터 입력받은 값을 제대로 검사하지 않고 사용할 경우 발생한다.

 위험성

ㅇ사용자의 쿠키 정보 노출로 세션 하이재킹과 같은 공격이 실행될 수 있다.

ㅇ사용자를 피싱 사이트로 접속하게 만들어 사용자의 중요 정보를 탈취할 수 있다.

ㅇ클라이언트(브라우저)에서 악성코드가 실행되어 사용자 PC를 좀비화할 수 있다.

 대응

입력값에 대해 정규식을 이용하여 정확하게 허용되는 패턴의 데이터만 입력되도록 한다.
ㅇ서버로 들어오는 모든 요청에 대해 XSS 필터를 적용하여 안전한 값만 전달되어 사용되도록 한다.
ㅇ출력값에 대해 HTML 인코딩을 적용하여 스크립트가 동작되지 않도록 한다.


공격자가 의도적으로 브라우저에서 실행될 수 있는 악성 스크립트를 웹 서버에 입력 또는 이것을 출력 시 위험한 문자를 중성화시키지 않고 처리하는 애플리케이션의 개발 과정에서 발생한다.
XSS는 일반적으로 자바스크립트에서 발생하지만, VB 스크립트, ActiveX 등 클라이언트에서 실행되는 동적 데이터를 생성하는 모든 언어에서 발생이 가능하다.

가장 일반적인 방법은 게시판 같은 곳에 HTML 문서에 <script>를 이용하여 이 스크립트 태그 안에 악성 스크립트를 저장하는 방식이다.
즉 텍스트만 표시되도록 설계된 어떤 게시판에 <script> “악성 스크립트” </script>과 같은 태그를 포함한다.

웹해킹의 기본은 웹페이지 소스보기부터 출발한다.
애초에 설계때부터 추후에 등장할 지 모를 버그 등 위험성을 모두 고려하여 설계하는것이 시큐어코딩이다.
XSS 취약점을 근본적으로 제거하기 위해서는 스크립트 등 해킹에 사용될 수 있는 코딩에 사용되는 입력 및 출력 값에 대해서 검증하고 무효화시켜야 한다.
입력 값에 대한 유효성 검사는 데이터가 입력되기 전에 가능하면, 입력 데이터에 대한 길이, 문자, 형식 및 사업적 규칙 유효성을 검사해야 한다.
출력 값을 무효화하기 위해서는 XSS 공격은 기본적으로 <script> 태그를 사용하기 때문에 XSS 공격을 차단하기 위해 태그 문자(<, >) 등 위험한 문자 입력 시 문자 참조(HTML entity)로 필터링하고, 서버에서 브라우저로 전송 시 문자를 인코딩하는 것이다.
예를 들어, 문자 “<”는 동일한 의미의 HTML “&lt;”로 변경한다. HTML 엔터티는 대부분의 브라우저에서 특수한 의미를 가지지 않으며, 단순한 문자로 처리된다.
이렇게 인코딩하면 사용자는 <script>가 <script>로 보이지만 HTML 문서에서는 &lt;script&gt;로 나타나서 브라우저에서 일반 문자로 인식하고 스크립트로 해석되어 실행되지는 않는다.

htmlspecialchars($str,ENT_QUOTES,'UTF-8');
이 함수는 문자열에서 특정한 특수 문자를 HTML 엔티티로 변환한다.
& (앰퍼샌드) 는 &amp; 로 바꾼다.
" (큰따옴표) 는 &quot; 로 바꾼다.
' (작은따옴표) 는 &#039; 로 바꾼다.
< (~ 보다 작다는 기호) 는 &lt; 로 바꾼다.
> (~ 보다 크다는 기호) 는 &gt; 로 바꾼다.
htmlspecialchars 함수를 사용하면 악성 사용자로부터 XSS 공격을 방지 할 수 있다.

게시판의 게시글, 댓글, 쪽지 등에 자바스크립트 코드가 들어가지 않도록 필터링해야 한다.
HTTP  헤더에 문자 인코딩을 지정하지 않는 페이지는 XSS 공격의 위험성 있으므로 반드시 문자 인코딩을 설정해야 한다. 모든 파라미터는 변조할 수 있다는 전제하에 Web 애플리케이션을 작성해야 한다.

아래 코드는 htmlspecialchars 함수, strip_tags 함수를 이용하여 사용할 경우와 사용하지 않을 경우의 XSS 공격이라는 가정하의 예제인 셈이다.

<?php
error_reporting(E_ALL);
ini_set("display_errors", 1);
ini_set('default_charset','UTF-8');

$str = "<h2> 'PHP' morden programing.</h2>";
// HTML 태그 속성 반영한 출력
echo $str.'<br/>';

$str2 = htmlspecialchars($str,ENT_QUOTES,'UTF-8'); // HTML 태그에 사용되는 특수문자를 HTML 엔티티로 변환
// HTML 태그 속성 제거된 문자열 출력
echo $str2.'<br/>';

// 정규식으로 HTML 태그 제거
$str3 = preg_replace("/(<([^>]+)>)/i","",$str);
echo $str3.'<br/>';
// ? : 0 또는 하나     {0,1} 와 같다.
// + : 1개 이상       {1,} 와 같다.
// * : 0 개 이상      {0,} 과 같다.
// [] : []안에 있는 문자열 중 한문자
// {} : {} 바로 앞에 있는 문자열(또는 문자)의 개수
// () : ()안에 있는 문자들의 그룹화
// | : OR 연산자 기능

// HTML 비 허용시
$str4 = strip_tags($str); // HTML 태그가 포함될 경우 HTML 태그 자체를 삭제
echo $str4.'<br/>';


$str ="<script>alert('xss 공격!');</script>";
$str1 = strip_tags($str); // HTML 태그가 포함될 경우 HTML 태그 자체를 삭제
echo $str1.'<br/>';
$str2 = preg_replace("/(<([^>]+)>)/i","",$str);
echo $str2.'<br/>';

$str ='<a href="javascript:alert(\'xss 공격!\')">XSS</a>';
$str1 = strip_tags($str); // HTML 태그가 포함될 경우 HTML 태그 자체를 삭제
echo $str1.'<br/>';


$str ='<img src="#" onerror="alert(\'XSS 공격!\');">';
$str = strip_tags($str); // HTML 태그가 포함될 경우 HTML 태그 자체를 삭제
echo $str.'<br/>';

$str ='<IFRAME src="&#106;&#097;&#118;&#097;&#115;&#099;&#114;&#105;&#112;&#116;&#058;
&#097;&#108;&#101;&#114;&#116;&#040;&#039;&#120;&#115;&#115;&#032;&#234;&#179;&#181;
&#234;&#178;&#169;&#033;&#039;&#041;&#059;" width="0" height="0" frameborder="0"></IFRAME>';
$str = strip_tags($str); // HTML 태그가 포함될 경우 HTML 태그 자체를 삭제
echo $str.'<br/>';

?>


결과 화면


Text to ASCII Converter : http://www.unit-conversion.info/texttools/ascii/#data


정규표현식으로 HTML 태그를 제거하는 것은 Javascript 함수로 만들어서 확인할 수 있다.


<iframe> 태그 삽입, <object> 태그 삽입, Web 브라우저 URL Spoofing(변조), 악성코드 은닉 등 다양한 XSS 공격 기법이 가능하므로 필터링에 각별한 고려가 필요하다.


국내에서는 게시판이 대부분 WYSIWYG 에디터를 이용하므로 선택적 필터링을 잘 해야 한다.


HTML Purifier는 해외의 보안 전문가들로부터 철저하게 검증받은 필터링 라이브러리이다.
whitelisting 기법을 사용하고, 잘못된 태그도 확실하게 걸러내준다.
처리속도가 빠르지 않기 때문에 HTML이 필요한 경우에만 사용해야 한다.


참고하면 도움되는 게시글
ㅇ 안경잡이 개발자 XSS 공격 강의 : https://www.youtube.com/watch?v=DoN7bkdQBXU
ㅇ [웹취약점] XSS 크로스 사이트 스크립팅 http://coashanee5.blogspot.com/2017/02/xss.html
ㅇ KISA(한국인터넷진흥원) XSS 공격 종류 및 대응 방법 : http://www.kisa.or.kr/uploadfile/201312/201312161355109566.pdf


'Web 프로그램 > Web Security' 카테고리의 다른 글

HTML Purifier 사용법 예시  (0) 2023.02.28
remote IP address  (0) 2021.03.24
파일 다운로드 공격 방지  (0) 2019.07.05
파일 업로드 공격 방지  (0) 2019.06.21
SQL Injection 공격 방지  (1) 2019.06.19
블로그 이미지

Link2Me

,
728x90
SQL Injection

설명

웹 애플리케이션에서 사용되는 SQL 구문에 공격자가 임의의 구문을 주입(Injection) 하여 내부 데이터베이스의 데이터를 유출, 변조할 수 있는 취약점이다.

 발생원인

웹 페이지에서 사용자로부터 입력받은 값을 제대로 검사하지 않고 그대로 데이터 질의어로 사용할 경우 발생한다.

 위험성

데이터 유출, 변조 외에도 서버에 파일을 쓰거나 읽을 수 있고, 직접 명령 실행도 가능할 수 있기 때문에 위험도가 높은 취약점이다.


SQL 인젝션은 데이터베이스를 사용하는 모든 언어에서 일어날 수 있다.
공격자는 SQL 인젝션 취약점을 사용하여 데이터베이스 내의 모든 정보를 추출할 수 있다.
보통 userID 와 passwd 를 입력하는 로그인 창에서 발생할 수 있는 공격 유형이다.


select * from members where userID='$userID' and passwd ='$passwd';

여기서 userID 에 입력하는 정보를 ' or 1=1-- 라고 입력하는 것이 받아들여지면 어떤 결과가 초래될까?
phpMyAdmin 상에서 직접 테스트 해보라.


SELECT * FROM members WHERE userID='' or 1=1 --' and passwd ='abc123';

과같이 -- 주석 뒷부분은 무용지물이 되고 조건문은 무조건 참이 되어 모든 결과를 반환하게 된다.


select * from members where userID='a' and passwd ='password' OR 1='1';
로 입력해도 무조건 참이 되어 모든 결과를 반환하게 된다.


더 위험한 것은 테이블 자체를 삭제시켜 버릴 수가 있다는 점이다.

SELECT * FROM members WHERE userID = 'a';DROP TABLE members;

$userID = "admin";
$password = "password' OR 1='1 --";
$sql = "SELECT * FROM members WHERE userID='{$userID}' AND passwd='{$password}'";
echo 'NonFiltering : '.$sql.'<br/>';
$sql=preg_replace("/\s{1,}1\=(.*)+/","",$sql); // 공백이후 1=1이 있을 경우 제거
$sql=preg_replace("/\-{2,}/","",$sql); // 주석 제거
$sql=preg_replace("/\s{1,}(or|and|null|where|limit)/i"," ",$sql); // 공백이후 or, and 등이 있을 경우 제거
echo 'Filtering : '.$sql.'<br/>'; 

공백이후 and 를 체크하지 않고 그냥 체크하면 userID 중에 hand 가 들어간 것도 필터링을 해서 문제가 될 수 있다.


그럼 여기서 안전하게 코딩하기 위한 포인트는 무엇인가?

① 필터링 메소드를 만들어서 악의적인 코드를 무조건 걸러내도록 한다.

    필터링 해야 할 특수문자 : '(홀따옴표) ;(콜론), --(주석), 공백 등

    https://link2me.tistory.com/1489 정규표현식 연습 참조하면 정규표현식 이해에 도움된다.

    정규표현식을 활용한 함수 만들기

    function SQLFiltering($str){
        // 해킹 공격을 대비하기 위한 코드
        $str=preg_replace("/\s{1,}1\=(.*)+/","",$str); // 공백이후 1=1이 있을 경우 제거
        $str=preg_replace("/\s{1,}(or|and|null|where|limit)/i"," ",$str); // 공백이후 or, and 등이 있을 경우 제거
        $str = preg_replace("/[\s\t\'\;\=]+/","", $str); // 공백이나 탭 제거, 특수문자 제거
        return $str;
    }

②  안전한 코딩은 prepared Statement 와 바인딩 변수를 이용하도록 권고하고 있다.

      Prepared Statement 를 쓰면 SQL 인젝션 공격이 불가능할 수 밖에 없다.

      바인딩 데이터는 SQL 문법이 아닌 내부의 인터프리터나 컴파일 언어로 처리하기 때문에

      문법적인 의미를 가질 수 없다.

$stmt = $mysqli->prepare("SELECT * FROM members WHERE userID= ? AND passwd = ?");
$stmt->bind_param("ss", $_POST['userID'], $_POST['passwd']);
$stmt->execute();
//fetching result would go here, but will be covered later
$stmt->close();


참고하면 도움이 될 게시글

ㅇ PHP Prepared Statements https://www.w3schools.com/php/php_mysql_prepared_statements.asp
ㅇ PHP Legacy 함수를 PDO 방식 함수로 변환하기 https://link2me.tistory.com/1636

'Web 프로그램 > Web Security' 카테고리의 다른 글

HTML Purifier 사용법 예시  (0) 2023.02.28
remote IP address  (0) 2021.03.24
파일 다운로드 공격 방지  (0) 2019.07.05
파일 업로드 공격 방지  (0) 2019.06.21
Cross-Site Scripting (XSS) 방지  (0) 2019.06.20
블로그 이미지

Link2Me

,
728x90

스마트폰 앱을 다운로드하는 코드이다.

파일 다운로드하는 코드에 스마트폰 장치를 인식하는 코드와 접목하여 어플을 다운로드 하도록 한다.

파일을 직접 접속하는 경우에는 동작하지 않도록 $_SERVER['HTTP_REFERER'] 기능을 사용하여 체크한다.

좀 더 정교하게 보완하는 것도 좋을 것이다.


<?php
require_once "deviceChk.php"; // 접속 Device 체크
if($mtype > 2) { // PC에서 접속하면 아래 코드 미 실행
    echo 'no access';
    exit;
}
require_once 'config/config.php';
if($mtype==2){
    $url='<a href="appDown.php">ABC <br/>어플 다운로드</a>';
}
?>

<!DOCTYPE html>
<html lang="ko">
<head>
  <title>ABC App 다운로드</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
    <div class="row align-items-center justify-content-center" style="height:60vh;">
        <div>
            <h4><?php echo $url;?></h4>
        </div>
    </div>
</div>
</body>
</html>

=== appDown.php ===

<?php
if(!isset($_SERVER['HTTP_REFERER'])) {
    echo 'direct access denied!';
    exit;
}

require_once "deviceChk.php"; // 접속 Device 체크
if($mtype==2){ // Android 폰 이면
    require_once 'config/config.php';
    $filepath = './files/'.$hostAbb.'.apk';
    $filesize = filesize($filepath);
    $path_parts = pathinfo($filepath);
    $filename = $path_parts['basename'];
    $extension = $path_parts['extension'];

    header("Pragma: public");
    header("Expires: 0");
    header("Content-Type: application/octet-stream");
    header("Content-Disposition: attachment; filename='$filename'");
    header("Content-Transfer-Encoding: binary");
    header("Content-Length: $filesize");
    header("Cache-Control: cache, must-revalidate");
    header("Pragma: no-cache");

    ob_clean();
    flush();
    //readfile($filepath); //Read and stream the file
    ///*
    $fp = fopen($filepath, "rb"); //rb 읽기전용 바이러니 타입
    if(!fpassthru($fp)) {
        fclose($fp);
    }
    //*/
}
?>


위 다운로드 코드에 약간 문제가 있는지 다운로드가 제대로 되지 않는 폰이 있어서 코드를 다시 수정했다.

아래 코드는 정상적으로 잘 다운로드 된다. 여러 폰에서 테스트를 했다. (update 2019.6.18)

<?php
if(!isset($_SERVER['HTTP_REFERER'])) {
    echo 'direct access denied!';
    exit;
}

require_once "deviceChk.php"; // 접속 Device 체크
if($mtype==2){ // Android 폰 이면
    require_once 'config/config.php';
    $filepath = './files/'.$hostAbb.'.apk';
    $filesize = filesize($filepath);
    $path_parts = pathinfo($filepath);
    $filename = $path_parts['basename'];
    $extension = $path_parts['extension'];

   header('Content-Description: File Transfer');
   header('Content-Type: application/octet-stream');
   header('Content-Disposition: attachment; filename="' . $filename . '"');
   header("Content-Transfer-Encoding: Binary");
   header('Expires: 0');
   header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
   header('Pragma: public');
   header("Content-length: ".filesize($filepath));
   ob_clean();
   flush();
   readfile($filepath);
}
?>


블로그 이미지

Link2Me

,
728x90

Visual Studio Code 는 거의 모든 언어를 지원한다.


Visual Studio Code에서 PHP 코딩에 유용한 Extension 을 설치해봤다.

설치한 extension 은 사용자 폴더 하위폴더에 .vscode 로 만들어져 있더라.

설치한 것이 마음에 안들면 이 폴더를 통째로 날리면 다시 설치할 수 있다. 개별로 지우는 것은 안해봤다.

이것 저것 Extension 을 설치해보니 사용하기에 좀 편해진 거 같다.

EditPlus 를 이용하여 날 코딩하던 걸 이걸로 변경해보면서 기능의 편리함을 체험해보려고 한다.

Local Web 연동처리는 Aptana Studio 도 같이 설치해서 활용하는 것이 좋을 거 같다.


Extension

 설 명

 PHP IntelliSense

 PHP를 위한 고급 자동완성 및 리팩터링을 지원한다.

 "PHP 실행파일이 설정되지 않았기때문에 유효성 검사를 할 수 없습니다."

 File > Preferences > Settings

 "php.validate.executablePath" : "C:/설치한 폴더/php.exe" 로 변경

 ※ php 버전은 7.X 버전이 설치된 폴더를 설정해준다.

 PHP 코드를 타이핑할때마다 문법을 체크해준다.

 PHP Formatter

 phpfmt - PHP formatter 를 설치한다.
 코드 정렬 단축키 : Shift + Alt + F (Windows), Shift + Option + F (MAC)
 HTML 과 혼용되어 있는 것은 정렬해주지 못하더라.

 Format HTML in PHP

 PHP 와 HTML 이 혼용된 파일에서 HTML 영역을 선택하여 마우스 우클릭하여

 코드를 깔끔하게 정렬할 수 있다.

 PHP Intelephense

 문서, 작업 영역, 기본 제공 생성자, 메서드 및 함수에 대한 자세한 서명 도움
 오류 허용 구문 분석기를 통해 열린 파일에 대한 여러 구문 분석 오류 진단

 Auto Close Tag

 HTML 닫기 태그를 자동으로 추가한다.

 Auto Rename Tag

 HTML 태그의 이름을 자동으로 바꾼다.

 ESLint

 자바스크립트 문법검사를 해준다.

 IntelliSense for CSS class names in HTML

 Bootstarp 라이브러리를 통해 개발할때 정말 유용한 플러그인

 작업 공간에 있는 정의를 기반으로 HTML Class 속성에 대한 CSS Class 이름을 자동 완성해 준다.

 jQuery Code Snippets

 

 Beautify

 




블로그 이미지

Link2Me

,
728x90

Visual Studio Code 를 설치해서 PHP Debuging 이 좀 더 편리한지 알아보는 중이다.


1. 한글 Pack 설치

   - 설치하면 메뉴가 한글로 나온다.

  

2. PHP Pack 설치

   https://code.visualstudio.com/docs/languages/php 에 기본 설치해야 할 PHP Extension 정보가 나온다.


3. APM(Apache + PHP + MySQL) 설치

   - 윈도우 기반에서 설치할 수 있는 툴은 autoset9, autoset10, xampp 가 있다.

   - 구글 검색하면 나온다.

   - 테스트를 해보니까 autoset9 은 제대로 지원을 못한다. autoset10 또는 xampp 를 설치하시라.


4. PHP 설치경로 설정

   - VS Code 프로그램에서 파일 - 기본설정 - 설정을 누른다.



php.validate.executablePath 를 추가하고 저장한다.


5. PHP 디버거 설정

    - https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug 에서

    - DEBUG 를 설치한다.

    - 커서를 아래로 내려서 Installation 을 읽고 Download 를 누르면

      https://xdebug.org/download.php 에서 APM 설치한 PHP 버전에 맞는 버전을 다운로드 받는다.



    - 다운로드한 php_xdebug-2.7.2-7.2-vc15-x86_64.dll 파일을 PHP 파일이 설치된 하위 폴더 ext 에 옮긴다.


https://xdebug.org/wizard.php 를 브라우저에서 열고 phpinfo(); 내용을 CTRL + A를 눌러 복사한 다음 붙여넣기를 한다.

지원하지 않는다고 나온다.

      하지만 https://xdebug.org/docs/install#configure-php 에서 보면 오래된 PHP 버전도 지원한다고 나온다.


Autoset10 으로 설정하고 phpinfo() 내용을 CTRL + C로 복사하여 붙여넣기 했더니 아래와 같이 보여준다.


    - php.ini 파일을 열어 아래와같이 수정한다.

[XDebug]

zend_extension="C:/AutoSet10/Server/bin/ext/php_xdebug-2.7.2-7.2-vc15-x86_64.dll"
xdebug.remote_enable = 1
xdebug.remote_autostart = 1


    - 이제 APM(autoset10)을 재시작한다.


6. VS Code를 재시작하여 디버깅을 해본다.

   구글링을 해보니 PHP 코드 로직 테스트 등에는 결과를 알 수 있어 좋을 거 같다.


7. Web 서버와 연동 기능

   - PHP Server를 설치하니까 Web 브라우저로 내용을 보여주기는 한다.

   - Aptana Studio 와 다르게 Local 에 설치된 Web 서버와 직접 연결하여 보여주는 기능이 없는 것인지

     못찾은 것인지 모르겠다..






블로그 이미지

Link2Me

,
728x90

PHP 절차지형 방식으로 MySQL 을 연동한 코드를 PDO 방식으로 변환할 때 필요한 정보를 적어둔다.

PDO 는 CUBRID, MS SQL Server, Firebird/Interbase, IBM, Informix, MySQL, Oracle, ODBC and DB2, PostgreSQL, SQLite, 4D 등 12가지 DB를 지원한다.

 

PHP 절차지향
PDO
 mysql_fetch_assoc($result)  $stmt->fetch(PDO::FETCH_ASSOC)
 mysql_num_rows($result)  $stmt->rowCount()
 while ($row = mysql_fetch_assoc($result)) {}  foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {}

 

SELECT 변환 예제

 $sql="select count(*), access from members where userID='".$userID."'";
 $result=mysqli_query($db,$sql);
 if($row=mysqli_fetch_row($result)){
 $sql="select count(*), access from members where userID=?";
 $params = array($userID);
 $stmt = $this->db->prepare($sql);
 $stmt->execute($params);
 if($row = $stmt->fetch()){

 

Update 변환 예제

 $sql = "UPDATE rb_accessLog SET hit=hit+1 Where ipaddr='".$access_ip."' and LogID='".$userID."'";
 $result=mysqli_query($db,$sql);
 $sql = "UPDATE rb_accessLog SET hit=hit+1 Where ipaddr=? and LogID=?";
 try {
    $this->db->beginTransaction();
    $params = array($access_ip,$userID);
    $stmt = $this->db->prepare($sql);
    $stmt->execute($params);
    $this->db->commit();
 } catch (PDOException $pex) {
    $this->db->rollBack();
    echo "에러 : " . $pex -> getMessage();
 }

 

Insert 변환 예제

 $sql ="INSERT INTO bbs_imgpath (uid,relateduid,imgpath) values(NULL,";
 $sql.="'".$uid."', '".$v."')";
 mysqli_query($this->db,$sql);
 $key = "uid,relateduid,imgpath";
 $val = "NULL,$uid,$v";
 $this->getDbInsert('bbs_imgpath', $key, $val);

 

getDbInsert 함수는 아래와 같이 만들었다.

<?php
 function getDbInsert($table$key$val) {
        try {
            $params = explode(','$val); // 문자열을 분리하여 배열로 만듬
            $this->db->beginTransaction();
            $sql = "insert into " . $table . " (" . $key . ") values(" . $this->recur_quot(count($params)) . ")";
            $stmt = $this->db->prepare($sql);
            $status = $stmt->execute($params); // $params 는 배열 값
            if ($status) {
                $this->db->commit();
                return 1;
            } else {
                return 0;
            }
        } catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : " . $pex->getMessage();
        }
    }
 
    public function recur_quot($cnt) {
        $R = array();
        for ($i = 0$i < $cnt$i++) {
            array_push($R"?");
        }
        return implode(","$R); // 배열을 문자열로
    } 
 
?
 

 

Delete 변환 예제

 $sql = "DELETE FROM bbs_imgpath where imgpath='".$v."' and relateduid=".$uid;
 mysqli_query($this->db,$sql);
 
 $where = "imgpath=? and relateduid=?";
 $params = array($v,$uid);
 $this->getDbDelete('bbs_imgpath', $where, $params);

 

getDbDelete 함수는 아래와 같이 만들었다.

 
 
<?php
    public function getDbDelete($table$where$params) {
        if ($this->isDataExisted($table$where$params)) {
            try {
                $this->db->beginTransaction();
                $sql = "delete from " . $table . ($where ? ' where ' . $where : '');
                $stmt = $this->db->prepare($sql);
                $status = $stmt->execute($params);
                if ($status) {// action 실행여부에 대한 결과이지 실제 데이터 삭제와는 무관하네.
                    $this->db->commit();
                    return 1// 삭제 성공
                } else {
                    return 0// 삭제 실패
                }
            } catch (PDOException $pex) {
                $this->db->rollBack();
                echo "에러 : " . $pex->getMessage();
            }
        } else {
            return 0// 삭제할 데이터가 없음
        }
 
    }
 
    // 삭제할 데이터의 존재 유무 파악
    public function isDataExisted($table$where$params) {
        $sql = 'select * from ' . $table . ($where ? ' where ' . $where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        if ($row = $stmt->fetch()) {
            return $row;
        } else {
            return false;
        }
    }
 
?>
 

 

사용자 함수

편리하게 사용하기 위해 만든 함수는 아래와 같이 변경하면 된다.

다만, 검색조건을 입력시 SQL Injection 필터링 함수를 한번 거쳐서 where 조건문에서 해킹 방지를 해주는 것은 필수요소다.

    function getDbData($table,$where,$column) {
        global $db;
        $sql ='select '.$column.' from '.$table.($where?' where '. $where:'');
        //echo $sql;
        $result = mysqli_query($db,$sql);
        $row = mysqli_fetch_array($result);
        return $row;
    }
     // 검색조건에 일치하는 데이터 가져오기
    public function getDbData($table, $where, $column, $returntype = '') {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        if ($returntype == 1) {
            return $stmt->fetch(PDO::FETCH_ASSOC);
        } else {
            return $stmt->fetch();
        }
    }
     // 검색조건에 일치하는 데이터 가져오기
    public function getDbDataAll($table, $where, $column) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll();
    }

 

 

관련으로 만든 Class 대부분은 아래와 같다.

 
<?php
class DBDataClass {
    protected $db// 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.
 
    public function __construct() {
        $this->dbConnect();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }
 
    private function dbConnect() {
        require_once 'dbinfo.php';
        try {
            // SQL PDO 객체 생성
            $this->db = new PDO(_DSN, _DBUSER, _DBPASS);
            $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->db->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
        } catch(PDOException $ex) {
            die("오류 : " . $ex->getMessage());
        }
    }
 
    // 신규 자료 추가(ok)
    function getDbInsert($table$key$val) {
        try {
            $params = explode(','$val); // 문자열을 분리하여 배열로 만듬
            $this->db->beginTransaction();
            $sql = "insert into " . $table . " (" . $key . ") values(" . $this->recur_quot(count($params)) . ")";
            $stmt = $this->db->prepare($sql);
            $status = $stmt->execute($params); // $params 는 배열 값
            $this->db->commit();
            return 1;
        } catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : " . $pex->getMessage();
            return 0;
        }
    }
 
    public function recur_quot($cnt) {
        $R = array();
        for ($i = 0$i < $cnt$i++) {
            array_push($R"?");
        }
        return implode(","$R); // 배열을 문자열로
    }
 
    function getDbUpdate($table$set$params$where) {
        $sql = "update " . $table . " set " . $set . ($where ? ' where ' . $where : '');
        try {
            $this->db->beginTransaction();
            $stmt = $this->db->prepare($sql);
            $status = $stmt->execute($params);
            $this->db->commit();
            return 1;
        } catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : " . $pex->getMessage();
            return 0;
        }
    }
 
    public function getDbDelete($table$where$params) {
        if ($this->isDataExisted($table$where$params)) {
            try {
                $this->db->beginTransaction();
                $sql = "delete from " . $table . ($where ? ' where ' . $where : '');
                $stmt = $this->db->prepare($sql);
                $status = $stmt->execute($params);
 
                $this->db->commit();
 
                return 1// 삭제 성공
            } catch (PDOException $pex) {
                $this->db->rollBack();
                echo "에러 : " . $pex->getMessage();
            }
        } else {
            return 0// 삭제할 데이터가 없음
        }
 
    }
 
    // 삭제할 데이터의 존재 유무 파악
    public function isDataExisted($table$where$params) {
        $sql = 'select * from ' . $table . ($where ? ' where ' . $where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        if ($row = $stmt->fetch()) {
            return $row;
        } else {
            return false;
        }
    }
 
 
    // 검색조건에 일치하는 데이터 가져오기
    public function getDbData($table$where$column$returntype = '') {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        if ($returntype == 1) {
            return $stmt->fetch(PDO::FETCH_ASSOC);
        } else {
            return $stmt->fetch();
        }
    }
 
    // 검색조건에 일치하는 데이터 가져오기
    public function getDbDataAll($table$where$column) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll();
    }
 
    // DB Query result 함수
    function getDbresult($table,$where,$column) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' .$where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll();
    }
 
    // DB 레코드 총 수(ok)
    public function getDbRows($table$where) {
        $sql = 'select count(*) from ' . $table . ($where ? ' where ' . $where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        if ($row = $stmt->fetch()) {
            return $row[0];
        } else {
            return false;
        }
    }
}
?> 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

Web 사이트 개발을 하고 나서 취약점을 점검해볼 수 있는 TOOL 을 설치해봤다.

www.zaproxy.org/download/

에서 다운로드 한다.

 

 

지자체 사이트는 시험삼아 넣어봤더니 결과를 한참 늦게 피드백을 준다.

내 호스팅 사이트는 결과를 엄청 빠르게 보여준다.

보안 설정이 취약한 부분이 뭔지 결과를 경고 메시지로 보여준다.

 

Secure Coding 관련으로 공부를 좀 하고 경고 메시지가 없어지도록 해야겠다.

블로그 이미지

Link2Me

,
728x90

메일 발송 조건

리눅스 CentOS 6 에서 sendmail 을 설치하고 관련 설정을 한 후 발송 테스트하고 적어둔다.

아래 코드는 회원 정보와 연동한 메일 발송 코드만 발췌한 것이다.

PHP는 자체적으로 메일을 발송하는 기능은 없다.

mail()함수는 메일 발송 폼을 작성하여 SMTP 서버로 전송해주는 역할을 한다.


<?php
$to = $email;
$from = "webmaster@abc.com";
$nameFrom = "AppMaster";

$subject = "비밀번호 변경";
$message ="<table>";
$message.="<tr>";
$message.= "<td>비밀번호는 암호화되어 있으므로, 회원님의 이메일 주소로 임시 비밀번호를 발급해 드립니다.</td>";
$message.="</tr>";
$message.="<tr>";
$message.= "<td>임시 비밀번호로 로그인 후, 반드시 새로운 비밀번호를 설정해 주십시요.</td>";
$message.="</tr>";
$message.="<tr>";
$message.= "<td>임시 비밀번호는  {$newPW}  입니다.</td>";
$message.="</tr>";
$message.="<tr>";
$message.= "<td>Web 사이트로 <a href='http://www.abc.com'>바로 이동</a> 하기</td>";
$message.="</tr>";
$message.="<tr>";
$message.= "<td>본 메일은 <font color=red>발신 전용</font>이라 수신이 불가능합니다.</td>";
$message.="</tr>";
$message.="</table>";

$mailheaders = "Return-Path: $from\r\n";
$mailheaders.= "From: $nameFrom <$from>\r\n";
$mailheaders.= "Content-Type: text/html;charset=utf-8\r\n";
$mailheaders.= "MIME-Version: 1.0\r\n";

mail($to, $subject, $message, $mailheaders, $from);
?>

블로그 이미지

Link2Me

,
728x90

jQuery 와 PHP 를 조합하여 로그인 실패시 접속시도 5회가 넘으면 접속을 차단하고, 관리자한테 문의하여 비밀번호를 초기화 요청할 수도 있고, 5분(지정시간) 경과후 다시 로그인을 시도할 수 있도록 하는 코드를 구현했다.


jQuery 함수만 적어둔다.


$('#login_submit').on('click',function(e){
    e.preventDefault();
    var userID = $('#loginID');
    var userPW = $('#loginPW');

    if(userID.val() ==''){
        alert('아이디를 입력하세요');
        userID.focus();
        return false;
    }

    if(userPW.val() ==''){
        alert('비밀번호를 입력하세요');
        userPW.focus();
        return false;
    }

    if(userPW.val().search(/\s/) != -1){
        alert("비밀번호는 공백없이 입력해주세요.");
        return false;
    }

    $.ajax({
        url:loginpath+'loginChk.php',
        type: 'POST',
        data: {
            userID:$("#loginID").val(),
            password:$('#loginPW').val()
        },
        dataType:'json',
        success:function(msg){
            switch(msg.result){
                case 0:
                    alert('입력한 정보를 다시 한번 확인하세요.');
                    break;
                case 1:
                case 2:
                    alert('로그인 '+msg.result+'회 실패했습니다');
                    break;
                case 3:
                case 4:
                case 5:
                    alert('로그인 '+msg.result+'회 실패했습니다\n 5회 틀릴 경우 접속이 차단됩니다.');
                    break;
                case 10:
                    alert('관리자가 로그인을 차단했습니다.');
                    break;
                case 11:
                    location.replace('index.php'); // 화면 갱신
                    break;
                case 12:
                    alert('가입 승인대기 중입니다.');
                    break;
                case 13:
                    alert('로그인 횟수 초과로 계정이 잠겼습니다.\n5분 후에 다시 로그인 하세요.');
                    break;
                case 21:
                    location.replace('index.php'); // 화면 갱신
                    break;
                default:
                    alert('다시 한번 확인하세요\n 에러메시지 : '+msg.result);
                    break;
            }
        },
        error: function(jqXHR, textStatus, errorThrown){
            alert("arjax error : " + textStatus + "\n" + errorThrown);
        }
    });

});
 


PHP 핵심 함수 코드이고 나머지 함수는 직접 구현하면 된다.

function LoginSuccessChk($userID,$password){ // Web 접속
    global $db;
    if(empty($userID) || empty($password)){
        return 0;
    } else {
        if($this->isUserExisted($userID) ==1){ // 가입된 userID가 존재하면
            if($this->PasswordChk($userID,$password) == 0){ // 패스워드 불일치
                // 1. access_failed_count 가 5 이상이면 access 를 3 으로 세팅한다.
                $rs = $this->updateAccountLock($userID);
                if($rs == 1){
                    // 3. DB에 저장된 시간과 현재 접속한 시간이 5분이 경과되면 자등으로 access=1 로 설정 변경한다.
                    $gaptime = $this->TimeDiffChk($userID);
                    if($gaptime == 1){
                        $this->clearAccountLock($userID);
                        return 0;
                    } else {
                        // 4. 5분이 경과되지 않았으면 메시지를 팝업한다.
                        return 13;
                    }
                } else {
                    // 2. access_failed_count 를 1 을 증가시킨다.
                    $this->plusLoginFailCount($userID);
                    return $this->LoginFailCountChk($userID); // 로그인 실패 횟수 반환
                }
            } else { // 패스워드 일치
                $rs = $this->AccessStatusChk($userID);
                if($rs == 11) { // 로그인 허용
                    // 1. access_failed_count 를 0 으로 초기화한다.
                    $this->clearLoginFailCount($userID);
                    // 2. access 를 1로 초기화한다.
                    $this->clearAccountLock($userID);
                    // 3. 접속일자 및 시간을 수정한다.
                    $this->AccessLog($userID,'');
                    return 11;
                } else if($rs == 13){
                    // DB에 저장된 시간과 현재 접속한 시간이 5분이 경과되면 자등으로 access=1 로 설정 변경한다.
                    $gaptime = $this->TimeDiffChk($userID);
                    if($gaptime == 1) {
                        $this->clearAccountLock($userID);
                        $this->AccessLog($userID,'');
                        return 11; // 로그인 허용
                    } else {
                        return 13; // 로그인 차단
                    }
                } else {
                    return $rs; // 10:불허, 12:승인대기, 13:차단
                }
            }
        } else {
            return 0; // 가입된 userID가 없으면
        }
    }
}


블로그 이미지

Link2Me

,
728x90

PHP 날짜와 시간 차이에 대한 테스트 결과다.

그냥 복사해서 테스트해보면 결과를 확인 할 수 있다.

 

<?php
date_default_timezone_set('Asia/Seoul');
$start = '2019-03-23';
$end = '2019-03-25';
$gap = DateDiff($start,$end);
echo '<pre>';print_r($gap);echo '</pre>';

echo DateDisplay( $gap->y, $gap->m, $gap->d).'<br />';
echo $gap->days.'일<br /><br />';

$curTime = date("Y-m-d H:i:s"); // 현재 날짜 및 시간
$access_date = "2019-03-26 09:56:22";

$date1 = strtotime($access_date);
$date2 = strtotime($curTime);
$gaptime = $date2 - $date1;

echo $gaptime.'<br />'; // 초단위 시간으로 반환
echo time_diff($access_date,$curTime).'<br />'; // 초단위 시간으로 반환

$seconds = $gaptime % 60;
$minutes = floor(($gaptime % 3600) / 60);
$hours = floor($gaptime / 3600);
echo $hours.":".$minutes.":".$seconds.'<br />';

$gap = DateDiff($access_date,$curTime);
echo $gap->format('%h:%i:%s').'<br /><br />';

echo '<pre>';print_r($gap);echo '</pre>';
echo DateDisplay( $gap->y, $gap->m, $gap->d).'<br />';

function DateDiff($startDate,$endDate){
    $date1 = new DateTime($startDate);
    $date2 = new DateTime($endDate);

    $gap = $date1->diff($date2);
    return $gap;
}

function DateDisplay($y,$m,$d){
    $gap ='Diff : ';
    if($y > 0) $gap.=$y."년 ";
    if($m > 0) $gap.=$m."월 ";
    $gap.=$d."일 ";
    return $gap;
}

function time_diff($datetime1, $datetime2) {
  return date('U',strtotime($datetime2))-date('U',strtotime($datetime1));
}
?>

 

 

로그인 시도 실패후 5분이 경과되면 다시 로그인을 할 수 있도록 만들기 위한 함수를 만들어 보자.

5분 = 300초 이므로

 

DB 테이블 칼럼에 저장된 시간을 $access_date = "2019-03-26 09:56:22";
이라 하고

현재 시간을 $curTime = date("Y-m-d H:i:s"); // 현재 날짜 및 시간

$gap = time_diff($access_date,$curTime); // 초단위 시간으로 반환

if($gap > 300){

   로그인 차단 설정을 허용으로 변경

} else {

   로그인 차단 메시지 팝업

}

블로그 이미지

Link2Me

,
728x90

MySQL DB 데이터를 csv 파일 형태로 저장하는 예제코드다.


<?php
$db['host'] = "localhost";
$db['name'] = "db명";
$db['user'] = "사용자id";
$db['pass'] = "비밀번호";
$db['port'] = "3306";
?>

<?php
include_once 'dbinfo.php';
$db = isConnectDb($db);

function isConnectDb($db)
{
    $conn = mysqli_connect($db['host'],$db['user'],$db['pass'],$db['name'],$db['port']);
    mysqli_set_charset($conn, "utf8"); 
    if (mysqli_connect_errno()) {
        echo "Failed to connect to MySQL: " . mysqli_connect_error();
        exit;
    } else {
        return $conn;
    }
}
?>

 <?php
$file_name = "cate_backup.xls";
header( "Content-type: application/vnd.ms-excel; charset=utf-8");
header( "Cache-Control: must-revalidate, post-check=0,pre-check=0" );
header( "Content-Disposition: attachment; filename=$file_name" );
header( "Content-Description: PHP4 Generated Data" );
//header() 함수를 쓰기전에 어떠한 출력도 있어선 안된다

include_once 'dbconect.php'; // DB 연동

$tblName = category;    // 테이블명
$sql ="SELECT uid, classname, relateduid FROM $tblName ORDER BY uid DESC";
// DESC 내림차순 정렬, ASC 오름차순 정렬
$result = mysqli_query($db,$sql);

// 테이블 상단 만들기
$EXCEL_STR = "
<table border='1'>
<tr>
<td align=center BGCOLOR='#9DEEE1'>번호</td>
<td align=center BGCOLOR='#9DEEE1'>구분자</td>
<td align=center BGCOLOR='#9DEEE1'>연관번호</td>
</tr>";

while ($row = mysqli_fetch_array($result) ){
    $EXCEL_STR .= "
    <tr>
    <td align=center>".$row['uid']."</td>
    <td align=center>".$row['classname']."</td>
    <td align=center>".$row['relateduid']."</td>
    </tr>
    ";
}

$EXCEL_STR .= "</table>";
echo $EXCEL_STR;
exit;
?>


블로그 이미지

Link2Me

,
728x90

csv 파일을 PHP를 이용하여 MySQL 에 업로드하는 예제코드다.

실전에서 활용하는 편리한 코드다.


<?php
error_reporting(0); // 경고 출력 방지, 주석처리해서 경고, 에러메시지 나오도록 하자.
require_once 'dbconnect.php';

date_default_timezone_set('Asia/Seoul');

// Edit upload location here
$tmpname    = $_FILES['file']['tmp_name'];
$realname    = $_FILES['file']['name'];

$fileExt    = getExt($realname);
$destination_path='uploads/';
$readFile = $destination_path . 'contactsdata.csv';

$errorFilename ="errorFile.txt";
$errorFile = $destination_path . $errorFilename;

$TABLENAME ='contacts'; // 테이블명

if (is_uploaded_file($tmpname)) {
    if (!strstr('[csv]',$fileExt)) {
        echo '<script type="text/javascript">alert("csv 파일만 등록할 수 있습니다.");</script>';
        exit;
    }
    move_uploaded_file($tmpname,$readFile);
    @chmod($readFile,0606);
}

/////////////////////////////////////////////////////////////////////////////////
$file_read = fopen($readFile,"r");
if(!$file_read){
    echo '<script type="text/javascript">alert("파일을 찾을 수가 없습니다!");</script>';
    echo "-2";
    exit;
}

// 파일 인코딩 모드 검사
$current_encoding = detectFileEncoding($readFile);
$total_line = 0;
$newcount = 0;
$upcount=0;
$ok = 0;

while($line = fgetcsv($file_read,1000, ";")) { // 구분자는 ; 로 지정
    // 파일 인코딩 모드 검사
    if($current_encoding != 'utf-8') {
        $line00 = iconv('euc-kr','utf-8',trim($line[0])); // 구분1(cat1)
        $line01 = iconv('euc-kr','utf-8',trim($line[1])); // 구분2(cat2)
        $line02 = iconv('euc-kr','utf-8',trim($line[3])); // officeNO
        $line03 = iconv('euc-kr','utf-8',trim($line[4])); // mobileNO
    } else {
        $line00 = trim($line[0]); // 구분1(cat1)
        $line01 = trim($line[1]); // 구분2(cat2)
        $line02 = trim($line[3]); // officeNO
        $line03 = trim($line[4]); // mobileNO
    }

    $cat1NM = $line00;
    $cat2NM = $line01;
    $officeNO = isset($line02) ? TelNumSplitRemove($line02) : '';
    $mobileNO = isset($line03) ? TelNumSplitRemove($line03) : '';

    $total_line++;
    if(strlen($mobileNO)<1) continue;

    // 구분1 코드 구하기
    $CA1= getDbData('category','name="'.$cat1NM.'" and parent_id=0','id');
    $cat1 = $CA1['id'];

    // 구분2 코드 구하기
    if(strlen($cat2NM)>0){
        $CA2= getDbData('category','parent_id="'.$cat1.'" and name="'.$cat2NM.'"','id');
        $cat2 = $CA2['id'];
    } else {
        $cat2 =0;
        continue; // 상황에 따라 체크 필요한 코드
    }

    $d_regis = date('Ymd');

    // 중복 등록 여부 검사
    $cnt = DataExistedChk($cat1,$cat2);
    if($cnt == "0"){
        $d_regis = date('Ymd');
        $QKEY = "cat1,cat2,officeNO,mobileNO";
        $QVAL = "'$cat1','$cat2','$officeNO','$mobileNO'";
        $rs = getDbInsert('contacts',$QKEY,$QVAL);
        $newcount++;
    } else {
        $QSET="officeNO='".$officeNO."',";
        $QSET.="mobileNO='".$mobileNO."'";
        $QVAL="cat1='".$cat1."' and cat2='".$cat2."' ";

        getDbUpdate('contacts',$QSET,$QVAL);
        $upcount++;
    }

    $ok ++;
    if (($ok % 500) == '0') {
        echo(" $ok 건 저장");
        flush();
        sleep(2); //500개 저장할때마다 2초씩 쉰다.
    }
} // while 문 종료
fclose($file_read);
unlink($readFile);  // 업로드 완료후에 파일 삭제 처리

$msg = '전체'.number_format($total_line).'건中 신규'.number_format($newcount).'건, 갱신'.number_format($upcount).'건 등록완료';
echo "<script type=\"text/javascript\">alert('$msg');</script>";

function getExt($filename){
    $ext = substr(strrchr($filename,"."),1);
    $ext = strtolower($ext);
    return $ext;
}

function detectFileEncoding($filepath) {
    // 리눅스 기본 기능을 활용한 파일 인코딩 검사
    $output = array();
    exec('file -i ' . $filepath, $output);
    if (isset($output[0])){
        $ex = explode('charset=', $output[0]);
        return isset($ex[1]) ? $ex[1] : null;
    }
    return null;
}

// ID Exist Check
function DataExistedChk($cat1,$cat2){
    global $db;
    $sql = "select count(*) from contacts where cat1='".$cat1."' and cat2='".$cat2."'";
    $result = mysqli_query($db,$sql);
    if($row = mysqli_fetch_row($result)){
        return $row[0];
    } else {
        return "0";
    }
}

//DB삽입
function getDbInsert($table,$key,$val){
    global $db;
    $sql ="insert into ".$table." (".$key.")values(".$val.")";
    if(mysqli_query($db,$sql)){
        return 1;
    } else {
        return 0;
    }
}

//DB업데이트
function getDbUpdate($table,$set,$where){
    global $db;
    mysqli_query($db,'set names utf8');
    mysqli_query($db,'set sql_mode=\'\'');
    $sql ="update ".$table." set ".$set.($where?' where '.getSqlFilter($where):'');
    if(mysqli_query($db,$sql)){
        return 1;
    } else {
        return 0;
    }
}

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


function TelNumSplitRemove($tel){
    return preg_replace("/[^0-9]/", "", $tel);    // 숫자 이외 제거
}
?>


블로그 이미지

Link2Me

,
728x90

PHP 로 jQuery 와 AJAX를 이용해서 파일을 업로드하는데 한참 삽질을 하고 적어둔다.

어쩌다 필요해서 하려고 하면 생각도 안나고 다시 제자리 걸음을 반복하는 초보자의 길을 언제나 벗어나려나 ㅠㅠ


MDB(Meterial Design Bootstrap) 기반 템플릿을 활용하여 코딩을 하느라고 class 가 이상것이 보이는 것이니 혹시라도 이 게시글을 읽으시는 분은 그냥 그런게 있나보다 하고 참조만 하시라.


파일 전송 Form 파일

<form id="uploadphoto" action="MemberPhotoUpload.php" method="post" enctype="multipart/form-data">
    <div class="card border-light mb-3" style="max-width: 30rem;">
      <div class="card-body text-dark">
        <h5 class="card-title">Image File Upload</h5>
        <input type="file" id="file" name="file" multiple />
        <input type="submit" value="Upload Photo" class="btnSubmit" />
      </div>
    </div>
</form> 

<table class='table table-responsive-sm' id='photoIDX' uid='<?php echo $idx;?>'>
    <tr><td colspan='5'>★★사진 등록시 [등록하기] , 사진 수정시 [사진을 클릭]하세요.</td></tr>
    <tr>
        <td rowspan='3'>
        <?php
            if(file_exists($imgpath)){
                echo "<img src='MemberPhotoView.php?idx=$idx&uid=$uid' width=115 height=140>";
            } else {
                echo "사진없음<br><span class='btn-floating peach-gradient photoBtn'><i class='fas fa-image'></i>등록하기</span>";
            }
        ?>
        </td>
        <th>아이디</th>
        <td><?php echo $row['userID'];?></td>
        <th>사무실번호</th>
        <td style='text-align:left;height:15px;'><?php echo $row['telNO'];?></td>
    </tr>
    <tr>
        <th>성명</th>
        <td><?php echo $row['userNM'];?></td>
        <th>휴대폰번호</th>
        <td style='text-align:left;height:15px;'><?php echo $mobileNO;?></td>
    </tr>
    <tr>
        <th>직위</th>
        <td><?php echo $chargeArr[$row['codeID']];?></td>
        <th>주업무</th>
        <td style='text-align:left;height:15px;'><?php echo $row['workrole'];?></td>
    </tr>
    <tr>
        <th>E-Mail</th>
        <td colspan='4' style='text-align:left;height:15px;'><?php echo $row['email'];?></td>
    </tr>
</table>


Script 파일

<script>

 $('.photoBtn').click(function(){
    var $obj=$(this);
    var idx=$(this).closest('#photoIDX').attr('uid');

    $('#dialog').load('MemberPhotoForm.php?idx='+idx, function() {
        $(this).dialog({
            autoOpen : true,
            height : 'auto',
            width : 'auto',
            position: { my: "center", at: "center", of: window },
            title : '사진 업로드',
            buttons : {
                "닫기" : function() {
                    $(this).dialog("close");
                }
            }
        });
        PhotoUploadChk(idx);
    });

});

function PhotoUploadChk(idx){
    $('form#uploadphoto').submit(function(e){
        e.preventDefault();
        var formData = new FormData();
        var files = $('#file')[0].files[0];
        formData.append('file',files);
        formData.append('uid',idx);

        formData.append('osType',$('select[name=osType]').val());

        var upfiles_cnt = $("input:file", this)[0].files.length;
        if(upfiles_cnt == 0){
            alert('선택된 파일이 없습니다.');
            return false;
        }

        $.ajax({
            url: 'MemberPhotoUpload.php',
            data: formData,
            cache: false,
            contentType: false,
            processData: false,
            type: 'post',
            success: function (msg) {
                if(msg == 1){
                    MemberView(idx);
                } else if(msg == 0){
                    alert('form 데이터 오류인지 확인하세요.');
                } else {
                    alert('파일이 업로드되지 못했습니다.');
                }
            }
        });

    });

}
</script>


PHP 파일 업로드 관련 파일

<?php
if(isset($_POST['uid']) && $_POST['uid']>0){
    $idx=$_POST['uid'];

    $tmpname    = $_FILES['file']['tmp_name'];
    $realname    = $_FILES['file']['name'];
    $fileExt    = getExt($realname);

    $file_path='./photos/'.$idx.'.jpg';

    if (is_uploaded_file($tmpname)) {
        if (!strstr('[jpg]',$fileExt)) {
            echo "-1";
            exit;
        }
        move_uploaded_file($tmpname,$file_path);
        @chmod($file_path,0606);
        echo "1";
    }
} else {
    echo "0";
}

function getExt($filename){
    $ext = substr(strrchr($filename,"."),1);
    $ext = strtolower($ext);
    return $ext;
}
?>


파일 사이즈 검사하여 파일 사이즈를 줄이는 코드는 포함되지 않았다.


$("#uploadbutton").click(function(){
    var form = $('form')[0]; // 폼객체를 불러와서
    var formData = new FormData(form); // FormData parameter 에 담아줌
    $.ajax({
        url: 'fileupload',
        processData: false,
        contentType: false,
        data: formData,
        type: 'POST',
        success: function(result){
            alert("업로드 성공!!");
        }
    });
});

블로그 이미지

Link2Me

,
728x90

MDB(Meterial Design for Bootstrap) 에서 sidenav 에 표시할 메뉴를 PHP 와 연동하여 자동으로 표시하는 방법을 구현했다.


먼저, 구글에서 multi level sidenav 를 검색하면 https://mdbootstrap.com/support/jquery/multi-level-in-sidenav-items/ 가 검색될 것이다.


검색된 Tree 구조는 아래와 같다.

 <li>
    <ul class="collapsible collapsible-accordion">
    <li><a class="collapsible-header waves-effect arrow-r"><i class="fa fa-hand-pointer-o"></i>메뉴1-1<i class="fa fa-angle-down rotate-icon"></i></a>
    <div class="collapsible-body">
        <ul>
            <li>
            <ul class="collapsible collapsible-accordion">
            <li><a class="collapsible-header waves-effect arrow-r"><i class="fa fa-hand-pointer-o"></i>메뉴2-1</a>
            <div class="collapsible-body">
                <ul>
                <li><a href="#" class="waves-effect">메뉴3-1</a></li>
                <li><a href="#" class="waves-effect">메뉴3-2</a></li>
                <li><a href="#" class="waves-effect">메뉴3-3</a></li>
                </ul>
            </div>
            </li>
        </ul>

        <ul>
        <li><a href="#" class="collapsible-header waves-effect">메뉴2-2</a></li>
        </ul>

    </div>
    </li>
    </ul>
</li>

<li>
    <a href="#" class="collapsible-header waves-effect">메뉴1-2</a>
</li>


이걸 가지고 PHP 와 연동된 자동 메뉴를 만들어 보자.

MDB 는 bootstrap 4 기반으로 연동된 템플릿이다.

bootstrap 4 에서는 순환구조가 매끄럽지 못한 거 같다. (내가 제대로 이해를 못했겠지만....)


테이블 구조

CREATE TABLE IF NOT EXISTS `menu_item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `parent_id` int(11) NOT NULL DEFAULT '0',
  `depth` int(3) NOT NULL DEFAULT '1',
  `name` varchar(50) NOT NULL,
  `text` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; 


Treeview 메뉴 코드

아래 코드는 https://link2me.tistory.com/1194 게시글과 비교하면서 살펴보면 도움될 것이다.

파일명 : muti_treeview.php

 <?php
if(!isset($_SESSION)) {
    session_start();
}
require_once 'path.php'; // root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
$d = new DBDataClass();
$res = $d->getDbresult("menu_item", "", "id,parent_id,name");
while($row = mysqli_fetch_assoc($res)){
    $sidemenu[] = $row;
}

$side_menu = bootstrap4_sideMenu($sidemenu);
echo $side_menu;

function bootstrap4_sideMenu($array,$parent_id = 0,$parents = array())
{
    global $d,$key;
    if($parent_id==0){ // sub가 있는 parent_id 값 추출
        foreach ($array as $element) {
            if (($element['parent_id'] != 0) && !in_array($element['parent_id'],$parents)) {
                $parents[] = $element['parent_id'];
            }
        }
    }

    $menu_html = '';
    foreach($array as $element){
        if($element['parent_id']==$parent_id){
            $url = $element['id'].";".$element['parent_id'];            

            if(in_array($element['id'],$parents)){
                $menu_html .= '<li>';
                $menu_html .= '<ul class="collapsible collapsible-accordion">';
                $menu_html .= '<li><a class="collapsible-header waves-effect arrow-r" href="'.$url.'"><i class="fa-hand-pointer-o">';
                $menu_html .= '</i>'.$element['name'].'<i class="fas fa-angle-down rotate-icon"></i></a>';
                $menu_html .= '<div class="collapsible-body">';

            } else {
                $menu_html .= '<li>';
                $menu_html .= '<a class="collapsible-header waves-effect" href="'.$url.'">'.$element['name'].'</a>';
                $menu_html .= '</li>';
            }

            if(in_array($element['id'],$parents)){
                $menu_html .= '<ul>';
                $menu_html .= bootstrap4_sideMenu($array, $element['id'], $parents);
                $menu_html .= '</ul>';
                $menu_html .= '</div>';
                $menu_html .= '</ul>';
                $menu_html .= '</li>';
            }
           
        }
    }
    return $menu_html;
}
?>


이제 MDB 템플릿 구조에 포함된 HTML 파일구조다. jQuery 코드는 모두 생략했다.

<!DOCTYPE html>
<head>
<?php require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기 ?>
<?php require_once $g['path_config'].'config.php';?>
<?php require_once $g['path_layout'].'default/_import.head.php';?>
<?php require_once $g['path_root'].'deviceChk.php';?>
</head>

<body class="fixed-sn mdb-skin">
<header>
    <!-- Sidebar navigation -->
    <div id="slide-out" class="side-nav sn-bg-4 fixed">
        <ul class="custom-scrollbar">
            <!-- Side navigation links -->
            <?php require_once $g['path_menu'].'item/multi_treeview.php' ?>
        </ul>
        <div class="sidenav-bg mask-strong"></div>
    </div><!--/. Sidebar navigation -->
    <!-- Navbar -->
    <nav class="navbar fixed-top navbar-toggleable-md navbar-expand-lg scrolling-navbar double-nav">
        <!-- SideNav slide-out button -->
        <div class="float-left">
            <a href="#" data-activates="slide-out" class="button-collapse"><i class="fas fa-bars"></i></a>
        </div>
        <!-- Breadcrumb-->
        <div class="breadcrumb-dn mr-auto">
            <p><?=$hostName;?></p>
        </div>
        <ul class="nav navbar-nav nav-flex-icons ml-auto">
            <?php require_once $g['path_menu'].'item/item_login.php' ?>
        </ul>
    </nav>
    <!-- /.Navbar -->
</header>

<!--Main layout-->
<main>
    <div class="container-fluid text-center">
        <div class="row">
            <div class="col-md-12">
                <div class="content" id="panel_content">
                </div>
                <div id="ajaxPath" menu-path="<?php echo $g['path_help'];?>"  ></div>
            </div>
        </div>
    </div>
</main>
<!--/Main layout-->
<!-- SCRIPTS -->
<?php require_once $g['path_layout'].'default/_import.tail.php';?>
</body>
</html>


블로그 이미지

Link2Me

,
728x90

네이버 지식인에 문의된 대구와 부산의 날씨 정보 파싱 예제다.


<?php
include_once 'simple_html_dom.php';

$url = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT001024"; // 용인
$url1 = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT007007"; // 대구
$url2 = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT008008"; // 부산

$R = weather_parse($url1);
$S = weather_parse($url2);
echo json_encode(array("Time1"=>$R[0], "Weather1"=>$R[1],"Finedust1"=>$R[2],"Time2"=>$S[0], "Weather2"=>$S[1],"Finedust2"=>$S[2])); // 대구와 부산 날씨 정보

function weather_parse($url){
    $str = file_get_contents_curl($url);
    $enc = mb_detect_encoding($str, array('EUC-KR', 'UTF-8', 'shift_jis', 'CN-GB'));
    if ($enc != 'UTF-8') {
        $str = iconv($enc, 'UTF-8', $str);
    }

    $html = new simple_html_dom(); // Create a DOM object
    $html->load($str); // Load HTML from a string

    $R = array();
    $item = $html->find('div[class=c_tit2]',0)->find('em',0)->plaintext;
    array_push($R,$item);
    $item = $html->find('div[class=w_now2]',0)->find('em',0)->plaintext;
    array_push($R,$item);
    $item = $html->find('div[class=w_now2]',0)->find('em',1)->plaintext;
    array_push($R,$item);
    return $R;
}

function file_get_contents_curl($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // https 일때 이 한줄 추가 필요
    $data = curl_exec($ch);
    curl_close($ch);
    return $data;
}
?>


파일에 대한 설명은 https://link2me.tistory.com/1603 참조하라.


블로그 이미지

Link2Me

,
728x90

PHP Simple HTML DOM Parser 를 활용하여 네이버 날씨정보를 파싱하는 방법이다.


준비물

http://simplehtmldom.sourceforge.net/ 에서 파일을 다운로드하고 simple_html_dom.php 를 include 한다.

다운로드 받은 파일에 포함된 예제는 읽어보거나, 구글링해서 사용법에 대한 예제를 찾아보면 된다.


<?php
include_once 'simple_html_dom.php';
function file_get_contents_curl($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // https 일때 이 한줄 추가 필요
    //Set curl to return the data instead of printing it to the browser.
    $data = curl_exec($ch);
    curl_close($ch);
    return $data;
}

?> 


그럼 2단계로 파싱할 곳을 선택한다.

내가 사는 지역인 용인지역을 파싱할 곳으로 선택하고, 네이버 날씨정보 URL을 알아낸다.


$url = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT001024";


$str = file_get_contents_curl($url);
$enc = mb_detect_encoding($str, array('EUC-KR', 'UTF-8', 'shift_jis', 'CN-GB'));
if ($enc != 'UTF-8') {
    $str = iconv($enc, 'UTF-8', $str);
}

print_r($str);


여기까지 하면 날씨정보에 대한 소스보기를 할 수 있다.

마우스 우클릭하여 소스보기를 하면 소스 정보가 나온다.

소스에서 파싱할 곳을 찾아내야 한다.


소스보기에 아래 그림을 찾아낼 수 있다.

<em>태그안에 있는 정보가 파싱할 정보라는 걸 확인할 수 있다.


그럼 이제 simple_html_dom 파싱 처리를 위한 단계로 코드를 구현하자.

// Create a DOM object
$html = new simple_html_dom();
// Load HTML from a string
$html->load($str);


<em> 태그를 파싱하는 방법은 아래와 같이 하면 모든 em 태그를 찾아준다.

foreach($html->find('em') as $element){
    echo $element->innertext . '<br>';
}


시간정보만 파싱해보자.

foreach($html->find('div[class=c_tit2]',0)->find('em') as $element){
    echo $element->innertext . '<br>';
}
와 같이하면 시간정보만 파싱되는 걸 확인된다.

$html->find('div[class=c_tit2]',0) 와 같이 0을 넣어주어야 하는 거 잊지말자. 소스와 비교하여 확인하면 된다.

find는 선택한 태크를 찾고 그 자식들을 모두 찾는데 다시 find 로 em 태크를 선택한 것이다.

육안으로 확인하면 하나 밖에 없다는 것을 알 수 있으므로

$time = $html->find('div[class=c_tit2]',0)->find('em');

를 하면 Trying to get property of non-object라고 하면서 에러가 발생한다.

$item = $html->find('div[class=c_tit2]',0)->find('em',0); 라고 하면 에러가 발생하지 않는다.

echo $item->plaintext; 를 하면 시간 정보가 출력되는 걸 확인할 수 있다.

한번에 하면 $item = $html->find('div[class=c_tit2]',0)->find('em',0)->plaintext; 로 하면 된다.


원하는 결과만 뽑아내면

$R = array();
foreach($html->find('em') as $element){
    array_push($R,$element->innertext);
}
echo '시간 : '.$R[1].'<br />';
echo '날씨 : '.$R[2].'<br />';
echo '미세먼지 : '.$R[3].'<br />';


하나 하나 분리해서 코드를 작성해보면....

$item = $html->find('div[class=c_tit2]',0)->find('em',0)->plaintext;
echo $item.'<br />';

$item = $html->find('div[class=w_now2]',0)->find('em',0)->plaintext;
echo $item.'<br />';

$item = $html->find('div[class=w_now2]',0)->find('em',1)->plaintext;
echo $item.'<br />';


코드를 Android 에서 JSON 으로 파싱처리하도록 수정했다.

<?php
include_once 'simple_html_dom.php';
function file_get_contents_curl($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // https 일때 이 한줄 추가 필요
    //Set curl to return the data instead of printing it to the browser.
    $data = curl_exec($ch);
    curl_close($ch);
    return $data;
}

$url = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT001024";

$str = file_get_contents_curl($url);
$enc = mb_detect_encoding($str, array('EUC-KR', 'UTF-8', 'shift_jis', 'CN-GB'));
if ($enc != 'UTF-8') {
    $str = iconv($enc, 'UTF-8', $str);
}

//print_r($str);

$html = new simple_html_dom(); // Create a DOM object
$html->load($str); // Load HTML from a string

$R = array();
foreach($html->find('em') as $element){
    array_push($R,$element->innertext);
}
$R[2] = preg_replace("(\<(/?[^\>]+)\>)", "", $R[2]); // html 태그를 없애는 정규식
echo json_encode(array("Time"=>$R[1], "Temperature"=>$R[2],"Fine dust"=>$R[3]));
?>


이렇게 하면 네이버 날씨정보 소스코드가 달라져도 이 PHP 파일만 수정해주면 되므로 Android 앱에서는 잘못된 정보로 업데이트할 일이 없을 것이다.


블로그 이미지

Link2Me

,
728x90

사용자가 직접 클릭하지 않고 자동으로 클릭이 되는 기능을 동작시키고 싶을 때가 있다.

왼쪽 메뉴를 클릭하거나 로그인 버튼을 눌렀을 때 팝업 모달이 자동으로 실행되게 하고 싶은데 처음에는 좀 해맸다.


로직에 대한 기본 이해 부족이었다.

동작 순서를 고려하여 순차적으로 실행되게 해야 한다.


1. 왼쪽 메뉴를 누르면

2. a href 링크의 고유 기능은 이벤트 실행 금지 시킨다. e.preventDefault();

3. 우측 content 에 원하는 걸 load 시킨다.

    $('.memberModify').on('click',function(e){
        var uri = $(this).attr('href');
        if(uri != '#'){
            $('#panel_content').load(uri);
        }
    });

4. load 되는 곳에 특정 키워드가 포함되어 있고 또 클릭을 해야 하는 불편이 있다면...

    $('.memberModify').on('click',function(e){
        e.preventDefault(); // a 링크 실행 방지
        var uri = $(this).attr('href');
        if(uri != '#'){
            $('#panel_content').load(uri,function(){
                if($('#modalLoginClick:contains("눌러주세요")')){
                    $('#modalLoginClick').trigger('click'); // 자동 클릭
                }
            });
        }
    });


trigger 메소드를 사용하면 자동 클릭이 되는 걸 확인할 수 있다.

load 시킨 다음에 자동 클릭이 되어야 정확하게 원하는 결과가 나왔다.


ㅇ trigger(eventType)
  eventType는 이벤트의 타입을 지정하는 문자열로써 click, dblclick, focus, blur 등등 해당 이벤트를 구동한다.




블로그 이미지

Link2Me

,
728x90

jQuery 에서 검색어를 입력하고 엔터키를 누르면 검색이 실행되도록 하는 코드를 테스트하는데 자꾸 오동작이 발생한다.

한마디로 개념 부족에서 오는 현상이었다.

프로그램은 절대 거짓말 하지 않는다는 지인의 말이 생각난다.

구현하려는 로직과 내가 코딩하는 것의 사이에서 놓치는 점이 무엇인가 다시 고민해보자.


처리하려는 로직을 순서대로 적어본다.

1. 엔터키를 입력할 때까지는 어떠한 것도 하지 않는다.

2. 엔터키가 입력되면 event.preventDefault(); 를 실행하여 자동으로 서밋되는 걸 방지한다.

3. 원하는 결과를 실행한다.


if(parseInt(device_type) === 3){ // PC에서 입력한 코드인지 확인
    // 27 is the ESC key (익스플로어나 크롬에서는 27로 나오고 파이어폭스는 0)
    $('#BBSSearchKeyword').on('keypress', function(event){
        var keycode = (event.keyCode ? event.keyCode : event.which);
        if(keycode == 13){ // 엔터키가 입력될 때까지는 어떤 것도 하지 않는다.
            event.preventDefault(); // 엔터키가 입력되면 이벤트의 기본 동작을 중단한다.
            //event.stopPropagation(); // 현재 이벤트가 상위로 전파되지 않도록 중단한다.
            BBSSearch(uri,where,keyword);
        }
    });
}

크롬브라우저 정상 동작, Firefox 브라우저 정상 동작
익스플로러에서 실행하니까 한글 입력 결과의 텍스트가 깨지면서 정확한 결과를 반환하지 않는다.
코드에서 UTF-8 을 기본으로 인식하는 코드를 추가해줘야 하나보다.

encodeURIComponent : URI로 데이터를 전달하기 위해서 문자열을 UTF-8 인코딩, IE8 이상의 브라우저에서 모두 지원한다.


블로그 이미지

Link2Me

,
728x90

jQuery 에서 값 입력 여부를 체크하는 걸 반복적으로 하니까 좀 귀찮은 거 같아서 간단하게 처리하는 걸 시도했다.


함수 만들기 이전 입력값 체크

if($('select[name=cat1]').val().length == 0){
    alert('카테고리1 선택을 안했습니다.');
    $('select[name=cat1]').focus();
    return false;
}
if($('select[name=cat2]').val().length == 0){
    alert('카테고리2 선택을 안했습니다.');
    $('select[name=cat2]').focus();
    return false;
}
if($('input[name=userID]').val().length == 0){
    alert('아이디를 입력하세요');
    $('input[name=userID]').focus();
    return false;
}
if($('input[name=userNM]').val().length == 0){
    alert('성명을 입력하세요');
    $('input[name=userNM]').focus();
    return false;
}


함수를 만들고 나서 입력값 체크

function CheckErr(vsel,msg){
    var count = vsel.val().length;
    if(count < 1){
        alert(msg);
        vsel.focus();
        return false;
    }
    return true;
}


if(CheckErr($('select[name=cat1]'),'카테고리1 선택을 안했습니다.') == false) return false;
if(CheckErr($('select[name=cat2]'),'카테고리2 선택을 안했습니다.') == false) return false;
if(CheckErr($('input[name=userID]'),'아이디를 입력하세요.') == false) return false;
if(CheckErr($('input[name=userNM]'),'성명을 입력하세요.') == false) return false;


블로그 이미지

Link2Me

,