403Webshell
Server IP : 119.195.102.159  /  Your IP : 216.73.217.134
Web Server : nginx/1.18.0
System : Linux picell 5.15.0-181-generic #191-Ubuntu SMP Fri May 22 19:09:02 UTC 2026 x86_64
User : altablue ( 1000)
PHP Version : 8.3.31
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : ON
Directory :  /home/mysqlbk/dodn_2/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/mysqlbk/dodn_2/dev2_snippets.ibd
�Ը,�|@AII@!��������������������������&&�������������������������Ը,@A�{�>�|?��eI�{�>?��e'��V�|@AI���������������������������������i������������������������������������������������������������������������������������������������������������������������������������������������������i�	

�������������������������������������������������������������������������������������������������������������������������i������������������������������������������������������������������������������������������������������������������������������������������������������i���������������������������������������������������������������������������������������������������������������������������������������������������������i������������������������������������������������������������������������������������������������������������������������������������������������������i��������������������������������������������������������������������������������������������������������������������������������'��V@A7��V���������|@W/E�I��	�5��I�I2infimumsupremum��	!�)�	1�
9�'A���.
pc7��V@W/�<����������|@W@E�I
j�4\28z?��IrI�Zinfimumsupremum+global�	��front-end� )global�(��content�0global�8global�@global�Hglobal�Pglobal�	Xglobal�`global�hglobal�
pglobal�xglobal��global��global��global��global��global��global��global��global��global��global��global��global��global��global��global��global� global�!global�"global�#global�$ global�%(global�&0global�'8global�(@global�)Hglobal�*Pglobal�+Xglobal�,`global�-hglobal�.pglobal�0xglobal�1�global�2�.global�3��.head-content�4���global�5p��>��N��^�c�<�@W@t11���������|@WQE�I4�4+28z?��I�I2infimumsupremum��*�� ��(��0T��8��@��H��P��	X���`��h��
p��x������������������F���������������8������������8����� ��!8��"��#��$ F��%(��&0��'8��(@*��)H��*P�ހ�+X��,`��-h��.p��0x��1���2���3���4��E��5p��!��O=��ct11@WQ���������|@\E�I7V�����!infimumsupremum
4��.g��업로드 파일 이름을 소문자로 변경이미지 및 파일 업로드의 파일 이름이 소문자인지 확인합니다.

이것은 샘플 스니펫입니다. 자유롭게 사용하거나, 편집하거나, 제거하세요.add_filter( 'sanitize_file_name', 'mb_strtolower' );sample, mediaglobal�
�������	s�����관리자 바 비활성화관리자를 제외한 모든 사용자에 대해 WordPress 관리자 바를 끕니다.

이것은 샘플 스니펫입니다. 자유롭게 사용하거나, 편집하거나, 제거하세요.add_action( 'wp', function () {
	if ( ! current_user_can( 'manage_options' ) ) {
		show_admin_bar( false );
	}
} );sample, admin-barfront-end�
����������� €�이모티콘 허용잘 알려지지 않은 곳에서 이모티콘 변환을 허용합니다.

이것은 샘플 스니펫입니다. 자유롭게 사용하거나, 편집하거나, 제거하세요.add_filter( 'widget_text', 'convert_smilies' );
add_filter( 'the_title', 'convert_smilies' );
add_filter( 'wp_title', 'convert_smilies' );
add_filter( 'get_bloginfo', 'convert_smilies' );sampleglobal�
�������
��
(��현재 연도현재 연도를 글이나 페이지에 삽입하는 쇼트코드.

이것은 샘플 스니펫입니다. 자유롭게 사용하거나, 편집하거나, 제거하세요.<?php echo date( 'Y' ); ?>sample, datescontent�
�������!� 0���My 라이브러리 유저 정보add_shortcode('my_user_info', function () {
    if (is_user_logged_in()) {
        $user = wp_get_current_user();
        $avatar_url = get_avatar_url($user->ID, ['size' => 50]);

        $output = '
        <div style="display: flex; align-items: center; gap: 15px; margin-bottom: 20px;">
            <img src="' . esc_url($avatar_url) . '" alt="Profile Image" style="width: 50px; height: 50px; border-radius: 50%;">
            <div>
                <h5 style="margin: 0;">👋 ' . esc_html($user->nickname) . ' 님, 환영합니다.</h5>
                <p style="color: grey; margin: 5px 0 0 0;">📧 ' . esc_html($user->user_email) . '</p>
            </div>
        </div>
        ';

        return $output;
    } else {
        return '<p>로그인 후 확인 가능합니다.</p>';
    }
});
global�
����U:��لA8Y��회원가입 입력값 유효성 검사 + 사용자 메타 저장// 회원가입 유효성 검사
add_action('woocommerce_register_post', function($username, $email, $errors) {
    // 회원가입 페이지에서만 실행
    if (!is_account_page()) {
        return;
    }

    if (empty($_POST['phone'])) {
        $errors->add('phone_error', '휴대폰 번호를 입력해주세요.');
    }
    if (empty($_POST['birthdate'])) {
        $errors->add('birthdate_error', '생년월일을 입력해주세요.');
    }
    if (empty($_POST['grade'])) {
        $errors->add('grade_error', '학년을 선택해주세요.');
    }
    if (empty($_POST['register_terms'])) {
        $errors->add('terms_error', '이용약관에 동의해야 회원가입이 가능합니다.');
    }
}, 10, 3);


// 회원가입 후 사용자 메타 저장
add_action('woocommerce_created_customer', function($customer_id) {
    if (!empty($_POST['phone'])) {
        update_user_meta($customer_id, 'phone', sanitize_text_field($_POST['phone']));
    }
    if (!empty($_POST['birthdate'])) {
        update_user_meta($customer_id, 'birthdate', sanitize_text_field($_POST['birthdate']));
    }
    if (!empty($_POST['grade'])) {
        update_user_meta($customer_id, 'grade', sanitize_text_field($_POST['grade']));
    }
});
global�
���:@����@��회원가입 폼 필드 추가add_action('woocommerce_register_form', function() {
    ?>
    <p class="form-row form-row-wide">
        <label for="phone">휴대폰 번호 <span class="required">*</span></label>
        <input type="text" name="phone" id="phone" />
    </p>
    <p class="form-row form-row-wide">
        <label for="birthdate">생년월일 <span class="required">*</span></label>
        <input type="date" name="birthdate" id="birthdate" />
    </p>
    <p class="form-row form-row-wide">
        <label for="grade">학년 <span class="required">*</span></label>
        <select name="grade" id="grade">
            <option value="">선택하세요</option>
			<option value="예비 고1">예비 고1</option>
            <option value="고1">고1</option>
            <option value="고2">고2</option>
            <option value="고3">고3</option>
			<option value="재수/N수">재수/N수</option>
        </select>
    </p>
    <p class="form-row form-row-wide">
        <label class="woocommerce-form__label woocommerce-form__label-for-checkbox">
            <input class="woocommerce-form__input-checkbox" name="register_terms" type="checkbox" value="1" />
            <span>이용약관에 동의합니다. <a href="https://dodn.picell.biz/%ec%84%9c%eb%b9%84%ec%8a%a4-%ec%9d%b4%ec%9a%a9%ec%95%bd%ea%b4%80/" target="_blank">약관 보기</a></span> <span class="required">*</span>
        </label>
    </p>
    <?php
});
global�
����[����:H�H��마이페이지 유효성 검사 + 사용자 메타 저장add_action('woocommerce_save_account_details_errors', function($errors, $user) {
    if (empty($_POST['phone'])) {
        $errors->add('phone_error', '휴대폰 번호를 입력해주세요.');
    }
    if (empty($_POST['birthdate'])) {
        $errors->add('birthdate_error', '생년월일을 입력해주세요.');
    }
}, 10, 2);

add_action('woocommerce_save_account_details', function($user_id) {
    if (!empty($_POST['phone'])) {
        update_user_meta($user_id, 'phone', sanitize_text_field($_POST['phone']));
    }
    if (!empty($_POST['birthdate'])) {
        update_user_meta($user_id, 'birthdate', sanitize_text_field($_POST['birthdate']));
    }
    if (!empty($_POST['grade'])) {
        update_user_meta($user_id, 'grade', sanitize_text_field($_POST['grade']));
    }
});global�
����[���ӃP/�	8z?�@>마이페이지 필드 추가add_action('woocommerce_edit_account_form', function() {
    ?>
    <p class="form-row form-row-wide">
        <label for="phone">휴대폰 번호&nbsp;<span class="required">*</span></label>
        <input type="text" name="phone" id="phone" value="" />
    </p>
    <p class="form-row form-row-wide">
        <label for="birthdate">생년월일&nbsp;<span class="required">*</span></label>
        <input type="date" name="birthdate" id="birthdate" value="" />
    </p>
    <p class="form-row form-row-wide">
        <label for="grade">학년&nbsp;<span class="required">*</span></label>
        <select name="grade" id="grade">
            <option value="">선택하세요</option>
            <option value="예비 고1">예비 고1</option>
            <option value="고1">고1</option>
            <option value="고2">고2</option>
            <option value="고3">고3</option>
			<option value="재수/N수">재수/N수</option>
        </select>
    </p>
    <?php
});global�
����[����Xl�8z?�@> 글 복사 및 우클릭 방지function protect_post_content() {
    if (is_singular('post')) {
        ?>
        <script>
        // 우클릭 방지
        document.addEventListener('contextmenu', function (e) {
            e.preventDefault();
        });

        // Ctrl+C 방지
        document.addEventListener('keydown', function (e) {
            if (e.ctrlKey && e.key.toLowerCase() === 'c') {
                e.preventDefault();
            }
        });
        </script>
        <?php
    }
}
add_action('wp_footer', 'protect_post_content');
global�
����bL��;�`��8z?�@>2사용자 아바타 숏코드// [user_profile_image] 숏코드
function user_profile_image_shortcode() {
    if ( is_user_logged_in() ) {
        $user_id = get_current_user_id();
        $profile_image_id = get_user_meta($user_id, 'profile_image', true); // Image Picker 필드 저장 키 확인
        if ( $profile_image_id ) {
            return wp_get_attachment_image( $profile_image_id, 'thumbnail', false, array(
                'class' => 'custom-user-avatar rounded-full'
            ));
        }
    }
    return '';
}
add_shortcode('user_profile_image', 'user_profile_image_shortcode');global�
����F���hZ�
8z?�@>D사용자 이름 숏코드// [user_name]
function custom_user_name_shortcode() {
    if ( is_user_logged_in() ) {
        $user = wp_get_current_user();
        return esc_html( $user->display_name );
    }
    return '';
}
add_shortcode( 'user_name', 'custom_user_name_shortcode' );global�
���tր�y�pՀ8z?�@>V로그아웃 버튼 숏코드// [user_logout]
function custom_user_logout_shortcode() {
    if ( is_user_logged_in() ) {
        $logout_url = wp_logout_url( home_url() ); // 로그아웃 후 홈으로 리다이렉트
        return '<a href="' . esc_url( $logout_url ) . '" class="custom-logout-btn">로그아웃</a>';
    }
    return '';
}
add_shortcode( 'user_logout', 'custom_user_logout_shortcode' );global�
���t��,�x	w�8z?�@>h예외처리// functions.php 끝에 추가
add_action('template_redirect', function() {
    $request = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
    // 허용할 로그인 경로들 (필요하면 추가)
    $allow_paths = ['/login', '/wp-login.php'];

    foreach ($allow_paths as $p) {
        if (strpos($request, $p) === 0) {

            // wp_filter 구조가 없으면 바로 리턴
            if ( empty($GLOBALS['wp_filter']['template_redirect']) || ! is_object($GLOBALS['wp_filter']['template_redirect']) ) {
                return;
            }

            $hook = $GLOBALS['wp_filter']['template_redirect'];

            // WP 버전에 따라 callbacks가 배열 혹은 다른구조일 수 있으니 안전 검사
            if ( empty($hook->callbacks) || ! is_array($hook->callbacks) ) {
                return;
            }

            foreach ($hook->callbacks as $priority => $callbacks) {
                foreach ($callbacks as $id => $cb ) {
                    if ( empty($cb['function']) ) continue;
                    $func = $cb['function'];

                    $match = false;

                    // 객체 또는 배열(객체/클래스 + 메서드)
                    if ( is_array($func) ) {
                        $objOrClass = $func[0];
                        $method = isset($func[1]) ? $func[1] : '';
                        $class = is_object($objOrClass) ? get_class($objOrClass) : (is_string($objOrClass) ? $objOrClass : '');
                        $full = $class . '::' . $method;
                        if ( stripos($full, 'elementor') !== false ) $match = true;
                    }
                    // 함수명(문자열)
                    elseif ( is_string($func) ) {
                        if ( stripos($func, 'elementor') !== false ) $match = true;
                    }
                    // 클로저 등은 건너뜀
                    else {
                        continue;
                    }

                    if ( $match ) {
                        // priority 키가 문자열일 수 있으므로 정수로 캐스트
                        remove_action('template_redirect', $func, (int)$priority);
                    }
                }
            }

            // 일단 한 번 처리되면 반복문 탈출
            break;
        }
    }
}, 0);
global�
����iŀ� �	�h�8z?�@>z멤버십function membership_end_by_product_shortcode( $atts ) {
    if ( ! is_user_logged_in() ) return '0000. 00. 00';

    $atts = shortcode_atts( array(
        'product_id' => 0,
        'duration' => 0, // 일 단위
    ), $atts, 'membership_end_by_product' );

    $product_id = intval($atts['product_id']);
    $duration = intval($atts['duration']);

    if ( $product_id <= 0 || $duration <= 0 ) return '0000. 00. 00';

    $user_id = get_current_user_id();

    // WooCommerce 주문 검색 (모든 주문)
    $orders = wc_get_orders(array(
        'customer' => $user_id,
        'status' => 'completed',
        'orderby' => 'date',
        'order' => 'DESC',
        'limit' => -1, // 모든 주문
    ));

    if ( empty($orders) ) return '0000. 00. 00';

    $end_timestamp = 0;

    foreach ( $orders as $order ) {
        foreach ( $order->get_items() as $item ) {
            if ( $item->get_product_id() == $product_id ) {
                $completed_date = $order->get_date_completed();
                if ( $completed_date ) {
                    $ts = strtotime("+{$duration} days", $completed_date->getTimestamp());
                    // 가장 최근 주문 날짜 기준 사용
                    if ( $ts > $end_timestamp ) {
                        $end_timestamp = $ts;
                    }
                }
            }
        }
    }

    if ( $end_timestamp > 0 ) {
        return date_i18n('Y. m. d', $end_timestamp);
    }

    return '0000. 00. 00';
}
add_shortcode('membership_end_by_product', 'membership_end_by_product_shortcode');
global�
����aE�����8z?�@>�포스트 갯수 구하기/**
 * [category_post_count slug="category-slug"]
 * 해당 카테고리 안의 포스트 수를 반환
 */
function category_post_count_shortcode( $atts ) {
    $atts = shortcode_atts( array(
        'slug' => '', // 카테고리 슬러그
    ), $atts, 'category_post_count' );

    if ( empty($atts['slug']) ) return '0';

    $category = get_category_by_slug( $atts['slug'] );
    if ( ! $category ) return '0';

    return intval( $category->count );
}
add_shortcode('category_post_count', 'category_post_count_shortcode');global�
����b���pp(Kc����@\���Y�|@[�E�I,7�-	��!infimumsupremumӃ/�	�마이페이지 필드 추가add_action('woocommerce_edit_account_form', function() {
    ?>
    <p class="form-row form-row-wide">
        <label for="phone">휴대폰 번호&nbsp;<span class="required">*</span></label>
        <input type="text" name="phone" id="phone" value="" />
    </p>
    <p class="form-row form-row-wide">
        <label for="birthdate">생년월일&nbsp;<span class="required">*</span></label>
        <input type="date" name="birthdate" id="birthdate" value="" />
    </p>
    <p class="form-row form-row-wide">
        <label for="grade">학년&nbsp;<span class="required">*</span></label>
        <select name="grade" id="grade">
            <option value="">선택하세요</option>
            <option value="예비 고1">예비 고1</option>
            <option value="고1">고1</option>
            <option value="고2">고2</option>
            <option value="고3">고3</option>
			<option value="재수/N수">재수/N수</option>
        </select>
    </p>
    <?php
});global�
����[����l��글 복사 및 우클릭 방지function protect_post_content() {
    if (is_singular('post')) {
        ?>
        <script>
        // 우클릭 방지
        document.addEventListener('contextmenu', function (e) {
            e.preventDefault();
        });

        // Ctrl+C 방지
        document.addEventListener('keydown', function (e) {
            if (e.ctrlKey && e.key.toLowerCase() === 'c') {
                e.preventDefault();
            }
        });
        </script>
        <?php
    }
}
add_action('wp_footer', 'protect_post_content');
global�
����bL��;� ���사용자 아바타 숏코드// [user_profile_image] 숏코드
function user_profile_image_shortcode() {
    if ( is_user_logged_in() ) {
        $user_id = get_current_user_id();
        $profile_image_id = get_user_meta($user_id, 'profile_image', true); // Image Picker 필드 저장 키 확인
        if ( $profile_image_id ) {
            return wp_get_attachment_image( $profile_image_id, 'thumbnail', false, array(
                'class' => 'custom-user-avatar rounded-full'
            ));
        }
    }
    return '';
}
add_shortcode('user_profile_image', 'user_profile_image_shortcode');global�
����F���(Z�
�사용자 이름 숏코드// [user_name]
function custom_user_name_shortcode() {
    if ( is_user_logged_in() ) {
        $user = wp_get_current_user();
        return esc_html( $user->display_name );
    }
    return '';
}
add_shortcode( 'user_name', 'custom_user_name_shortcode' );global�
���tր�y�0Հ�로그아웃 버튼 숏코드// [user_logout]
function custom_user_logout_shortcode() {
    if ( is_user_logged_in() ) {
        $logout_url = wp_logout_url( home_url() ); // 로그아웃 후 홈으로 리다이렉트
        return '<a href="' . esc_url( $logout_url ) . '" class="custom-logout-btn">로그아웃</a>';
    }
    return '';
}
add_shortcode( 'user_logout', 'custom_user_logout_shortcode' );global�
���t��,�8	w��예외처리// functions.php 끝에 추가
add_action('template_redirect', function() {
    $request = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
    // 허용할 로그인 경로들 (필요하면 추가)
    $allow_paths = ['/login', '/wp-login.php'];

    foreach ($allow_paths as $p) {
        if (strpos($request, $p) === 0) {

            // wp_filter 구조가 없으면 바로 리턴
            if ( empty($GLOBALS['wp_filter']['template_redirect']) || ! is_object($GLOBALS['wp_filter']['template_redirect']) ) {
                return;
            }

            $hook = $GLOBALS['wp_filter']['template_redirect'];

            // WP 버전에 따라 callbacks가 배열 혹은 다른구조일 수 있으니 안전 검사
            if ( empty($hook->callbacks) || ! is_array($hook->callbacks) ) {
                return;
            }

            foreach ($hook->callbacks as $priority => $callbacks) {
                foreach ($callbacks as $id => $cb ) {
                    if ( empty($cb['function']) ) continue;
                    $func = $cb['function'];

                    $match = false;

                    // 객체 또는 배열(객체/클래스 + 메서드)
                    if ( is_array($func) ) {
                        $objOrClass = $func[0];
                        $method = isset($func[1]) ? $func[1] : '';
                        $class = is_object($objOrClass) ? get_class($objOrClass) : (is_string($objOrClass) ? $objOrClass : '');
                        $full = $class . '::' . $method;
                        if ( stripos($full, 'elementor') !== false ) $match = true;
                    }
                    // 함수명(문자열)
                    elseif ( is_string($func) ) {
                        if ( stripos($func, 'elementor') !== false ) $match = true;
                    }
                    // 클로저 등은 건너뜀
                    else {
                        continue;
                    }

                    if ( $match ) {
                        // priority 키가 문자열일 수 있으므로 정수로 캐스트
                        remove_action('template_redirect', $func, (int)$priority);
                    }
                }
            }

            // 일단 한 번 처리되면 반복문 탈출
            break;
        }
    }
}, 0);
global�
����iŀ� �	@h��멤버십function membership_end_by_product_shortcode( $atts ) {
    if ( ! is_user_logged_in() ) return '0000. 00. 00';

    $atts = shortcode_atts( array(
        'product_id' => 0,
        'duration' => 0, // 일 단위
    ), $atts, 'membership_end_by_product' );

    $product_id = intval($atts['product_id']);
    $duration = intval($atts['duration']);

    if ( $product_id <= 0 || $duration <= 0 ) return '0000. 00. 00';

    $user_id = get_current_user_id();

    // WooCommerce 주문 검색 (모든 주문)
    $orders = wc_get_orders(array(
        'customer' => $user_id,
        'status' => 'completed',
        'orderby' => 'date',
        'order' => 'DESC',
        'limit' => -1, // 모든 주문
    ));

    if ( empty($orders) ) return '0000. 00. 00';

    $end_timestamp = 0;

    foreach ( $orders as $order ) {
        foreach ( $order->get_items() as $item ) {
            if ( $item->get_product_id() == $product_id ) {
                $completed_date = $order->get_date_completed();
                if ( $completed_date ) {
                    $ts = strtotime("+{$duration} days", $completed_date->getTimestamp());
                    // 가장 최근 주문 날짜 기준 사용
                    if ( $ts > $end_timestamp ) {
                        $end_timestamp = $ts;
                    }
                }
            }
        }
    }

    if ( $end_timestamp > 0 ) {
        return date_i18n('Y. m. d', $end_timestamp);
    }

    return '0000. 00. 00';
}
add_shortcode('membership_end_by_product', 'membership_end_by_product_shortcode');
global�
����aE���Hi��포스트 갯수 구하기/**
 * [category_post_count slug="category-slug"]
 * 해당 카테고리 안의 포스트 수를 반환
 */
function category_post_count_shortcode( $atts ) {
    $atts = shortcode_atts( array(
        'slug' => '', // 카테고리 슬러그
    ), $atts, 'category_post_count' );

    if ( empty($atts['slug']) ) return '0';

    $category = get_category_by_slug( $atts['slug'] );
    if ( ! $category ) return '0';

    return intval( $category->count );
}
add_shortcode('category_post_count', 'category_post_count_shortcode');global�
����b�����P�C��스크랩버튼 액션add_action('wp_footer', function() {

    ?>

    <style>

    /* Favorite 버튼 아이콘 변경 */

    .ccc-favorite-post-toggle-button {

        display: inline-block;

        width: 40px;

        height: 40px;

        background: url('https://dodn.picell.biz/wp-content/uploads/2025/08/favorite_icon.png') no-repeat center center;

        background-size: contain;

        text-indent: -9999px; /* 텍스트 숨김 */

        border: none;

        cursor: pointer;

    }

    /* 스크랩된 상태(save) */

    .ccc-favorite-post-toggle-button.save {

        background-image: url('https://dodn.picell.biz/wp-content/uploads/2025/09/fav_ok.png') !important;

    }



    /* 레이어 팝업 */

    #favorite-modal {

        display: none;

        position: fixed;

        top: 50%;

        left: 50%;

        transform: translate(-50%, -50%);

        background: #fff;

        padding: 20px 30px;

        border-radius: 10px;

        box-shadow: 0 0 15px rgba(0,0,0,0.3);

        z-index: 9999;

        text-align: center;

        font-family: sans-serif;

    }

    #favorite-modal.show { display: block; }

    #favorite-modal .buttons {

        margin-top: 15px;

        display: flex;

        justify-content: center;

        gap: 10px;

    }

    #favorite-modal button { padding: 5px 10px; cursor: pointer; }

    </style>



    <div id="favorite-modal">

        <div class="message"></div>

        <div class="buttons">

            <button id="go-to-favorites">스크랩으로 이동</button>

            <button id="close-favorite-modal">닫기</button>

        </div>

    </div>



    <script>

    document.addEventListener('DOMContentLoaded', function() {

        const modal = document.getElementById('favorite-modal');

        const messageBox = modal.querySelector('.message');

        const closeBtn = document.getElementById('close-favorite-modal');

        const goBtn = document.getElementById('go-to-favorites');



        // 플러그인에서 Ajax로 클래스(save)를 토글하기 때문에 클릭 후 약간의 딜레이 필요

        document.body.addEventListener('click', function(e) {

            if (e.target.classList.contains('ccc-favorite-post-toggle-button')) {

                e.preventDefault();



                const btn = e.target;



                // Ajax 반영될 때까지 조금 기다렸다 확인

                setTimeout(() => {

                    if (btn.classList.contains('save')) {

                        messageBox.textContent = '스크랩 취소되었습니다.';

                        modal.classList.add('show');

                    } else {

                        messageBox.textContent = '스크랩 되었습니다.';

                        modal.classList.add('show');

                    }

                }, 500); // 0.5초 후 확인

            }

        });



        // 모달 닫기

        closeBtn.addEventListener('click', function() {

            modal.classList.remove('show');

        });



        // 스크랩 페이지 이동

        goBtn.addEventListener('click', function() {

            window.location.href = '/my-라이브러리/#my-favorite';

        });

    });

    </script>

    <?php

}, 999);global�
����ڀ�p	�c���Y@[͹ϙ�	�|@[_E�I1
�/ ��!infimumsupremumy�π�탭별 주소 활성화add_action('wp_footer', function() {
    ?>
    <script>
    (function(){
        // 해시를 변경하되 스크롤을 발생시키지 않도록 replaceState 사용
        function setHashNoScroll(hash) {
            try {
                if(history.replaceState) history.replaceState(null, null, hash);
                else location.hash = hash;
            } catch(e) { location.hash = hash; }
        }

        // id 기준으로 버튼 찾아 클릭해서 탭 활성화 시도
        function activateTabById(id) {
            if(!id) return false;
            var btn = document.getElementById(id);
            if(btn && (btn.classList.contains('e-n-tab-title') || btn.getAttribute('role') === 'tab')) {
                // 클릭 트리거 (Elementor의 내부 핸들러가 동작하도록)
                btn.click();
                return true;
            }
            return false;
        }

        // 일정 시간 동안 주기적으로 요소가 생기는지 확인하는 유틸
        function waitAndActivate(id, timeoutMs) {
            timeoutMs = timeoutMs || 5000;
            var elapsed = 0;
            var interval = setInterval(function() {
                elapsed += 200;
                if (activateTabById(id)) {
                    clearInterval(interval);
                } else if (elapsed >= timeoutMs) {
                    clearInterval(interval);
                }
            }, 200);
        }

        document.addEventListener('DOMContentLoaded', function() {
            // 1) 페이지 로드 시 URL hash가 있으면 해당 탭 활성화 시도 (동적 로드 대비 대기 포함)
            if(window.location.hash) {
                var id = window.location.hash.replace('#','');
                if(id) waitAndActivate(id, 5000);
            }

            // 2) 사용자가 탭 버튼을 클릭하면 주소창에 해시를 넣어준다 (짧은 지연을 두어 Elementors의 상태 변경 후 실행)
            document.addEventListener('click', function(e) {
                var btn = e.target.closest('.e-n-tab-title');
                if(!btn) return;
                if(btn.id) {
                    // 약간의 딜레이로 Elementors 내부 동작(탭 변경)이 먼저 일어나도록 함
                    setTimeout(function(){
                        setHashNoScroll('#' + btn.id);
                    }, 50);
                }
            }, false);

            // 3) 키보드(화살표 등)로 탭 전환될 때도 해시를 업데이트하도록 aria-selected 관찰
            var observer = new MutationObserver(function(mutations) {
                mutations.forEach(function(m) {
                    var target = m.target;
                    if(target && target.classList && target.classList.contains('e-n-tab-title')) {
                        if(target.getAttribute('aria-selected') === 'true' && target.id) {
                            if(window.location.hash !== '#' + target.id) {
                                setHashNoScroll('#' + target.id);
                            }
                        }
                    }
                });
            });

            // 탭 버튼들이 언제 로드될지 모름 -> 로드될 때까지 대기해서 observer 연결
            function attachObserversOnce() {
                var headings = document.querySelectorAll('.e-n-tabs-heading');
                if(headings.length) {
                    headings.forEach(function(h) {
                        var buttons = h.querySelectorAll('.e-n-tab-title');
                        buttons.forEach(function(b) {
                            observer.observe(b, { attributes: true, attributeFilter: ['aria-selected'] });
                        });
                    });
                } else {
                    // 아직 없으면 잠깐 후 재시도
                    setTimeout(attachObserversOnce, 300);
                }
            }
            attachObserversOnce();
        });
    })();
    </script>
    <?php
}, 999);

add_action('wp_footer', function () {
    ?>
    <script>
    document.addEventListener("DOMContentLoaded", function() {
        function activateTabByHash() {
            const hash = window.location.hash.replace("#", "");
            if (!hash) return;

            const tabButton = document.getElementById(hash);
            if (tabButton && tabButton.classList.contains("e-n-tab-title")) {
                tabButton.click(); // 탭 활성화
            }
        }

        // 페이지 로드 시 실행
        activateTabByHash();

        // 해시 변경 시 실행
        window.addEventListener("hashchange", activateTabByHash);

        // 탭 클릭 시 주소창에 hash 반영
        document.querySelectorAll(".e-n-tab-title[id]").forEach(btn => {
            btn.addEventListener("click", function() {
                history.replaceState(null, null, "#" + this.id);
            });
        });
    });
    </script>
    <?php
});
global�
����h���~�̀�공동 작성자// functions.php 또는 Code Snippets
add_shortcode('ppa_coauthor_posts', function($atts) {
    global $wpdb;

    $atts = shortcode_atts(array(
        'coauthor_term_id' => 0,
        'posts' => 10, // 한 번에 로드할 글 수
    ), $atts, 'ppa_coauthor_posts');

    $coauthor_term_id = intval($atts['coauthor_term_id']);
    $posts_per_page = intval($atts['posts']);

    if (!$coauthor_term_id) return '공동작성자 ID를 지정해주세요.';

    $paged = 1; // 처음 페이지
    $offset = ($paged - 1) * $posts_per_page;

    $rel_table = $wpdb->prefix . 'ppma_author_relationships';

    $post_ids = $wpdb->get_col($wpdb->prepare(
        "SELECT DISTINCT p.ID
         FROM {$wpdb->posts} p
         LEFT JOIN {$rel_table} r ON p.ID = r.post_id
         WHERE p.post_status = 'publish'
           AND (p.post_author = %d OR r.author_term_id = %d)
         ORDER BY p.ID DESC
         LIMIT %d OFFSET %d",
        $coauthor_term_id, $coauthor_term_id, $posts_per_page, $offset
    ));

    if (empty($post_ids)) return '<div class="premium-error-notice">
			추후 업데이트 예정입니다.		</div>';

    $html = '<div class="elementor-element elementor-widget premium-blog-wrap premium-blog-even ppa-coauthor-posts" style="flex-direction:column !important; row-gap:unset;"data-page="'.esc_attr($paged).'" data-coauthor="'.esc_attr($coauthor_term_id).'" data-posts="'.esc_attr($posts_per_page).'">';

    foreach ($post_ids as $post_id) {
        $post = get_post($post_id);
        if (!$post) continue;

        $categories = get_the_category($post_id);
        $cat_html = '';
        if (!empty($categories)) {
            $cat_html .= '<ul class="post-categories">';
            foreach ($categories as $cat) {
                $cat_html .= '<li><a href="' . get_category_link($cat->term_id) . '"style="background:#BFD2E2">' . esc_html($cat->name) . '</a></li>';
            }
            $cat_html .= '</ul>';
        }

        $thumb_url = get_the_post_thumbnail_url($post_id, 'medium');
        $thumb_html = $thumb_url ? '<div class="premium-blog-thumb"><img src="'.esc_url($thumb_url).'" alt="'.esc_attr(get_the_title($post_id)).'"></div>' : '';

        $html .= '<div class="premium-blog-post-outer-container">
            <div class="premium-blog-post-container premium-blog-skin-side">
                <div class="premium-blog-content-wrapper" style="padding:0px; margin:20px 0px; background-color:unset;">
                    <div class="premium-blog-inner-container">
                        <div class="premium-blog-cats-container">' . $cat_html . '</div>
                        <h2 class="premium-blog-entry-title">
                            <a href="' . get_permalink($post_id) . '" style="font-weight:500; color:#262626">' . get_the_title($post_id) . '</a>
                        </h2>
                        <div class="premium-blog-entry-meta"></div>
                    </div>
                </div>
            </div>
        </div>';
    }

    $html .= '</div>';

    // 무한스크롤 JS
    $html .= '
    <script>
    jQuery(document).ready(function($){
        var loading = false;
        var container = $(".ppa-coauthor-posts");

        $(window).scroll(function(){
            if(loading) return;
            if($(window).scrollTop() + $(window).height() >= container.offset().top + container.outerHeight() - 200){
                loading = true;
                var page = parseInt(container.data("page")) + 1;
                var coauthor = container.data("coauthor");
                var posts = container.data("posts");

                $.ajax({
                    url: "'.admin_url('admin-ajax.php').'",
                    type: "GET",
                    data: {
                        action: "ppa_load_more",
                        coauthor: coauthor,
                        posts: posts,
                        page: page
                    },
                    success: function(response){
                        if(response){
                            container.append(response);
                            container.data("page", page);
                            loading = false;
                        }
                    }
                });
            }
        });
    });
    </script>';

    return $html;
});

// Ajax handler
add_action('wp_ajax_nopriv_ppa_load_more', 'ppa_load_more_ajax');
add_action('wp_ajax_ppa_load_more', 'ppa_load_more_ajax');
function ppa_load_more_ajax() {
    global $wpdb;

    $coauthor_term_id = intval($_GET['coauthor']);
    $posts_per_page = intval($_GET['posts']);
    $paged = intval($_GET['page']);
    $offset = ($paged - 1) * $posts_per_page;

    $rel_table = $wpdb->prefix . 'ppma_author_relationships';

    $post_ids = $wpdb->get_col($wpdb->prepare(
        "SELECT DISTINCT p.ID
         FROM {$wpdb->posts} p
         LEFT JOIN {$rel_table} r ON p.ID = r.post_id
         WHERE p.post_status = 'publish'
           AND (p.post_author = %d OR r.author_term_id = %d)
         ORDER BY p.ID DESC
         LIMIT %d OFFSET %d",
        $coauthor_term_id, $coauthor_term_id, $posts_per_page, $offset
    ));

    if(!empty($post_ids)){
        foreach($post_ids as $post_id){
            $categories = get_the_category($post_id);
            $cat_html = '';
            if(!empty($categories)){
                $cat_html .= '<ul class="post-categories">';
                foreach($categories as $cat){
                   $cat_html .= '<li><a href="' . get_category_link($cat->term_id) . '"style="background:#BFD2E2">' . esc_html($cat->name) . '</a></li>';
                }
                $cat_html .= '</ul>';
            }
            $thumb_url = get_the_post_thumbnail_url($post_id, 'medium');
            $thumb_html = $thumb_url ? '<div class="premium-blog-thumb"><img src="'.esc_url($thumb_url).'" alt="'.esc_attr(get_the_title($post_id)).'"></div>' : '';

            echo '<div class="premium-blog-post-outer-container">
                    <div class="premium-blog-post-container premium-blog-skin-side">
                         <div class="premium-blog-content-wrapper" style="padding:0px; margin:20px 0px; background-color:unset;">
                    <div class="premium-blog-inner-container">
                        <div class="premium-blog-cats-container">' . $cat_html . '</div>
                        <h2 class="premium-blog-entry-title">
                            <a href="' . get_permalink($post_id) . '" style="font-weight:500; color:#262626">' . get_the_title($post_id) . '</a>
                        </h2>
                        <div class="premium-blog-entry-meta"></div>
                    </div>
                </div>
                    </div>
                  </div>';
        }
    }

    wp_die();
}
global�
����L���� �P��testadd_action('wp_footer', function() {
    global $wpdb;
    $coauthor_term_id = 72; // 팀 지인선 author_term_id
    $rel_table = $wpdb->prefix . 'ppma_author_relationships';

    // author_term_id 기준으로 조회
    $post_ids = $wpdb->get_col( $wpdb->prepare(
        "SELECT post_id FROM {$rel_table} WHERE author_term_id = %d",
        $coauthor_term_id
    ));

    echo '<pre>';
    var_dump($post_ids);
    echo '</pre>';
});global�
�������pc�ϙ�@[_C��	
�|@[E�I)�!���!infimumsupremumI�
���주소 변경add_shortcode('custom_mshop_address_form', function() {
    if ( ! is_user_logged_in() ) {
        return '<p>로그인 후 이용 가능합니다.</p>';
    }

    $user_id = get_current_user_id();

    // 현재 저장된 값 불러오기
    $fields = array(
        'billing_first_name_kr'      => get_user_meta($user_id, 'billing_first_name_kr', true),
        'mshop_billing_address-postnum' => get_user_meta($user_id, 'mshop_billing_address-postnum', true),
        'mshop_billing_address-addr1'   => get_user_meta($user_id, 'mshop_billing_address-addr1', true),
        'mshop_billing_address-addr2'   => get_user_meta($user_id, 'mshop_billing_address-addr2', true),
        'billing_email_kr'           => get_user_meta($user_id, 'billing_email_kr', true),
        'billing_phone_kr'           => get_user_meta($user_id, 'billing_phone_kr', true),
    );

    ob_start(); ?>
    <form method="post">
        <p>
            <label for="billing_first_name_kr">이름</label>
            <input type="text" name="billing_first_name_kr" value="<?php echo esc_attr($fields['billing_first_name_kr']); ?>">
        </p>
        <p>
            <label for="mshop_billing_address-postnum">우편번호</label>
            <input type="text" name="mshop_billing_address-postnum" value="<?php echo esc_attr($fields['mshop_billing_address-postnum']); ?>" readonly>
            <a href="#ms_addr_1" class="ms_addr_1 ms-open-popup-link" data-id="mshop_billing_address">주소 검색</a>
        </p>
        <p>
            <label for="mshop_billing_address-addr1">기본주소</label>
            <input type="text" name="mshop_billing_address-addr1" value="<?php echo esc_attr($fields['mshop_billing_address-addr1']); ?>" readonly>
        </p>
        <p>
            <label for="mshop_billing_address-addr2">상세주소</label>
            <input type="text" name="mshop_billing_address-addr2" value="<?php echo esc_attr($fields['mshop_billing_address-addr2']); ?>">
        </p>
        <p>
            <label for="billing_email_kr">이메일</label>
            <input type="email" name="billing_email_kr" value="<?php echo esc_attr($fields['billing_email_kr']); ?>">
        </p>
        <p>
            <label for="billing_phone_kr">전화번호</label>
            <input type="text" name="billing_phone_kr" value="<?php echo esc_attr($fields['billing_phone_kr']); ?>">
        </p>

        <p><button type="submit" name="save_mshop_address">저장하기</button></p>
    </form>
    <?php
    return ob_get_clean();
});

// 저장 처리
add_action('init', function() {
    if (isset($_POST['save_mshop_address'])) {
        $user_id = get_current_user_id();
        if (!$user_id) return;

        $keys = [
            'billing_first_name_kr',
            'mshop_billing_address-postnum',
            'mshop_billing_address-addr1',
            'mshop_billing_address-addr2',
            'billing_email_kr',
            'billing_phone_kr',
        ];

        foreach ($keys as $key) {
            if (isset($_POST[$key])) {
                update_user_meta($user_id, $key, sanitize_text_field($_POST[$key]));
            }
        }
    }
});
add_action('wp_enqueue_scripts', function() {
    if ( is_page('my-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC') ) { 
        wp_enqueue_script('mshop-address-js', plugins_url('mshop-address/mshop-address.js'), ['jquery'], null, true);
        wp_enqueue_style('mshop-address-css', plugins_url('mshop-address/mshop-address.css'));

        // 엘리멘터 탭에서 팝업 버튼 바인딩
        wp_add_inline_script('mshop-address-js', "
            jQuery(document).ready(function($){
                function bindMShopPopup(){
                    $('.ms-open-popup-link').each(function(){
                        var \$btn = $(this);
                        if (!\$btn.data('mshop-bound')) {
                            \$btn.on('click', function(e){
                                e.preventDefault();
                                var id = $(this).data('id');
                                if (typeof window['ms_addr_'+id+'_open'] === 'function') {
                                    window['ms_addr_'+id+'_open']();
                                }
                            });
                            \$btn.data('mshop-bound', true);
                        }
                    });
                }

                bindMShopPopup();

                // 엘리멘터 탭 클릭 시 재바인딩
                $(document).on('click', '.e-n-tab-title', function(){
                    setTimeout(bindMShopPopup, 100); // DOM 반영 후 실행
                });
            });
        ");
    }
});
global�
����(x����"��NAVER 회원가입(sign_up) SCRIPTadd_action('wp_footer', 'my_tracking_code_on_join_page');
function my_tracking_code_on_join_page() {
    // 현재 페이지가 /join 인지 확인
    if (is_page('join')) {
        ?>
		<!-- NAVER 회원가입(sign_up) SCRIPT -->
		<script type='text/javascript' src='//wcs.naver.net/wcslog.js'></script>
		<script type='text/javascript'>
			if(window.wcs){
			if(!wcs_add) var wcs_add = {};
			wcs_add['wa'] = 's_2e8b4f000568';
			var _conv = {};
				_conv.type = 'sign_up';    	
			wcs.trans(_conv);
			}
            console.log("회원가입 페이지 추적 코드 실행됨");
        </script>
        <!-- 🔥 회원가입 추적 코드 끝 -->
        <?php
    }
}global�
����.����' ��NAVER 상품상세(view_product) SCRIPTadd_action('wp_footer', 'add_naver_view_product_tracking_code');
function add_naver_view_product_tracking_code() {
    if ( !is_product() ) return; // 상품 상세 페이지에서만 실행

    global $product;

    if ( !$product ) return;

    // 기본 정보 추출
    $product_id   = $product->get_id();
    $product_name = $product->get_name();
    $categories   = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'names'));
    $category     = !empty($categories) ? implode(', ', $categories) : '';
    $price        = $product->get_price(); // 단가
    $quantity     = 1; // 상품 상세페이지에서는 기본 1개로 표시
    $option       = '';

    // 변수 상품일 경우 첫 번째 옵션명 가져오기
    if ($product->is_type('variable')) {
        $available_variations = $product->get_available_variations();
        if (!empty($available_variations)) {
            $variation = reset($available_variations);
            $option = implode(', ', array_values($variation['attributes']));
        }
    }

    ?>
    <script type='text/javascript'>
    if(window.wcs){
        if(!wcs_add) var wcs_add = {};
        wcs_add["wa"] = "s_2e8b4f000568"; 
        var _conv = {};
        _conv.type = 'view_product'; //(필수)
        _conv.items = [{
            id: "<?php echo esc_js($product_id); ?>",
            name: "<?php echo esc_js($product_name); ?>",
            category: "<?php echo esc_js($category); ?>",
            quantity: "<?php echo esc_js($quantity); ?>",
            payAmount: "<?php echo esc_js($price); ?>",
            option: "<?php echo esc_js($option); ?>"
        }];
        wcs.trans(_conv);
    }
    </script>
    <?php
}
global�
����0I���#(d��구매완료 dataLayer 스크립트add_action( 'woocommerce_thankyou', 'add_purchase_datalayer', 10, 1 );

function add_purchase_datalayer( $order_id ) {
  if ( !$order_id ) return;
  
  $order = wc_get_order( $order_id );
  
  if ( ! $order ) return;

  ?>
  <script>
  window.dataLayer = window.dataLayer || [];
  dataLayer.push({
    event: "purchase",
    transaction_id: "<?php echo esc_js( $order->get_id() ); ?>",
    value: <?php echo esc_js( $order->get_total() ); ?>,
    currency: "KRW",
    items: <?php
      $items_data = [];
      foreach ( $order->get_items() as $item ) {
        $product = $item->get_product();
        
        $unit_price = $item->get_total() / $item->get_quantity();  

        $items_data[] = [
          'item_id'   => $product ? esc_js( $product->get_id() ) : '', 
          'item_name' => esc_js( $item->get_name() ),
          'price'     => esc_js( $unit_price ), 
          'quantity'  => esc_js( $item->get_quantity() )
        ];
      }
      echo wp_json_encode( $items_data );
    ?>
  });
  </script>
  <?php
}global�
����$��*�0���로그인 세션 타임 30분/**
 * Plugin Name: Mshop Membership Smart Session Extend
 * Description: 엠샵 멤버스 로그인 세션 30분 + 활동 감지 시 자동 연장
 */

// 로그인 세션 기본 30분
add_filter( 'auth_cookie_expiration', function( $expiration, $user_id, $remember ) {
    return 60 * 30;
}, 10, 3 );

// AJAX 요청 처리
add_action( 'wp_ajax_extend_session', 'msm_smart_extend_user_session' );
add_action( 'wp_ajax_nopriv_extend_session', 'msm_smart_extend_user_session' );

function msm_smart_extend_user_session() {
    if ( is_user_logged_in() ) {
        wp_set_auth_cookie(get_current_user_id(), false); // 세션 갱신
    }
    wp_send_json_success();
}

// JS 삽입 (활동 감지 + 서버 부담 최소화)
add_action( 'wp_footer', function() {
    if ( is_user_logged_in() ) : ?>
    <script>
    (function() {
        let lastActivity = Date.now();
        let lastPing = 0;
        const pingInterval = 5 * 60 * 1000; // 5분
        const minIdleTime = 1 * 60 * 1000; // 최소 1분 이상 활동 없으면 ping 안 보냄

        // 활동 이벤트 등록 (데스크톱+모바일)
        const activityEvents = ['mousemove','mousedown','keydown','scroll','touchstart','touchmove'];
        activityEvents.forEach(evt => {
            document.addEventListener(evt, () => { lastActivity = Date.now(); }, false);
        });

        // 주기적 ping
        setInterval(() => {
            const now = Date.now();
            if ( (now - lastActivity) < pingInterval && (now - lastPing) > minIdleTime ) {
                fetch('<?php echo admin_url("admin-ajax.php"); ?>?action=extend_session', {
                    method: 'POST',
                    credentials: 'same-origin'
                });
                lastPing = now;
            }
        }, 1000); 
    })();
    </script>
    <?php endif;
});
global�
���Ҁ�pcC��@[��A
	�|@Z�E�I:ʀ
6
��!infimumsupremum��I��Hero Section
/**
 * Hero Section Slider Sync
 * Elementor Swiper와 m_hero1, m_hero2 이미지 컨테이너 동기화
 * 
 * Code Snippets 플러그인에 추가
 * 
 * 설정:
 * - Autoplay Speed: 5000ms
 * - Transition Speed: 500ms
 * - Fade Effect
 */

add_action('wp_head', 'hero_sync_styles');
function hero_sync_styles() {
?>
<style id="hero-sync-styles">
/* Hero Container Fade Transitions */
#hero_section #m_hero1,
#hero_section #m_hero2 {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    visibility: hidden;
    transition: opacity 500ms ease-in-out, visibility 500ms ease-in-out;
    z-index: 1;
}

#hero_section #m_hero1.hero-active,
#hero_section #m_hero2.hero-active {
    opacity: 1;
    visibility: visible;
    z-index: 2;
}

#hero_section #m_hero1.hero-inactive,
#hero_section #m_hero2.hero-inactive {
    opacity: 0;
    visibility: hidden;
    z-index: 1;
}

/* 좌측 이미지 컨테이너 - position relative */
#hero_section [data-id="5ada9ff"] > .e-con-inner {
    position: relative;
}

/* 우측 슬라이더 컨테이너 - position relative for dots */
#hero_section [data-id="9043873"] {
    position: relative;
}

/* Custom Dot Navigation (hero2 스타일) - 슬라이더 하단 */
.hero-custom-dots {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding-top: 24px;
    z-index: 10;
}

.hero-dot {
    width: 36px;
    height: 4px;
    background-color: #4A4A4A;
    cursor: pointer;
    transition: background-color 300ms ease;
}

.hero-dot:hover {
    background-color: #666666;
}

.hero-dot--active {
    background-color: #F3F3F3;
}

/* Elementor 기본 pagination 숨기기 */
#hero_section .swiper-pagination {
    display: none !important;
}

/* Hero Text Styles */
#hero_section .hero__info,
#hero_section .hero2__info {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 24px;
    padding: 40px 18px;
    text-align: center;
    width: 100%;
}

#hero_section .hero__tag,
#hero_section .hero2__tag {
    font-family: "Noto Serif KR", serif;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.6;
    color: #FFC84A;
}

#hero_section .hero__title,
#hero_section .hero2__title {
    font-family: "Noto Serif KR", serif;
    font-size: 32px;
    font-weight: 700;
    line-height: 1.3;
    letter-spacing: 0;
    color: #FFFFFF !important;
    margin: 0;
}

#hero_section .hero__description,
#hero_section .hero2__description {
    font-family: "Noto Serif KR", serif;
    font-size: 14px!important;
    font-weight: 400;
    line-height: 1.6;
    color: #ffffff !important;
    text-align: center;
    margin-bottom: 0px;
}

/* Mobile Responsive - 모바일에서 겹침 효과 */
@media (max-width: 767px) {
    /* 모바일: 좌측 이미지 슬라이더 위에 텍스트 슬라이더 겹침 */
    #hero_section {
        position: relative;
    }
    
    /* 좌측 이미지 컨테이너 - 전체 영역 */
    #hero_section [data-id="5ada9ff"] {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        z-index: 1;
    }
    
    /* 우측 슬라이더 컨테이너 - 위에 겹침 */
    #hero_section [data-id="9043873"] {
        position: relative;
        z-index: 2;
        background: rgba(34, 34, 34, 0.7);
    }

    #hero_section .hero__info,
    #hero_section .hero2__info {
        padding: 40px 0px;
    }

    #hero_section .hero__title,
    #hero_section .hero2__title {
        font-size: 24px;
    }

    .hero-custom-dots {
        padding-top: 20px;
    }

    .hero-dot {
        width: 24px;
        height: 3px;
    }
}
</style>
<?php
}

add_action('wp_footer', 'hero_sync_script');
function hero_sync_script() {
?>
<script id="hero-sync-script">
(function() {
  'use strict';

  document.addEventListener('DOMContentLoaded', function() {
    initHeroSync();
  });

  window.addEventListener('elementor/frontend/init', function() {
    setTimeout(initHeroSync, 100);
  });

  function initHeroSync() {
    const heroSection = document.getElementById('hero_section');
    if (!heroSection) return;

    const hero1 = document.getElementById('m_hero1');
    const hero2 = document.getElementById('m_hero2');
    
    if (!hero1 || !hero2) return;

    const swiperElement = heroSection.querySelector('.elementor-main-swiper');
    if (!swiperElement || !swiperElement.swiper) {
      setTimeout(initHeroSync, 500);
      return;
    }

    const swiper = swiperElement.swiper;
    console.log('Hero Sync initialized');

    // 초기 상태 설정
    setActiveHero(0);

    // Swiper 슬라이드 변경 이벤트
    swiper.on('slideChange', function() {
      setActiveHero(swiper.realIndex);
    });

    // 커스텀 dot 네비게이션 생성 (슬라이더 하단에 배치)
    createCustomDots(heroSection, swiper);

    function setActiveHero(index) {
      const heroes = [hero1, hero2];
      
      heroes.forEach((hero, i) => {
        if (i === index) {
          hero.classList.add('hero-active');
          hero.classList.remove('hero-inactive');
        } else {
          hero.classList.remove('hero-active');
          hero.classList.add('hero-inactive');
        }
      });
    }

    function createCustomDots(container, swiperInstance) {
      const existingDots = container.querySelector('.hero-custom-dots');
      if (existingDots) existingDots.remove();

      const dotsContainer = document.createElement('div');
      dotsContainer.className = 'hero-custom-dots';

      // 2개의 슬라이드 (hero1, hero2)
      for (let i = 0; i < 2; i++) {
        const dot = document.createElement('span');
        dot.className = 'hero-dot';
        if (i === 0) dot.classList.add('hero-dot--active');
        
        dot.addEventListener('click', function() {
          swiperInstance.slideToLoop(i);
        });
        
        dotsContainer.appendChild(dot);
      }

      // 우측 슬라이더 컨테이너(9043873) 하단에 dots 추가
      const sliderContainer = container.querySelector('[data-id="9043873"]');
      if (sliderContainer) {
        sliderContainer.appendChild(dotsContainer);
      }

      // 슬라이드 변경 시 dot 업데이트
      swiperInstance.on('slideChange', function() {
        const dots = dotsContainer.querySelectorAll('.hero-dot');
        dots.forEach((dot, i) => {
          if (i === swiperInstance.realIndex) {
            dot.classList.add('hero-dot--active');
          } else {
            dot.classList.remove('hero-dot--active');
          }
        });
      });
    }
  }
})();
</script>
<?php
}
global�
���8׀�?�	���Jet Price
// 이 코드가 functions.php에 있어야 함
add_filter('jet-engine/listings/allowed-callbacks', function($callbacks) {
    $callbacks['dodn_discount_percent'] = 'DODN: 할인율';
    $callbacks['dodn_sale_price'] = 'DODN: 할인가격';
    $callbacks['dodn_regular_price'] = 'DODN: 정가';
    return $callbacks;
});

function dodn_discount_percent($result = null) {
    global $product;
    if (!$product) $product = wc_get_product(get_the_ID());
    if (!$product || !$product->is_on_sale()) return '';
    
    $regular = (float) $product->get_regular_price();
    $sale = (float) $product->get_sale_price();
    if ($regular <= 0) return '';
    
    return '-' . round((($regular - $sale) / $regular) * 100) . '%';
}

function dodn_sale_price($result = null) {
    global $product;
    if (!$product) $product = wc_get_product(get_the_ID());
    if (!$product || !$product->is_on_sale()) return '';
    
    return '₩' . number_format((float) $product->get_sale_price());
}

function dodn_regular_price($result = null) {
    global $product;
    if (!$product) $product = wc_get_product(get_the_ID());
    if (!$product) return '';
    
    $formatted = '₩' . number_format((float) $product->get_regular_price());
    if ($product->is_on_sale()) {
        return '<del>' . $formatted . '</del>';
    }
    return $formatted;
}global�
��������L� ���제품 개요 수정// 짧은 설명 - 태그 제거, 2행 제한용
add_filter('jet-engine/listings/allowed-callbacks', function($callbacks) {
    $callbacks['dodn_clean_excerpt'] = 'DODN: 짧은 설명 (태그 제거)';
    return $callbacks;
}, 20);

function dodn_clean_excerpt($result = null) {
    global $product;
    if (!$product) $product = wc_get_product(get_the_ID());
    if (!$product) return '';
    
    $short_desc = $product->get_short_description();
    
    // HTML 태그 제거
    $text = strip_tags($short_desc);
    
    // 줄바꿈, 탭 등을 공백으로 변환
    $text = preg_replace('/\s+/', ' ', $text);
    
    // 앞뒤 공백 제거
    $text = trim($text);
    
    // 글자수 제한 (선택사항 - 약 80자)
    if (mb_strlen($text) > 80) {
        $text = mb_substr($text, 0, 80) . '...';
    }
    
    return $text;
}global�
���������(��Mypage Icon Linkadd_action('wp_footer', function () {
    ?>
    <script>
    document.addEventListener('DOMContentLoaded', function () {
        const icon = document.querySelector('.ct-header-account .ct-icon');
        if (!icon) return;

        const targetUrl = 'https://dodn.picell.biz/my-라이브러리/';

        icon.style.cursor = 'pointer';

        icon.addEventListener('click', function (e) {
            // 드롭다운 hover/focus 동작은 유지
            // click 이벤트만 링크 이동으로 처리
            e.preventDefault();
            e.stopPropagation();
            window.location.href = targetUrl;
        });
    });
    </script>
    <?php
});
global�
����U���6�0�� �Call Back User Posts Countadd_shortcode( 'writer_post_count', function() {
    // 1. JetEngine이 현재 처리 중인 리스팅 아이템(User 객체)을 강제로 가져옵니다.
    $current_object = jet_engine()->listings->data->get_current_object();
    
    // 객체가 없거나 ID가 없으면 '0' 리턴 (안전장치)
    if ( ! $current_object || ! isset( $current_object->ID ) ) {
        return 'Article 0';
    }

    $user_id = $current_object->ID;
    
    // 2. 합산할 포스트 타입 설정
    $post_types = ['post', 'product']; 
    $total = 0;

    foreach ( $post_types as $pt ) {
        // 공개된 글만 카운트
        $total += count_user_posts( $user_id, $pt, true );
    }

    // 3. 최종 출력 포맷 (여기서 Article 글자를 붙여서 내보냅니다)
    return sprintf( 'Article %s', $total );
} );global�
���1ƀ��8<�!�Pagenation Conditonadd_shortcode( 'current_query_max_pages', function() {
    global $wp_query;
    // 페이지 정보가 없으면 1, 있으면 해당 숫자 리턴
    return isset( $wp_query->max_num_pages ) ? (int)$wp_query->max_num_pages : 1;
} );global�
���Fـ�%�@��"�장바구니 & PDF 구매하기/**
 * 단일상품 버튼 텍스트 변경
 * - 다운로드 상품 : PDF 구매하기
 * - 그 외 상품   : 바로 구매하기
 */
add_filter( 'woocommerce_product_single_add_to_cart_text', function( $text, $product ) {

    // product 객체 안전 처리
    if ( ! $product ) {
        global $product;
    }

    if ( ! $product ) {
        return $text;
    }

    // 다운로드 상품
    if ( $product->is_downloadable() ) {
        return 'PDF 구매하기';
    }

    // 일반 상품
    return '바로 구매하기';

}, 10, 2 );
global�
��������H>�#�Recent Read Article/**
 * JetEngine Macro: Read Posts IDs
 * returns: "123,456,789"
 */
add_filter( 'jet-engine/macros-list', function ( $macros ) {

    $macros['px_read_posts_ids'] = [
        'label'    => '읽은 글 ID 목록',
        'callback' => function () {

            if ( ! is_user_logged_in() ) {
                return '';
            }

            $read = get_user_meta( get_current_user_id(), 'px_read_posts', true );

            if ( ! is_array( $read ) || empty( $read ) ) {
                return '';
            }

            // 최대 30개
            $read = array_slice( $read, 0, 30 );

            // "1,2,3" 형태로 반환
            return implode( ',', array_map( 'intval', $read ) );
        },
    ];

    return $macros;
} );
global�
���0��;�P��$�Read Article Setting Field/**
 * 읽은 글 추적 - 로그인 사용자
 * user_meta: px_read_posts = [post_id, post_id, ...]
 */
add_action( 'wp', function () {

    if ( ! is_user_logged_in() ) return;
    if ( ! is_singular( 'post' ) ) return; // 아티클 CPT면 변경

    $user_id = get_current_user_id();
    $post_id = get_queried_object_id();

    if ( ! $post_id ) return;

    $key  = 'px_read_posts';
    $read = get_user_meta( $user_id, $key, true );

    if ( ! is_array( $read ) ) {
        $read = [];
    }

    // 이미 기록된 글이면 패스
    if ( in_array( $post_id, $read, true ) ) return;

    // 최신 읽은 글을 앞에 추가
    array_unshift( $read, $post_id );

    // 최대 100개까지만 유지 (원하면 조절)
    $read = array_slice( $read, 0, 100 );

    update_user_meta( $user_id, $key, $read );
});
global�
���,V��ȀX�%�tempadd_action( 'wp_footer', function () {
    if ( ! is_user_logged_in() ) return;

    echo '<pre>';
    print_r( get_user_meta( get_current_user_id(), 'px_read_posts', true ) );
    echo '</pre>';
});
global�
���,���p�`�d�&�Listing Grid in Favorite id// Query ID가 'my_scrap_list'인 리스팅 그리드에만 작동
add_filter( 'jet-engine/listing/grid/posts-query-args', function( $args, $widget ) {

    // 1. 위젯에 설정한 Query ID가 맞는지 확인
    if ( 'my_scrap_list' !== $widget->get_settings( 'query_id' ) ) {
        return $args;
    }

    // 2. 비로그인이면 빈 배열 반환 (결과 없음)
    if ( ! is_user_logged_in() ) {
        $args['post__in'] = array(0); 
        return $args;
    }

    // 3. 유저 메타에서 스크랩 ID 가져오기
    $user_id = get_current_user_id();
    $scrap_ids = get_user_meta( $user_id, 'ccc_my_favorite_post_ids', true );

    // 4. 데이터가 있으면 쿼리에 주입 (문자열이든 배열이든 처리)
    if ( ! empty( $scrap_ids ) ) {
        // 만약 문자열("10,20")로 되어 있다면 배열로 변환
        if ( is_string( $scrap_ids ) ) {
            $scrap_ids = explode( ',', $scrap_ids );
        }
        $args['post__in'] = $scrap_ids;
    } else {
        // 스크랩한 게 없으면 결과 없음 처리
        $args['post__in'] = array(0);
    }

    return $args;
}, 10, 2 );global�
���A��p#�c��A@Z����

�|@YkE�I*z�	&]��!infimumsupremum�
=�'�Order List
/**
 * [DODN] 주문내역 리스트 (원본 디자인 싱크로율 100%)
 */

// 1. 숏코드 등록
add_shortcode( 'my_order_history', 'dodn_list_style_final' );

function dodn_list_style_final( $atts ) {
    wp_enqueue_script('jquery');

    // 비로그인 처리
    if ( ! is_user_logged_in() ) return '<div class="dodn-msg">로그인이 필요합니다.</div>';

    $atts = shortcode_atts( array( 'count' => 10 ), $atts );
    $user_id = get_current_user_id();

    // 주문 조회 (HPOS 호환)
    $customer_orders = wc_get_orders( array(
        'limit'    => $atts['count'],
        'customer' => $user_id,
        'status'   => 'any',
        'orderby'  => 'date',
        'order'    => 'DESC',
    ) );

    ob_start();
    ?>
    
    <div class="page-wrap">
  
        <?php if ( ! $customer_orders ) : ?>
            <div class="dodn-msg">주문 내역이 없습니다.</div>
        <?php else : ?>
            <table class="data-table">
                <thead>
                    <tr>
                        <th class="th-id">주문번호</th>
                        <th class="th-date">날짜</th>
                        <th class="th-status">상태</th>
                        <th class="th-product">상품</th>
                        <th class="th-total">총 금액</th>
                        <th class="th-action">상세</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ( $customer_orders as $order ) : 
                        $order_id = $order->get_id();
                        
                        // 상품명 요약
                        $items = $order->get_items();
                        $product_names = array();
                        foreach ( $items as $item ) { $product_names[] = $item->get_name(); }
                        $product_str = implode( ', ', $product_names );
                        if ( mb_strlen($product_str) > 30 ) $product_str = mb_substr($product_str, 0, 30) . '...';
                        
                        // 날짜 포맷 (예: 2026년 1월 4일)
                        $date_str = $order->get_date_created()->date_i18n( 'Y년 n월 j일' );
                        ?>
                        <tr>
                            <td data-label="주문번호">#<?php echo $order->get_order_number(); ?></td>
                            <td data-label="날짜"><?php echo $date_str; ?></td>
                            <td data-label="상태"><?php echo esc_html( wc_get_order_status_name( $order->get_status() ) ); ?></td>
                            <td data-label="상품" class="td-product"><?php echo esc_html( $product_str ); ?></td>
                            <td data-label="총 금액"><?php echo $order->get_formatted_order_total(); ?></td>
                            <td data-label="상세">
                                <a href="#" class="btn-detail-view" data-order-id="<?php echo $order_id; ?>">상세보기</a>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        <?php endif; ?>

        <div id="dodnModal" class="dodn-modal-overlay" style="display:none;">
            <div class="dodn-modal-box">
                <div class="dodn-modal-header">
                    <h3>주문 상세 정보</h3>
                    <button type="button" class="dodn-close-btn">&times;</button>
                </div>
                <div id="dodnModalBody" class="dodn-modal-body"></div>
            </div>
        </div>
    </div>
    <?php
    return ob_get_clean();
}

// 2. AJAX 핸들러 (우커머스 표준 로드)
add_action( 'wp_ajax_dodn_load_detail', 'dodn_load_detail_func' );
add_action( 'wp_ajax_nopriv_dodn_load_detail', 'dodn_load_detail_func' );

function dodn_load_detail_func() {
    $order_id = isset($_POST['order_id']) ? intval($_POST['order_id']) : 0;
    $order = wc_get_order($order_id);
    if ( ! $order ) wp_send_json_error( '주문 없음' );
    if ( ! current_user_can('manage_options') && $order->get_user_id() != get_current_user_id() ) wp_send_json_error( '권한 없음' );

    ob_start();
    ?>
    <div class="woocommerce-order">
        <p class="order-intro">
            <?php printf( esc_html__( '주문 #%1$s (%2$s)', 'woocommerce' ), $order->get_order_number(), wc_get_order_status_name( $order->get_status() ) ); ?>
        </p>
        <?php 
        if ( $order->has_downloadable_item() && $order->is_download_permitted() ) {
            wc_get_template( 'order/order-downloads.php', array( 'downloads' => $order->get_downloadable_items(), 'show_title' => true ) );
        }
        wc_get_template( 'order/order-details.php', array( 'order_id' => $order_id ) ); 
        ?>
    </div>
    <?php
    wp_send_json_success( ob_get_clean() );
}global�
���X)���
L�(�Not logged in
/**
 * [DODN] 비회원 콘텐츠 제한 (엘리멘터 레이아웃 보호 버전)
 * - 내용을 자르지 않고 CSS로 가리는 방식 (HTML 깨짐 방지)
 */
add_filter( 'the_content', 'dodn_elementor_safe_gate', 999 );

function dodn_elementor_safe_gate( $content ) {
    // 1. 로그인 회원 및 관리자는 원본 그대로 출력
    if ( is_user_logged_in() ) {
        return $content;
    }

    // 2. 아티클 상세 페이지만 적용 (포스트 타입 확인: 'post' 또는 사용자 정의 타입)
    if ( ! is_singular( 'post' ) ) { 
        return $content;
    }

    // 3. 안내 메시지 (로그인 유도 박스)
    $cta_html = '
    <div class="dodn-cta-wrapper">
        <h3 class="cta-title">다음 내용이 궁금하신가요?<br>두든 멤버가 되어 모든 콘텐츠를 읽어보세요</h3>
        <div class="cta-benefits">
            매주 업데이트 되는 아티클<br/>
            30+개의 과목별 인사이트와 입시 뉴스<br/>
            두든 멤버만을 위한 오픈채팅방 초대<br/>
           and more...
        </div>
        <a href="/login" class="cta-login-btn">로그인하고 이어서 읽기</a>
    </div>
    ';

    // 4. 구조 조립
    // 원본 컨텐츠($content)를 수정하지 않고 래퍼로 감싸기만 함 -> 레이아웃 보존
    $gate_layout = '
        <div class="dodn-content-mask">
            ' . $content . '
            <div class="dodn-gradient-overlay"></div>
        </div>
        ' . $cta_html;

    return $gate_layout;
}global�
����C���� Հ)�footer menu shortcodefunction dodn_footer1_shortcode() {
    // 테스트용: 메뉴가 없으면 경고 메시지 출력
    if ( ! has_nav_menu( 'footer1' ) ) {
        return '<p style="color:red;">[경고] 메뉴가 할당되지 않았습니다. 외모 > 메뉴 > 위치 관리에서 설정해주세요.</p>';
    }

    return wp_nav_menu( array(
        'theme_location' => 'footer1',
        'container'      => 'nav',
        'container_class'=> 'dodn-footer-nav footer-1-area',
        'menu_class'     => 'dodn-menu-list menu-1',
        'echo'           => false,
        'depth'          => 1,
        'fallback_cb'    => '__return_false',
    ) );
}global�
�������V�(��*�Visual Biography/**
 * 사용자 프로필 '자기소개' 필드를 비주얼 에디터로 교체
 */
function custom_user_bio_visual_editor( $user ) {
    ?>
    <style>
        tr.user-description-wrap { display: none !important; }
    </style>

    <table class="form-table">
        <tr>
            <th><label for="description">자기소개 (에디터)</label></th>
            <td>
                <?php
                // wp_editor 설정
                $settings = array(
                    'textarea_name' => 'description', // 저장 시 description 필드로 인식되게 함
                    'textarea_rows' => 10,            // 에디터 높이
                    'media_buttons' => true,          // 미디어 추가 버튼 표시 여부 (필요 없으면 false)
                    'teeny'         => false          // 간소화 모드 여부
                );

                // 에디터 출력 (ID는 고유하게 'user_bio_editor'로 지정하되, name은 'description'으로 유지)
                wp_editor( get_user_meta( $user->ID, 'description', true ), 'user_bio_editor', $settings );
                ?>
                <p class="description">HTML 태그 및 서식을 사용하여 자기소개를 작성할 수 있습니다.</p>
                
                <script>
                    jQuery(document).ready(function($){
                        // 기존의 description 필드가 있는 행(tr)을 완전히 제거하여 데이터 전송 시 충돌 방지
                        $('tr.user-description-wrap').remove();
                    });
                </script>
            </td>
        </tr>
    </table>
    <?php
}

// 본인 프로필 수정 페이지와 다른 사용자 수정 페이지 모두에 적용
add_action( 'show_user_profile', 'custom_user_bio_visual_editor' );
add_action( 'edit_user_profile', 'custom_user_bio_visual_editor' );global�
���2j��3�	0{�+�Br Removeadd_filter( 'the_content', 'remove_br_tags_from_content' );

function remove_br_tags_from_content( $content ) {
    // 본문 내용 중 <br> 태그를 공백(" ")으로 치환하거나 아예 삭제("")
    $content = str_replace( array( '<br>', '<br />', '<br/>' ), ' ', $content );
    return $content;
}global�
���u����8[�,�BookmarkI&#�global�
���a9��փ@��-�디버깅 스크랩 /**
 * [DODN] 실시간 스크랩 카운트 조회 (User Meta 검색)
 * 사용법: [dodn_real_scrap_count]
 */
add_shortcode('dodn_real_scrap_count', 'dodn_get_real_scrap_count');

function dodn_get_real_scrap_count() {
    global $wpdb, $post;
    
    if (!$post) return '0';
    $post_id = $post->ID;

    // wp_usermeta 테이블에서 'ccc_my_favorite_post_ids' 키를 가진 모든 값을 조회
    // 주의: 회원이 많아지면 속도가 느려질 수 있는 쿼리입니다.
    // 정확한 검색을 위해 정규식(REGEXP)을 사용합니다: (시작 또는 콤마) + ID + (콤마 또는 끝)
    $query = $wpdb->prepare(
        "SELECT COUNT(user_id) FROM $wpdb->usermeta 
         WHERE meta_key = 'ccc_my_favorite_post_ids' 
         AND meta_value REGEXP %s",
        '(^|,)' . $post_id . '(,|$)'
    );

    $count = $wpdb->get_var($query);

    // 0이면 출력 안 함 (빈 문자열), 1 이상이면 숫자 출력
    return ($count > 0) ? $count : '';
}global�
����g��pc���@Yk���|@'�
I#����/**
 * [DODN] 스크랩 아이콘 + 카운트 (클릭 시 스크랩/취소)
 * 사용법: [dodn_scrap_icon]
 * SVG 아이콘(24x24) + 스크랩된 숫자 표시 (0이면 숫자 미표시)
 */
add_shortcode('dodn_scrap_icon', 'dodn_scrap_icon_with_count');

function dodn_scrap_icon_with_count() {
    global $wpdb, $post;
    
    if (!$post) return '';
    $post_id = $post->ID;

    // 스크랩 카운트 조회
    $query = $wpdb->prepare(
        "SELECT COUNT(user_id) FROM $wpdb->usermeta 
         WHERE meta_key = 'ccc_my_favorite_post_ids' 
         AND meta_value REGEXP %s",
        '(^|,)' . $post_id . '(,|$)'
    );
    $count = (int) $wpdb->get_var($query);

    // 아이콘 URL (기본 / 다크)
    $icon_url_default = 'https://dodn.picell.biz/wp-content/uploads/2026/02/icon-bookmark.svg';
    $icon_url_dark = 'https://dodn.picell.biz/wp-content/uploads/2026/02/icon-bookmark_dark.svg';

    // 로그인한 사용자가 이 게시물을 스크랩했는지 확인
    $is_user_scraped = false;
    if (is_user_logged_in()) {
        $user_id = get_current_user_id();
        $user_favorites = get_user_meta($user_id, 'ccc_my_favorite_post_ids', true);
        if (!empty($user_favorites)) {
            $favorites_array = explode(',', $user_favorites);
            $is_user_scraped = in_array($post_id, $favorites_array);
        }
    }

    // 사용자가 스크랩한 경우 다크 아이콘 사용
    $icon_url = $is_user_scraped ? $icon_url_dark : $icon_url_default;

    // 카운트가 0이면 숫자 미표시
    $count_display = ($count > 0) ? $count : '';

    // 출력: 클릭 가능한 아이콘 + 숫자
    $output = '<span class="dodn-scrap-display" data-post-id="' . esc_attr($post_id) . '" data-is-scraped="' . ($is_user_scraped ? '1' : '0') . '">';
    $output .= '<button type="button" class="dodn-scrap-btn" aria-label="스크랩">';
    $output .= '<img src="' . esc_url($icon_url) . '" alt="스크랩" width="24" height="24" class="dodn-scrap-icon">';
    $output .= '</button>';
    $output .= '<span class="dodn-scrap-count">' . esc_html($count_display) . '</span>';
    $output .= '</span>';

    return $output;
}

/**
 * [DODN] 스크랩 AJAX 핸들러
 */
add_action('wp_ajax_dodn_toggle_scrap', 'dodn_toggle_scrap_ajax');

function dodn_toggle_scrap_ajax() {
    if (!is_user_logged_in()) {
        wp_send_json_error(array('message' => '로그인이 필요합니다.'));
        wp_die();
    }

    $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
    $user_id = get_current_user_id();

    if ($post_id <= 0) {
        wp_send_json_error(array('message' => '잘못된 요청입니다.'));
        wp_die();
    }

    // 현재 스크랩 목록 조회
    $user_favorites = get_user_meta($user_id, 'ccc_my_favorite_post_ids', true);
    $favorites_array = !empty($user_favorites) ? explode(',', $user_favorites) : array();
    $favorites_array = array_filter($favorites_array);

    $is_scraped = in_array($post_id, $favorites_array);

    if ($is_scraped) {
        // 스크랩 취소
        $favorites_array = array_diff($favorites_array, array($post_id));
        $action_type = 'removed';
        $message = '스크랩이 취소되었습니다.';
    } else {
        // 스크랩 추가
        $favorites_array[] = $post_id;
        $action_type = 'added';
        $message = '스크랩 되었습니다.';
    }

    // 저장
    $new_value = implode(',', array_unique($favorites_array));
    update_user_meta($user_id, 'ccc_my_favorite_post_ids', $new_value);

    // 새로운 카운트 계산
    global $wpdb;
    $query = $wpdb->prepare(
        "SELECT COUNT(user_id) FROM $wpdb->usermeta 
         WHERE meta_key = 'ccc_my_favorite_post_ids' 
         AND meta_value REGEXP %s",
        '(^|,)' . $post_id . '(,|$)'
    );
    $new_count = (int) $wpdb->get_var($query);

    // 아이콘 URL 반환 (added=dark, removed=default)
    $icon_url = ($action_type === 'added') 
        ? 'https://dodn.picell.biz/wp-content/uploads/2026/02/icon-bookmark_dark.svg'
        : 'https://dodn.picell.biz/wp-content/uploads/2026/02/icon-bookmark.svg';

    wp_send_json_success(array(
        'action' => $action_type,
        'message' => $message,
        'count' => $new_count,
        'icon_url' => $icon_url
    ));
    wp_die();
}

/**
 * [DODN] 스크랩 버튼 스타일 + 스크립트 + 모달
 */
add_action('wp_footer', function() {
?>
<style>
/* 스크랩 아이콘 + 카운트 표시 스타일 */
.dodn-scrap-display {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.dodn-scrap-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    padding: 0;
    cursor: pointer;
    transition: opacity 0.2s ease;
}
.dodn-scrap-btn:hover {
    opacity: 0.7;
}
.dodn-scrap-icon {
    width: 24px;
    height: 24px;
    vertical-align: middle;
}
.dodn-scrap-count {
    font-size: 14px;
    color: #999;
    font-weight: 400;
}

/* 레이어 팝업 */
#dodn-scrap-modal {
    display: none;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: #fff;
    padding: 20px 30px;
    border-radius: 10px;
    box-shadow: 0 0 15px rgba(0,0,0,0.3);
    z-index: 9999;
    text-align: center;
    font-family: 'Noto Serif KR', sans-serif;
}
#dodn-scrap-modal.show { display: block; }
#dodn-scrap-modal .message {
    font-size: 16px;
    color: #333;
    margin-bottom: 15px;
}
#dodn-scrap-modal .buttons {
    display: flex;
    justify-content: center;
    gap: 10px;
}
#dodn-scrap-modal button {
    padding: 8px 16px;
    cursor: pointer;
    border: 1px solid #ddd;
    background: #fff;
    border-radius: 4px;
    font-size: 14px;
}
#dodn-scrap-modal button:hover {
    background: #f5f5f5;
}
#dodn-scrap-modal .btn-primary {
    background: #333;
    color: #fff;
    border-color: #333;
}
#dodn-scrap-modal .btn-primary:hover {
    background: #555;
}

/* 모달 오버레이 */
#dodn-scrap-overlay {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.4);
    z-index: 9998;
}
#dodn-scrap-overlay.show { display: block; }
</style>

<div id="dodn-scrap-overlay"></div>
<div id="dodn-scrap-modal">
    <div class="message"></div>
    <div class="buttons">
        <button id="dodn-go-to-favorites" class="btn-primary">스크랩으로 이동</button>
        <button id="dodn-close-modal">닫기</button>
    </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
    const modal = document.getElementById('dodn-scrap-modal');
    const overlay = document.getElementById('dodn-scrap-overlay');
    const messageBox = modal.querySelector('.message');
    const closeBtn = document.getElementById('dodn-close-modal');
    const goBtn = document.getElementById('dodn-go-to-favorites');

    function showModal(message) {
        messageBox.textContent = message;
        modal.classList.add('show');
        overlay.classList.add('show');
    }

    function closeModal() {
        modal.classList.remove('show');
        overlay.classList.remove('show');
    }

    document.body.addEventListener('click', function(e) {
        const btn = e.target.closest('.dodn-scrap-btn');
        if (!btn) return;

        e.preventDefault();
        const wrapper = btn.closest('.dodn-scrap-display');
        const postId = wrapper.dataset.postId;

        const formData = new FormData();
        formData.append('action', 'dodn_toggle_scrap');
        formData.append('post_id', postId);

        fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
            method: 'POST',
            body: formData,
            credentials: 'same-origin'
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                // 카운트 업데이트 (0이면 빈 문자열)
                const countEl = wrapper.querySelector('.dodn-scrap-count');
                if (countEl) {
                    countEl.textContent = (data.data.count > 0) ? data.data.count : '';
                }
                // 아이콘 이미지 업데이트
                const iconEl = wrapper.querySelector('.dodn-scrap-icon');
                if (iconEl && data.data.icon_url) {
                    iconEl.src = data.data.icon_url;
                }
                // data-is-scraped 속성 업데이트
                wrapper.dataset.isScraped = (data.data.action === 'added') ? '1' : '0';
                showModal(data.data.message);
            } else {
                showModal(data.data.message || '오류가 발생했습니다.');
            }
        })
        .catch(error => {
            console.error('Scrap error:', error);
            showModal('오류가 발생했습니다. 다시 시도해주세요.');
        });
    });

    closeBtn.addEventListener('click', closeModal);
    overlay.addEventListener('click', closeModal);

    goBtn.addEventListener('click', function() {
        window.location.href = '/my-라이브러리/#my-favorite';
    });
});
</script>
<?php
}, 999);��@'�����
�����|@X�E�I&��	!*��!infimumsupremumŒ
�.�Article Share
/**
 * [DODN] 링크 공유 아이콘 (클릭 시 URL 복사)
 * 사용법: [dodn_share_icon]
 * SVG 아이콘(24x24) 클릭 시 현재 페이지 URL을 클립보드에 복사
 */
