<?php
/**
 * Share button
 *
 * @package vk-all-in-one-expantion-unit
 */

// global なので $options にすると ExUnit 全体の $options の値を汚染するので $sns_options を使用
$sns_options = veu_get_sns_options();
if ( veu_is_sns_btns_auto_insert() ) {
	if ( ! empty( $sns_options['hook_point'] ) ) {
		$hook_points = explode( "\n", $sns_options['hook_point'] );
		foreach ( $hook_points as $hook_point ) {
			add_action( $hook_point, 'veu_the_sns_btns' );
		}
	} elseif ( 'content' === veu_content_filter_state() ) {
		add_filter( 'the_content', 'veu_add_sns_btns', 200, 1 );
	} else {
		add_action( 'loop_end', 'veu_add_sns_btns_loopend' );
	}
}

/**
 * Fix share button hide option
 */
function veu_fix_sns_btns_hide() {
	$options = veu_get_sns_options();
	if ( ! empty( $options['snsBtn_ignorePosts'] ) ) {
		$ignore_post_ids = explode( ',', $options['snsBtn_ignorePosts'] );
		foreach ( $ignore_post_ids as $ignore_post_id ) {
			$ignore_post_id = trim( $ignore_post_id );
			if ( ! empty( get_post( $ignore_post_id ) ) ) {
				update_post_meta( $ignore_post_id, 'sns_share_botton_hide', true );
			}
		}
		unset( $options['snsBtn_ignorePosts'] );
		update_option( 'vkExUnit_sns_options', $options );
	}
}
add_action( 'admin_init', 'veu_fix_sns_btns_hide' );


/**
 * Display share button on hook point
 *
 * @param object $query : main query.
 * @return void
 */
function veu_the_sns_btns( $query ) {
	echo veu_get_sns_btns();
}

/**
 * Display share button on loop end
 *
 * @param object $query : main query.
 * @return void
 */
function veu_add_sns_btns_loopend( $query ) {
	if ( ! $query->is_main_query() ) {
		return;
	}
	if ( is_front_page() || is_home() || is_404() ) {
		return;
	}
	echo veu_add_sns_btns( '' );
}

/**
 * Display share button on content or fook point
 * 基本的にクラッシックテーマ向け機能
 * 本文下やフックにボタンを表示するかどうか
 * ブロックで配置した分には影響しない
 *
 * @param string $content : post content.
 * @return bool $auto_insert : post content.
 */
function veu_is_sns_btns_auto_insert() {
	$auto_insert = false;
	$options     = veu_get_sns_options();
	if ( ! empty( $options['enableSnsBtns'] ) ) {
		$auto_insert = true;
	}
	return $auto_insert;
}

/**
 * Check sns btn display
 *
 * @return bool
 */
function veu_is_sns_btns_display() {
	$options               = veu_get_sns_options();
	$post_type             = vk_get_post_type();
	$post_type             = $post_type['slug'];
	$sns_share_button_hide = get_post_meta( get_the_ID(), 'sns_share_botton_hide', true );

	// カスタムフィールドで非表示の場合は表示しない
	if ( ! empty( $sns_share_button_hide ) ) {
		return false;
	}

	// 404ページの内容を G3 ProUnit で指定の記事本文に書き換えた場合に表示されないように
	if ( is_404() ) {
		return false;
	}

	// シェアボタンを表示しない投稿タイプが配列で指定されている場合（チェックが入ってたら）.
	if ( ! empty( $options['snsBtn_exclude_post_types'][ $post_type ] ) ) {
		return false;
	}

	// 上記に該当しない場合は表示.
	return true;
}

/**
 * シェアボタンのCSS
 *
 * @param array $options : オプション値.
 * @return string $outer_css : style
 */
