<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>dsclub &amp;gt; 참조 &amp;gt; 개발 내역</title>
<link>https://dsclub.kr/dev</link>
<language>ko</language>
<description>개발 내역 (2025-05-11 02:13:14)</description>

<item>
<title>Twave 테마 회원 프로필 업데이트 시 회원 여분 테이블 초기화 되는 오류 수정</title>
<link>https://dsclub.kr/dev/31</link>
<description><![CDATA[<p>뭘 잘 못 건드린 것인지, php7.4 -&gt; php8.2로 업데이트 한 탓인지 지속적으로 오류가 나타나고 있다.</p><p>오늘 찾은 오류의 경우 기존 php7.4버전일 때의 Twave에서는 나타나지 않던 오류이다.</p><p><br /></p><p>우선 프로필 수정 시 회원 여분필드가 넘어가지 않아서 회원 어분필드 자체가 초기화되는 오류가 발생했다.</p><p>따라서 이를 해결해주기 위해 Twave thme/mobile/skin/member/profile_form.php의 27번 째 줄 아래에 다음과 같은 코드를 추가하였다.</p><p><br /></p><p><br /></p><div class="t2-code-block"><pre><code>    &lt;!-- 여분필드 Hidden 입력 시작 --&gt;</code></pre><p>    &lt;?php for ($i=1; $i&lt;=10; $i++) { ?&gt;</p><p>    &lt;input type="hidden" name="mb_&lt;?php echo $i ?&gt;" value="&lt;?php echo isset($member['mb_'.$i]) ? get_text($member['mb_'.$i]) : ''; ?&gt;"&gt;</p><p>    &lt;?php } ?&gt;</p><p>    &lt;!-- 여분필드 Hidden 입력 끝 --&gt;</p></div><p><br /></p><p>위치 상으로는<br /></p><p><br /></p><div class="t2-code-block"><pre><code>    &lt;input type="hidden" name="mb_nick" value="&lt;?php echo get_text($member['mb_nick']) ?&gt;"&gt;</code></pre><p>    &lt;?php } ?&gt;</p></div><p><br /></p><p>아래에 추가하면 된다.</p><p><br /></p><p>또한 프로필 수정 이후 계속 회원 가입 폼(register_form.php)로 넘어가는 오류가 발생하여,</p><p>/bbs/profile_form.php에서 register_form_update.php로 업데이트 하는 부분을 profile_form_update.php로 바꿔주고,</p><p>register_form_update.php를 복제 후 profile_form_update.php로 이름을 변경해준 뒤, 프로필 수정 완료 이후 리다이렉트 되는 경로를 '.G5_HTTP_BBS_URL.'/register_form.php에서 '.G5_HTTP_BBS_URL.'/member_profile.php?mb_id='.$mb_id.'으로 바꿔주었다.</p><p><br /></p>]]></description>
<dc:creator>Tak2</dc:creator>
<dc:date>2025-05-11T02:13:14+09:00</dc:date>
</item>


<item>
<title>php7.4 -&gt; php8.2 업데이트 이후 나타난 현상 기록</title>
<link>https://dsclub.kr/dev/30</link>
<description><![CDATA[<p>최근 서버 환경을 PHP 7.4에서 PHP 8.2로 업데이트한 결과에 대한 기록이다.</p><p><br /></p><p>5월 초, PHP 7.4에서 PHP 8.2로 업데이트하고 나서 접속자 기록을 확인하던 중,</p><p>예상치 못한 급격한 트래픽 증가가 발생하여 이 글을 쓰게되었다.</p><p><br /></p><p>지난 3-4개월간 사이트 일일 접속자 수는 약 2,000명(±150명) 수준으로 안정적이었다.</p><p>4월달 까지만 해도 일 방문자는 약 1900~2500 명 정도로 안정적으로 유지되어왔다.</p><p><span style="font-style:italic;">(사실 지난 2024년을 기준으로 한다면 작년 평균 일일 방문자 약 1000명 보다 두 배나 증가한 수치이긴 하다만, 1월 후반부터 1천명 후반대에서 2천명 중반대 정도로 안정적인 일일 방문자 수치가 유지되었기에 안정적이라 셈치겠다.)</span></p><p><br /></p><p>그런데, 아래의 그래프를 한 번 보자.</p><p><br /></p><p><br /></p><div class="t2-media-block"><div style="width:1139px;max-width:100%;margin:0px auto;"><img src="https://dsclub.kr/data/editor/250509/9486542801746725394057_0.webp" style="width:1139px;" alt="9486542801746725394057_0.webp" /></div></div><p><br /></p><p><br /></p><p>php7.4에서 8.2로 업데이트 다음날 5월 7일에 8,071명, 이틀날인 5월 8일에는 8,530명으로 방문자가 급증했다.</p><p>이는 23년~ 후반에 비정상적인 최대 일일 방문자 2700명의 약 3배이며, 24년 후반의 4천명을 두 배 가량 뛰어넘는 수치이다. 아무리 내가 운영중인 사이트라고 해도 솔직히 글도 그렇게 많지도 않고 유명하지도 않은 사이트이다. 최근에 백링크가 새로 생길 행동도 하지 않았다. 그렇기에 비정상적인 것이다.</p><p><br /></p><p>보통 24년 말 이후로 매달 4-5만 명 정도가 접속했는데, PHP 8.2 업데이트 이후 5월 1일부터 5월 7일까지만 해도 벌써 3만 명 가까이 되는 방문자가 접속했다. 이는 기존 평균 방문자 수와 비교했을 때 상당한 증가다.</p><p><br /></p><p>이러한 원인을 정확히 분석하고 싶지만, 나는 전지전능하지도, 코딩을 잘하지도 않는 그저 채찍피티 코드 짜집퍼이기 때문에 간단하게 원인 분석만 적어본다.</p><p><br /></p><p>원인 분석을 해보자면 다음과 같이 추측할 수 있다.</p><p><br /></p><div class="t2-code-block"><pre><code>1.  페이지 로딩 속도 개선:</code></pre><p>PHP 8.2는 이전 버전보다 처리 속도가 평균 30-40% 향상된 것으로 알려져 있다. </p><p>여러 벤치마크 테스트에 따르면 특히 JIT 컴파일러 지원으로 연산 집약적 작업에서 더 효율적이라고 한다. 이로 인해 페이지 로딩 시간이 단축되어 사용자 이탈률이 감소했을 가능성이 높다.</p><p>(한마디로 기존 평균 일일 방문자 수치인 2천명이 사실은 페이지 로징 지연으로 인해 이탈이 발생된 수치라는 것이다.)</p></div><p><br /></p><div class="t2-code-block"><pre><code>2. 서버 부하 감소:</code></pre><p>PHP 8.2는 향상된 메모리 관리 기능으로 서버가 동시에 더 많은 요청을 처리할 수 있게 되었다. 이로 인해 이전에는 트래픽 병목 현상이 발생하여 접속을 못 하던 것이 이제 더 많은 사용자가 원활하게 접속할 수 있게 된 것일 수도 있다. 지금 dsclub을 운영중인 서버가 라즈베리파이라 병목 현상이 해결되었던 것일 수도 있겠다.</p></div><p><br /></p><p>어느 쪽이든, 이번 업데이트가 사이트 성능과 사용자 경험에 긍정적인 영향을 미친 것은 분명하다. 앞으로 트래픽 패턴을 지속적으로 모니터링하면서 서버 자원을 적절히 조정할 필요가 있겠다.</p>]]></description>
<dc:creator>Tak2</dc:creator>
<dc:date>2025-05-09T02:13:16+09:00</dc:date>
</item>