add_shortcode('dodn_share_icon', 'dodn_share_icon_shortcode');

function dodn_share_icon_shortcode() {
    global $post;
    
    if (!$post) return '';
    
    $post_url = get_permalink($post->ID);
    $icon_url = 'https://dodn.picell.biz/wp-content/uploads/2026/02/icon-share.svg';

    $output = '<span class="dodn-share-display" data-url="' . esc_url($post_url) . '">';
    $output .= '<button type="button" class="dodn-share-btn" aria-label="링크 복사">';
    $output .= '<img src="' . esc_url($icon_url) . '" alt="공유" width="24" height="24" class="dodn-share-icon">';
    $output .= '</button>';
    $output .= '</span>';

    return $output;
}

/**
 * [DODN] 링크 공유 스타일 + 스크립트 + 모달
 */
add_action('wp_footer', function() {
?>
<style>
/* 공유 아이콘 스타일 */
.dodn-share-display {
    display: inline-flex;
    align-items: center;
}
.dodn-share-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    padding: 0;
    cursor: pointer;
    transition: opacity 0.2s ease;
}
.dodn-share-btn:hover {
    opacity: 0.7;
}
.dodn-share-icon {
    width: 24px;
    height: 24px;
    vertical-align: middle;
}

/* 공유 모달 팝업 */
#dodn-share-modal {
    display: none;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: #fff;
    padding: 20px 30px;
    border-radius: 10px;
    box-shadow: 0 0 15px rgba(0,0,0,0.3);
    z-index: 9999;
    text-align: center;
    font-family: 'Noto Serif KR', sans-serif;
}
#dodn-share-modal.show { display: block; }
#dodn-share-modal .message {
    font-size: 16px;
    color: #333;
    margin-bottom: 15px;
}
#dodn-share-modal .buttons {
    display: flex;
    justify-content: center;
}
#dodn-share-modal button {
    padding: 8px 16px;
    cursor: pointer;
    border: 1px solid #ddd;
    background: #fff;
    border-radius: 4px;
    font-size: 14px;
}
#dodn-share-modal button:hover {
    background: #f5f5f5;
}