function veu_sns_outer_css( $options ) {

	// snsBtn_bg_fill_not が定義されている場合.
	$sns_btn_bg_fill_not = false;
	if ( ! empty( $options['snsBtn_bg_fill_not'] ) ) {
		$sns_btn_bg_fill_not = true;
	}

	// snsBtn_color が定義されている場合.
	if ( isset( $options['snsBtn_color'] ) ) {
		$sns_btn_color = esc_html( $options['snsBtn_color'] );
	} else {
		$sns_btn_color = '';
	}

	// 背景塗り && 色指定がない場合.
	if ( ! $sns_btn_bg_fill_not && ! $sns_btn_color ) {
		// （ ExUnitのCSSファイルに書かれている色が適用されているので個別には出力しなくてよい ）
		$outer_css = '';

		// 背景なし枠線の場合.
	} elseif ( $sns_btn_bg_fill_not ) {
		// 色指定がない場合.
		if ( ! $sns_btn_color ) {
			$sns_btn_color = '#ccc';
		}
		$outer_css = ' style="border:1px solid ' . $sns_btn_color . ';background:none;box-shadow: 0 2px 0 rgba(0,0,0,0.15);"';

		// それ以外（ 背景塗りの時 ）.
	} else {
		$outer_css = ' style="border:1px solid ' . $sns_btn_color . ';background-color:' . $sns_btn_color . ';box-shadow: 0 2px 0 rgba(0,0,0,0.15);"';
	}
	return $outer_css;
}

/**
 * シェアボタンのアイコンと文字部分のCSS
 *
 * @param array $options : オプション値.
 * @return string $style : style
 */
function veu_sns_icon_css( $options ) {
	// snsBtn_bg_fill_not が定義されている場合.
	$sns_btn_bg_fill_not = '';
	if ( ! empty( $options['snsBtn_bg_fill_not'] ) ) {
		$sns_btn_bg_fill_not = true;
	}

	// snsBtn_color が定義されている場合.
	if ( isset( $options['snsBtn_color'] ) ) {
		$style = esc_html( $options['snsBtn_color'] );
	} else {
		$style = '';
	}

	if ( ! $sns_btn_bg_fill_not && ! $style ) {
		$style = '';
	} elseif ( $sns_btn_bg_fill_not ) {
		// 線のとき.
		if ( ! $style ) {
			$style = '#ccc';
		}
		$style = ' style="color:' . $style . ';"';
	} else {
		// 塗りのとき.
		$style = ' style="color:#fff;"';
	}
	return $style;
}

/**
 * Share button html
 *
 * @param array $attr : class / position and so on.
 * @return string Button DOM
 */
