diff --git a/modules/poll/conf/info.xml b/modules/poll/conf/info.xml
new file mode 100644
index 000000000..e143c0927
--- /dev/null
+++ b/modules/poll/conf/info.xml
@@ -0,0 +1,8 @@
+
+
+ 엮인글
+
+ 제로
+ 엮인글 관리 모듈
+
+
diff --git a/modules/poll/conf/module.xml b/modules/poll/conf/module.xml
new file mode 100644
index 000000000..50ed7c141
--- /dev/null
+++ b/modules/poll/conf/module.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/modules/poll/lang/ko.lang.php b/modules/poll/lang/ko.lang.php
new file mode 100644
index 000000000..a09478230
--- /dev/null
+++ b/modules/poll/lang/ko.lang.php
@@ -0,0 +1,22 @@
+
+ * @brief 엮인글(trackback) 모듈의 기본 언어팩
+ **/
+
+ $lang->cmd_delete_checked_trackback = '선택항목 삭제';
+
+ $lang->msg_cart_is_null = '삭제할 글을 선택해주세요';
+ $lang->msg_checked_trackback_is_deleted = '%d개의 엮인글이 삭제되었습니다';
+
+ $lang->search_target_list = array(
+ 'url' => '대상 URL',
+ 'blog_name' => '대상 사이트 이름',
+ 'title' => '제목',
+ 'excerpt' => '내용',
+ 'regdate' => '등록일',
+ 'ipaddress' => 'IP 주소',
+ );
+
+?>
diff --git a/modules/poll/queries/deleteModuleTrackbacks.xml b/modules/poll/queries/deleteModuleTrackbacks.xml
new file mode 100644
index 000000000..dc80d00fe
--- /dev/null
+++ b/modules/poll/queries/deleteModuleTrackbacks.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/modules/poll/queries/deleteTrackback.xml b/modules/poll/queries/deleteTrackback.xml
new file mode 100644
index 000000000..e5a5e60f8
--- /dev/null
+++ b/modules/poll/queries/deleteTrackback.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/modules/poll/queries/deleteTrackbacks.xml b/modules/poll/queries/deleteTrackbacks.xml
new file mode 100644
index 000000000..7b81949a0
--- /dev/null
+++ b/modules/poll/queries/deleteTrackbacks.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/modules/poll/queries/getTotalTrackbackList.xml b/modules/poll/queries/getTotalTrackbackList.xml
new file mode 100644
index 000000000..9171d08df
--- /dev/null
+++ b/modules/poll/queries/getTotalTrackbackList.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/poll/queries/getTrackback.xml b/modules/poll/queries/getTrackback.xml
new file mode 100644
index 000000000..82eb419f1
--- /dev/null
+++ b/modules/poll/queries/getTrackback.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/modules/poll/queries/getTrackbackCount.xml b/modules/poll/queries/getTrackbackCount.xml
new file mode 100644
index 000000000..9204bf91e
--- /dev/null
+++ b/modules/poll/queries/getTrackbackCount.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/poll/queries/getTrackbackCountByIPAddress.xml b/modules/poll/queries/getTrackbackCountByIPAddress.xml
new file mode 100644
index 000000000..23871ee91
--- /dev/null
+++ b/modules/poll/queries/getTrackbackCountByIPAddress.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/poll/queries/getTrackbackList.xml b/modules/poll/queries/getTrackbackList.xml
new file mode 100644
index 000000000..204972316
--- /dev/null
+++ b/modules/poll/queries/getTrackbackList.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/poll/queries/insertTrackback.xml b/modules/poll/queries/insertTrackback.xml
new file mode 100644
index 000000000..d31308330
--- /dev/null
+++ b/modules/poll/queries/insertTrackback.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/poll/schemas/trackbacks.xml b/modules/poll/schemas/trackbacks.xml
new file mode 100644
index 000000000..acf2ffcf6
--- /dev/null
+++ b/modules/poll/schemas/trackbacks.xml
@@ -0,0 +1,12 @@
+
diff --git a/modules/poll/tpl/filter/delete_checked.xml b/modules/poll/tpl/filter/delete_checked.xml
new file mode 100644
index 000000000..a410c54e6
--- /dev/null
+++ b/modules/poll/tpl/filter/delete_checked.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/poll/tpl/trackback_list.html b/modules/poll/tpl/trackback_list.html
new file mode 100644
index 000000000..ebf329a47
--- /dev/null
+++ b/modules/poll/tpl/trackback_list.html
@@ -0,0 +1,83 @@
+
+
+
+
+ {$lang->total_count} : {number_format($total_count)},
+ {$lang->page_count} : {number_format($page)} / {number_format($total_page)}
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/poll/trackback.class.php b/modules/poll/trackback.class.php
new file mode 100644
index 000000000..11c0353ba
--- /dev/null
+++ b/modules/poll/trackback.class.php
@@ -0,0 +1,38 @@
+insertActionForward('trackback', 'controller', 'procTrackbackReceive');
+ $oModuleController->insertActionForward('trackback', 'view', 'dispTrackbackAdminList');
+ $oModuleController->insertActionForward('trackback', 'controller', 'procTrackbackAdminDeleteChecked');
+
+ return new Object();
+ }
+
+ /**
+ * @brief 설치가 이상이 없는지 체크하는 method
+ **/
+ function moduleIsInstalled() {
+ return new Object();
+ }
+
+ /**
+ * @brief 업데이트 실행
+ **/
+ function moduleUpdate() {
+ return new Object();
+ }
+
+ }
+?>
diff --git a/modules/poll/trackback.controller.php b/modules/poll/trackback.controller.php
new file mode 100644
index 000000000..97bd08954
--- /dev/null
+++ b/modules/poll/trackback.controller.php
@@ -0,0 +1,217 @@
+stop('msg_cart_is_null');
+ $trackback_srl_list= explode('|@|', $cart);
+ $trackback_count = count($trackback_srl_list);
+ if(!$trackback_count) return $this->stop('msg_cart_is_null');
+
+ // 글삭제
+ for($i=0;$i<$trackback_count;$i++) {
+ $trackback_srl = trim($trackback_srl_list[$i]);
+ if(!$trackback_srl) continue;
+
+ $this->deleteTrackback($trackback_srl, true);
+ }
+
+ $this->setMessage( sprintf(Context::getLang('msg_checked_trackback_is_deleted'), $trackback_count) );
+ }
+
+ /**
+ * @brief 엮인글 입력
+ **/
+ function procTrackbackReceive() {
+ Context::setRequestMethod("XMLRPC");
+
+ $obj = Context::gets('document_srl','url','title','excerpt');
+
+ // GET으로 넘어온 document_srl을 참조, 없으면 오류~
+ $document_srl = $obj->document_srl;
+ if(!$document_srl) return $this->stop('fail');
+
+ // document model 객체 생성후 원본글을 가져옴
+ $oDocumentModel = &getModel('document');
+ $document = $oDocumentModel->getDocument($document_srl);
+
+ // 원본글이 없거나 트랙백 허용을 하지 않으면 오류 표시
+ if(!$document_srl) return $this->stop('fail');
+ if($document->allow_trackback=='N') return $this->stop('fail');
+
+ // 엮인글 정리
+ $obj = Context::convertEncoding($obj);
+ if(!$obj->blog_name) $obj->blog_name = $obj->title;
+ $obj->excerpt = strip_tags($obj->excerpt);
+
+ // 엮인글를 입력
+ $obj->list_order = $obj->trackback_srl = getNextSequence();
+ $obj->module_srl = $document->module_srl;
+ $output = executeQuery('trackback.insertTrackback', $obj);
+
+ // 입력에 이상이 없으면 해당 글의 엮인글 수를 올림
+ if(!$output->toBool()) return $this->stop( 'fail');
+
+ // trackback model 객체 생성
+ $oTrackbackModel = &getModel('trackback');
+
+ // 해당 글의 전체 엮인글 수를 구해옴
+ $trackback_count = $oTrackbackModel->getTrackbackCount($document_srl);
+
+ // document controller 객체 생성
+ $oDocumentController = &getController('document');
+
+ // 해당글의 엮인글 수를 업데이트
+ $output = $oDocumentController->updateTrackbackCount($document_srl, $trackback_count);
+
+ // 결과 return
+ if(!$output->toBool()) return $this->stop('fail');
+
+ $this->setMessage('success');
+ }
+
+ /**
+ * @brief 단일 엮인글 삭제
+ **/
+ function deleteTrackback($trackback_srl, $is_admin = false) {
+ // trackback model 객체 생성
+ $oTrackbackModel = &getModel('trackback');
+
+ // 삭제하려는 엮인글이 있는지 확인
+ $trackback = $oTrackbackModel->getTrackback($trackback_srl);
+ if($trackback->data->trackback_srl != $trackback_srl) return new Object(-1, 'msg_invalid_request');
+ $document_srl = $trackback->data->document_srl;
+
+ // document model 객체 생성
+ $oDocumentModel = &getModel('document');
+
+ // 권한이 있는지 확인
+ if(!$is_admin && !$oDocumentModel->isGranted($document_srl)) return new Object(-1, 'msg_not_permitted');
+
+ $args->trackback_srl = $trackback_srl;
+ $output = executeQuery('trackback.deleteTrackback', $args);
+ if(!$output->toBool()) return new Object(-1, 'msg_error_occured');
+
+ // 엮인글 수를 구해서 업데이트
+ $trackback_count = $oTrackbackModel->getTrackbackCount($document_srl);
+
+ // document controller 객체 생성
+ $oDocumentController = &getController('document','controller');
+
+ // 해당글의 엮인글 수를 업데이트
+ $output = $oDocumentController->updateTrackbackCount($document_srl, $trackback_count);
+ $output->add('document_srl', $document_srl);
+
+ return $output;
+ }
+
+ /**
+ * @brief 글에 속한 모든 트랙백 삭제
+ **/
+ function deleteTrackbacks($document_srl) {
+ // 삭제
+ $args->document_srl = $document_srl;
+ $output = executeQuery('trackback.deleteTrackbacks', $args);
+
+ return $output;
+ }
+
+ /**
+ * @brief 모듈에 속한 모든 트랙백 삭제
+ **/
+ function deleteModuleTrackbacks($module_srl) {
+ // 삭제
+ $args->module_srl = $module_srl;
+ $output = executeQuery('trackback.deleteModuleTrackbacks', $args);
+
+ return $output;
+ }
+
+ /**
+ * @brief 엮인글을 발송
+ *
+ * 발송 후 결과처리는 하지 않는 구조임
+ **/
+ function sendTrackback($document, $trackback_url, $charset) {
+ // 발송할 정보를 정리
+ $http = parse_url($trackback_url);
+ $obj->blog_name = Context::getBrowserTitle();
+ $obj->title = $document->title;
+ $obj->excerpt = cut_str($document->content, 240);
+ $obj->url = sprintf("%s?document_srl=%d", Context::getRequestUri(), $document->document_srl);
+
+ // blog_name, title, excerpt, url의 문자열을 요청된 charset으로 변경
+ if($charset && function_exists('iconv')) {
+ foreach($obj as $key=>$val) {
+ $obj->{$key} = iconv('UTF-8',$charset,$val);
+ }
+ }
+
+ // socket으로 발송할 내용 작성
+ if($http['query']) $http['query'].="&";
+ if(!$http['port']) $http['port'] = 80;
+
+ $content =
+ sprintf(
+ "title=%s&".
+ "url=%s&".
+ "blog_name=%s&".
+ "excerpt=%s",
+ urlencode($obj->title),
+ urlencode($obj->url),
+ urlencode($obj->blog_name),
+ urlencode($obj->excerpt)
+ );
+ if($http['query']) $content .= '&'.$http['query'];
+ $content_length = strlen($content);
+
+ // header 정리
+ $header =
+ sprintf(
+ "POST %s HTTP/1.1\r\n".
+ "Host: %s\r\n".
+ "Content-Type: %s\r\n".
+ "Content-Length: %s\r\n\r\n".
+ "%s\r\n",
+ $http['path'],
+ $http['host'],
+ "application/x-www-form-urlencoded",
+ $content_length,
+ $content
+ );
+ if(!$http['host']||!$http['port']) return;
+
+ // 발송하려는 대상 서버의 socket을 연다
+ $fp = @fsockopen($http['host'], $http['port'], $errno, $errstr, 5);
+ if(!$fp) return;
+
+ // 작성한 헤더 정보를 발송
+ fputs($fp, $header);
+
+ // 결과를 기다림 (특정 서버의 경우 EOF가 떨어지지 않을 수가 있음
+ while(!feof($fp)) {
+ $line = trim(fgets($fp, 4096));
+ if(eregi("^",$line)) break;
+ }
+
+ // socket 닫음
+ fclose($fp);
+ }
+ }
+?>
diff --git a/modules/poll/trackback.model.php b/modules/poll/trackback.model.php
new file mode 100644
index 000000000..b05d65005
--- /dev/null
+++ b/modules/poll/trackback.model.php
@@ -0,0 +1,116 @@
+trackback_srl = $trackback_srl;
+ return executeQuery('trackback.getTrackback', $args);
+ }
+
+ /**
+ * @brief document_srl 에 해당하는 엮인글의 전체 갯수를 가져옴
+ **/
+ function getTrackbackCount($document_srl) {
+ $args->document_srl = $document_srl;
+ $output = executeQuery('trackback.getTrackbackCount', $args);
+ $total_count = $output->data->count;
+
+ return (int)$total_count;
+ }
+
+ /**
+ * @brief 특정 document에 특정 ip로 기록된 트랙백의 갯수
+ * spamfilter 에서 사용할 method임
+ **/
+ function getTrackbackCountByIPAddress($document_srl, $ipaddress) {
+ $args->document_srl = $document_srl;
+ $args->ipaddress = $ipaddress;
+ $output = executeQuery('trackback.getTrackbackCountByIPAddress', $args);
+ $total_count = $output->data->count;
+
+ return (int)$total_count;
+ }
+
+ /**
+ * @brief 특정 문서에 속한 엮인글의 목록을 가져옴
+ **/
+ function getTrackbackList($document_srl) {
+ $args->document_srl = $document_srl;
+ $args->list_order = 'list_order';
+ $output = executeQuery('trackback.getTrackbackList', $args);
+
+ if(!$output->toBool()) return $output;
+
+ $trackback_list = $output->data;
+
+ if(!is_array($trackback_list)) $trackback_list = array($trackback_list);
+
+ return $trackback_list;
+ }
+
+ /**
+ * @brief 모든 엮인글를 시간 역순으로 가져옴 (관리자용)
+ **/
+ function getTotalTrackbackList($obj) {
+ // 검색 옵션 정리
+ $search_target = trim(Context::get('search_target'));
+ $search_keyword = trim(Context::get('search_keyword'));
+
+ if($search_target && $search_keyword) {
+ switch($search_target) {
+ case 'url' :
+ if($search_keyword) $search_keyword = str_replace(' ','%',$search_keyword);
+ $args->s_url = $search_keyword;
+ break;
+ case 'title' :
+ if($search_keyword) $search_keyword = str_replace(' ','%',$search_keyword);
+ $args->s_title= $search_keyword;
+ break;
+ case 'blog_name' :
+ if($search_keyword) $search_keyword = str_replace(' ','%',$search_keyword);
+ $args->s_blog_name= $search_keyword;
+ break;
+ case 'excerpt' :
+ if($search_keyword) $search_keyword = str_replace(' ','%',$search_keyword);
+ $args->s_excerpt = $search_keyword;
+ break;
+ case 'regdate' :
+ $args->s_regdate = $search_keyword;
+ break;
+ case 'ipaddress' :
+ $args->s_ipaddress= $search_keyword;
+ break;
+ }
+ }
+
+
+ // 변수 설정
+ $args->sort_index = $obj->sort_index;
+ $args->page = $obj->page?$obj->page:1;
+ $args->list_count = $obj->list_count?$obj->list_count:20;
+ $args->page_count = $obj->page_count?$obj->page_count:10;
+
+ // trackback.getTotalTrackbackList 쿼리 실행
+ $output = executeQuery('trackback.getTotalTrackbackList', $args);
+
+ // 결과가 없거나 오류 발생시 그냥 return
+ if(!$output->toBool()||!count($output->data)) return $output;
+
+ return $output;
+ }
+ }
+?>
diff --git a/modules/poll/trackback.view.php b/modules/poll/trackback.view.php
new file mode 100644
index 000000000..9d77fa147
--- /dev/null
+++ b/modules/poll/trackback.view.php
@@ -0,0 +1,63 @@
+page = Context::get('page'); ///< 페이지
+ $args->list_count = 50; ///< 한페이지에 보여줄 글 수
+ $args->page_count = 10; ///< 페이지 네비게이션에 나타날 페이지의 수
+
+ $args->sort_index = 'list_order'; ///< 소팅 값
+
+ // 목록 구함
+ $oTrackbackModel = &getModel('trackback');
+ $output = $oTrackbackModel->getTotalTrackbackList($args);
+
+ // 목록의 loop를 돌면서 mid를 구하기 위한 module_srl값을 구함
+ $trackback_count = count($output->data);
+ if($trackback_count) {
+ foreach($output->data as $key => $val) {
+ $module_srl = $val->module_srl;
+ if(!in_array($module_srl, $module_srl_list)) $module_srl_list[] = $module_srl;
+ }
+ if(count($module_srl_list)) {
+ $args->module_srls = implode(',',$module_srl_list);
+ $mid_output = executeQuery('module.getModuleInfoByModuleSrl', $args);
+ if($mid_output->data && !is_array($mid_output->data)) $mid_output->data = array($mid_output->data);
+ for($i=0;$idata);$i++) {
+ $mid_info = $mid_output->data[$i];
+ $module_list[$mid_info->module_srl] = $mid_info;
+ }
+ }
+ }
+
+ // 템플릿에 쓰기 위해서 변수 설정
+ Context::set('total_count', $output->total_count);
+ Context::set('total_page', $output->total_page);
+ Context::set('page', $output->page);
+ Context::set('trackback_list', $output->data);
+ Context::set('page_navigation', $output->page_navigation);
+ Context::set('module_list', $module_list);
+
+ // 템플릿 지정
+ $this->setTemplatePath($this->module_path.'tpl');
+ $this->setTemplateFile('trackback_list');
+ }
+
+ }
+?>