Plugin Directory

Changeset 3357122


Ignore:
Timestamp:
09/06/2025 01:59:35 PM (7 months ago)
Author:
ejointjp
Message:

v2.0.1

Location:
blogcard-for-wp
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • blogcard-for-wp/trunk/blogcard-for-wp.php

    r2942236 r3357122  
    11<?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// 直接アクセスを防ぐ
     14if (!defined('ABSPATH')) {
     15  exit;
     16}
     17
     18// プラグインの定数
     19define('WPBC_PLUGIN_PATH', plugin_dir_path(__FILE__));
     20
     21/**
     22 * プラグインの初期化
     23 */
     24function 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}
     31add_action('init', 'wpbc_init');
     32
     33/**
     34 * REST APIエンドポイントを追加
     35 */
     36function 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    ],
    6650  ]);
    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}
     97add_action('rest_api_init', 'wpbc_rest_api_init');
     98
     99/**
     100 * メタデータ取得のREST APIコールバック
     101 */
     102function 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 */
     130function 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 */
     152function 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 */
     190function 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 */
     372function 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 */
     427function 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 */
     460function 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_%'"
    87470    );
    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 */
     499function 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 */
     548function 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 */
     568function 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  
    11=== Blogcard for WP ===
    22Contributors: ejointjp
    3 Donate link: https://e-joint.jp
    43Tags: link, blog, blogcard, card, url
    5 Requires at least: 5.0
    6 Tested up to: 6.2.2
    7 Stable tag: 1.0.7
    8 Requires PHP: 7.0
     4Requires at least: 6.4
     5Tested up to: 6.8.2
     6Requires PHP: 7.4
     7Stable tag: 2.0.1
    98License: GPLv2 or later
    109License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1110
    12 URLを貼るだけでnoteやZennのようなサムネイルつきのリンク、ブログカードを表示できます。
     11A WordPress plugin that makes it easy to create blog cards. Simply enter a URL and automatically fetch metadata to display beautiful cards.
    1312
    1413== Description ==
    1514
    16 WordPress標準のURL埋め込み機能は、リンク先がWordPressサイトでなければ埋め込むことができません。
     15WordPress's standard URL embedding feature only works with WordPress sites.
    1716
    18 そこで、あらゆるリンク先をサムネイルつきのカードでスタイリッシュに表示するためにこのプラグインを作りました。
     17This plugin was created to display any link destination as a stylish card with thumbnails.
    1918
    20 URLをブロックエディターに貼り付けるだけ。あっという間にブログカードを表示できます。
     19Just paste the URL into the block editor. Blog cards are displayed in an instant.
    2120
     21New features from version 2.0.0 include the ability to create blog cards from site search.
     22This 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
     381. Upload plugin files to `/wp-content/plugins/blogcard-for-wp/` directory
     392. Activate the plugin from the 'Plugins' menu in WordPress admin
     403. Add the 'Blog Card' block in post/page editor
     414. Enter a URL and press Enter to automatically generate a blog card
     42
     43== Usage ==
     44
     45=== Basic Usage ===
     46
     471. Click the '+' button in the post/page editor
     482. Search for and select 'Blog Card'
     493. Enter the destination URL in the URL input field
     504. Press Enter to automatically fetch metadata and generate a blog card
     51
     52=== Advanced Settings ===
     53
     54When 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
    2263
    2364== Frequently Asked Questions ==
    2465
     66= Can any site URL be converted to a blog card? =
     67
     68Yes, 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
     72Yes, 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
     76Retrieved 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
     80Yes, it supports responsive design and displays beautifully on mobile devices.
     81
     82= Does it affect SEO? =
     83
     84By setting appropriate rel attributes (nofollow, etc.), you can create SEO-friendly links.
     85
    2586== Screenshots ==
    2687
    27 1. URLを入力しEnterするだけで、瞬時にリンク先の情報のカードを作成します。
     881. Instantly create beautiful blog cards.
    2889
    29 2. フロント側の表示はこんな感じ。
     902. Very easy to use - just enter a URL and press Enter.
    3091
    31 3. 取得した情報は後から編集できます。また、リンクの属性情報も自由に変更できます。
     923. New feature from v2.0.0 - Create blog cards from site search.
     93
     944. Detailed settings are available in the right sidebar.
     95
     965. Detailed settings are available in the right sidebar.
    3297
    3398== Changelog ==
    3499
     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
    35108= 1.0.7 =
    36 バグ修正
     109* Bug fixes
     110* Improved metadata retrieval stability
    37111
    38112= 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 =
     121Major 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 =
     124This is a bug fix version. We recommend upgrading.
     125
     126== Support ==
     127
     128For 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
     135This 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
     142If you would like to customize or add features, please send a pull request to the GitHub repository.
     143
     144== License ==
     145
     146This plugin is released under the GPLv2 or later license.
     147
     148== Acknowledgments ==
     149
     150In developing this plugin, we referenced the following open source projects:
     151
     152* WordPress
     153* React
     154* Other open source libraries
     155
     156== Development ==
     157
     158This plugin is actively maintained and developed. Contributions are welcome!
     159
     160For development setup and contribution guidelines, please refer to the GitHub repository.
Note: See TracChangeset for help on using the changeset viewer.