728x90

SMS OTP 인증 구현을 위한 첫번째 단계 설계 개념이다.

ID/PW 인증 방식에 SMS OTP 인증을 추가하는 개념이다.


1. ID/PW 인증
   - 로그인한 정보에 오늘 날짜 SMS 인증 기록 유무를 가져온다.
   - 오늘날짜 인증기록이 없으면 SMS 인증으로 분기한다.
   - SMS 인증 기록이 있으면, PIN 번호 인증을 한다.
2. SMS OTP 인증 (1일 1회 인증 원칙)
   - SMS 인증 API를 통해 휴대폰으로 인증번호 발송
   - 인증번호 6자리를 입력하고 검증(verify) API 로 전송
   - 검증(verify) API 로부터 success return을 받으면
   - 서버에 사용자 인증 시각 정보를 기록한다.
   - 전일 인증 기록이 있을 경우 당일 인증 요청이 없는 것으로 간주하고 사용자 인증 정보를 update 한다.
   - error를 받으면 에러 상황에 맞는 것을 Popup으로 보여주고 로그인 종료처리한다.

위 로직을 기반으로 PHPClass 함수를 정의하고 코드 구현 예정이다.


Android : 서버 인증 결과를 JSON 으로 받아서 화면에서 인증 처리

  - Custom Dialog 방식 또는 New Activity 방식

PHP : Ajax를 이용한 인증 처리 로직 수정 보완

블로그 이미지

Link2Me

,
728x90

네이버 지오코더 API 를 이용하여 주소 → 위치좌표를 반환하는 PHP 코드와 네이버 지도상에 마커를 보여주는 Javascript 구현 코드 예제다.

IP주소 및 경로 등록이 되어있어야만 Naver Map 이 화면에 보여지며, Naver Cloud 신청에 대한 사항은 https://link2me.tistory.com/1832 게시글을 참조하면 된다.

 

주소 → 위치좌표 변환을 위해서는 Naver Cloud 에서 Geocorder API가 신청되어 있어야 한다.

 

Web 브라우저에 보이기 위해서는 Web Dynamic Map 이 신청되어 있어야 한다.

 

 

<?php
//error_reporting(0);
//error_reporting(E_ALL);
//ini_set("display_errors", 1);

function getNaverGeocode($addr) {
    $addr = urlencode($addr);
    $url = "https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode?query=".$addr;
    $headers = array();
    $headers[] ="X-NCP-APIGW-API-KEY-ID:xz06ez230a";
    $headers[] ="X-NCP-APIGW-API-KEY:e0A5km9e7Rh5S9QOkr1TRfa4NW8MaDblKAXcrQXL";

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $result = curl_exec($ch);
    curl_close($ch);
    return $result;
}

$shop_name = "광제당한의원";
$addr = "경기도 안산시 단원구 선부광장1로 56";
$geo = getNaverGeocode($addr);
$data = json_decode($geo,1);

$map_y_point = $data['addresses'][0]['x']; // x 좌표값과 y좌표값이 바뀌어서 출력됨
$map_x_point = $data['addresses'][0]['y'];

$lat = $map_x_point;
$lng = $map_y_point;
?>
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta http-equiv="expires" content="0" />
<meta http-equiv="pragma" content="no-cache" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script>
<script src="https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=
xz06ez230a&submodules=geocoder"></script>

</head>
<body>
<div class="container-fluid text-center">
    <div class="row">
        <div class="col-md-12">
            <div class="content" id="content">
                <div id="map" style="width:100%;height:450px;"></div>
            </div>
        </div>
    </div>
</div>

<script>
var lat = "<?php echo $lat;?>";
var lng = "<?php echo $lng;?>";
var shop_name = "<?php echo $shop_name;?>";
var addr = "<?php echo $addr;?>";
var HOME_PATH = window.HOME_PATH || '.';

var map = new naver.maps.Map('map', {
   useStyleMap: true,
   center: new naver.maps.LatLng(lat, lng), //지도의 초기 중심 좌표
   zoom: 15, //지도의 초기 줌 레벨
   minZoom: 7, //지도의 최소 줌 레벨
   zoomControlOptions : { //줌 컨트롤의 옵션
       position : naver.maps.Position.TOP_RIGHT
   },
   mapTypeControl : true
});

// 마커 표시
var marker = new naver.maps.Marker({
    position: new naver.maps.LatLng(lat, lng),
    map: map
});

var contentString = [
        '<div style="text-align:center;padding-left:15px;padding-right:15px;padding-top:5px;">',
        '   <h4>'+shop_name+'</h4>',
        '   <p>'+addr+'<br />',
        '   </p>',
        '</div>'
    ].join('');

var infowindow = new naver.maps.InfoWindow({
    content: contentString
});

naver.maps.Event.addListener(marker, "click", function(e) {
    if (infowindow.getMap()) {
        infowindow.close();
    } else {
        infowindow.open(map, marker);
    }
});

infowindow.open(map, marker);
</script>
</body>
</html>
 

 

참고사이트

https://github.com/navermaps/maps.js/tree/master/examples/map

 

https://navermaps.github.io/maps.js/docs/tutorial-3-geocoder-geocoding.example.html

 

https://navermaps.github.io/android-map-sdk/guide-ko/0.html

 

https://sir.kr/g5_tip/10880

 

 

블로그 이미지

Link2Me

,
728x90

PHPExcel 이 deprecated 되어 PHP 7.2 이상에서 지원이 안되므로 PHPSpreadsheet 를 설치해야 한다.


이용환경 : CentOS 7


PHP 7.3 yum 설치 스크립트 : https://link2me.tistory.com/1841 참조


설치 방법

- 윈도우 환경에서는 https://getcomposer.org/download/ 에서 설치 파일을 다운로드 하는 거 같다.

- 리눅스 설치 환경에서는 composor 가 설치되어 있어야 한다.

https://zetawiki.com/wiki/%EB%A6%AC%EB%88%85%EC%8A%A4_composer_%EC%84%A4%EC%B9%98

에 나온 방법대로 설치를 한다.


먼저 vi /etc/php.ini 에서 allow_url_fopen = On 이 되어 있어야 아래 코드가 동작된다.


curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
composer -V
export COMPOSER_ALLOW_SUPERUSER=1
composer -V
echo "export COMPOSER_ALLOW_SUPERUSER=1" >> ~/.bashrc
cat ~/.bashrc | grep export




이제 PhpSpreadsheet 의 설치는 PHP가 설치된 경로에서 한다.


cd /var/www/html/

composer require phpoffice/phpspreadsheet




실 사용 예제

기존 PHPExcel 라이브러리를 이용하여 사용하던 코드에서 변경할 부분만 변경해서 사용하면 된다.

<?php
error_reporting(0);
ini_set('memory_limit', -1); // 메모리 제한을 해제해준다.

require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php'; // 세션 체크
require_once $g['path_config'].'config.php';
require_once $g['path_class'].'dbconnect.php';
require_once $g['path_class'].'adminClass.php';
require_once $g['path_admin'].'PHPExcel.php';
$a = new adminClass();

$fname="members";
$posArr=$a->StaffPositionMNArray();

$objPHPExcel = new PHPExcel();
$objPHPExcel->setActiveSheetIndex(0); //set first sheet as active

$objSheet = $objPHPExcel->getActiveSheet();

$objPHPExcel->getDefaultStyle()->getFont()->setSize(12); // 폰트 사이즈
$objSheet->SetCellValue('A2', "번호");
$objSheet->SetCellValue('B2', "성명");
$objSheet->SetCellValue('C2', "직급");
$objSheet->SetCellValue('D2', "아이디");
$objSheet->SetCellValue('E2', "부서1");
$objSheet->SetCellValue('F2', "부서2");
$objSheet->SetCellValue('G2', "부서3");
$objSheet->SetCellValue('H2', "부서4");
$objSheet->SetCellValue('I2', "등록일자");
$objSheet->SetCellValue('J2', "최근접속");
cellColor('A2:J2', 'F28A8C'); // 헤더 배경색 지정

$i=3; // 값을 기록할 셀의 시작위치
$rowCount = 1; // 넘버링

$sql="select * from members ";
$sql.=" order by idx";
$result=mysqli_query($db,$sql);
while($R=mysqli_fetch_array($result)){

    $objSheet->SetCellValue('A'.$i, $rowCount);
    $objSheet->SetCellValue('B'.$i, $R['username']);
    $objSheet->SetCellValue('C'.$i, $posArr[$R['codeID']]);
    $objSheet->SetCellValue('D'.$i, $R['userid']);
    $objSheet->SetCellValue('E'.$i, $R['group2']);
    $objSheet->SetCellValue('F'.$i, $R['group3']);
    $objSheet->SetCellValue('G'.$i, $R['group4']);
    $objSheet->SetCellValue('H'.$i, $R['group5']);
    $objSheet->SetCellValue('I'.$i, substr($R['regdate'],0,10));
    $objSheet->SetCellValue('J'.$i, SETDATE($R['date']));

    $i++;
    $rowCount++;
}

