<?php //外部ブログカード関数
/**
 * Cocoon WordPress Theme
 * @author: yhira
 * @link: https://wp-cocoon.com/
 * @license: http://www.gnu.org/licenses/gpl-2.0.html GPL v2 or later
 */
if ( !defined( 'ABSPATH' ) ) exit;

//外部URLからブログをカードタグの取得
if ( !function_exists( 'url_to_external_blog_card_tag' ) ):
function url_to_external_blog_card_tag($url){
  $url = strip_tags($url);//URL

  // 無限ループ対策: 自身のURLの場合は外部・内部関わらずいかなるフェーズでも処理しない
  // (スキーム違いHTTP/HTTPSやクエリ違いなどによるセーフガードすり抜けを防止)
  if ( is_current_url_same($url) ) {
    return;
  }

  //サイトの内部リンクは処理しない場合（※wpForoページは外部リンクとして処理する）
  if ( includes_home_url($url) && !includes_wpforo_url($url) ) {
    $id = cocoon_url_to_postid( $url );//IDを取得（URLから投稿IDへ変換・キャッシュ対応）
    if ( $id && get_post_status($id) ) {//IDを取得できる場合はループを飛ばす
      return;
    }//IDが取得できない場合は外部リンクとして処理する
  }

  //独自ブログカード（キャッシュ）の利用
  $tag = url_to_external_ogp_blogcard_tag($url);

  return $tag;
}
endif;

//本文中の外部URLをはてなブログカードタグに変更する
if ( !function_exists( 'url_to_external_blog_card' ) ):
function url_to_external_blog_card($the_content) {
  // return $the_content;
  // //ブロックエディターのブログカード用の本文整形
  // $the_content = fix_blogcard_content($the_content);
  //1行にURLのみが期待されている行（URL）を全て$mに取得
  $res = preg_match_all('/^(<p[^>]*?>)?(<a[^>]+?>)?https?:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+(<\/a>)?(?!.*<br *\/?>).*?(<\/p>)?/im', $the_content,$m);

  //マッチしたURL一つ一つをループしてカードを作成
  foreach ($m[0] as $match) {

    //マッチしたpタグが適正でないときはブログカード化しない
    if ( !is_p_tag_appropriate($match) ) {
      continue;
    }

    $url = strip_tags($match);//URL

    //ブログカード化しないURLで除外
    $exclude_urls = apply_filters('exclusion_external_blog_card_urls', array());
    $is_excluded = false;
    foreach ($exclude_urls as $exclude_url) {
      if (includes_string($url, $exclude_url)) {
        $is_excluded = true;
        break;
      }
    }
    if ($is_excluded) {
      continue;
    }

    $tag = url_to_external_blog_card_tag($url);

    if ( !$tag ) continue;

    //本文中のURLをブログカードタグで置換
    $the_content = preg_replace('{^'.preg_quote($match, '{}').'}im', "\n".$tag , $the_content, 1);
  }
  //ブログカード無効化の解除
  $the_content = cancel_blog_card_deactivation($the_content);
  $the_content = cancel_blog_card_deactivation($the_content, false);

  return $the_content;//置換後のコンテンツを返す
}
endif;
if ( is_external_blogcard_enable() ) {//外部リンクブログカードが有効のとき
  add_filter('the_content','url_to_external_blog_card', 11);//本文表示をフック
  add_filter('widget_text', 'url_to_external_blog_card', 11);//テキストウィジェットをフック
  add_filter('widget_text_pc_text', 'url_to_external_blog_card', 11);
  //add_filter('widget_classic_text', 'url_to_external_blog_card', 11);
  add_filter('widget_text_mobile_text', 'url_to_external_blog_card', 11);
  add_filter('the_category_tag_content', 'url_to_external_blog_card', 11);
  add_filter('appeal_area_message', 'url_to_external_blog_card', 11);
  add_filter('the_author_box_description', 'url_to_external_blog_card', 11);
  //コメント内ブログカード
  if (is_comment_external_blogcard_enable()) {
    add_filter('comment_text', 'url_to_external_blog_card', 11);
  }
}


