build();
});
return response($xml, 200)->header('Content-Type', 'application/xml; charset=utf-8');
}
private function build(): string
{
// 레이아웃의 canonical(url()->current())과 같은 방식으로 요청 기준 주소를 쓴다.
$baseUrl = rtrim(url('/'), '/');
$today = date('Y-m-d');
$xml = '' . PHP_EOL;
$xml .= '' . PHP_EOL;
// 고정 페이지. 로그인/회원가입/비밀번호 재설정은 색인 가치가 없어 제외한다.
$xml .= $this->url("{$baseUrl}/", $today, 'daily', '1.0');
foreach (['rank', 'search', 'trailer', 'review', 'company'] as $section) {
$xml .= $this->url("{$baseUrl}/movie/{$section}", $today, 'daily', '0.8');
}
$xml .= $this->url("{$baseUrl}/tag", $today, 'daily', '0.6');
// 게시판 목록
$boards = DB::table('tb_board')
->where('is_display', '=', 1)
->orderBy('sort')
->pluck('code');
foreach ($boards as $code) {
$xml .= $this->url("{$baseUrl}/board/{$code}", $today, 'daily', '0.8');
}
// 게시글. 공지/전체공지도 실제로 열리는 페이지이므로 함께 넣는다.
DB::table('tb_post')
->join('tb_board', 'tb_board.id', '=', 'tb_post.board_id')
->where('tb_board.is_display', '=', 1)
->where('tb_post.is_delete', '=', 0)
->where('tb_post.is_secret', '=', 0)
->where('tb_post.is_personal', '=', 0)
->select('tb_board.code', 'tb_post.id', 'tb_post.updated_at', 'tb_post.created_at')
->orderBy('tb_post.id')
->chunk(1000, function ($posts) use (&$xml, $baseUrl, $today) {
foreach ($posts as $post) {
$modified = $post->updated_at ?: $post->created_at;
$lastModified = $modified ? date('Y-m-d', strtotime($modified)) : $today;
if (!$this->append($xml, "{$baseUrl}/board/{$post->code}/{$post->id}", $lastModified, 'weekly', '0.6')) {
return false;
}
}
return true;
});
// 영화 상세.
// 뷰가 route('movie.search.show', base64_encode($row->movie_cd)) 로 링크를 만들므로 같은 규칙을 쓴다.
DB::table('tb_movie')
->select('movie_cd')
->orderBy('movie_cd')
->chunk(1000, function ($movies) use (&$xml, $baseUrl, $today) {
foreach ($movies as $movie) {
if (!$movie->movie_cd) {
continue;
}
$path = rawurlencode(base64_encode($movie->movie_cd));
if (!$this->append($xml, "{$baseUrl}/movie/search/{$path}", $today, 'weekly', '0.7')) {
return false;
}
}
return true;
});
$xml .= '' . PHP_EOL;
if ($this->truncated) {
// 조용히 잘리면 커버리지가 줄어든 것을 알 수 없다.
logger()->warning('[sitemap] URL 상한(' . self::MAX_URLS . ')에 도달해 일부가 제외되었습니다.');
}
return $xml;
}
/**
* 상한을 지키며 URL 을 덧붙인다. 상한에 닿으면 false 를 반환해 순회를 멈춘다.
*/
private function append(string &$xml, string $location, string $lastModified, string $changeFrequency, string $priority): bool
{
if ($this->count >= self::MAX_URLS) {
$this->truncated = true;
return false;
}
$xml .= $this->url($location, $lastModified, $changeFrequency, $priority);
return true;
}
private function url(string $location, string $lastModified, string $changeFrequency, string $priority): string
{
$this->count++;
return ' ' . PHP_EOL
. ' ' . htmlspecialchars($location, ENT_XML1) . '' . PHP_EOL
. ' ' . $lastModified . '' . PHP_EOL
. ' ' . $changeFrequency . '' . PHP_EOL
. ' ' . $priority . '' . PHP_EOL
. ' ' . PHP_EOL;
}
}