본문 바로가기

Common Gateway Interface/Perl

[옛 강좌] 26. Perlprog - BBS 5

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Perlprog - BBS 5

Description

게시판 만들기 5 - 답변


BBS 만들기 5

 게시판 만들기 다섯번째 야그 입니다. 이번엔 답변을 쓰는 것에 대해 알아 보도록 하겠습니다.

 여기서 특별히 신경을 쓰실 부분은 원래의 답과, 그 글에 대한 답변을 어떻게 구성할거냐 하는 겁니다. 그냥 앞 부분에(지금 저의 게시판처럼) '[답변]' 이렇게 하구, 맨 끝의 목록에 붙인다면 신경쓸것이 전혀 없죠.

 그냥 쓰기 폼에, 제목부분을 따서 입력시켜 놓으면 그만이죠. 하지만 이렇게 하지 않겠습니다. 답변을 하게 되면, 원래의 제목줄 밑으로 '└▷' 표시와 함께 넣겠습니다. 음... 이렇게 되면 인덱스 번호를 어떻게 붙일까여...

 '1'번에 답변을 쓰게 되면 '1-1'이 되고, '1-1'에 답변을 쓰게 되면 '1-1-1'이 되고, '1'번의 두번째 답변은 '1-2'가 되고 등등등....

 위의 인덱스 파일을 보면

1
1-2
1-1
1-1-1

이렇게 되는 거죠. 여기에 1-1에 또 답변을 한다면...

1
1-2
1-1
1-1-2
1-1-1

이렇게 되는 겁니다. 그럼 찬찬히 가겠습니다.

board_reply.cgi - 답변하기 - 폼

#! /usr/local/bin/perl

require "./basic.pl";
require "./board.conf";

&parse_input(*ReplyData);

if (!$ReplyData{'reply'})
{
	&show_reply_form($ReplyData{'num'});
}
else
{
	&save_reply_data;
}
					

 이것이 board_reply.cgi 메인 루틴 입니당.. ^^;;; 너무 짧다구여? 그렇지 않습니다. 서브루틴안에 서브루틴이 많이 존재합니다.

 위의 코드를 잠시 보죠. 지난번 board_view.cgi에서 답변에 링크를 어떻게 걸었는지 보겠습니다.

<a href=\"./board_reply.cgi?num=$file_number&virtual=$head_number\"  onMouseOver=\"window.status=('Reply data $head_number'); return true;\">$REPLAY_IMAGE</a>

위와 같죠. 넘기는 변수로는 파일의 번호(실제 인덱스 번호), 가상번호. 이렇게 두 개입니다.

 우선 reply의 값을 검사하죠. 없으면 폼을 보여주고, 있으면 자료를 저장합니다. board_view.cgi에서 reply라는 변수명과 값은 없습니다. 그러니 폼을 보여주죠. 하지만 폼에는 hidden 속성으로 reply 변수와 값이 존재합니다.

 그렇기 때문에 처음 답변 버튼을 누르면, 폼이 뜨고, 그상태에서 저장 단추를 누르면 그 데이터가 저장이 됩니다. 왜 이렇게 했을까여.... 그건 board_reply.cgi에서 폼을 보여주는 것과 저장하는것, 이 두 가지를 모두 처리하기 위해서입니다.

show_reply_form()

 그럼 show_reply_form()을 보겠습니다. 이 함수의 인자로는 실제의 파일 번호가 넘어갑니다. 그건 원래 글의 타이틀과 내용을 추려내기 위해서입니다.

