Dev Blog

스크립트 & 소스코드 공유

실무에서 유용하게 쓰는 스크립트와 코드 조각을 블로그 게시판으로 저장하고 공유합니다.

108건의 글

PHP 함수 - 한글 단어 마지막 글자 받침(종성) 확인

한글 문자열의 마지막 글자에 받침이 있는지 확인하는 PHP 함수 has_batchim()을 소개한다. UTF-8 문자열을 UTF-16BE로 변환해 마지막 글자의 유니코드 코드 포인트를 구하고, '가'의 기준값 44032를 뺀 값을 28로 나눈 나머지가 0보다 크면 받침이 있는 것으로 판별한다. 예시로 깃허브에 '을/를' 조사를 붙이는 사용법을 보여준다.

<?php
// License: Public Domain
//
// 마지막 글자에 받침이 있으면 0보다 큰 정수를 반환하고
// 받침이 없으면 0을 반환한다.
//
// 예:
// $word = '깃허브';
// echo $word . (has_batchim($word) ? '을' : '를');
function has_batchim($str, $charset = 'UTF-8') {
$str = mb_convert_encoding($str, 'UTF-16BE', $charset);
$str = str_split(substr($str, strlen($str) - 2));
$code_point = (ord($str[0]) * 256) + ord($str[1]);
if ($code_point < 44032 || $code_point > 55203) return 0;
return ($code_point - 44032) % 28;
}

해시태그로 인스타그램 데이터 불러오기 (PHP 파싱)

PHP의 cURL과 인스타그램 태그 URL을 이용해 해시태그별 게시물 데이터(본문, 썸네일, 좋아요 수 등)를 가져와 JSON 파일로 저장하고, 그누보드 게시판에서 이를 호출해 화면에 출력하는 전체 소스를 정리했다. 크론 작업으로 1시간마다 데이터를 갱신하는 구조와 여러 게시글의 태그를 관리하는 함수를 포함한다.

function _curl($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
$contents = curl_exec($ch);
curl_close($ch);
return $contents;

}
function scrape_insta_hash($tags = array(),$debug = false) {
$insta_array = array();
$output = array();
$output["update_date"] = date("Y-m-d H:i:s");
$index = 0;
foreach($tags as $tag){
$url = 'https://www.instagram.com/explore/tags/'.$tag.'/?__a=1';
$insta_source = _curl($url); // instagrame tag url
$limit = 60; // 가져 올 개수
$results_array = json_decode($insta_source, TRUE);

if($debug == true){
return $results_array;
}
for ($i=0; $i < $limit; $i++) {
$latest_array = $results_array['graphql']['hashtag']['edge_hashtag_to_media']['edges'][$i]['node'];
$text=$latest_array["edge_media_to_caption"]["edges"][0]["node"]["text"];
$shortcode=$latest_array['shortcode'];
$thumbnail_src=$latest_array['thumbnail_src'];
$taken_at_timestamp = $latest_array["taken_at_timestamp"];
$edge_media_preview_like = $latest_array["edge_media_preview_like"];
if($thumbnail_src){
$output[$index]["text"] = $text;
$output[$index]["shortcode"] = $shortcode;
$output[$index]["thumbnail_src"] = $thumbnail_src;
$output[$index]["taken_at_timestamp"] = $taken_at_timestamp;
$output[$index]["edge_media_preview_like"] = $edge_media_preview_like;
$index++;
}
}
}
if($index == 0){
return false;
}
return $output;
}

function get_insta_data($wr_id){
if($wr_id){
$output = array();
$insta_json_data = (array)json_decode(_curl("http://".$_SERVER["HTTP_HOST"].'/data/insta/insta_list.'.$wr_id.'.json'));
unset($insta_json_data["update_date"]);
foreach($insta_json_data as $k => $v){
$output[$v->taken_at_timestamp] = (array)$v;
}
krsort($output,SORT_ASC);
return $output;
}
}

