View file rusnet-interactive-map/classes/class-cpt-marker.php

File size: 22.64Kb
<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Rusnetim_CPT_Marker {

    public function __construct() {
        add_action( 'init', [ $this, 'register_post_type' ] );
        add_action( 'add_meta_boxes', [ $this, 'add_meta_boxes' ] );
        add_action( 'save_post_rusnetim_marker', [ $this, 'save_meta' ] );
        add_filter( 'manage_rusnetim_marker_posts_columns', [ $this, 'custom_columns' ] );
        add_action( 'manage_rusnetim_marker_posts_custom_column', [ $this, 'custom_column_content' ], 10, 2 );
        add_action( 'restrict_manage_posts', [ $this, 'admin_posts_filter' ] );
        add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );

        add_action( 'wp_ajax_rusnetim_toggle_visibility', [ $this, 'ajax_toggle_visibility' ] );
    }

    public function register_post_type() {
        $labels = array(
            'name'               => esc_html__( 'Markers', 'rusnet-interactive-map' ),
            'singular_name'      => esc_html__( 'Marker', 'rusnet-interactive-map' ),
            'menu_name'          => esc_html__( 'Markers', 'rusnet-interactive-map' ),
            'add_new'            => esc_html__( 'Add New', 'rusnet-interactive-map' ),
            'add_new_item'       => esc_html__( 'Add New Marker', 'rusnet-interactive-map' ),
            'edit_item'          => esc_html__( 'Edit Marker', 'rusnet-interactive-map' ),
            'new_item'           => esc_html__( 'New Marker', 'rusnet-interactive-map' ),
            'view_item'          => esc_html__( 'View Marker', 'rusnet-interactive-map' ),
            'search_items'       => esc_html__( 'Search Markers', 'rusnet-interactive-map' ),
            'not_found'          => esc_html__( 'No markers found', 'rusnet-interactive-map' ),
            'not_found_in_trash' => esc_html__( 'No markers found in Trash', 'rusnet-interactive-map' ),
        );

        $args = array(
            'labels'              => $labels,
            'public'              => false,
            'show_ui'             => true,
            'show_in_nav_menus'   => false,
            'supports'            => array( 'title', 'editor' ),
            'has_archive'         => false,
            'rewrite'             => false,
            'query_var'           => false,
            'capability_type'     => 'post',
            'map_meta_cap'        => true,
            'taxonomies'          => array( 'rusnetim_category' ),
        );

        register_post_type( 'rusnetim_marker', $args );
    }

    public function add_meta_boxes() {
        add_meta_box(
            'marker-coords',
            esc_html__( 'Coordinates', 'rusnet-interactive-map' ),
            [ $this, 'meta_box_coords' ],
            'rusnetim_marker',
            'normal',
            'high'
        );

        add_meta_box(
            'marker-icon',
            esc_html__( 'Icon and Color', 'rusnet-interactive-map' ),
            [ $this, 'meta_box_icon' ],
            'rusnetim_marker',
            'normal',
            'high'
        );

        add_meta_box(
            'marker-url',
            esc_html__( 'Link', 'rusnet-interactive-map' ),
            [ $this, 'meta_box_url' ],
            'rusnetim_marker',
            'normal',
            'default'
        );

        add_meta_box(
            'marker-image',
            esc_html__( 'Image Gallery', 'rusnet-interactive-map' ),
            [ $this, 'meta_box_image' ],
            'rusnetim_marker',
            'normal',
            'default'
        );
    }

    public function meta_box_coords( $post ) {
        wp_nonce_field( 'rusnetim_save_marker', 'rusnetim_marker_nonce' );
        $coord = get_post_meta( $post->ID, '_marker_coord', true );
        $coord = $coord ?: '53.334554,83.786980';

        $visible = get_post_meta( $post->ID, '_marker_visible', true );
        if ( '' === $visible ) {
            $visible = '1';
        }
        ?>
        <p>
            <label for="marker_coord"><?php esc_html_e( 'Latitude, longitude:', 'rusnet-interactive-map' ); ?></label>
            <input type="text" id="marker_coord" name="marker_coord" value="<?php echo esc_attr( $coord ); ?>" class="widefat" />
            <span class="description"><?php esc_html_e( 'Example: 53.334554,83.786980', 'rusnet-interactive-map' ); ?></span>
        </p>
        <p>
            <label for="marker_visible">
                <input type="checkbox" id="marker_visible" name="marker_visible" value="1" <?php checked( $visible, '1' ); ?> />
                <?php esc_html_e( 'Show marker on maps', 'rusnet-interactive-map' ); ?>
            </label>
        </p>
        <?php
        // ADDED HOOK: after coordinates and visibility fields
        do_action( 'rusnetim_marker_coords_meta_box', $post );
    }

    public function meta_box_icon( $post ) {
        $icon  = get_post_meta( $post->ID, '_marker_icon', true );
        $color = get_post_meta( $post->ID, '_marker_color', true );
        $custom_icon = get_post_meta( $post->ID, '_marker_custom_icon', true );
        $icon_width  = get_post_meta( $post->ID, '_marker_icon_width', true );
        $icon_height = get_post_meta( $post->ID, '_marker_icon_height', true );

        $defaults = Rusnetim_Options::get_all();
        $my_iconset = $defaults['my_iconset'];

        ?>
        <p>
            <label for="marker_icon"><?php esc_html_e( 'Icon type (preset or myset:slug):', 'rusnet-interactive-map' ); ?></label>
            <input type="text" id="marker_icon" name="marker_icon" value="<?php echo esc_attr( $icon ); ?>" class="widefat" placeholder="islands#dotIcon or myset:office" />
            <span class="description">
                <?php esc_html_e( 'You can use standard presets (islands#dotIcon) or icons from your own set in the format myset:slug (e.g., myset:office).', 'rusnet-interactive-map' ); ?>
                <a href="https://tech.yandex.com/maps/doc/jsapi/2.1/ref/reference/option.presetStorage-docpage/" target="_blank"><?php esc_html_e( 'Documentation', 'rusnet-interactive-map' ); ?></a>
            </span>
        </p>

        <p>
            <label for="marker_color"><?php esc_html_e( 'Color:', 'rusnet-interactive-map' ); ?></label>
            <input type="text" id="marker_color" name="marker_color" value="<?php echo esc_attr( $color ); ?>" class="widefat rusnetim-color-field" style="width: 100px;" data-default-color="" />
            <span class="description"><?php esc_html_e( 'HEX (e.g., #1e98ff). If empty, category color or global color will be used.', 'rusnet-interactive-map' ); ?></span>
        </p>

        <h4><?php esc_html_e( 'Custom icon (image)', 'rusnet-interactive-map' ); ?></h4>
        <div id="marker-icon-custom-wrapper">
            <div style="display: flex; gap: 5px; align-items: center;">
                <input type="text" id="marker_custom_icon" name="marker_custom_icon" value="<?php echo esc_attr( $custom_icon ); ?>" class="widefat" placeholder="https://... or attachment ID" style="flex:1;" />
                <button type="button" class="button rusnetim-upload-icon-btn"><?php esc_html_e( 'Select', 'rusnet-interactive-map' ); ?></button>
            </div>
            <div id="marker-icon-preview" style="margin-top: 10px;"></div>
            <button type="button" id="marker-icon-remove" class="button" style="display: none;"><?php esc_html_e( 'Remove', 'rusnet-interactive-map' ); ?></button>
            <span class="description"><?php esc_html_e( 'Upload your own image (SVG, PNG). If specified, it will be used instead of the preset icon.', 'rusnet-interactive-map' ); ?></span>
        </div>

        <p style="display: flex; gap: 10px; flex-wrap: wrap;">
            <span>
                <label for="marker_icon_width"><?php esc_html_e( 'Width (px):', 'rusnet-interactive-map' ); ?></label>
                <input type="number" id="marker_icon_width" name="marker_icon_width" value="<?php echo esc_attr( $icon_width ); ?>" min="0" step="1" style="width: 80px;" />
            </span>
            <span>
                <label for="marker_icon_height"><?php esc_html_e( 'Height (px):', 'rusnet-interactive-map' ); ?></label>
                <input type="number" id="marker_icon_height" name="marker_icon_height" value="<?php echo esc_attr( $icon_height ); ?>" min="0" step="1" style="width: 80px;" />
            </span>
            <span class="description"><?php esc_html_e( 'Leave empty for auto-detection.', 'rusnet-interactive-map' ); ?></span>
        </p>
        <?php
        do_action( 'rusnetim_marker_icon_meta_box', $post );
    }

    public function meta_box_url( $post ) {
        $url = get_post_meta( $post->ID, '_marker_url', true );
        ?>
        <p>
            <label for="marker_url"><?php esc_html_e( 'URL or post ID:', 'rusnet-interactive-map' ); ?></label>
            <input type="text" id="marker_url" name="marker_url" value="<?php echo esc_attr( $url ); ?>" class="widefat" />
            <span class="description"><?php esc_html_e( 'Clicking on the marker will open the link. You can specify page/post ID.', 'rusnet-interactive-map' ); ?></span>
        </p>
        <?php
        // ADDED HOOK: after URL field
        do_action( 'rusnetim_marker_url_meta_box', $post );
    }

    public function meta_box_image( $post ) {
        $gallery_ids = get_post_meta( $post->ID, '_marker_gallery', true );
        if ( ! is_array( $gallery_ids ) ) {
            $gallery_ids = array();
        }
        $single_image = get_post_meta( $post->ID, '_marker_image', true );
        ?>
        <div id="marker-gallery-wrapper">
            <div style="margin-bottom: 10px;">
                <button type="button" class="button" id="rusnetim-add-gallery-btn"><?php esc_html_e( 'Select images', 'rusnet-interactive-map' ); ?></button>
                <button type="button" class="button" id="rusnetim-clear-gallery-btn" style="display:<?php echo empty( $gallery_ids ) ? 'none' : 'inline-block'; ?>;"><?php esc_html_e( 'Clear', 'rusnet-interactive-map' ); ?></button>
            </div>
            <div id="marker-gallery-preview" style="display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 10px;">
                <?php
                foreach ( $gallery_ids as $attachment_id ) {
                    $image = wp_get_attachment_image_src( $attachment_id, 'thumbnail' );
                    if ( $image ) {
                        echo '<div class="gallery-thumb" data-id="' . esc_attr( $attachment_id ) . '" style="position: relative;">';
                        echo '<img src="' . esc_url( $image[0] ) . '" style="max-width: 80px; max-height: 80px; border: 1px solid #ddd; padding: 2px; border-radius: 4px;">';
                        echo '<span class="remove-gallery-thumb" style="position: absolute; top: -5px; right: -5px; background: #f00; color: #fff; border-radius: 50%; width: 18px; height: 18px; text-align: center; line-height: 18px; font-size: 14px; cursor: pointer;">&times;</span>';
                        echo '</div>';
                    }
                }
                ?>
            </div>
            <input type="hidden" name="marker_gallery" id="marker_gallery" value="<?php echo esc_attr( implode( ',', $gallery_ids ) ); ?>" />
            <span class="description"><?php esc_html_e( 'Select images for the marker gallery.', 'rusnet-interactive-map' ); ?></span>
            <p><em><?php esc_html_e( 'Old "Image" field is kept for compatibility, but it\'s recommended to use the new gallery.', 'rusnet-interactive-map' ); ?></em></p>
            <input type="hidden" name="marker_image_old" id="marker_image_old" value="<?php echo esc_attr( $single_image ); ?>" />
        </div>
        <?php
        // ADDED HOOK: after gallery fields
        do_action( 'rusnetim_marker_image_meta_box', $post );
    }

    public function save_meta( $post_id ) {
        if ( ! isset( $_POST['rusnetim_marker_nonce'] ) ||
             ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['rusnetim_marker_nonce'] ) ), 'rusnetim_save_marker' ) ) {
            return;
        }

        if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
            return;
        }

        if ( ! current_user_can( 'edit_post', $post_id ) ) {
            return;
        }

        if ( isset( $_POST['marker_coord'] ) ) {
            $coord = Rusnetim_Options::sanitize_coords( sanitize_text_field( wp_unslash( $_POST['marker_coord'] ) ) );
            update_post_meta( $post_id, '_marker_coord', $coord );
        }

        $visible = isset( $_POST['marker_visible'] ) ? '1' : '0';
        update_post_meta( $post_id, '_marker_visible', $visible );

        if ( isset( $_POST['marker_icon'] ) ) {
            $icon = sanitize_text_field( wp_unslash( $_POST['marker_icon'] ) );
            update_post_meta( $post_id, '_marker_icon', $icon );
        }

        if ( isset( $_POST['marker_color'] ) ) {
            $color = sanitize_hex_color( wp_unslash( $_POST['marker_color'] ) );
            update_post_meta( $post_id, '_marker_color', $color );
        }

        if ( isset( $_POST['marker_custom_icon'] ) ) {
            $custom_icon = sanitize_text_field( wp_unslash( $_POST['marker_custom_icon'] ) );
            update_post_meta( $post_id, '_marker_custom_icon', $custom_icon );
        }

        if ( isset( $_POST['marker_icon_width'] ) ) {
            $width = intval( wp_unslash( $_POST['marker_icon_width'] ) );
            update_post_meta( $post_id, '_marker_icon_width', $width ?: '' );
        }

        if ( isset( $_POST['marker_icon_height'] ) ) {
            $height = intval( wp_unslash( $_POST['marker_icon_height'] ) );
            update_post_meta( $post_id, '_marker_icon_height', $height ?: '' );
        }

        if ( isset( $_POST['marker_url'] ) ) {
            $url = esc_url_raw( wp_unslash( $_POST['marker_url'] ) );
            update_post_meta( $post_id, '_marker_url', $url );
        }

        if ( isset( $_POST['marker_gallery'] ) ) {
            $gallery = sanitize_text_field( wp_unslash( $_POST['marker_gallery'] ) );
            $ids = array_filter( array_map( 'intval', explode( ',', $gallery ) ) );
            update_post_meta( $post_id, '_marker_gallery', $ids );
        } else {
            delete_post_meta( $post_id, '_marker_gallery' );
        }

        if ( isset( $_POST['marker_image_old'] ) ) {
            $image = sanitize_text_field( wp_unslash( $_POST['marker_image_old'] ) );
            update_post_meta( $post_id, '_marker_image', $image );
        }

        do_action( 'rusnetim_save_marker_meta', $post_id );
    }

    public function custom_columns( $columns ) {
        $new_columns = array();
        foreach ( $columns as $key => $value ) {
            if ( $key === 'title' ) {
                $new_columns[ $key ] = $value;
                $new_columns['marker_visible'] = esc_html__( 'Visibility', 'rusnet-interactive-map' );
            } else {
                $new_columns[ $key ] = $value;
            }
        }
        $new_columns['marker_coord']   = esc_html__( 'Coordinates', 'rusnet-interactive-map' );
        $new_columns['taxonomy-rusnetim_category'] = esc_html__( 'Category', 'rusnet-interactive-map' );
        $new_columns['marker_shortcode'] = esc_html__( 'Shortcode', 'rusnet-interactive-map' );
        unset( $new_columns['date'] );
        return $new_columns;
    }

    public function custom_column_content( $column, $post_id ) {
        switch ( $column ) {
            case 'marker_coord':
                $coord = get_post_meta( $post_id, '_marker_coord', true );
                echo esc_html( $coord ?: '—' );
                break;
            case 'marker_shortcode':
                echo '<input type="text" readonly value="' . esc_attr( '[rusnetim_map marker_id=&quot;' . $post_id . '&quot;]' ) . '" class="widefat" onclick="this.select();" />';
                break;
            case 'marker_visible':
                $visible = get_post_meta( $post_id, '_marker_visible', true );
                $visible = ( $visible === '1' || $visible === '' ) ? '1' : '0';
                $icon = ( $visible === '1' ) ? 'dashicons-visibility' : 'dashicons-hidden';
                $title = ( $visible === '1' ) ? esc_attr__( 'Hide marker', 'rusnet-interactive-map' ) : esc_attr__( 'Show marker', 'rusnet-interactive-map' );
                echo '<span class="rusnetim-toggle-visibility dashicons ' . esc_attr( $icon ) . '" data-id="' . esc_attr( $post_id ) . '" data-nonce="' . esc_attr( wp_create_nonce( 'rusnetim_toggle_' . $post_id ) ) . '" title="' . esc_attr( $title ) . '" style="cursor: pointer;"></span>';
                break;
        }
    }

    public function ajax_toggle_visibility() {
        if ( ! isset( $_POST['nonce'] ) || ! isset( $_POST['post_id'] ) ) {
            wp_die( -1 );
        }

        $post_id = intval( wp_unslash( $_POST['post_id'] ) );
        $nonce = sanitize_text_field( wp_unslash( $_POST['nonce'] ) );

        if ( ! wp_verify_nonce( $nonce, 'rusnetim_toggle_' . $post_id ) ) {
            wp_die( -1 );
        }

        if ( ! current_user_can( 'edit_post', $post_id ) ) {
            wp_die( -1 );
        }

        $current = get_post_meta( $post_id, '_marker_visible', true );
        $new = ( $current === '0' ) ? '1' : '0';
        update_post_meta( $post_id, '_marker_visible', $new );
        wp_send_json_success( [ 'visible' => $new ] );
    }

    public function admin_posts_filter() {
        global $typenow;
        if ( 'rusnetim_marker' === $typenow ) {
            $taxonomy = 'rusnetim_category';
            $selected = isset( $_GET[ $taxonomy ] ) ? sanitize_text_field( wp_unslash( $_GET[ $taxonomy ] ) ) : '';
            wp_dropdown_categories( array(
                'show_option_all' => esc_html__( 'All categories', 'rusnet-interactive-map' ),
                'taxonomy'        => $taxonomy,
                'name'            => $taxonomy,
                'value_field'     => 'slug',
                'selected'        => $selected,
                'show_count'      => false,
                'hide_empty'      => true,
            ) );
        }
    }

    public function enqueue_scripts( $hook_suffix ) {
        $screen = get_current_screen();
        if ( $screen && ( 'rusnetim_marker' === $screen->post_type ) ) {
            wp_enqueue_media();
            wp_enqueue_style( 'wp-color-picker' );
            wp_enqueue_script( 'wp-color-picker' );

            wp_enqueue_script(
                'rusnetim-admin-iconset',
                RUSNETIM_PLUGIN_URL . 'assets/js/admin/admin-iconset.js',
                array( 'jquery', 'wp-color-picker' ),
                RUSNETIM_VERSION,
                true
            );

            wp_enqueue_script(
                'rusnetim-admin-gallery',
                RUSNETIM_PLUGIN_URL . 'assets/js/admin/admin-gallery.js',
                array( 'jquery', 'wp-color-picker' ),
                RUSNETIM_VERSION,
                true
            );

            wp_localize_script( 'rusnetim-admin-iconset', 'rusnetim_admin', array(
                'ajaxurl' => admin_url( 'admin-ajax.php' ),
                'nonce'   => wp_create_nonce( 'rusnetim_ajax_nonce' ),
                'i18n'    => array(
                    'selectImage' => __( 'Select image', 'rusnet-interactive-map' ),
                    'useImage'    => __( 'Use this image', 'rusnet-interactive-map' ),
                    'enterSlug'   => __( 'Please enter a slug for the new icon.', 'rusnet-interactive-map' ),
                    'invalidSlug' => __( 'Slug may only contain latin letters, digits and hyphen.', 'rusnet-interactive-map' ),
                    'slugExists'  => __( 'An icon with this slug already exists.', 'rusnet-interactive-map' ),
                    'remove'      => __( 'Remove', 'rusnet-interactive-map' ),
                )
            ) );

            wp_add_inline_script( 'wp-color-picker', '
                jQuery(document).ready(function($) {
                    $(".rusnetim-color-field").wpColorPicker();
                });
            ' );

            $custom_css = '
                .post-type-rusnetim_marker .inside .widefat,
                .post-type-rusnetim_marker .inside input[type="text"],
                .post-type-rusnetim_marker .inside input[type="number"],
                .post-type-rusnetim_marker .inside textarea {
                    width: 100% !important;
                    box-sizing: border-box;
                    max-width: 100%;
                }
                .post-type-rusnetim_marker .inside span[style*="display: flex"] {
                    flex-wrap: wrap;
                }
                .post-type-rusnetim_marker .inside span[style*="display: flex"] input {
                    min-width: 200px;
                    flex: 1 1 auto;
                }
                .post-type-rusnetim_marker .inside p {
                    max-width: 100%;
                }
                .column-marker_visible {
                    width: 80px;
                    text-align: center;
                }
                .rusnetim-toggle-visibility {
                    font-size: 20px;
                    width: auto;
                    height: auto;
                }
                .rusnetim-toggle-visibility.dashicons-hidden {
                    opacity: 0.5;
                }
            ';
            wp_register_style( 'rusnetim-admin-marker-styles', false );
            wp_enqueue_style( 'rusnetim-admin-marker-styles' );
            wp_add_inline_style( 'rusnetim-admin-marker-styles', $custom_css );

            if ( $screen->base === 'edit' ) {
                $toggle_script = '
                    jQuery(document).ready(function($) {
                        $(".rusnetim-toggle-visibility").on("click", function() {
                            var $this = $(this);
                            var post_id = $this.data("id");
                            var nonce = $this.data("nonce");
                            $.post(ajaxurl, {
                                action: "rusnetim_toggle_visibility",
                                post_id: post_id,
                                nonce: nonce
                            }, function(response) {
                                if (response.success) {
                                    var visible = response.data.visible;
                                    if (visible === "1") {
                                        $this.removeClass("dashicons-hidden").addClass("dashicons-visibility");
                                        $this.attr("title", "' . esc_js( __( 'Hide marker', 'rusnet-interactive-map' ) ) . '");
                                    } else {
                                        $this.removeClass("dashicons-visibility").addClass("dashicons-hidden");
                                        $this.attr("title", "' . esc_js( __( 'Show marker', 'rusnet-interactive-map' ) ) . '");
                                    }
                                }
                            });
                        });
                    });
                ';
                wp_add_inline_script( 'jquery', $toggle_script );
            }
        }
    }
}