sub show_reply_form
{
	my ($num) = @_;
	my ($ReplyFileName, @ReplyData, $re_key, $re_value, $re_list, @Reply, $re_body);
	
	$ReplyFileName = $data_dir."/board$num".".dat";
	
	open (FILE, "$ReplyFileName") || die &error_message("$ReplyFileName을 열수 없습니다.");
	@ReplyData = <FILE>;
	close (FILE);
	#### 답변하고자 하는 실제 파일을 정의하고 엽니다.
	
	for $re_list(0..7)
	{
		($re_key, $re_value) = split(/=/, $ReplyData[$re_list]);
		
		$re_value =~ tr/+/ /;
		$re_value =~ s/%(..)/pack("c", hex($1))/ge;
		$Reply{$re_key} = $re_value;
	}
	#### 우리가 필요한건 타이틀만 꺼내면 되죠. 폼에 자동으로 입력시켜 주기 위해서. 그리고 아래의 폼대로 입력폼을 작성합니다.
	#### board_write.cgi 와 거의 같습니다. 단 form action 부분이 틀리죠. 그리고, 제목과, 내용란에 기존의 내용을 넣습니다.
	#### 내용을 넣을때 기존에 작성된 데이터 앞에 '>'를 넣어줍니다.
	
	&print_header;
	
	print "
		<html>
		<head>
		<title></title>
		<script language=JavaScript>
		
		function frm_send()
		{
		
			var strvl = new Array(5);
			
			strvl[0] = document.frm.title.value;
			strvl[1] = document.frm.name.value;
			strvl[2] = document.frm.email.value;
			strvl[3] = document.frm.body.value;
			strvl[4] = document.frm.pawd.value;
			
			if (strvl[0] == \"\")
			{
				alert(\"제목을 쓰셔야 되여\");
			}
			else
			{
				if (strvl[1] == \"\")
				{
					alert(\"이름을 쓰세요\");
				}
				else
				{
					if (strvl[2] == \"\")
					{
						alert(\"전자우편을 써주세요\");
					}
					else
					{
						if (strvl[3] == \"\")
						{
							alert(\"내용은 필수 입니다\");
						}
						else
						{
							if (strvl[4] == \"\")
							{
								alert(\"암호를 써주세여\");
							}
							else
							{
								document.frm.submit();
								return (true);
							}
						}
					}
				}
			}
		}
		</script>       
	</head>
	<body bgcolor=white>
		<form action=\"./board_reply.cgi\" method=post name=frm>\n";
		
	&print_content;
	
	print "
		<table border=0 width=550 bgcolor=white>
			<tr>";
	
	#### 제목란에 위에서 분리해낸 제목을 넣습니다. 
	
	print "
				<td align=right width=150><font size=2 color=black><b>제 목 : </b></font></td>
				<td><input type=text name=title size=35 maxlength=80 value=\"Re:$Reply{'TITLE'}\"></td>
			</tr>
			<tr>
				<td align=right><font size=2 color=black><b>이 름 : </b></font></td>
				<td><input type=text name=name size=16 maxlength=5></td>
			</tr>
			<tr>
				<td align=right><font size=2 color=black><b>전자우편 : </b></font></td>
				<td><input type=text name=email size=25 maxlength=80></td>
			</tr>
			<tr>
				<td align=right><font size=2 color=black><b>홈페이지 : </b></font></td>
				<td><input type=text name=homepage size=30 maxlength=80 value=http://&gt;</td>
			</tr>
			<tr>
				<td align=right valign=top><font size=2 color=black><b>내 용 : </b></font></td>
				<td><textarea name=body rows=20 cols=60>\n";
				
	#### 기존의 내용을 분리하고 앞에 '>'를 넣어서 출력을 합니다. 답변하시는 분은 그 뒤에 이어서 답변을 하면 됩니다.
	
	($re_key, $re_body) = split(/=/, $ReplyData[8]);
	chomp($re_body);
	
	print ">$re_body";
	
	for $re_list(9..$#ReplyData)
	{
		chomp($ReplyData[$re_list]);
		print ">$ReplyData[$re_list]";
	}
	
	print "
				</textarea></td>
			</tr>
			<tr>
				<td align=right><font size=2 color=black><b>비밀번호 : </b></font></td>
				<td><input type=password name=pawd size=8 maxlength=8><font size=2 color=black>(8자 까지)</font></td>
			</tr>
			<tr>
				<td></td>
				<td><font size=2 color=blue>(참고사항 : HTML은 지원 되지 않습니다.)</font></td>
			</tr>
			<tr>
				<td colspan=2><hr width=100% color=#bedaff size=3></td>
			</tr>
			<tr>
				<td colspan=2 align=center><a href=\"javascript:frm_send()\" onMouseOver=\"window.status=('Send Data'); return true;\">$WRITE_IMAGE</a></td>
			</tr>
		</table>\n";
		
		&about_board;
		
		#### reply변수명과 값을 hidden속성으로 다음 cgi프로그램에 전달합니다. 이건 위에서 설명했죠? 그와 더불어 답변을 선택한 
데이터의 인덱스번호도 hidden속성으로 전달합니다.
		print "
			<input type=hidden name=reply value=1>
			<input type=hidden name=idx value=$num>
		</form>
	</body>
</html>\n";
}
						

board_reply.cgi - 답변 저장

 폼 출력이 끝났습니다. 여기서 우리가 저장을 누르면, board_reply.cgi가 호출되고, reply=1이라는 값이 있기에 save_reply_data() 함수를 호출하겠죠.

 그럼 위의 함수를 살펴봅시다. 여기서 우리가 신경 쓸 부분은 새로운 인덱스 생성과, 그 새로이 생성된 인덱스 번호를 인덱스 파일에 저장하는 부분입니다. 맨 윗줄에 저장하면 안 되져.

 위에서 보셨듯이

1
1-2
1-1

 이런 식으로 답변을 하고자 했던 데이터의 밑에 저장이 되어야 합니다.

 물론 인덱스 파일만 이렇게 하면 안됩니다. 패스워드를 저장한 파일도 이렇게 해야 됩니다.

save_reply_data()

sub save_reply_data
{
	my (@ReIdx, @ReTemp, $re_idx, $re_file_name, $RePass, @RePwdData, $re_date, $re_i);
	my ($re_total, $re_now);
	
	#### 인덱스파일과 패스워드 파일내의 전체 배열에서 우리가 선택한 인덱스번호의 배열 번호를 찾기 위한 get_list() 함수를 호출합니다. 이유는 그 번호 바로 아래에 답변한 데이터가 들어가야 하기 때문입니다.
	#### 넘기는 인수는 실제 인덱스 번호입니다. 그리고 저장시 필요한 ip, 시간을 얻어 냅니다. 
	
	$re_now = &get_list($ReplyData{'idx'});
	$re_ip = $ENV{'REMOTE_ADDR'};
	$re_date = &get_date;
	#### 바로 아래의 부분이 답변을 한 데이터의 인덱스 번호를 추출해내는 함수인 get_reply_idx() 함수를 호출하는 부분입니다. 요건 아래서.... 
	
	$re_idx = &get_reply_idx($re_now, $ReplyData{'idx'});
	#### 그리고 파일명을 정하고, 인덱스 파일을 열어서, 답변을 하고자 했던 데이터의 바로 아랫줄에 인덱스 데이터를 저장합니다.
	
	$re_file_name = $data_dir."/board$re_idx.dat";
	open (FILE, "$idx_file") || die &error_message("$idx_file을 열수 없습니다.");
	@ReIdx = <FILE>;
	close (FILE);
	
	open (FILE, ">$idx_file") || die &error_message("$idx_file을 열수 없습니다.");
	$re_i = 0;
	foreach (@ReIdx)
	{
		@ReTemp = split(/::/, $_);
		if ($re_i eq $re_now)
		{
			print FILE $_;
			print FILE $re_idx,"::",$ReplyData{'name'},"::",$ReplyData{'title'},"::","0","::",$re_date,"\n";
		}
		else
		{
			print FILE $_;
		}
		$re_i++;
	}
	close (FILE);
	
	#### 암호를 저장합니다. 저장하는 방식은 인덱스 파일을 저장할때와 같습니다. 
	
	$RePass = &get_password($ReplyData{'pawd'});
	
	open (FILE, "$pwd_file") || die &error_message("$pwd_file을 열수 없습니다.");
	@RePwdData = <FILE>;
	close (FILE);
	
	open (FILE, ">$pwd_file") || die &error_message("$pwd_file을 열수 없습니다.");
	$re_i = 0;
	foreach (@RePwdData)
	{
		@ReTemp = split(/::/, $_);
		if ($re_i eq $re_now)
		{
			print FILE $_;
			print FILE $re_idx, "::", $RePass, "\n";
		}
		else
		{
			print FILE $_;
		}
		$re_i++;
	}
	close (FILE);
	
	#### 그리구, 전체 데이터를 저장합니다. 
	
	open (FILE, ">$re_file_name") || die &error_message("$re_file_name을 열수 없습니다.");
	print FILE "IDX=$re_idx\n";
	print FILE "COUNT=0\n";
	print FILE "DATE=$re_date\n";
	print FILE "HOMEPAGE=$ReplyData{'homepage'}\n";
	print FILE "NAME=$ReplyData{'name'}\n";
	print FILE "IP=$re_ip\n";
	print FILE "EMAIL=$ReplyData{'email'}\n";
	print FILE "TITLE=$ReplyData{'title'}\n";
	print FILE "BODY=$ReplyData{'body'}\n";
	close (FILE);
	
	#### 리스트를 보여주는 view_list.cgi로 옮깁니다. 
	print "Location: $url/cgi-bin/view_list.cgi\n\n";
}
						

 이렇게 하면 일련의 작업이 끝난겁니다. 그럼 위에 있던 get_list(), get_reply_idx() 함수에 관해서 살펴보도록 하겠습니다.

 get_list() 함수는 그리 어려운 내용이 없습니다. 인덱스 파일을 열고, 넘어온 인덱스 번호가 배열의 몇 번째인가 하는 것만 알아내면 됩니다. 우리는 1,2,3.... 펄은 0,1,2 이기 때문에 마지막에 1을 더한 수를 리턴합니다.

get_list()

sub get_list
{
	my ($get_idx) = @_;
	
	open (FILE, "$idx_file") || die &error_message("$idx_file을 열수 없습니다.");
	$get_count = 0;
	
	while (<FILE>)
	{
		@temp = split(/::/, $_);
		if ($temp[0] eq $get_idx)
		{
			last;
		}
		$get_count++;
	}
	
	close (FILE);
	return $get_count++;
}
						

 답변의 인덱스 번호를 생성하는 과정도 그리 어려운건 아니지만, 잘 이해해야 만이 쉽습니다.

  1. 우선 답변을 하고자 했던 인덱스 번호에 '-1'을 붙여서 임의의 인덱스 번호를 생성합니다.
  2. 그리고 이 임의의 인덱스 번호를, 원래 답변을 하고자 했던 인덱스 번호 바로 아래의 인덱스 번호와 비교를 합니다.
  3. 그 다음, 두 개의 인덱스 번호의 길이를 검사합니다. 길이가 다르다면, 그 글의 답변은 지금이 처음이기 때문에, 임시 인덱스 번호가 실제 인덱스 번호가 됩니다. 이 인덱스 번호를 리턴합니다.
  4. 만약 길이가 같다면, '-'이 있는지 없는지부터 검사합니다. '-'이 없으면, 임시 인덱스 번호가 실제 인덱스번호가 되는거죠.
  5. '-'이 있다면, 이미 그 글에 대한 답변이 있는거니깐, 그 인덱스번호에 '1'을 더해서 실제 인덱스번호로 리턴합니다.

 왜 위와 같은 과정을 거치는지는 잘 생각해 보세요. 다른 방법도 있겠지만.

get_reply_idx()

sub get_reply_idx
{
	my ($now_num, $now_num_idx) = @_;
	my (@NowNum, @NowNumIdx, @NowNumIdxNum, $IdxEndNum);
	
	open (FILE, "$idx_file") || die &error_message("$idx_file을 열수 없습니다.");
	@NowNum = <FILE>;
	close (FILE);
	
	@NowNumIdx = split(/::/, $NowNum[$now_num + 1]);
	@NowNumIdxEnd = split(/-/, $NowNumIdx[0]);
	
	$IdxEndNum = $now_num_idx."-1";
	
	if (length($IdxEndNum) eq length($NowNumIdx[0]))
	{
		if ($NowNumIdx[0] =~ /-/)
		{
			return $IdxEndNum = $now_num_idx."-".($NowNumIdxEnd[$#NowNumIdxEnd] + 1);
		}
		else
		{
			return $IdxEndNum;
		}
	}
	else
	{
		return $IdxEndNum;
	}
}
						

NOTES

 끝났습니다. board_reply.cgi.

 이로서 답변하는 기능까지 구현을 하였습니다. 삭제, 편집, 검색, 관리자.. 이렇게 남았군여. 그럼 담에 또 한가지의 기능을 구현하도록 하겠습니다.

의문나는 점이나 이상한 점을 발견하였을 시는, 주저마시고 게시판에 남겨주세요.

이 문서는 Perl 패키지내의 pod2html를 이용하여 만들었습니다. - Kim Young Soo

'Common Gateway Interface > Perl' 카테고리의 다른 글

[옛 강좌] 28. Perlprog - BBS 7  (0) 2015.04.29
[옛 강좌] 27. Perlprog - BBS 6  (0) 2015.04.29
[옛 강좌] 25. Perlprog - BBS 4  (0) 2015.04.29
[옛 강좌] 24. Perlprog - BBS 3  (0) 2015.04.29
[옛 강좌] 23. Perlprog - BBS 2  (0) 2015.04.29