라공에디션

Re: 유튜브, 트위터 임베딩 관련 autolink 함수 수정판 백업

<span class="sv_member">니루</span>
니루 @tabom0w0
2026-05-14 02:10
⚠️꼭 백업하고 진행하세요⚠️

lib/board_common.lib.php

function autolink($text, $bo_table = '')
{
    // 1. 태그 보호 (코드 블록 및 주요 HTML 태그)
    $placeholders = array();
    $placeholder_index = 0;

    // 니코동 및 GIST 임베드 스크립트 골라서 보호
    $text = preg_replace_callback('/<script[^>]+src="(?:https:\/\/embed\.nicovideo\.jp\/|https:\/\/gist\.github\.com\/)[^>]+><\/script>/is', function ($matches) use (&$placeholders, &$placeholder_index) {
        $key = "___PROTECT_BLOCK_{$placeholder_index}___";
        $placeholders[$key] = $matches[0];
        $placeholder_index++;
        return $key;
    }, $text);

    // 기존의 안전한 태그들(pre, code 등)도 이어서 보호
    $safe_patterns = [
        '/<pre[^>]*>.*?<\/pre>/is',
        '/<code[^>]*>.*?<\/code>/is',
        '/<a[^>]*>.*?<\/a>/is',
        '/<img[^>]*>/is',
        '/<iframe[^>]*>.*?<\/iframe>/is'
    ];

    foreach ($safe_patterns as $pattern) {
        $text = preg_replace_callback($pattern, function ($matches) use (&$placeholders, &$placeholder_index) {
            $key = "___PROTECT_BLOCK_{$placeholder_index}___";
            $placeholders[$key] = $matches[0];
            $placeholder_index++;
            return $key;
        }, $text);
    }

    // 1. 비디오 렌더링 공통 로직 (유튜브 + 동영상 파일)
    $get_video_render = function ($url) {
        $url = trim($url);

        // 1-1. 유튜브 체크
        $yt_pattern = '/(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/|shorts\/))([a-zA-Z0-9_-]+)/i';
        if (preg_match($yt_pattern, $url, $match)) {
            return '<div class="auto_link_video"><iframe src="https://www.youtube.com/embed/' . $match[1] . '" frameborder="0" allowfullscreen></iframe></div>';
        }

        // 1-2. 일반 동영상 파일 체크 (mp4, webm, ogv)
        if (preg_match('/\.(mp4|webm|ogv)$/i', $url)) {
            $ext = pathinfo($url, PATHINFO_EXTENSION);
            return '<div class="auto_link_video_wrapper">
                    <video controls preload="metadata" style="width: 100%; max-height: 500px; background: #000;">
                        <source src="' . $url . '" type="video/' . $ext . '">
                        브라우저가 비디오 태그를 지원하지 않습니다. <a href="' . $url . '">파일 다운로드</a>
                    </video>
                </div>';
        }

        return null;
    };

    // 2. 비디오[URL] 패턴 처리 (사용자가 명시적으로 입력한 경우)
    $text = preg_replace_callback('/비디오\[(https?:\/\/[^\]]+)\]/i', function ($matches) use ($get_video_render) {
        $url = $matches[1];
        $render = $get_video_render($url);
        return $render ? $render : '비디오 링크 오류 또는 지원하지 않는 형식입니다.';
    }, $text);

    // 3. 이미지[URL] 패턴 처리 (이미 <a>로 보호된 경우 제외하고 텍스트 형태일 때)
    $text = preg_replace_callback('/\[\s*(https?:\/\/[^\]]+)\s*\]/i', function ($matches) {
        $url = $matches[1];
        // 트위터/X 이미지 처리 등 특정 조건이 필요하면 여기서 분기
        return '<img src="' . $url . '" class="auto_link_image" alt="image" style="max-width: 100%; height: auto;">';
    }, $text);

    // 4. 트위터/X 임베드 처리
    $text = preg_replace_callback(
        '/https?:\/\/(?:www\.)?(?:twitter\.com|x\.com)\/[A-Za-z0-9_]+\/status\/([0-9]+)(?:\?[^\s<>"',]*)?(?:,(m))?/i',
        function ($matches) {
            $tweet_id = $matches[1];
            $is_video_mode = isset($matches[2]) && strtolower($matches[2]) === 'm';

            // 1. 기본 속성 설정
            $data_attr = 'data-width="500" data-lang="ko"';

            // 2. ,m 이 붙었을 경우 영상 최적화 속성 추가
            if ($is_video_mode) {
                $data_attr .= ' data-media-max-width="560"';
            }

            // 3. 공식 blockquote 구조 생성
            $html = '<blockquote class="twitter-tweet" ' . $data_attr . ' style="margin: 10px auto;">';
            $html .= '<a href="https://twitter.com/i/status/' . $tweet_id . '"></a>';
            $html .= '</blockquote>';

            return $html;
        },
        $text
    );

    // 5. 링크[URL] 패턴 (OG 카드)
    $text = preg_replace_callback('/링크\[(https?:\/\/[^\]]+)\]/i', function ($matches) {
        $url = trim($matches[1]);
        if (function_exists('fetch_og_metadata') && $og = fetch_og_metadata($url)) {
            return render_link_card($url, $og);
        }
        return '<a href="' . htmlspecialchars($url, ENT_QUOTES) . '" class="auto_link" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($url, ENT_QUOTES) . '</a>';
    }, $text);

    // 6. 일반 URL 패턴 처리
    $text = preg_replace_callback('/https?:\/\/[^\s<>"']+/i', function ($matches) use ($get_video_render) {
        $url = $matches[0];

        // 비디오/유튜브 통합 체크
        if ($video_html = $get_video_render($url)) {
            return $video_html;
        }

        // 트위터/X URL 체크 (패스)
        if (preg_match('/(?:twitter\.com|x\.com)\/[A-Za-z0-9_]+\/status\/[0-9]+/i', $url)) {
            return $url;
        }

        // 이미지 체크
        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;">';
        }

        // 일반 링크
        return '<a href="' . $url . '" class="auto_link" target="_blank" rel="noopener noreferrer">URL</a>';
    }, $text);

    // 7. 해시태그 처리
    $parts = preg_split('/(____PROTECT_BLOCK_\d+____|<[^>]+>)/s', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
    foreach ($parts as &$part) {
        if (!preg_match('/^____PROTECT_BLOCK_/', $part) && strpos($part, '<') !== 0) {
            $part = preg_replace_callback('/#([가-힣a-zA-Z][^\s#<>]*)/u', function ($matches) use ($bo_table) {
                $tag = $matches[1];
                $clean_tag = preg_replace('/[;\!\?\.\,\)\]]+$/', '', $tag);

                if (preg_match('/^[0-9a-fA-F]{3,8}$/', $clean_tag) || 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);
        }
    }
    $text = implode('', $parts);

    // 8. 보호된 블록 복원 (역순으로 복원하여 중첩 방지)
    $placeholders = array_reverse($placeholders, true);
    foreach ($placeholders as $key => $original) {
        $text = str_replace($key, $original, $text);
    }

    return $text;
}



댓글목록

니루

댓글

니루 @tabom0w0
gist script 임베드 코드도 예외 등록처리했다... ㅇ<-<