function veu_get_sns_btns( $attr = array() ) {

	$options   = veu_get_sns_options();
	$outer_css = veu_sns_outer_css( $options );
	$icon_css  = veu_sns_icon_css( $options );

	// 現在のURL.
	$current_url = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );

	$link_url   = rawurlencode( get_permalink() );
	$page_title = rawurlencode( veu_get_the_sns_title() );

	$classes     = '';
	$social_btns = '';

	// 個別の記事で ボタンを表示する指定にしてある場合 or サイトエディターの場合.
	if ( veu_is_sns_btns_display() || false !== strpos( $current_url, 'context=edit' ) ) {
		if ( function_exists( 'veu_add_common_attributes_class' ) ) {
			$classes .= veu_add_common_attributes_class( $classes, $attr );
		}

		if ( isset( $attr['position'] ) ) {
			$classes .= ' veu_socialSet-position-' . $attr['position'];
		}
		if ( isset( $attr['className'] ) ) {
			$classes .= ' ' . $attr['className'];
		}

		$auto_class = ( isset( $attr['auto'] ) && $attr['auto'] ) ? ' veu_socialSet-auto' : '';

		$social_btns = '<div class="veu_socialSet' . $auto_class . esc_attr( $classes ) . ' veu_contentAddSection"><script>window.twttr=(function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],t=window.twttr||{};if(d.getElementById(id))return t;js=d.createElement(s);js.id=id;js.src="https://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);t._e=[];t.ready=function(f){t._e.push(f);};return t;}(document,"script","twitter-wjs"));</script><ul>';
		// facebook.
		if ( ! empty( $options['useFacebook'] ) ) {
			$social_btns .= '<li class="sb_facebook sb_icon">';
			$social_btns .= '<a class="sb_icon_inner" href="' . esc_url( '//www.facebook.com/sharer.php?src=bm&u=' . $link_url . '&t=' . $page_title ) . '" target="_blank" ' . $outer_css . 'onclick="window.open(this.href,\'FBwindow\',\'width=650,height=450,menubar=no,toolbar=no,scrollbars=yes\');return false;">';
			$social_btns .= '<span class="vk_icon_w_r_sns_fb icon_sns"' . $icon_css . '></span>';
			$social_btns .= '<span class="sns_txt"' . $icon_css . '>Facebook</span>';
			$social_btns .= '<span class="veu_count_sns_fb"' . $icon_css . '></span>';
			$social_btns .= '</a>';
			$social_btns .= '</li>';
		}

		// X.
		if ( ! empty( $options['useTwitter'] ) ) {
			$social_btns .= '<li class="sb_x_twitter sb_icon">';
			$social_btns .= '<a class="sb_icon_inner" href="' . esc_url( '//twitter.com/intent/tweet?url=' . $link_url . '&text=' . $page_title ) . '" target="_blank" ' . $outer_css . '>';
			$social_btns .= '<span class="vk_icon_w_r_sns_x_twitter icon_sns"' . $icon_css . '></span>';
			$social_btns .= '<span class="sns_txt"' . $icon_css . '>X</span>';
			$social_btns .= '</a>';
			$social_btns .= '</li>';
		}

		// bluesky.
		// 「タイトル + 改行(%0A) + URL」の %0A は esc_url() だと除去されてしまうため、
		// rawurlencode() 済みの構成要素を esc_attr() で属性エスケープする（Threads も同様）
		// esc_url() strips the %0A of "title + line break (%0A) + URL", so escape the
		// rawurlencode()-ed parts with esc_attr() for the attribute instead ( same for Threads ).
		if ( ! empty( $options['useBluesky'] ) ) {
			$social_btns .= '<li class="sb_bluesky sb_icon">';
			$social_btns .= '<a class="sb_icon_inner" href="' . esc_attr( 'https://bsky.app/intent/compose?text=' . $page_title . '%0A' . $link_url ) . '" target="_blank" ' . $outer_css . '>';
			$social_btns .= '<span class="vk_icon_w_r_sns_bluesky icon_sns"' . $icon_css . '></span>';
			$social_btns .= '<span class="sns_txt"' . $icon_css . '>Bluesky</span>';
			$social_btns .= '</a>';
			$social_btns .= '</li>';
		}

		// threads.
		// Threads の共有ボタン。投稿画面を開く intent URL に「タイトル + 改行(%0A) + URL」を渡す。
		// Threads share button. Pass "title + line break (%0A) + URL" to the intent URL that opens the compose screen.
		// アイコンは自前フォント vk_sns に字形がないため Font Awesome のブランドアイコン fa-threads を使用する。
		// Use the Font Awesome brand icon fa-threads because the in-house vk_sns font has no glyph for it.
		if ( ! empty( $options['useThreads'] ) ) {
			$social_btns .= '<li class="sb_threads sb_icon">';
			$social_btns .= '<a class="sb_icon_inner" href="' . esc_attr( 'https://www.threads.net/intent/post?text=' . $page_title . '%0A' . $link_url ) . '" target="_blank" ' . $outer_css . '>';
			$social_btns .= '<span class="icon_sns"' . $icon_css . '><i class="fa-brands fa-threads"></i></span>';
			$social_btns .= '<span class="sns_txt"' . $icon_css . '>Threads</span>';
			$social_btns .= '</a>';
			$social_btns .= '</li>';
		}

		// hatena.
		if ( ! empty( $options['useHatena'] ) ) {
			$social_btns .= '<li class="sb_hatena sb_icon">';
			$social_btns .= '<a class="sb_icon_inner" href="' . esc_url( '//b.hatena.ne.jp/add?mode=confirm&url=' . $link_url . '&title=' . $page_title ) . '" target="_blank" ' . $outer_css . ' onclick="window.open(this.href,\'Hatenawindow\',\'width=650,height=450,menubar=no,toolbar=no,scrollbars=yes\');return false;">';
			$social_btns .= '<span class="vk_icon_w_r_sns_hatena icon_sns"' . $icon_css . '></span>';
			$social_btns .= '<span class="sns_txt"' . $icon_css . '>Hatena</span>';
			$social_btns .= '<span class="veu_count_sns_hb"' . $icon_css . '></span>';
			$social_btns .= '</a>';
			$social_btns .= '</li>';
		}

		// line.
		// line: は esc_url() のデフォルト許可プロトコル外で空文字になるため、許可プロトコルを明示する
		// The line: scheme is not in esc_url()'s default allowed protocols ( the URL would become
		// an empty string ), so pass the allowed protocol explicitly.
		if ( wp_is_mobile() && ! empty( $options['useLine'] ) ) :
			$social_btns .= '<li class="sb_line sb_icon">';
			$social_btns .= '<a class="sb_icon_inner"  href="' . esc_url( 'line://msg/text/' . $page_title . ' ' . $link_url, array( 'line' ) ) . '" ' . $outer_css . '>';
			$social_btns .= '<span class="vk_icon_w_r_sns_line icon_sns"' . $icon_css . '></span>';
			$social_btns .= '<span class="sns_txt"' . $icon_css . '>LINE</span>';
			$social_btns .= '</a>';
			$social_btns .= '</li>';
		endif;
		// copy.
		if ( ! empty( $options['useCopy'] ) ) {
			$social_btns .= '<li class="sb_copy sb_icon">';
			$social_btns .= '<button class="copy-button sb_icon_inner"' . $outer_css . 'data-clipboard-text="' . esc_attr( urldecode( $page_title ) ) . ' ' . esc_attr( urldecode( $link_url ) ) . '">';
			$social_btns .= '<span class="vk_icon_w_r_sns_copy icon_sns"' . $icon_css . '><i class="fas fa-copy"></i></span>';
			$social_btns .= '<span class="sns_txt"' . $icon_css . '>Copy</span>';
			$social_btns .= '</button>';
			$social_btns .= '</li>';
		}

		$social_btns .= '</ul></div><!-- [ /.socialSet ] -->';
	}

	return $social_btns;
}

