BLOG main image
현석이와 민석이가 있어 행복한 우리집
Creative Commons License

2. 텍스트큐브 1차 영역

2.1. 주요 작업내용

사용자 삽입 이미지
운영중인 제로보드XE 데이터에서 텍스트큐브 데이터 구조에 맞춰 백업XML을 생성하는 작업을 진행하였다. 요즘 나오는 블로그/게시판 어플리케이션의 경우 데이터의 백업과 복구에 구조화된 XML파일을 사용한다. 이는 이기종 데이터베이스에 대한 독립성과 데이터베이스의 데이터 및 첨부파일을 하나의 파일에 담아서 관리할 수 있다는 점에서 데이터 마이그레이션의 자유도를 높여준다.

텍스트큐브를 설치하면 관리자 메뉴의 데이터 관리 부분에 데이터를 백업할 수 있는 기능이 있는데, 이 단계의 작업은 데이터백업 프로그램을 수정하는 것이다. 여기서 한가지 고려해야 하는 사항이 생성되고 난 이후 XML파일 사이즈에 관한 것이다. 본인이 서비스 받고 있는 80포트의 경우 단일 파일사이즈가 2기가을 넘을 수 없다. 따라서 본인이 가지고 있는 컨텐츠의 전체량이 2기가가 넘는다면 백업프로그램에서 2기가 미만으로 파일을 쪼개서 생성하는 로직이 추가되어야 한다.

2.2. 백업 XML 파일 주요구조

XML을 구성하는 주요데이터는, 블로그 셋팅 정보, 카테고리 정보, 게시글 정보(첨부파일, 댓글 포함), 기타 정보로 구성된다. 기타 정보는 공지사항, 테그목록, 링크, 유입경로 정보, 댓글알림, 댓글알림 싸이트 정보, 각종 통계정보,  스킨/플러그인/사용자 셋팅 정보, 방명록 정보, 필터정보, 피드 정보로 이루어져 있다. 본인의 경우 동일 서버에 제로보드XE와 텍스트큐브가 동시에 설치된 환경에서 마이그레이션을 수행하였으므로 카테고리 정보와 게시글 정보만 제로보드XE에서 추출하고, 블로그 셋팅정보와 나머지 정보는 초기 설정된 텍스트큐브 1차 DB에서 해당 정보를 추출하도록 구성하였다. 따라서 본인이 수정한 부분은 카테고리 정보와 게시글 추출 부분에 한정되어 있으며, 파일 분할로 인해 셋팅정보의 마이그레이션 여부를 true로 처리한 부분과 파일분할 로직이 추가되었다.

2.3. 마이그레이션 방법 수정

초기 백업프로그램은 마이그레이션 방법이 전체 삭제 후 재적재로 셋팅되어 있다. 본인은 파일을 몇개로 분리 후 ADD하는 형태로 마이그레이션을 진행하였으므로, 마이그레이션 방법을 true로 설정하였다.

// ROOT/interface/owner/data/export/index.php
// 마이그레이션 방법 true면 기존데이터에 추가, false면 삭제 후 재적재
$writer->write('<blog type="tattertools/1.1" migrational="true">');

2.4. 카테고리(Category()) 정보 추출

텍스트큐브의 경우 블로그 프로그램이므로 카테고리가 제로보드 XE의 모듈에 해당한다. 제로보드XE의 경우 게시판 하나는 모듈로 구성되며, 모듈에 별도의 카데고리를 가지고 있는 구조이다. 따라서 제로보드XE를 텍스트큐브로 전환하기 위해서는 모듈을 카테고리로 추출하고, 모듈에 구성된 카테고리를 텍스트큐브의 child 카테고리로 구성해야 한다. 본인의 싸이트의 경우 사진방, 에세이, 비디오방, 자료실 이라고 하는 게시판이 있고, 사진방은 년도별로 카테고리가 구성되어 있다. 정석으로 하려면 사진방을 1차카테고리로 년도를 2차카테고리로 추출해야하지만, 로직이 복잡해지는 점을 감안하여, 년도는 별도의 카테고리로 추출하지 않고, 이관 완료 후 관리자 메뉴에서 수작업으로 다시 카테고리를 구분해 주었다.