// 표 그리기
$i--;
$objSheet->getStyle('A2:J'.$i)->getBorders()->getAllBorders()->setBorderStyle(PHPExcel_Style_Border::BORDER_THIN);

// 헤더 칼럼 가운데 정렬
$objSheet->getStyle('A2:J2')->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_CENTER);

// 셀 높이
$objSheet->getRowDimension(1)->setRowHeight(20);

// 칼럼 사이즈 자동 조정
$objSheet->getColumnDimension('A')->setWidth(6); // setWidth(18);  // 칼럼 크기 직접 지정
$objSheet->getColumnDimension('B')->setWidth(8);  // 칼럼 크기 직접 지정
$objSheet->getColumnDimension('C')->setWidth(10);
$objSheet->getColumnDimension('D')->setWidth(10);
$objSheet->getColumnDimension('E')->setWidth(21);
$objSheet->getColumnDimension('F')->setWidth(19);
$objSheet->getColumnDimension('G')->setWidth(18);
$objSheet->getColumnDimension('H')->setWidth(18);
$objSheet->getColumnDimension('I')->setWidth(11);
$objSheet->getColumnDimension('J')->setWidth(11);

// 파일 PC로 다운로드
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename='.$fname.'.xlsx');
header('Cache-Control: max-age=0');

$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, "Excel2007");
$objWriter->save('php://output');
exit;

// 엑셀의 셀 배경색 지정
function cellColor($cells,$color){
    global $objSheet;

    $objSheet->getStyle($cells)->getFill()->applyFromArray(array(
        'type' => PHPExcel_Style_Fill::FILL_SOLID,
        'startcolor' => array(
             'rgb' => $color
        )
    ));
}

function SETDATE($date){
    $date = preg_replace("/[^0-9]/", "", $date);    // 숫자 이외 제거
    return preg_replace("/([0-9]{4})([0-9]{2})([0-9]{2})$/", "\\1-\\2-\\3", $date);
}

?>
 



이제 PHPSpreadsheet 라이브러리를 이용한 코드 예제를 보면 변경된 부분이 뭔지 알 수 있다.

<?php
error_reporting(0);
ini_set('memory_limit', -1); // 메모리 제한을 해제해준다.

require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php'; // 세션 체크
require_once $g['path_config'].'config.php';
require_once $g['path_class'].'dbconnect.php';
require_once $g['path_class'].'adminClass.php';
require_once $g['path_root'].'vendor/autoload.php';
$a = new adminClass();

$fname="members";
$posArr=$a->StaffPositionMNArray();

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

$objPHPExcel = new Spreadsheet();
$objPHPExcel->setActiveSheetIndex(0); //set first sheet as active

$objSheet = $objPHPExcel->getActiveSheet();

$objPHPExcel->getDefaultStyle()->getFont()->setSize(12); // 폰트 사이즈
$objSheet->SetCellValue('A2', "번호");
$objSheet->SetCellValue('B2', "성명");
$objSheet->SetCellValue('C2', "직급");
$objSheet->SetCellValue('D2', "아이디");
$objSheet->SetCellValue('E2', "부서1");
$objSheet->SetCellValue('F2', "부서2");
$objSheet->SetCellValue('G2', "부서3");
$objSheet->SetCellValue('H2', "부서4");
$objSheet->SetCellValue('I2', "등록일자");
$objSheet->SetCellValue('J2', "최근접속");
// 헤더 배경색 지정
$objSheet->getStyle('A2:J2')->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)->getStartColor()->setARGB('F28A8C');

$i=3; // 값을 기록할 셀의 시작위치
$rowCount = 1; // 넘버링

$sql="select * from members ";
$sql.=" order by idx";
$result=mysqli_query($db,$sql);
while($R=mysqli_fetch_array($result)){

    $objSheet->SetCellValue('A'.$i, $rowCount);
    $objSheet->SetCellValue('B'.$i, $R['username']);
    $objSheet->SetCellValue('C'.$i, $posArr[$R['codeID']]);
    $objSheet->SetCellValue('D'.$i, $R['userid']);
    $objSheet->SetCellValue('E'.$i, $R['group2']);
    $objSheet->SetCellValue('F'.$i, $R['group3']);
    $objSheet->SetCellValue('G'.$i, $R['group4']);
    $objSheet->SetCellValue('H'.$i, $R['group5']);
    $objSheet->SetCellValue('I'.$i, substr($R['regdate'],0,10));
    $objSheet->SetCellValue('J'.$i, SETDATE($R['date']));

    $i++;
    $rowCount++;
}

// 표 그리기
$i--;
$objSheet->getStyle('A2:J'.$i)->getBorders()->getAllBorders()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);

// 헤더 칼럼 가운데 정렬
$objSheet->getStyle('A2:J2')->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER);

// 셀 높이
$objSheet->getRowDimension(1)->setRowHeight(20);

// 칼럼 사이즈 자동 조정
$objSheet->getColumnDimension('A')->setWidth(6); // setWidth(18);  // 칼럼 크기 직접 지정
$objSheet->getColumnDimension('B')->setWidth(8);  // 칼럼 크기 직접 지정
$objSheet->getColumnDimension('C')->setWidth(10);
$objSheet->getColumnDimension('D')->setWidth(10);
$objSheet->getColumnDimension('E')->setWidth(21);
$objSheet->getColumnDimension('F')->setWidth(19);
$objSheet->getColumnDimension('G')->setWidth(18);
$objSheet->getColumnDimension('H')->setWidth(18);
$objSheet->getColumnDimension('I')->setWidth(11);
$objSheet->getColumnDimension('J')->setWidth(11);

// 파일 PC로 다운로드
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename='.$fname.'.xlsx');
header('Cache-Control: max-age=0');

/* // OLD 버전
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, "Excel2007");
$objWriter->save('php://output');
*/
$writer = new Xlsx($objPHPExcel);
$writer->save('php://output');
exit;

function SETDATE($date){
    $date = preg_replace("/[^0-9]/", "", $date);    // 숫자 이외 제거
    return preg_replace("/([0-9]{4})([0-9]{2})([0-9]{2})$/", "\\1-\\2-\\3", $date);
}

?>
 


블로그 이미지

Link2Me

,
728x90

MDB 템플릿을 사용하다가 정보 찾는데 걸리는 시간을 절약하기 위해 적어둔다.

bootstrap4 기반으로 만들어진 템플릿이라 bootstrap4 용일 수도 있다.

 

글자 굵은 글씨

<b class="font-weight-bold">'.$R['jiAddress'].' '.$R['aDong'].$bldNM).'</b>

 

글자 왼쪽 정렬

<td class="text-left"><?php echo $R['content'];?></td>

 

빨간 글씨

<td class="text-danger"><?php echo $R['content'];?></td>

 

<?php if($mtype==3):?><td><?php echo $R['d_regis'];?></td><?php endif;?>

 

<tr>
    <th class="text-left" style='width:80pt'>인입구배선</th>
    <td class="text-left" style='width:85%'>
        <div class="form-check form-check-inline">
          <input type="radio" class="form-check-input" name="wireEnt" id="wireEnt1" value="1" <?php if(!isset($C['wireEnt'])):?>checked<?php elseif($C['wireEnt'] == "1"):?>checked<?php else:?><?php endif?>>
          <label class="form-check-label" for="wireEnt1">O</label>
        </div>
        <div class="form-check form-check-inline">
          <input type="radio" class="form-check-input" name="wireEnt" id="wireEnt2" value="2" <?php if(!isset($C['wireEnt'])):?><?php elseif($C['wireEnt'] == "2"):?>checked<?php else:?><?php endif?>>
          <label class="form-check-label" for="wireEnt2">X</label>
        </div>
        <div class="form-check form-check-inline">
          <input type="radio" class="form-check-input" name="wireEnt" id="wireEnt3" value="3" <?php if(!isset($C['wireEnt'])):?><?php elseif($C['wireEnt'] == "3"):?>checked<?php else:?><?php endif?>>
          <label class="form-check-label" for="wireEnt3">△</label>
        </div>
        <div class="form-check form-check-inline">
          <input type="radio" class="form-check-input" name="wireEnt" id="wireEnt4" value="4" <?php if(!isset($C['wireEnt'])):?><?php elseif($C['wireEnt'] == "4"):?>checked<?php else:?><?php endif?>>
          <label class="form-check-label" for="wireEnt4">해당없음</label>
        </div>
    </td>