/**
 * Add sns btn to $content
 *
 * @param string $content : post content.
 * @return string $content add sns btns
 */
function veu_add_sns_btns( $content ) {

	// ウィジェットなら表示しない.
	global $is_pagewidget;
	if ( $is_pagewidget ) {
		return $content;
	}

	// 抜粋でも表示しない.
	if ( function_exists( 'vk_is_excerpt' ) ) {
		if ( vk_is_excerpt() ) {
			return $content;
		}
	}

	// アーカイブページでも表示しない.
	if ( is_archive() ) {
		return $content;
	}

	// フォーム内など不適切なループ外で混入するのを防ぐ
	if ( ! apply_filters( 'veu_sns_btns_check_mainloop', in_the_loop() && is_main_query() ) ) {
		return $content;
	}

	// フォーム内の自動挿入SNSボタンを表示しない.
	if ( strpos( $content, '<form' ) !== false ) {
		$form_start = strpos( $content, '<form' );
		if ( preg_match( '/<\/form>/', $content, $matches, PREG_OFFSET_CAPTURE, $form_start ) ) {
			$form_end = $matches[0][1] + strlen( $matches[0][0] );
		} else {
			return $content; // </form> が見つからない場合はそのまま返す
		}

		$form_inner = substr( $content, $form_start, $form_end - $form_start );

		// veu_socialSet-auto クラスを含む要素だけ削除（ブロックは残す）
		$form_inner_cleaned = preg_replace(
			'/<div[^>]+class="[^"]*veu_socialSet-auto[^"]*"[^>]*>.*?<\/div>/s',
			'',
			$form_inner
		);

		$content = substr( $content, 0, $form_start ) . $form_inner_cleaned . substr( $content, $form_end );
	}

	if ( veu_is_sns_btns_display() ) {
		$options = veu_get_sns_options();

		if ( ! empty( $options['snsBtn_position']['before'] ) ) {
			$content = veu_get_sns_btns(
				array(
					'position' => 'before',
					'auto'     => true,
				)
			) . $content;
		}

		if ( ! empty( $options['snsBtn_position']['after'] ) ) {
			$content .= veu_get_sns_btns(
				array(
					'position' => 'after',
					'auto'     => true,
				)
			);
		}
	}

	return $content;
}