따라서 본인의 경우, chile 카테고리를 별도로 추출하지 않고, 주석처리하고, 제로보드XE의 모듈 테이블을 카테고리로 추출하였다. 그리고, 첫번째 파일에서만 카테고리 정보를 추출하도록 if로직을 처리하였다. ADD형태로 데이터를 적재하는데, 2번째 이상의 파일에서 카테고리가 들어오면 동일한 카테고리가 중복으로 생성된다.

// ROOT/interface/owner/data/export/index.php
// 첫번째 파일에서만 카테고리 정보 추출
// child 카테고리는 별도로 추출하지 않으므로 주석처리 함
$category = new Category();
if ($start_post_id == 0 && $category->open()) {
 do {
  if($category->id != 0) {
      $category->escape();
   $writer->write('<category>' . '<name>' . $category->name . '</name>' . '<priority>' . $category->priority . '</priority>');
   //child 카테고리 추출하지 않음
   //if ($childCategory = $category->getChildren()) {
   // do {
   //  $childCategory->escape();
   //  $writer->write('<category>' . '<name>' . $childCategory->name . '</name>' . '<priority>' . $childCategory->priority . '</priority>' . '</category>');
   // } while ($childCategory->shift());
   // $childCategory->close();
   //}
   $writer->write('</category>');
   $writer->write(CRLF);
  } else {
      $category->escape();
   $writer->write('<category>' . '<name>' . $category->name . '</name>' . '<priority>' . $category->priority . '</priority>');
   $writer->write('<root>1</root>');
   $writer->write('</category>');
   $writer->write(CRLF);
  }
 } while ($category->shift());
 $category->close();
}

// ROOT/components/Textcube.Data.Category.php
// 제로보드 XE의 모듈테이블(xez_modules)에서 카테고리 정보 Select
/*@polymorphous(bool $parentOnly, $fields, $sort)@*/
 /*@polymorphous(numeric $id, $fields, $sort)@*/
 function open($filter = true, $fields = '*', $sort = 'priority') {
  global $database;
  if (is_numeric($filter)) {
   $filter = 'AND id = ' . $filter;
  } else if (is_bool($filter)) {
   if ($filter)
    $filter = 'AND parent IS NULL';
  } else if (!empty($filter)) {
   $filter = 'AND ' . $filter;
  }
  if (!empty($sort))
   $sort = 'ORDER BY ' . $sort;
  $this->close();
  $this->_result = mysql_query("select 1 as blogid, case mid when 'essay' then 1 when 'photo' then 2 when 'video' then 3 when 'pds' then 4 end as id, NULL as parent, mid as name, case mid when 'essay' then 1 when 'photo' then 2 when 'video' then 3 when 'pds' then 4 end as priority, 0 as entries, 0 as entriesInLogin, mid as label, 2 as visibility, NULL as bodyId from xez_modules order by 2");
  if ($this->_result)
   $this->_count = mysql_num_rows($this->_result);
  return $this->shift();
 }


2.5. 게시글(Post()) 정보 추출

텍스트큐브의 게시글(Post)은 게시본문, 첨부파일, 댓글을 포함하고 있는 구조이다. 본문과 첨부파일, 본문과 댓글은 1:N의 구조이며, 댓글의 경우 상하위 계층구조로 구성되고, 2단계까지만으로 구성되어 있다. 제로보드XE의 경우 게시글 간에도 계층이 존재하고, 댓글의 경우도 단계가 제약이 없이 무한이 댓글에 댓글을 달 수 있는 구조이다. 본인의 컨텐츠의 경우 다행이 이런 텍스트큐브의 게시글 구조에 적합하게 본문간에는 계층이 존재하지 않고, 댓글도 2단계까지밖에 존재하지 않아 수월하게 마이그레이션 로직을 구현할 수 있었다. 만일 자신이 가진 컨텐츠가 상당히 깊은 댓글계층과 게시글 간의 계층이 존재한다면 이를 처리하기 위한 별도의 로직이 필요하다.

