[
'header' => 'Authorization: Bearer ' . $bearer_token . "\r\n",
'timeout' => 10,
'ignore_errors' => true,
]
]);
$json = @file_get_contents($url, false, $context);
if ($json === false) return false;
$data = json_decode($json, true);
if (!isset($data['data']['id'])) return false;
return $data;
}
/**
* ツイートの画像1枚をローカルに保存し、ローカルURLを返す。
* PLUGIN_TWEET_LOCAL_IMAGE_DIR / URL が未設定の場合や保存失敗時は null を返す。
*
* 保存ファイル名: {tweetid}_{連番}.{拡張子}
*
* @param string $img_url Twitter CDN上の画像URL
* @param string $tweetid ツイートID
* @param int $idx このツイートの何枚目か(0始まり)
* @return string|null ローカルURL、または null
*/
function plugin_tweet_save_local_image($img_url, $tweetid, $idx)
{
$local_dir = PLUGIN_TWEET_LOCAL_IMAGE_DIR;
$local_url = PLUGIN_TWEET_LOCAL_IMAGE_URL;
if (empty($local_dir) || empty($local_url)) return null;
// 拡張子の決定(?format=jpg や .jpg 形式に対応)
$ext = 'jpg';
if (preg_match('/[?&]format=(\w+)/', $img_url, $m)) {
$ext = $m[1];
} elseif (preg_match('/\.(\w+)(?:\?|$)/', basename(parse_url($img_url, PHP_URL_PATH)), $m)) {
$ext = $m[1];
}
$filename = $tweetid . '_' . $idx . '.' . $ext;
$localpath = rtrim($local_dir, '/') . '/' . $filename;
$localurl = rtrim($local_url, '/') . '/' . $filename;
if (file_exists($localpath)) {
return $localurl;
}
// 保存ディレクトリを作成
if (!file_exists($local_dir)) {
if (!@mkdir($local_dir, 0777, true)) return null;
}
// 最高解像度(orig)で取得
$fetch_url = preg_replace('/name=\w+/', 'name=orig', $img_url);
$img_data = @file_get_contents($fetch_url);
if ($img_data === false) return null;
if (@file_put_contents($localpath, $img_data) === false) return null;
return $localurl;
}
/**
* ツイート画像用の タグを生成する。
* ローカル保存画像かつPLUGIN_TWEET_CLOUDFLARE_IMAGESが有効な場合は
* ref.inc.phpと同じ /cdn-cgi/image/ srcsetパターンでCloudflare経由配信する。
*
* @param string $img_url 画像URL
* @param string $alt alt属性テキスト
* @param bool $is_local ローカル保存画像かどうか
* @return string
タグ文字列
*/
function plugin_tweet_img_tag($img_url, $alt, $is_local = false)
{
$alt_esc = htmlspecialchars($alt, ENT_QUOTES, 'UTF-8');
if ($is_local && PLUGIN_TWEET_CLOUDFLARE_IMAGES) {
$cf = htmlspecialchars(parse_url($img_url, PHP_URL_PATH), ENT_QUOTES, 'UTF-8');
return '
';
}
return '
';
}
/**
* v2データから blockquote HTML を構築する。
* widgets.js が動作すれば公式ウィジェットとして表示され、
* 動作しない場合でも画像・引用ツイートを含む静的 HTML としてフォールバック表示される。
*
* @param array $v2data plugin_tweet_fetch_v2() の戻り値
* @param string $tweeturl ツイートURL
* @param array $opts プラグイン引数('noimg', 'noconv' を含む可能性あり)
* @param array $local_imgs media_key => ローカルURL の連想配列(省略時は空)
* @return string HTML文字列
*/
function plugin_tweet_build_html_v2($v2data, $tweeturl, $opts = [], $local_imgs = [])
{
$tweet = $v2data['data'];
$includes = isset($v2data['includes']) ? $v2data['includes'] : [];
$noimg = in_array('noimg', $opts);
$noconv = in_array('noconv', $opts);
// 著者情報(アバターURLも取得)
$author_name = '';
$author_username = '';
$author_avatar = '';
if (!empty($includes['users'])) {
foreach ($includes['users'] as $user) {
if (isset($tweet['author_id']) && $user['id'] === $tweet['author_id']) {
$author_name = htmlspecialchars($user['name'], ENT_QUOTES, 'UTF-8');
$author_username = htmlspecialchars($user['username'], ENT_QUOTES, 'UTF-8');
if (!empty($user['profile_image_url'])) {
// _normal (48px) → _bigger (73px) に変換してアバター品質向上
$avatar_url = str_replace('_normal.', '_bigger.', $user['profile_image_url']);
$author_avatar = htmlspecialchars($avatar_url, ENT_QUOTES, 'UTF-8');
}
break;
}
}
}
// 投稿日時
$created_at_display = '';
$created_at_iso = '';
if (!empty($tweet['created_at'])) {
try {
$dt = new DateTime($tweet['created_at']);
$dt->setTimezone(new DateTimeZone('Asia/Tokyo'));
$created_at_display = $dt->format('Y年n月j日 H:i');
$created_at_iso = $dt->format('c');
} catch (Exception $e) {}
}
// ツイート本文
$text = htmlspecialchars($tweet['text'], ENT_QUOTES, 'UTF-8');
// 画像HTML(noimgオプション時はスキップ)
$media_html = '';
$media_count = 0;
if (!$noimg && !empty($includes['media'])) {
$imgs = '';
foreach ($includes['media'] as $media) {
$media_key = isset($media['media_key']) ? $media['media_key'] : '';
if ($media['type'] === 'photo' && !empty($media['url'])) {
$is_local = isset($local_imgs[$media_key]) && $local_imgs[$media_key];
$raw_url = $is_local ? $local_imgs[$media_key] : $media['url'];
$alt = isset($media['alt_text']) ? $media['alt_text'] : '';
$imgs .= plugin_tweet_img_tag($raw_url, $alt, $is_local);
$media_count++;
} elseif (in_array($media['type'], ['video', 'animated_gif']) && !empty($media['preview_image_url'])) {
$is_local_thumb = isset($local_imgs[$media_key . '_thumb']) && $local_imgs[$media_key . '_thumb'];
$raw_url = $is_local_thumb ? $local_imgs[$media_key . '_thumb'] : $media['preview_image_url'];
$imgs .= plugin_tweet_img_tag($raw_url, 'video thumbnail', $is_local_thumb);
$media_count++;
}
}
if ($imgs !== '') {
$media_html = '
' . $qt_text . '
' . '' . $qt_url . '' . '' . $header_html . '' . '' . $media_html . $quoted_html . $footer_html . '' . $safe_url . '' . '' . $text . '
' . $fallback_text . ''; } // --- noimg / noconv(oEmbed HTML の場合のみ文字列置換で対応)--- if (!$used_v2) { if (in_array('noimg', $tw)) { $html = str_replace('class="twitter-tweet"', 'class="twitter-tweet" data-cards="hidden"', $html); } if (in_array('noconv', $tw)) { $html = str_replace('class="twitter-tweet"', 'class="twitter-tweet" data-conversation="none"', $html); } } // --- tweet-card.css 読み込み(キャッシュモード用スタイル) --- // tweet-card.css を skin/ ディレクトリに配置し、絶対パスで参照してください $css_link = ''; if (!in_array($css_link, $head_tags, true)) { $head_tags[] = $css_link; } // --- ライトボックスJS(画像クリック拡大) --- $lightbox_js = ''; if (!in_array($lightbox_js, $head_tags, true)) { $head_tags[] = $lightbox_js; } // --- widgets.js の読み込み処理 --- if (PLUGIN_TWEET_LAZYLOAD) { $script = ''; } else { $script = ''; } if (!in_array($script, $head_tags, true)) { $head_tags[] = $script; } return "\t$html\n"; } ?>