add_action(
	'rest_api_init',
	function () {
		register_rest_route(
			'vk_ex_unit/v1',
			'/hatena_entry/(?P<linkurl>.+)',
			array(
				'methods'             => 'GET',
				'callback'            => 'vew_sns_hatena_restapi_callback',
				'permission_callback' => '__return_true',
			)
		);
		register_rest_route(
			'vk_ex_unit/v1',
			'/hatena_entry',
			array(
				'methods'             => 'POST',
				'callback'            => 'vew_sns_hatena_restapi_callback',
				'args'                => array(
					'linkurl' => array(
						'description' => 'linkurl',
						'required'    => true,
						'type'        => 'string',
					),
				),
				'permission_callback' => '__return_true',
			)
		);
		register_rest_route(
			'vk_ex_unit/v1',
			'/facebook_entry/(?P<linkurl>.+)',
			array(
				'methods'             => 'GET',
				'callback'            => 'vew_sns_facebook_restapi_callback',
				'permission_callback' => '__return_true',
			)
		);
		register_rest_route(
			'vk_ex_unit/v1',
			'/facebook_entry',
			array(
				'methods'             => 'POST',
				'callback'            => 'vew_sns_facebook_restapi_callback',
				'args'                => array(
					'linkurl' => array(
						'description' => 'linkurl',
						'required'    => true,
						'type'        => 'string',
					),
				),
				'permission_callback' => '__return_true',
			)
		);
	}
);

add_filter(
	'vkExUnit_master_js_options',
	function ( $options ) {
		$opt                              = veu_get_sns_options();
		$options['hatena_entry']          = get_rest_url( 0, 'vk_ex_unit/v1/hatena_entry/' );
		$options['facebook_entry']        = get_rest_url( 0, 'vk_ex_unit/v1/facebook_entry/' );
		$options['facebook_count_enable'] = false;
		$options['entry_count']           = (bool) ( 'disable' !== $opt['entry_count'] );
		$options['entry_from_post']       = (bool) ( 'post' === $opt['entry_count'] );

		$opt = veu_get_sns_options();
		if ( ! empty( $opt['fbAccessToken'] ) ) {
			$options['facebook_count_enable'] = true;
		}
		return $options;
	},
	10,
	1
);

/**
 * Hatena count
 *
 * @param string $data : Setting parametor ( url and so on ).
 * @return string api response
 */