// ROOT/interface/owner/data/export/index.php
// 게시글 추출 부분. 첫부분에 파일 분할을 위해 등록일자 구간을 SQL 조건으로 셋팅 함
$d_fil =  sprintf("xd.regdate > '%s' AND xd.regdate <= '%s' ", $from_regdate, $to_regdate);
if ($post->open($d_fil, '*', 'published, id')) {
 do {
   $post_id = $post_id + 1;
  $writer->write('<post' . ' slogan="' . htmlspecialchars($post->slogan) . '"' . $newlineStyle . '>' . '<id>' . $post_id . '</id>' . '<visibility>' . $post->visibility . '</visibility>' . '<title>' . htmlspecialchars($post->title) . '</title>' . '<content formatter="' . htmlspecialchars($post->contentFormatter) . '" editor="' . htmlspecialchars($post->contentEditor) .'">' . htmlspecialchars(UTF8::correct($post->content)) . '</content>' . '<location>' . htmlspecialchars($post->location) . '</location>' . (!is_null($post->password) ? '<password>' . htmlspecialchars($post->password) . '</password>' : '') . '<acceptComment>' . $post->acceptComment . '</acceptComment>' . '<acceptTrackback>' . $post->acceptTrackback . '</acceptTrackback>' . '<published>' . $post->published . '</published>' . '<created>' . $post->created . '</created>' . '<modified>' . $post->modified . '</modified>');

  if ($post->category)
   $writer->write('<category>' . htmlspecialchars($post->category) . '</category>');
  if ($post->loadTags()) {
   foreach ($post->tags as $tag)
    $writer->write('<tag>' . htmlspecialchars($tag) . '</tag>');
  }
  $writer->write(CRLF);
  if ($attachment = $post->getAttachments()) {
   do {
    $writer->write('<attachment' . ' mime="' . htmlspecialchars($attachment->mime) . '"' . ' size="' . $attachment->size . '"' . ' width="' . $attachment->width . '"' . ' height="' . $attachment->height . '"' . '>' . '<name>' . htmlspecialchars($attachment->name) . '</name>' . '<label>' . htmlspecialchars($attachment->label) . '</label>' . '<enclosure>' . ($attachment->enclosure ? 1 : 0) . '</enclosure>' . '<attached>' . $attachment->attached . '</attached>' . '<downloads>' . $attachment->downloads . '</downloads>');
    if ($includeFileContents && file_exists($attachment->uploaded_filename)) {
     $writer->write('<content>');
     Base64Stream::encode($attachment->uploaded_filename, $writer);
     $writer->write('</content>');
    }
    $writer->write('</attachment>');
    $writer->write(CRLF);
   } while ($attachment->shift());
   $attachment->close();
  }
  if ($comment = $post->getComments()) {
   do {
    if($comment->isFiltered == 0) {
      $comment_id = $comment_id + 1;
     $writer->write('<comment>' . '<id>'. $comment_id . '</id>' . '<commenter' . ' id="' . $comment->commenter . '">' . '<name>' . htmlspecialchars(UTF8::correct($comment->name)) . '</name>' . '<homepage>' . htmlspecialchars(UTF8::correct($comment->homepage)) . '</homepage>' . '<ip>' . $comment->ip . '</ip>' . '<openid>' . $comment->openid . '</openid>' . '</commenter>' . '<content>' . htmlspecialchars($comment->content) . '</content>' . '<password>' . htmlspecialchars($comment->password) . '</password>' . '<secret>' . htmlspecialchars($comment->secret) . '</secret>' . '<written>' . $comment->written . '</written>' . '<isFiltered>' . $comment->isFiltered . '</isFiltered>');
     $writer->write(CRLF);
     if ($childComment = $comment->getChildren()) {
      do {
       if($childComment->isFiltered == 0) {
         $comment_id = $comment_id + 1;
        $writer->write('<comment>' . '<id>' . $comment_id . '</id>' . '<commenter' . ' id="' . $childComment->commenter . '"' . '>' . '<name>' . htmlspecialchars(UTF8::correct($childComment->name)) . '</name>' . '<homepage>' . htmlspecialchars(UTF8::correct($childComment->homepage)) . '</homepage>' . '<ip>' . $childComment->ip . '</ip>' . '<openid>' . $comment->openid . '</openid>' . '</commenter>' . '<content>' . htmlspecialchars($childComment->content) . '</content>' . '<password>' . htmlspecialchars($childComment->password) . '</password>' . '<secret>' . htmlspecialchars($childComment->secret) . '</secret>' . '<written>' . $childComment->written . '</written>' . '<isFiltered>' . $childComment->isFiltered . '</isFiltered>' . '</comment>');
       $writer->write(CRLF);
       }
      } while ($childComment->shift());
      $childComment->close();
     }
     $writer->write('</comment>');
     $writer->write(CRLF);
    }
   } while ($comment->shift());
   $comment->close();
  }
  if ($trackback = $post->getTrackbacks()) {
   do {
    if($trackback->isFiltered == 0) {
     $writer->write('<trackback>' . '<url>' . htmlspecialchars(UTF8::correct($trackback->url)) . '</url>' . '<site>' . htmlspecialchars(UTF8::correct($trackback->site)) . '</site>' . '<title>' . htmlspecialchars(UTF8::correct($trackback->title)) . '</title>' . '<excerpt>' . htmlspecialchars(UTF8::correct($trackback->excerpt)) . '</excerpt>' . '<ip>' . $trackback->ip . '</ip>' . '<received>' . $trackback->received . '</received>' . '<isFiltered>' . $trackback->isFiltered . '</isFiltered>' . '</trackback>');
     $writer->write(CRLF);
    }
   } while ($trackback->shift());
   $trackback->close();
  }
  if ($log = $post->getTrackbackLogs()) {
   $writer->write('<logs>');
   do {
    $writer->write('<trackback>' . '<url>' . htmlspecialchars(UTF8::correct($log->url)) . '</url>' . '<sent>' . $log->sent . '</sent>' . '</trackback>');
    $writer->write(CRLF);
   } while ($log->shift());
   $writer->write('</logs>');
   $log->close();
  }
  $writer->write('</post>');
 } while ($post->shift());
 $post->close();
}