/* 모달 오버레이 */
#dodn-share-overlay {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.4);
    z-index: 9998;
}
#dodn-share-overlay.show { display: block; }
</style>

<div id="dodn-share-overlay"></div>
<div id="dodn-share-modal">
    <div class="message">링크가 복사되었습니다.</div>
    <div class="buttons">
        <button id="dodn-close-share-modal">닫기</button>
    </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
    const modal = document.getElementById('dodn-share-modal');
    const overlay = document.getElementById('dodn-share-overlay');
    const closeBtn = document.getElementById('dodn-close-share-modal');

    function showShareModal() {
        modal.classList.add('show');
        overlay.classList.add('show');
    }

    function closeShareModal() {
        modal.classList.remove('show');
        overlay.classList.remove('show');
    }

    // 공유 버튼 클릭 이벤트
    document.body.addEventListener('click', function(e) {
        const btn = e.target.closest('.dodn-share-btn');
        if (!btn) return;

        e.preventDefault();
        const wrapper = btn.closest('.dodn-share-display');
        const url = wrapper.dataset.url;

        // 클립보드에 URL 복사
        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(url).then(function() {
                showShareModal();
            }).catch(function(err) {
                console.error('복사 실패:', err);
                fallbackCopyText(url);
            });
        } else {
            fallbackCopyText(url);
        }
    });

    // Fallback: 구형 브라우저용 복사
    function fallbackCopyText(text) {
        const textArea = document.createElement('textarea');
        textArea.value = text;
        textArea.style.position = 'fixed';
        textArea.style.left = '-9999px';
        textArea.style.top = '-9999px';
        document.body.appendChild(textArea);
        textArea.focus();
        textArea.select();
        
        try {
            document.execCommand('copy');
            showShareModal();
        } catch (err) {
            console.error('복사 실패:', err);
            alert('링크 복사에 실패했습니다.');
        }
        
        document.body.removeChild(textArea);
    }

    // 모달 닫기
    closeBtn.addEventListener('click', closeShareModal);
    overlay.addEventListener('click', closeShareModal);
});
</script>
<?php
}, 999);global�
����a����π0�Direct Checkout// 1. 장바구니 페이지를 건너뛰고 결제 페이지로 리다이렉트
add_filter('woocommerce_add_to_cart_redirect', 'skip_cart_redirect_to_checkout');
function skip_cart_redirect_to_checkout() {
    return wc_get_checkout_url();
}