</tr>

 

 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

오랫만에 PHP PDO 코드를 만들다보니 에러가 나와 어디를 수정해야 할지 헷갈린다.

PHP PDO로 구현한 코드에서 500 Error 메시지의 원인을 찾아내고 해결한 후 적어둔다.

 

Ajax 는 PHP B 파일에서 발생하는 에러를 파악하기 어려울 수 있다.

이럴경우에는 절분시험을 하듯이 B 파일에서만 별도로 데이터가 넘어왔다는 가정하에

아래 두줄 추가하고 alert(msg) 해서 메시지 내용을 살펴보고 코드의 문제점을 발견하고 찾아서 해결하면 된다.

<?php
error_reporting(E_ALL);
ini_set("display_errors", 1);

 

if (!isset($_POST['uid'])) {
    echo "-9";
    exit ;
}

 

에러를 무조건 출력시켜서 메시지를 분석해야 잘못된 부분을 찾아내기 쉽다.

 

 

 

 

 

 

 

코딩할 당시에는 알지만 오래되면 기억이 나지 않기 때문에 내가 나중에 보려고 정리하는 것이므로 댓글 태클은 정중히 사양.......

블로그 이미지

Link2Me

,
728x90

코드를 수정하다보니 SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data 이런 에러메시지가 나온다.


아무리 찾아도 문제가 없는데 말이다.

변수명을 변경했는데 캐싱된 데이터가 계속 잘못된 정보를 서버로 보내면서 벌어진 현상이다.


크롬 브라우저에서 Disable cache 를 하지 않아서 생긴 현상이다. 윽ㅠㅠㅠㅠㅠㅠ

블로그 이미지

Link2Me

,
728x90

회원가입, 로그인, 게시판 글 등록시 스팸방지를 위해서 캡차코드를 적용할 수 있다.


https://www.phpcaptcha.org/ 에서 파일을 다운로드 하여 홈페이지에 업로드한다.


샘플코드를 간단하게 줄여서 테스트하고 적어둔다.