//外部サイトからブログカードサムネイルを取得する
if ( !function_exists( 'fetch_card_image' ) ):
function fetch_card_image($image, $url = null){
  // URLのクエリを除去（?以降を削除）
  $image = preg_replace('/\?.*$/i', '', $image);

  // ファイル名・拡張子をそのまま使用
  $filename = basename($image);
  $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
  // 拡張子がない場合（例: https://example.com/image）はjpgをデフォルトとする
  if (!$ext) {
    $ext = 'jpg';
  }

  // キャッシュディレクトリを取得・存在しなければ作成
  $dir = get_theme_blog_card_cache_path();
  if ( !file_exists($dir) ) {
    mkdir($dir, 0777);
  }

  // キャッシュ保存先パス
  $new_file = trailingslashit($dir) . md5($image) . '.' . $ext;

  // WordPress関数を用い一時ダウンロード（タイムアウトをデフォルトの300秒から5秒に短縮してフリーズを防ぐ）
  $tmp = download_url($image, 5);
  if ( is_wp_error($tmp) ) {
    return;
  }

  // キャッシュにコピー
  copy($tmp, $new_file);
  @unlink($tmp);

  // 公開URLに変換して返す
  return str_replace(WP_CONTENT_DIR, content_url(), $new_file);
}
endif;