2.5.1. 게시글 본문(Post()) 추출

게시글 본문의 경우, 제로보드XE에서만 사용되는 이미지처리를 위한 object 테그가 포함되어 있다. 이를 그대로 이관할 경우 이미지가 모두 엑박으로 뜨게 된다. 본인의 경우 이를 처리하기 위해 게시글 본문에서 <BR> 테그를 제외한 모든 HTML테그를 삭제처리 하고, 첨부파일에 있는 이미지파일을 텍스트큐브의 이미지갤러리 오브젝트로 변환처리하였다. 이런 처리를 위해 게시글 본문처리 부분에서 첨부파일 정보를 한번 더 읽어야 했다.

// ROOT/components/Textcube.Data.Post.php
// 제로보드XE의 게시글 테이블에서 본문정보 Select.
// 줄바꿈을 제외한 나머지 HTML 테그 무력화를 위한 부분 추가
 /*@polymorphous(numeric $id, $fields, $sort)@*/
 function open($filter = '', $fields = '*', $sort = 'published DESC') {
  global $database;
  $blogid = getBlogId();
  if (is_numeric($filter))
   $filter = 'AND id = ' . $filter;
  else if (!empty($filter))
   $filter = 'AND ' . $filter;
  if (!empty($sort))
   $sort = 'ORDER BY ' . $sort;
  $this->close();
  $this->_result = mysql_query("SELECT 1 AS blogid, tu.userid AS userid, xd.document_srl AS id, 0 AS draft, 2 AS visibility, xm.mid AS category, xd.title AS title, xd.title AS slogan, REPLACE(REPLACE(REPLACE(REPLACE(xd.content,'<br','<BR'),'<BR />','<BR>'),'<BR>','QWERTYUIOPASDFGHJKL'),'<IMG','((-------IMAGE-------))<IMG') AS content, 'ttml' AS contentFormatter, 'modern' AS contentEditor, '/' AS location, '' AS PASSWORD , 1 AS acceptComment, 1 AS acceptTrackback, UNIX_TIMESTAMP( xd.regdate ) AS published, UNIX_TIMESTAMP( xd.regdate ) AS created, UNIX_TIMESTAMP( xd.last_update ) AS modified, xd.comment_count AS comments, xd.trackback_count AS trackbacks FROM xez_documents xd, tcd_Users tu, xez_modules xm WHERE (xd.user_name = tu.name OR xd.nick_name = tu.name) AND xd.module_srl = xm.module_srl $filter ORDER BY 17");
  if ($this->_result)
   $this->_count = mysql_num_rows($this->_result);
  return $this->shift();
 }