Form 입력코드

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
  <title>Securimage Example Form</title>
  <link rel="stylesheet" href="securimage.css" media="screen">
  <style type="text/css">
  <!--
  div.error { display: block; color: #f00; font-weight: bold; font-size: 1.2em; }
  span.error { display: block; color: #f00; font-style: italic; }
  .success { color: #00f; font-weight: bold; font-size: 1.2em; }
  form label { display: block; font-weight: bold; }
  fieldset { width: 90%; }
  legend { font-size: 24px; }
  .note { font-size: 18px;
  -->
  </style>
</head>
<body>

<fieldset>
<legend>Example Form</legend>
<form method="post" action="capchado.php" id="contact_form">
  <input type="hidden" name="do" value="contact">
  <p>
    <label for="ct_name">Name*:</label>
    <input type="text" id="ct_name" name="ct_name" size="35" value="">
  </p>

  <div>
    <?php
      require_once 'securimage.php';
      $options = array();
      $options['input_name'] = 'ct_captcha'; // change name of input element for form post
      $options['disable_flash_fallback'] = false; // allow flash fallback
      echo Securimage::getCaptchaHtml($options);
    ?>
  </div>

  <p>
    <br>
    <input type="submit" value="전송">
  </p>

</form>
</fieldset>

</body>
</html>


Form 에서 전송받은 데이터를 처리하는 코드

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

if ($_SERVER['REQUEST_METHOD'] == 'POST' && @$_POST['do'] == 'contact') {
    var_dump($_POST); // 디버깅 목적으로 찍어보라.

    $captcha = @$_POST['ct_captcha'];
    require_once dirname(__FILE__) . '/securimage.php';
    $securimage = new Securimage();

    if ($securimage->check($captcha) == false) {
        $captcha_error = '잘못된 보안코드가 입력되었습니다.';
        echo '<br/><br/>'.$captcha_error.'<br/>';
    } else {
      echo '<br/><br/>캡차 제대로 입력했어요<br/>';
    }

} // POST
?>


이 코드를 jQuery 로 처리하기 위해서는 소스보기로 해서 캡차의 input name 값이 뭔지 확인을 해보고 jQuery 입력체크를 하면 된다.

블로그 이미지

Link2Me

,
728x90

mysql_query() expects parameter 2 to be resource, boolean given in


DB 데이터 업데이트 작업을 하는데 MySQLi 가 아닌 MySQL 로 하다보니 위와 같은 메시지가 나온다.


이런 메시지가 나오는 것은 MySQL 연결 정보가 제대로 연결되지 않아서다.


$db= mysql_connect("localhost","root","user_password");
mysql_select_db("dbname", $db);

블로그 이미지

Link2Me

,
728x90

개발용 서버에서 기능 테스트를 할 때 안전하게 전화번호 부분을 일괄 임시번호로 변경하면 개인정보 보호에 더 좋을 수 있어서 일괄 변경하는 기능을 만들어서 적용해봤다.

 

<?php
require_once '../sessionChk.php'// 세션 체크
require_once "../config/dbconnect.php";
 
$i = 0;
 
$sql = "SELECT idx FROM SYS_MEMBER";
$result = mysqli_query($db,$sql);
while($row = mysqli_fetch_row($result)){
    $i++;
    $mobileNO = '010-1000-'.sprintf('%04d',$i);
    $mobileNO = str_replace('-','',$mobileNO);
    $sql = "UPDATE SYS_MEMBER SET mobileNO='$mobileNO' WHERE idx=".$row[0];
    mysqli_query($db,$sql);
}
 
echo $i.' rows Updated';
 
?>
 

 

 

 

 

<?php
if(!isset($_SESSION)) {
    session_start();
}
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php';
}
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
$d = new DBDataClass();
 
$sql ="select * from members";
$result=mysqli_query($db,$sql);
$i=0;
while($row=mysqli_fetch_array($result)){
    $i++;
    if(strlen($row['mobileNO'])>0){
        $telNO = TelNumSplitArr($row['mobileNO'])."0001".sprintf("%04d",$i);
        echo $telNO.'<br/>';
        $QKEY ="mobileNO='".$telNO."'";
        $d->getDbUpdate('members',$QKEY,'uid="'.$row['uid'].'"');
    }
 
    if(strlen($row['officeNO'])>0){
        $officeNO = TelNumSplitArr($row['officeNO'])."0001".sprintf("%04d",$i);
        //echo $officeNO.'<br/>';
        $QKEY ="officeNO='".$officeNO."'";
        $d->getDbUpdate('members',$QKEY,'uid="'.$row['uid'].'"');
    }
 
}
 
//전화번호를 정규식을 이용하여 지역번호, 010과 같은 앞자리는 그대로 두기 위해서 앞부분만 추출한다.
function TelNumSplitArr($tel){
    $tel = preg_replace("/[^0-9]/"""$tel);    // 숫자 이외 제거
    if (substr($tel,0,2)=='02')
        return preg_replace("/([0-9]{2})([0-9]{3,4})([0-9]{4})$/""\\1"$tel);
    else
        return preg_replace("/([0-9]{3})([0-9]{3,4})([0-9]{4})$/""\\1"$tel);
}
 
?> 

 

블로그 이미지

Link2Me

,
728x90

PHP 에서 문자를 발송할 때 ***님이라고 이름을 변경해서 보내는 문자를 할 때 좋은 기능이 될 거 같아서 적어둔다.


<?php
$str = '안녕하세요. [name]님. 오늘 날씨가 참 좋습니다. 이번 모임은 OO에서 개최합니다.';
$arr = array('홍길동','이순신','김구','을지문덕');

foreach($arr as $name){ // 배열에만 동작하는 반복문 foreach문
    // 콜백을 이용한 정규 표현식 검색과 치환을 수행
    // preg_replace_callback ( $pattern , $callback , $str )
    // 매치가 발견되면 새 str 반환되고, 그렇지 않으면 변경되지 않은 str 가 반환
    $text_message = preg_replace_callback('#\[name\]#',
        function($match) use ($name) {
            return replaceForm($name);
        }, $str);
    echo $text_message.'<br/>';
}

function replaceForm($name) {
    static $idx = 0;
    $idx++;
    return str_replace('{{idx}}', $idx, $name);
}

?>


블로그 이미지

Link2Me

,
728x90

Class 를 만들어서 사용하면 여러모로 편하고 좋다.

본 Class 함수는 KIMSQ RB에서 사용하는 함수를 Class화해서 사용하고 있다.

그만큼 코딩을 편하면서도 깔끔하게 이해하게 해준다.

MDB(Meterial Design for Bootstrap) 든 일반 게시판이든 사용하는데는 다 사용 가능하다. MDB는 템플릿으로 깔끔하게 보여주기 때문에 디자인에 대한 고민을 덜어준다.


<?php

// 파일명 : dbDataClass.php

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

    // DB Query Cutom 함수
    function getDbData($table,$where,$column) {
        global $db;
        $sql ='select '.$column.' from '.$table.($where?' where '.$this->getSqlFilter($where):'');
        //echo $sql; // 디버깅 목적으로 필요할 때가 있다.
        $result = mysqli_query($db,$sql);
        $row = mysqli_fetch_array($result);
        return $row;
    }

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

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

    }

    //DB데이터 레코드 총 개수
    function getDbRows($table,$where){
        global $db;
        $sql = 'select count(*) from '.$table.($where?' where '.$this->getSqlFilter($where):'');
        $result = mysqli_query($db,$sql);
        if($result){
            $rows = mysqli_fetch_row($result);
            return $rows[0] ? $rows[0] : 0;
        } else {
            echo "Failed to connect to MySQL: " . mysqli_connect_error($db);
            exit(); // **this is missing**
        }
    }

    //DB삽입
    function getDbInsert($table,$key,$val){
        global $db;
        $sql ="insert into ".$table." (".$key.")values(".$val.")";
        //echo $sql;
        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 '.$this->getSqlFilter($where):'');
        if(mysqli_query($db,$sql)){
            return 1;
        } else {
            return 0;
        }
    }

    //DB삭제
    function getDbDelete($table,$where)    {
        global $db;
        $sql ="delete from ".$table.($where?' where '.$this->getSqlFilter($where):'');
        if(mysqli_query($db,$sql)){
            return 1;
        } else {
            return 0;
        }
    }

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

    // 암호화
    function AES_Encode($plain_text){
        global $key;
        return base64_encode(openssl_encrypt($plain_text, "aes-256-cbc", $key, true, str_repeat(chr(0), 16)));
    }

    // 복호화
    function AES_Decode($base64_text){
        global $key;
        return openssl_decrypt(base64_decode($base64_text), "aes-256-cbc", $key, true, str_repeat(chr(0), 16));
    }

}//end dbClass

?>


블로그 이미지

Link2Me

,
728x90

Eclipse 에서 PHP 개발 테스트를 하기 위해서 PHP Development Tools 를 설치했다.

구글 검색해서 4.1 버전 이미지를 보면서 설치를 했다.




검색어 php 를 입력하고 Go를 누르면 아래와 같이 PHP Development Tools 최신 버전이 나온다.

현재 그림은 이미 Installed 된 것이라 이렇게 보이지만 설치하지 않았으면 Install 이라고 나온다.


이후에 나온 그림은 캡처를 하지 않아서 게재할 수가 없다.

다른 블로그에 나온 이미지를 참조해도 되고 순서대로 진행하면 된다.

설치를 하고 나면 프로그램 재실행을 해준다.


이후에 PHP 파일에서 APM 과 연결하여 PHP 파일을 직접 실행하는 방법이다.


먼저 Preferences 를 눌러서 아래 화면이 나오면 PHP 항목을 선택한 다음에

Servers 를 선택하고 Default PHP Web Server 경로를 수정해줘야 한다.

APM(Apache + PHP + MySQL) 이 설치된 Autoset9 의 설치경로의 web root 경로(httpd.conf 에서 설정한 경로)를 지정해준다.



이제 PHP Project 를 생성할 때 아래와 같이 1번 항목이 보이게 된다.

2번 항목에는 이미 만들어놓은 PHP 경로를 적어주면 된다.


실제 파일에서 아래와 같이 한번만 실행하면 다음부터는 1번만 누르면 마지막 설정한 걸 실행한다.




사족을 달자면...

맥북 2010 프로에는 윈도우10 설치가 안되어 윈도우 8.1 을 설치해서 사용하고 있는데 Aptana 를 설치하면 에러가 발생하면서 설치가 안되어서 Eclipse Java 설치한 것에 PHP 모듈을 추가해 본 것이다.

여러가지 PHP 툴을 설치하지만 딱 마음에 드는게 별로 없다.

Eclipse PDT 를 사용해 보고 장점이 발견되면 추가로 적어둘려고 한다.


블로그 이미지

Link2Me

,
728x90

자바 강좌를 들으면서 실습용으로 Eclipse를 설치해서 사용중인데 기본 인코딩 모드가 UTF-8 이 아니었다.

PHP 모듈을 추가 설치하면서 PHP 파일을 읽어오는데 한글이 깨져 보여서 기본 인코딩 모드를 변경했다.



Visual Studio Code 에서 자바 실습하는 걸 해보려고 했지만 영~~ 아니다.

PHP 코드를 정렬하고자 Visual Studio Code 를 실행해서 해봤지만 확장자가 PHP 인것은 제대로 못하는 가 보다.

그래서 Eclipse 에서 PHP 모듈을 추가 설치했다.

블로그 이미지

Link2Me

,
728x90

파일 삭제 처리하는 로직 구현이다.

<?php
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php';
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
$d = new DBDataClass;

if(!isset($_GET['idx']) || empty($_GET['idx'])){
    $rs=0;
} else {
    // 등록자 여부 체크
    $R = $d->getUidData('bbs_data',$_GET['idx']);
    if($R['userID'] == $_SESSION['userID'] || (isset($_SESSION['authID']) && ($_SESSION['authID']==1 || $_SESSION['authID']==2))){
        // 등록자 또는 관리자(관리자, 최고관리자) 인 경우에는 삭제 가능
        $rs=$d->getDbDelete('bbs_data', 'uid='.$_GET['idx'].'');
        $d->getDbDelete('bbs_comment', 'parentid='.$_GET['idx'].''); //댓글 같이 삭제됨
    } else {
        $rs = -2;
    }
}
echo $rs;
?>


블로그 이미지

Link2Me

,
728x90

게시판 기능을 구현하고 있는데, 코딩 구현보다 Layout 기능 익히는 게 더 힘들어서 적어둔다.

기능 구현이 끝나면 수정해야 할 부분(보안 문제 고려사항)

error_reporting(E_ALL);
ini_set("display_errors", 1);


error_reporting(0); 로 변경해야 한다.

 

<?php

// 파일명 : bbsView.php
error_reporting(E_ALL);
ini_set("display_errors", 1);

require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php'; // PC접속은 IP 접속 허용이 된 경우에만 접속 가능
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
require_once $g['path_class'].'bbsClass.php';
$c = new bbsClass();
$d = new DBDataClass();

$bid = isset($_GET['bid']) ? $_GET['bid']: '';
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;

$R = $d->getDbData('bbs_data', 'uid="'.$_GET['uid'].'"', '*');
$html = ($R['html'] == 1) ? 'HTML' : 'TEXT';

 

// 쿠키를 이용한 조회수 중복 방지
if(!empty($R['uid']) && empty($_COOKIE['bbs_data_'.$R['uid']])) {
    if(strcmp($_SESSION['userID'],$R['userID']) !== 0){ // 등록자 본인이 아니면
        $d->getDbUpdate('bbs_data','hit=hit+1','uid='.$R['uid']);
        setcookie('bbs_data_'.$R['uid'], TRUE, time() + (60 * 60 * 24), '/');
    }
}

?>
<table class="table table-bordered table-hover table-sm" cellspacing="0" width="100%">
    <tr>
        <td style="width:70px;">제목</td>
        <td class="text-left"><?php echo $R['subject']?></td>
    </tr>
    <tr>
        <td>내용</td>
        <td class="text-left"><?php echo $c->getContents($R['content'],$html);?></td>
    </tr>
</table>

<?php include_once $g['path_bbs'].'bbsComment.php';?>

 

<div class="table-responsive text-nowrap">
    <div class="float-left info">
        <button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" type="button" id="BBSHome">목록</button>
    </div>
    <div class="float-right info">
        <?php if($R['userID'] == $_SESSION['userID'] || (isset($_SESSION['authID']) && $_SESSION['authID']==1 )):?>
        <a href="bbsWrite.php" class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" id="bbsModify" data-id="<?=$R['uid'];?>" curPage="<?=$curPage;?>">수정</a>
        <button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" type="button" id="bbsDelete" data-id="<?=$R['uid'];?>" curPage="<?=$curPage;?>">삭제</button>
        <?php endif;?>
    </div>
</div> 

 

bbsComment.php ==> 댓글 달기 및 댓글 보기

<?php

// 파일명 : bbsComment.php

error_reporting(E_ALL);
ini_set("display_errors", 1);

require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php';
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
$d = new DBDataClass();

?>

<div class="form-group">
    <form name="commentForm" class="form-inline">
        <input type="hidden" name="mode" value="new" />
        <input type="hidden" name="parentid" value="<?php echo $_GET['uid'];?>" />
        <input type="hidden" name="userID" value="<?php echo $_SESSION['userID'];?>" />
        <input type="hidden" name="userNM" value="<?php echo $_SESSION['userNM'];?>" />
        <input type="hidden" name="p" value="<?=$curPage;?>" />

        <div class="input-group mb-3" style="width:100%;">
          <input type="text" name="comment" class="form-control" placeholder="댓글을 입력하세요" aria-label="comment"
            aria-describedby="comment_form">
          <div class="input-group-append">
            <button type="button" class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" id="comment_form">댓글</button>
          </div>
        </div>

    </form>
</div>

<div class="table-responsive text-nowrap">
<table class="table table-hover table-sm" cellspacing="0" width="100%">
    <tbody>
    <?php $COMM = $d->getDbArray('bbs_comment','parentid="'.$_GET['uid'].'"','*','uid DESC',0,1);?>
    <?php while($C = mysqli_fetch_array($COMM)):?>
        <tr id="<?php echo $C['uid'];?>">
            <td class="text-left"><?php echo $C['userID'];?></td>
            <td class="text-left"><?php echo $C['comment'];?></td>
            <td><?php echo substr($C['d_regis'],0,8);?></td>
            <td>
            <button class="btn btn-md m-0 z-depth-0 comment_del" type="button" title='삭제'>&times;</button>
            </td>
        </tr>
    <?php endwhile;?>
    </tbody>
</table>
</div>

<?php
// 파일명 : bbsCommentChk.php
if(!isset($_SESSION)){
    session_start();
}
//echo '<pre>';print_r($_POST);echo '</pre>';
//exit;
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST);
    require_once 'path.php';// root 폴더를 기준으로 상대경로 자동 구하기
    require_once $g['path_config'].'dbconnect.php';
    require_once $g['path_class'].'dbDataClass.php';
    $d = new DBDataClass(); // AES_Decode()

    $comment = trim($comment);
    date_default_timezone_set('Asia/Seoul');

    if($mode == 'new'){
        $d_regis = date('YmdHis');
        $access_ip=$_SERVER['REMOTE_ADDR'];
        $QKEY = "parentid,comment,d_regis,userID,userNM,ip";
        $QVAL = "'$parentid','$comment','$d_regis','$userID','$userNM','$access_ip'";
        $d->getDbInsert('bbs_comment',$QKEY,$QVAL);
        echo 1;
    } else {
        // 등록자 여부 체크
        $R = $d->getUidData('bbs_comment',$uid);
        if($R['userID'] == $_SESSION['userID']){
            $QSET="comment='".$comment."'";
            $QVAL="uid='".$uid."'";

            $d->getDbUpdate('bbs_data',$QSET,$QVAL);
            echo 2;
        } else {
            echo -2;
        }
    }
} else {
    echo -1;
}
?>

