라공홈 커스텀 함수 백업/배포

<span class="sv_member">린더</span>
린더 @frauroteschuhe
2026-02-04 12:15

개인적인 백업을 겸해서 배포.

민감한 설정을 건드리는 부분이 많으니 적당히 필요한 부분을 선택해서 적용할 것.

익명 질문은 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: /



댓글목록

등록된 댓글이 없습니다.