// ROOT/components/Textcube.Data.Post.php
// Select 된 게시글 본문에 대한 Cleasing
 function shift() {
  $this->reset();
  if ($this->_result && ($row = mysql_fetch_assoc($this->_result))) {
   foreach ($row as $name => $value) {
    if ($name == 'blogid')
     continue;
    switch ($name) {
     case 'visibility':
      if ($value <= 0)
       $value = 'private';
      else if ($value == 1)
       $value = 'protected';
      else if ($value == 2)
       $value = 'public';
      else
       $value = 'syndicated';
      break;
     case 'category': /*@backward-compatibility@*/
      if (empty($value))
       $value = null;
      break;
     case 'acceptComment':
     case 'acceptTrackback':
      $value = $value ? true : false;
      break;
     case 'slogan':
      $value = UTF8::lessenAsEncoding(Post::makeSlogan($value), 255);
      break;
     case 'content':
      //$value = str_tag_truncate($value);
      break;

   }
    $this->$name = $value;
   }
   
   if($this->category == 'video'){
    $this->content = str_replace("QWERTYUIOPASDFGHJKL", "<BR />", $this->content);
   } else if($this->category == 'photo') {
    //HTML 테그제거 및 여백제거 
    $this->content = stripHTML($this->content);
    $this->content = str_replace("QWERTYUIOPASDFGHJKL", "<BR />", $this->content);
    $this->content = str_replace("((-------IMAGE-------))","", $this->content);
   
    $this->content = str_replace("<BR /><BR /><BR />", "<BR /><BR />", $this->content);
    $this->content = str_replace("<BR /><BR /><BR />", "<BR /><BR />", $this->content);
    $this->content = str_replace("<BR /><BR /><BR />", "<BR /><BR />", $this->content);
    $this->content = str_replace("<BR /><BR /><BR />", "<BR /><BR />", $this->content);
    $this->content = str_replace("<BR /><BR /><BR />", "<BR /><BR />", $this->content);
    $this->content = str_replace("<BR /><BR /><BR />", "<BR /><BR />", $this->content);
    $this->content = str_replace("<BR /><BR /><BR />", "<BR /><BR />", $this->content);
    $this->content = str_replace("<BR /><BR /><BR />", "<BR /><BR />", $this->content);
    $this->content = str_replace("<BR /><BR /><BR />", "<BR /><BR />", $this->content);
    $this->content = str_replace("<BR /><BR /><BR />", "<BR /><BR />", $this->content);
    $this->content = str_replace("<BR /><BR /><BR />", "<BR /><BR />", $this->content);
    $this->content = str_replace("<BR /><BR /><BR />", "<BR /><BR />", $this->content);
    $this->content = str_replace("<BR /><BR /><BR />", "<BR /><BR />", $this->content);
   
    //게시글에 첨부된 이미지 오브젝트 삽입 처리
    $gallery_content = '';
     if ($attachment = $this->getAttachments()) {
     $gallery_content = $gallery_content.'';
     $attachment->close();
    }
   
    $this->content = $this->content.$gallery_content;
   
   } else if($this->category == 'pds') {
    $this->content = stripHTML($this->content);
    $this->content = str_replace("QWERTYUIOPASDFGHJKL", "<BR />", $this->content);

        $pds_content = '';
     if ($attachment = $this->getAttachments()) {
     do {
      if (file_exists($attachment->uploaded_filename))
        $pds_content = $pds_content.'';
        ......
     } while ($attachment->shift());
     $attachment->close();
    }
   
    $this->content = $this->content.$pds_content;

   } else {
    $this->content = stripHTML($this->content);
    $this->content = str_replace("QWERTYUIOPASDFGHJKL", "<BR />", $this->content);
   }
 return true;
  }
  return false;
 }


2.5.2. 게시글의 첨부파일(Attachment()) 추출

텍스트큐브의 경우 첨부파일의 마임타입과 이미지일 경우 이미지 가로/세로 크기를 데이터로 관리한다. 하지만, 제로보드XE의 경우 별도로 이를 관리하지 않으므로, 이를 처리하기 위해 첨부파일에 대한 추가정보를 가져오는 부분을 처리하였다.

// ROOT/components/Textcube.Data.Attachment.php
// 제로보드XE의 첨부파일 테이블에서 첨부파일 정보 Select
 function open($filter = '', $fields = '*', $sort = 'regdate') {
  global $database;
  $blogid = getBlogId();
  if (!empty($filter))
   $filter = 'AND ' . $filter;
  if (!empty($sort))
   $sort = 'ORDER BY ' . $sort;
  $this->close();
  $this->_result = mysql_query("SELECT file_srl+1000000000 as file_srl, 1 as blogid ,upload_target_srl as parent ,source_filename as name ,source_filename as label ,REPLACE(uploaded_filename,'./files/attach','/web/ytno/www.ytno.com/zbxe/files/attach') as uploaded_filename ,'' as mime ,file_size as size ,0 as width ,0 as height ,UNIX_TIMESTAMP( regdate ) as attached ,0 as downloads ,0 as enclosure  FROM xez_files WHERE 1 = 1 $filter $sort");
  if ($this->_result) {
   if ($this->_count = mysql_num_rows($this->_result))
    return $this->shift();
   else
    mysql_free_result($this->_result);
  }
  unset($this->_result);
  return false;
 }