<?php
// 파일명 : bbsCommentDelete.php
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php';
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
$d = new DBDataClass;

if(!isset($_GET['idx']) || empty($_GET['idx'])){
    $rs=0;
} else {
    // 등록자 여부 체크
    $R = $d->getUidData('bbs_comment',$_GET['idx']);
    if($R['userID'] == $_SESSION['userID'] || (isset($_SESSION['authID']) && ($_SESSION['authID']==1 || $_SESSION['authID']==2))){
        // 등록자 또는 관리자(관리자, 최고관리자) 인 경우에는 삭제 가능
        $rs=$d->getDbDelete('bbs_comment', 'uid='.$_GET['idx'].'');
    } else {
        $rs = -2;
    }
}
echo $rs;
?>

 

jQuery 처리 부분은 https://link2me.tistory.com/1665 를 참조하면 된다.

 

블로그 이미지

Link2Me

,
728x90

게시판에서 등록한 글을 MySQL DB에 저장하는 로직 구현이다.

jQuery 와 연동하여 처리하므로 MySQL DB에 등록되지 전에 값이 제대로 잘 전달되는지 체크하는 걸 시도한다.

echo '<pre>';print_r($_POST);echo '</pre>';
exit;


POST 변수가 문제없이 잘 전달되었으면 이제 관련 코드를 아래와 같이 구현했다.

게시판을 완벽하게 처리하는 것은 아니고 1단에 대한 값만 등록하고, 게시글이 등록되었을 때 관리자에게 메일이 전달되도록 하는 걸 추가했다.


<?php
if(!isset($_SESSION)){
    session_start();
}
//echo '<pre>';print_r($_POST);echo '</pre>';
//exit;
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST);
    require_once 'path.php';// root 폴더를 기준으로 상대경로 자동 구하기
    require_once $g['path_config'].'dbconnect.php';
    require_once $g['path_class'].'dbDataClass.php';
    require_once $g['path_class'].'bbsClass.php';
    $d = new DBDataClass();
    $c = new bbsClass();

    if($mode == 'write'){
        $subject = trim($subject);
        $content = trim($content);
        $html = 0;
        $depth = 1;
        $notice = 0;
        date_default_timezone_set('Asia/Seoul');

        if($uid == 0){
            $d_regis = date('YmdHis');
            $access_ip=$_SERVER['REMOTE_ADDR'];
            $QKEY = "bbsid,subject,content,html,depth,notice,d_regis,userID,userNM,ip";
            $QVAL = "'$bid','$subject','$content',$html,$depth,$notice,'$d_regis','$userID','$userNM','$access_ip'";
            $d->getDbInsert('bbs_data',$QKEY,$QVAL);
            Mail2Admin($subject,$content);
            echo 1;
        } else {
            // 등록자 여부 체크
            $R = $d->getDbData('bbs_data','uid='.$uid,'*');
            if($R['userID'] === $_SESSION['userID']){ // 관리자도 수정은 불가
                $QSET="subject='".$subject."',";
                $QSET.="content='".$content."',";
                $QSET.="html='".$html."'";
                $QVAL="uid='".$uid."'";

                $d->getDbUpdate('bbs_data',$QSET,$QVAL);
                echo 2;
            } else {
                echo -2;
            }
        }
    } else if($mode == 'delete'){
        $d->getDbDelete('bbs_data',"uid='".$uid."'");
        echo 3; // 삭제
    }
} else {
    echo -1;
}

function Mail2Admin($subject,$message){
    global $c;
    $to = "jsk005@naver.com";
    $from = "webmaster@abc.com";
    $nameFrom = "AppMaster";
    $message = $c->getContents($message,'TEXT');

    $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);
}
?>

function getContents($str,$html){
    if ($html == 'HTML'){
        $str = htmlspecialchars_decode(stripslashes($str));
        $str = str_replace('<A href=','<a target="_blank" href=',$str);
        $str = str_replace('<a href=','<a target="_blank" href=',$str);
        $str = str_replace('<a target="_blank" href="#','<a href="#',$str);
        $str = str_replace(' target="_blank">','>',$str);
        $str = str_replace('< param','<param',$str);
        $str = str_replace("\t",'&nbsp;&nbsp;&nbsp;&nbsp;',$str);
        $str = str_replace('@IFRAME@','iframe',$str);

        $str = str_replace('imgOrignWin(this.src)=""','onclick="imgOrignWin(this.src);"',$str);
        $str = str_replace('imgorignwin(this.src)=""','onclick="imgOrignWin(this.src);"',$str);
        $_atkParam = array(';a=','&a=','?a=','m=admin','system=');
        foreach($_atkParam as $_prm){
            $str = str_replace($_prm,'',$str);
        }
    }
    else {
        $str = str_replace('<','&lt;',$str);
        $str = str_replace('>','&gt;',$str);
        $str = str_replace('&nbsp;','&amp;nbsp;',$str);
        $str = str_replace("\t",'&nbsp;&nbsp;&nbsp;&nbsp;',$str);
        $str = nl2br($str);
    }
    return $str;
}



블로그 이미지

Link2Me

,
728x90

MDB(Meterial Design for Bootstrap4) 게시판을 만드는 골격과 jQuery 내용이다.

Bootstrap4 기본 문법 사항은 https://www.w3schools.com/bootstrap4/default.asp 에 잘 나와 있다.

그리고 MDB 는 https://mdbootstrap.com/ 를 수시로 참조하면 템플릿 구성을 깔끔하게 만들 수 있다.

유료 템플릿을 구입하여 사용하고 있기 때문에 무료용으로 사용할 경우에는 동작 안되는 것이 있을 수 있으니 관련 Class를 수정해서 사용하면 될 것이다.