function set_insta_data($tags = array(),$file_name,$mode = ""){
global $g5;
$insta_json_data = json_decode(_curl("http://".$_SERVER["HTTP_HOST"].'/data/insta/'.$file_name));
if($mode == "write"){
$insta_json_data = "";
}
$check_date = date("Y-m-d H:i:s",strtotime($insta_json_data->update_date." +1 hours"));
if($check_date < date("Y-m-d H:i:s") || $insta_json_data == ""){
//update
$output = scrape_insta_hash($tags);
if($output != false){
$outputJson = json_encode($output);
file_put_contents($_SERVER["DOCUMENT_ROOT"].'/data/insta/'.$file_name, $outputJson);
}
}
}
function get_sql_set($arr = array()){
$sql_set = "";
foreach($arr as $k => $v){
$sql_set .= " `".$k."` = '".$v."'";
if($index != count($arr)-1){
$sql_set .= ",";
}
$index++;
}
return $sql_set;
}

if(!function_exists('exe_query'))
{
function exe_query($query,$fetchType = 'default'){
if(function_exists('mysqli_query') && G5_MYSQLI_USE) {
$query = preg_replace('/\/\*.*?\*\/(\r\n|\n|\r)?/s', '', $query);
$query = trim($query);
$result = sql_query($query);
$output = array();
$i = 0;
while($row = $fetchType == 'default' || $fetchType == '' ? @mysqli_fetch_assoc($result) : $fetchType($result)){
foreach($row as $key=>$value){
$output[$i][$key] = $value;
}
$i++;
}
}
return $output;
}
}
function update_insta_data($wr_id){
global $g5;
if($wr_id > 0){
$result = exe_query("select * from ".$g5['write_prefix']."m11 where wr_id = '".$wr_id."'");
$tag_list = array();
foreach($result as $row){
$tags=$row["wr_3"];
if($tags){
$tag_arr = explode("#",$tags);
foreach($tag_arr as $tag){
$tag_list[$row["wr_id"]][] = $tag;
}
}
}
foreach($tag_list as $tag_wr_id => $tag_val){
set_insta_data($tag_val,"insta_list.".$tag_wr_id.".json","write");
}
}
}
function crontab_update_insta_data(){
global $g5;
$result = exe_query("select * from ".$g5['write_prefix']."m11");
$tag_list = array();
foreach($result as $row){
$tags=$row["wr_3"];
if($tags){
$tag_arr = explode("#",$tags);
foreach($tag_arr as $tag){
$tag_list[$row["wr_id"]][] = $tag;
}
}
}
foreach($tag_list as $tag_wr_id => $tag_val){
set_insta_data($tag_val,"insta_list.".$tag_wr_id.".json");
}
}

인스타그램 데이타를 호출하는 부분

<div class="mright">
<img src="/img/t_insta.jpg"> <span style="float: right;"><?=$write[wr_3] ?></span>
<?php $insta_data= get_insta_data($wr_id); ?>
<div class="insta_data" >
<? $index = 1; ?>
<ul>
<? foreach($insta_data as $row){ if($index > 15) break; ?>
<? if($row["thumbnail_src"]){ ?>
<li class="hoverEffect">
<a href="https://www.instagram.com/p/<?=$row["shortcode"] ?>" target="_blank">
<img class="card-img-top" src="<?=$row["thumbnail_src"] ?>" class="">
<p class="txt"><i class="fa fa-heart-o" aria-hidden="true"></i> <?=$row["edge_media_preview_like"]->count ?></p>
</a>
</li>
<? $index++;} ?>
<? } ?>
</ul>
</div>
</div>

JavaScript로 동영상을 연속 재생하는 배너 (onended 이벤트)

여러 개의 동영상을 순서대로 연속 재생하는 메인 배너 구현 예제다. video 태그의 onended 이벤트로 현재 영상이 끝나면 다음 영상이 자동 재생되도록 하고, 하단 닷(dot) 버튼을 클릭하면 해당 영상으로 이동해 재생한다. 배너 레이아웃 CSS와 jQuery 제어 코드를 포함한 전체 예제를 제공한다.