<item>
<title>php7.4 -&gt; php8.2</title>
<link>https://dsclub.kr/dev/28</link>
<description><![CDATA[<p>php8.2 설치</p><p><br /></p><div class="t2-code-block"><pre><code>sudo apt install php8.2</code></pre></div><p><br /></p><p><br /></p><p>기타 라이브러리 설치</p><p><br /></p><div class="t2-code-block"><pre><code>sudo apt install php8.2-fpm php8.2-mysql php8.2-cli php8.2-xml php8.2-zip php8.2-common -y</code></pre></div><p><br /></p><p><br /></p><p>그누보드5 요구사항 설치</p><p><br /></p><div class="t2-code-block"><pre><code>sudo apt install php8.2-curl php8.2-gd php8.2-iconv</code></pre></div><p><br /></p><p><br /></p><p>기존 php7.4 중지</p><p><br /></p><div class="t2-code-block"><pre><code>sudo systemctl stop php7.4-fpm</code></pre></div><p><br /></p><p><br /></p><p>php8.2 시작</p><p><br /></p><div class="t2-code-block"><pre><code>sudo systemctl start php8.2-fpm</code></pre></div><p><br /></p><p><br /></p><p>php 버전 변경</p><p><br /></p><div class="t2-code-block"><pre><code>sudo update-alternatives --config php</code></pre></div><p><br /></p><p>입력 후 8.2 버전에 해당하는 숫자를 입력 후 Enter</p><p><br /></p><p>nginx 기준.</p><p><br /></p><div class="t2-code-block"><pre><code>/etc/nginx/sites-available/</code></pre></div><p><br /></p><p>에서 도메인 명으로 된 파일이 있으면 해당 파일을, 없으면 default를 수정하기 위해<br /></p><p><br /></p><div class="t2-code-block"><pre><code>vi /etc/nginx/sites-available/default</code></pre></div><p><br /></p><p>를 입력(default는 예시)</p><p><br /></p><p>접속하여</p><p><br /></p><p><br /></p><div class="t2-code-block"><pre><code>fastcgi_pass unix:/run/php/php7.4-fpm.sock;</code></pre></div><p><br /></p><p>의 php7.4부분을 아래와 같이 8.2로 변경</p><p><br /></p><div class="t2-code-block"><pre><code>fastcgi_pass unix:/run/php/php8.2-fpm.sock;</code></pre></div><p><br /></p><p><br /></p><p>:wq! 를 입력하여 강제 저장 후 나가기</p><p><br /></p><p>설정 오류 확인:</p><p><br /></p><div class="t2-code-block"><pre><code>nginx -t</code></pre></div><p><br /></p><p><br /></p><p>nginx 재시작</p><p><br /></p><div class="t2-code-block"><pre><code>sudo systemctl restart nginx</code></pre></div><p><br /></p><p><br /></p><p>그누보드5를 php8.2로 업데이트할 때 주의사항:</p><p>php7.4버전에 비해 문법에 대해 엄격해져 php버전 변경 이후 흰 화면이 출력될 수 있다. 이 때는</p><p>(nginx 기준)</p><p><br /></p><div class="t2-code-block"><pre><code>tail -f /var/log/nginx/error.log</code></pre></div><p><br /></p><p>를 입력하여 오류사항을 확인하고 하나하나 수정해야한다.</p>]]></description>
<dc:creator>최고관리자</dc:creator>
<dc:date>2025-05-06T01:04:07+09:00</dc:date>
</item>