// ROOT/components/Textcube.Data.Attachment.php
// 첨부파일의 추가정보(마임타입, 이미지 사이즈) 추출
 function shift() {
  $this->reset();
  if ($this->_result && ($row = mysql_fetch_assoc($this->_result))) {
   foreach ($row as $name => $value) {
    if ($name == 'blogid')
     continue;
    switch ($name) {
     case 'enclosure':
      $value = $value ? true : false;
      break;
    }
    $this->$name = $value;
   }
   
   $this->name = $this->generateName($this->name);
   $this->mime = $this->getMIMEType($this->getFileExtension($this->name));

   if ($imageAttributes = getImageSize($this->uploaded_filename)) {
    $this->width = $imageAttributes[0];
    $this->height = $imageAttributes[1];
   } else {
    $this->width = 0;
    $this->height = 0;
   }

   return true;
  }
  return false;
 }


2.5.3. 댓글(Comment()) 추출

댓글 정보의 경우, 텍스트큐브에서는 HTML을 사용하지 않는다. 따라서, 댓글의 모든 HTML테그를 삭제처리 하였으며, 상하위 계층구조를 처리하였다.

// ROOT/components/Textcube.Data.Comment.php
// 제로보드XE의 댓글 정보를 Select
 function open($filter = '', $fields = '*', $sort = 'xc.regdate') {
  global $database;
  if (is_numeric($filter))
   $filter = 'AND id = ' . $filter;
  else if (!empty($filter))
   $filter = 'AND ' . $filter;
  if (!empty($sort))
   $sort = 'ORDER BY ' . $sort;
  $this->close();
  $this->_result = mysql_query("SELECT 1 AS blogid, CASE xc.user_id WHEN 'ytno' THEN 1  ELSE NULL END AS replier, xc.comment_srl AS id, '' AS openid, xc.document_srl AS entry, CASE xc.parent_srl WHEN 0  THEN NULL ELSE xc.parent_srl END AS parent, IFNULL( xm.user_name, xc.nick_name ) AS name, CASE xc.user_id WHEN 'ytno' THEN '' ELSE IFNULL( xm.password, xc.password ) END AS password , CASE xc.user_id WHEN 'ytno' THEN '' ELSE IFNULL( xm.homepage, '' )  END AS homepage, 0 AS secret, REPLACE(REPLACE(REPLACE(xc.content,'<br','<BR'),'<BR />','<BR>'),'<BR>','QWERTYUIOPASDFGHJKL') AS  comment , xc.ipaddress AS ip, UNIX_TIMESTAMP( xc.regdate ) AS written, 0 AS isFiltered FROM xez_comments xc LEFT OUTER JOIN xez_member xm ON xm.user_id = xc.user_id OR xc.nick_name = xm.user_name OR xc.nick_name = xm.nick_name WHERE 1 = 1 $filter $sort");
  if ($this->_result) {
   if ($this->_count = mysql_num_rows($this->_result))
    return $this->shift();
   else
    mysql_free_result($this->_result);
  }
  unset($this->_result);
  return false;
 }

// ROOT/components/Textcube.Data.Comment.php
// 댓글의 HTML 제거. 단 <BR>의 경우 개행문자로 변경
 function shift() {
  $this->reset();
  if ($this->_result && ($row = mysql_fetch_assoc($this->_result))) {
   foreach ($row as $name => $value) {
    if ($name == 'blogid')
     continue;
    switch ($name) {
     case 'replier':
      $name = 'commenter';
      break;
     case 'comment':
      $name = 'content';
      break;
    }
    $this->$name = $value;
   }
   
   $this->content = stripHTML($this->content);
   $this->content = str_replace("QWERTYUIOPASDFGHJKL", "\n", $this->content);
   
   return true;
  }
  return false;
 }