<style>
#video-box {min-width:1200px; position:relative;}
#video-box .main-banner {display:none; width:100%; height:750px;  overflow:hidden;position:relative; background-color:#000;}
#video-box .main-banner.open{display:block;}
#video-box .main-banner video {position:absolute;width:100%;top:50%; left:50%;transform:translate(-50%,-50%);border:0;}
#video-box .dot-box {position:absolute; width:100%; bottom:0; text-align:center;}
#video-box .dot-box a{display:inline-block; margin:0.5em; width:20px;height:20px; border-radius:50%; background-color:#06F;text-indent:-9999px;}
#video-box .dot-box a.open{background-color:yellowgreen;}
@media screen and (max-width:1400px) { #video-box .main-banner {height:550px;} }
</style>
<div id="video-box">
<div class="main-banner open">
<video autoplay muted>
<source src="https://www.bodyfriend.co.kr/video/1.mp4" type="video/mp4">
</video>
</div>
<div class="main-banner">
<video muted>
<source src="https://www.bodyfriend.co.kr/video/main02.mp4" type="video/mp4">
</video>
</div>
<div class="main-banner">
<video muted>
<source src="https://www.bodyfriend.co.kr/video/main03.mp4" type="video/mp4">
</video>
</div>
<div class="dot-box"></div>
</div>
<script>
$(function() {
var len = $('.main-banner').length
$('.main-banner').each(function(index, element) {
$(this).addClass('v' + index);
var video = $(this).find('video')[0];
video.onended = function(e) {
$('.main-banner').removeClass('open');
$('.main-banner.v'+((index+1) % len)).addClass('open');
$('.main-banner.v'+((index+1) % len)).find('video')[0].play();
$('#video-box a.dot').removeClass('open');
$('#video-box a.dot.v'+((index+1) % len)).addClass('open');
};
$('#video-box .dot-box').append('<a href="#" class="dot v'+index+'" data-idx="'+index+'">'+(index+1)+'번째 비디오</a>');
});
$('#video-box a.dot').click(function(e) {
var idx = $(this).data('idx');
$('.main-banner.open').find('video')[0].pause();
$('.main-banner').removeClass('open');
$('.main-banner.v'+(idx % len)).addClass('open');
$('.main-banner.v'+(idx % len)).find('video')[0].play();
$('#video-box a.dot').removeClass('open');
$('#video-box a.dot.v'+(idx % len)).addClass('open');
});
$('#video-box a.dot.v0').addClass('open');
});
</script>

그누보드 전체 회원 정보를 엑셀로 저장하는 PHP 소스

그누보드에서 전체 회원 정보를 엑셀 파일로 저장하는 PHP 소스다. _common.php 파일이 있는 경로(예: /adm/)에 파일을 만들고 소스를 붙여넣으면 되며, 관리자 모드로 접속해야 다운로드가 가능하다. 2019년 1월 15일 업데이트로 탈퇴한 회원은 목록에서 제외되도록 수정되었다.

  • '_common.php'파일 있는 경로(예. /adm/)에 파일하나 만들고 아래 소스 복붙하시면 됩니다.

(관리자모드에서 진입해야 다운로드 가능)

[update]

190115 : 탈퇴인원 제외

<?php
include_once("./_common.php");
include_once(G5_LIB_PATH.'/PHPExcel.php');

if (!$is_admin =="super")
alert_close("최고 관리자 영역 입니다.");

function column_char($i) { return chr( 65 + $i ); }

$headers = array('번호', 'ID', '이름', '닉네임', '레벨', '포인트', '가입일', 'Email', '홈페이지', '성별', '생일', '전화', '휴대폰', '메일링', '정보공개', '최근로그인', '주소');
$widths  = array(18, 15, 15, 15, 15, 15, 15, 50, 20, 20, 20, 20, 20, 20, 20, 20, 100);
$header_bgcolor = 'FFABCDEF';
$last_char = column_char(count($headers) - 1);

$sql    = " select * from g5_member where mb_leave_date = '' order by mb_datetime desc ";
$result = sql_query($sql);
for($i=1; $row=sql_fetch_array($result); $i++) {

if ($row['mb_sex'] =="M") { $mb_sex ="남자"; } else if ($row['mb_sex'] =="F"){ $mb_sex ="여자"; }
if ($row['mb_mailling'] =="1") { $mb_mailling ="받음"; } else if ($row['mb_mailling'] =="0"){ $mb_mailling ="안받음"; }
if ($row['mb_open'] =="1") { $mb_open  ="공개"; } else if ($row['mb_open'] =="0"){ $mb_open ="비공개"; }
$rows[] =
array(
$i,
$row[mb_id],
$row[mb_name],
$row[mb_nick],
$row[mb_level],
$row[mb_point],
$row[mb_datetime],
$row[mb_email],
$row[mb_homepage],
$mb_sex,
$row[mb_birth],
$row[mb_tel],
$row[mb_hp],
$mb_mailling,
$mb_open,
$row[mb_today_login],
print_address($row['mb_addr1'], $row['mb_addr2'], $row['mb_addr3'], $row['mb_zip1'])
);
}

$data = array_merge(array($headers), $rows);

$excel = new PHPExcel();
$excel->setActiveSheetIndex(0)->getStyle( "A1:${last_char}1" )->getFill()->setFillType(PHPExcel_Style_Fill::FILL_SOLID)->getStartColor()->setARGB($header_bgcolor);
$excel->setActiveSheetIndex(0)->getStyle( "A:$last_char" )->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_CENTER)->setWrapText(true);
foreach($widths as $i => $w) $excel->setActiveSheetIndex(0)->getColumnDimension( column_char($i) )->setWidth($w);
$excel->getActiveSheet()->fromArray($data,NULL,'A1');

