content_search = new skyinpo_Content_Search(); $this->link_suggestions = new skyinpo_Link_Suggestions($this->content_search, new skyinpo_Keyword_Matcher()); } /** * Boot the plugin. */ public static function init() { $plugin = new self(); add_action('enqueue_block_editor_assets', array($plugin, 'enqueue_block_editor_assets')); add_action('admin_enqueue_scripts', array($plugin, 'enqueue_classic_editor_assets')); add_action('rest_api_init', array($plugin, 'register_rest_routes')); add_action('wp_ajax_skyinpo_search_posts', array($plugin, 'ajax_search_posts')); add_action('wp_ajax_skyinpo_suggest_links', array($plugin, 'ajax_suggest_links')); add_action('post_submitbox_misc_actions', array($plugin, 'render_classic_suggestion_button')); add_filter('mce_external_plugins', array($plugin, 'register_tinymce_plugin')); add_filter('mce_buttons', array($plugin, 'register_tinymce_button')); } /** * Add the inline editor search/link tool. */ public function enqueue_block_editor_assets() { $asset_path = SKYINPO_PLUGIN_PATH . 'assets/js/editor.js'; $style_path = SKYINPO_PLUGIN_PATH . 'assets/css/editor.css'; wp_enqueue_script( 'skyinpo-internal-post-linker-editor', SKYINPO_PLUGIN_URL . 'assets/js/editor.js', array( 'wp-block-editor', 'wp-components', 'wp-compose', 'wp-editor', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-rich-text', 'wp-url', 'wp-data', 'wp-plugins', 'wp-edit-post', ), file_exists($asset_path) ? filemtime($asset_path) : SKYINPO_VERSION, true ); wp_enqueue_style( 'skyinpo-internal-post-linker-editor', SKYINPO_PLUGIN_URL . 'assets/css/editor.css', array('wp-components'), file_exists($style_path) ? filemtime($style_path) : SKYINPO_VERSION ); wp_localize_script( 'skyinpo-internal-post-linker-editor', 'skyinpo_InternalPostLinker', $this->get_editor_settings() ); } /** * Add Classic Editor assets on post editing screens. * * @param string $hook Current admin hook. */ public function enqueue_classic_editor_assets($hook) { if (!in_array($hook, array('post.php', 'post-new.php'), true)) { return; } $script_path = SKYINPO_PLUGIN_PATH . 'assets/js/classic-editor.js'; $style_path = SKYINPO_PLUGIN_PATH . 'assets/css/classic-editor.css'; wp_register_script( 'skyinpo-internal-post-linker-classic-editor', SKYINPO_PLUGIN_URL . 'assets/js/classic-editor.js', array(), file_exists($script_path) ? filemtime($script_path) : SKYINPO_VERSION, false ); wp_add_inline_script( 'skyinpo-internal-post-linker-classic-editor', 'window.skyinpo_InternalPostLinkerClassic = ' . wp_json_encode($this->get_editor_settings()) . ';', 'before' ); wp_enqueue_script('skyinpo-internal-post-linker-classic-editor'); wp_enqueue_style( 'skyinpo-internal-post-linker-classic-editor', SKYINPO_PLUGIN_URL . 'assets/css/classic-editor.css', array(), file_exists($style_path) ? filemtime($style_path) : SKYINPO_VERSION ); } /** * Register the TinyMCE plugin script. * * @param array $plugins TinyMCE plugins. * @return array */ public function register_tinymce_plugin($plugins) { if (!current_user_can('edit_posts') && !current_user_can('edit_pages')) { return $plugins; } $plugins['skyinpo_internal_post_linker'] = SKYINPO_PLUGIN_URL . 'assets/js/classic-editor.js'; return $plugins; } /** * Register the TinyMCE toolbar button. * * @param string[] $buttons TinyMCE buttons. * @return string[] */ public function register_tinymce_button($buttons) { if (!current_user_can('edit_posts') && !current_user_can('edit_pages')) { return $buttons; } $link_position = array_search('link', $buttons, true); if (false === $link_position) { $buttons[] = 'skyinpo_internal_post_linker'; return $buttons; } array_splice($buttons, $link_position + 1, 0, 'skyinpo_internal_post_linker'); return $buttons; } /** * Register REST routes used by the editor UI. */ public function register_rest_routes() { register_rest_route( $this->namespace, '/search', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array($this, 'search_posts'), 'permission_callback' => array($this, 'can_search_posts'), 'args' => array( 'term' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '', ), 'post_id' => array( 'type' => 'integer', 'sanitize_callback' => 'absint', 'default' => 0, ), ), ) ); } /** * Confirm the current user can use the post editor. * * @return bool */ public function can_search_posts() { return current_user_can('edit_posts'); } /** * Search linkable posts over admin-ajax for editor requests. */ public function ajax_search_posts() { check_ajax_referer('skyinpo_search_posts', 'nonce'); if (!$this->can_search_posts()) { wp_send_json_error( array('message' => __('You are not allowed to search posts.', '7sky-internal-post-linker')), 403 ); } $results = $this->content_search->get_search_results( isset($_GET['term']) ? sanitize_text_field(wp_unslash($_GET['term'])) : '', isset($_GET['post_id']) ? absint(wp_unslash($_GET['post_id'])) : 0 ); wp_send_json_success($results); } /** * Suggest internal links for the current editor content. */ public function ajax_suggest_links() { check_ajax_referer('skyinpo_suggest_links', 'nonce'); if (!$this->can_search_posts()) { wp_send_json_error( array('message' => __('You are not allowed to scan posts.', '7sky-internal-post-linker')), 403 ); } $content = isset($_POST['content']) ? wp_kses_post(wp_unslash((string) $_POST['content'])) : ''; $current_post_id = isset($_POST['post_id']) ? absint(wp_unslash($_POST['post_id'])) : 0; wp_send_json_success($this->link_suggestions->get_link_suggestions($content, $current_post_id)); } /** * Render a Classic Editor publish-box entry for link suggestions. */ public function render_classic_suggestion_button() { if (!$this->can_search_posts()) { return; } printf( '
', esc_html__('Suggest internal links', '7sky-internal-post-linker') ); } /** * Search linkable internal content. * * @param WP_REST_Request $request Request object. * @return WP_REST_Response */ public function search_posts(WP_REST_Request $request) { $term = trim((string) $request->get_param('term')); $current_post_id = absint($request->get_param('post_id')); return rest_ensure_response($this->content_search->get_search_results($term, $current_post_id)); } /** * Return shared editor settings. * * @return array */ private function get_editor_settings() { return array( 'searchPath' => '/' . $this->namespace . '/search', 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('skyinpo_search_posts'), 'suggestNonce' => wp_create_nonce('skyinpo_suggest_links'), 'currentPostId' => get_the_ID() ? absint(get_the_ID()) : 0, 'minSearchLength' => 2, 'searchTimeoutMs' => 8000, 'suggestionLimit' => 20, ); } }