function vew_sns_hatena_restapi_callback( $data ) {

	$siteurl = get_site_url();

	// Avoiding Apache config "AllowEncodedSlashes" option issue
	$link_url = str_replace( '-#-', '/', urldecode( $data['linkurl'] ) );

	// ホスト名のみを抽出して厳密に比較する。
	// 部分文字列一致 (strpos) では example.com.attacker.com のような
	// 攻撃者制御下のドメインで自サイトのホスト名を含めることでバリデーションを
	// 迂回されうるため、wp_parse_url() でホスト名のみを取り出し、
	// 大文字小文字を無視して完全一致 (strcasecmp) で判定する。サブドメインは許可しない。
	// Extract only the host name and compare strictly. Substring matching with
	// strpos can be bypassed by attacker-controlled domains such as
	// example.com.attacker.com that contain the site's host name, so we extract
	// the host with wp_parse_url() and compare for an exact, case-insensitive
	// match. Subdomains are not allowed.
	$link_host = wp_parse_url( $link_url, PHP_URL_HOST );
	$site_host = wp_parse_url( $siteurl, PHP_URL_HOST );

	if ( empty( $link_host ) || empty( $site_host ) || 0 !== strcasecmp( $link_host, $site_host ) ) {
		$response = new WP_REST_Response( array() );
		$response->set_status( 403 );
		return $response;
	}

	$link_url = urlencode( $link_url );

	$r = wp_safe_remote_get( 'https://bookmark.hatenaapis.com/count/entry?url=' . $link_url );

	if ( ! is_wp_error( $r ) ) {
		$response = new WP_REST_Response( array( 'count' => $r['body'] ) );
		if ( 'GET' === $data->get_method() ) {
			if ( empty( $r['headers']['cache-control'] ) ) {
				$cache_control = 'Cache-Control: public, max-age=3600, s-maxage=3600';
			} else {
				$cache_control = $r['headers']['cache-control'];
			}
			$response->header( 'Cache-Control', $cache_control );
		} else {
			$response->header( 'Cache-Control', 'no-cache' );
		}
		$response->set_status( 200 );
		return $response;
	}
	$response = new WP_REST_Response( array( 'errors' => array( 'Service Unavailable' ) ) );
	$response->set_status( 503 );

	return $response;
}

/**
 * Facebook count
 *
 * @param string $data : Setting parametor ( url and so on ).
 * @return string api response
 */
function vew_sns_facebook_restapi_callback( $data ) {

	$siteurl = get_site_url();

	// Avoiding Apache config "AllowEncodedSlashes" option issue
	$link_url = str_replace( '-#-', '/', urldecode( $data['linkurl'] ) );

	// ホスト名のみを抽出して厳密に比較する。
	// 部分文字列一致 (strpos) では example.com.attacker.com のような
	// 攻撃者制御下のドメインで自サイトのホスト名を含めることでバリデーションを
	// 迂回されうるため、wp_parse_url() でホスト名のみを取り出し、
	// 大文字小文字を無視して完全一致 (strcasecmp) で判定する。サブドメインは許可しない。
	// Extract only the host name and compare strictly. Substring matching with
	// strpos can be bypassed by attacker-controlled domains such as
	// example.com.attacker.com that contain the site's host name, so we extract
	// the host with wp_parse_url() and compare for an exact, case-insensitive
	// match. Subdomains are not allowed.
	$link_host = wp_parse_url( $link_url, PHP_URL_HOST );
	$site_host = wp_parse_url( $siteurl, PHP_URL_HOST );

	if ( empty( $link_host ) || empty( $site_host ) || 0 !== strcasecmp( $link_host, $site_host ) ) {
		$response = new WP_REST_Response( array() );
		$response->set_status( 403 );
		return $response;
	}

	$link_url = urlencode( $link_url );

	$options = veu_get_sns_options();
	if ( empty( $options['fbAccessToken'] ) ) {
		$response = new WP_REST_Response( array( 'errors' => array( 'Service Unavailable' ) ) );
		$response->set_status( 503 );
		return $response;
	}

	$r = wp_safe_remote_get( 'https://graph.facebook.com/?fields=engagement&access_token=' . $options['fbAccessToken'] . '&id=' . $link_url );

	if ( ! is_wp_error( $r ) ) {
		$j = json_decode( $r['body'] );

		if ( isset( $j->engagement->share_count ) ) {
			$response = new WP_REST_Response( array( 'count' => $j->engagement->share_count ) );
			if ( 'GET' === $data->get_method() ) {
				$response->header( 'Cache-Control', 'Cache-Control: public, max-age=3600, s-maxage=3600' );
			} else {
				$response->header( 'Cache-Control', 'no-cache' );
			}
			$response->set_status( 200 );
			return $response;
		}
	}
	$response = new WP_REST_Response( array( 'errors' => array( 'Service Unavailable' ) ) );
	$response->set_status( 503 );

	return $response;
}