파일을 여러개로 나누어서 필요한 파일을 Load 하여 사용하는 방법으로 구현했다.

ㅇ 상대경로 자동 설정 : https://link2me.tistory.com/1197 참조

ㅇ MDB Layout 알아둘 사항 : https://link2me.tistory.com/1593 참조

ㅇ PHP Class 개념 이해 : https://link2me.tistory.com/1164 참조.

    PHP Class 함수는 스스로 작성해야 하며, 타 게시글을 찾다보면 이해하게 될 것으로 본다.

    게시판 구현에 필요한 대부분의 코드는 기록했지만 PHP Class 는 오픈하지 않았다.

    스스로 만들어야 할 영역이라고 보면 된다.

    DBDataClass.php 에 대한 사항은 https://link2me.tistory.com/1680 에 오픈했다.

ㅇ 페이징 처리 : https://link2me.tistory.com/1112 참조하되, bootstrap4 기반으로 Class 변경하는 건 직접 하시라.

    PHP Class 작성에 대한 일부 코드를 확인할 수 있다.


bbs.php 파일

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

<body class="fixed-sn mdb-skin">
<header>
    <!-- Navbar -->
    <nav class="navbar fixed-top navbar-toggleable-md navbar-expand-lg scrolling-navbar double-nav">
        <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>
    <div class="container-fluid text-center">
        <div class="row">
            <div class="col-md-12">
                <div class="content" id="panel_content">
                <!-- 불러온 파일이 이곳에 위치되도록 jQuery 처리 -->
                </div>
                <div id="ajaxPath" data-path="<?=$g['path_menu'];?>"></div>
            </div>
        </div>
    </div>
</main>
<!--/Main layout-->

<!-- SCRIPTS -->
<?php require_once $g['path_layout'].'default/_import.tail.php';?>
<script>
// SideNav Initialization
$(".button-collapse").sideNav();
new WOW().init();

var uri = (uri != null)? uri : "bbsList.php";
var datapath =$("#ajaxPath").attr('data-path');
var bbsid = (bbsid != null)? bbsid : "toadmin";

BBSListTable(uri,bbsid,1,'','',0);

$('.memberShow').on('click',function(e){
    e.preventDefault(); // a 링크, submit 실행 방지
    url = $(this).attr('href');
    var item = $(this).text();
    if(url != '#'){
        $('#panel_content').load(url, function(){
            ModifyData();
        });
    }
});

function fixedEncodeURIComponent (str) {
    // encodeURIComponent 함수는 IE8 이상의 브라우저에서 모두 지원한다.
    return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
}

function BBSListTable(uri,bbsid,curPage,where,keyword,idx){
    $('#panel_content').load(uri+'?bid='+bbsid+'&p='+curPage+'&where='+where+'&keyword='+fixedEncodeURIComponent(keyword)+'&uid='+idx, function() {
        //e.preventDefault();
        var curPage = $('#paging .act a').text();
        $('#paging li').click(function(e) {
            e.preventDefault();
            switch($(this).text()){
                case '◁':
                    curPage=parseInt($(this).next().text()) - 1;
                    break;
                case '▷':
                    curPage=parseInt($(this).prev().text()) + 1;
                    break;
                default:
                    curPage = $(this).text();
                    break;
            }
            BBSListTable(uri,bbsid,curPage,where,keyword,0);
        });

        $('#BBSListTable tbody tr').mouseover(function() {
            $(this).children().css({
                'backgroundColor' : '#DCDCDC', 'cursor' : 'pointer'
            });
        }).mouseout(function() {
            $(this).children().css({
                'backgroundColor' : '#FFFFFF', 'cursor' : 'default'
            });
        }).click(function() {
            var idx = $(this).attr('id');
            var bbsid =$("#bbsid").attr('data-bbsid');
            uri = "bbsView.php";
            BBSListTable(uri,bbsid,curPage,where,keyword,idx);
        });

        $('#BBSSearch').click(function(e){
            BBSSearch(uri,bbsid,curPage,where,keyword);
        });

        $('#BBSSearchKeyword').on('keypress', function(event){
            var agent = navigator.userAgent.toLowerCase();
            var keycode = (event.keyCode ? event.keyCode : event.which);
            if(keycode == 13){ // 엔터키가 입력될 때까지는 어떤 것도 하지 않는다.
                event.preventDefault(); // 엔터키가 입력되면 현재 이벤트의 기본 동작을 중단한다.
                //event.stopPropagation(); // 현재 이벤트가 상위로 전파되지 않도록 중단한다.
                BBSSearch(uri,bbsid,curPage,where,keyword);
            }
        });

        $('#BBSHome').click(function(e){
            e.preventDefault();
            uri = "bbsList.php";
            BBSListTable(uri,bbsid,1,'','',0);
        });

        $('#bbsWrite').click(function(e){
            e.preventDefault();
            uri = $(this).attr('href');
            var bbsid =$("#bbsid").attr('data-bbsid');
            BBSListTable(uri,bbsid,curPage,where,keyword,0);
        });

        $('#bbsModify').click(function(e){
            e.preventDefault();
            uri = $(this).attr('href');
            var idx = $(this).attr('data-id');
            var page = $(this).attr('curPage');
            BBSListTable(uri,bbsid,page,where,keyword,idx);
        });

        $('#bbsRegister').click(function(){
            var subject = $('#subject');
            var content = $('textarea#content');
            var curPage = $('input[name=p]').val();

            if(subject.val() ==''){
                alert('제목을 입력하세요');
                subject.focus();
                return false;
            }

            if(content.val() ==''){
                alert('내용을 입력하세요');
                content.focus();
                return false;
            }

            $.ajax({
                url:datapath+'bbsWriteChk.php',
                type: 'POST',
                data: $("#bbswriteForm").serializeArray(),
                dataType:'text',
                success:function(msg){
                    if(msg == 1){
                        alert('등록했습니다.');
                        BBSListTable(uri,bbsid,curPage,where,keyword,idx);
                    } else if(msg == 2){
                        alert('수정했습니다.');
                        BBSListTable(uri,bbsid,curPage,where,keyword,idx);
                    } else if(msg==-2){
                        alert('수정권한이 없습니다.');
                    } else {
                        alert('데이터를 다시 한번 확인하세요\n'+msg);
                        return false;
                    }
                },
                error: function(jqXHR, textStatus, errorThrown){
                    alert("arjax error : " + textStatus + "\n" + errorThrown);
                }
            });

        });

        $('#bbsDelete').click(function(e){
            e.preventDefault();
            var idx = $(this).attr('data-id');
            var page = $(this).attr('curPage');
            BBSDelete(uri,bbsid,page,where,keyword,idx);
        });

        $('#comment_form').click(function(e){
            e.preventDefault();
            var comment = $("input[name=comment]");
            if(comment.val() ==''){
                alert('댓글을 입력하세요');
                comment.focus();
                return false;
            }
            var page = $("input[name=p]").val();
            var uid = $("input[name=parentid]").val();

            $.ajax({
                url:datapath+'bbsCommentChk.php',
                type: 'POST',
                data: {
                    mode:$("input[name=mode]").val(),
                    parentid:uid,
                    userID:$("input[name=userID]").val(),
                    userNM:$("input[name=userNM]").val(),
                    comment:$("input[name=comment]").val()
                    },
                dataType:'text',
                success:function(msg){
                    if(msg == 1){
                        alert('등록했습니다.');
                        uri = "bbsView.php";
                        BBSListTable(uri,bbsid,page,where,keyword,uid);
                    } else if(msg==-2){
                        alert('수정권한이 없습니다.');
                        return false;
                    } else {
                        alert('데이터를 다시 한번 확인하세요\n'+msg);
                        return false;
                    }
                },
                error: function(jqXHR, textStatus, errorThrown){
                    alert("arjax error : " + textStatus + "\n" + errorThrown);
                }
            });

        });

        $(".comment_del").click(function(){
            var idx = $(this).parent().parent().attr('id');
            var page = $("input[name=p]").val();
            CommnetDelete(uri,bbsid,page,where,keyword,idx);
        });

        $('#ToMainPage').click(function(){
            window.location.href = $(location).attr('protocol')+"//"+$(location).attr('host');
        });

    });
}

function BBSSearch(uri,bbsid,curPage,where,keyword){
    var where = $('[name=where]').val();
    var keyword = $('[name=keyword]').val();
    if(keyword.length == 0){
        alert('검색어를 입력하세요');
        $('input[name=keyword]').focus();
        return false;
    }
    BBSListTable(uri,bbsid,1,where,keyword,0);
}

