Changeset 3357122
- Timestamp:
- 09/06/2025 01:59:35 PM (7 months ago)
- Location:
- blogcard-for-wp
- Files:
-
- 5 edited
-
assets/screenshot-1.png (modified) (previous)
-
assets/screenshot-2.png (modified) (previous)
-
assets/screenshot-3.png (modified) (previous)
-
trunk/blogcard-for-wp.php (modified) (1 diff)
-
trunk/readme.txt (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
blogcard-for-wp/trunk/blogcard-for-wp.php
r2942236 r3357122 1 1 <?php 2 /* 3 Plugin Name: Blogcard for WP 4 Plugin URI: https://e-joint.jp/works/blogcard-for-wp 5 Description: URLを貼るだけで、ブログカード風のリンクが作れるGutenbergブロックです。 6 Version: 1.0.7 7 Author: Takashi Fujisaki 8 Author URI: https://e-joint.jp 9 License: GPL-2.0-or-later 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 */ 12 13 /* 14 WP Blogcard is free software: you can redistribute it and/or modify 15 it under the terms of the GNU General Public License as published by 16 the Free Software Foundation, either version 2 of the License, or 17 any later version. 18 19 WP Blogcard is distributed in the hope that it will be useful, 20 but WITHOUT ANY WARRANTY; without even the implied warranty of 21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 GNU General Public License for more details. 23 24 You should have received a copy of the GNU General Public License 25 along with WP Blogcard. If not, see https://www.gnu.org/licenses/gpl-2.0.html. 26 */ 27 28 include_once plugin_dir_path(__FILE__) . 'inc/return_json.php'; 29 30 add_action('init', 'wpbc_block_init'); 31 function wpbc_block_init() 32 { 33 // Block JS 34 wp_register_script( 35 'wp-blogcard-editor', 36 plugins_url('dist/index.js', __FILE__), 37 [], 38 filemtime(plugin_dir_path(__FILE__) . 'dist/index.js'), 39 '', 40 true 41 ); 42 wp_set_script_translations('wp-blogcard-editor', 'wpbc-blocks'); 43 44 // Editor CSS 45 wp_register_style( 46 'wp-blogcard-editor', 47 plugins_url('dist/editor-style.css', __FILE__), 48 [], 49 filemtime(plugin_dir_path(__FILE__) . 'dist/editor-style.css'), 50 'all' 51 ); 52 53 // Frontend CSS 54 wp_register_style( 55 'wp-blogcard', 56 plugins_url('dist/style.css', __FILE__), 57 [], 58 filemtime(plugin_dir_path(__FILE__) . 'dist/style.css'), 59 'all' 60 ); 61 62 register_block_type('su/blogcard', [ 63 'editor_script' => 'wp-blogcard-editor', 64 'editor_style' => 'wp-blogcard-editor', 65 'style' => 'wp-blogcard' 2 3 /** 4 * Plugin Name: Blogcard for WP 5 * Plugin URI: https://github.com/takashi-fujisaki/hello-block 6 * Description: URLを入力してブログカードを生成するブロックプラグイン 7 * Version: 2.0.1 8 * Author: Takashi Fujisaki 9 * License: GPL v2 or later 10 * Text Domain: wpbc 11 */ 12 13 // 直接アクセスを防ぐ 14 if (!defined('ABSPATH')) { 15 exit; 16 } 17 18 // プラグインの定数 19 define('WPBC_PLUGIN_PATH', plugin_dir_path(__FILE__)); 20 21 /** 22 * プラグインの初期化 23 */ 24 function wpbc_init() { 25 // テキストドメインの読み込み 26 load_plugin_textdomain('wpbc', false, dirname(plugin_basename(__FILE__)) . '/languages'); 27 28 // ブロックの登録 29 register_block_type(WPBC_PLUGIN_PATH . 'build/block.json'); 30 } 31 add_action('init', 'wpbc_init'); 32 33 /** 34 * REST APIエンドポイントを追加 35 */ 36 function wpbc_rest_api_init() { 37 register_rest_route('wpbc/v1', '/metadata', [ 38 'methods' => 'POST', 39 'callback' => 'wpbc_get_metadata', 40 'permission_callback' => function () { 41 return current_user_can('edit_posts'); 42 }, 43 'args' => [ 44 'url' => [ 45 'required' => true, 46 'type' => 'string', 47 'sanitize_callback' => 'esc_url_raw', 48 ], 49 ], 66 50 ]); 67 } 68 69 /** 70 * Categories 71 * 72 * @param array $categories Categories. 73 * @param array $post Post. 74 */ 75 if (!function_exists('su_categories')) { 76 function su_categories($categories, $post) 77 { 78 return array_merge( 79 $categories, 80 [ 81 [ 82 'slug' => 'su', // ブロックカテゴリーのスラッグ. 83 'title' => 'su blocks' // ブロックカテゴリーの表示名. 84 // 'icon' => 'wordpress', //アイコンの指定(Dashicons名). 85 ] 86 ] 51 52 register_rest_route('wpbc/v1', '/internal-metadata', [ 53 'methods' => 'GET', 54 'callback' => 'wpbc_get_internal_metadata', 55 'permission_callback' => function () { 56 return current_user_can('edit_posts'); 57 }, 58 'args' => [ 59 'url' => [ 60 'required' => true, 61 'type' => 'string', 62 'sanitize_callback' => 'esc_url_raw', 63 ], 64 ], 65 ]); 66 67 register_rest_route('wpbc/v1', '/search', [ 68 'methods' => 'GET', 69 'callback' => 'wpbc_search_posts', 70 'permission_callback' => function () { 71 return current_user_can('edit_posts'); 72 }, 73 'args' => [ 74 'q' => [ 75 'required' => true, 76 'type' => 'string', 77 'sanitize_callback' => 'sanitize_text_field', 78 ], 79 ], 80 ]); 81 82 register_rest_route('wpbc/v1', '/clear-cache', [ 83 'methods' => 'POST', 84 'callback' => 'wpbc_clear_cache', 85 'permission_callback' => function () { 86 return current_user_can('edit_posts'); 87 }, 88 'args' => [ 89 'type' => [ 90 'type' => 'string', 91 'default' => 'plugin', 92 'enum' => ['plugin', 'all'] 93 ], 94 ], 95 ]); 96 } 97 add_action('rest_api_init', 'wpbc_rest_api_init'); 98 99 /** 100 * メタデータ取得のREST APIコールバック 101 */ 102 function wpbc_get_metadata($request) { 103 $url = $request->get_param('url'); 104 105 if (empty($url)) { 106 return new WP_Error('missing_url', 'URLが指定されていません。', ['status' => 400]); 107 } 108 109 $metadata = wpbc_fetch_metadata($url); 110 111 // WP_Errorオブジェクトの場合はそのまま返す 112 if (is_wp_error($metadata)) { 113 return $metadata; 114 } 115 116 // falseの場合は一般的なエラー 117 if ($metadata === false) { 118 return new WP_Error('metadata_fetch_failed', 'メタデータの取得に失敗しました。', ['status' => 500]); 119 } 120 121 return [ 122 'success' => true, 123 'data' => $metadata 124 ]; 125 } 126 127 /** 128 * 内部メタデータ取得のREST APIコールバック 129 */ 130 function wpbc_get_internal_metadata($request) { 131 $url = $request->get_param('url'); 132 133 if (empty($url)) { 134 return new WP_Error('missing_url', 'URLが指定されていません。', ['status' => 400]); 135 } 136 137 $metadata = wpbc_fetch_internal_metadata($url); 138 139 if (is_wp_error($metadata)) { 140 return $metadata; 141 } 142 143 return [ 144 'success' => true, 145 'data' => $metadata 146 ]; 147 } 148 149 /** 150 * URLが内部サイトのURLかどうかを判定する 151 */ 152 function wpbc_is_internal_url($url) { 153 if (empty($url)) { 154 return false; 155 } 156 157 $site_url = get_site_url(); 158 $parsed_site_url = parse_url($site_url); 159 $parsed_url = parse_url($url); 160 161 if (!$parsed_site_url || !$parsed_url) { 162 return false; 163 } 164 165 $site_host = $parsed_site_url['host']; 166 $url_host = $parsed_url['host']; 167 168 // 完全一致 169 if ($site_host === $url_host) { 170 return true; 171 } 172 173 // サブドメインのチェック 174 // 例:www.example.com は example.com のサブドメイン 175 $site_suffix = '.' . $url_host; 176 $url_suffix = '.' . $site_host; 177 if ( 178 substr($site_host, -strlen($site_suffix)) === $site_suffix || 179 substr($url_host, -strlen($url_suffix)) === $url_suffix 180 ) { 181 return true; 182 } 183 184 return false; 185 } 186 187 /** 188 * メタデータを取得する関数 189 */ 190 function wpbc_fetch_metadata($url) { 191 // キャッシュキーを生成 192 $cache_key = 'wpbc_metadata_' . md5($url); 193 194 // キャッシュから取得を試行 195 $cached_data = get_transient($cache_key); 196 if ($cached_data !== false) { 197 // キャッシュから取得した場合はcachedフラグを追加 198 $cached_data['cached'] = true; 199 return $cached_data; 200 } 201 202 // URLの検証 203 if (!filter_var($url, FILTER_VALIDATE_URL)) { 204 return new WP_Error('invalid_url', '無効なURL形式です。', ['status' => 400]); 205 } 206 207 // 内部URLの場合は内部メタデータ取得関数を使用 208 if (wpbc_is_internal_url($url)) { 209 $metadata = wpbc_fetch_internal_metadata($url); 210 if (is_wp_error($metadata)) { 211 return $metadata; 212 } 213 214 // キャッシュに保存(24時間) 215 set_transient($cache_key, $metadata, 24 * HOUR_IN_SECONDS); 216 217 return $metadata; 218 } 219 220 $metadata = [ 221 'title' => '', 222 'description' => '', 223 'thumbnail' => '', 224 'domain' => parse_url($url, PHP_URL_HOST), 225 'final_url' => $url // リダイレクト後の最終URL 226 ]; 227 228 // HTTPリクエストでメタデータを取得(リダイレクトを追跡) 229 $response = wp_remote_get($url, [ 230 'timeout' => 20, // タイムアウトを20秒に延長 231 'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 232 'redirection' => 5, // 最大5回までリダイレクトを追跡 233 'sslverify' => false, // SSL証明書の検証を無効化(テスト用) 234 'headers' => [ 235 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 236 'Accept-Language' => 'ja-JP,ja;q=0.9,en;q=0.8', 237 'Accept-Encoding' => 'gzip, deflate, br', 238 'Connection' => 'keep-alive', 239 'Upgrade-Insecure-Requests' => '1', 240 'Sec-Fetch-Dest' => 'document', 241 'Sec-Fetch-Mode' => 'navigate', 242 'Sec-Fetch-Site' => 'none', 243 'Cache-Control' => 'no-cache', 244 ] 245 ]); 246 247 // ネットワークエラーの場合 248 if (is_wp_error($response)) { 249 $error_message = $response->get_error_message(); 250 $error_code = 'network_error'; 251 $status_code = 500; 252 253 // タイムアウトエラーの場合は特別な処理 254 if (strpos($error_message, 'timeout') !== false || strpos($error_message, 'timed out') !== false) { 255 $error_code = 'timeout_error'; 256 $error_message = 'サーバーの応答が遅すぎます。しばらく時間をおいてから再度お試しください。'; 257 $status_code = 408; // Request Timeout 258 } elseif (strpos($error_message, 'Connection refused') !== false) { 259 $error_code = 'connection_refused'; 260 $error_message = 'サーバーに接続できませんでした。'; 261 $status_code = 503; // Service Unavailable 262 } elseif (strpos($error_message, 'SSL') !== false || strpos($error_message, 'certificate') !== false) { 263 $error_code = 'ssl_error'; 264 $error_message = 'SSL証明書の検証に失敗しました。'; 265 $status_code = 495; // SSL Certificate Error 266 } 267 268 // デバッグ用ログ 269 error_log('wpbc_fetch_metadata network error for URL: ' . $url . ' - Error: ' . $error_message . ' - Code: ' . $error_code); 270 return new WP_Error($error_code, $error_message, ['status' => $status_code]); 271 } 272 273 // HTTPステータスコードを取得 274 $response_code = wp_remote_retrieve_response_code($response); 275 276 // 200以外の場合はエラーを返す 277 if ($response_code !== 200) { 278 $error_message = 'HTTPエラーが発生しました'; 279 switch ($response_code) { 280 case 404: 281 $error_message = 'ページが見つかりません (404)'; 282 break; 283 case 403: 284 $error_message = 'アクセスが拒否されました (403)'; 285 break; 286 case 500: 287 $error_message = 'サーバーエラーが発生しました (500)'; 288 break; 289 case 503: 290 $error_message = 'サービスが利用できません (503)'; 291 break; 292 default: 293 $error_message = "HTTPエラーが発生しました ({$response_code})"; 294 break; 295 } 296 // デバッグ用ログ 297 error_log('wpbc_fetch_metadata HTTP error for URL: ' . $url . ' - Status: ' . $response_code . ' - Message: ' . $error_message); 298 return new WP_Error('http_error', $error_message, ['status' => $response_code]); 299 } 300 301 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) { 302 $body = wp_remote_retrieve_body($response); 303 304 // リダイレクト後の最終URLを取得 305 // wp_remote_get()は自動的にリダイレクトを追跡するので、 306 // レスポンスのURLを取得する 307 $response_url = wp_remote_retrieve_header($response, 'url'); 308 if ($response_url) { 309 $final_url = $response_url; 310 } else { 311 // ヘッダーから取得できない場合は元のURLを使用 312 $final_url = $url; 313 } 314 315 // 最終URLのドメインとURLを更新 316 $metadata['domain'] = parse_url($final_url, PHP_URL_HOST); 317 $metadata['final_url'] = $final_url; 318 319 // HTMLをパースしてメタデータを抽出 320 $dom = new DOMDocument(); 321 @$dom->loadHTML(mb_convert_encoding($body, 'HTML-ENTITIES', 'UTF-8')); 322 $xpath = new DOMXPath($dom); 323 324 // タイトルを取得 325 $title_nodes = $xpath->query('//title'); 326 if ($title_nodes->length > 0) { 327 $metadata['title'] = trim($title_nodes->item(0)->textContent); 328 } 329 330 // OGPメタタグを取得 331 $og_title = $xpath->query('//meta[@property="og:title"]/@content'); 332 if ($og_title->length > 0) { 333 $metadata['title'] = trim($og_title->item(0)->textContent); 334 } 335 336 // 説明文を取得 337 $description_nodes = $xpath->query('//meta[@name="description"]/@content'); 338 if ($description_nodes->length > 0) { 339 $metadata['description'] = trim($description_nodes->item(0)->textContent); 340 } 341 342 $og_description = $xpath->query('//meta[@property="og:description"]/@content'); 343 if ($og_description->length > 0) { 344 $metadata['description'] = trim($og_description->item(0)->textContent); 345 } 346 347 // サムネイル画像を取得 348 $og_image = $xpath->query('//meta[@property="og:image"]/@content'); 349 if ($og_image->length > 0) { 350 $metadata['thumbnail'] = trim($og_image->item(0)->textContent); 351 } 352 353 // ファビコンを取得 354 $favicon_url = wpbc_get_favicon_url($xpath, $url); 355 if ($favicon_url) { 356 $metadata['favicon'] = $favicon_url; 357 } 358 } 359 360 // 新しく取得した場合はcachedフラグをfalseに設定 361 $metadata['cached'] = false; 362 363 // キャッシュに保存(24時間) 364 set_transient($cache_key, $metadata, 24 * HOUR_IN_SECONDS); 365 366 return $metadata; 367 } 368 369 /** 370 * サイト内検索のREST APIコールバック 371 */ 372 function wpbc_search_posts($request) { 373 $query = $request->get_param('q'); 374 375 if (empty($query)) { 376 return new WP_Error('missing_query', '検索クエリが指定されていません。', ['status' => 400]); 377 } 378 379 // 投稿と固定ページを検索 380 $args = [ 381 'post_type' => ['post', 'page'], 382 'post_status' => 'publish', 383 's' => $query, 384 'posts_per_page' => -1, 385 'orderby' => 'relevance', 386 'order' => 'DESC' 387 ]; 388 389 $posts = get_posts($args); 390 $results = []; 391 392 // 投稿を先に並べる 393 $posts_sorted = []; 394 $pages_sorted = []; 395 396 foreach ($posts as $post) { 397 if (get_post_type($post->ID) === 'post') { 398 $posts_sorted[] = $post; 399 } else { 400 $pages_sorted[] = $post; 401 } 402 } 403 404 // 投稿と固定ページを結合(投稿を優先) 405 $sorted_posts = array_merge($posts_sorted, $pages_sorted); 406 407 foreach ($sorted_posts as $post) { 408 $results[] = [ 409 'id' => $post->ID, 410 'title' => get_the_title($post->ID), 411 'url' => get_permalink($post->ID), 412 'type' => get_post_type($post->ID), 413 'date' => get_the_date('Y-m-d', $post->ID), 414 'thumbnail' => get_the_post_thumbnail_url($post->ID, 'thumbnail') 415 ]; 416 } 417 418 return [ 419 'success' => true, 420 'data' => $results 421 ]; 422 } 423 424 /** 425 * 内部サイトのメタデータを取得する 426 */ 427 function wpbc_fetch_internal_metadata($url) { 428 // URLから投稿IDを取得 429 $post_id = url_to_postid($url); 430 431 if (!$post_id) { 432 return new WP_Error('post_not_found', '指定されたURLの投稿が見つかりません。', ['status' => 404]); 433 } 434 435 // 投稿のタイトルを取得 436 $title = get_the_title($post_id); 437 438 // 投稿の抜粋を取得 439 $excerpt = get_the_excerpt($post_id); 440 441 // サムネイル画像を取得(thumbnailサイズ) 442 $thumbnail_id = get_post_thumbnail_id($post_id); 443 $thumbnail_url = ''; 444 if ($thumbnail_id) { 445 $thumbnail_url = wp_get_attachment_image_url($thumbnail_id, 'thumbnail'); 446 } 447 448 return [ 449 'title' => $title, 450 'description' => $excerpt, 451 'thumbnail' => $thumbnail_url, 452 'url' => $url, 453 'cached' => false 454 ]; 455 } 456 457 /** 458 * キャッシュクリアのREST APIコールバック 459 */ 460 function wpbc_clear_cache($request) { 461 global $wpdb; 462 463 $type = $request->get_param('type'); 464 $cleared_count = 0; 465 466 if ($type === 'all') { 467 // 全てのWordPressキャッシュをクリア 468 $result = $wpdb->query( 469 "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_%' OR option_name LIKE '_transient_timeout_%'" 87 470 ); 88 } 89 add_filter('block_categories_all', 'su_categories', 10, 2); 90 } 91 92 add_action('enqueue_block_editor_assets', 'wpbc_enqueue_block_editor_assets'); 93 function wpbc_enqueue_block_editor_assets() 94 { 95 /** 96 * PHPで生成した値をJavaScriptに渡す 97 * 98 * 第1引数: 渡したいJavaScriptの名前(wp_enqueue_scriptの第1引数に書いたもの) 99 * 第2引数: JavaScript内でのオブジェクト名 100 * 第3引数: 渡したい値の配列 101 */ 102 wp_localize_script('wp-blogcard-editor', 'wpbcAjaxValues', [ 103 'api' => admin_url('admin-ajax.php'), 104 'action' => 'wpbc-action', 105 'nonce' => wp_create_nonce('wpbc-ajax'), 106 'actionRemoveCache' => 'wpbc-action-remove-cache', // cache削除用 107 'nonceRemoveCache' => wp_create_nonce('wpbc-ajax-remove-cache') // cache削除用 108 ]); 109 } 110 111 112 /** 113 * Ajaxで返すもの 114 */ 115 function wpbc_ajax() 116 { 117 if (wp_verify_nonce($_POST['nonce'], 'wpbc-ajax')) { 118 119 // キャッシュ機能を有効にするには第2引数をtrue 120 $json = wpbc_json($_POST, true); 121 echo $json; 122 123 die(); 124 } 125 } 126 add_action('wp_ajax_wpbc-action', 'wpbc_ajax'); 127 128 function wpbc_ajax_remove_cache() 129 { 130 if (wp_verify_nonce($_POST['nonce'], 'wpbc-ajax-remove-cache')) { 131 132 $transient_name = wpbc_transient_name($_POST['url']); 133 echo (delete_transient($transient_name)); 134 135 die(); 136 } 137 } 138 add_action('wp_ajax_wpbc-action-remove-cache', 'wpbc_ajax_remove_cache'); 139 140 /** 141 * wp-optionsに保存するcacheにつけるoption_name 142 * @param string $url 143 */ 144 function wpbc_transient_name($url) 145 { 146 return 'wpbc--' . rawurlencode($url); 147 } 471 $cleared_count = $result; 472 473 // オブジェクトキャッシュもクリア 474 if (function_exists('wp_cache_flush')) { 475 wp_cache_flush(); 476 } 477 478 $message = '全てのキャッシュをクリアしました。'; 479 } else { 480 // プラグインのキャッシュのみをクリア 481 $result = $wpdb->query( 482 "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_wpbc_metadata_%' OR option_name LIKE '_transient_timeout_wpbc_metadata_%'" 483 ); 484 $cleared_count = $result; 485 486 $message = 'ブログカードのキャッシュをクリアしました。'; 487 } 488 489 return [ 490 'success' => true, 491 'message' => $message, 492 'cleared_count' => $cleared_count 493 ]; 494 } 495 496 /** 497 * ファビコンを取得する関数 498 */ 499 function wpbc_get_favicon_url($xpath, $base_url) { 500 $favicon_url = null; 501 502 // ファビコンのURLを取得するクエリ(優先順位順) 503 $queries = [ 504 "//link[@rel='icon' and @type='image/svg+xml']", 505 "//link[@rel='icon' and @type='image/png']", 506 "//link[@rel='shortcut icon']", 507 "//link[contains(@rel, 'icon')]", 508 "//link[@rel='apple-touch-icon']", 509 "//meta[@name='msapplication-TileImage']" 510 ]; 511 512 foreach ($queries as $query) { 513 $results = $xpath->query($query); 514 foreach ($results as $item) { 515 if ($item instanceof DOMElement) { 516 $attr = ($item->tagName == 'meta') ? 'content' : 'href'; 517 $temp_url = $item->getAttribute($attr); 518 519 // URLを絶対URLに変換 520 $temp_url = wpbc_convert_to_absolute_url($temp_url, $base_url); 521 522 // ファビコンのURLが有効かチェック 523 if (wpbc_check_favicon_exists($temp_url)) { 524 return $temp_url; 525 } 526 } 527 } 528 } 529 530 // ドメイン直下のファビコンを探す 531 $default_favicons = [ 532 wpbc_convert_to_absolute_url("/favicon.svg", $base_url), 533 wpbc_convert_to_absolute_url("/favicon.ico", $base_url) 534 ]; 535 536 foreach ($default_favicons as $favicon_url) { 537 if (wpbc_check_favicon_exists($favicon_url)) { 538 return $favicon_url; 539 } 540 } 541 542 return null; 543 } 544 545 /** 546 * 相対URLを絶対URLに変換する 547 */ 548 function wpbc_convert_to_absolute_url($url, $base_url) { 549 if (preg_match('/^https?:\/\//', $url)) { 550 return $url; 551 } 552 553 $parts = parse_url($base_url); 554 $scheme = $parts['scheme']; 555 $host = $parts['host']; 556 557 if (strpos($url, '/') === 0) { 558 return "$scheme://$host$url"; 559 } 560 561 $base_path = isset($parts['path']) ? dirname($parts['path']) : ''; 562 return "$scheme://$host" . rtrim($base_path, '/') . '/' . ltrim($url, '/'); 563 } 564 565 /** 566 * ファビコンが存在するかチェックする 567 */ 568 function wpbc_check_favicon_exists($url) { 569 $headers = @get_headers($url, 1); 570 return $headers && strpos($headers[0], '200 OK') !== false; 571 } -
blogcard-for-wp/trunk/readme.txt
r2942236 r3357122 1 1 === Blogcard for WP === 2 2 Contributors: ejointjp 3 Donate link: https://e-joint.jp4 3 Tags: link, blog, blogcard, card, url 5 Requires at least: 5.06 Tested up to: 6. 2.27 Stable tag: 1.0.7 8 Requires PHP: 7.0 4 Requires at least: 6.4 5 Tested up to: 6.8.2 6 Requires PHP: 7.4 7 Stable tag: 2.0.1 9 8 License: GPLv2 or later 10 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 10 12 URLを貼るだけでnoteやZennのようなサムネイルつきのリンク、ブログカードを表示できます。 11 A WordPress plugin that makes it easy to create blog cards. Simply enter a URL and automatically fetch metadata to display beautiful cards. 13 12 14 13 == Description == 15 14 16 WordPress 標準のURL埋め込み機能は、リンク先がWordPressサイトでなければ埋め込むことができません。15 WordPress's standard URL embedding feature only works with WordPress sites. 17 16 18 そこで、あらゆるリンク先をサムネイルつきのカードでスタイリッシュに表示するためにこのプラグインを作りました。 17 This plugin was created to display any link destination as a stylish card with thumbnails. 19 18 20 URLをブロックエディターに貼り付けるだけ。あっという間にブログカードを表示できます。 19 Just paste the URL into the block editor. Blog cards are displayed in an instant. 21 20 21 New features from version 2.0.0 include the ability to create blog cards from site search. 22 This allows you to search for articles within your own site and convert them into blog cards. 23 24 == Key Features == 25 26 * **Easy Operation**: Simply enter a URL and press Enter to instantly generate a blog card 27 * **Universal Compatibility**: Works with non-WordPress sites (note, Zenn, Qiita, GitHub, etc.) 28 * **Automatic Metadata Fetching**: Automatically retrieves title, description, and thumbnail images 29 * **Customizable**: Retrieved information can be edited later 30 * **SEO Friendly**: Set rel attributes like nofollow, nofollow, sponsored, ugc 31 * **Responsive Design**: Beautiful display on mobile devices 32 * **Caching**: Caches retrieved metadata for fast display 33 * **Site Search**: Search and convert internal posts into blog cards 34 * **Dark Mode Support**: Supports dark mode with .dark class 35 36 == Installation == 37 38 1. Upload plugin files to `/wp-content/plugins/blogcard-for-wp/` directory 39 2. Activate the plugin from the 'Plugins' menu in WordPress admin 40 3. Add the 'Blog Card' block in post/page editor 41 4. Enter a URL and press Enter to automatically generate a blog card 42 43 == Usage == 44 45 === Basic Usage === 46 47 1. Click the '+' button in the post/page editor 48 2. Search for and select 'Blog Card' 49 3. Enter the destination URL in the URL input field 50 4. Press Enter to automatically fetch metadata and generate a blog card 51 52 === Advanced Settings === 53 54 When the block is selected, the following settings appear in the right sidebar: 55 56 * **URL**: Destination URL 57 * **TARGET Attribute**: How to open the link (_blank, _self, etc.) 58 * **Rel Attributes**: noopener, nofollow, noreferrer, sponsored, ugc 59 * **Thumbnail**: Show/hide thumbnail image 60 * **Title**: Manual title editing 61 * **Description**: Manual description editing 62 * **Thumbnail Image**: Manual thumbnail image setting 22 63 23 64 == Frequently Asked Questions == 24 65 66 = Can any site URL be converted to a blog card? = 67 68 Yes, basically any site URL can be converted to a blog card. However, some sites may not allow metadata retrieval. 69 70 = Can retrieved metadata be edited? = 71 72 Yes, title, description, and thumbnail images can be edited later. Link attributes (target, rel, etc.) can also be changed freely. 73 74 = How is caching managed? = 75 76 Retrieved metadata is automatically cached and displayed quickly for the same URL. Cache can be manually cleared from the admin screen. 77 78 = Does it display correctly on mobile? = 79 80 Yes, it supports responsive design and displays beautifully on mobile devices. 81 82 = Does it affect SEO? = 83 84 By setting appropriate rel attributes (nofollow, etc.), you can create SEO-friendly links. 85 25 86 == Screenshots == 26 87 27 1. URLを入力しEnterするだけで、瞬時にリンク先の情報のカードを作成します。88 1. Instantly create beautiful blog cards. 28 89 29 2. フロント側の表示はこんな感じ。90 2. Very easy to use - just enter a URL and press Enter. 30 91 31 3. 取得した情報は後から編集できます。また、リンクの属性情報も自由に変更できます。 92 3. New feature from v2.0.0 - Create blog cards from site search. 93 94 4. Detailed settings are available in the right sidebar. 95 96 5. Detailed settings are available in the right sidebar. 32 97 33 98 == Changelog == 34 99 100 = 2.0.0 = 101 * Significantly improved block editor UI 102 * Added site search functionality 103 * Enhanced error handling 104 * Optimized performance 105 * Improved responsive design 106 * Enhanced accessibility 107 35 108 = 1.0.7 = 36 バグ修正 109 * Bug fixes 110 * Improved metadata retrieval stability 37 111 38 112 = 1.0.0 = 39 リリース 113 * Initial release 114 * Basic blog card functionality 115 * Automatic metadata fetching 116 * Customization features 117 118 == Upgrade Notice == 119 120 = 2.0.0 = 121 Major feature improvements and UI refresh have been implemented. Existing blog cards will continue to work, but we recommend upgrading to take advantage of new features. 122 123 = 1.0.7 = 124 This is a bug fix version. We recommend upgrading. 125 126 == Support == 127 128 For questions about the plugin or bug reports, please use the following methods: 129 130 * GitHub Issues: [Plugin repository URL] 131 * Email: [Support email address] 132 133 == Developer Information == 134 135 This plugin is developed using the following technologies: 136 137 * WordPress Block Editor (Gutenberg) 138 * React 139 * WordPress REST API 140 * PHP 7.4 or higher 141 142 If you would like to customize or add features, please send a pull request to the GitHub repository. 143 144 == License == 145 146 This plugin is released under the GPLv2 or later license. 147 148 == Acknowledgments == 149 150 In developing this plugin, we referenced the following open source projects: 151 152 * WordPress 153 * React 154 * Other open source libraries 155 156 == Development == 157 158 This plugin is actively maintained and developed. Contributions are welcome! 159 160 For development setup and contribution guidelines, please refer to the GitHub repository.
Note: See TracChangeset
for help on using the changeset viewer.