header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"members-".date("ymd", time()).".xls\"");
header("Cache-Control: max-age=0");

$writer = PHPExcel_IOFactory::createWriter($excel, 'Excel5');
$writer->save('php://output');
?>

유튜브 영상을 웹페이지 배경으로 사용하는 CSS 기법

유튜브 동영상을 웹페이지 전체 배경으로 사용하는 HTML/CSS 기법이다. video-background 컨테이너와 video-foreground 안의 iframe을 화면에 꽉 차게 배치하고 z-index를 낮춰 콘텐츠 뒤에 깔리게 한다. 화면 비율에 따라 16:9, 9:16 미디어 쿼리로 확대 영역을 조정하는 전체 CSS와 HTML 예제 코드를 제공한다.

* {
  box-sizing: border-box;
}

.video-background {
  background: #000;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: -99;
}

.video-foreground,
.video-background iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

#vidtop-content {
  top: 0;
  color: #fff;
}

.vid-info {
  position: absolute;
  top: 0;
  right: 0;
  width: 33%;
  background: rgba(0, 0, 0, 0.3);
  color: #fff;
  padding: 1rem;
  font-family: Avenir, Helvetica, sans-serif;
}

.vid-info h1 {
  font-size: 2rem;
  font-weight: 700;
  margin-top: 0;
  line-height: 1.2;
}

.vid-info a {
  display: block;
  color: #fff;
  text-decoration: none;
  background: rgba(0, 0, 0, 0.5);
  transition: .6s background;
  border-bottom: none;
  margin: 1rem auto;
  text-align: center;
}

@media (min-aspect-ratio: 16/9) {
  .video-foreground {
    height: 300%;
    top: -100%;
  }
}

@media (max-aspect-ratio: 16/9) {
  .video-foreground {
    width: 300%;
    left: -100%;
  }
}

@media all and (max-width: 600px) {
  .vid-info {
    width: 50%;
    padding: .5rem;
  }

  .vid-info h1 {
    margin-bottom: .2rem;
  }
}

@media all and (max-width: 500px) {
  .vid-info .acronym {
    display: none;
  }
}
<div class="video-background">
  <div class="video-foreground">
    <iframe class="ww" src="https://www.youtube-nocookie.com/embed/n0AY4ebPy4w?autoplay=1&loop=1&controls=0&vq=hd720"
      frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
      allowfullscreen></iframe>
  </div>
</div>
<div id="vidtop-content">
  <div class="vid-info">
    <h1>YouTube</h1>
    <p>Good!</p>
  </div>
</div>


Let's create something great together.

프로젝트 문의 및 견적 요청은 언제든 환영합니다.