2.6. 기타 정보 추출

기타 정보의 경우 제로보드XE를 사용하지 않고, 초기 설치된 텍스트큐브의 데이터를 그대로 사용하였다. 즉, 소스의 변경을 하지 않았다.


2.7. 파일분리 추출

2GB의 파일 사이즈 제한으로 파일을 여러개로 분리하였으며, 이를 위해 등록일자에 대한 구간을 분리기준으로 설정하여 처리하였다.

// ROOT/interface/owner/data/export/index.php
// 추출구간 등록일 및 게시물 ID 셋팅
$start_post_id = 629;
$start_comment_id = 498;
$from_regdate = '20071000000000';
$to_regdate   = '30000000000000';

// ROOT/interface/owner/data/export/index.php
// 생성 XML파일명 분할 처리
$writer = new OutputWriter();
if (defined('__TEXTCUBE_BACKUP__')) {
 if (!file_exists(ROOT . '/cache/backup')) {
  mkdir(ROOT . '/cache/backup');
  @chmod(ROOT . '/cache/backup', 0777);
 }
 if (!is_dir(ROOT . '/cache/backup')) {
  exit;
 }
 if ($writer->openFile(ROOT . "/cache/backup/$start_post_id.xml")) {
 } else {
  exit;
 }
} else {
 if ($writer->openStdout()) {
  header('Content-Disposition: attachment; filename="Textcube-Backup-' . Timestamp::getDate() . '.xml"');
  header('Content-Description: Textcube Backup Data');
  header('Content-Transfer-Encoding: binary');
  header('Content-Type: application/xml');
 } else {
  exit;
 }
}

// ROOT/interface/owner/data/export/index.php
// 게시물 분할 Select
$d_fil =  sprintf("xd.regdate > '%s' AND xd.regdate <= '%s' ", $from_regdate, $to_regdate);
if ($post->open($d_fil, '*', 'published, id')) {
 do {
   $post_id = $post_id + 1;
....

2.8. 주의사항

XML파일에 의한 데이터 복구작업은 기존 데이터를 삭제 후 적재하는 방법과 기존 데이터에 추가하는 방법으로 구분하여 진행된다. 기존데이터에 대한 삭제 여부는 XML파일의 맨 상단의 <XML 테그부분의 migration="false" 속성에 의해 결정된다. false로 되어 있을 경우 기존 데이터를 삭제하고 적재하므로, 여러개의 파일로 XML을 생성한 경우라면 반드시 true로 추출하도록 추출로직에 반영해 주어야 한다.

여러개의 XML파일을 생성하는 경우라면, 게시글 본문을 제외한 나머지 부분은 2번째 파일부터는 추출하지 않도록 해야 한다. 첫번째 파일에서만 셋팅정보와 카테고리 및 기타 정보를 추출하게 해야 두번째 파일을 적재하는 과정에서 데이터가 엉키는 현상을 방지할 수 있다.

프로그램 수정 완료 후 백업을 수행할 때 반드시 파일을 포함하고, 다운로드 대신 서버에 백업파일을 생성해야 한다. 다운로드로 할 경우, 자칫 본인의 호스팅 트래픽 용량을 초과하여 더이상 추출이 불가능해지기 때문이다.

2008/04/05 - [에세이] - 제로보드XE에서 티스토리로 마이그레이션 성공
2008/04/07 - [에세이] - 마이그레이션[제로보드XE -> 티스토리] (1. 개요)
2008/04/08 - [에세이] - 마이그레이션[제로보드XE -> 티스토리] (2. 텍스트큐브 1차 영역)
2008/04/08 - [에세이] - 마이그레이션[제로보드XE -> 티스토리] (3. 텍스트큐브 2차 영역)
2008/04/08 - [에세이] - 마이그레이션[제로보드XE -> 티스토리] (4. 티스토리 영역)
2008/04/08 - [에세이] - 마이그레이션[제로보드XE -> 티스토리] (5. 맺음말 및 소감)


Posted by 노영택
◀ PREV : [1] : ... [58] : [59] : [60] : [61] : [62] : [63] : [64] : [65] : [66] : ... [750] : NEXT ▶

카테고리

분류 전체보기 (750)
생각 정리하기 (3)
블로그 이야기 (10)
신변잡기 (10)
사진방 (708)
비디오방 (17)
자료실 (2)

달력

«   2010/08   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31