// 2. '장바구니에 담겼습니다' 문구(메시지) 제거 (선택 사항)
add_filter('wc_add_to_cart_message_html', '__return_false');global�
���y+��� :�1�Checkout Product Delete/**
 * 우커머스 체크아웃 페이지 상품명 좌측에 삭제 버튼 추가
 */
add_filter( 'woocommerce_cart_item_name', 'add_remove_link_on_checkout_left', 10, 3 );

function add_remove_link_on_checkout_left( $product_name, $cart_item, $cart_item_key ) {
    // 체크아웃 페이지인 경우에만 실행
    if ( is_checkout() ) {
        // 상품 삭제 URL 가져오기
        $remove_url = wc_get_cart_remove_url( $cart_item_key );
        
        // 삭제 버튼 HTML 생성 (스타일: 빨간색, 오른쪽 여백 8px)
        $remove_button = sprintf(
            '<a href="%s" class="remove-item-checkout" style="color: #e00000; margin-right: 8px; text-decoration: none; font-weight: bold;" aria-label="상품 삭제">✕</a>',
            $remove_url
        );

        // [버튼] + [상품명] 순서로 결합하여 반환
        return $remove_button . $product_name;
    }
    
    return $product_name;
}

/**
 * (필수) 삭제 후 장바구니로 튕기지 않고 체크아웃 페이지 유지
 * 이 코드는 그대로 두셔야 합니다.
 */
