728x90

파일 다운로드 코드 예시이다.

파일을 업로드할 때 실제 저장되는 파일명과 View 파일 화면상에 출력되는 파일명이 다르게 처리하는 것이 중요하다.

 

<?php
if(!isset($_SESSION)){
    session_start();
}
 
require_once 'path.php';// root 폴더를 기준으로 상대경로 자동 구하기
require_once $g['path_config'].'dbconnect.php';
require_once $g['path_class'].'dbDataClass.php';
require_once $g['path_class'].'dbconnect.php';
require_once $g['path_class'].'loginClass.php';
 
$c = new LoginClass;
$d = new LegacyDBClass;
 
$idx = $_GET['idx'];
$idx = preg_replace("/[^0-9]/","",$idx);
 
$R = $d->getDbData('bbs_data','idx='.$idx,'*');
 
$file_path$g['path_root'].'files/infile/' . $R['filename'];
$filename = urldecode($R['realname']);
if(file_exists($file_path)) { 
    header("Content-disposition: attachment; filename={$filename}"); //Tell the filename to the browser    
    header('Content-type: application/octet-stream'); //Stream as a binary file! So it would force browser to download    
    readfile($file_path); //Read and stream the file
else {    
    echo -2;
}
 
?>
 

 

 

블로그 이미지

Link2Me

,
728x90

PHP 위지윅 에디터로 DB에 저장된 게시글을 열람하는 코드 예시이다.

파일 다운로드하는 코드를 jQuery 로 구현하려고 했으나 원하지 않는 결과가 나와서 a href 링크를 그대로 활용하여 코드를 구현했다.

<?php
error_reporting(0);
/*
ini_set("display_startup_errors", 1);
ini_set("display_errors", 1);
error_reporting(E_ALL);
// */
 
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php';
require_once $g['path_root'].'deviceChk.php';
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'].'dbconnect.php';
require_once $g['path_class'].'adminClass.php';
require_once $g['path_class'].'bbsClass.php';
 
$a = new adminClass();
$b = new bbsClass();
$d = new LegacyDBClass;
 
$idx = preg_replace("/[^0-9]/"""$_GET['idx']); // 숫자 이외 제거
$curPage = isset($_GET['p']) ? $d->XSSFilter($_GET['p']) : 1;
 
$R = $d->getDbData('bbs_data''idx='.$idx'*');
$html = ($R['html'== 1) ? 'HTML' : 'TEXT';
 
// 쿠키를 이용한 중복 조회수 증가 방지
if(!empty($R['idx']) && empty($_COOKIE['bbs_data_'.$R['idx']])) {
    if(strcmp($_SESSION['userID'],$R['userID']) !== 0){ // 등록자 본인이 아니면
        $d->getDbUpdate('bbs_data','hit=hit+1','idx='.$R['idx']); // 확인 필요
        setcookie('bbs_data_'.$R['idx'], TRUE, time() + (60 * 60 * 24), '/');
    }
}
 
$status = array('','접수','처리중','처리완료','처리불가');
 
$imgpath="../img/etc/";
 
?>
<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 $b->conv_content($R['content']);?></td>
    </tr>
    <tr>
        <td>등록자</td>
        <td class="text-left"><?php echo $d->letterMasking($R['userNM']);?></td>
    </tr>
    <tr>
        <td>첨부파일</td>
        <td class="text-left">
            <a href="bbsFiledown.php?idx=<?=$R['idx'];?>">
            <span class="badge badge-pill badge-secondary" id="attachFile" data-toggle="tooltip" title="누르면 다운로드 가능합니다.">
            <?php echo $R['realname'];?></span>
            </a>
        </td>
    </tr>
</table>
 
<?php include_once $g['path_admin'].'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 id="bbsView" data-id="<?=$R['idx'];?>" curPage="<?=$curPage;?>"></div>
    </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">수정</a>
        <?php endif;?>
        <?php if($R['userID'== $_SESSION['userID'] ):?>
        <button class="btn btn-md btn-outline-default m-0 px-3 py-2 z-depth-0 waves-effect" type="button" id="bbsDelete">삭제</button>
        <?php endif;?>
    </div>
</div>
<script>
$('#BBSHome').click(function(e) {
    e.preventDefault();
    var uri = "bbsList.php";
    var page = $('#bbsView').attr('curPage');
    MemberListTable('','',page,uri,'','','','','');
});
 
$('#bbsModify').click(function(e){
    e.preventDefault();
    var uri = $(this).attr('href');
    var idx = $('#bbsView').attr('data-id');
    var page = $('#bbsView').attr('curPage');
    MemberListTable('','',page,uri,'','','','',idx);
});
 
$('#bbsDelete').click(function(e){
    e.preventDefault();
    var idx = $('#bbsView').attr('data-id');
    var curPage = $('#bbsView').attr('curPage');
 
    var verify = confirm('삭제하시겠습니까? \n 복구할 수 없습니다.');
    if (verify) {
        $.ajax({
            url:'bbsDelete.php',
            type: 'POST',
            data: { 
                idx:encrypt.encrypt(idx) 
            },
            dataType:'text',
            success:function(msg){
                if (msg == 1) {
                    alert('삭제되었습니다.');
                    var uri = "bbsList.php";
                    MemberListTable('','',curPage,uri,'','','','',0);
                } else if(msg == -2){
                    alert('삭제 권한이 없습니다.');
                } else {
                    alert('삭제중 오류가 발생하였습니다.');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("ajax error : " + textStatus + "\n" + errorThrown);
            }
        });
 
    }
 
});
 
$('#bbsStatus').click(function(e){
    e.preventDefault();
    var uri = "bbsStatus.php";
    var idx = $('#bbsView').attr('data-id');
    var page = $('#bbsView').attr('curPage');
    MemberListTable('','',page,uri,'','','','',idx);
});
 
$('#comment_form').click(function(e){
    e.preventDefault();
    var comment = $("textarea[name=comment]");
    if(comment.val() ==''){
        alert('댓글을 입력하세요');
        comment.focus();
        return false;
    }
    var page = $("input[name=p]").val();
    var idx = $("input[name=parentid]").val();
 
    $.ajax({
        url:'bbsCommentChk.php',
        type: 'POST',
        data: {
            mode:$("input[name=mode]").val(),
            parentid:idx,
            comment:$("textarea[name=comment]").val()
            },
        dataType:'text',
        success:function(msg){
            if(msg == 1){
                alert('등록했습니다.');
                uri = "bbsView.php";
                MemberListTable('','',page,uri,'','','','',idx);
            } else if(msg==-2){
                alert('수정권한이 없습니다.');
                return false;
            } else {
                alert('데이터를 다시 한번 확인하세요\n'+msg);
                return false;
            }
        },
        error: function(jqXHR, textStatus, errorThrown){
            alert("ajax error : " + textStatus + "\n" + errorThrown);
        }
    });
 
});
 
$(".comment_del").click(function(){
    var idx = $(this).parent().parent().attr('id');
    var page = $("input[name=p]").val();
    CommnetDelete(page,idx);
});
 
function CommnetDelete(curPage,idx){
    var verify = confirm('삭제하시겠습니까? \n 복구할 수 없습니다.');
    if (verify) {
        $.ajax({
            url:'bbsCommentDelete.php',
            type: 'POST',
            data: { 
                idx:encrypt.encrypt(idx) 
            },
            dataType:'text',
            success:function(msg){
                if (msg == 1) {
                    uri = "bbsView.php";
                    var idx = $("input[name=parentid]").val();
                    MemberListTable('','',curPage,uri,'','','','',idx);
                } else if(msg == -2){
                    alert('삭제 권한이 없습니다.');
                } else {
                    //alert('삭제중 오류가 발생하였습니다.\n'+msg);
                    alert('삭제중 오류가 발생하였습니다.');
                }
            },
            error: function(jqXHR, textStatus, errorThrown){
                alert("ajax error : " + textStatus + "\n" + errorThrown);
            }
        });
    }
 
}
 
</script>
 

 

 

블로그 이미지

Link2Me

,
728x90

PHP 위지윅 에디터를 이용하여 글쓰기를 할 경우 DB에 저장하는 코드 예시이다.

https://link2me.tistory.com/1142 게시글을 참조하면 도움된다.

 

 
<?php
if(!isset($_SESSION)){
    session_start();
}
// 데이터가 제대로 넘어오는지 검증 목적
//echo '<pre>';print_r($_POST);echo '</pre>';
//echo '<pre>';print_r($_FILES);echo '</pre>';
//exit;
if(isset($_POST&& $_SERVER['REQUEST_METHOD'== "POST"){
    @extract($_POST);
    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';
    require_once $g['path_class'].'dbconnect.php';
    require_once $g['path_class'].'adminClass.php';
    require_once $g['path_class'].'bbsClass.php';
 
    $a = new adminClass();
    $b = new bbsClass();
    $d = new LegacyDBClass;
 
    if($mode == 'write'){
        $subject = trim($subject);
        $subject = preg_replace("/\s{2,}/"" "$subject); // s(공백문자)가 2회 이상을 1번으로 변경
        $subject = $d->XSSFilter($subject);
        $content = trim($content);
        $content = $b->html_purifier($content);
 
        $filename = NULL;
        if ($_FILES['file']['name']) { // 파일이 첨부되어 있으면
            $allowed_ext = array('jpg','bmp','png','gif','zip','doc','xls''xlsx');
            $tmpname = $_FILES['file']['tmp_name'];  // 임시파일명
            $realname  = $_FILES['file']['name'];  // 실제 파일명
            $filesize  = $_FILES['file']['size']; // 파일 크기
            $filetype  = $_FILES['file']['type']; // 파일 형태
 
            if (!$_FILES['file']['error']) { // 오류 없이 파일 업로드 성공
 
                $fileExt = $b->getExt($realname);  // 파일 확장자 구하는 함수
                if(!in_array($fileExt$allowed_ext)) {
                    @unlink($tmpname);
                    echo "-1"// 허용되지 않는 확장자
                    exit;
                } else {
                    // 새로운 파일을 업로드하면 기존 파일은 삭제 처리
                    if($idx > 0) {
                        $R = $d->getDbData('bbs_data','idx='.$idx,'*');
                        if($R['filename'!= NULL) {
                            $oldfilename = $g['path_root'].'files/infile/' . $R['filename'];
                            @unlink($oldfilename);
                        }
                    }
 
                    // 신규 파일 등록
                    if (is_uploaded_file($tmpname)){  // 임시파일이 디렉토리에 존재하는 경우
                        $filename = date("Ymd").md5(uniqid($tmpname)) .round(microtime(true)).'.'.$fileExt;
                        $uploadFile = $g['path_root'].'files/infile/' . $filename//change this directory
                        if(move_uploaded_file($tmpname$uploadFile)){ // 임시 디렉토리에 있던 파일을 실제 파일의 이름으로 전송
                            @chmod($uploadFile,0606); // 리눅스에서 파일 권한 변경
                        }
                    }
                }
            }
        }
 
        $userID = $_SESSION['userID'];
        $userNM = $_SESSION['userNM'];
        $html = 1;
        $depth = 1;
        $notice = 0;
        date_default_timezone_set('Asia/Seoul');
 
        if($idx == 0){
            $d_regis = date('YmdHis');
            $access_ip=$d->getClientIP();
            $QKEY = "subject,content,html,depth,filename,notice,d_regis,userID,userNM,ip";
            $QVAL = "'$subject','$content',$html,$depth,'$filename',$notice,'$d_regis','$userID','$userNM','$access_ip'";
            $d->getDbInsert('bbs_data',$QKEY,$QVAL);
            echo 1;
        } else {
            $idx = $d->XSSFilter($idx);
            // 등록자 여부 체크
            $R = $d->getDbData('bbs_data','idx='.$idx,'*');
            if($R['userID'=== $_SESSION['userID']){ // 등록자만 수정 가능하며 관리자도 수정은 불가
                $QSET="subject='".$subject."',";
                $QSET.="content='".$content."',";
                $QSET.="filename='".$filename."',";
                $QSET.="html='".$html."'";
                $QVAL="idx='".$idx."'";
 
                $d->getDbUpdate('bbs_data',$QSET,$QVAL);
                echo 2;
            } else {
                echo -2;
            }
        }
    } else if($mode == 'delete'){
        $d->getDbDelete('bbs_data',"idx='".$idx."'");
        echo 3// 삭제
    }
else {
    echo -1;
}
 
?>
 

여기서 더 신경써야 할 사항은

realname 을 DB 칼럼에 추가로 저장하는 것이다.

그래야 파일 다운로드할 때 실제 서버에 저장된 파일명과 다운로드되는 파일명이 달라서 해킹 시도 자체를 방지하는 것이 된다.

 

 

 

최근에는 그누보드 소스코드를 분석해보니 html_purifier 를 이용하여 악성코드를 걸러내고 있는 거 같다.

아래 코드는 위에 사용하는 Class 내에 있는 함수들 중에서 일부 발췌하였다.

<?php
class bbsClass extends DBDataClass {
 
    function conv_content($content){
        $source = array();
        $target = array();
 
        $source[] = "//";
        $target[] = "";
 
            $source[] = "/\n/";
            $target[] = "<br/>";
 
        // 테이블 태그의 개수를 세어 테이블이 깨지지 않도록 한다.
        $table_begin_count = substr_count(strtolower($content), "<table");
        $table_end_count = substr_count(strtolower($content), "</table");
        for ($i=$table_end_count$i<$table_begin_count$i++)
        {
            $content .= "</table>";
        }
 
        $content = preg_replace($source$target$content);
 
        $content = $this->html_purifier($content);
 
        $content = str_replace('<A href=','<a target="_blank" href=',$content);
        $content = str_replace('<a href=','<a target="_blank" href=',$content);
 
        return $content;
    }
 
    function html_purifier($html){
        // 절대경로 지정
        //include_once('/home/httpd/htdocs/vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php');
        // 상대경로 지정
        include_once('../vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php');
        $config = HTMLPurifier_Config::createDefault();
        // data/cache 디렉토리에 CSS, HTML, URI 디렉토리 등을 만든다.
        $config->set('Attr.EnableID'false);
        $config->set('Attr.DefaultImageAlt''');
 
        // 인터넷 주소를 자동으로 링크로 바꿔주는 기능
        $config->set('AutoFormat.Linkify'true);
 
        // 이미지 크기 제한 해제 (한국에서 많이 쓰는 웹툰이나 짤방과 호환성 유지를 위해)
        $config->set('HTML.MaxImgLength'null);
        $config->set('CSS.MaxImgLength'null);
 
        $config->set('Core.Encoding''UTF-8'); // 인코딩
 
        $config->set('HTML.SafeEmbed'false);
        $config->set('HTML.SafeObject'false);
        $config->set('Output.FlashCompat'false);
        $config->set('HTML.SafeIframe'true);
 
        $config->set('Attr.AllowedFrameTargets'array('_blank'));
 
        $purifier = new HTMLPurifier($config);
        return $purifier->purify($html);
    }
 
 
    // 파일 확장자 추출
    function getExt($filename){
        $ext = substr(strrchr($filename,"."),1); // 공격방어 감지를 위해 콤마의 취지를 뒤에서부터 검색
        // strrchr(문자열, 찾을 문자) : 찾을 문자가 마지막으로 나온 위치부터 끝까지 반환 
        // strstr(문자열, 찾을 문자) : 찾을 문자열이 나온 처음 위치부터 끝까지 반환.
        $ext = strtolower($ext); // 확장자 소문자로 변환
        return $ext;
    }
 
}//end class boardClass
 

 

 

 

블로그 이미지

Link2Me

,
728x90

PHP 위지윅 에디터를 추가하여 글쓰기하는 기능의 화면이다.

파일 첨부가 없는 경우에는 ajax 처리가 정상동작 한다.

하지만 파일 첨부가 있는 것은 인식 못하고 있다. 수정해 줘야 동작된다. 해답은 아래에....

<?php
error_reporting(0);
//*
ini_set("display_startup_errors"1);
ini_set("display_errors"1);
error_reporting(E_ALL);
// */
// 테이블 접속 처리
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'].'dbconnect.php';
require_once $g['path_class'].'bbsClass.php';
 
$b = new bbsClass();
$d = new LegacyDBClass;
 
 
$idx = isset($_GET['idx']) ? $d->XSSFilter($_GET['idx']): 0;
$curPage = isset($_GET['p']) ? $d->XSSFilter($_GET['p']) : 1;
 
if($idx > 0){
    $R=$d->getDbData('bbs_data','idx='.(int)$idx,'*'); 
else {
    $R['idx'= 0;
    $R['subject'= '';
    $R['content'= '';
}
 
?>
<div class="table-responsive text-nowrap">
<form id="bbsForm" method="post" enctype="multipart/form-data" 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="idx" value="<?php echo $R['idx'];?>" />
        <tr>
            <td><input type="text" name="subject" id="subject" class="form-control mb-4" placeholder="제목을 입력하세요" value="<?=$R['subject'];?>"></td>
        </tr>
 
        <tr>
            <td>
            <?php if($skin_editor==1):?>
                <textarea name="content" id="content" class="form-control mb-4 summernote" rows="10">
                <?php echo htmlspecialchars($R['content']);?>
                </textarea>
            <?php endif;?>
            <?php if($skin_editor==2):?>
                <textarea name="content" id="ir1" class="form-control mb-4" rows="10" style="width:100%;display:none;">
                <?php
                    if(isset($R['content'])&& strlen($R['content'])){
                        echo $b->conv_content($R['content']);
                    } else { echo '';}
                ?>
                </textarea>
                <!-- 아래 스크립트를 추가 -->
                <script type="text/javascript" src="<?php echo $g['path_plugin']?>smarteditor/js/service/se2_insert.js"></script>
            <?php endif;?>
            </td>
        </tr>
        <tr>
            <td><input type=file name="file" id="file" class="form-control mb-4" size=30 /></td>
        </tr>
 
        <tr>
            <td>
            <button class="btn btn-info btn-block my-4" id="bbsRegister" type="submit">등록</button>
            </td>
        </tr>
    </table>
</form>
</div>
<script>
$('#bbsRegister').click(function(e){
    e.preventDefault();
    var subject = $('#subject');
    var curPage = $('input[name=p]').val();
    var uri = "bbsList.php";
 
    if(subject.val() ==''){
        alert('제목을 입력하세요');
        subject.focus();
        return false;
    }
 
    // 이부분에 smarteditor validation 검증
    var ir1_data = oEditors.getById['ir1'].getIR();
    var checkarr = ['<p>&nbsp;</p>','&nbsp;','<p><br></p>','<p></p>','<br>'];
    if(jQuery.inArray(ir1_data.toLowerCase(), checkarr) != -1){
        alert("내용을 입력하세요.");
        oEditors.getById["ir1"].exec('FOCUS');
        return false;
    }
 
    // id가 ir1인 에디터의 내용이 textarea에 적용됨
    oEditors.getById["ir1"].exec("UPDATE_CONTENTS_FIELD", []);
 
    $.ajax({
        url:'bbsWriteChk.php',
        type: 'POST',
        data: $("#bbsForm").serializeArray(),
        dataType:'text',
        success:function(msg){
            console.log(msg);
            if(msg == 1){
                alert('게시글을 등록했습니다.');
                MemberListTable('','',curPage,uri,'','','','',0);
            } else if(msg == 2){
                alert('게시글을 수정했습니다.');
                MemberListTable('','',curPage,uri,'','','','',0);
            } else if(msg==-1){
                alert('첨부할 수 없는 파일입니다.');
            } else if(msg==-2){
                alert('수정권한이 없습니다.');
            } else {
                alert('데이터를 다시 한번 확인하세요');
                return false;
            }
        },
        error: function(jqXHR, textStatus, errorThrown){
            alert("arjax error : " + textStatus + "\n" + errorThrown);
        }
    });
 
});
</script>
 

 

 

파일 첨부를 인식하는 코드

encType: 'multipart/form-data', // 필수
processData: false, // 필수
contentType: false, // 필수
data: formData, // 필수

<?php
error_reporting(0);
//*
ini_set("display_startup_errors"1);
ini_set("display_errors"1);
error_reporting(E_ALL);
// */
// 테이블 접속 처리
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'].'dbconnect.php';
require_once $g['path_class'].'bbsClass.php';
 
$b = new bbsClass();
$d = new LegacyDBClass;
 
 
$idx = isset($_GET['idx']) ? $d->XSSFilter($_GET['idx']): 0;
$curPage = isset($_GET['p']) ? $d->XSSFilter($_GET['p']) : 1;
 
if($idx > 0){
    $R=$d->getDbData('bbs_data','idx='.(int)$idx,'*'); 
else {
    $R['idx'= 0;
    $R['subject'= '';
    $R['content'= '';
}
 
?>
<div class="table-responsive text-nowrap">
<form id="bbsForm" 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="idx" value="<?php echo $R['idx'];?>" />
        <tr>
            <td><input type="text" name="subject" id="subject" class="form-control mb-4" placeholder="제목을 입력하세요" value="<?=$R['subject'];?>"></td>
        </tr>
 
        <tr>
            <td>
            <?php if($skin_editor==1):?>
                <textarea name="content" id="content" class="form-control mb-4 summernote" rows="10">
                <?php echo htmlspecialchars($R['content']);?>
                </textarea>
            <?php endif;?>
            <?php if($skin_editor==2):?>
                <textarea name="content" id="ir1" class="form-control mb-4" rows="10" style="width:100%;display:none;">
                <?php
                    if(isset($R['content'])&& strlen($R['content'])){
                        echo $b->conv_content($R['content']);
                    } else { echo '';}
                ?>
                </textarea>
                <!-- 아래 스크립트를 추가 -->
                <script type="text/javascript" src="<?php echo $g['path_plugin']?>smarteditor/js/service/se2_insert.js"></script>
            <?php endif;?>
            </td>
        </tr>
        <tr>
            <td><input type=file name="file" id="file" class="form-control mb-4" size=30 /></td>
        </tr>
 
        <tr>
            <td>
            <button class="btn btn-info btn-block my-4" id="bbsRegister" type="submit">등록</button>
            </td>
        </tr>
    </table>
</form>
</div>
<script>
$('#bbsRegister').click(function(e){
    e.preventDefault();
    var idx = $('input[name=idx]').val();
    var subject = $('#subject');
    var curPage = $('input[name=p]').val();
    var uri = "bbsList.php";
 
    if(subject.val() ==''){
        alert('제목을 입력하세요');
        subject.focus();
        return false;
    }
 
    // 이부분에 smarteditor validation 검증
    var ir1_data = oEditors.getById['ir1'].getIR();
    var checkarr = ['<p>&nbsp;</p>','&nbsp;','<p><br></p>','<p></p>','<br>'];
    if(jQuery.inArray(ir1_data.toLowerCase(), checkarr) != -1){
        alert("내용을 입력하세요.");
        oEditors.getById["ir1"].exec('FOCUS');
        return false;
    }
 
    // id가 ir1인 에디터의 내용이 textarea에 적용됨
    oEditors.getById["ir1"].exec("UPDATE_CONTENTS_FIELD", []);
 
    // 파일 첨부 기능을 사용할 때에는 아래와 같이 해야 동작된다.
    var formData = new FormData();
    var files = $('#file')[0].files[0];
    formData.append('file',files);
    formData.append('mode','write');
    formData.append('idx', idx);
    formData.append('subject',subject.val());
    formData.append('content',ir1_data);
 
    $.ajax({
        url:'bbsWriteChk.php',
        type: 'POST',
        dataType:'text',
        encType: 'multipart/form-data'// 필수
        processData: false// 필수
        contentType: false// 필수
        data: formData, // 필수
        async: false,
        success:function(msg){
            console.log(msg);
            if(msg == 1){
                alert('게시글을 등록했습니다.');
                MemberListTable('','',curPage,uri,'','','','',0);
            } else if(msg == 2){
                alert('게시글을 수정했습니다.');
                MemberListTable('','',curPage,uri,'','','','',0);
            } else if(msg==-1){
                alert('첨부할 수 없는 파일입니다.');
            } else if(msg==-2){
                alert('수정권한이 없습니다.');
            } else {
                alert('데이터를 다시 한번 확인하세요');
                return false;
            }
        },
        error: function(jqXHR, textStatus, errorThrown){
            alert("arjax error : " + textStatus + "\n" + errorThrown);
        }
    });
 
});
</script>
 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

먼저 https://getbootstrap.com/docs/4.6/components/pagination/ 사이트에서 기본적인 Paging 처리에 대한 예제를 살펴본다.

또는 https://www.w3schools.com/bootstrap4/bootstrap_pagination.asp 에서 Pagination 항목을 선택해서 봐도 된다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Bootstrap4 Pagination Example</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<?php
/*
    // bootstrap4 에서 px 에서 em 으로 변경
    m : Margin, p : Padding
    t : top, b : bottom, l : left, r : right,
    x : x축 -> left , right, y : y축 -> top , bottom
 
    0 : 0
    1 : .25rem( font-size가 16px이면, 4px) 크기
    2 : .5rem( font-size가 16px이면, 8px) 크기
    3 : 1rem( font-size가 16px이면, 16px) 크기
    4 : 1.5rem( font-size가 16px이면, 24px) 크기
    5 : 3rem( font-size가 16px이면, 48px) 크기
    auto : margin 자동 세팅
 
    n : negative을 의미
    n1 : -.25rem( font-size가 16px이면, -4px) 크기
    n2 : -.5rem( font-size가 16px이면, -8px) 크기
    n3 : -1rem( font-size가 16px이면, -16px) 크기
    n4 : -1.5rem( font-size가 16px이면, -24px) 크기
    n5 : -3rem( font-size가 16px이면, -48px) 크기
 
    my- = it sets margin-left and margin-right at the same time on y axes
*/
?>
<div class="container mt-2">
    <div id="paging" class="mt-2">
        <ul class="pagination">
            <li class="page-item"><a class="page-link" href="#">Previous</a></li>
            <li class="page-item"><a class="page-link" href="#">1</a></li>
            <li class="page-item"><a class="page-link" href="#">2</a></li>
            <li class="page-item"><a class="page-link" href="#">3</a></li>
            <li class="page-item"><a class="page-link" href="#">Next</a></li>
        </ul>
    </div>
</div>
 
</body>
</html>

 

 

ul 태그의 pagination 클래스, li 태그의 page-item 클래스 등 페이징 처리를 위한 구조를 보고 PHP 코드로 변환 준비를 한다.

PHP 코드는 MySQL DB와 연동하여 전제 페이지 수, 현재 페이지 등의 정보를 고려하여 코드를 구현한다.

<?php
class bbsClass extends DBDataClass {
 
    function PageLinkView($link_url$totalcnt$rowsPage$curPage$m) {
        echo '<div id="paging">';
        echo '<ul class="pagination">';
        $Info = $this->PageList($totalcnt$rowsPage$curPage'');
        if ($Info['current_block'> 2) {
            echo "<li class='page-item'><a class='page-link' href='" . $link_url . "?p=1&$m'>◀</a></li> ";
        }
        if ($Info['current_block'> 1) {
            echo "<li class='page-item'><a class='page-link' href='" . $link_url . "?p=" . $Info['prev'] . "&$m'>◁</a></li> ";
        }
        foreach ($Info['current'as $w) {
            if ($curPage == $w) {
                echo "<li class='act'><a class='page-link' href='" . $link_url . "?p=" . $w . "&$m'><span style='color:red;'>" . $w . "</span></a></li> ";
            } else {
                echo "<li class='page-item'><a class='page-link' href='" . $link_url . "?p=" . $w . "&$m'>" . $w . "</a></li> ";
            }
        }
        if ($Info['current_block'< ($Info['total_block'])) {
            echo "<li class='page-item'><a class='page-link' href='" . $link_url . "?p=" . $Info['next'] . "&$m'>▷</a></li> ";
        }
        if ($Info['current_block'< ($Info['total_block'- 1)) {
            echo "<li class='page-item'><a class='page-link' href='" . $link_url . "?p=" . $Info['totalPage'] . "&$m'>▶</a></li> ";
        }
        echo '</ul>';
        echo '</div>';
    }
 
    // $curPage : 현재 페이지, $totalcnt : 총 게시물수
    // $block_limit : 한 화면에 뿌려질 게시글 개수
    function PageList($totalcnt,$rowsPage,$curPage,$block_limit) {
        $block_limit = $block_limit ? $block_limit : 10;  // 한 화면에 보여줄 개수 기본 10으로 설정
 
        // 총 페이지수 구하기
        $totalPage = ceil($totalcnt/$rowsPage);
        if($totalPage == 0) {
            ++$totalPage;
        }
        $total_block = ceil($totalPage / $block_limit); //전체 블록 갯수
 
        $curPage = $curPage ? $curPage : 1// 현재 페이지
 
        // 현재 블럭 : 화면에 표시될 페이지 리스트
        $current_block=ceil($curPage/$block_limit);
        // 현재 블럭에서 시작페이지
        $fstPage = (((ceil($curPage/$block_limit)-1)*$block_limit)+1);
        // 현재 블럭에서 마지막 페이지
        $endPage = $fstPage + $block_limit -1;
        if($totalPage < $endPage) {
            $endPage = $totalPage;
        }
 
        // 시작 바로 전 페이지
        $prev_page = $fstPage - 1;
        // 마지막 다음 페이지
        $next_page = $endPage + 1;
 
        foreach(range($fstPage$endPageas $val) {
            $row[] = $val;
        }
        // 배열로 결과를 돌려준다.
        return array(
            'total_block' => $total_block,
            'current_block' => $current_block,
            'totalPage' => $totalPage,
            'fstPage' => $fstPage,
            'endPage' => $endPage,
            'prev' => $prev_page,
            'next' => $next_page,
            'current' => $row
        );
    }
 
    //총페이지수
    function getTotalPage($num,$rec){
        return @intval(($num-1)/$rec)+1;
    }
 
}//end class boardClass

 

 

아래 Class 는 PDO(PHP Data Object) 기반으로 구현한 코드이다.

Legacy PHP 기반의 Class 는 https://link2me.tistory.com/1112 를 참조하면 된다.

 
<?php
class DBDataClass {
    protected $db// 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.
 
    public function __construct() {
        $this->dbConnect();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }
 
    private function dbConnect() {
        require_once 'dbinfo.php';
        try {
            // MySQL 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());
        }
    }
 
    /*
     $sql = "INSERT INTO users (name, surname, sex) VALUES (?,?,?)";
     $stmt= $pdo->prepare($sql);
     $stmt->execute(array($name, $surname, $sex));
    */
    public function recur_quot($cnt) {
        $R = array();
        for ($i = 0$i < $cnt$i++) {
            array_push($R"?");
        }
        return implode(","$R); // 배열을 문자열로
    }
 
    // 신규 자료 추가(ok)
    function putDbInsert($table$key$params) {
        try {
            $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;
        }
    }
 
 
    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);
                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필터링
    public function getSqlFilter($sql) {
        //$sql = preg_replace("/[\;]+/","", $sql); // 공백은 제거 불가
        return $sql;
    }
 
    // Simple function to handle PDO prepared statements
    function sql($sql$params) {
        // $params : array 를 사용해야 한다.
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(); // foreach 문으로 결과 처리
    }
 
    // 검색조건에 일치하는 데이터 가져오기
    public function putDbData($sql,$params$returntype = '') {
        // $params : array 를 사용해야 한다.
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        if ($returntype == 1) {
            return $stmt->fetch(PDO::FETCH_ASSOC);
        } else {
            return $stmt->fetch();
        }
    }
 
    public function putDbArray($sql$params) {
        // $params : array 를 사용해야 한다.
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(); //foreach 문과 연동하여 결과처리
    }
 
    public function putDbArrayALL($sql) {
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll(); //foreach 문과 연동하여 결과처리
    }
    
    // 검색조건에 일치하는 데이터 가져오기
    public function getDbData($table$where$column$returntype = '') {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($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();
    }
 
    // DB Query result 함수
    function getDbresult($table,$where,$column) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll();
    }
 
    // 검색조건에 일치하는 데이터 가져오기
    public function fetchDbData($table$where$column$returntype = '') {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        if ($returntype == 1) {
            return $stmt->fetch();
        } else {
            return $stmt->fetchAll();
        }
    }
 
    // table 결과 조회 용도
    public function fetchDbArray($table$where$column$orderby$rowsPage$curPage) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '') . ($orderby ? ' order by ' . $orderby : '') . ($rowsPage ? ' limit ' . (($curPage - 1* $rowsPage) . ', ' . $rowsPage : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll(); //foreach 문과 연동하여 결과처리 하면 됨
    }
 
    // table 결과 조회 용도
    public function getDbArray($table$where$column$orderby$rowsPage$curPage) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '') . ($orderby ? ' order by ' . $orderby : '') . ($rowsPage ? ' limit ' . (($curPage - 1* $rowsPage) . ', ' . $rowsPage : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        //return $stmt;
        return $stmt->fetchAll(); //foreach 문과 연동하여 결과처리 하면 됨
    }
 
    // WhereArgs 조건에 일치하는 선택 DbData 가져오기(ok)
    public function getDbDataFromIdx($table$whereArgs$idx) {
        $sql = 'select * from ' . $table . ' where ' . $whereArgs . '=?';
        $stmt = $this->db->prepare($sql);
        $stmt->execute(array($idx));
        return $stmt->fetch();
    }
 
    // DB 레코드 총 수(ok)
    public function getDbRows($table$where) {
        $sql = 'select count(*) from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        if ($row = $stmt->fetch()) {
            return $row[0];
        } else {
            return false;
        }
    }
 
}
?>

 

<?php
define('_DBHOST','localhost');
//define('_DBHOST','192.168.1.240'); // PHP 서버와 DB서버가 분리된 경우
define('_DBNAME',''); // DB 명
define('_DBUSER',''); // DB 사용자
define('_DBPASS'''); // DB 패스워드
define('_DBTYPE','mysql'); // 데이터베이스 종류
define('_DSN',_DBTYPE.':host='._DBHOST.';dbname='._DBNAME.';charset=utf8');
?>

 

블로그 이미지

Link2Me

,
728x90

네이버스마트 에디터를 이용하여 게시판을 만들어보려고 하니 XSS 대한 공부를 해야 할 거 같다.


크로스 사이트 스크립트란
사이트 간 스크립팅(또는 크로스 사이트 스크립팅, 영문 명칭 cross-site scripting, 영문 약어 XSS)은 웹 애플리케이션에서 많이 나타나는 취약점의 하나로 웹 페이지에 악성 스크립트를 삽입할 수 있는 취약점이다.
주로 여러 사용자가 보게 되는 게시판에 악성 스크립트가 담긴 글을 올리는 형태로 이루어진다.
이 취약점은 웹 애플리케이션이 사용자로부터 입력 받은 값을 제대로 검사하지 않고 사용할 경우 나타난다.
이 취약점으로 해커가 사용자의 정보(쿠키, 세션 등)를 탈취하거나, 자동으로 비정상적인 기능을 수행할 수 있다.
주로 다른 웹사이트와 정보를 교환하는 식으로 작동하므로 사이트 간 스크립팅이라고 한다.



기능을 테스트한 걸 적어둔다. 아직 완벽하게 다 기능 구현을 이해 못한거 같다.

구글링해서 찾은 코드와 여러 자료를 참조하여 함수를 만들어보고 있는 중이다.


입력 데이터


MySQL DB에 저장된 내용(html_encode 함수 이용)




 <?php
class bbsClass {

    function html_encode($str){
        // SQL 과 XSS 공격을 모두 막는 함수
        // htmlentities는 문자열에서 모든 HTML을 제거한다. 한글이 깨질수 있다.
        // ENT_QUOTES : 홑따옴표와 겹따옴표 모두 변환
        // htmlspecialchars, htmlentities 두개다 기본 euc-kr을 지원하지 않는다.
        return htmlentities($this->mysql_fix_string($str), ENT_QUOTES, "UTF-8");
    }

    function mysql_fix_string($str){
        global $db;
        // escape variables for security
        // mysqli_real_escape_string() 함수는 SQL 문에서 특수 문자열을 이스케이프한다.
        // $firstname = mysqli_real_escape_string($con, $_POST['firstname']);
        if(get_magic_quotes_gpc()) $str = stripslashes($str);
        return mysqli_real_escape_string($db,$str);
    }

    function html_decode($str){
        return htmlspecialchars_decode(stripslashes($str));
    }

    function xss_clean($data){ // html_decode 함수의 일종
        // 출처 : https://stackoverflow.com/questions/1336776/xss-filtering-function-in-php
        // Fix &entity\n;
        $data = str_replace(array('&amp;','&lt;','&gt;'), array('&amp;amp;','&amp;lt;','&amp;gt;'), $data);
        $data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data);
        $data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data);
        $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');

        // Remove any attribute starting with "on" or xmlns
        $data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);

        // Remove javascript: and vbscript: protocols
        $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
        $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
        $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);

        // Only works in IE: <span style="width: expression(alert('Ping!'));"></span>
        $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
        $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
        $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);

        // Remove namespaced elements (we do not need them)
        $data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);

        do
        {
            // Remove really unwanted tags
            $old_data = $data;
            $data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
        }
        while ($old_data !== $data);

        // we are done...
        return $data;
    }

}//end class bbsClass


검색하다보니 HTML Purifier 를 이용한 필터링을 하는 걸 추천하는거 같다.

http://htmlpurifier.org/ 에서 파일을 다운로드하여 압축을 푼다.

HTML Purifier 4.10.0 버전이 PHP5, PHP7 지원한다고 적혀있다.


기능을 테스트 해보니 $html_decode 함수인거 같다.

https://gist.github.com/kijin/5829736 에 사용법이 잘 설명되어 있다.


'Web 프로그램 > 테이블, 게시판, 검색' 카테고리의 다른 글

PHP 위지윅 에디터 글쓰기 기능  (0) 2023.06.28
bootstrap4 paging with PHP  (0) 2022.02.27
PHP 네이버 스마트에디터 연동 방법  (0) 2018.04.15
PHP 검색어 함수  (0) 2018.04.08
HTML 특수기호  (0) 2018.01.17
블로그 이미지

Link2Me

,
728x90

PHP 에서 write.php 만들 때 textarea 만 포함하면 텍스트 형태로만 저장되어 표현의 다양성이 부족하다.

그래서 스마트에디터를 검색해보니 오픈소스인 네이버 스마트에디터(smart editor)를 연동하면 된다고 나온다.

자료를 많이 검색해도 잘 이해가 안되어 개념 잡는데 고생을 좀 했다.

 

네이버 스마트에디터(SmartEditor)는 JavaScript로 구현된 웹 기반의 WYSIWYG 편집기다.

 

연동에 필요한 사항 전부를 하나 하나 작성해둔다.

1. 네이버 스마트 에디터 다운로드

   스마트에디터2.0 오픈소스는 github으로 이전했다.

    https://github.com/naver/smarteditor2/releases 에서 받아야 한다.

    스마트에디터3.0은 2.0버전과 동작방식이 전혀 다르고 설치형이 아니라 서비스형이다.

    네이버는 오픈소스 계획이 없다.


2. 스마트 에디터 설치 준비

    - 압축을 풀어서 plugin/editor/ 폴더에 복사를 한다.

      다른 API 도 연동할 수 있으므로 plugin 폴더를 만들고 그 하위 폴더에 위치시켰다.

    - readme.txt 파일을 보면 ....

      /css : 에디터에서 사용하는 css 파일
      /img : 에디터에서 사용하는 이미지 파일
      /js : 에디터를 적용할 때 사용하는 JS 파일
      /photo_uploader : 사진 퀵 업로더 팝업 UI를 구성하는 파일

 

3. 스마트 에디터 연동 과정

   - smart editor 본문에 이미지를 추가하면 sample/photo_uploader/file_uploader.php 가 동작된다.

     이미지가 file_uploader.php 에서 지정한 폴더에 저장된다.

   - 원하는 폴더에 저장되게 하고 싶다면 file_uploader.php 를 수정한다.

   - 이미지가 저장되면 코드가 자동으로 생성되어 smart editor 본문에 추가된다.

   - 저장된 이미지의 경로명을 DB에 저장하고 싶다면 저장할 테이블을 만들고 아래 코드로 추출한다.

     // 정규식을 이용해서 img 태그 전체 / src 값만 추출하기
     preg_match_all("/<img[^>]*src=[\"']?([^>\"']+)[\"']?[^>]*>/i", $content, $getImageSrcPaths);

     $b->bbs_imgSaveDB($getImageSrcPaths[1],$uid); // 이 코드는 개발자가 직접 구현 필요


4. 코드 예시

=== writeForm.php ===

<?php
require_once 'dbconnect.php'; // db접속
require_once 'phpclass/bbsClass.php'; // 게시판 클래스
$b = new bbsClass();

$uid = isset($_GET['uid']) ? $_GET['uid'] : '';
if($uid){
    $sql ="select * from table_name where id=".$uid;
    $rs = mysqli_query($dbconn,$sql);
    if($R = mysqli_fetch_array($rs)){

    }
}
?>

<!DOCTYPE html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>
<meta http-equiv="X-UA Compatible" control="IE=edge,chrome=1" />
<link rel="stylesheet" type="text/css" href="css/table.css" />
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<!-- 네이버 스마트에디터 스크립트 include -->
<script type="text/javascript" src="plugin/editor/js/HuskyEZCreator.js"></script>
</head>
<body>
<div class="rdata_write">
    <form name="writeForm" id="writeForm" method="post" action="write.php">
    <input type="hidden" name="uid" value="<?php echo isset($R['uid'])? $R['uid']:'';?>">
    <table class="table" width="800">
        <tr>
            <td class="td1">제목</td>
            <td class="td2"><input type="text" name="subject" value="<?php echo isset($R['title'])? htmlspecialchars($R['title']):'';?>" />
            </td>
        </tr>

        <tr>
            <td class="td1">내용</td>
            <td class="td2">
            <textarea name="content" id="ir1" rows="20" style="width:100%;height400px;">
            <?php
                if(isset($R['content'])&& strlen($R['content'])){
                    echo $b->html_decode($R['content']);
                } else { echo '';}
            ?>
            </textarea>
            <!-- 아래 스크립트를 추가 -->
            <script type="text/javascript" src="plugin/editor/js/se2_insert.js"></script>
            </td>
        </tr>

        <tr>
        <td colspan="2" align="center"><input type="button" id="savebtn" value="서버전송" class="btnblue" /></td>
        </tr>

    </table>
    </form>
</div>
</body>
</html>

=== se2_insert.js ===

$(function(){
    // 전역변수 선언
    var oEditors = [];
    nhn.husky.EZCreator.createInIFrame({
        oAppRef: oEditors,
        elPlaceHolder: "ir1",
        // 스킨 경로명에 맞게 수정
        sSkinURI: "plugin/editor/SmartEditor2Skin.html",
        htParams : {
            // 툴바 사용 여부 (true:사용/ false:사용하지 않음)
            bUseToolbar : true,
            // 입력창 크기 조절바 사용 여부 (true:사용/ false:사용하지 않음)
            bUseVerticalResizer : true,
            // 모드 탭(Editor | HTML | TEXT) 사용 여부 (true:사용/ false:사용하지 않음)
            bUseModeChanger : true,
            fOnBeforeUnload : function(){
                //alert("");
            }
        }, //boolean
        fOnAppLoad : function(){
            // 기존 저장된 내용의 text 내용을 에디터상에 뿌려주고자 할때 사용
        },
        fCreator: "createSEditor2"
    });

    function pasteHTML(str) { // 에디터에 내용 삽입
        sHTML = str;
        oEditors.getById["ir1"].exec("PASTE_HTML", [sHTML]);
    }

    function editerResetHTML() { // 에디터 내용 초기화
       oEditors.getById["ir1"].exec("SET_CONTENTS", [""]);  // 내용초기화
    }

    function setDefaultFont() {
        var sDefaultFont = '돋움';
        var nFontSize = 11;
        oEditors.getById["ir1"].setDefaultFont(sDefaultFont, nFontSize);
    }

    $("#savebtn").click(function(){
        // 에디터의 내용을 에디터 생성시에 사용했던 textarea에 넣어준다.
        if($("input[name='subject']").val() ==""){
            alert('제목을 입력하세요');
            $("input[name='subject']").focus();
            return false;
        }

        // 이부분에 에디터 validation 검증
        var ir1_data = oEditors.getById['ir1'].getIR();
        var checkarr = ['<p>&nbsp;</p>','&nbsp;','<p><br></p>','<p></p>','<br>'];
        if(jQuery.inArray(ir1_data.toLowerCase(), checkarr) != -1){
            alert("내용을 입력해 주십시오.");
            oEditors.getById["ir1"].exec('FOCUS');
            return false;
        }

        // id가 ir1인 에디터의 내용이 textarea에 적용됨
        oEditors.getById["ir1"].exec("UPDATE_CONTENTS_FIELD", []);

        //폼 submit
        $("#writeForm").submit();
    });
});
 

 

=== delete.php ===

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

if($_SERVER['REQUEST_METHOD'] !== 'POST'){
    echo 0;
    exit;
}
extract($_POST);

// 업로드된 파일이 있다면 파일삭제
require_once 'dbconnect.php'; // db접속
require_once 'phpclass/bbsClass.php'; // 게시판 클래스
$b = new bbsClass();
if($uid){
    $b->bbs_imgDeleteDB($uid); // 이미지 DB 삭제 및 파일 삭제
    $sql ="delete from table_name where uid=".$uid;
    mysqli_query($dbconn,$sql);
    mysqli_close($dbconn);
}
?>

 

=== write.php ===

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

if($_SERVER['REQUEST_METHOD'] !== 'POST'){
    echo 0;
    exit;
}
extract($_POST);
// 정규식을 이용해서 img 태그 전체 / src 값만 추출하기
preg_match_all("/<img[^>]*src=[\"']?([^>\"']+)[\"']?[^>]*>/i", $content, $getImageSrcPaths);

require_once 'dbconnect.php'; // db접속
require_once 'phpclass/bbsClass.php'; // 게시판 클래스
$b = new bbsClass();

$ip    = $_SERVER['REMOTE_ADDR'];
$agent = $_SERVER['HTTP_USER_AGENT'];

if($uid){
    $sql ="Update table_name SET content='".$b->html_encode($content)."', title='".$subject."' Where uid=".$uid;
    mysqli_query($dbconn,$sql);
    if(count($getImageSrcPaths[1])){
        $b->bbs_imgSaveDB($getImageSrcPaths[1],$uid);
    }
    echo 1;
} else {
    $sql ="INSERT INTO table_name (uid,title,content,d_regis) VALUES (NULL,'".$subject."','".$b->html_encode($content)."','".date("Y-m-d H:i:s")."')";
    mysqli_query($dbconn,$sql);
    $uid = mysqli_insert_id($dbconn);
    if(count($getImageSrcPaths[1])){
        $b->bbs_imgSaveDB($getImageSrcPaths[1],$uid);
    }
    echo 2;
}
?>

 

5. Smart Editor2에서 이미지 삽입 후 정렬이나 크기 조정 옵션을 설정 하는 방법
   GitHub : https://github.com/naver/smarteditor2/issues/36 를 참고하라고 나오네요.(이건 테스트 안해봤음)

실력이 부족해서 여기까지 알아내는데도 많은 시간이 걸렸다.

이미지 저장 처리 함수는 여기에 적어두지 않는다. 그 부분은 직접 로직 설계를 하면서 구현 ....

블로그 이미지

Link2Me

,
728x90

PHP 검색어를 입력하면 색상이 다르게 표시해주는 함수다.

아래 간단한 함수 highlight 와 highlightkeyword 차이를 비교해보면 영문 검색시 차이가 난다. 한글 검색은 highlight 함수로도 충분하다.

네이버스마트 에디터로 작성한 view.php 에서 검색을 해보니 highlight 가 정상적으로 잘 보인다.


<?php
function highlight($text='', $keyword=''){
  if(strlen($text) > 0 && strlen($keyword) > 0) {
    return (str_replace($keyword, "<span style=\"color:red;\">$keyword</span>", $text));
  }
   return ($text);
}

function highlightkeyword($str, $search) {
    // 영어 대문자, 소문자 구분없이 검색
    $highlightcolor = "#FE2E2E";
    $occurrences = substr_count(strtolower($str), strtolower($search));
    $newstring = $str;
    $match = array();
 
    for ($i=0;$i<$occurrences;$i++) {
        $match[$i] = stripos($str, $search, $i);
        $match[$i] = substr($str, $match[$i], strlen($search));
        $newstring = str_replace($match[$i], '[#]'.$match[$i].'[@]', strip_tags($newstring));
    }
 
    $newstring = str_replace('[#]', '<span style="color: '.$highlightcolor.';">', $newstring);
    $newstring = str_replace('[@]', '</span>', $newstring);
    return $newstring;
 
}

$str="진실을 깨닫도록 시도해 보세요... 생명이 느껴지시나요? 생명의 빛을 느껴보세요.";
$str="The을 깨닫도록 시도해 보세요... the 느껴지시나요? the의 빛을 느껴보세요.";
$keyword ='the';
echo highlight($str, $keyword);

echo '<br />';
echo highlightkeyword($str, $keyword);
?>


출처 : https://tomelliott.com/php/highlight-search-keyword-string-function

블로그 이미지

Link2Me

,
728x90

HTML(Hyper Text Markup Language)은 태그로 구성되어 있다.
- HTML 태그는 기본적으로 '<' 기호로 시작하여 '>'로 끝난다.
- 브라우저에서 HTML을 파싱할 때는 꺽쇠기호 <> 를 HTML 태그의 시작과 끝으로 인식 할 수 있다
- 따옴표 " "는 HTML 속성의 값이 시작되거나 끝난 것으로 인식할 수 있다.
- & 기호는 Entity 기호의 시작으로 오인될 수 있다.

HTML 에서 special character 를 나타내는 방법은 두가지가 있다.
Numeric HTML entity, Symbolic HTML entity 다.
http://entitycode.com/ 에 가면 주요 특수문자을 찾을 수 있다.
http://dmobi.tistory.com/72 에도 많은 특수문자를 참조할 수 있다.

"<", ">", "&" 이런 기호들을 Web 브라우저에서 제대로 표현하려면 &lt; &gt; &amp; 로 변환해주어야 한다.

PHP에서는
htmlspecialchars() HTML에서 사용하는 특수문자를 변경해주는 역할을 한다.


stripslashes()는 따옴표에 붙는 백슬래시를 제거해주는 함수다.
따옴표에 백슬래시를 붙여주는 함수는 addslashes() 다.
HTML 코드를 DB에 저장할 때 따옴표나 여러 특수문자 때문에 에러를 발생하기도 한다
DB에 저장할 때는 특수문자를 적절하게 변경해주어야 한다.


PHP에서 HTML 구문을 그대로 출력하기 위해 htmlspecialchars를 사용하여 인코딩된 문자열을  DB에 넣어준다.
반대로 DB에 있는 값을 불러와 HTML 소스로 변환할 때는 일반적으로 htmlspecialchars_decode를 많이 사용한다.
그런데 이 함수를 사용하는데 문제가 있다. htmlspecialchars_decode는 &nbsp;를 공백으로 다시 되돌려 주지 않는다.


&nbsp;와 같은 것은 html_entity_decode를 사용하면 된다.
이 함수는 htmlentities와 대응되는 함수이다.

htmlspecialchars와 htmlentities는 유사하나 htmlentities가 더 많은 문자를 변환한다.
htmlspecialchars_decode와 html_entity_decode는 유사하나 html_entity_decode가 더 많은 문자를 되돌린다.


$str = "I'll \"walk\" the <b>dog</b> now.\n";

$str_encode = htmlentities($str);
echo $str_encode; // I'll &quot;walk&quot; the &lt;b&gt;dog&lt;/b&gt; now.

$str_decode = html_entity_decode($str_encode);
echo $str_decode; // I'll "walk" the <b>dog</b> now.


$str = "<a href=\"https://www.w3schools.com\">w3schools.com</a>\n";
$str_encode = htmlentities($str);
echo $str_encode; // &lt;a href=&quot;https://www.w3schools.com&quot;&gt;w3schools.com&lt;/a&gt;

$str_decode = html_entity_decode($str_encode);
echo $str_decode; //  



PHP 버전이 낮은 경우에는 get_magic_quotes_gpc()함수로 magic_quotes 값을 확인 후 써야 한다.
그렇지 않으면 불필요한 백슬래시까지 데이터로 처리하는 경우가 발생하기 때문이다.
Returns 0 if magic_quotes_gpc is off, 1 otherwise.
PHP 5.4.0. always returns FALSE because the magic quotes feature was removed from PHP.

if ( get_magic_quotes_gpc() ) {
    $str = htmlspecialchars( stripslashes( $str) ) ;
}else{
    $str = htmlspecialchars( $str) ;
}

// Usage across all PHP versions
if (get_magic_quotes_gpc()) {
    $lastname = stripslashes($_POST['name']);
}
else {
    $lastname = $_POST['name'];
}

문자 치환이 필요한 경우
- DB와 연동하기 위해 특수문자 또는 줄바꿈 처리시
- 게시판에서 특정 문자의 제거 또는 치환으로 특수문자와 줄바꿈 변경에 자주 사용

블로그 이미지

Link2Me

,
728x90

부트스트랩 기반 WYSIWYG editor 인 summernote 기능을 익혀 보는데 쉽지 않았다.

구글링을 통해 많은 예제를 참고하면서 테스트를 해봤다.

http://summernote.org/ 에서 summernote 파일을 다운로드 받아서 설치한다.


summernote 에디터에 이미지를 첨부하게 되면, 이미지가 지정된 폴더에 업로드되고 그 결과가 editor 화면에 보여지게 된다.

이미지는 하나를 업로드 하고 다시 선택해서 업로드를 해보니 업로드는 잘 되고 두개 모두 에디터에 보여진다.


설명보다 받아서 테스트해보고 내것으로 소화하면 될 거 같다.


==== summernote.php ====

<?php
require_once 'connect.php'; // db접속 성공
require_once 'phpclass/boardClass.php'; // 게시판 클래스
if(isset($_GET['uid']) || empty($_GET['uid'])){
    $_GET['uid'] = '';
}

$uid = $_GET['uid'] ? $_GET['uid'] : '';
$b = new boardClass();
$R = $b->rdata_array($uid); // 배열함수
if($R == 0){
    $R = array();
    $R['uid'] = '';
    $R['subject']= '';
    $R['content'] = "DB에서 값을 가져온다고 가정";
    //$R['content'] = '';
}

?>

<!DOCTYPE html>
<head>
<meta charset=UTF-8" />
<meta name="robots" content="noindex,nofollow"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>
<meta http-equiv="X-UA Compatible" />
<link rel="stylesheet" href="plugin/bootstrap/css/bootstrap.min.css" />
<script src="http://code.jquery.com/jquery.min.js"></script>
<script src="plugin/bootstrap/js/bootstrap.min.js"></script>
<!-- include summernote css/js-->
<link href="plugin/summernote/summernote.css" rel="stylesheet">
<script src="plugin/summernote/summernote.js"></script>
<script src="plugin/summernote/lang/summernote-ko-KR.js"></script>
<script type="text/javascript">
$(document).ready(function() {
    $('.summernote').summernote({
        height: 200,
        minHeight: null,
        maxHeight: null,
        lang : 'ko-KR',
        callbacks: {
            onImageUpload : function(files, editor, welEditable) {
                console.log('image upload:', files);
                sendFile(files[0], editor, welEditable);
            },
        }
    });

    // summernote 에 값을 기록(set)
    // var markupStr = 'hello world';
    // $('.summernote').summernote('code', markupStr);

    // summernote 에디터에 이미지 업로드
    function sendFile(file,editor,welEditable) {
        data = new FormData();
        data.append("file", file);
        $.ajax({
            url: "saveimage.php", // image 저장 경로
            data: data,
            cache: false,
            contentType: false,
            enctype: 'multipart/form-data',
            processData: false,
            type: 'POST',
            success: function(data){
                var obj = JSON.parse(data);
                if (obj.success) {
                    var image = $('<img>').attr('src', '' + obj.save_url); // 에디터에 img 태그로 저장
                    $('.summernote').summernote("insertNode", image[0]); // summernote 에디터에 img 태그를 보여줌
                } else {
                    switch(parseInt(obj.error)) {
                        case 1: alert('업로드 용량 제한에 걸렸습니다.'); break;
                        case 2: alert('MAX_FILE_SIZE 보다 큰 파일은 업로드할 수 없습니다.'); break;
                        case 3: alert('파일이 일부분만 전송되었습니다.'); break;
                        case 4: alert('파일이 전송되지 않았습니다.'); break;
                        case 6: alert('임시 폴더가 없습니다.'); break;
                        case 7: alert('파일 쓰기 실패'); break;
                        case 8: alert('알수 없는 오류입니다.'); break;
                        case 100: alert('허용된 파일이 아닙니다.'); break;
                        case 101: alert('0 byte 파일은 업로드할 수 없습니다.'); break;
                    }
                }
            },
            error: function(jqXHR, textStatus, errorThrown) {
                console.log(textStatus+" "+errorThrown);
            }
        });
    }

    $("#savebtn").click(function(){
        if($("input[name='subject']").val() ==""){
            alert('제목을 입력하세요');
            $("input[name='subject']").focus();
            return false;
        }

        var contents = $('.summernote').summernote('code');
        //alert(contents.length);
        if(contents.length < 12){ // 소스보기로 보면 <p><br></p> 가 기본으로 있음
            window.alert('내용을 입력하세요');
            $('.summernote').summernote('focus');
            return false;
        }

        $("#writeForm").submit();
    });

});
</script>
</head>
<body>
<div class="rdata_write">
    <form name="writeForm" id="writeForm" action="write.php" method="post" enctype="multipart/form-data">
    <input type="hidden" name="uid" value="<?php echo $R['uid'];?>">
    <table class="table" width="1000">
        <tr>
            <td class="td1">No</td>
            <td class="td2"><?php if($R['uid']):?><?php echo $R['uid'];?><?php endif;?></td>
        </tr>

        <tr>
            <td class="td1">제목</td>
            <td class="td2"><input type="text" name="subject" value="<?php echo htmlspecialchars($R['subject']);?>" style="width:98%;" />
            </td>
        </tr>

        <tr>
            <td class="td1">내용</td>
            <td class="td2"><!-- summernote editor -->
                <textarea class="summernote" name="content" rows="10"><?php echo htmlspecialchars($R['content']);?></textarea>
            </td>
        </tr>

        <tr>
        <td colspan="2" align="center"><input type="button" id="savebtn" value="서버전송" class="btn btn-danger" /></td>
        </tr>

    </table>
    </form>
</div>
</body>
</html>


==== saveimage.php ====

<?php
$allowed = array("jpg", "png", "gif", "zip");

if(isset($_POST) and $_SERVER['REQUEST_METHOD'] == "POST"){
    if ($_FILES['file']['name']) {
        $tmpname = $_FILES['file']['tmp_name'];
        $realname = $_FILES['file']['name'];
        if (!$_FILES['file']['error']) { // 오류 없이 파일 업로드 성공
            if(! in_array(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION), $allowed) ){
                @unlink($tmpname);
                echo json_encode(array('success' => false, 'error' => 100));
            } else if (!getimagesize($tmpname)) {
                @unlink($tmpname);
                echo json_encode(array('success'=> false, 'error' => 101));
            } else {
                $fileExt = getExt($realname);
                $filename = date("Ymd").md5(uniqid($tmpname)) .round(microtime(true)).'.'.$fileExt;
                $uploadFile = './upload/' . $filename; //change this directory
                if(move_uploaded_file($tmpname, $uploadFile)){
                    @chmod($uploadFile,0606);
                    echo json_encode(array('success' => true, 'save_url' => $uploadFile ));
                }
            }
        } else {
            echo json_encode(array('success'=> false, 'error' => $error));
        }
    }
}

// 확장자 추출 함수
function getExt($filename){
    $ext = substr(strrchr($filename,"."),1);
    $ext = strtolower($ext);
    return $ext;
}

?>


==== write.php ====

테스트 목적이라서 내용이 없고 파일 내용이 잘 넘어가는지 여부만 테스트를 했다.

write.php 에서 content 필드의 내용을 잘 정리하는 함수 테스트까지는 못한 상태이다.

print_r($_POST) 보다는 var_dump($_POST)가 정보를 더 많이 보여준다.


<?php
extract($_POST);
var_dump($_POST);
//print_r($_POST);
exit;

?>


테스트해보니 $_POST['content'] 의 값은 그대로 잘 저장된다.

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

@extract($_POST);
//var_dump($_POST);exit;
$tableName = 'table_items';
if(isset($_SESSION['userid']) && !empty($_SESSION['userid'])) { // 세션정보가 있으면(로그인되어있으면)
    include_once $_SERVER['DOCUMENT_ROOT'].'/_core/config/config.php';
    include_once $_site['web_config'].'/dbconnect.php'; // db접속
    include_once $_site['web_class'].'/dbClass.php';
    $c=new MySQLiDbClass;

    $content = trim($content);
    if($uid){ // 수정
        $R = $c->getUidData($tableName,$uid);
        $QVAL = "ItemName='$ItemName',Price='$Price',Quantity='$Quantity',content='$content'";
        $c->getDbUpdate($tableName,$QVAL,'uid='.$R['uid']);
        echo json_encode(array('result'=>'2'));
    } else { // 신규
        $QKEY = "ItemName,Price,Quantity,content";
        $QVAL = "'$ItemName','$Price','$Quantity','$content'";
        $c->getDbInsert($tableName,$QKEY,$QVAL);
        echo json_encode(array('result'=>'1'));
    }
} else {
    echo json_encode(array('result'=>'0'));
}
?>


https://github.com/kimsQ/rb/issues/60 은 나중에 읽어봐야 할 거 같다.


summernote.zip


에디터 테스트한다고 고생해서 다른 분들은 수고를 줄이라고 파일 첨부합니다.

블로그에 정리해주신 분들의 자료가 완벽하게 정리된 것을 찾기가 쉽지 않아서 정말 많은 검색을 통해서 자료에 살을 붙이면서 테스트에 성공했습니다.

write.php 파일과의 처리는 ajax 처리는 아닙니다. ajax 처리로 변경하려면 다른 게시글을 참조하면 됩니다.

'Web 프로그램 > 테이블, 게시판, 검색' 카테고리의 다른 글

PHP 검색어 함수  (0) 2018.04.08
HTML 특수기호  (0) 2018.01.17
부트스트랩 테이블 만들기(수정)  (0) 2017.02.02
부트스트랩 테이블 만들기  (0) 2017.01.31
부트스트랩 테이블 속성  (0) 2017.01.31
블로그 이미지

Link2Me

,
728x90

테이블에 대한 사항은 별도로 table-01.php 파일로 분리했다.

코드가 너무 복잡해 보이는 경향을 줄이기 위함이다.


=== index.php ===

<?php
require_once 'dbconnect.php'; // db접속 성공
require_once 'phpclass/dbClass.php';
require_once 'phpclass/boardiClass.php';

$c = new MySQLiDbClass();

$link_url = $_SERVER['PHP_SELF']; // 현재 실행중인 파일명 가져오기
$rowsPage = 12;

// 화면에 출력할 칼럼 발췌
$flddata ="uid,ItemName,Price,Quantity";
$where ="";
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;
$result = $c->getDbArray('items',$where,$flddata,'',$rowsPage,$curPage);
$totalcnt = $c->getDbRows('items',$where);

$b = new boardiClass();
?>
<!DOCTYPE html>
<head>
    <meta charset="UTF-8" />
    <meta name="robots" content="noindex,nofollow"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>
    <meta http-equiv="X-UA Compatible" control="IE=edge,chrome=1" />
    <link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" />
    <link rel="stylesheet" href="css/table.css" />
    <script src="http://code.jquery.com/jquery.min.js" ></script>
    <script src="bootstrap/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="js/display.js"></script>
</head>
<body>
<main class="container-fluid">
    <div class="container">
        <div class="col-md-2 sidebar">
            <div class="row">

               <!-- 사이드 메뉴 영역 -->

            </div>
        </div>

        <!-- Main 화면 -->
        <div class="col-md-10 content">
            <div class="row">
                <div class="col-md-12">
                    <div class="panel panel-default">
                        <div class="panel-body">
                            <?php require_once 'table-01.php';?>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</main>

</body>
</html>


=== table-01.php ===

<table class="table table-hover">
    <thead>
        <tr>
            <th class="header" width="30"><input type="checkbox" id="checkall" /></th>
            <th class="header" width="100">No</th>
            <th class="header" width="250">아이템</th>
            <th class="header" width="250">가격</th>
            <th class="header" width="200">수량</th>
        </tr>
    </thead>
    <tbody>
        <?php
            // 테이블 리스트
            $b->tablelistView_checkbox($result);
        ?>
    </tbody>                                       
    <td colspan="5" style="text-align:left;">
    <button type="button" class="btn btn-success btn-sm" id="optdel">선택삭제</button>
    <button type="button" class="btn btn-success btn-sm" id="optmp3">선택듣기</button>
    </td>
</table>

<a class="btn btn-default pull-right">글쓰기</a>

<?php $b->PageLinkView($link_url,$totalcnt,$rowsPage,$curPage);?>


// column 개수, 게시물 총개수 만큼 자동으로 화면 출력
function tablelistView_checkbox($result){
    global $dbconn;
    while($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
        $view='<tr class="tr1">';
        foreach($row as $column => $value) {
            if($column== 'uid'){
                $view.='<td><input type="checkbox" class="chkbox" name="uid[]" value="'.$value.'" /></td>';
            }
            $view.='<td class="td2">'.$value.'</td>';
        }
        $view.='</tr>';
        echo $view;
    }
}

 function PageLinkView($link_url,$totalcnt,$rowsPage,$curPage,$m){
    echo '<div style="position:relative;vertical-align:top;padding-top:0;margin-top:0">';
        echo "<span style='position:absolute;top:10px;'>[전체 글수:".$totalcnt."]</span>";
        echo '<div class="text-center">';
        echo '<ul class="pagination">';
            $Info = $this->PageList($totalcnt,$rowsPage,$curPage,'');
            if($Info['current_block'] > 2){
                echo "<li><a href='".$link_url."?m=$m&p=1'>◀</a></li> ";
            }
            if($Info['current_block'] > 1){
                echo "<li><a href='".$link_url."?m=$m&p=".$Info['prev']."'>◁</a></li> ";
            }
            foreach($Info['current'] as $w) {
                if($curPage == $w){
                    echo "<li><a href='".$link_url."?m=$m&p=".$w."'><span style='color:red;'>".$w."</span></a></li> ";
                } else {
                    echo "<li><a href='".$link_url."?m=$m&p=".$w."'>".$w."</a></li> ";
                }
            }
            if($Info['current_block'] < ($Info['total_block'])){
                echo "<li><a href='".$link_url."?m=$m&p=".$Info['next']."'>▷</a></li> ";
            }
            if($Info['current_block'] < ($Info['total_block']-1)){
                echo "<li><a href='".$link_url."?m=$m&p=".$Info['totalPage']."'>▶</a></li> ";
            }
        echo '</ul>';
        echo '</div>';
    echo '</div>';
}

// $curPage : 현재 페이지, $totalcnt : 총 게시물수
// $block_limit : 한 화면에 뿌려질 게시글 개수
function PageList($totalcnt,$rowsPage,$curPage,$block_limit) {
    $block_limit = $block_limit ? $block_limit : 10;  // 한 화면에 보여줄 개수 기본 10으로 설정

    // 총 페이지수 구하기
    $totalPage = ceil($totalcnt/$rowsPage);
    if($totalPage == 0) {
        ++$totalPage;
    }
    $total_block = ceil($totalPage / $block_limit); //전체 블록 갯수

    $curPage = $curPage ? $curPage : 1; // 현재 페이지

    // 현재 블럭 : 화면에 표시될 페이지 리스트
    $current_block=ceil($curPage/$block_limit);
    // 현재 블럭에서 시작페이지
    $fstPage = (((ceil($curPage/$block_limit)-1)*$block_limit)+1);
    // 현재 블럭에서 마지막 페이지
    $endPage = $fstPage + $block_limit -1;
    if($totalPage < $endPage) {
        $endPage = $totalPage;
    }

    // 시작 바로 전 페이지
    $prev_page = $fstPage - 1;
    // 마지막 다음 페이지
    $next_page = $endPage + 1;

    foreach(range($fstPage, $endPage) as $val) {
        $row[] = $val;
    }
    // 배열로 결과를 돌려준다.
    return array(
        'total_block' => $total_block,
        'current_block' => $current_block,
        'totalPage' => $totalPage,
        'fstPage' => $fstPage,
        'endPage' => $endPage,
        'prev' => $prev_page,
        'next' => $next_page,
        'current' => $row
    );
}


블로그 이미지

Link2Me

,
728x90

http://link2me.tistory.com/1128 에서 작성했던 테이블을 부트스트랩 기반으로 변경을 해봤다.


=== index.php ===

<?php
require_once 'dbconnect.php'; // db접속 성공
require_once 'phpclass/dbClass.php';
require_once 'phpclass/boardiClass.php';

$c = new MySQLiDbClass();

$link_url = $_SERVER['PHP_SELF']; // 현재 실행중인 파일명 가져오기
$rowsPage = 12;

// 화면에 출력할 칼럼 발췌
$flddata ="uid,ItemName,Price,Quantity";
$where ="";
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;
$result = $c->getDbArray('items',$where,$flddata,'',$rowsPage,$curPage);
$totalcnt = $c->getDbRows('items',$where);

$b = new boardiClass();
?>
<!DOCTYPE html>
<head>
    <meta charset="UTF-8" />
    <meta name="robots" content="noindex,nofollow"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>
    <meta http-equiv="X-UA Compatible" control="IE=edge,chrome=1" />
    <link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" />
    <link rel="stylesheet" type="text/css" href="css/table.css" />
    <script src="http://code.jquery.com/jquery.min.js" ></script>
    <script src="bootstrap/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="js/display.js"></script>
</head>
<body>
<div class="container">
    <div class="table-responsive">
        <table class="table table-hover">
            <thead>
                <tr>
                    <th class="header" width="30"><input type="checkbox" id="checkall" /></th>
                    <th class="header" width="100">No</th>
                    <th class="header" width="250">아이템</th>
                    <th class="header" width="250">가격</th>
                    <th class="header" width="200">수량</th>
                </tr>
            </thead>
            <?php
                // 테이블 리스트
                $b->tablelistView_checkbox($result);
            ?>
            <td colspan="5" style="text-align:left;">
            <button type="button" class="btn btn-success btn-sm" id="optdel">선택삭제</button>
            <button type="button" class="btn btn-success btn-sm" id="optmp3">선택듣기</button>
            </td>
        </table>
       
        <a class="btn btn-default pull-right">글쓰기</a>       
        <?php $b->PageLinkView($link_url,$totalcnt,$rowsPage,$curPage);?>
    </div>
</div>

</body>
</html>


=== table.css ===

th.header {
    background:#F4F9FF;
    color:#444444;
    vertical-align:middle;
    border-bottom:1px solid #E6E6E6;
    font-size:1em;
    height:2em;
    }


=== display.js ===

$(function(){
    // class td1 중에서 짝수번째 요소만 선택해서 배경색을 지정색으로 표시
    // $(".tr1:odd").css("background-color","#F4F9FF"); // 부트스트랩 클래스에서 설정가능하므로 불필요

    $('.tr1').click(function() {
        var idx=$(this).attr('id');
    }).mouseover(function() {
        $(this).children('.td2').css({'backgroundColor':'#DCDCDC','cursor':'pointer'});
    }).mouseout(function() {
        $(this).children('.td2').css({'backgroundColor':'#FFFFFF','cursor':'default'});
    });

    // 전체 선택, 전체 해제
    $("#checkall").change(function () {
        $("input:checkbox").prop('checked', $(this).prop("checked"));
    });

    $('#optdel').click(function(){
        var chkdata = new Array();
        // 헤더에 있는 체크박스는 제외
        //$('.chkbox:checked').each(function() {
        $('input:checkbox[name="uid[]"]:checked').each(function() {
            // 체크한 아이템 배열로 저장
            chkdata.push($(this).val());
            //alert($(this).val());
        });
        if(chkdata.length != 0){ // 배열의 길이가 0 이 아니면
            alert(chkdata);
            $.post('ajax.php',{chkdata:chkdata}, function(response) {
                alert(response);
            });
        } else {
            alert('선택한 항목이 없습니다.');
        }
    });

    $('#optmp3').click(function(){
        var chkdata = new Array();
        $('input:checkbox[name="uid[]"]:checked').each(function() {
            // 체크한 아이템 배열로 저장
            chkdata.push($(this).val());
        });
        if(chkdata.length != 0){ // 배열의 길이가 0 이 아니면
            //alert(chkdata);
            $.post('ajax.php',{chkdata:chkdata}, function(response) {
                alert(response);
            });
        } else {
            alert('선택한 항목이 없습니다.');
        }
    });

});


=== boardiClass.php 에서 발췌 ===

페이지네이션 표시를 아래 그림처럼 해주기 위해서는

<div class="text-center">

<ul class="pagination">
  <li><a href="#">1</a></li>
  <li><a href="#">2</a></li>
  <li><a href="#">3</a></li>
  <li><a href="#">4</a></li>
  <li><a href="#">5</a></li>
</ul>

</div>

과 같이 해주면 된다.

http://www.w3schools.com/bootstrap/bootstrap_pagination.asp 를 테스트 해볼 수 있다.


    function PageLinkView($link_url,$totalcnt,$rowsPage,$curPage){
        echo '<div style="position:relative;vertical-align:top;padding-top:0;margin-top:0">';
            echo "<span style='position:absolute;top:10px;'>[전체 글수:".$totalcnt."]</span>";
            echo '<div class="text-center">';
            echo '<ul class="pagination">';
                $Info = $this->PageList($totalcnt,$rowsPage,$curPage,'');
                if($Info['current_block'] > 2){
                    echo "<li><a href='".$link_url."?p=1'>◀</a></li> ";
                }
                if($Info['current_block'] > 1){
                    echo "<li><a href='".$link_url."?p=".$Info['prev']."'>◁</a></li> ";
                }
                foreach($Info['current'] as $w) {
                    if($curPage == $w){
                        echo "<li><a href='".$link_url."?p=".$w."'><span style='color:red;'>".$w."</span></a></li> ";
                    } else {
                        echo "<li><a href='".$link_url."?p=".$w."'>".$w."</a></li> ";
                    }
                }
                if($Info['current_block'] < ($Info['total_block'])){
                    echo "<li><a href='".$link_url."?p=".$Info['next']."'>▷</a></li> ";
                }
                if($Info['current_block'] < ($Info['total_block']-1)){
                    echo "<li><a href='".$link_url."?p=".$Info['totalPage']."'>▶</a></li> ";
                }
            echo '</ul>';
            echo '</div>';
        echo '</div>';
    }


블로그 이미지

Link2Me

,
728x90

부트스트랩에서 사용하는 테이블은 모두 반응형(responsive) 테이블이다.

따라서 브라우저의 길이에 맞춰 테이블의 크기 및 내용 부분 또한 가변적으로 변하게 된다.

테이블 태그 부분에 class="table" 을 적용해주면 width 가 100%인 테이블이 생성된다.


<table class="table">

각 행의 아래에 테두리(border)가 있는 테이블이 만들어진다.


<table class="table table-bordered">

모든 셀에 테두리가 만들어 진다.


<table class="table table-striped">

한 줄 건너 배경색이 달라지는 스트라이프 형태의 테이블이 된다.


<table class="table table-hover">

테이블에 마우스를 올렸을 때 마우스 커서가 있는 행이 다른 색으로 변한다.


<table class="table table-condensed">

셀 패딩이 반으로 감소돼 작은 크기의 테이블이 된다.


테이블에 적용된 클래스 선택자의 경우 혼합해서 사용 가능하다.

<table class="table table-bordered table-hover">


테이블 행 또는 셀에 색상 지정

클래스 설명
.active 특정 행이나 셀에 마우스 오버 색상 적용
색상은 회색
.success 성공 또는 긍정적인 작업 표시
색상은 연두색 계열 #d0e9c6
.info

중립적인 정보 변경이나 작업 표시

.warning 주의를 필요로 하는 경고를 표시
색상은 겨자색 계열 #faf2cc
.danger 위험 또는 잠재적으로 부정적인 작업 표시
색상은 붉은색 계열 #ebcccc


<div class="table-responsive"></div>

부트스트랩에서는 화면 폭이 좁을 때(768px 이하) 하단에 스크롤바가 나타나며 수평으로 스크롤해서 테이블을 볼 수 있게 했다. <table> 태그 외곽에 <div> 태그로 감싸서 table-responsive 클래스를 추가하면 반응형 테이블로 사용할 수 있다.

블로그 이미지

Link2Me

,
728x90

부트스트랩으로 버튼을 꾸미는 방법



기본 버튼과 부트스트랩 버튼의 차이는 class="btn btn-default" 과 같이 부트스트랩에서 제공하는 class 속성을 추가해주는 것이다.


<div>
    <button type="button">기본</button>
    <button type="button" class="btn btn-default">기본</button>
    <button type="button" class="btn btn-primary">Primary</button>
    <button type="button" class="btn btn-success">성공</button>
    <button type="button" class="btn btn-info">정보</button>
    <button type="button" class="btn btn-warning">경고</button>
    <button type="button" class="btn btn-danger">위험</button>
</div>
<div>
    <button type="button">기본</button>
    <button type="button" class="btn btn-default btn-sm">기본</button>
    <button type="button" class="btn btn-primary btn-sm">Primary</button>
    <button type="button" class="btn btn-success btn-sm">성공</button>
    <button type="button" class="btn btn-info btn-sm">정보</button>
    <button type="button" class="btn btn-warning btn-sm">경고</button>
    <button type="button" class="btn btn-danger btn-sm">위험</button>
</div>
<div>
    <button type="button">기본</button>
    <button type="button" class="btn btn-default btn-xs">기본</button>
    <button type="button" class="btn btn-primary btn-xs">Primary</button>
    <button type="button" class="btn btn-success btn-xs">성공</button>
    <button type="button" class="btn btn-info btn-xs">정보</button>
    <button type="button" class="btn btn-warning btn-xs">경고</button>
    <button type="button" class="btn btn-danger btn-xs">위험</button>
</div>
<div>
    <button type="button">기본</button>
    <button type="button" class="btn btn-default btn-lg">기본</button>
    <button type="button" class="btn btn-primary btn-lg">Primary</button>
    <button type="button" class="btn btn-success btn-lg">성공</button>
    <button type="button" class="btn btn-info btn-lg">정보</button>
    <button type="button" class="btn btn-warning btn-lg">경고</button>
    <button type="button" class="btn btn-danger btn-lg">위험</button>
</div>


블로그 이미지

Link2Me

,
728x90

테이블에서 체크박스를 전체 선택/해제 그리고 체크박스 선택된 것만 특정 액션을 취하도록 하고 싶은 경우가 있다.

이걸 고려한 HTML5 및 JQuery 를 작성해봤다.

프로그램은 테스트를 통해서 몸으로 느끼고 내것으로 만들어야만 하는 작업이라는 걸 계속 체험하고 있다.



전체선택 박스 만들기

<th class="header" width="30"><input type="checkbox" id="checkall" /></th>



선택삭제 박스 만들기

<td colspan="5" style="text-align:left;"><span class="btnbox1">선택삭제</span></td>


.btnbox1 {
    background-color:#BFD7E9;
    border: none;
    padding: 10px 10px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 12px;
    margin: 4px 2px;
    cursor: pointer;
    }


체크 박스 나오게하는 테이블 만들기

=== boardClass.php 파일의 일부 ===

// column 개수, 게시물 총개수 만큼 자동으로 화면 출력
function tablelistView_checkbox($result){
    global $DB_CONNECT;
    if(is_object($DB_CONNECT)  && get_class($DB_CONNECT)=='mysqli'){
        while($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
            $view='<tr class="tr1">';
            foreach($row as $column => $value) {
                if($column== 'uid'){
                    $view.='<td><input type="checkbox" class="chkbox" name="uid[]" value="'.$value.'" /></td>';
                }
                $view.='<td class="td2">'.$value.'</td>';
            }
            $view.='</tr>';
            echo $view;
        }
    } else {
        while($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
            $view='<tr class="tr1">';
            foreach($row as $column => $value) {
                if($column== 'uid'){
                    $view.='<td><input type="checkbox" class="chkbox" name="uid[]" value="'.$value.'" /></td>';
                }
                $view.='<td class="td2">'.$value.'</td>';
            }
            $view.='</tr>';
            echo $view;
        }
    }
}


==== display.js ====

$(function(){
    // class td1 중에서 짝수번째 요소만 선택해서 배경색을 지정색으로 표시
    $(".tr1:odd").css("background-color","#F4F9FF");

    $('.tr1').click(function() {
        var idx=$(this).attr('id');
    }).mouseover(function() {
        $(this).children('.td2').css({'backgroundColor':'#DCDCDC','cursor':'pointer'});
    }).mouseout(function() {
        $(this).children('.td2').css({'backgroundColor':'#FFFFFF','cursor':'default'});
    });

    // 전체 선택, 전체 해제
    $("#checkall").change(function () {
        $("input:checkbox").prop('checked', $(this).prop("checked"));
    });

    $('.btnbox1').click(function(){
        var chkdata = new Array();
        // 헤더에 있는 체크박스는 제외
        //$('.chkbox:checked').each(function() {
        $('input:checkbox[name="uid[]"]:checked').each(function() {
            chkdata.push($(this).val());
            //alert($(this).val());
        });
        if(chkdata.length != 0){
            alert(chkdata);
            $.post('ajax.php',{chkdata:chkdata}, function(response) {
                alert(response);
            });
        } else {
            alert('선택한 항목이 없습니다.');
        }
    });

});


==== ajax.PHP ===

값이 POST 방식으로 전달되는 것만 알 수 있게 연습한 파일이다.

실제로는 MySQL DB와 연동하여 수행처리하는 로직을 만들면 된다.


<?php
if(isset($_POST['chkdata'])){
    /*
    foreach($_POST['chkdata'] as $column => $value) {
        echo $value;
        // MySQL 함수와 연동하여 DB Delete 수행 처리
    }
    */
    $response = implode(" , ",$_POST['chkdata']); // 배열을 문자열로 저장
    echo $response;

    if($response) {

       $sql = "delete from tablename where uid in (".$response.")";

        mysqli_query($dbconn,$sql);

    }

}
?>


=== 테이블을 출력하는 파일 전체 ===

http://link2me.tistory.com/1127 와 비교하여 달라진 부분이 뭔지 보면 알 것이다.


<?php
require_once 'dbconnect.php'; // db접속 성공
require_once 'phpclass/dbClass.php';
require_once 'phpclass/boardClass.php';

$c = new MySQLiDbClass();

$link_url = $_SERVER['PHP_SELF']; // 현재 실행중인 파일명 가져오기
$rowsPage = 12;

// 화면에 출력할 칼럼 발췌

$flddata ="uid,ItemName,Price,Quantity";
$where ="";
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;
$result = $c->getDbArray('items',$where,$flddata,'',$rowsPage,$curPage);
$totalcnt = $c->getDbRows('items',$where);

$d = new boardClass();
?>
<!DOCTYPE html>
<head>
<meta charset=UTF-8" />
<meta name="robots" content="noindex,nofollow"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>
<meta http-equiv="X-UA Compatible" control="IE=edge,chrome=1" />
<link rel="stylesheet" type="text/css" href="../css/table.css" />
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script type="text/javascript" src="../js/display.js"></script>
</head>
<body>
<div class="container">
    <table class="table" width="800">
        <thead>
            <tr>
                <th class="header" width="30"><input type="checkbox" id="checkall" /></th>
                <th class="header" width="100">No</th>
                <th class="header" width="250">아이템</th>
                <th class="header" width="250">가격</th>
                <th class="header" width="200">수량</th>
            </tr>
        </thead>
        <?php
            // 테이블 리스트
            $d->tablelistView_checkbox($result);
        ?>
        <td colspan="5" style="text-align:left;"><span class="btnbox1">선택삭제</span></td>
    </table>
</div>
<?php $d->PageLinkView($link_url,$totalcnt,$rowsPage,$curPage);?>
</body>
</html>

블로그 이미지

Link2Me

,
728x90

HTML5 기반으로 MySQL 과 연동하여 테이블을 구성하는 것 연습삼아 작성해봤다.

테이블은 Csharp 연동시 샘플 테이블을 가지고 작성했다.

AutoSet9 은 MySQLi 방식으로 연동된다.

dbconnect.php 파일을 통해서 MySQLi DB와 연동하고 dbClass.php 파일을 연결하여 dbClass 함수를 호출하여 코드를 간결하게 작성했으며, 게시판 Class(boardClass) 에서 만든 코드를 이용하여 실제 코드는 몇줄 안되는 것처럼 보인다.


<?php
require_once 'dbconnect.php'; // db접속 성공
require_once 'phpclass/dbClass.php';
require_once 'phpclass/boardClass.php';

$c = new MySQLiDbClass();

$link_url = $_SERVER['PHP_SELF']; // 현재 실행중인 파일명 가져오기
$rowsPage = 12;


// 화면에 출력할 칼럼 발췌

$flddata ="uid,ItemName,Price,Quantity";
$where ="";
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;
$result = $c->getDbArray('items',$where,$flddata,'',$rowsPage,$curPage);
$totalcnt = $c->getDbRows('items',$where);

$d = new boardClass();
?>
<!DOCTYPE html>
<head>
<meta charset=UTF-8" />
<meta name="robots" content="noindex,nofollow"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>
<meta http-equiv="X-UA Compatible" control="IE=edge,chrome=1" />
<link rel="stylesheet" type="text/css" href="../css/table.css" />
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script type="text/javascript" src="../js/display.js"></script>
</head>
<body>
<div class="container">
    <table class="table" width="800">
        <thead>
            <tr>
                <th class="header" width="100">No</th>
                <th class="header" width="250">아이템</th>
                <th class="header" width="250">가격</th>
                <th class="header" width="200">수량</th>
            </tr>
        </thead>
        <?php
            // 테이블 리스트
            $d->tablelistView($result);
        ?>
    </table>
</div>
<?php $d->PageLinkView($link_url,$totalcnt,$rowsPage,$curPage);?>
</body>
</html>


아래 코드는 위의 HTML 파일과 연관된 내용을 작성한다.


=== dbinfo.php ===

<?php
$db['host'] = "localhost";
$db['name'] = "csharp";
$db['user'] = "root";  // 원래는 root 사용자를 사용하면 안되는데 연습용인지라...
$db['pass'] = "autoset";
$db['port'] = "3306";
?>


=== dbconnect.php ===

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

function isConnectDb($db)
{
    $conn = mysqli_connect($db['host'],$db['user'],$db['pass'],$db['name'],$db['port']);
    mysqli_set_charset($conn, "utf8");  // DB설정이 잘못되어 euc-kr 로 되어 있으면 문제가 됨
    if (mysqli_connect_errno()) {
        echo "Failed to connect to MySQL: " . mysqli_connect_error();
        exit;
    } else {
        return $conn;   
    }
}
?>


=== dbClass.php ===

이 파일은 일부만 발췌하여 적는다.

본 코드에 사용된 함수는 http://link2me.tistory.com/1110 게시글에 올린 첨부파일을 받으면 된다.


class MySQLiDbClass {


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

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


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

}


=== boardClass.php ===

테이블 리스트를 보여주는 함수는 MySQL 접속인지, MySQLi 접속인지 자동으로 판단하는 함수 로직을 구현하여 코드의 확장성을 고려했다.

게시판에 주로 사용되는 사항을 함수화하여 코드를 간략화하였으나, 개발자 취향에 따라 코드를 함수화해서 사용하든 전부 나열식으로 코딩하든 개발자의 몫이다.

아울러 페이지 링크 부분은 세심하게 테스트까지 마친 코드이다.


class boardClass {

    // column 개수, 게시물 총개수 만큼 자동으로 화면 출력
    function tablelistView($result){
        global $DB_CONNECT;
        if(is_object($DB_CONNECT)  && get_class($DB_CONNECT)=='mysqli'){
            while($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
                $view='<tr class="tr1">';
                foreach($row as $column => $value) {
                    $view.='<td class="td2">'.$value.'</td>';
                }
                $view.='</tr>';
                echo $view;
            }
        } else {
            while($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
                $view='<tr class="tr1">';
                foreach($row as $column => $value) {
                    $view.='<td class="td2">'.$value.'</td>';
                }
                $view.='</tr>';
                echo $view;
            }
        }
    }

    function PageLinkView($link_url,$totalcnt,$rowsPage,$curPage){
        echo '<div style="position:relative;vertical-align:top;padding-top:0;margin-top:0">';
            echo "<span style='position:absolute;top:10px;'>[총 자료수:".$totalcnt."]</span>";
            echo '<div class="pagelink">';
                $Info = $this->PageList($totalcnt,$rowsPage,$curPage,'');
                if($Info['current_block'] > 2){
                    echo "<a href='".$link_url."?p=1'>◀</a> ";
                }
                if($Info['current_block'] > 1){
                    echo "<a href='".$link_url."?p=".$Info['prev']."'>◁</a> ";
                }
                foreach($Info['current'] as $w) {
                    if($curPage == $w){
                        echo "<a href='".$link_url."?p=".$w."'><span style='color:red;font-size:22pt'>".$w."</span></a> ";
                    } else {
                        echo "<a href='".$link_url."?p=".$w."'>".$w."</a> ";
                    }
                }
                if($Info['current_block'] < ($Info['total_block'])){
                    echo "<a href='".$link_url."?p=".$Info['next']."'>▷</a> ";
                }
                if($Info['current_block'] < ($Info['total_block']-1)){
                    echo "<a href='".$link_url."?p=".$Info['totalPage']."'>▶</a> ";
                }
            echo '</div>';
        echo '</div>';
    }


    // $curPage : 현재 페이지, $totalcnt : 총 게시물수
    // $block_limit : 한 화면에 뿌려질 게시글 개수
    function PageList($totalcnt,$rowsPage,$curPage,$block_limit) {
        $block_limit = $block_limit ? $block_limit : 10;  // 한 화면에 보여줄 개수 기본 10으로 설정

        // 총 페이지수 구하기
        $totalPage = ceil($totalcnt/$rowsPage); 
        if($totalPage == 0) {
            ++$totalPage;
        }
        $total_block = ceil($totalPage / $block_limit); //전체 블록 갯수
       
        $curPage = $curPage ? $curPage : 1; // 현재 페이지

        // 현재 블럭 : 화면에 표시될 페이지 리스트
        $current_block=ceil($curPage/$block_limit);
        // 현재 블럭에서 시작페이지
        $fstPage = (((ceil($curPage/$block_limit)-1)*$block_limit)+1);
        // 현재 블럭에서 마지막 페이지
        $endPage = $fstPage + $block_limit -1;
        if($totalPage < $endPage) {
            $endPage = $totalPage;
        }

        // 시작 바로 전 페이지
        $prev_page = $fstPage - 1;
        // 마지막 다음 페이지
        $next_page = $endPage + 1;

        foreach(range($fstPage, $endPage) as $val) {
            $row[] = $val;
        }
        // 배열로 결과를 돌려준다.
        return array(
            'total_block' => $total_block,
            'current_block' => $current_block,
            'totalPage' => $totalPage,
            'fstPage' => $fstPage,
            'endPage' => $endPage,
            'prev' => $prev_page,
            'next' => $next_page,
            'current' => $row
        );
    }

}

블로그 이미지

Link2Me

,
728x90

PHP 에서 페이징 처리하는 방법이다.
Paging 이란 전체 자료를 가져오는 것이 아니라 일정한 갯수만큼 화면에 가져오도록 처리하는 로직이다.


1. 한 페이지에 보여줄 개수를 정한다.

2. DB에서 전체 자료수(total Rows)에서 총 페이지수를 구한다.

3. 실제 페이지 번호가 표시될 개수를 정한다. 보편적으로 10개씩 보여주도록 할 수 있다.

4. 첫페이지로 가는 버튼

5. 마지막 페이지로 가는 버튼

6. 이전 블록으로 가는 버튼

7. 다음 블록으로 가는 버튼

 

변수를 정의해보자.

1. $totalcnt : 총 게시물수

2. $rowsPage : 한 페이지에 보여줄 개수

3. $totalPage = ceil($totalcnt/$rowsPage); // 총 페이지수

    총페이지수가 0 이면 1로 표기

    if($totalPage == 0) {

          ++$totalPage;

    }

4. $total_block = ceil($totalPage / $block_limit); //전체 블록 갯수

5. $curPage = $curPage ? $curPage : 1; // 현재 페이지

6. $block_limit : 한 화면에 뿌려질 게시글 개수

7. $current_block=ceil($curPage/$block_limit); // 현재 블럭 : 화면에 표시될 페이지 리스트

8. $fstPage = (((ceil($curPage/$block_limit)-1)*$block_limit)+1); // 현재 블럭의 시작

9. $endPage = $fstPage + $block_limit -1; // 현재 블럭의 마지막

    if($totalPage < $endPage) {
        $endPage = $totalPage;
     }
10. $prev_page = $fstPage - 1; // 시작 바로 전 페이지

11. $next_page = $endPage + 1; // 마지막 다음 페이지

 

 

이제 본문에 뿌릴 개수를 함수로 만들어 보자.

<?php
class MySQLDbClass {

 

    function isConnectDb($db) {
        $conn = mysql_connect($db['host'].':'.$db['port'],$db['user'],$db['pass']);
        //Set encoding
        mysql_query("SET CHARSET utf8");
        mysql_query("SET NAMES 'utf8' COLLATE 'utf8_general_ci'");
        if(!$conn){
        die('Not connected :' . mysql_error());
        exit;
        }
        $selc = mysql_select_db($db['name'],$conn); // 접근한 계정으로 사용할 수 있는 DB 선택
        return $selc ? $conn : false;
    }

 

    //DB데이터 ARRAY -> 테이블에 출력할 데이터 배열
    // order by : 정렬 순서, rowsPage : 화면에 출력할 개수, curPage : 현재 페이지
    function getDbArray($table,$where,$flddata,$orderby,$rowsPage,$curPage){
        global $db;
        $curPage = $curPage ? $curPage : 1; // 현재 페이지가 없으면 1로 설정
        $sql = 'select '.$flddata.' from '.$table.($where?' where '.$this->getSqlFilter($where):'').($orderby?' order by '.$orderby:'').($rowsPage?' limit '.(($curPage-1)*$rowsPage).', '.$rowsPage:'');
        //echo $sql;
        $result = mysql_query($sql);
        return $result;
    }

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

}//end dbClass

 

화면 하단에 뿌릴 페이징 처리 함수 만들기

dbClass 함수 안에 넣어도 되고 별도의 클래스를 만들어서 호출해서 사용해도 된다.

// $curPage : 현재 페이지, $totalcnt : 총 게시물수
// $block_limit : 한 화면에 뿌려질 게시글 개수
function PageList($totalcnt,$rowsPage,$curPage,$block_limit) {
    $block_limit = $block_limit ? $block_limit : 10;  // 한 화면에 보여줄 개수 기본 10으로 설정

    // 총 페이지수 구하기
    $totalPage = ceil($totalcnt/$rowsPage); 
    if($totalPage == 0) {
        ++$totalPage;
    }
    $total_block = ceil($totalPage / $block_limit); //전체 블록 갯수
   
    $curPage = $curPage ? $curPage : 1; // 현재 페이지

    // 현재 블럭 : 화면에 표시될 페이지 리스트
    $current_block=ceil($curPage/$block_limit);
    // 현재 블럭에서 시작페이지
    $fstPage = (((ceil($curPage/$block_limit)-1)*$block_limit)+1);
    // 현재 블럭에서 마지막 페이지
    $endPage = $fstPage + $block_limit -1;
    if($totalPage < $endPage) {
        $endPage = $totalPage;
    }

    // 시작 바로 전 페이지
    $prev_page = $fstPage - 1;
    // 마지막 다음 페이지
    $next_page = $endPage + 1;

    foreach(range($fstPage, $endPage) as $val) {
        $row[] = $val;
    }
    // 배열로 결과를 돌려준다.
    return array(
        'total_block' => $total_block,
        'current_block' => $current_block,
        'totalPage' => $totalPage,
        'fstPage' => $fstPage,
        'endPage' => $endPage,
        'prev' => $prev_page,
        'next' => $next_page,
        'current' => $row
    );
}

 

페이징 화면에 출력하기

HTML 문법은 bootstrap3 또는 bootstrap4에 맞게 Class만 수정해주면 된다.

function PageLinkView($link_url,$totalcnt,$rowsPage,$curPage){
    echo '<div style="position:relative;vertical-align:top;padding-top:0;margin-top:0">';
        echo "<span style='position:absolute;top:10px;'>[총 자료수:".$totalcnt."]</span>";
        echo '<div class="pagelink">';
            $Info = $this->PageList($totalcnt,$rowsPage,$curPage,'');
            if($Info['current_block'] > 2){
                echo "<a href='".$link_url."?p=1'>◀</a> ";
            }
            if($Info['current_block'] > 1){
                echo "<a href='".$link_url."?p=".$Info['prev']."'>◁</a> ";
            }
            foreach($Info['current'] as $w) {
                if($curPage == $w){
                    echo "<a href='".$link_url."?p=".$w."'><span style='color:red;font-size:22pt'>".$w."</span></a> ";
                } else {
                    echo "<a href='".$link_url."?p=".$w."'>".$w."</a> ";
                }
            }
            if($Info['current_block'] < ($Info['total_block'])){
                echo "<a href='".$link_url."?p=".$Info['next']."'>▷</a> ";
            }
            if($Info['current_block'] < ($Info['total_block']-1)){
                echo "<a href='".$link_url."?p=".$Info['totalPage']."'>▶</a> ";
            }
        echo '</div>';
    echo '</div>';
}   

 

사용법 예제

<?php
include_once 'dbinfo.php';
require_once 'phpclass/dbClass.php';

$conn = new MySQLDbClass(); // DB 함수
$db = $conn->isConnectDb($DB);

// 화면에 출력할 칼럼 발췌
$flddata ="(select name from cate where uid=d.cat1) as cat1,subject,content";
$rowsPage = 12;
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;
$result = $conn->getDbArray('data d','',$flddata,'',$rowsPage,$curPage);
$totalcnt 구하는 함수 처리

 

본문 Layout : 테이블을 작성해서 $result 배열 결과물을 table td 태그안에 기록

페이징 Layout : 페이징 화면 출력

 

?>

 

함수로 만든 모든 것을 표기하지는 않았다.

이런 형태로 만들어 볼 수 있다는 것만 알 수 있게 작성했다.

 

실 사용 예제

- 네이버지식인에 링크를 걸어줬더니 성의없는 답변이라는 댓글을 보고서 실제 사용하는 예제를 추가했음.

- 어떻게 처리하는지에 대한 흐름만 이해하면 되는 사항.

<?php
if(!isset($_SESSION)) {
    session_start();
}
 
require_once '../dbconnect.php';
require_once '../phpclass/dbClass.php';
require_once '../phpclass/bbsClass.php';
require_once '../phpclass/adminClass.php';
$a = new adminClass();
$b = new bbsClass();
$c = new MySQLDbClass();
 
// 설문 테이블 상태 변경 체크
$a->surveyDBupdate();
 
$url = $_SERVER['PHP_SELF']; // 현재 실행중인 파일명 가져오기
$page = isset($_GET['page'])? trim($_GET['page']):1;//페이지 변수 설정
$rowsPage = 10// 한 화면에 표시되는 게시글 수
$curPage = isset($_GET['p']) ? $_GET['p'] : 1;
$bd_name = isset($_GET['bd_name']) ? $_GET['bd_name'] :'';
$mode = isset($_GET['mode']) ? $_GET['mode'] :'list';
$sel = isset($_GET['sel'])? $_GET['sel'] : '';
 
$flddata ="*";// 화면에 출력할 칼럼 발췌
$where = isset($_GET['where']) ? $_GET['where']: '';
$keyword = isset($_GET['keyword']) ? $_GET['keyword']: '';
$xorderbyisset($xorderby) ? $xorderby : 'uid DESC';
if($where && $keyword) {
    if($where == 'title'$sql = "title LIKE '%".$keyword."%' ";
    if($where == 'content'$sql = "content LIKE '%".$keyword."%' ";
    if($where == 'unify') {
        $sql = "(title LIKE '%".$keyword."%' OR content LIKE '%".$keyword."%') ";
    }
else {
    $sql ='';
}
if(!empty($sel)){
    if($keyword){
        $sql .= " and status = ".$sel;
    } else {
        $sql = " status = ".$sel;
    }
}
 
//echo $sql.'<br />';
 
$link_url=($bd_name?'bd_name='.$bd_name.'&amp;':'').($mode?'mode='.$mode.'&amp;':'').($sel?'sel='.$sel.'&amp;':'').($where?'where='.$where.'&amp;':'').($keyword?'keyword='.urlencode(stripslashes($keyword)).'&amp;':'');
$g['bbs_reset'= $url// 초기화
 
$table ='poll_admin';
$rows$c->getDbArray($table,$sql,$flddata,$xorderby,$rowsPage,$curPage);
$NUM = $c->getDbRows($table,$sql); // 전체 게시글수
$TPG = $b->getTotalPage($NUM,$rowsPage);
?>
 
<!DOCTYPE html>
<head>
<title><?php echo $title;?></title>
<meta charset="utf-8">
<meta name="robots" content="noindex,nofollow"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../vendor/bootstrap/css/bootstrap.min.css">
<link href="vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic" rel="stylesheet" type="text/css">
<link rel="stylesheet" type="text/css" href="../css/survey.css" />
<script src="../vendor/jquery/jquery.min.js"></script>
<script src="../vendor/bootstrap/js/bootstrap.min.js"></script>
<script src="../js/modal.js"></script>
</head>
<body>
<div class="container">
<table class="table table-bordered">
<tr>
    <td align=center><font class='title'><b>설문조사<br><br>
    <div class="pull-left info bmargin">
        <select name='sel' onchange='document.searchf.sel.value=this.value;document.searchf.submit();'>
        <option value=''>전체목록보기</option>
        <option value='1' <?php if("1"==$sel):?> selected<?php endif?>>진행중목록</option>
        <option value='2' <?php if("2"==$sel):?> selected<?php endif?>>종료 목록</option>");
        </select>
    </div>
    <div class="pull-right info bmargin">
        <a href='survey_admin.php'><button class="w3-button w3-blue-grey w3-tiny">관리</button></a>&nbsp;
        <a href='add_form.php?mode=new'><button class="w3-button w3-blue-grey w3-tiny">등록</button></a>
    </div>
<!--  설문 내용 -->
<table class='w3-table w3-bordered'>
    <thead>
        <tr class="w3-blue-grey">
            <th width="8%">No</th>
            <th width="43%">설문 제목</th>
            <th width="11%">총응답</th>
            <th width="13%">시작일</th>
            <th width="13%">종료일</th>
            <th width="10%">상 태</th>
        </tr>
    </thead>
    <tbody>
        <?php $i=1;foreach($rows as $R):?>
        <tr>
            <td><?php echo $i;?></td>
            <td>
            <?php
                $memo="&nbsp;<a href='survey_memo.php?bd_name=".$R['bd_name']."&mode=list&sel=".$sel."'><img src='./image/memo.gif' border='0' alt='메모보기'></a>";
                if($R['enddate'< date("Y-m-d")){
                    echo "<a href='survey_res.php?bd_name=".$R['bd_name']."&sel=".$sel."'> ".$R['title']."</a>$memo</td>";
                    $status = "종 료";
                } else {
                    if($R['startdate'> date("Y-m-d")) $status = "준비중";
                    else $status = "진행중</td>";
                    echo "<a href='survey.php?bd_name=".$R['bd_name']."&sel=".$sel."'> ".$R['title']."</a>$memo</td>";
                }
            ?>
            </td>
            <td><?php echo $R['totalcnt'];?></td>
            <td><?php echo $R['startdate'];?></td>
            <td><?php echo $R['enddate'];?></td>
            <td><?php echo $status;?></td>
        </tr>
        <?php $i++;endforeach;?>
    </tbody>
</table>
<div><br></div>
<div class='searchbox'>
    <form name="searchf" class="form-inline" action="<?php echo $url.'?'.$link_url;?>">
        <input type="hidden" name="mode" value="<?php echo $mode?>" />
        <input type="hidden" name="sel" value="<?php echo $sel?>" />
        <input type="hidden" name="p" value="1" />
        <select name="where" class="form-control input-sm">
            <option value="unify">통합</option>
            <option value="title">제목</option>
            <option value="content">설명</option>
        </select>
        <div class="input-group input-group-sm">
            <input type="text" name="keyword" value="" class="form-control input-search" placeholder="검색어">
            <span class="input-group-btn">
                <button type="button" class="btn btn-default" onclick="this.form.where.value='title';this.form.keyword.value='',this.form.submit();" title="리셋"><i class="glyphicon glyphicon-repeat"></i></button>
                <button type="submit" class="btn btn-info" title="검색"><i class="glyphicon glyphicon-search"></i></button>
            </span>
        </div>
    </form>
</div>
<div class="pull-right info">
    <a href="<?php echo $g['bbs_reset']?>" class="btn btn-default btn-sm pull-right">처음목록</a>
</div>
<?php $b->PageLinkView($url,$NUM,$rowsPage,$curPage,$link_url);?>
</td>
</tr>
</table>
</div>
</body>
</html>
<?php mysqli_close($db);?>
 

 

<?php
class bbsClass extends MySQLDbClass {
 
    function PageLinkView($link_url$totalcnt$rowsPage$curPage$m) {
        echo '<div id="paging">';
        echo '<ul class="pagination">';
        $Info = $this -> PageList($totalcnt$rowsPage$curPage'');
        if ($Info['current_block'> 2) {
            echo "<li><a href='" . $link_url . "?p=1&$m'>◀</a></li> ";
        }
        if ($Info['current_block'> 1) {
            echo "<li><a href='" . $link_url . "?p=" . $Info['prev'] . "&$m'>◁</a></li> ";
        }
        foreach ($Info['current'as $w) {
            if ($curPage == $w) {
                echo "<li class='act'><a href='" . $link_url . "?p=" . $w . "&$m'><span style='color:red;'>" . $w . "</span></a></li> ";
            } else {
                echo "<li><a href='" . $link_url . "?p=" . $w . "&$m'>" . $w . "</a></li> ";
            }
        }
        if ($Info['current_block'< ($Info['total_block'])) {
            echo "<li><a href='" . $link_url . "?p=" . $Info['next'] . "&$m'>▷</a></li> ";
        }
        if ($Info['current_block'< ($Info['total_block'- 1)) {
            echo "<li><a href='" . $link_url . "?p=" . $Info['totalPage'] . "&$m'>▶</a></li> ";
        }
        echo '</ul>';
        echo '</div>';
    }
 
    // $curPage : 현재 페이지, $totalcnt : 총 게시물수
    // $block_limit : 한 화면에 뿌려질 게시글 개수
    function PageList($totalcnt,$rowsPage,$curPage,$block_limit) {
        $block_limit = $block_limit ? $block_limit : 10;  // 한 화면에 보여줄 개수 기본 10으로 설정
 
        // 총 페이지수 구하기
        $totalPage = ceil($totalcnt/$rowsPage);
        if($totalPage == 0) {
            ++$totalPage;
        }
        $total_block = ceil($totalPage / $block_limit); //전체 블록 갯수
 
        $curPage = $curPage ? $curPage : 1// 현재 페이지
 
        // 현재 블럭 : 화면에 표시될 페이지 리스트
        $current_block=ceil($curPage/$block_limit);
        // 현재 블럭에서 시작페이지
        $fstPage = (((ceil($curPage/$block_limit)-1)*$block_limit)+1);
        // 현재 블럭에서 마지막 페이지
        $endPage = $fstPage + $block_limit -1;
        if($totalPage < $endPage) {
            $endPage = $totalPage;
        }
 
        // 시작 바로 전 페이지
        $prev_page = $fstPage - 1;
        // 마지막 다음 페이지
        $next_page = $endPage + 1;
 
        foreach(range($fstPage$endPageas $val) {
            $row[] = $val;
        }
        // 배열로 결과를 돌려준다.
        return array(
            'total_block' => $total_block,
            'current_block' => $current_block,
            'totalPage' => $totalPage,
            'fstPage' => $fstPage,
            'endPage' => $endPage,
            'prev' => $prev_page,
            'next' => $next_page,
            'current' => $row
        );
    }
 
    //총페이지수
    function getTotalPage($num,$rec){
        return @intval(($num-1)/$rec)+1;
    }
 
}//end class boardClass

 

 
<?php
extract($_GET);
extract($_POST);
class MySQLDbClass {
    protected $db;
    private $host = 'localhost';
    private $database = 'survey';
    private $userid = 'root';
    private $password = 'autoset';
 
    public function __construct() {
        $this->db = $this->connectDB();
    }
 
    function __destruct(){
        mysqli_close($this->connectDB());
        //mysqli_close($this->db);
    }
 
    private function connectDB() {
        $dbconn = mysqli_connect($this->host, $this->userid, $this->password, $this->database);
        mysqli_set_charset($dbconn"utf8"); // DB설정이 잘못되어 euc-kr 로 되어 있으면 문제가 됨
        if (mysqli_connect_errno()) {
           printf("Connect failed: %s\n", mysqli_connect_error());
           exit();
        } else {
          return $dbconn;
        }
    }
 
    //DB-UID데이터
    function getUidData($table,$uid){
        return $this->getDbData($table,'uid='.(int)$uid,'*');
    }
 
    // DB Query Cutom 함수
    function getDbData($table,$where,$column) {
        $result = mysqli_query($this->db,'select '.$column.' from '.$table.($where?' where '.$this->getSqlFilter($where):''));
        $row = mysqli_fetch_array($result);
        return $row;
    }
 
    // DB Query result 함수
    function getDbresult($table,$where,$column) {
        $result = mysqli_query($this->db,'select '.$column.' from '.$table.($where?' where '.$this->getSqlFilter($where):''));
        return $result;
    }
 
    //DB데이터 ARRAY -> 테이블에 출력할 데이터 배열
    function getDbArray($table,$where,$flddata,$orderby,$rowsPage,$curPage){
        $sql = 'select '.$flddata.' from '.$table.($where?' where '.$this->getSqlFilter($where):'').($orderby?' order by '.$orderby:'').($rowsPage?' limit '.(($curPage-1)*$rowsPage).', '.$rowsPage:'');
        if($result = mysqli_query($this->db,$sql)){
            return $result;
        }
    }
 
    //DB데이터 레코드 총 개수
    function getDbRows($table,$where){
        $sql = 'select count(*) from '.$table.($where?' where '.$this->getSqlFilter($where):'');
        if($result = mysqli_query($this->db,$sql)){
            $rows = mysqli_fetch_row($result);
            return $rows[0] ? $rows[0] : 0;
        }
    }
 
    //DB삽입
    function getDbInsert($table,$key,$val){
        mysqli_query($this->db,"insert into ".$table." (".$key.")values(".$val.")");
    }
 
    //DB업데이트
    function getDbUpdate($table,$set,$where){
        mysqli_query('set names utf8');
        mysqli_query('set sql_mode=\'\'');
        mysqli_query($this->db,"update ".$table." set ".$set.($where?' where '.$this->getSqlFilter($where):''));
    }
 
    //DB삭제
    function getDbDelete($table,$where)    {
        mysqli_query($this->db,"delete from ".$table.($where?' where '.$this->getSqlFilter($where):''));
    }
 
    //SQL필터링
    function getSqlFilter($sql){
        return $sql;
    }
 
}//end dbClass
 
?>

 

블로그 이미지

Link2Me

,
728x90

테이블 게시글 순서를 변경 저장해야 할 경우가 있다.

그래서 jQuery tableDnD 를 이용해서 테스트한 것을 적어둔다.


마우스로 Drag 하여 놓기를 하고  순서변경만 누르면 DB에 반영된다.




파일은 https://github.com/isocra/TableDnD 에서 다운로드 받아 jquery.tablednd.js 만 include 한다.


순서를 변경하는 것은 직급 서열 순서를 변경하는 경우에 처리할 것이므로, 화면에 표시할 게시글수를 2페이지로 분리해서 나오도록 하면 안된다.

그래서 limit 에 조건을 만족하는 개수만큼 표시되도록 처리했다.


<?php
session_start();
// 세션 검사 처리는 생략
// DB 연결
include_once $_SERVER['DOCUMENT_ROOT'].'/db.info.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/phpclass/dbClass.php';
$c=new MySQLDbClass();
$DB_CONNECT = $c->isConnectDb($DB);

$where =""; // where 조건문
$limit = $NUM = $c->getDbRows('BOOKS',''); // 조건에 맞는 게시글 개수
$flddata ="uid, title, price, category, orderNO";
$orderby = "orderNO";
$curPage = 1;
$result = $c->getDbArray('BOOKS',$where,$flddata,$orderby,$limit,$curPage);

?>

<!DOCTYPE html>
<head>
<meta charset=UTF-8" />
<meta name="robots" content="noindex,nofollow"/> <!-- 검색엔진에서 검색 제외 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>
<meta http-equiv="X-UA Compatible" control="IE=edge,chrome=1" /> <!--브라우저 호환성 설정 -->
<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<script type="text/javascript" src="js/jquery.tablednd.js"></script>
<style>
body {font-family: "맑은 고딕";}
table,td,th {border: 1px solid #000;border-collapse:collapse;padding:5px;}
/*사용자 정의 스타일 */
.dragRow {background-color: #fee;}

td.dragHandle {}

td.showDragHandle {
    background-image: url(images/updown2.gif);
    background-repeat: no-repeat;
    background-position: center left;
    cursor: move;
}

table td {padding:5px;}
</style>

<script>
$(function() {
    var ordered_items;
    $('#table_1').tableDnD({
        onDragClass: "dragRow",
        onDrop: function(table, row) {
        ordered_items = $.tableDnD.serialize('id');
        }
    });

    $('#btn_order').css("cursor","pointer").click(function() {
        $.ajax ({
            type:"POST",
            url:"./order_process.php",
            data:ordered_items,
            success:function(data) {
                alert ("서열순서를 DB에 반영했습니다.");
            }
        });   
    });


    // class td1 중에서 짝수번째 요소만 선택해서 배경색을 지정색으로 표시
    $(".tr1:odd").css("background-color","#F4F9FF");

}); // ready

</script>
<body>
<div class="container">
    <div id="article">
        총 : <?php echo number_format($NUM)?> 개
    </div>

    <section id="content">
        <table id="table_1" border="1" style="width:800px;">
        <tr class="nodrop nodrag"><!-- Drag & Drop 제외 -->
            <th>uid</th>
            <th>타이틀</th>
            <th>가격</th>
            <th>구분</th>
            <th style="width:50px;">순번</th>
        </tr>
        <?php
        while($row = mysql_fetch_array($result)){
            echo '<tr id="'.$row[0].'" class="tr1">';
            for($i=0;$i < count(explode(",",$flddata)); $i++){
                echo '<td>'.$row[$i].'</td>';
            }
            echo '</tr>';
        }
        ?>
        </table>
        <input type="button" id="btn_order" name="Test" value = "순서변경"></button>
    </section><!-- content -->
</div><!-- container -->
</body>
</html>


===== order_process.php =====

<?php
if(!isset($_POST['table_1'])) {
    echo 'error';
    exit;
}

$table_1 = $_POST['table_1'];
foreach ($table_1 as $key=>$value) {
    // 정렬순서 변경내역 DB에 반영
    require_once $_SERVER['DOCUMENT_ROOT'].'/dbconnect.php'; // db접속
    $query = 'UPDATE BOOKS SET orderNO = '.($key+1).' WHERE uid = '.$value.' LIMIT 1';
    mysql_query($query) or die (mysql_error());
}

?>



블로그 이미지

Link2Me

,
728x90

테이블 게시물 순서 바꾸기를 연습해봤다.

테이블 순서를 변경해주는 jQuery 소스는 https://github.com/isocra/TableDnD 에서 받을 수 있다.

jquery.tablednd.js


첨부로 올린 이 파일을 받아도 된다.

이것은 테이블에서 마우스로 위아래 게시물 순서를 변경할 수 있다는 것만 연습해볼 수 있다.

DB와 연동하여 실제 게시물 순서를 변경하는 것 까지는 안된다.

하지만 거의 DB에서 가져온 것과 같은 결과를 얻어내는 방법까지 설명을 추가했다.


<!DOCTYPE html>
<head>
<meta charset=UTF-8" />
<meta name="robots" content="noindex,nofollow"/> <!-- 검색엔진에서 검색 제외 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>
<meta http-equiv="X-UA Compatible" control="IE=edge,chrome=1" /> <!--브라우저 호환성 설정 -->
<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<script type="text/javascript" src="js/jquery.tablednd.js"></script>
<style>
body {font-family: "맑은 고딕";}
table,td,th {border: 1px solid #000;border-collapse:collapse;padding:5px;}
/*사용자 정의 스타일 */
.dragRow {background-color: #eee;}

td.dragHandle {}

td.showDragHandle {background-image: url(images/updown2.gif);
    background-repeat: no-repeat;background-position: center left;
    cursor: move;
}

table td {padding:5px;}
</style>

<script>
$(function() {
    var ordered_items;
    onDragClass: "drag",
    $('#table_1').tableDnD({
        onDrop: function(table, row) {
        ordered_items = $.tableDnD.serialize('id');
        }
    });

    $('#btn_order').click(function() {
        $('#result').load("test.php?" + ordered_items);
    });

});
</script>
</script>


<div id="result">테이블 순서변경 결과 메시지 보여주는 곳</div>
<br>
<table id="table_1" border="1" style="width: 500px;">
    <tr id="1"><td>1</td><td>첫번째</td></tr>
    <tr id="2"><td>2</td><td>두번째</td></tr>
    <tr id="3"><td>3</td><td>세번째</td></tr>
    <tr id="4"><td>4</td><td>네번째</td></tr>
    <tr id="5"><td>5</td><td>다섯번째</td></tr>
</table>
<input type="button" id="btn_order" name="Test" value = "순서변경"></button>


</body>
</html>


===== test.php =====

<?php
$table_1 = $_GET['table_1'];
echo '저장순서 : ';
foreach ($table_1 as $key=>$value) {
    echo '순번 '. ($key+1) . ' 번의 DB idx = '. $value . ', ';
    // DB에 결과를 반영하고자 하는 쿼리문을 적어준다
    // $query = 'UPDATE table SET orderNO = '.($key+1).' WHERE idx = '.$value.' LIMIT 1';
    // mysql_query($query) or die (mysql_error());
}

?>


DB에서 읽어온 파일을 테이블로 구성한다면 <tr id='row['idx']'> 에 값이 저장되도록 한다.

테이블의 순서를 변경하면 자동으로 DB에 저장된 순서로 orderNO 에 순서의 값이 저장된다.


게시물을 읽어서 테이블에 보여주는 개수를 잘 처리하는 것이 중요 포인트가 될 수도 있다.

가령 뽑아내야 할 게시글의 수가 18개 였다고 가정해보자.

그러면 화면에서는 18개가 모두 나오도록 처리하는게 중요하다.

화면에 출력해야 할 Query 문의 개수(count)를 구한 다음에 그 개수만큼 출력하려면 LIMIT $count 만큼 나오도록 쿼리문을 만들어야 한다.

블로그 이미지

Link2Me

,
728x90
    데이터를 URL에 실어 GET방식으로 전송해야 할 때가 있다.
    GET 방식의 단점은 전달되는 변수와 값이 주소표시줄에 그대로 표시되므로
    데이터를 의도적으로 조작할 수 있다.
    urlencode() 함수는 이러한 문제점을 방지하기 위해 GET방식으로 URL에 실려 전달되는 데이터를 인코딩하는 함수이다.
    urlencode() 함수는 문자열만 적용된다.
    한글문자열은 헥사코드로 변환되고, 공백문자는 + 로 변환된다. 영문자는 변환되지 않고 그대로 전달된다.
    인코딩된 문자열을 전달받은 문서에서는 자동으로 디코딩이 이뤄진다.
    따라서, 인코딩된 문자열을 디코딩하기 위해 urldecode() 함수를 사용할 필요는 없다.


    테이블에 존재하는 특정 레코드들 중 특정 단어를 포함하는 레코드를 검색하고자 하는 경우 해당 검색어를 GET방식으로 문서에 전달하게 된다. 이 경우 전달되는 검색어는 반드시 urlencode() 함수로 인코딩해 전달해야 한다.

    한글이 입력될 가능성이 있는 부분엔 반드시 urlencode() 함수로 인코딩하여 처리한다고 생각하면 된다.


    GET 방식이 아닌 POST 방식으로 전달되는 경우, urlencode() 함수로 인코딩하면 반드시 urldecode()함수로 디코딩해주어야 원래의 문자열을 확인할 수 있다.


    Login 시 사용하는 userID, userPW 정보는 네트워크 상에서 스니핑 툴로 정보를 알아낼 수가 있다.

    이 경우에는 userID, userPW 정보도 암호화해서 보내고 서버에서 암호를 풀어서 사용자 식별을 해야 한다.

    (서버에서 패스워드 정보는 암호화되므로 암호화된채로 비교 검사)

    urlencode 를 했다고 해서 정보가 안보이는게 아니라는 것을 꼭 알아야 한다. 영문, 숫자 등은 그대로 보인다.

    단지 한글 정보만 헥사값을 변경된다는 것일 뿐이다.

    자바스크립트 암호화로 구글링을 검색하면 많이 나온다.


블로그 이미지

Link2Me

,