라공홈 커스텀 함수 백업/배포
개인적인 백업을 겸해서 배포.
민감한 설정을 건드리는 부분이 많으니 적당히 필요한 부분을 선택해서 적용할 것.
익명 질문은 https://asked.kr/linder0118
1. autolink() 함수
위치 : lib > board_common.lib.php
커스텀 내용 :
1) 지비님의 텍스트 서식 모음(https://posty.pe/oieuma) 적용
2) 비제 님의 치환자(https://mysunshinedying.tistory.com/20) 적용
function autolink($text, $bo_table = '') {
// 0. <code>, <pre> 태그 내용을 임시로 보호
$code_blocks = array();
$code_index = 0;
// <pre>...</pre> 먼저 보호 (중첩된 <code> 포함)
$text = preg_replace_callback('/<pre[^>]*>.*?<\/pre>/is', function($matches) use (&$code_blocks, &$code_index) {
$placeholder = "___CODE_BLOCK_{$code_index}___";
$code_blocks[$placeholder] = $matches[0];
$code_index++;
return $placeholder;
}, $text);
// <code>...</code> 보호 (단독 인라인 코드용)
$text = preg_replace_callback('/<code[^>]*>.*?<\/code>/is', function($matches) use (&$code_blocks, &$code_index) {
$placeholder = "___CODE_BLOCK_{$code_index}___";
$code_blocks[$placeholder] = $matches[0];
$code_index++;
return $placeholder;
}, $text);
// 기존 HTML 태그 보호 (img, a, iframe - 이미 변환된 태그 중복 변환 방지)
$html_tags = array();
$html_index = 0;
// <img> 태그 보호
$text = preg_replace_callback('/<img[^>]*>/is', function($matches) use (&$html_tags, &$html_index) {
$placeholder = "___HTML_TAG_{$html_index}___";
$html_tags[$placeholder] = $matches[0];
$html_index++;
return $placeholder;
}, $text);
// <a>...</a> 태그 보호
$text = preg_replace_callback('/<a[^>]*>.*?<\/a>/is', function($matches) use (&$html_tags, &$html_index) {
$placeholder = "___HTML_TAG_{$html_index}___";
$html_tags[$placeholder] = $matches[0];
$html_index++;
return $placeholder;
}, $text);
// <iframe>...</iframe> 태그 보호
$text = preg_replace_callback('/<iframe[^>]*>.*?<\/iframe>/is', function($matches) use (&$html_tags, &$html_index) {
$placeholder = "___HTML_TAG_{$html_index}___";
$html_tags[$placeholder] = $matches[0];
$html_index++;
return $placeholder;
}, $text);
// 0. 텍꾸 패턴 선처리
$text = preg_replace('`제목1\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--title1">$1</span>', $text);
$text = preg_replace('`제목2\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--title2">$1</span>', $text);
$text = preg_replace('`제목3\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--title3">$1</span>', $text);
$text = preg_replace('`제목4\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--title4">$1</span>', $text);
$text = preg_replace('`제목5\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--title5">$1</span>', $text);
$text = preg_replace('`제목6\[(?![\s*])(.*?)(?<![\s*])\]-(?![\s*])(.*?)(?<![\s*])-`', '<span class="textggu--title6" data-text="$2">$1</span>', $text);
$text = preg_replace('`제목7\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--title7">$1</span>', $text);
$text = preg_replace('`소제1\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--sub1">$1</span>', $text);
$text = preg_replace('`소제2\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--sub2">$1</span>', $text);
$text = preg_replace('`소제3\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--sub3">$1</span>', $text);
$text = preg_replace('`소제4\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--sub4">$1</span>', $text);
$text = preg_replace('`소제5\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--sub5">$1</span>', $text);
$text = preg_replace('`소제6\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--sub6">$1</span>', $text);
$text = preg_replace('`소제7\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--sub7">$1</span>', $text);
$text = preg_replace('`기타1\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--etc1">$1</span>', $text);
$text = preg_replace('`기타2\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--etc2">$1</span>', $text);
$text = preg_replace('`기타3\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--etc3">$1</span>', $text);
$text = preg_replace('`기타4\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--etc4">$1</span>', $text);
$text = preg_replace('`기타5\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--etc5">$1</span>', $text);
$text = preg_replace('`기타6\[(?![\s*])(.*?)(?<![\s*])\]`', '<span class="textggu--etc6">$1</span>', $text);
$text = preg_replace('`요1\[(?![\s*])(.*?)(?<![\s*])\]-(?![\s*])(.*?)(?<![\s*])-`', '<span class="details1"><details><summary>$2</summary>$1</details></span>', $text);
$text = preg_replace('/(?<!\*)\*\*(?![\s*])(.*?)(?<![\s*])\*\*(?!\*)/', '<strong>$1</strong>', $text);
$text = preg_replace('/(?<!\*)\*(?![\s*])(.*?)(?<![\s*])\*(?!\*)/', '<span class="italictext">$1</span>', $text);
// 1. 비디오[URL] 패턴 먼저 처리
$video_bracket_pattern = '/비디오\[(https?:\/\/[^\]]+)\]/i';
$text = preg_replace_callback($video_bracket_pattern, function($matches) {
$url = trim($matches[1]);
// 유튜브 링크 변환 (embed URL 포함)
if (
preg_match('/youtu\.be\/([a-zA-Z0-9_-]+)/', $url, $id) ||
preg_match('/youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/', $url, $id) ||
preg_match('/youtube\.com\/embed\/([a-zA-Z0-9_-]+)/', $url, $id)
) {
$videoId = $id[1];
return '<div class="auto_link_video"><iframe src="https://www.youtube.com/embed/'.$videoId.'" frameborder="0" allowfullscreen></iframe></div>';
}
return '비디오 링크 오류';
}, $text);
// 2. 이미지[URL] 패턴 처리
$image_bracket_pattern = '/이미지\[(https?:\/\/[^\]]+)\]/i';
$text = preg_replace_callback($image_bracket_pattern, function($matches) {
$url = $matches[1];
return '<img src="' . $url . '" class="auto_link_image" alt="image" style="max-width: 100%; height: auto;">';
}, $text);
// 2.5. 링크[URL] 패턴 처리 (OG 메타데이터 기반 링크 카드)
$link_bracket_pattern = '/링크\[(https?:\/\/[^\]]+)\]/i';
$text = preg_replace_callback($link_bracket_pattern, function($matches) {
$url = trim($matches[1]);
$og = fetch_og_metadata($url);
if ($og) {
return render_link_card($url, $og);
}
// OG 파싱 실패 시 일반 링크로 표시
return '<a href="' . htmlspecialchars($url, ENT_QUOTES) . '" class="auto_link" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($url, ENT_QUOTES) . '</a>';
}, $text);
// 3. URL 패턴 (http, https) - 이미 HTML 태그 안에 있는 URL은 제외
// Negative lookbehind: href=" 또는 src=" 뒤가 아닌 경우만
$url_pattern = '/(?<!href=")(?<!src=")(https?:\/\/[^\s<>"']+)/i';
// URL을 자동 변환
$text = preg_replace_callback($url_pattern, function($matches) {
$url = $matches[1];
// 유튜브 URL 체크 → iframe 변환
if (
preg_match('/youtu\.be\/([a-zA-Z0-9_-]+)/', $url, $id) ||
preg_match('/youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/', $url, $id) ||
preg_match('/youtube\.com\/embed\/([a-zA-Z0-9_-]+)/', $url, $id)
) {
$videoId = $id[1];
return '<div class="auto_link_video"><iframe src="https://www.youtube.com/embed/'.$videoId.'" frameborder="0" allowfullscreen></iframe></div>';
}
// 이미지 확장자 체크 → img 태그
if (preg_match('/\.(jpg|jpeg|png|gif|webp)$/i', $url)) {
return '<img src="' . $url . '" class="auto_link_image" alt="image" style="max-width: 100%; height: auto;">';
}
// 일반 URL → "URL" 텍스트로 링크 표시
return '<a href="' . $url . '" class="auto_link" target="_blank" rel="noopener noreferrer">URL</a>';
}, $text);
// 4. 해시태그 자동링크 (제일 마지막에 처리 - URL 안의 #과 충돌 방지)
// HTML 태그 내부는 처리하지 않음 (style 속성 내 색상코드 보호)
$parts = preg_split('/(<[^>]+>)/s', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach ($parts as &$part) {
// HTML 태그가 아닌 텍스트 부분만 처리
if (strpos($part, '<') !== 0) {
$hashtag_pattern = '/#([가-힣a-zA-Z][^\s#<>]*)/u';
$part = preg_replace_callback($hashtag_pattern, function($matches) use ($bo_table) {
$tag = $matches[1];
// 태그에서 끝의 특수문자 제거 (;, !, ?, . 등)
$clean_tag = preg_replace('/[;\!\?\.\,\)\]]+$/', '', $tag);
// 16진수 색상코드(3자리, 4자리, 6자리, 8자리) 체크
if (preg_match('/^[0-9a-fA-F]{3,8}$/', $clean_tag)) {
return '#' . $tag;
}
// 순수 숫자는 해시태그로 변환하지 않음
if (preg_match('/^\d+/', $clean_tag)) {
return '#' . $tag;
}
$link = G5_BBS_URL . '/board.php?bo_table=' . $bo_table . '&hash=' . urlencode($tag);
return '<a href="' . $link . '" class="hashtag">#' . $tag . '</a>';
}, $part);
}
}
unset($part);
$text = implode('', $parts);
// 5. 보호했던 HTML 태그 복원 (img, a, iframe)
foreach ($html_tags as $placeholder => $original) {
$text = str_replace($placeholder, $original, $text);
}
// 6. 보호했던 <code>, <pre> 태그 복원
foreach ($code_blocks as $placeholder => $original) {
$text = str_replace($placeholder, $original, $text);
}
return $text;
}2. process_content_with_unused_images() 함수
위치 : lib > board_common.lib.php
특징 : 함께 배포하는 convert_placeholders_to_videos() 함수 없을 시 사용 불가
<!--StartFragment-->
커스텀 내용 :
1) convert_placeholders_to_videos() 추가
function process_content_with_unused_images($content, $bo_table, $wr_id, $bo_use_dhtml_editor, &$unused_images) {
// 1. 사용되지 않은 이미지 추출 (플레이스홀더 변환 전)
$unused_images = get_unused_images($content, $bo_table, $wr_id);
// 3. conv_content() 호출 (HTML 정리, XSS 방지 등)
$content = conv_content($content, $bo_use_dhtml_editor);
// 4. autolink() 호출 (URL 자동 링크, 해시태그, 이미지[URL], 비디오[URL])
$content = autolink($content, $bo_table);
// 2. 플레이스홀더를 실제 이미지로 변환
$content = convert_placeholders_to_images($content, $bo_table, $wr_id);
$content = convert_placeholders_to_videos($content, $bo_table, $wr_id);
// 5. 이모티콘 변환 (/이름 → 이미지)
if (function_exists('convert_emoticon')) {
$content = convert_emoticon($content);
}
return $content;
}3. convert_placeholders_to_videos() 함수
위치 : lib > board_common.lib.php
특징 : bbs > write.file.extend.php 필요
커스텀 내용 :
1) 업로드한 비디오 파일을 {비디오:0}의 방식으로 본문 내에 삽입 가능하게 함
function convert_placeholders_to_videos($content, $bo_table, $wr_id) {
global $g5;
if (!isset($g5['board_file_table'])) {
return $content;
}
$bo_table = preg_replace('/[^a-zA-Z0-9_]/', '', (string)$bo_table);
$wr_id = (int)$wr_id;
if ($bo_table === '' || $wr_id <= 0) {
return $content;
}
// 1) 해당 글의 첨부파일 전체 조회 (순서 유지)
$sql = "SELECT bf_file, bf_no
FROM {$g5['board_file_table']}
WHERE bo_table = '{$bo_table}'
AND wr_id = '{$wr_id}'
ORDER BY bf_no ASC";
$result = sql_query($sql, false);
if (!$result) {
return $content;
}
// 2) 비디오만 순서 배열로 구성
$mime_map = [
'mp4' => 'video/mp4',
'webm' => 'video/webm',
'ogg' => 'video/ogg',
'mov' => 'video/quicktime',
'avi' => 'video/x-msvideo',
'mkv' => 'video/x-matroska',
'flv' => 'video/x-flv'
];
$videos = [];
while ($row = sql_fetch_array($result)) {
$ext = strtolower(pathinfo($row['bf_file'], PATHINFO_EXTENSION));
if (!isset($mime_map[$ext])) {
continue;
}
$videos[] = [
'file' => $row['bf_file'],
'mime' => $mime_map[$ext]
];
}
if (empty($videos)) {
return $content;
}
// 3) {비디오:n} → n번째 비디오로 치환
$content = preg_replace_callback(
'/\{비디오:(\d+)(?:-(\d+))?\}/u',
function ($m) use ($videos, $bo_table) {
$n = (int)$m[1] - 1; // 1-based → 0-based
$width = isset($m[2]) ? (int)$m[2] : null;
if (!isset($videos[$n])) {
return $m[0];
}
$file = $videos[$n]['file'];
$mime = $videos[$n]['mime'];
$path = G5_DATA_URL . '/file/' . $bo_table . '/' . $file;
$style = "max-width:100%; height:auto;";
if ($width && $width > 0) {
$style = "max-width:{$width}px; height:auto;";
}
return "<video controls style='{$style}'>
<source src='{$path}' type='{$mime}'>
이 브라우저는 video 태그를 지원하지 않습니다.
</video>";
},
$content
);
return $content;
}4. 검색방지용 robots.txt
위치 : root
특징 : 텍스트 파일로 저장
커스텀 내용 :
1) 모든 검색 에이전트 비허용
2) 단, 트위터봇은 opengraph 사용 위해 허용.
User-agent : Twitterbot
User-agent : kakaotalk-scrap/1.0
Allow : /
-----------------------
User-agent: *
Disallow: /
댓글목록
등록된 댓글이 없습니다.