<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Rusnetim_Admin {
private $page_hook = 'rusnetim-options';
public function __construct() {
add_action( 'admin_menu', [ $this, 'add_admin_menu' ], 20 );
add_action( 'admin_init', [ $this, 'register_settings' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_filter( 'mce_external_plugins', [ $this, 'add_tinymce_plugin' ] );
add_filter( 'mce_buttons', [ $this, 'add_tinymce_button' ] );
add_action( 'admin_head', [ $this, 'output_tinymce_template' ] );
add_filter( 'parent_file', [ $this, 'set_parent_file' ] );
add_filter( 'submenu_file', [ $this, 'set_submenu_file' ], 10, 2 );
}
public function add_admin_menu() {
add_menu_page(
'Rusnet Interactive Map',
'Rusnet Map',
'manage_options',
$this->page_hook,
[ $this, 'option_page' ],
'dashicons-location-alt',
30
);
}
public function set_parent_file( $parent_file ) {
global $current_screen;
if ( ! $current_screen ) {
return $parent_file;
}
if ( $current_screen->post_type === 'rusnetim_marker' ) {
$parent_file = 'edit.php?post_type=rusnetim_marker';
} elseif ( $current_screen->taxonomy === 'rusnetim_category' ) {
$parent_file = 'edit.php?post_type=rusnetim_marker';
}
return $parent_file;
}
public function set_submenu_file( $submenu_file, $parent_file ) {
global $current_screen;
if ( ! $current_screen ) {
return $submenu_file;
}
if ( $current_screen->post_type === 'rusnetim_marker' ) {
if ( $current_screen->action === 'add' ) {
$submenu_file = 'post-new.php?post_type=rusnetim_marker';
} else {
$submenu_file = 'edit.php?post_type=rusnetim_marker';
}
} elseif ( $current_screen->taxonomy === 'rusnetim_category' ) {
$submenu_file = 'edit-tags.php?taxonomy=rusnetim_category&post_type=rusnetim_marker';
}
return $submenu_file;
}
public function register_settings() {
register_setting( 'rusnetim_options', 'rusnetim_options', [ Rusnetim_Options::class, 'sanitize' ] );
add_settings_section( 'general_section', esc_html__( 'Map options', 'rusnet-interactive-map' ), '', $this->page_hook . '_general' );
$general_fields = [
'center_map_option' => [
'title' => esc_html__( 'Map center', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => esc_html__( 'Drag the map to set its default coordinates', 'rusnet-interactive-map' ),
],
'zoom_map_option' => [
'title' => esc_html__( 'Map zoom', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => esc_html__( 'Zoom the map to set its default scale', 'rusnet-interactive-map' ),
],
'type_map_option' => [
'title' => esc_html__( 'Map type', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => esc_html__( 'Choose default map type: yandex#map (scheme), yandex#satellite, yandex#hybrid.', 'rusnet-interactive-map' ),
],
'height_map_option' => [
'title' => esc_html__( 'Map height', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => __( 'Specify height with units: <code>400px</code>, <code>30rem</code>, <code>50vh</code>.', 'rusnet-interactive-map' ),
],
'controls_map_option' => [
'title' => esc_html__( 'Map controls', 'rusnet-interactive-map' ),
'type' => 'textarea',
'desc' => '<div id="addcontrol" style="text-align: left;"><a data-control="typeSelector">' . esc_html__( 'Map type', 'rusnet-interactive-map' ) . '</a>, <a data-control="zoomControl">' . esc_html__( 'Zoom', 'rusnet-interactive-map' ) . '</a>, <a data-control="searchControl">' . esc_html__( 'Search', 'rusnet-interactive-map' ) . '</a>, <a data-control="routeButtonControl">' . esc_html__( 'Route', 'rusnet-interactive-map' ) . '</a>, <a data-control="rulerControl">' . esc_html__( 'Ruler', 'rusnet-interactive-map' ) . '</a>, <a data-control="trafficControl">' . esc_html__( 'Traffic', 'rusnet-interactive-map' ) . '</a>, <a data-control="fullscreenControl">' . esc_html__( 'Full screen', 'rusnet-interactive-map' ) . '</a>, <a data-control="geolocationControl">' . esc_html__( 'Geolocation', 'rusnet-interactive-map' ) . '</a></div>'
],
'wheelzoom_map_option' => [
'title' => esc_html__( 'Wheel zoom', 'rusnet-interactive-map' ),
'type' => 'checkbox',
'desc' => esc_html__( 'The map can be scaled with mouse scroll', 'rusnet-interactive-map' ),
],
'mobiledrag_map_option' => [
'title' => esc_html__( 'Mobile drag', 'rusnet-interactive-map' ),
'type' => 'checkbox',
'desc' => esc_html__( 'The map can be dragged on mobile', 'rusnet-interactive-map' ),
],
];
foreach ( $general_fields as $id => $field ) {
add_settings_field(
$id,
$field['title'],
[ $this, 'display_field' ],
$this->page_hook . '_general',
'general_section',
[ 'id' => $id, 'type' => $field['type'], 'desc' => $field['desc'] ]
);
}
add_settings_section( 'clustering_section', esc_html__( 'Clustering options', 'rusnet-interactive-map' ), '', $this->page_hook . '_clustering' );
$clustering_fields = [
'cluster_map_option' => [
'title' => esc_html__( 'Clustering', 'rusnet-interactive-map' ),
'type' => 'checkbox',
'desc' => esc_html__( 'Enable marker clustering by default', 'rusnet-interactive-map' )
. '<br><span class="description">'
. esc_html__( 'Clustering automatically groups nearby markers into clusters. This improves map performance and readability when many markers are close together.', 'rusnet-interactive-map' )
. '</span>'
],
'cluster_grid_option' => [
'title' => esc_html__( 'Cluster grid size', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => esc_html__( 'Cluster grid size in pixels (2, 4, 8, 16, 32, 64, 128, 256).', 'rusnet-interactive-map' )
. '<br><span class="description">'
. esc_html__( 'Smaller values (e.g., 32) require markers to be very close to form a cluster; larger values (e.g., 128) create larger clusters, grouping markers that are farther apart.', 'rusnet-interactive-map' )
. '</span>',
],
'cluster_color_option' => [
'title' => esc_html__( 'Cluster color', 'rusnet-interactive-map' ),
'type' => 'color',
'desc' => esc_html__( 'Choose the color for cluster icons.', 'rusnet-interactive-map' ),
],
];
foreach ( $clustering_fields as $id => $field ) {
add_settings_field(
$id,
$field['title'],
[ $this, 'display_field' ],
$this->page_hook . '_clustering',
'clustering_section',
[ 'id' => $id, 'type' => $field['type'], 'desc' => $field['desc'] ]
);
}
// ADDED HOOK: after clustering fields
do_action( 'rusnetim_after_clustering_fields', $this->page_hook . '_clustering' );
add_settings_section( 'infopanel_section', esc_html__( 'Info Panel options', 'rusnet-interactive-map' ), '', $this->page_hook . '_infopanel' );
$infopanel_fields = [
'info_panel_side' => [
'title' => esc_html__( 'Info panel side', 'rusnet-interactive-map' ),
'type' => 'select',
'desc' => esc_html__( 'Choose on which side the info panel should appear.', 'rusnet-interactive-map' ),
'options' => apply_filters( 'rusnetim_info_panel_side_options', [
'right' => esc_html__( 'Right', 'rusnet-interactive-map' ),
'left' => esc_html__( 'Left', 'rusnet-interactive-map' ),
] ),
],
'info_panel_width' => [
'title' => esc_html__( 'Info panel width', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => esc_html__( 'Specify width with units: px, %, em, rem, vw, vh (e.g., 300px, 50%, 20rem). For modal, this will be the width of the content box.', 'rusnet-interactive-map' ),
],
'info_panel_title_position' => [
'title' => esc_html__( 'Title position in panel', 'rusnet-interactive-map' ),
'type' => 'select',
'desc' => esc_html__( 'Where to display the marker title: above or below the gallery.', 'rusnet-interactive-map' ),
'options' => [
'below' => esc_html__( 'Below gallery', 'rusnet-interactive-map' ),
'above' => esc_html__( 'Above gallery', 'rusnet-interactive-map' ),
],
],
'info_title_tag' => [
'title' => esc_html__( 'Title HTML tag', 'rusnet-interactive-map' ),
'type' => 'select',
'desc' => esc_html__( 'Choose HTML element to wrap the title.', 'rusnet-interactive-map' ),
'options' => [
'h1' => 'h1',
'h2' => 'h2',
'h3' => 'h3',
'h4' => 'h4',
'h5' => 'h5',
'h6' => 'h6',
'div' => 'div',
'span' => 'span',
'p' => 'p',
],
],
'info_title_font_size' => [
'title' => esc_html__( 'Title font size', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => esc_html__( 'e.g., 18px, 1.2rem, 1.5em. Leave empty for default.', 'rusnet-interactive-map' ),
],
'info_title_color' => [
'title' => esc_html__( 'Title color', 'rusnet-interactive-map' ),
'type' => 'color',
'desc' => esc_html__( 'Select color for title.', 'rusnet-interactive-map' ),
],
'info_panel_bg_color' => [
'title' => esc_html__( 'Info panel background color', 'rusnet-interactive-map' ),
'type' => 'color',
'desc' => esc_html__( 'Select background color for the info panel.', 'rusnet-interactive-map' ),
],
];
foreach ( $infopanel_fields as $id => $field ) {
add_settings_field(
$id,
$field['title'],
[ $this, 'display_field' ],
$this->page_hook . '_infopanel',
'infopanel_section',
[
'id' => $id,
'type' => $field['type'],
'desc' => $field['desc'],
'options' => isset( $field['options'] ) ? $field['options'] : []
]
);
}
// ADDED HOOK: after infopanel fields
do_action( 'rusnetim_after_infopanel_fields', $this->page_hook . '_infopanel' );
add_settings_section( 'category_filter_section', esc_html__( 'Category Filter options', 'rusnet-interactive-map' ), '', $this->page_hook . '_category_filter' );
$cat_filter_fields = [
'cat_filter_container_justify' => [
'title' => esc_html__( 'Container alignment', 'rusnet-interactive-map' ),
'type' => 'select',
'desc' => esc_html__( 'How to align filter buttons within the container.', 'rusnet-interactive-map' ),
'options' => [
'flex-start' => esc_html__( 'Left', 'rusnet-interactive-map' ),
'center' => esc_html__( 'Center', 'rusnet-interactive-map' ),
'flex-end' => esc_html__( 'Right', 'rusnet-interactive-map' ),
'space-between' => esc_html__( 'Space between', 'rusnet-interactive-map' ),
'space-around' => esc_html__( 'Space around', 'rusnet-interactive-map' ),
'space-evenly' => esc_html__( 'Space evenly', 'rusnet-interactive-map' ),
],
],
'cat_filter_button_tag' => [
'title' => esc_html__( 'Button tag', 'rusnet-interactive-map' ),
'type' => 'select',
'desc' => esc_html__( 'HTML element used for filter buttons.', 'rusnet-interactive-map' ),
'options' => [
'button' => '<button>',
'a' => '<a>',
'div' => '<div>',
'span' => '<span>',
],
],
'cat_filter_button_font_size' => [
'title' => esc_html__( 'Font size', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => esc_html__( 'e.g., 14px, 1rem, 1.2em. Leave empty for default.', 'rusnet-interactive-map' ),
],
'cat_filter_button_color' => [
'title' => esc_html__( 'Text color', 'rusnet-interactive-map' ),
'type' => 'color',
'desc' => esc_html__( 'Color of the button text (normal state).', 'rusnet-interactive-map' ),
],
'cat_filter_button_bg_color' => [
'title' => esc_html__( 'Background color', 'rusnet-interactive-map' ),
'type' => 'color',
'desc' => esc_html__( 'Background color of the button (normal state).', 'rusnet-interactive-map' ),
],
'cat_filter_button_border' => [
'title' => esc_html__( 'Border', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => __( 'CSS border property, e.g. <code>1px solid #ccc</code>.', 'rusnet-interactive-map' ),
],
'cat_filter_button_box_shadow' => [
'title' => esc_html__( 'Box shadow', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => __( 'CSS box-shadow, e.g. <code>0 2px 4px rgba(0,0,0,0.1)</code>.', 'rusnet-interactive-map' ),
],
'cat_filter_button_padding' => [
'title' => esc_html__( 'Padding', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => __( 'Button padding, e.g. <code>6px 12px</code>.', 'rusnet-interactive-map' ),
],
'cat_filter_button_border_radius' => [
'title' => esc_html__( 'Border radius', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => __( 'Border radius, e.g. <code>4px</code>.', 'rusnet-interactive-map' ),
],
'cat_filter_button_active_color' => [
'title' => esc_html__( 'Active text color', 'rusnet-interactive-map' ),
'type' => 'color',
'desc' => esc_html__( 'Text color when button is active (selected category).', 'rusnet-interactive-map' ),
],
'cat_filter_button_active_bg_color' => [
'title' => esc_html__( 'Active background color', 'rusnet-interactive-map' ),
'type' => 'color',
'desc' => esc_html__( 'Background color when button is active.', 'rusnet-interactive-map' ),
],
'cat_filter_button_active_border' => [
'title' => esc_html__( 'Active border', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => __( 'Border when button is active <code>2px solid #ff0000</code>.', 'rusnet-interactive-map' ),
],
];
foreach ( $cat_filter_fields as $id => $field ) {
add_settings_field(
$id,
$field['title'],
[ $this, 'display_field' ],
$this->page_hook . '_category_filter',
'category_filter_section',
[
'id' => $id,
'type' => $field['type'],
'desc' => $field['desc'],
'options' => $field['options'] ?? []
]
);
}
// ADDED HOOK: after category filter fields
do_action( 'rusnetim_after_category_filter_fields', $this->page_hook . '_category_filter' );
add_settings_section( 'markers_section', esc_html__( 'Marker options', 'rusnet-interactive-map' ), '', $this->page_hook . '_markers' );
$markers_fields = [
'type_icon_option' => [
'title' => esc_html__( 'Icon preset', 'rusnet-interactive-map' ),
'type' => 'text',
'desc' => '<a href="https://tech.yandex.com/maps/doc/jsapi/2.1/ref/reference/option.presetStorage-docpage/" target="_blank">' . esc_html__( 'Other icon types', 'rusnet-interactive-map' ) . '</a>',
],
'color_icon_option' => [
'title' => esc_html__( 'Marker color', 'rusnet-interactive-map' ),
'type' => 'color',
'desc' => esc_html__( 'For example:', 'rusnet-interactive-map' ) . ' #1e98ff',
],
'custom_icon_option' => [
'title' => esc_html__( 'Custom icon', 'rusnet-interactive-map' ),
'type' => 'custom_icon',
'desc' => esc_html__( 'Upload your own icon image', 'rusnet-interactive-map' ),
],
'custom_icon_width_option' => [
'title' => esc_html__( 'Icon width', 'rusnet-interactive-map' ),
'type' => 'number',
'desc' => esc_html__( 'Width in pixels (optional)', 'rusnet-interactive-map' ),
],
'custom_icon_height_option' => [
'title' => esc_html__( 'Icon height', 'rusnet-interactive-map' ),
'type' => 'number',
'desc' => esc_html__( 'Height in pixels (optional)', 'rusnet-interactive-map' ),
],
];
foreach ( $markers_fields as $id => $field ) {
add_settings_field(
$id,
$field['title'],
[ $this, 'display_field' ],
$this->page_hook . '_markers',
'markers_section',
[ 'id' => $id, 'type' => $field['type'], 'desc' => $field['desc'] ]
);
}
// ADDED HOOK: after markers fields
do_action( 'rusnetim_after_markers_fields', $this->page_hook . '_markers' );
add_settings_section( 'apikey_section', esc_html__( 'Yandex.Maps API key', 'rusnet-interactive-map' ), '', $this->page_hook . '_general' );
add_settings_field(
'apikey_map_option',
esc_html__( 'API key', 'rusnet-interactive-map' ),
[ $this, 'display_field' ],
$this->page_hook . '_general',
'apikey_section',
[
'id' => 'apikey_map_option',
'type' => 'text',
'desc' => __( '<a href="https://developer.tech.yandex.com/services/">Get a key</a> (JavaScript API & HTTP Geocoder) if necessary', 'rusnet-interactive-map' ),
]
);
add_settings_section( 'reset_section', esc_html__( 'Reset options', 'rusnet-interactive-map' ), '', $this->page_hook . '_general' );
add_settings_field(
'reset_maps_option',
esc_html__( 'Reset options', 'rusnet-interactive-map' ),
[ $this, 'display_field' ],
$this->page_hook . '_general',
'reset_section',
[
'id' => 'reset_maps_option',
'type' => 'checkbox',
'desc' => esc_html__( 'Restore defaults', 'rusnet-interactive-map' ),
]
);
// ADDED HOOK: after all general fields (including apikey and reset)
do_action( 'rusnetim_after_general_fields', $this->page_hook . '_general' );
do_action( 'rusnetim_after_register_settings', $this->page_hook );
}
public function display_field( $args ) {
$id = $args['id'];
$type = $args['type'];
$desc = $args['desc'] ?? '';
$options = Rusnetim_Options::get_all();
$value = $options[ $id ] ?? '';
switch ( $type ) {
case 'text':
?>
<input class="regular-text" type="text" id="<?php echo esc_attr( $id ); ?>" name="rusnetim_options[<?php echo esc_attr( $id ); ?>]" value="<?php echo esc_attr( $value ); ?>" />
<?php if ( $desc ) echo '<br /><span class="description">' . wp_kses_post( $desc ) . '</span>'; ?>
<?php
break;
case 'textarea':
?>
<textarea class="code large-text" cols="50" rows="2" id="<?php echo esc_attr( $id ); ?>" name="rusnetim_options[<?php echo esc_attr( $id ); ?>]"><?php echo esc_textarea( $value ); ?></textarea>
<?php if ( $desc ) echo '<br /><span class="description">' . wp_kses_post( $desc ) . '</span>'; ?>
<?php
break;
case 'checkbox':
?>
<input type="hidden" name="rusnetim_options[<?php echo esc_attr( $id ); ?>]" value="off" />
<input type="checkbox" id="<?php echo esc_attr( $id ); ?>" name="rusnetim_options[<?php echo esc_attr( $id ); ?>]" <?php checked( $value, 'on' ); ?> />
<label for="<?php echo esc_attr( $id ); ?>"><?php echo wp_kses_post( $desc ); ?></label>
<?php
break;
case 'color':
?>
<input type="text" id="<?php echo esc_attr( $id ); ?>" name="rusnetim_options[<?php echo esc_attr( $id ); ?>]" value="<?php echo esc_attr( $value ); ?>" class="rusnetim-color-field" data-default-color="#1e98ff" />
<?php if ( $desc ) echo '<br /><span class="description">' . wp_kses_post( $desc ) . '</span>'; ?>
<?php
break;
case 'custom_icon':
?>
<div id="global-icon-wrapper">
<div style="display: flex; gap: 5px; align-items: center;">
<input type="text" id="<?php echo esc_attr( $id ); ?>" name="rusnetim_options[<?php echo esc_attr( $id ); ?>]" value="<?php echo esc_attr( $value ); ?>" class="regular-text" style="flex:1;" />
<button type="button" class="button rusnetim-upload-icon-btn"><?php esc_html_e( 'Select Image', 'rusnet-interactive-map' ); ?></button>
</div>
<div id="global-icon-preview" style="margin-top: 10px;"></div>
<button type="button" id="global-icon-remove" class="button" style="display: none;"><?php esc_html_e( 'Remove', 'rusnet-interactive-map' ); ?></button>
<?php if ( $desc ) echo "<br /><span class='description'>" . wp_kses_post( $desc ) . "</span>"; ?>
</div>
<?php
break;
case 'number':
?>
<input type="number" id="<?php echo esc_attr( $id ); ?>" name="rusnetim_options[<?php echo esc_attr( $id ); ?>]" value="<?php echo esc_attr( $value ); ?>" min="0" step="1" />
<?php if ( $desc ) echo '<br /><span class="description">' . wp_kses_post( $desc ) . '</span>'; ?>
<?php
break;
case 'select':
?>
<select id="<?php echo esc_attr( $id ); ?>" name="rusnetim_options[<?php echo esc_attr( $id ); ?>]">
<?php foreach ( $args['options'] as $opt_value => $opt_label ) : ?>
<option value="<?php echo esc_attr( $opt_value ); ?>" <?php selected( $value, $opt_value ); ?>>
<?php echo esc_html( $opt_label ); ?>
</option>
<?php endforeach; ?>
</select>
<?php if ( $desc ) echo '<br /><span class="description">' . wp_kses_post( $desc ) . '</span>'; ?>
<?php
break;
case 'preset':
$presets = rusnetim_get_category_filter_presets();
?>
<div class="rusnetim-presets">
<div class="rusnetim-preset-buttons">
<?php foreach ( $presets as $key => $preset ) : ?>
<button type="button" class="rusnetim-preset-btn button" data-preset="<?php echo esc_attr( $key ); ?>">
<?php echo esc_html( $preset['label'] ); ?>
</button>
<?php endforeach; ?>
</div>
<div class="rusnetim-preview-box">
<button class="preview-btn preview-normal" style="display:inline-block;"><?php esc_html_e( 'Normal', 'rusnet-interactive-map' ); ?></button>
<button class="preview-btn preview-active" style="display:inline-block;"><?php esc_html_e( 'Active', 'rusnet-interactive-map' ); ?></button>
</div>
<?php if ( ! empty( $args['desc'] ) ) : ?>
<p class="description"><?php echo wp_kses_post( $args['desc'] ); ?></p>
<?php endif; ?>
</div>
<?php
break;
}
}
public function enqueue_scripts( $hook_suffix ) {
$is_options_page = ( $hook_suffix === 'toplevel_page_' . $this->page_hook ) || ( isset( $_GET['page'] ) && $_GET['page'] === $this->page_hook );
$is_marker_edit = ( $hook_suffix === 'post.php' || $hook_suffix === 'post-new.php' ) && get_post_type() === 'rusnetim_marker';
$is_category_edit = ( $hook_suffix === 'edit-tags.php' || $hook_suffix === 'term.php' ) && isset( $_GET['taxonomy'] ) && $_GET['taxonomy'] === 'rusnetim_category';
if ( $is_options_page || $is_marker_edit || $is_category_edit ) {
wp_enqueue_media();
wp_enqueue_style( 'wp-color-picker' );
wp_enqueue_script( 'wp-color-picker' );
$active_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general';
if ( $is_options_page && $active_tab === 'general' ) {
$defaults = Rusnetim_Options::get_all();
$locale = Rusnetim_Options::sanitize_locale( get_locale() );
$apikey = $defaults['apikey_map_option'];
$api_url = Rusnetim_API::get_api_url( $locale, $apikey );
wp_enqueue_script( 'yandex-maps-api', $api_url, [], null, true );
$preview_data = [
'center' => array_map( 'floatval', explode( ',', $defaults['center_map_option'] ) ),
'zoom' => (int) $defaults['zoom_map_option'],
'type' => $defaults['type_map_option'],
'controls' => Rusnetim_Options::sanitize_controls( $defaults['controls_map_option'] ),
'icon_preset'=> $defaults['type_icon_option'],
'icon_color' => $defaults['color_icon_option'],
'apikey' => $apikey,
];
wp_enqueue_script( 'rusnetim-admin-preview', RUSNETIM_PLUGIN_URL . 'assets/js/admin/admin-map-preview.js', [ 'jquery' ], RUSNETIM_VERSION, true );
wp_localize_script( 'rusnetim-admin-preview', 'rusnetim_admin_preview', $preview_data );
$custom_css = "
#addcontrol a {
cursor: pointer;
color: #2271b1;
text-decoration: underline;
}
#addcontrol a:hover {
color: #135e96;
}
";
wp_add_inline_style( 'common', $custom_css );
}
wp_localize_script( 'rusnetim-admin-iconset', 'rusnetim_admin', [
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'rusnetim_ajax_nonce' ),
'i18n' => [
'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();
});
');
if ( $is_options_page && isset( $_GET['tab'] ) && $_GET['tab'] === 'category_filter' ) {
wp_enqueue_script(
'rusnetim-admin-presets',
RUSNETIM_PLUGIN_URL . 'assets/js/admin/admin-presets.js',
[ 'jquery' ],
RUSNETIM_VERSION,
true
);
$presets = rusnetim_get_category_filter_presets();
wp_localize_script( 'rusnetim-admin-presets', 'rusnetimPresets', $presets );
$preview_css = '
.rusnetim-preset-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 15px;
}
.rusnetim-preset-btn {
padding: 6px 12px;
border: 1px solid #ccc;
background: #f7f7f7;
cursor: pointer;
border-radius: 3px;
}
.rusnetim-preset-btn:hover {
background: #eaeaea;
}
.rusnetim-preview-box {
display: flex;
gap: 20px;
align-items: center;
margin-top: 15px;
padding: 20px;
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: 5px;
}
.preview-btn {
display: inline-block;
cursor: default;
pointer-events: none;
margin: 0;
}';
wp_register_style( 'rusnetim-admin-presets-styles', false );
wp_enqueue_style( 'rusnetim-admin-presets-styles' );
wp_add_inline_style( 'rusnetim-admin-presets-styles', $preview_css );
}
}
}
public function add_tinymce_plugin( $plugin_array ) {
$handle = 'rusnetim-tinymce';
$src = RUSNETIM_PLUGIN_URL . 'assets/js/tinymce/plugin.js';
wp_register_script( $handle, $src, [], RUSNETIM_VERSION, true );
wp_localize_script( $handle, 'rusnetim_defaults', Rusnetim_Options::get_all() );
wp_localize_script( $handle, 'rusnetim_object', $this->get_tinymce_l10n() );
wp_enqueue_script( $handle );
$plugin_array['rusnetim_plugin'] = $src;
return $plugin_array;
}
public function add_tinymce_button( $buttons ) {
array_push( $buttons, 'rusnetim' );
return $buttons;
}
public function output_tinymce_template() {
include_once RUSNETIM_PLUGIN_DIR . 'templates/tmpl-editor-rusnetim.html';
}
private function get_tinymce_l10n() {
return [
'YaMap' => esc_html__( 'Map', 'rusnet-interactive-map' ),
'AddMap' => esc_html__( 'Add map', 'rusnet-interactive-map' ),
'EditMap' => esc_html__( 'Edit map', 'rusnet-interactive-map' ),
'PluginTitle' => esc_html__( 'Rusnet Interactive Map', 'rusnet-interactive-map' ),
'MarkerTab' => esc_html__( 'Placemark', 'rusnet-interactive-map' ),
'MapTab' => esc_html__( 'Map', 'rusnet-interactive-map' ),
'MarkerIcon' => esc_html__( 'Icon', 'rusnet-interactive-map' ),
'BlueOnly' => esc_html__( 'Blue only', 'rusnet-interactive-map' ),
'MarkerUrl' => esc_html__( 'Link', 'rusnet-interactive-map' ),
'MarkerUrlTip' => esc_html__( 'Placemark hyperlink url or post ID', 'rusnet-interactive-map' ),
'MapHeight' => esc_html__( 'Map height', 'rusnet-interactive-map' ),
'MarkerName' => esc_html__( 'Placemark name', 'rusnet-interactive-map' ),
'MarkerNameTip' => esc_html__( 'Text for hint or icon content', 'rusnet-interactive-map' ),
'MapControlsTip' => esc_html__( 'Use the links below', 'rusnet-interactive-map' ),
'MarkerCoord' => esc_html__( 'Coordinates', 'rusnet-interactive-map' ),
'NoCoord' => esc_html__( 'Click on the map to choose or create the mark', 'rusnet-interactive-map' ),
'MapControls' => esc_html__( 'Map controls', 'rusnet-interactive-map' ),
'MarkerDelete' => esc_html__( 'Delete', 'rusnet-interactive-map' ),
'type' => esc_html__( 'Map type', 'rusnet-interactive-map' ),
'zoom' => esc_html__( 'Zoom', 'rusnet-interactive-map' ),
'ScrollZoom' => esc_html__( 'Wheel zoom', 'rusnet-interactive-map' ),
'MobileDrag' => esc_html__( 'Mobile drag', 'rusnet-interactive-map' ),
'search' => esc_html__( 'Search', 'rusnet-interactive-map' ),
'route' => esc_html__( 'Route', 'rusnet-interactive-map' ),
'ruler' => esc_html__( 'Ruler', 'rusnet-interactive-map' ),
'traffic' => esc_html__( 'Traffic', 'rusnet-interactive-map' ),
'fullscreen' => esc_html__( 'Full screen', 'rusnet-interactive-map' ),
'geolocation' => esc_html__( 'Geolocation', 'rusnet-interactive-map' ),
'MarkerColor' => esc_html__( 'Marker color', 'rusnet-interactive-map' ),
'MapContainerID' => esc_html__( 'Put in ID', 'rusnet-interactive-map' ),
'MapContainerIDTip'=> esc_html__( 'Do not create a block in the content. Use the existing block of the WP theme with the specified ID', 'rusnet-interactive-map' ),
'ClusterTab' => esc_html__( 'Clustering', 'rusnet-interactive-map' ),
'ClusterEnable' => esc_html__( 'Enable clustering', 'rusnet-interactive-map' ),
'ClusterGrid' => esc_html__( 'Cluster grid size (px)', 'rusnet-interactive-map' ),
'ClusterInfo' => esc_html__( 'Clustering groups nearby markers into clusters.<br>The grid size determines how close markers need to be to form a cluster.', 'rusnet-interactive-map' ),
'Extra' => esc_html__( 'Extra', 'rusnet-interactive-map' ),
'DeveloperInfoTab' => esc_html__( 'Design & Development', 'rusnet-interactive-map' ),
'ExtraHTML' => '',
];
}
public function option_page() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'rusnet-interactive-map' ) );
}
$tabs = array(
'general' => esc_html__( 'General', 'rusnet-interactive-map' ),
'clustering' => esc_html__( 'Clustering', 'rusnet-interactive-map' ),
'infopanel' => esc_html__( 'Info Panel', 'rusnet-interactive-map' ),
'category_filter' => esc_html__( 'Category Filter', 'rusnet-interactive-map' ),
'markers' => esc_html__( 'Markers', 'rusnet-interactive-map' ),
'info' => esc_html__( 'Info', 'rusnet-interactive-map' ),
);
$tabs = apply_filters( 'rusnetim_settings_tabs', $tabs );
$active_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general';
$defaults = Rusnetim_Options::get_all();
?>
<div class="wrap">
<h1><?php esc_html_e( 'Rusnet Interactive Map', 'rusnet-interactive-map' ); ?></h1>
<h2 class="nav-tab-wrapper">
<?php foreach ( $tabs as $tab_key => $tab_label ) : ?>
<a href="?page=<?php echo esc_attr( $this->page_hook ); ?>&tab=<?php echo esc_attr( $tab_key ); ?>"
class="nav-tab <?php echo $active_tab === $tab_key ? 'nav-tab-active' : ''; ?>">
<?php echo esc_html( $tab_label ); ?>
</a>
<?php endforeach; ?>
</h2>
<?php settings_errors(); ?>
<?php
if ( array_key_exists( $active_tab, $tabs ) ) {
switch ( $active_tab ) {
case 'general':
?>
<form method="post" id="RusnetimOptions" enctype="multipart/form-data" action="options.php">
<?php wp_nonce_field( 'rusnetim_options_verify', 'rusnetim_options_nonce' ); ?>
<div id="yamap" style="position: relative; min-height: 15rem; margin-bottom: 1rem;"></div><br />
<?php
settings_fields( 'rusnetim_options' );
do_settings_sections( $this->page_hook . '_general' );
submit_button();
?>
</form>
<?php
break;
case 'clustering':
?>
<form method="post" action="options.php">
<?php
settings_fields( 'rusnetim_options' );
do_settings_sections( $this->page_hook . '_clustering' );
submit_button();
?>
</form>
<?php
break;
case 'infopanel':
?>
<form method="post" action="options.php">
<?php
settings_fields( 'rusnetim_options' );
do_settings_sections( $this->page_hook . '_infopanel' );
submit_button();
?>
</form>
<?php
break;
case 'category_filter':
?>
<form method="post" action="options.php">
<?php
settings_fields( 'rusnetim_options' );
do_settings_sections( $this->page_hook . '_category_filter' );
submit_button();
?>
</form>
<?php
break;
case 'markers':
?>
<form method="post" action="options.php">
<?php
settings_fields( 'rusnetim_options' );
do_settings_sections( $this->page_hook . '_markers' );
submit_button();
?>
</form>
<?php
break;
case 'info':
?>
<div class="thanks" style="border: 4px #ffdb4d solid; background: #fff; padding: 0 1rem 2rem 1rem; display: flex; flex-wrap: wrap; gap: 20px;">
<div style="flex:1; min-width: 280px;">
<?php
echo wp_kses_post(
__(
'<div style="position: relative; display: block; width: 100%; white-space: normal !important;">
<h2 style="color: #444;font-size: 18px;font-weight: 600;line-height: 36px;">Need other icon types?</h2>
Additional icon types can be found in the
<a href="https://tech.yandex.com/maps/doc/jsapi/2.1/ref/reference/option.presetStorage-docpage/" style="white-space: normal">Yandex.Maps documentation</a>.
</div>
<div style="position: relative; display: block; width: 100%; white-space: normal !important; margin-top: 20px;">
<h2 style="color: #444;font-size: 18px;font-weight: 600;line-height: 36px;">Like the Rusnet Interactive Map plugin?</h2>
You can support its development by making a donation
(<a href="https://rusnet.su/donate" style="white-space: normal">on the ANO "Rusnet" website</a>)
or leave a positive review in the
<a href="https://wordpress.org/support/plugin/rusnet-interactive-map/reviews/" style="white-space: normal">plugin repository</a>.
It\'s very motivating!
</div>
<div style="position: relative; display: block; width: 100%; white-space: normal !important; margin-top: 20px;">
<h2 style="color: #444;font-size: 18px;font-weight: 600;line-height: 36px;">Any questions?</h2>
Ask them on the
<a href="https://rusnet.su" style="white-space: normal">ANO "Rusnet" website</a>
or in the
<a href="https://wordpress.org/support/plugin/rusnet-interactive-map" style="white-space: normal">WordPress support forum</a>.
</div>',
'rusnet-interactive-map'
)
);
?>
</div>
<div style="flex:1; min-width: 280px;">
<?php
echo wp_kses_post(
__(
'<div style="position: relative; display: block; width: 100%; white-space: normal !important;">
<h2 style="color: #444;font-size: 18px;font-weight: 600;line-height: 36px;">Support and development</h2>
The Rusnet Interactive Map plugin is now maintained and developed by the ANO "Rusnet" team.<br>
We are open to cooperation and custom modifications for your projects.
<p style="margin-top: .5rem; text-align: center;">
<b>Contacts:</b>
<a href="mailto:info@rusnet.su">info@rusnet.su</a>,
<a href="https://rusnet.su">https://rusnet.su</a>
</p>
</div>',
'rusnet-interactive-map'
)
);
?>
</div>
</div>
<?php
break;
default:
do_action( 'rusnetim_admin_tab_content', $active_tab );
break;
}
} else {
do_action( 'rusnetim_admin_tab_content', $active_tab );
}
?>
</div>
<?php
}
}
function rusnetim_get_category_filter_presets() {
$presets = array(
'default' => array(
'label' => esc_html__( 'Default', 'rusnet-interactive-map' ),
'fields' => array(
'cat_filter_button_font_size' => '',
'cat_filter_button_color' => '',
'cat_filter_button_bg_color' => '',
'cat_filter_button_border' => '',
'cat_filter_button_box_shadow' => '',
'cat_filter_button_padding' => '6px 12px',
'cat_filter_button_border_radius' => '4px',
'cat_filter_button_active_color' => '',
'cat_filter_button_active_bg_color' => '',
'cat_filter_button_active_border' => '',
),
),
'modern' => array(
'label' => esc_html__( 'Modern', 'rusnet-interactive-map' ),
'fields' => array(
'cat_filter_button_font_size' => '14px',
'cat_filter_button_color' => '#333333',
'cat_filter_button_bg_color' => '#f5f5f5',
'cat_filter_button_border' => '1px solid #cccccc',
'cat_filter_button_box_shadow' => '0 2px 4px rgba(0,0,0,0.1)',
'cat_filter_button_padding' => '8px 16px',
'cat_filter_button_border_radius' => '20px',
'cat_filter_button_active_color' => '#ffffff',
'cat_filter_button_active_bg_color' => '#0073aa',
'cat_filter_button_active_border' => '1px solid #0073aa',
),
),
'minimal' => array(
'label' => esc_html__( 'Minimal', 'rusnet-interactive-map' ),
'fields' => array(
'cat_filter_button_font_size' => '13px',
'cat_filter_button_color' => '#555555',
'cat_filter_button_bg_color' => 'transparent',
'cat_filter_button_border' => 'none',
'cat_filter_button_box_shadow' => 'none',
'cat_filter_button_padding' => '4px 8px',
'cat_filter_button_border_radius' => '0',
'cat_filter_button_active_color' => '#000000',
'cat_filter_button_active_bg_color' => '#e0e0e0',
'cat_filter_button_active_border' => 'none',
),
),
);
return apply_filters( 'rusnetim_category_filter_presets', $presets );
}