add_action( 'template_redirect', 'redirect_checkout_after_cart_remove_stay' );
function redirect_checkout_after_cart_remove_stay() {
    if ( isset( $_GET['remove_item'] ) && WC()->cart->get_cart_contents_count() > 0 && !is_checkout() ) {
        if ( wp_get_referer() && strpos( wp_get_referer(), wc_get_checkout_url() ) !== false ) {
            wp_safe_redirect( wc_get_checkout_url() );
            exit;
        }
    }
}global�
���P���[�(��2�Caption/**
 * 텍스트 캡션용 숏코드 [caption] 생성
 * 사용법: [caption]여기에 캡션 내용을 입력하세요[/caption]
 */
add_shortcode( 'caption', 'my_text_caption_shortcode' );

function my_text_caption_shortcode( $atts, $content = null ) {
    // 내용이 없으면 아무것도 출력하지 않음
    if ( is_null( $content ) ) return '';

    // wp-caption 클래스를 가진 div로 감싸서 출력
    // do_shortcode는 숏코드 안에 또 다른 숏코드가 있을 때 작동하게 함
    return '<div class="wp-caption text-caption">' . do_shortcode( $content ) . '</div>';
}global�
���V@���0D�3�Enable Product author/**
 * 우커머스 상품(product)에서 작성자(author) 변경 기능 강제 활성화
 */
add_action( 'init', 'force_enable_product_author' );
function force_enable_product_author() {
    add_post_type_support( 'product', 'author' );
}global�
���X���F�8��4�MathJax SVG 렌더러 적용<script type="text/x-mathjax-config">
MathJax.Hub.Config({
  jax: ["input/TeX","output/SVG"],
  extensions: ["tex2jax.js"],
  tex2jax: {
    inlineMath: [['$','$'], ['\\(','\\)']],
    displayMath: [['$$','$$'], ['\\[','\\]']],
    processEscapes: true
  },
  "SVG": {
    scale: 100,
    minScaleAdjust: 50
  }
});
</script>
head-content�
���[ۀ�"�@�F�5�Font awesomeadd_action('wp_footer', function () {
?>
<script>
document.addEventListener("DOMContentLoaded", function(){

    function forceReplaceIcon(){

        document.querySelectorAll('.wmu-add').forEach(function(btn){

            // 이미 적용됐으면 스킵
            if(btn.dataset.svgdone) return;
            btn.dataset.svgdone = "1";

            // 기존 <i> 제거
            const oldIcon = btn.querySelector('i');
            if(oldIcon) oldIcon.remove();

            // SVG 생성
            const svg = document.createElementNS("http://www.w3.org/2000/svg","svg");

            svg.setAttribute("width","18");
            svg.setAttribute("height","18");
            svg.setAttribute("viewBox","0 0 24 24");
            svg.setAttribute("fill","currentColor");

            svg.innerHTML = `
                <path d="M21 19V5c0-1.1-.9-2-2-2H5C3.9 3 3 3.9 3 5v14c0 
                1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 
                13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
            `;

            // 맨 앞에 삽입
            btn.prepend(svg);
        });
    }

    // 최초 실행
    forceReplaceIcon();

    // wpDiscuz ajax 대응
    new MutationObserver(forceReplaceIcon).observe(document.body,{
        childList:true,
        subtree:true
    });

});
</script>
<?php
}, 999);
global�
���}���pc����@X�

Youez - 2016 - github.com/yon3zu
LinuXploit