'string', 'single' => true, 'show_in_rest' => true, 'auth_callback' => static function ( $allowed, $meta_key, $post_id ) { return current_user_can( 'edit_post', $post_id ); }, ) ); } } } /** * The "Short link" panel in the block editor's document sidebar. */ public static function enqueue_editor_panel() { if ( ! self::user_can_use() ) { return; } $screen = get_current_screen(); if ( ! $screen || ! in_array( $screen->post_type, self::post_types(), true ) ) { return; } wp_enqueue_script( 'useclick-editor', USECLICK_PLUGIN_URL . 'assets/js/editor.js', array( 'wp-plugins', 'wp-editor', 'wp-element', 'wp-components', 'wp-data', 'wp-i18n', 'wp-a11y', 'wp-dom-ready' ), USECLICK_VERSION, true ); wp_add_inline_script( 'useclick-editor', 'window.useclickEditor = ' . wp_json_encode( array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'useclick_nonce' ), 'dashboardUrl' => USECLICK_APP_URL . '/dashboard', ) ) . ';', 'before' ); wp_set_script_translations( 'useclick-editor', 'useclick' ); wp_enqueue_style( 'useclick-editor', USECLICK_PLUGIN_URL . 'assets/css/editor.css', array(), USECLICK_VERSION ); } /** * AJAX: click total for a post's short link (block editor panel). */ public static function ajax_post_link_stats() { UseClick_Admin::verify_ajax( self::capability() ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce checked in verify_ajax(). $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0; if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) { wp_send_json_error( array( 'message' => __( 'You are not allowed to edit this post.', 'useclick' ) ), 403 ); } $slug = (string) get_post_meta( $post_id, self::META_SLUG, true ); if ( '' === $slug ) { wp_send_json_success( array( 'clicks' => null ) ); } $api = new UseClick_API(); $counts = $api->get_clicks_for_slugs( array( $slug ) ); wp_send_json_success( array( 'clicks' => isset( $counts[ $slug ] ) ? (int) $counts[ $slug ] : null ) ); } /** * Post types that get the box and the column. * * @return string[] */ public static function post_types() { $types = get_post_types( array( 'public' => true, 'show_ui' => true, ) ); unset( $types['attachment'] ); /** * Filters the post types that get UseClick.io short links. * * @param string[] $types Post type names. */ return (array) apply_filters( 'useclick_post_types', array_values( $types ) ); } /** * Capability needed (in addition to editing the post) to create a short link for it. * * @return string */ public static function capability() { /** * Filters who can create short links for posts. Links count against the * site's UseClick.io plan, so the default is editors and administrators. * * @param string $capability Default 'edit_others_posts'. */ return (string) apply_filters( 'useclick_post_link_capability', 'edit_others_posts' ); } /** * Whether the current user may use the feature at all. * * @return bool */ private static function user_can_use() { return UseClick_Admin::has_api_key() && current_user_can( self::capability() ); } /** * Add the editor box. */ public static function add_meta_box() { if ( ! self::user_can_use() ) { return; } foreach ( self::post_types() as $type ) { // Classic editor only: the block editor gets the sidebar panel (assets/js/editor.js). add_meta_box( 'useclick-short-link', __( 'Short link', 'useclick' ), array( __CLASS__, 'render_meta_box' ), $type, 'side', 'default', array( '__back_compat_meta_box' => true ) ); } } /** * Render the editor box. * * @param WP_Post $post Post being edited. */ public static function render_meta_box( $post ) { $useclick_post_id = (int) $post->ID; $useclick_short_url = (string) get_post_meta( $post->ID, self::META_URL, true ); $useclick_slug = (string) get_post_meta( $post->ID, self::META_SLUG, true ); $useclick_published = 'publish' === get_post_status( $post ); $useclick_clicks = null; if ( '' !== $useclick_slug ) { $api = new UseClick_API(); $counts = $api->get_clicks_for_slugs( array( $useclick_slug ) ); $useclick_clicks = isset( $counts[ $useclick_slug ] ) ? $counts[ $useclick_slug ] : null; } include USECLICK_PLUGIN_DIR . 'templates/partials/post-link.php'; } /** * Register the list column for every supported post type. */ public static function register_columns() { if ( ! self::user_can_use() ) { return; } foreach ( self::post_types() as $type ) { add_filter( 'manage_' . $type . '_posts_columns', array( __CLASS__, 'add_column' ) ); add_action( 'manage_' . $type . '_posts_custom_column', array( __CLASS__, 'render_column' ), 10, 2 ); } } /** * Add the column after the title. * * @param array $columns Columns. * @return array */ public static function add_column( $columns ) { $out = array(); foreach ( $columns as $key => $label ) { $out[ $key ] = $label; if ( 'title' === $key ) { $out['useclick_short_link'] = __( 'Short link', 'useclick' ); } } if ( ! isset( $out['useclick_short_link'] ) ) { $out['useclick_short_link'] = __( 'Short link', 'useclick' ); } return $out; } /** * Render the column cell. * * @param string $column Column key. * @param int $post_id Post ID. */ public static function render_column( $column, $post_id ) { if ( 'useclick_short_link' !== $column ) { return; } $short_url = (string) get_post_meta( $post_id, self::META_URL, true ); $slug = (string) get_post_meta( $post_id, self::META_SLUG, true ); if ( '' !== $short_url ) { $counts = self::list_counts(); echo '