current_page() ? $classes . ' tpg-admin' : $classes; } public function menu() { $icon = 'data:image/svg+xml;base64,' . base64_encode( '' ); add_menu_page( '3pika Gallery', '3pika Gallery', 'edit_posts', 'tpika-galleries', array( $this, 'page_list' ), $icon, 26 ); add_submenu_page( 'tpika-galleries', __( 'All Galleries', '3pika-gallery' ), __( 'All Galleries', '3pika-gallery' ), 'edit_posts', 'tpika-galleries', array( $this, 'page_list' ) ); add_submenu_page( 'tpika-galleries', __( 'Create New Gallery', '3pika-gallery' ), __( 'Create New Gallery', '3pika-gallery' ), 'edit_posts', 'tpika-new', array( $this, 'page_builder' ) ); add_submenu_page( 'tpika-galleries', __( 'Settings', '3pika-gallery' ), __( 'Settings', '3pika-gallery' ), 'manage_options', 'tpika-settings', array( $this, 'page_settings' ) ); $label = __( 'Go Premium', '3pika-gallery' ); add_submenu_page( 'tpika-galleries', __( 'Premium', '3pika-gallery' ), $label, 'manage_options', 'tpika-premium', array( $this, 'page_premium' ) ); } /** * Current admin page slug if it's ours. * * @return string */ private function current_page() { $page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification return in_array( $page, self::PAGES, true ) ? $page : ''; } public function assets( $hook ) { $page = $this->current_page(); if ( ! $page ) { return; } $ver = TPIKA_VERSION . '.' . (int) @filemtime( TPIKA_PATH . 'assets/admin/admin.css' ); // cache-bust after every update wp_enqueue_style( 'tpika-admin', TPIKA_URL . 'assets/admin/admin.css', array(), $ver ); wp_enqueue_script( 'tpika-admin', TPIKA_URL . 'assets/admin/admin.js', array( 'jquery' ), TPIKA_VERSION . '.' . (int) @filemtime( TPIKA_PATH . 'assets/admin/admin.js' ), true ); wp_localize_script( 'tpika-admin', 'TPIKA_ADMIN', array( 'i18n' => array( 'copied' => __( 'Copied!', '3pika-gallery' ), 'confirmDelete' => __( 'Delete this gallery permanently? Images stay in the Media Library.', '3pika-gallery' ), 'confirmTitle' => __( 'Are you sure?', '3pika-gallery' ), 'cancel' => __( 'Cancel', '3pika-gallery' ), 'ok' => __( 'Confirm', '3pika-gallery' ), 'deleteBtn' => __( 'Delete', '3pika-gallery' ), 'noFile' => __( 'No file selected', '3pika-gallery' ), 'selectWm' => __( 'Select image', '3pika-gallery' ), 'use' => __( 'Use this image', '3pika-gallery' ), ), ) ); if ( 'tpika-new' === $page ) { wp_enqueue_media(); wp_enqueue_script( 'tpika-builder', TPIKA_URL . 'assets/admin/builder.js', array( 'jquery', 'jquery-ui-sortable', 'tpika-admin' ), TPIKA_VERSION, true ); $id = isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification $g = $id ? TPIKA_Post_Type::get( $id ) : null; wp_localize_script( 'tpika-builder', 'TPIKA_BUILDER', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'tpika_admin' ), 'id' => $g ? $g['id'] : 0, 'title' => $g ? $g['title'] : '', 'images' => $g ? TPIKA_Post_Type::images_for_admin( $g['images'] ) : array(), 'filters' => $g ? $g['filters'] : array(), 'editUrl' => tpika_admin_url( 'tpika-new' ), 'previewNonceUrl' => home_url( '/' ), 'i18n' => array( 'addImages' => __( 'Add images', '3pika-gallery' ), 'useImages' => __( 'Add to gallery', '3pika-gallery' ), 'selectPoster' => __( 'Select poster image', '3pika-gallery' ), 'selectWm' => __( 'Select watermark image', '3pika-gallery' ), 'use' => __( 'Use this image', '3pika-gallery' ), 'saved' => __( 'Gallery saved.', '3pika-gallery' ), 'saving' => __( 'Saving…', '3pika-gallery' ), 'save' => __( 'Save Gallery', '3pika-gallery' ), 'error' => __( 'Could not save. Please try again.', '3pika-gallery' ), 'newFilter' => __( 'New filter', '3pika-gallery' ), /* translators: %d: number of selected items */ 'removeSelected' => __( 'Remove %d selected item(s) from the gallery?', '3pika-gallery' ), 'deleteFilter' => __( 'Delete this filter? It will be removed from all images.', '3pika-gallery' ), 'unsaved' => __( 'You have unsaved changes.', '3pika-gallery' ), 'invalidVideo' => __( 'Please enter a valid YouTube, Vimeo or MP4 URL.', '3pika-gallery' ), 'noFilters' => __( 'No filters yet. Create filters in the Filters tab.', '3pika-gallery' ), 'video' => __( 'Video', '3pika-gallery' ), 'copied' => __( 'Copied!', '3pika-gallery' ), /* translators: %d: number of items */ 'items' => __( '%d items', '3pika-gallery' ), /* translators: %d: number of selected items */ 'selected' => __( '%d selected', '3pika-gallery' ), 'titleRequired' => __( 'Please give the gallery a name.', '3pika-gallery' ), 'remove' => __( 'Remove', '3pika-gallery' ), 'deleteBtn' => __( 'Delete', '3pika-gallery' ), 'colorAuto' => __( 'Automatic: follows the accent color (Settings → Layout). Click to choose an own color.', '3pika-gallery' ), 'colorOwn' => __( 'Own color for this filter', '3pika-gallery' ), 'colorReset' => __( 'Back to automatic (accent color)', '3pika-gallery' ), 'textAuto' => __( 'Text color: automatic (black or white depending on the background). Click to choose.', '3pika-gallery' ), 'textOwn' => __( 'Own text color for this filter', '3pika-gallery' ), 'textReset' => __( 'Back to automatic text color', '3pika-gallery' ), 'all' => __( 'All', '3pika-gallery' ), 'noFilter' => __( 'No filter', '3pika-gallery' ), 'appliedAll' => __( 'Colors applied to all filters.', '3pika-gallery' ), 'selectVideo' => __( 'Select video', '3pika-gallery' ), 'useVideo' => __( 'Use this video', '3pika-gallery' ), ), ) ); } elseif ( 'tpika-settings' === $page ) { wp_enqueue_media(); } } /* ---------------- Pages ---------------- */ public function page_list() { include TPIKA_PATH . 'admin/views/list.php'; } public function page_builder() { $id = isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification if ( $id && ! current_user_can( 'edit_post', $id ) ) { wp_die( esc_html__( 'You are not allowed to edit this gallery.', '3pika-gallery' ) ); } $gallery = $id ? TPIKA_Post_Type::get( $id ) : null; $settings = $gallery ? $gallery['settings'] : TPIKA_Settings::get(); include TPIKA_PATH . 'admin/views/builder.php'; } public function page_settings() { $settings = TPIKA_Settings::get(); include TPIKA_PATH . 'admin/views/settings.php'; } public function page_premium() { include TPIKA_PATH . 'admin/views/premium.php'; } /* ---------------- Field renderer (shared by builder + settings) ---------------- */ /** * Render one settings field row. * * @param string $key Key. * @param array $field Definition. * @param mixed $value Current value. */ public static function field( $key, $field, $value ) { $name = 's[' . $key . ']'; $id = 'tpg-f-' . $key; $class = 'tpg-field'; $extra = ''; // Extra attribute of the control. ?>
>
', esc_attr( $name ), esc_attr( $id ), esc_attr( $extra ) ); foreach ( $field['options'] as $ov => $ol ) { $opt_extra = ''; printf( '', esc_attr( $ov ), selected( (string) $value, (string) $ov, false ), esc_attr( $opt_extra ), esc_html( $ol ) ); } echo ''; break; case 'checkbox': printf( '', esc_attr( $name ), esc_attr( $id ), checked( ! empty( $value ), true, false ), esc_attr( $extra ) ); break; case 'number': printf( '', esc_attr( $name ), esc_attr( $id ), esc_attr( $value ), isset( $field['min'] ) ? (int) $field['min'] : 0, isset( $field['max'] ) ? (int) $field['max'] : 9999, esc_attr( $extra ) ); break; case 'radius': // All corners (linked) or four separate corners; the corner string lives in the sibling "radius_corners" setting. $corners = isset( $GLOBALS['tpika_field_values']['radius_corners'] ) ? (string) $GLOBALS['tpika_field_values']['radius_corners'] : ''; $linked = ! preg_match( '/^\d+ \d+ \d+ \d+$/', $corners ); $parts = $linked ? array_fill( 0, 4, (int) $value ) : array_map( 'intval', explode( ' ', $corners ) ); $titles = array( __( 'Top left', '3pika-gallery' ), __( 'Top right', '3pika-gallery' ), __( 'Bottom right', '3pika-gallery' ), __( 'Bottom left', '3pika-gallery' ) ); $min = isset( $field['min'] ) ? (int) $field['min'] : 0; $max = isset( $field['max'] ) ? (int) $field['max'] : 200; printf( '
', esc_attr( $linked ? 'tpg-radius is-linked' : 'tpg-radius' ) ); printf( '', esc_attr( $name ), esc_attr( $id ), (int) $value, (int) $min, (int) $max ); printf( '', esc_attr__( 'Link / unlink corners', '3pika-gallery' ), wp_kses( tpika_icon( 'lock' ) . tpika_icon( 'unlock' ), tpika_kses_icons() ) ); echo ''; foreach ( $parts as $ci => $cv ) { printf( '', (int) $ci, esc_attr( $titles[ $ci ] ), (int) $cv, (int) $min, (int) $max ); } echo ''; printf( '', esc_attr( $linked ? '' : $corners ) ); echo '
'; break; case 'color': printf( '
', esc_attr( $name ), esc_attr( $id ), esc_attr( $value ? $value : '#2271b1' ), esc_attr( $extra ) ); if ( ! empty( $field['presets'] ) ) { echo ''; foreach ( $field['presets'] as $hex ) { printf( '', esc_attr( strtolower( (string) $value ) === strtolower( $hex ) ? 'tpg-preset is-on' : 'tpg-preset' ), esc_attr( $hex ), esc_attr( $hex ), esc_attr( $hex ), esc_attr( $extra ) ); } echo ''; } echo '
'; break; case 'choice': echo '
'; foreach ( $field['options'] as $ov => $ol ) { $opt_class = 'tpg-choice'; $opt_extra = $extra; printf( ''; } echo '
'; break; case 'filter_select': // Options are filled by builder.js from the gallery's filters. printf( '', esc_attr( $name ), esc_attr( $id ), esc_attr( $value ), esc_html__( 'All (no filter)', '3pika-gallery' ) ); break; default: printf( '', esc_attr( $name ), esc_attr( $id ), esc_attr( $value ), esc_attr( $extra ) ); } if ( ! empty( $field['desc'] ) ) { echo '

' . esc_html( $field['desc'] ) . '

'; } ?>
$section ) { echo '

' . esc_html( $section['title'] ) . '

'; foreach ( $section['fields'] as $key => $field ) { if ( ! empty( $field['hidden'] ) ) { continue; // Stored by another control. } if ( 'global' === $context && 'filter_select' === $field['type'] ) { continue; // Only meaningful inside a gallery. } self::field( $key, $field, isset( $values[ $key ] ) ? $values[ $key ] : '' ); } echo '
'; } } /* ---------------- Notices ---------------- */ private function flash( $type, $text ) { set_transient( 'tpika_msg_' . get_current_user_id(), array( $type, $text ), 60 ); } public function notices() { if ( ! $this->current_page() ) { return; } $msg = get_transient( 'tpika_msg_' . get_current_user_id() ); if ( $msg ) { delete_transient( 'tpika_msg_' . get_current_user_id() ); // Shown as our own toast (admin.js) instead of a WordPress notice box. echo ''; } } /* ---------------- Handlers (admin-post) ---------------- */ public function handle_delete() { $id = isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0; check_admin_referer( 'tpika_delete_' . $id ); if ( ! current_user_can( 'delete_post', $id ) || TPIKA_Post_Type::TYPE !== get_post_type( $id ) ) { wp_die( esc_html__( 'Not allowed.', '3pika-gallery' ) ); } wp_delete_post( $id, true ); $this->flash( 'success', __( 'Gallery deleted.', '3pika-gallery' ) ); wp_safe_redirect( tpika_admin_url( 'tpika-galleries' ) ); exit; } public function handle_settings() { check_admin_referer( 'tpika_settings' ); if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Not allowed.', '3pika-gallery' ) ); } if ( isset( $_POST['tpika_reset'] ) ) { update_option( TPIKA_Settings::OPTION, TPIKA_Settings::defaults() ); $this->flash( 'success', __( 'Settings reset to defaults.', '3pika-gallery' ) ); } else { $input = isset( $_POST['s'] ) && is_array( $_POST['s'] ) ? map_deep( wp_unslash( $_POST['s'] ), 'sanitize_text_field' ) : array(); // Unchecked checkboxes are absent from POST: treat missing checkbox keys as 0. foreach ( TPIKA_Settings::flat_fields() as $key => $field ) { if ( 'checkbox' !== $field['type'] || isset( $input[ $key ] ) ) { continue; } $input[ $key ] = 0; } // Every value is validated against the settings schema (type, allowed options, range). $clean = TPIKA_Settings::sanitize( $input, TPIKA_Settings::get() ); TPIKA_Settings::update( $clean ); $this->flash( 'success', __( 'Settings saved.', '3pika-gallery' ) ); } wp_safe_redirect( tpika_admin_url( 'tpika-settings' ) ); exit; } }