//外部サイトから直接OGP情報を取得してブログカードにする
if ( !function_exists( 'url_to_external_ogp_blogcard_tag' ) ):
function url_to_external_ogp_blogcard_tag($url){
  if ( !$url ) return;
  $url = strip_tags($url);//URL
  // URLのパス部分から拡張子を取得し、HTMLページではないことが明らかなバイナリファイル（動画・音声・画像・書庫等）を除外する
  // wp_parse_url()を使うことで、クエリ文字列(?...)やフラグメント(#...)付きURLでも正しく判定できる
  // 注意: ホワイトリスト方式（HTMLページ系の拡張子のみ許可）にすると、GitHub等のソースコード表示ページ
  //       （例: .../style.css や .../main.js は中身がHTML）まで弾いてしまうため、ブラックリスト方式を採用する
  //       拡張子だけでは判定しきれないバイナリは、OGP取得時のContent-Type判定とダウンロードサイズ上限（open-graph.php）で別途防御する
  $url_path = wp_parse_url($url, PHP_URL_PATH);
  if ($url_path) {
    $ext = strtolower(pathinfo($url_path, PATHINFO_EXTENSION));
    // 明確に非HTMLなバイナリ拡張子の一覧（動画・音声・画像・ドキュメント・書庫・実行ファイル等）
    $non_html_exts = array(
      // 動画
      'mp4', 'm4v', 'mov', 'avi', 'wmv', 'flv', 'mkv', 'webm', 'mpeg', 'mpg', 'mpe', 'ogv', '3gp', '3g2', 'm2ts',
      // 音声
      'mp3', 'wav', 'ogg', 'oga', 'm4a', 'aac', 'flac', 'wma', 'opus', 'mid', 'midi',
      // 画像
      'jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg', 'ico', 'tif', 'tiff', 'avif', 'heic', 'heif', 'psd',
      // ドキュメント・書庫・実行ファイル等
      'pdf', 'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'iso', 'dmg', 'exe', 'msi', 'apk',
      'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
    );
    // 明確なバイナリ拡張子の場合のみブログカード化を中止する（それ以外はContent-Type判定に委ねる）
    if ($ext && in_array($ext, $non_html_exts, true)) {
      return;
    }
  }
  $url = ampersand_urldecode($url);
  $params = get_url_params($url);
  $user_title = !empty($params['title']) ? $params['title'] : null;
  $user_snippet = !empty($params['snippet']) ? $params['snippet'] : null;

  $url_hash = TRANSIENT_BLOGCARD_PREFIX.md5( $url );
  $error_title = $url; //エラーの場合はURLを表示
  $title = $error_title;
  $error_image = get_site_screenshot_url($url);

  $image = $error_image;
  $snippet = '';
  $ogp = null;



  require_once abspath(__FILE__).'open-graph.php';

  //プラグイン等他環境下での Fatal Error を防ぐために function_exists で安全に判定する
  $is_refresh_mode = function_exists('is_external_blogcard_refresh_mode') && is_external_blogcard_refresh_mode();
  $is_user_admin   = function_exists('is_user_administrator') && is_user_administrator();
  $is_rest_req     = function_exists('is_rest') ? is_rest() : (defined('REST_REQUEST') && REST_REQUEST);

  //ブロックエディタ画面等REST API経由やクラシックエディタ保存時などバックエンド側の場合は、無駄な多重リクエストによるタイムアウト(504)を防ぐため既存キャッシュを優先する
  if ( !($is_refresh_mode && $is_user_admin && !is_admin() && !$is_rest_req) ) {
    //保存したキャッシュを取得
    $ogp = get_transient( $url_hash );
  }

  // キャッシュがない場合
  if (empty($ogp)) {
    $ogp_fetched = OpenGraphGetter::fetch($url);
    if ($ogp_fetched == false) {
      $ogp = 'error';
      // エラー時もネガティブキャッシュを保存する（相手先サーバーダウン時の無限タイムアウトや504エラー対策）
      // 1時間（HOUR_IN_SECONDS）キャッシュして、復旧までの間の自サイトへの負荷を防ぐ
      set_transient( $url_hash, 'error', HOUR_IN_SECONDS );
    } else {
      // 必要な値だけコピーして stdClass に格納
      $ogp = new stdClass();
      $ogp->title       = $ogp_fetched->title ?? '';
      $ogp->description = $ogp_fetched->description ?? '';
      $ogp->site_name   = $ogp_fetched->site_name ?? '';
      // og:url はリダイレクト先等のcanonical URLを示すため、ドメイン名取得やファビコン表示に使用
      $ogp->url         = isset($ogp_fetched->url) ? $ogp_fetched->url : '';
      if (isset($ogp_fetched->image)) {
        if (apply_filters('is_externa_blogcard_thumbnail_cache', true)) {
          // キャッシュを取得した場合
          $ogp->image = fetch_card_image($ogp_fetched->image, $url);
        } else {
          // キャッシュを取得しなかった場合
          $ogp->image = $ogp_fetched->image;
        }
      } else {
        // 画像がない場合
        $ogp->image = '';
      }

      // キャッシュ保存
      set_transient( $url_hash, $ogp,
                     DAY_IN_SECONDS * intval(get_external_blogcard_cache_retention_period()) );
    }
  }

  // キャッシュがある場合
  if ($ogp !== 'error') {
    if ( isset( $ogp->title ) && $ogp->title )
      $title = $ogp->title;//タイトルの取得

    if ( isset( $ogp->description ) && $ogp->description )
      $snippet = $ogp->description;//ディスクリプションの取得

    if ( isset($ogp->image) && $ogp->image ) {
      $image = $ogp->image;// 画像の取得
    }
  }

  //ドメイン名を取得（OGP情報のURLが正しいかのチェック）
  // punycode_decode() はフルURL対応ラッパー（内部でホスト名のみデコード＆URL再構築）のため、
  // フルURLに対してはこちらを使用する。$durl はファビコンAPIにも渡される。
  $durl = function_exists('punycode_decode') ? punycode_decode($url) : $url;
  // ネガティブキャッシュ('error'文字列)時は $ogp がオブジェクトではないためプロパティアクセスを避ける
  if ($ogp !== 'error' && isset($ogp->url) && preg_match(URL_REG, $ogp->url)) {
    $durl = function_exists('punycode_decode') ? punycode_decode($ogp->url) : $ogp->url;
  }
  // $durl は既にPunycodeデコード済みのため、ドメイン名もデコード後の状態で取得される
  $domain = get_domain_name($durl);

  $domain_style = get_external_blogcard_domain_style(); // "domain" or "name"
  if ($ogp !== 'error' && $domain_style === 'name' && !empty($ogp->site_name)) {
    $domain = $ogp->site_name; // OGPのサイト名を優先
  }


  //og:imageが相対パスのとき
  if(!$image || (strpos($image, '//') === false) || (is_ssl() && (strpos($image, 'https:') === false))){    // //OGPのURL情報があるか
    //相対パスの時はエラー用の画像を表示
    $image = $error_image;
  }
  $title = strip_tags($title);
  if ($user_title) {
    $title = $user_title;
  }
  //タイトルのフック
  // 第2引数に元の外部URL($url)を渡すことで、フック側でどのブログカードかを判別可能に
  // ※ $url は ampersand_urldecode() でデコード済み、かつ ?title= 等のCocoon独自パラメータを含む場合があるため、完全一致比較より strpos() 等での部分一致判定を推奨
  $title = apply_filters('cocoon_blogcard_title',$title, $url);
  $title = apply_filters('cocoon_external_blogcard_title',$title, $url);


  $image = strip_tags($image);

  $snippet = get_content_excerpt($snippet, get_entry_card_excerpt_max_length());
  $snippet = strip_tags($snippet);
  if ($user_snippet) {
    $snippet = $user_snippet;
  }
  // 第2引数に元の外部URL($url)を渡すことで、フック側でどのブログカードかを判別可能に
  // ※ $url はデコード済み・Cocoon独自パラメータを含む場合がある（上記タイトルフックの注記参照）
  $snippet = apply_filters( 'cocoon_blogcard_snippet', $snippet, $url );
  $snippet = apply_filters( 'cocoon_external_blogcard_snippet', $snippet, $url );

  //新しいタブで開く場合
  $target = is_external_blogcard_target_blank() ? ' target="_blank"' : '';

  $rel = '';
  if (is_external_blogcard_target_blank()) {
    $rel = ' rel="noopener"';
  }
  //コメント内でブログカード呼び出しが行われた際はnofollowをつける
  global $comment; //コメント内以外で$commentを呼び出すとnullになる
  if (is_external_blogcard_target_blank() && $comment) {
    $rel = ' rel="nofollow noopener"';
  }

  //GoogleファビコンAPIを利用する
  ////www.google.com/s2/favicons?domain=nelog.jp
  $favicon_tag = '<div class="blogcard-favicon external-blogcard-favicon">'.
    get_original_image_tag('https://www.google.com/s2/favicons?domain='.$durl, 16, 16, 'blogcard-favicon-image external-blogcard-favicon-image').
  '</div>';

  //サイトロゴ
  $site_logo_tag = '<div class="blogcard-domain external-blogcard-domain">'.$domain.'</div>';
  $site_logo_tag = '<div class="blogcard-site external-blogcard-site">'.$favicon_tag.$site_logo_tag.'</div>';

  //サムネイルを取得できた場合
  $thumbnail = '';
  // 第2引数に元の外部URL($url)を渡すことで、フック側でどのブログカードかを判別して画像を出し分け可能に
  // ※ $url はデコード済み・Cocoon独自パラメータを含む場合がある（上記タイトルフックの注記参照）
  $image = apply_filters('get_external_blogcard_thumbnail_url', $image, $url);
  if ( $image ) {
    $thumbnail = get_original_image_tag($image, THUMB160WIDTH, THUMB160HEIGHT, 'blogcard-thumb-image external-blogcard-thumb-image');
  }

  //取得した情報からブログカードのHTMLタグを作成
  $tag =
  '<a href="'.esc_url($url).'" title="'.esc_attr($title).'" class="blogcard-wrap external-blogcard-wrap a-wrap cf"'.$target.$rel.'>'.
    '<div class="blogcard external-blogcard'.get_additional_external_blogcard_classes().' cf">'.
      '<div class="blogcard-label external-blogcard-label">'.
        '<span class="fa"></span>'.
      '</div>'.
      '<figure class="blogcard-thumbnail external-blogcard-thumbnail">'.$thumbnail.'</figure>'.
      '<div class="blogcard-content external-blogcard-content">'.
        '<div class="blogcard-title external-blogcard-title">'.$title.'</div>'.
        '<div class="blogcard-snippet external-blogcard-snippet">'.$snippet.'</div>'.
      '</div>'.
      '<div class="blogcard-footer external-blogcard-footer cf">'.$site_logo_tag.'</div>'.
    '</div>'.
  '</a>';

  return $tag;
}
endif;