function BBSDelete(uri,bbsid,curPage,where,keyword,idx){
    var verify = confirm('삭제하시겠습니까? \n 복구할 수 없습니다.');
    if (verify) {
        $.get('bbsDelete.php?idx='+idx, function(msg) {
            if (msg == 1) {
                alert('삭제되었습니다.');
                uri = "bbsList.php";
                BBSListTable(uri,bbsid,curPage,where,keyword,0);
            } else if(msg == -2){
                alert('삭제 권한이 없습니다.');
            } else {
                alert('삭제중 오류가 발생하였습니다.\n'+msg); // 디버깅 모드
                //alert('삭제중 오류가 발생하였습니다.'); // 운용 모드
            }
        });
    }
}

function CommnetDelete(uri,bbsid,curPage,where,keyword,idx){
    var verify = confirm('삭제하시겠습니까? \n 복구할 수 없습니다.');
    if (verify) {
        $.get('bbsCommentDelete.php?idx='+idx, function(msg) {
            if (msg == 1) {
                uri = "bbsView.php";
                var uid = $("input[name=parentid]").val();
                BBSListTable(uri,bbsid,curPage,where,keyword,uid);
            } else if(msg == -2){
                alert('삭제 권한이 없습니다.');
            } else {
                alert('삭제중 오류가 발생하였습니다.\n'+msg);
                //alert('삭제중 오류가 발생하였습니다.');
            }
        });
    }
}
</script>
</body>
</html>

<?php

// 파일명 : bbsList.php

error_reporting(0);
// 테이블 접속 처리
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php';
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
require_once $g['path_class'].'bbsClass.php';
require_once $g['path_class'].'adminClass.php';
$a = new adminClass();
$b = new bbsClass();
$d = new DBDataClass(); // AES_Decode()

$bbsid = isset($_GET['bid']) ? $_GET['bid'] :'toadmin';
$link_url = "bbs.php"; // 현재 실행중인 파일명 가져오기
$page = isset($_GET['page'])? trim($_GET['page']):1;//페이지 변수 설정
$rowsPage = ($mtype == 3) ? 5 : 5; // 한 화면에 표시되는 게시글 수
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;
$m = isset($_GET['m']) ? $_GET['m'] :'list';

$flddata ="*";// 화면에 출력할 칼럼 발췌
$where = isset($_GET['where']) ? $_GET['where']: '';
$keyword = isset($_GET['keyword']) ? $_GET['keyword']: '';
$xorderby= isset($xorderby) ? $xorderby : 'uid DESC'; // 없는 칼럼인지 꼭 체크하라.

$sqlque = 'display=1';
$uri =isset($_GET['uri'])? $_GET['uri']:'';

if($where && $keyword) {
    if($where == 'subject') $sqlque .= " and (subject LIKE '%".$keyword."%') ";
    if($where == 'userNM') $sqlque .= " and (userNM LIKE '%".$keyword."%') ";
}

$g['url_link']=($m?'m='.$m.'&amp;':'').($where?'where='.$where.'&amp;':'').($keyword?'keyword='.urlencode(stripslashes($keyword)).'&amp;':'');
$g['bbs_reset'] = $link_url.'?'.($m?'m='.$m.'&amp;':'');

$table ='bbs_data'; // 기본 접속 테이블과 동일구조 테이블 처리
$NUM = $d->getDbRows($table,$sqlque); // 전체 게시글수
$TPG = $b->getTotalPage($NUM,$rowsPage);
$rows= $d->getDbArray($table,$sqlque,$flddata,$xorderby,$rowsPage,$curPage);
$i = 0;
?>
<div class="table-responsive text-nowrap">
<div class="float-left">
    <?php if( $keyword ):?><strong>"<?php echo $keyword?>"</strong> 검색결과 : <?php endif?>
    <?php echo number_format($NUM)?>개 (<?php echo $curPage;?>/<?php echo $TPG;?>페이지)
</div>
<div class="float-right">
    <?php if(isset($_SESSION['userID'])):?>
    <a href="bbsWrite.php" class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 " id="bbsWrite">글쓰기</a>
    <?php endif?>
    <div id="bbsid" data-bbsid="<?php echo $bbsid; ?>"></div>
</div>

<table id="BBSListTable" class="table table-striped table-bordered table-hover table-sm" cellspacing="0" width="100%">
    <thead align='center'>
        <tr>
            <?php if($mtype==3):?><th scope="col">No</th><?php endif;?>
            <th scope="col">제목</th>
            <?php if($mtype==3):?><th scope="col">이름</th><?php endif;?>
            <th scope="col">날짜</th>
            <?php if($mtype==3):?><th scope="col">조회수</th><?php endif;?>
        </tr>
    </thead>
    <tbody>
        <?php if($NUM == 0):?>
        <tr>
            <td colspan="5">등록된 게시글이 없습니다.</td>
        </tr>
        <?php else:?>
        <?php foreach($rows as $R):?>
        <?php
            $no = $NUM - (($curPage - 1) * $rowsPage) - $i;
            $i++;
        ?>
        <tr id="<?php echo $R['uid']; ?>">
            <?php if($mtype==3):?><td><?php echo $no;?></td><?php endif;?>
            <td class="text-left"><?php echo $R['subject'];?></td>
            <?php if($mtype==3):?><td><?php echo $R['userNM'];?></td><?php endif;?>
            <td><?php echo substr($R['d_regis'],0,8);?></td>
            <?php if($mtype==3):?><td><?php echo $R['hit'];?></td><?php endif;?>
        </tr>
        <?php endforeach;?>
        <?php endif;?>
    </tbody>
</table>
<div class='form-group'>
    <form name="BBSForm" class="form-inline" action="<?php echo $link_url;?>">
        <input type="hidden" name="m" value="<?php echo $m;?>" />
        <input type="hidden" name="orderby" value="<?php echo $xorderby;?>" />

        <div class="input-group mb-3" style="width:80px;">
            <select name="where" class="browser-default custom-select">
                <option value="subject">제목</option>
                <option value="userNM">등록자</option>
            </select>
        </div>
        <div class="input-group mb-3" style="width:calc(100% - 80px);">
          <input type="text" name="keyword" class="form-control" id="BBSSearchKeyword">
          <div class="input-group-append">
            <button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" type="button" id="BBSSearch">검색</button>
            <button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" type="button" id="BBSHome">목록</button>
          </div>
        </div>
    </form>
</div>
<?php $b->PageLinkView($link_url,$NUM,$rowsPage,$curPage,$g['url_link']);?>
<button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0" id="ToMainPage" type="button">메인화면 전환</button>
</div>

<?php

// 파일명 : bbsView.php

error_reporting(0);
// error_reporting(E_ALL); 
//ini_set("display_errors", 1);

require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php';
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
require_once $g['path_class'].'bbsClass.php';
$c = new bbsClass();
$d = new DBDataClass();

$bid = isset($_GET['bid']) ? $_GET['bid']: '';
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;

$R = $d->getDbData('bbs_data', 'uid='.$_GET['uid'], '*');
$html = ($R['html'] == 1) ? 'HTML' : 'TEXT';
?>
<table class="table table-bordered table-hover table-sm" cellspacing="0" width="100%">
    <tr>
        <td style="width:70px;">제목</td>
        <td class="text-left"><?php echo $R['subject']?></td>
    </tr>
    <tr>
        <td>내용</td>
        <td class="text-left"><?php echo $c->getContents($R['content'],$html);?></td>
    </tr>
</table>

<?php include_once $g['path_bbs'].'bbsComment.php';?>

<div class="table-responsive text-nowrap">
    <div class="float-left info">
        <button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" type="button" id="BBSHome">목록</button>
    </div>
    <div class="float-right info">
        <?php if($R['userID'] == $_SESSION['userID']):?>
        <a href="bbsWrite.php" class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" id="bbsModify" data-id="<?=$R['uid'];?>" curPage="<?=$curPage;?>">수정</a>
        <?php endif;?>
        <?php if($R['userID'] == $_SESSION['userID'] || (isset($_SESSION['authID']) && $_SESSION['authID']==1) ):?>
        <button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" type="button" id="bbsDelete" data-id="<?=$R['uid'];?>" curPage="<?=$curPage;?>">삭제</button>
        <?php endif;?>
    </div>
</div>

<?php

// 파일명 : bbsWrite.php

error_reporting(0);
// 테이블 접속 처리
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
if($mtype == 3){
    require_once $g['path_root'].'ipFiltering.php';
}
require_once $g['path_config'].'config.php';
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
require_once $g['path_class'].'bbsClass.php';
require_once $g['path_class'].'adminClass.php';
$a = new adminClass();
$b = new bbsClass();
$d = new DBDataClass(); // AES_Decode()

