Tip 게시판이 작업 일지를 대신 하기도 해서
타 프로그램의 API를 이용해서 게시판 본문에 정보 불러와서 출력하는 방법을 남긴다.
1. 해당 본문의 확장변수 아래에 다른 프로그램의 API정보를 호출해서 일정을 출력하는 형태
- 확장변수의 QR코드 이미지 생성 부분은 이 글 아래글 참조

2. 확장변수의 "Club 주소"를 자동으로 인식하여, 해당 프로그램의 일정을 가져온다.- 확장변수 ID : url
3. mh_league_schedule.helper.php 파일을 생성해서 게시판 스킨에 업로드 - mh_league_schedule.css 도 만들어 줘야 한다.
- 해당 php 파일에는 QR코드 색상 랜덤 코드도 같이 들어 있다.
<?php
/**
* mh_league_schedule.helper.php
*
* 외부 사이트(ClubMen 리그전 관리 앱)의 JSON API를 호출하여
* 게시판 본문 상단에 삽입할 "다가오는 일정 + 지난 일정" HTML을 반환한다.
* 개인 리그전(tournament.php)과 팀 리그전(teamleague.php)을 함께 조회해서 날짜순으로 합쳐 보여준다.
*
* 위젯이 아니라 스킨(_read.html)에서 직접 include하여 사용하는 형태.
*
* API 응답 형식(공통): {"success":true,"list":[{"id":1,"title":"...","date":"YYYY-MM-DD","start_time":"HH:MM:SS", ...}]}
*
* 지난 일정의 1~4위 순위는 tournament.js의 calcRankings()가 대진표 결과를 브라우저에서
* 직접 계산하는 방식이라 API로 바로 못 가져온다. 대신 상세 모달 딥링크로 "결과 보기" 버튼만 제공한다.
*/
if (!function_exists('mh_get_league_schedule_html_from_base'))
{
/**
* 게시물 확장변수(예: id="url")에 저장된 사이트 기본 주소를 받아
* 개인/팀 리그전 API 주소를 조립한 뒤 통합 일정 HTML을 반환한다.
*
* @param string $base_url 확장변수에 저장된 값 (예: https://clubmen.inour.net/cm/league/)
* @param int $list_count 앞으로 있을 일정 표시 개수
* @param int $past_count 지난 일정 표시 개수 (0이면 지난 일정 섹션 생략)
*/
function mh_get_league_schedule_html_from_base($base_url, $list_count = 5, $cache_min = 10, $title = '다가오는 일정', $past_count = 3)
{
$base_url = trim(html_entity_decode((string)$base_url, ENT_QUOTES, 'UTF-8'));
if ($base_url === '')
{
return '';
}
// 'league'가 포함된 주소(예: https://clubmen.inour.net/cm/league/)에서만 일정을 출력
if (strpos($base_url, 'league') === false)
{
return '';
}
$base_url = rtrim($base_url, '/');
$sources = [
'individual' => $base_url . '/api/tournament.php?action=list&for_calendar=1',
'team' => $base_url . '/api/teamleague.php?action=list',
];
return mh_get_league_schedule_html($sources, $list_count, $cache_min, $title, $base_url, $past_count);
}
}
if (!function_exists('mh_get_league_schedule_html'))
{
/**
* @param array $sources ['individual' => api_url, 'team' => api_url] 형태. 필요한 것만 넣으면 됨.
* @param string $link_base_url 리그전 이름/결과 클릭 시 이동할 기본 주소.
* 개인 리그전은 '{$link_base_url}/?tournament_id={id}',
* 팀 리그전은 '{$link_base_url}/?team_league_id={id}' 형태로 상세 모달 딥링크 생성.
* (tournament.js / teamleague.js 양쪽에 쿼리파라미터 처리 코드가 있어야 동작함)
* @param int $past_count 지난 일정 표시 개수 (0이면 지난 일정 섹션 생략)
*/
function mh_get_league_schedule_html($sources, $list_count = 5, $cache_min = 10, $title = '다가오는 일정', $link_base_url = '', $past_count = 3)
{
if (is_string($sources))
{
$sources = ['individual' => $sources];
}
$cache_key = 'mh_league_schedule:' . md5(json_encode($sources));
$cached = \Rhymix\Framework\Cache::get($cache_key);
if ($cached === null)
{
$merged = [];
$today = date('Y-m-d');
foreach ($sources as $type => $api_url)
{
$context = stream_context_create(['http' => ['timeout' => 5, 'ignore_errors' => true]]);
$response = @file_get_contents($api_url, false, $context);
if ($response === false)
{
continue;
}
$data = json_decode($response);
if (!$data || empty($data->success) || empty($data->list))
{
continue;
}
foreach ($data->list as $item)
{
if (!isset($item->date))
{
continue;
}
$item->mh_type = $type;
$item->mh_is_past = ($item->date < $today);
$merged[] = $item;
}
}
$upcoming = array_values(array_filter($merged, function ($item) {
return !$item->mh_is_past;
}));
usort($upcoming, function ($a, $b) {
return strcmp($a->date, $b->date);
});
$upcoming = array_slice($upcoming, 0, (int)$list_count);
$past = array_values(array_filter($merged, function ($item) {
return $item->mh_is_past;
}));
usort($past, function ($a, $b) {
return strcmp($b->date, $a->date); // 최근 지난 일정이 먼저
});
$past = array_slice($past, 0, (int)$past_count);
$cached = ['upcoming' => $upcoming, 'past' => $past];
// API 실패나 빈 결과도 그대로 캐싱해서 매 요청마다 외부 서버를 재호출하지 않도록 함
\Rhymix\Framework\Cache::set($cache_key, $cached, (int)$cache_min * 60);
}
$upcoming_html = _mh_league_render_rows($cached['upcoming'], $link_base_url, false);
$past_html = (int)$past_count > 0 ? _mh_league_render_rows($cached['past'], $link_base_url, true) : '';
$title_html = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
$body = '';
$body .= '<h4 class="mh_league_schedule_title">' . $title_html . '</h4>';
$body .= $upcoming_html !== '' ? $upcoming_html : '<p class="mh_league_schedule_empty">예정된 일정이 없습니다.</p>';
if ((int)$past_count > 0)
{
$body .= '<h4 class="mh_league_schedule_title mh_league_schedule_title_past">지난 일정</h4>';
$body .= $past_html !== '' ? $past_html : '<p class="mh_league_schedule_empty">지난 일정이 없습니다.</p>';
}
return '<div class="mh_league_schedule_box">' . $body . '</div>';
}
}
if (!function_exists('_mh_league_render_rows'))
{
/**
* 일정 목록을 <ul> HTML로 렌더링. $is_past이면 이름은 링크 없이 텍스트로만,
* 대신 "결과 보기" 버튼을 상세 모달 딥링크로 붙인다.
*/
function _mh_league_render_rows($list, $link_base_url, $is_past)
{
if (empty($list))
{
return '';
}
$rows = '';
foreach ($list as $item)
{
$is_team = (($item->mh_type ?? 'individual') === 'team');
$date = htmlspecialchars($item->date ?? '', ENT_QUOTES, 'UTF-8');
$time = !empty($item->start_time) ? htmlspecialchars(substr($item->start_time, 0, 5), ENT_QUOTES, 'UTF-8') : '';
$name = htmlspecialchars($item->title ?? '', ENT_QUOTES, 'UTF-8');
$fee = isset($item->entry_fee) ? (int)$item->entry_fee : 0;
$fee_html = $fee > 0 ? number_format($fee) . '원' : '무료';
$note = trim((string)($item->note ?? ''));
$note_html = $note !== '' ? htmlspecialchars($note, ENT_QUOTES, 'UTF-8') : '';
$type_badge = $is_team
? '<span class="mh_league_schedule_badge mh_league_schedule_badge_team">팀</span>'
: '<span class="mh_league_schedule_badge mh_league_schedule_badge_individual">개인</span>';
$detail_url = '';
if ($link_base_url !== '' && isset($item->id))
{
$param = $is_team ? 'team_league_id' : 'tournament_id';
$detail_url = $link_base_url . '/?' . $param . '=' . urlencode($item->id);
}
if ($is_past)
{
$name_html = '<span class="mh_league_schedule_name_text">' . $name . '</span>';
$name_html .= $detail_url !== ''
? ' <a href="' . htmlspecialchars($detail_url, ENT_QUOTES, 'UTF-8') . '" target="_blank" rel="noopener" class="mh_league_schedule_result_link">결과 보기</a>'
: '';
}
else
{
$name_html = $detail_url !== ''
? '<a href="' . htmlspecialchars($detail_url, ENT_QUOTES, 'UTF-8') . '" target="_blank" rel="noopener" class="mh_league_schedule_link">' . $name . '</a>'
: $name;
}
$rows .= '<li class="mh_league_schedule_item' . ($is_past ? ' mh_league_schedule_item_past' : '') . '">';
$rows .= '<div class="mh_league_schedule_row">';
$rows .= $type_badge;
$rows .= '<span class="mh_league_schedule_date">' . $date . '</span>';
$rows .= $time !== '' ? '<span class="mh_league_schedule_time">' . $time . '</span>' : '';
$rows .= '<span class="mh_league_schedule_name">' . $name_html . '</span>';
$rows .= '</div>';
if (!$is_past)
{
$rows .= '<div class="mh_league_schedule_sub">';
$rows .= '<span class="mh_league_schedule_fee">참가비 ' . $fee_html . '</span>';
$rows .= $note_html !== '' ? '<span class="mh_league_schedule_note">부상 ' . $note_html . '</span>' : '';
$rows .= '</div>';
}
$rows .= '</li>';
}
return '<ul class="mh_league_schedule_list">' . $rows . '</ul>';
}
}
if (!function_exists('mh_qr_color_hex'))
{
/**
* 문자열(보통 Club주소)을 시드로 항상 같은 색상 하나를 뽑아준다.
* 같은 주소는 항상 같은 색이 나오고(새로고침해도 안 바뀜), 주소가 다르면 다른 색이 나온다.
* QR코드는 스캔이 돼야 하므로 너무 밝은 색은 피하고 채도/명도를 고정해서 항상 적당히 진한 색만 나오게 한다.
*
* @return string 6자리 hex 색상 코드 (예: '7B1FA2', # 없음)
*/
function mh_qr_color_hex($seed)
{
$seed = trim((string)$seed);
if ($seed === '')
{
return '000000';
}
// 경로가 달라도(예: /cm/, /cm/league/) 같은 도메인이면 같은 색이 나오도록 도메인만 사용
$host = parse_url($seed, PHP_URL_HOST);
$color_key = $host !== null && $host !== '' ? $host : $seed;
$hue = crc32($color_key) % 360;
return _mh_hsl_to_hex($hue, 65, 38);
}
}
if (!function_exists('_mh_hsl_to_hex'))
{
/**
* HSL(0-360, 0-100, 0-100)을 6자리 hex RGB로 변환.
*/
function _mh_hsl_to_hex($h, $s, $l)
{
$s = $s / 100;
$l = $l / 100;
$c = (1 - abs(2 * $l - 1)) * $s;
$x = $c * (1 - abs(fmod($h / 60, 2) - 1));
$m = $l - $c / 2;
if ($h < 60)
{
$r = $c; $g = $x; $b = 0;
}
elseif ($h < 120)
{
$r = $x; $g = $c; $b = 0;
}
elseif ($h < 180)
{
$r = 0; $g = $c; $b = $x;
}
elseif ($h < 240)
{
$r = 0; $g = $x; $b = $c;
}
elseif ($h < 300)
{
$r = $x; $g = 0; $b = $c;
}
else
{
$r = $c; $g = 0; $b = $x;
}
$r = (int)round(($r + $m) * 255);
$g = (int)round(($g + $m) * 255);
$b = (int)round(($b + $m) * 255);
return sprintf('%02X%02X%02X', $r, $g, $b);
}
}
4. _read.html 수정
getContent(본문내용) 상단에 위치
{@
~ 중간 내용
// 리그전 일정
include_once($tpl_path . '/mh_league_schedule.helper.php');
$mh_league_schedule_html = mh_get_league_schedule_html_from_base($oDocument->getExtraEidValue('url'), 5, 10, '다가오는 일정');
}
~ 중간 내용
<!--// 리그전 일정 (mh_league_schedule) -->
<load target="css/mh_league_schedule.css" />
<block cond="$mh_league_schedule_html">{$mh_league_schedule_html}</block>
{$oDocument->getContent(false)}
물론 해당 프로그램이 API를 제공해야 되고 여러가지 단서가 붙지만
이렇게 게시판 본문에 해당 사이트의 정보들을 출력할 수 도 있다.
언급된 파일들과 QR코드 파일들 압축해서 올리니 참조하세요.
이 자료의 평점 (참여 : 0 / 총 점수 : )
평균 평점 :
★
★
★
★
★
0.0점
로그인 후 평가하실 수 있습니다.
Tip이 도움이 되었다면 댓글과 평가 부탁합니다.
Tip에 대한 궁금한 점은 댓글로 남겨 주시면 성심껏 답변 드립니다. 