<item>
<title>dsclub 업데이트 사항</title>
<link>https://dsclub.kr/dev/24</link>
<description><![CDATA[관리자 부분 업데이트)<br />접속자의 아이피를 통한 국가 확인 기능 추가(ipinfo.io api 이용)<br /><br />사용자 업데이트)<br />1.보존된 접속자 기록 약 10만건을 이용하여 아이피 검색 시 가장 가까운/해당 국가를 알려주는 "ip 위치 " 서비스 시작 (하단 검색창에 ip 위치 또는 아이피 국가 라고 검색하면 이용 가능)<br /><br />2.게시글 작성 시 게시판 선택 메뉴가 게시글 수정 시 뜨는 문제와 짧은 url에서는 정확한 게시판 이름을 띄우지 않는 문제 해결<br /><br /><br />3. 유튜브 표현 플러그인(dsclub 자체 제작 -클로드 ai)의 기존 에디터에서 표현했던 유튜브에 오류를 일으키는 문제 해결<br /><br /><br />4. 검색 시에도 멘션(@) 기능이 작동하는 문제 해결]]></description>
<dc:creator>tak2</dc:creator>
<dc:date>2024-12-02T11:58:40+09:00</dc:date>
</item>


<item>
<title>Twave 테마 회원가입 시 캡챠 검증 안하는 오류 해결</title>
<link>https://dsclub.kr/dev/23</link>
<description><![CDATA[버전: TwaveCv2 또는 Twave<br /><br />/theme/TwaveCv2(또는Tewave)/mobile/skin/member/register_form.php<br />의 가장 하단 스크립트를 아래의 코드로 덮어씌우기<br /><br /><br /><div>&lt;script&gt;</div><div>$(function() {</div><div>    $("#reg_zip_find").css("display", "inline-block");</div><div>    var pageTypeParam = "pageType=register";</div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre;">	</span>&lt;?php if($config['cf_cert_use'] &amp;&amp; $config['cf_cert_simple']) { ?&gt;</div><div><span class="Apple-tab-span" style="white-space:pre;">	</span>// 이니시스 간편인증</div><div><span class="Apple-tab-span" style="white-space:pre;">	</span>var url = "&lt;?php echo G5_INICERT_URL; ?&gt;/ini_request.php";</div><div><span class="Apple-tab-span" style="white-space:pre;">	</span>var type = "";    </div><div>    var params = "";</div><div>    var request_url = "";</div><div><br /></div><div><span class="Apple-tab-span" style="white-space:pre;">	</span>$(".win_sa_cert").click(function() {</div><div><span class="Apple-tab-span" style="white-space:pre;">		</span>if(!cert_confirm()) return false;</div><div><span class="Apple-tab-span" style="white-space:pre;">		</span>type = $(this).data("type");</div><div><span class="Apple-tab-span" style="white-space:pre;">		</span>params = "?directAgency=" + type + "&amp;" + pageTypeParam;</div><div>        request_url = url + params;</div><div>        call_sa(request_url);</div><div><span class="Apple-tab-span" style="white-space:pre;">	</span>});</div><div>    &lt;?php } ?&gt;</div><div>    &lt;?php if($config['cf_cert_use'] &amp;&amp; $config['cf_cert_ipin']) { ?&gt;</div><div>    // 아이핀인증</div><div>    var params = "";</div><div>    $("#win_ipin_cert").click(function() {</div><div><span class="Apple-tab-span" style="white-space:pre;">		</span>if(!cert_confirm()) return false;</div><div>        params = "?" + pageTypeParam;</div><div>        var url = "&lt;?php echo G5_OKNAME_URL; ?&gt;/ipin1.php"+params;</div><div>        certify_win_open('kcb-ipin', url);</div><div>        return;</div><div>    });</div><div><br /></div><div>    &lt;?php } ?&gt;</div><div>    &lt;?php if($config['cf_cert_use'] &amp;&amp; $config['cf_cert_hp']) { ?&gt;</div><div>    // 휴대폰인증</div><div>    var params = "";</div><div>    $("#win_hp_cert").click(function() {</div><div><span class="Apple-tab-span" style="white-space:pre;">		</span>if(!cert_confirm()) return false;</div><div>        params = "?" + pageTypeParam;</div><div>        &lt;?php     </div><div>        switch($config['cf_cert_hp']) {</div><div>            case 'kcb':                </div><div>                $cert_url = G5_OKNAME_URL.'/hpcert1.php';</div><div>                $cert_type = 'kcb-hp';</div><div>                break;</div><div>            case 'kcp':</div><div>                $cert_url = G5_KCPCERT_URL.'/kcpcert_form.php';</div><div>                $cert_type = 'kcp-hp';</div><div>                break;</div><div>            case 'lg':</div><div>                $cert_url = G5_LGXPAY_URL.'/AuthOnlyReq.php';</div><div>                $cert_type = 'lg-hp';</div><div>                break;</div><div>            default:</div><div>                echo 'alert("기본환경설정에서 휴대폰 본인확인 설정을 해주십시오");';</div><div>                echo 'return false;';</div><div>                break;</div><div>        }</div><div>        ?&gt;</div><div>        </div><div>        certify_win_open("&lt;?php echo $cert_type; ?&gt;", "&lt;?php echo $cert_url; ?&gt;"+params);</div><div>        return;</div><div>    });</div><div>    &lt;?php } ?&gt;</div><div>});</div><div><br /></div><div>// submit 최종 폼체크</div><div>function fregisterform_submit(f)</div><div>{</div><div>    if (f.w.value == "") {</div><div>        if (f.mb_password.value.length &lt; 3) {</div><div>            alert("비밀번호를 3글자 이상 입력하십시오.");</div><div>            f.mb_password.focus();</div><div>            return false;</div><div>        }</div><div>    }</div><div><br /></div><div>    if (f.mb_password.value != f.mb_password_re.value) {</div><div>        alert("비밀번호가 같지 않습니다.");</div><div>        f.mb_password_re.focus();</div><div>        return false;</div><div>    }</div><div><br /></div><div>    if (f.mb_password.value.length &gt; 0) {</div><div>        if (f.mb_password_re.value.length &lt; 3) {</div><div>            alert("비밀번호를 3글자 이상 입력하십시오.");</div><div>            f.mb_password_re.focus();</div><div>            return false;</div><div>        }</div><div>    }</div><div><br /></div><div>    // 이름 검사</div><div>    if (f.w.value=="") {</div><div>        if (f.mb_name.value.length &lt; 1) {</div><div>            alert("이름을 입력하십시오.");</div><div>            f.mb_name.focus();</div><div>            return false;</div><div>        }</div><div><br /></div><div>        /*</div><div>        var pattern = /([^가-힣\x20])/i;</div><div>        if (pattern.test(f.mb_name.value)) {</div><div>            alert("이름은 한글로 입력하십시오.");</div><div>            f.mb_name.select();</div><div>            return false;</div><div>        }</div><div>        */</div><div>    }</div><div><br /></div><div>    &lt;?php if($w == '' &amp;&amp; $config['cf_cert_use'] &amp;&amp; $config['cf_cert_req']) { ?&gt;</div><div>    // 본인확인 체크</div><div>    if(f.cert_no.value=="") {</div><div>        alert("회원가입을 위해서는 본인확인을 해주셔야 합니다.");</div><div>        return false;</div><div>    }</div><div>    &lt;?php } ?&gt;</div><div><br /></div><div>    // 닉네임 검사</div><div>    if ((f.w.value == "") || (f.w.value == "u" &amp;&amp; f.mb_nick.defaultValue != f.mb_nick.value)) {</div><div>        var msg = reg_mb_nick_check();</div><div>        if (msg) {</div><div>            alert(msg);</div><div>            f.reg_mb_nick.select();</div><div>            return false;</div><div>        }</div><div>    }</div><div><br /></div><div>    // E-mail 검사</div><div>    if ((f.w.value == "") || (f.w.value == "u" &amp;&amp; f.mb_email.defaultValue != f.mb_email.value)) {</div><div>        var msg = reg_mb_email_check();</div><div>        if (msg) {</div><div>            alert(msg);</div><div>            f.reg_mb_email.select();</div><div>            return false;</div><div>        }</div><div>    }</div><div><br /></div><div>    &lt;?php if (($config['cf_use_hp'] || $config['cf_cert_hp']) &amp;&amp; $config['cf_req_hp']) {  ?&gt;</div><div>    // 휴대폰번호 체크</div><div>    var msg = reg_mb_hp_check();</div><div>    if (msg) {</div><div>        alert(msg);</div><div>        f.reg_mb_hp.select();</div><div>        return false;</div><div>    }</div><div>    &lt;?php } ?&gt;</div><div><br /></div><div>    if (typeof f.mb_icon != "undefined") {</div><div>        if (f.mb_icon.value) {</div><div>            if (!f.mb_icon.value.toLowerCase().match(/.(gif|jpe?g|png)$/i)) {</div><div>                alert("회원아이콘이 이미지 파일이 아닙니다.");</div><div>                f.mb_icon.focus();</div><div>                return false;</div><div>            }</div><div>        }</div><div>    }</div><div><br /></div><div>    if (typeof f.mb_img != "undefined") {</div><div>        if (f.mb_img.value) {</div><div>            if (!f.mb_img.value.toLowerCase().match(/.(gif|jpe?g|png)$/i)) {</div><div>                alert("회원이미지가 이미지 파일이 아닙니다.");</div><div>                f.mb_img.focus();</div><div>                return false;</div><div>            }</div><div>        }</div><div>    }</div><div><br /></div><div>    if (typeof(f.mb_recommend) != "undefined" &amp;&amp; f.mb_recommend.value) {</div><div>        if (f.mb_id.value == f.mb_recommend.value) {</div><div>            alert("본인을 추천할 수 없습니다.");</div><div>            f.mb_recommend.focus();</div><div>            return false;</div><div>        }</div><div><br /></div><div>        var msg = reg_mb_recommend_check();</div><div>        if (msg) {</div><div>            alert(msg);</div><div>            f.mb_recommend.select();</div><div>            return false;</div><div>        }</div><div>    }</div><div><br /></div><div>// CAPTCHA verification</div><div>    &lt;?php echo chk_captcha_js(); ?&gt;</div><div><br /></div><div>    // Additional CAPTCHA check before form submission</div><div>    if (f.captcha_no.value.length &lt; 1) {</div><div>        alert("자동등록방지 코드를 입력하세요.");</div><div>        f.captcha_no.focus();</div><div>        return false;</div><div>    }</div><div><br /></div><div>    // Use AJAX to verify CAPTCHA on the server-side</div><div>    $.ajax({</div><div>        url: '&lt;?php echo G5_URL ?&gt;/bbs/captcha_check.php',</div><div>        type: 'POST',</div><div>        data: {</div><div>            captcha_no: f.captcha_no.value,</div><div>            captcha_key: f.captcha_key.value</div><div>        },</div><div>        async: false,</div><div>        success: function(response) {</div><div>            if (response.trim() !== 'true') {</div><div>                alert('자동등록방지 코드가 올바르지 않습니다.');</div><div>                f.captcha_no.focus();</div><div>                document.getElementById("btn_submit").disabled = false;</div><div>                return false;</div><div>            }</div><div>        },</div><div>        error: function() {</div><div>            alert('CAPTCHA 검증 중 오류가 발생했습니다.');</div><div>            document.getElementById("btn_submit").disabled = false;</div><div>            return false;</div><div>        }</div><div>    });</div><div><br /></div><div>    document.getElementById("btn_submit").disabled = "disabled";</div><div><br /></div><div>    return true;</div><div>}</div><div><br /></div><div>// Refresh CAPTCHA functionality</div><div>function refreshCaptcha() {</div><div>    $.ajax({</div><div>        url: '&lt;?php echo G5_URL ?&gt;/bbs/captcha_reload.php',</div><div>        type: 'GET',</div><div>        success: function(response) {</div><div>            $('#captcha_img').attr('src', response);</div><div>        }</div><div>    });</div><div>}</div><div>&lt;/script&gt;<br /><br /><br /><br /><br />클루드 ai로 해결했습니다 ^^</div>]]></description>
<dc:creator>tak2</dc:creator>
<dc:date>2024-11-26T19:29:03+09:00</dc:date>
</item>


<item>
<title>dsclub 업데이트 사항</title>
<link>https://dsclub.kr/dev/22</link>
<description><![CDATA[<p>1. 뱃지 부여와 연동된 레벨 시스템 구현<br />2. 레벨 시스템의 수식 수정하여 지수함수로 변경<br />3. 보유 벳지 출력 부분 구현<br />4. 게기판 게시글쓰기 버튼 애니메이션 적용<br />5.검색어 자동 완성(검색어 추천) 기능 구현 - 자유게시판, 공지, 갤러리, 코딩게시판, it게시판 + 인기 검색어 데이터베이스 기반</p>]]></description>
<dc:creator>tak2</dc:creator>
<dc:date>2024-11-17T04:59:45+09:00</dc:date>
</item>


<item>
<title>(Twave) 프로필 수정 기능 추가</title>
<link>https://dsclub.kr/dev/21</link>
<description><![CDATA[<p>기존에는 기기 자체의 사진 편집기를 통해 사진 편집 후 프로필 이미지를 업로드 해야 프로필 이미지를 꾸밀 수 있었는데 이 점을 보완하여 프로필 사진 확대/축소/회전 기능을 기본 탑재하였습니다.<br /><br />profile_form.skin.php에 아래의 코드를 원하는 위치에 추가하시면 됩니다.<br /><br /></p><p>&lt;style&gt;</p><p>  /* 이미지 편집 모달 스타일 */</p><p>  .image-editor-modal {</p><p>    display: none;</p><p>    position: fixed;</p><p>    z-index: 1000;</p><p>    top: 0;</p><p>    left: 0;</p><p>    width: 100%;</p><p>    height: 100%;</p><p>    background-color: rgba(0, 0, 0, 0.85);</p><p>    align-items: center;</p><p>    justify-content: center;</p><p>    font-family: 'Arial', sans-serif;</p><p>  }</p><p><br /></p><p>  .image-editor-content {</p><p>    background-color: #ffffff;</p><p>    padding: 10px;</p><p>    border-radius: 20px;</p><p>    max-width: 90%;</p><p>    text-align: center;</p><p>    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);</p><p>    width: 90%;</p><p>    max-width: 360px;</p><p>  }</p><p><br /></p><p>  .editor-canvas {</p><p>    border-radius: 8px;</p><p>    border: 1px solid #e6e6e6;</p><p>    width: 100%;</p><p>    height: auto;</p><p>    margin-bottom: 10px;</p><p>  }</p><p><br /></p><p>  /* 버튼 스타일 */</p><p>  .image-editor-buttons {</p><p>    display: flex;</p><p>    justify-content: space-around;</p><p>    margin-top: 10px;</p><p>  }</p><p><br /></p><p>  .image-editor-buttons button {</p><p>    background-color: #2792fb;</p><p>    color: #fff;</p><p>    border: none;</p><p>    border-radius: 20px;</p><p>    padding: 8px 12px;</p><p>    cursor: pointer;</p><p>    font-size: 13px;</p><p>    transition: background-color 0.3s ease;</p><p>    flex: 1;</p><p>    margin: 0 5px;</p><p>  }</p><p><br /></p><p>  .image-editor-buttons button:hover {</p><p>    background-color: #2792fb;</p><p>  }</p><p><br /></p><p>  /* 취소 버튼 별도 스타일 */</p><p>  #cancelEdit {</p><p>    background-color: #dbdbdb;</p><p>    color: #262626;</p><p>  }</p><p><br /></p><p>  #cancelEdit:hover {</p><p>    background-color: #c1c1c1;</p><p>  }</p><p>&lt;/style&gt;</p><p><br /></p><p>&lt;!-- 이미지 편집 모달 구조 --&gt;</p><p>&lt;div class="image-editor-modal" id="imageEditorModal"&gt;</p><p>  &lt;div class="image-editor-content"&gt;</p><p>    &lt;canvas id="editorCanvas" class="editor-canvas"&gt;&lt;/canvas&gt;</p><p>    &lt;div class="image-editor-buttons"&gt;</p><p>      &lt;button type="button" id="rotateLeft"&gt;회전&lt;/button&gt;</p><p>      &lt;button type="button" id="applyChanges"&gt;적용&lt;/button&gt;</p><p>      &lt;button type="button" id="cancelEdit"&gt;취소&lt;/button&gt;</p><p>    &lt;/div&gt;</p><p>  &lt;/div&gt;</p><p>&lt;/div&gt;</p><p><br /></p><p>&lt;script&gt;</p><p>  const imageEditorModal = document.getElementById('imageEditorModal');</p><p>  const editorCanvas = document.getElementById('editorCanvas');</p><p>  const ctx = editorCanvas.getContext('2d');</p><p>  let scale = 1;</p><p>  let rotation = 0;</p><p>  let img = new Image();</p><p>  let offsetX = 0;</p><p>  let offsetY = 0;</p><p>  let initialDistance = null;</p><p>  const canvasSize = 300; // 1:1 비율을 위한 고정 크기</p><p>  const snapThreshold = 20; // 중앙으로 스냅되는 거리 설정</p><p><br /></p><p>  document.getElementById('reg_mb_img').addEventListener('change', function(event) {</p><p>    const file = event.target.files[0];</p><p>    if (file) {</p><p>      const reader = new FileReader();</p><p>      reader.onload = function(e) {</p><p>        imageEditorModal.style.display = 'flex';</p><p>        document.body.style.overflow = 'hidden'; // 모달 열릴 때 스크롤 잠금</p><p>        img.src = e.target.result;</p><p>        img.onload = function() {</p><p>          editorCanvas.width = canvasSize;</p><p>          editorCanvas.height = canvasSize;</p><p>          applyTransformations();</p><p>        };</p><p>      };</p><p>      reader.readAsDataURL(file);</p><p>    }</p><p>  });</p><p><br /></p><p>  function applyTransformations() {</p><p>    ctx.clearRect(0, 0, editorCanvas.width, editorCanvas.height);</p><p>    ctx.save();</p><p>    ctx.translate(editorCanvas.width / 2 + offsetX, editorCanvas.height / 2 + offsetY);</p><p>    ctx.scale(scale, scale);</p><p>    ctx.rotate((rotation * Math.PI) / 180);</p><p>    const imgAspect = img.width / img.height;</p><p>    const canvasAspect = editorCanvas.width / editorCanvas.height;</p><p>    let renderWidth, renderHeight;</p><p>    </p><p>    if (imgAspect &gt; canvasAspect) {</p><p>      renderHeight = editorCanvas.height;</p><p>      renderWidth = img.width * (renderHeight / img.height);</p><p>    } else {</p><p>      renderWidth = editorCanvas.width;</p><p>      renderHeight = img.height * (renderWidth / img.width);</p><p>    }</p><p>    </p><p>    ctx.drawImage(img, -renderWidth / 2, -renderHeight / 2, renderWidth, renderHeight);</p><p>    ctx.restore();</p><p>  }</p><p><br /></p><p>  function snapToCenter() {</p><p>    if (Math.abs(offsetX) &lt; snapThreshold) {</p><p>      offsetX = 0;</p><p>    }</p><p>    if (Math.abs(offsetY) &lt; snapThreshold) {</p><p>      offsetY = 0;</p><p>    }</p><p>  }</p><p><br /></p><p>  // 핀치 줌 및 드래그 기능</p><p>  editorCanvas.addEventListener('touchstart', function(e) {</p><p>    e.preventDefault(); // 기본 동작 방지</p><p>    if (e.touches.length === 1) { // 드래그 시작</p><p>      lastX = e.touches[0].clientX;</p><p>      lastY = e.touches[0].clientY;</p><p>    } else if (e.touches.length === 2) { // 핀치 줌 시작</p><p>      initialDistance = getDistance(e.touches[0], e.touches[1]);</p><p>    }</p><p>  });</p><p><br /></p><p>  editorCanvas.addEventListener('touchmove', function(e) {</p><p>    e.preventDefault(); // 기본 동작 방지</p><p>    if (e.touches.length === 1) { // 드래그 이동</p><p>      const deltaX = e.touches[0].clientX - lastX;</p><p>      const deltaY = e.touches[0].clientY - lastY;</p><p>      lastX = e.touches[0].clientX;</p><p>      lastY = e.touches[0].clientY;</p><p>      offsetX += deltaX;</p><p>      offsetY += deltaY;</p><p>      snapToCenter(); // 이동할 때마다 자석 기능 적용</p><p>      applyTransformations();</p><p>    } else if (e.touches.length === 2) { // 핀치 줌</p><p>      const newDistance = getDistance(e.touches[0], e.touches[1]);</p><p>      if (initialDistance) {</p><p>        const scaleChange = newDistance / initialDistance;</p><p>        scale = Math.max(0.5, Math.min(3, scale * scaleChange)); // 0.5 ~ 3배율 사이로 제한</p><p>        initialDistance = newDistance;</p><p>        applyTransformations();</p><p>      }</p><p>    }</p><p>  });</p><p><br /></p><p>  editorCanvas.addEventListener('touchend', function(e) {</p><p>    e.preventDefault();</p><p>    if (e.touches.length === 0) { // 터치 종료</p><p>      initialDistance = null;</p><p>      snapToCenter(); // 터치가 끝날 때도 자석 기능 적용</p><p>      applyTransformations();</p><p>    }</p><p>  });</p><p><br /></p><p>  function getDistance(touch1, touch2) {</p><p>    const dx = touch2.clientX - touch1.clientX;</p><p>    const dy = touch2.clientY - touch1.clientY;</p><p>    return Math.sqrt(dx * dx + dy * dy);</p><p>  }</p><p><br /></p><p>  // 모달 외부의 터치 및 스크롤 방지</p><p>  document.addEventListener('touchmove', function(e) {</p><p>    if (imageEditorModal.style.display === 'flex') {</p><p>      e.preventDefault(); // 기본 동작 및 외부 스크롤 방지</p><p>    }</p><p>  }, { passive: false });</p><p><br /></p><p>  // 회전 버튼</p><p>  document.getElementById('rotateLeft').addEventListener('click', function() {</p><p>    rotation += 90;</p><p>    applyTransformations();</p><p>  });</p><p><br /></p><p>  // 적용 버튼 클릭 시 동작</p><p>  document.getElementById('applyChanges').addEventListener('click', function(event) {</p><p>    event.preventDefault();</p><p>    editorCanvas.toBlob(function(blob) {</p><p>      const fileInput = document.getElementById('reg_mb_img');</p><p>      const file = new File([blob], 'edited_profile.png', { type: 'image/png' });</p><p>      const dataTransfer = new DataTransfer();</p><p>      dataTransfer.items.add(file);</p><p>      fileInput.files = dataTransfer.files;</p><p><br /></p><p>      document.getElementById('profileImage').src = editorCanvas.toDataURL();</p><p>      imageEditorModal.style.display = 'none';</p><p>      document.body.style.overflow = ''; // 모달 닫힐 때 스크롤 복구</p><p>    });</p><p>  });</p><p><br /></p><p>  document.getElementById('cancelEdit').addEventListener('click', function(event) {</p><p>    event.preventDefault();</p><p>    imageEditorModal.style.display = 'none';</p><p>    document.body.style.overflow = ''; // 모달 닫힐 때 스크롤 복구</p><p>  });</p><p>&lt;/script&gt;<br /><br /></p><p><br />( 현재 dsclub에서 사용중인 테마의 버전은 TwaveCv2 - Twave 커스텀 버전2 입니다 )</p>]]></description>
<dc:creator>tak2</dc:creator>
<dc:date>2024-11-02T19:54:51+09:00</dc:date>
</item>


<item>
<title>Twave 테마  채팅 파일 업로드 공유 보안 수정</title>
<link>https://dsclub.kr/dev/18</link>
<description><![CDATA[<p>기존:<br />채팅 데이터폴더에 회원 구분 없이 파일들을 한 곳에 저장<br /><br /><br />수정:<br />채팅할 때 공유한 파일들을 보내는유저/받는유저 디렉토리에 저장하도록 수정<br /><br />회원 간 채팅 폴더 하나 뚫려도 다른 유저들의 공유 파일들은 노출 되지 않음.<br /><br /><br />국어를 잘 못해서 설명하기 어렵지만, 기존에는  회원들 간의 채팅 공유 파일들을 A폴더라는 한 곳에 전부 보관해서 A폴더만 털려도 개인정보가 유출 될 위험이 있었지만, 수정 버전은 (보내는 회원 받는 회원 ) 폴더에 저장하기에 각각의 대화 마다 공유파일이 각각 다른 폴더에 저장되어서 한 폴더가 뚫렸다고 다른 폴더 안의 개인정보(공유 파일)들이 노출 될 위험이 줄어들게 됨.<br /><br />A, B가 채팅한다고 하면 A가 B에게 보낸 파일들 폴더 하나, B가 A에게 보낸 폴더 하나 이렇게 채팅 시 총 두 개의 폴더를 만들어서 최대한 유출되는 데이터 양이 줄어들도록 구현</p>]]></description>
<dc:creator>tak2</dc:creator>
<dc:date>2024-10-21T00:12:43+09:00</dc:date>
</item>


<item>
<title>Twave 비회원 아이디(닉네임) 출력되지 않던 문제 해결</title>
<link>https://dsclub.kr/dev/17</link>
<description><![CDATA[<p>$name_tag_close = '&lt;/a&gt;'; 위에 $name_tag['name'] = $name;를 추가해서 해결하였습니다.<br /></p>]]></description>
<dc:creator>tak2</dc:creator>
<dc:date>2024-10-20T00:14:30+09:00</dc:date>
</item>


<item>
<title>1:1 채팅 파일 공유 관련 기능 추가</title>
<link>https://dsclub.kr/dev/16</link>
<description><![CDATA[<p>기존에는 보안 및 서버 용량과 트래픽 관리를 위해 채팅 공유 파일들을 정기적으로 삭제를 해야 하는 문제가 있었고 그에 따른 과거 공유 데이터를 다운로드 및 복구할 수 없다는 문제가 있었습니다.<br />따라서 이 문제를 해결하기 위해 로컬 스토리지에 미디어화 했던 이미지, 동영상들을 base64로 인코딩하여 저장해서 서버에 저장된 데이터가 사라져도 이용할 수 있게 구현하였습니다. 다만 서버에서 이미 제거된 데이터의 원본과 비교하여 퀄리티 차이가 발생할 수 있으며, 기기의 용량에 따라 해당 기능에 문제가 발생할 수 있고 로컬 스토리지에 사라졌었던 과거 데이터가 저장되어있지 않은 다른 기기에서는 복구 및 재확인이 불가능합니다.<br /><br />*Twave에 적용될 수 있습니다.</p>]]></description>
<dc:creator>tak2</dc:creator>
<dc:date>2024-10-11T02:28:57+09:00</dc:date>
</item>


<item>
<title>프로필 보안기능 강화</title>
<link>https://dsclub.kr/dev/15</link>
<description><![CDATA[<p>DSc(dsclub)의 기반인 Twave에서의 개인 프로필 탭(본인 프로필 수정할 수 있는 화면)의 보안, 팔로우 버그유도 부분을 파악하여 적절하게 차단/리다이렉트 처리하여 버그 또는 보안문제가 일어나지 않도록 수정하였습니다.<br /><br /><br />(Twave 테마에 적용 예정)<br /><br /><br />/member_profile.php (member_profile.skin.php)<br /></p><p>&lt;?php</p><p>if(!$member['mb_id']) alert('로그인후 이용해주세요', G5_BBS_URL.'/login.php'); // 로그인안하면 로그인페이지로</p><p>include_once(G5_LIB_PATH.'/latest.lib.php');</p><p><br /></p><p>// add_stylesheet('css 구문', 출력순서); 숫자가 작을 수록 먼저 출력됨</p><p>add_stylesheet('&lt;link rel="stylesheet" href="'.$member_skin_url.'/style.css"&gt;', 0);</p><p><br /></p><p>// 현재 URL의 mb_id 파라미터 값을 가져옵니다.</p><p>$current_mb_id = isset($_GET['mb_id']) ? $_GET['mb_id'] : '';</p><p><br /></p><p>// 회원의 mb_id 값</p><p>$member_mb_id = $member['mb_id'];</p><p><br /></p><p>// mb_id가 일치하지 않는 경우</p><p>if ($current_mb_id !== $member_mb_id) {</p><p>    // 메시지를 출력합니다.</p><p>    echo "&lt;script&gt;alert('타인의 개인 프로필 탭은 확인할 수 없습니다.');&lt;/script&gt;";</p><p>    </p><p>    // 현재 도메인을 자동으로 가져옵니다.</p><p>    $current_domain = ($_SERVER['HTTPS'] ? "https://" : "http://") . $_SERVER['HTTP_HOST'];</p><p>    </p><p>    // 도메인으로 리다이렉트합니다.</p><p>    echo "&lt;script&gt;window.location.href = '$current_domain';&lt;/script&gt;";</p><p>    }</p><p>?&gt;<br /><br /><br />/profile.php (profile.skin.php)<br /></p><p>&lt;?php</p><p>if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가</p><p>include_once(G5_LIB_PATH.'/latest.lib.php');</p><p><br /></p><p>// add_stylesheet('css 구문', 출력순서); 숫자가 작을 수록 먼저 출력됨</p><p>add_stylesheet('&lt;link rel="stylesheet" href="'.$member_skin_url.'/style.css"&gt;', 0);</p><p><br /></p><p>$member_mb_id = get_member($row['mb_id']);</p><p><br /></p><p>// 현재 URL의 mb_id 파라미터 값 가져오기</p><p>$current_mb_id = isset($_GET['mb_id']) ? $_GET['mb_id'] : null;</p><p><br /></p><p>// 회원 정보의 mb_id</p><p>$member_mb_id = $member['mb_id'];</p><p><br /></p><p>// mb_id가 일치하는지 확인</p><p>if ($current_mb_id === $member_mb_id) {</p><p>    // 일치할 경우 리다이렉트</p><p>    header("Location: /bbs/member_profile.php?mb_id=" . urlencode($member_mb_id));</p><p>    exit; // 스크립트 종료</p><p>}</p><p>?&gt;</p><p><br /></p>]]></description>
<dc:creator>tak2</dc:creator>
<dc:date>2024-10-03T04:00:59+09:00</dc:date>
</item>


<item>
<title>채팅 유튜브 출력 기능 추가 및 기타 업그레이드</title>
<link>https://dsclub.kr/dev/14</link>
<description><![CDATA[<p>1) 지금부터 1:1 채팅에 유튜브 영상을 첨부할 수 있습니다.<br />2) 미디어 출력 기능과 ios와의 호환성을 향상시켰으며 성능 또한 일부 향상이 있습니다.<br />3) 기존에는 미디어 출력 기능으로 인해 최신 메시지에서 채팅을 시작하는 것이 아닌 채팅 내역 중간에서 다시 아래로 화면을 스크롤 해야 했으나 자동 하단 스크롤 기능을 도입하여 편하게 이용하실 수 있습니다.<br />4) ios 기기들은 동영상 첨부 시 영상이 제대로 출력되지 않던 일부 호환성 문제들을 해결했습니다. (+ mov확장자 지원)</p>]]></description>
<dc:creator>tak2</dc:creator>
<dc:date>2024-09-23T05:49:50+09:00</dc:date>
</item>


<item>
<title>채팅 알람기능 추가</title>
<link>https://dsclub.kr/dev/13</link>
<description><![CDATA[<p>Twave테마의 추가자료를 dsclub에도 적용하였습니다,</p>]]></description>
<dc:creator>tak2</dc:creator>
<dc:date>2024-09-22T12:16:53+09:00</dc:date>
</item>


<item>
<title>1:1 채팅 첨부파일 파일 업로드 기능 업그레이드</title>
<link>https://dsclub.kr/dev/12</link>
<description><![CDATA[<p>기존에는 새로고침 또는 파일 용량이 크다면 업로드가 되지 않고 오류가 발생하던 것을 <br />브라우저의 로컬스토리지를 이용하여 해결하였습니다.<br />+만료/오류 사진은 에러 이미지로 변경되도록 설정하였습니다.</p>]]></description>
<dc:creator>tak2</dc:creator>
<dc:date>2024-09-21T21:44:25+09:00</dc:date>
</item>


<item>
<title>프로필 비밀번호 수정 오류 해결</title>
<link>https://dsclub.kr/dev/11</link>
<description><![CDATA[<p>그누보드5에서 비밀번호 변경 시 비밀번호를 1차 입력, 최종 확인 입력을 통해 비밀번호를 변경할 수 있는데, 최종 확인 입력 부분을 연동하지 않아(?) 발생했던 오류였습니다.<br /><br /><br />*Twave 테마에서도 동일한 증상이 일어나는 것으로 보이며 추후 수정할 예정입니다.</p>]]></description>
<dc:creator>tak2</dc:creator>
<dc:date>2024-09-20T17:38:30+09:00</dc:date>
</item>

</channel>
</rss>