$bid = isset($_GET['bid']) ? $_GET['bid']: '';
$uid = isset($_GET['uid']) ? $_GET['uid']: '';
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;
$R=$d->getUidData('bbs_data',$uid);

?>
<div class="table-responsive text-nowrap">
<form id="bbswriteForm" method="post" class="text-center border border-light p-5">
    <table class="table table-striped table-bordered table-hover table-sm" cellspacing="0" width="100%">
        <input type="hidden" name="mode" value="write" />
        <input type="hidden" name="p" value="<?php echo $curPage;?>" />
        <input type="hidden" name="bid" value="<?php echo $bid;?>" />
        <input type="hidden" name="uid" value="<?php echo $R['uid'];?>" />
        <input type="hidden" name="userID" value="<?php echo $_SESSION['userID'];?>" />
        <input type="hidden" name="userNM" value="<?php echo $_SESSION['userNM'];?>" />

        <tr>
            <td>제목</td>
            <td><input type="text" name="subject" id="subject" class="form-control mb-4" placeholder="제목을 입력하세요" value="<?=$R['subject'];?>"></td>
        </tr>

        <tr>
            <td>내용</td>
            <td><textarea name="content" id="content" class="form-control mb-4" rows="8"  placeholder="내용을 입력하세요"><?=$R['content'];?></textarea></td>
        </tr>

        <tr>
            <td colspan="2">
            <button class="btn btn-info btn-block my-4" id="bbsRegister" type="submit">등록</button>
            </td>
        </tr>
    </table>
</form>
</div>


블로그 이미지

Link2Me

,
728x90

게시판 테이블 샘플이다.


CREATE TABLE IF NOT EXISTS `bbs_data` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `display` tinyint(2) NOT NULL DEFAULT '1',
  `bbsid` varchar(30) NOT NULL DEFAULT '',
  `level` tinyint(4) NOT NULL DEFAULT '1',
  `subject` varchar(200) NOT NULL DEFAULT '',
  `content` text NOT NULL,
  `html` tinyint(2) NOT NULL DEFAULT '0',
  `depth` tinyint(4) NOT NULL DEFAULT '0',
  `d_regis` varchar(14) NOT NULL DEFAULT '',
  `d_modify` varchar(14) NOT NULL DEFAULT '',
  `hit` int(11) NOT NULL DEFAULT '0',
  `hidden` tinyint(4) NOT NULL DEFAULT '0',
  `notice` tinyint(4) NOT NULL DEFAULT '0',
  `userID` varchar(60) NOT NULL,
  `userNM` varchar(50) NOT NULL,
  `ip` varchar(25) NOT NULL DEFAULT '',
  PRIMARY KEY (`uid`),
  KEY `bbsid` (`bbsid`),
  KEY `subject` (`subject`),
  KEY `userID` (`userID`),
  KEY `d_regis` (`d_regis`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;



댓글 게시판 테이블


CREATE TABLE IF NOT EXISTS `bbs_comment` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `parentid` int(11) NOT NULL DEFAULT '0',
  `comment` varchar(255) NOT NULL DEFAULT '',
  `d_regis` varchar(14) NOT NULL DEFAULT '',
  `userID` varchar(50) NOT NULL DEFAULT '',
  `userNM` varchar(50) NOT NULL,
  `ip` varchar(25) NOT NULL DEFAULT '',
  PRIMARY KEY (`uid`),
  KEY `parent_uid` (`parentid`),
  KEY `d_regis` (`d_regis`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;


블로그 이미지

Link2Me

,
728x90

일반적으로 HTML5 에서는 아래와 같이 테이블 체크박스를 만들어주면 된다.

<table>
    <thead>
        <tr>
            <th><input type="checkbox" id="chkall" /></th>
            <th>성명</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><input type="checkbox" /></td>
            <td>홍길동</td>
        </tr>
        <tr>
            <td><input type="checkbox" /></td>
            <td>이상철</td>
        </tr>
    </tbody>
</table>

 

하지만 MDB에서는 이렇게 하면 동작 자체가 안된다.

아래와 같은 형식으로 구현해야 코드가 동작한다.

<table>
    <thead>
        <tr>
            <th>
            <th scope="col">
                <div class="form-check form-check-inline">
                <input type="checkbox" class="form-check-input" id="checkall">
                <label class="form-check-label" for="checkall"></label>
                </div>
            </th>
            </th>
            <th>성명</th>
        </tr>
    </thead>
    <tbody>
        <?php $i=0; while($R = mysql_fetch_array($RCD)):?>
        <tr>
            <td>
                <div class="form-check form-check-inline">
                <input type="checkbox" class="form-check-input" name="uid[]" id="<?php echo $R['uid'];?>" value="<?php echo $R['uid'];?>">
                <label class="form-check-label" for="<?php echo $R['uid'];?>"></label>
                </div>
            </td>
            <td><?php echo $R['name'];?></td>
        </tr>
        <?php $i++; endwhile;?>
    </tbody>
</table>

 

$i=0;
while($R = mysqli_fetch_array($result)){
	$i++;
	if(in_array($R['id'],$cat3Array)){ 
		echo'<div class="form-check form-check-inline">
			  <input type="checkbox" class="form-check-input" name="cat3[]" id="'.$R['id'].'" value="'.$R['id'].'" checked>
			  <label class="form-check-label" for="'.$R['id'].'">'.$R['name'].'</label>
			</div>';
	} else {
		echo'<div class="form-check form-check-inline">
			  <input type="checkbox" class="form-check-input" name="cat3[]" id="'.$R['id'].'" value="'.$R['id'].'">
			  <label class="form-check-label" for="'.$R['id'].'">'.$R['name'].'</label>
			</div>';
	}
	if($i % 3 == 0) {echo '<br />';}
}

 

필요할 때만 구현하다보니검색하고 사용법을 익히는 시간 낭비를 하게 되어 적어둔다.

블로그 이미지

Link2Me

,
728x90

File Download 공격

설명
서버에 존재하는 파일이 의도하지 않게 클라이언트로 다운로드 되는 취약점이다.
해커가 원하는 파일을 임의로 다운로드하거나 파일 내용을 노출시킬 수 있다.
 발생원인 애플리케이션 로직에서 파일을 클라이언트로 다운로드할 때 입력 값 검증을 하지 않을 경우 발생한다.
 위험성 공격자에게 권한이 없는 데이터를 획득할 수 있도록 하며, 시스템 정보 등 중요 파일을 획득할 수 있도록 한다.
 대응 ㅇ외부 입력값을 자원의 식별자로 사용하는 경우, 철저하게 검증한 후 사용한다.
ㅇ사용자별 사용 가능한 자원을 사전에 리스트로 정의하여 사용 범위를 제한한다.
ㅇ파일을 사용하는 경우, 파일명에 경로순회공격 위험이 있는 문자를 제거하는 필터를 이용한다.

 

공격자가 의도적인 공격을 방지하기 위해서는 웹사이트 게시판, 자료실 등에서 php 프로그램을 이용하여 파일을 다운로드 받은 페이지가 있는지 조사를 한다.

 

$file_id = $_REQUEST['file_id'];
if(strstr($file_id,"../") || strstr($file_id,"..\\")){
    echo "<script>alert('Access Denied!')</script>";
    exit;
}

 

와 같이 ../ 상위 폴더로 이동을 못하게 검사하여 처리하는 걸 추가한다.

JSP, CGI 기반인 경우에는 관련 코드를 추가하여 방지하도록 한다.

 

<?php
require_once '../sessionChk.php'// 세션 체크
 
if(isset($_GET['filename'])) {
    $filename = $_GET['filename'];
    if(strstr($filename,"../"|| strstr($filename,"..\\")){
        echo("<meta http-equiv='Refresh' content='0; URL=/error.php'>");;
        exit;
    }
 
else {
    $dir="../files/"
    $filename = "upload_form.xlsx";
 
    if (file_exists($dir.$filename)) {
         header("Content-Type: application/octet-stream");
         header("Content-Disposition: attachment;; filename=$filename");
         header("Content-Transfer-Encoding: binary"); 
         header("Content-Length: ".(string)(filesize($dir.$filename))); 
         header("Cache-Control: cache, must-revalidate"); 
         header("Pragma: no-cache"); 
         header("Expires: 0"); 
         $fp = fopen($dir.$filename"rb"); //rb 읽기전용 바이러니 타입
         if(!fpassthru($fp)) {
             fclose($fp);                        
         }
    } else {
        header("Location: /"); // web root 디렉토리로 이동
        exit;
    }
}
 
?>

 

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

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

Link2Me

,