= 0 ) { require_once( ABSPATH . WPINC . '/class-wp-http.php'); } else { require_once( ABSPATH . WPINC . '/class-http.php'); } require_once( ABSPATH . WPINC . '/class-wp-error.php'); final class CodocUtil { public $codoc_url = CODOC_URL; public function __construct($params = ['usercode' => null, 'token' => null,'codoc_url' => null]) { $this->setAuthInfo(['usercode' => $params['usercode'], 'token' => $params['token']]); if (isset($params['codoc_url'])) { $this->codoc_url = $params['codoc_url']; } return $this; } public function callAPI($method,$path,$body = [],$headers = []) { $usercode = $this->usercode; $token = $this->token; $headers['X-CodocToken'] = $token; $host = $this->codoc_url; $sslverify = true; if (preg_match('/local/',$this->codoc_url)) { $sslverify = false; $host = 'https://host.docker.internal'; } $url = sprintf("%s/api/v1/cms/%s%s",$host,$usercode,$path); $http = new WP_Http(); try { $response = $http->request( $url, [ 'sslverify' => $sslverify, 'method' => $method, 'timeout' => 10, 'headers' => $headers, 'body' => $body, ] ); // notification を処理 $body = json_decode($response['body'],true); if (isset($body['notification']) && !empty($body['notification'])) { // notification をトランジェントに保存(24時間保持) set_transient('codoc_api_notification', $body['notification'], DAY_IN_SECONDS); } if ( is_wp_error($response) || $response['response']['code'] != 200 ) { //何もしない //var_dump( $response ); } } catch(Exception $e) { } if (!is_wp_error($response) and isset($response['body']) and is_string($response['body']) and is_array(json_decode($response['body'], true)) and (json_last_error() == JSON_ERROR_NONE)) { return json_decode($response['body']); } else { return null; } } public function callPaywallAPI($method,$path,$body = [],$headers = []) { $site_usercode = get_option(CODOC_USERCODE_OPTION_NAME); $paywall_token = $this->get_paywall_token_code(); $host = $this->codoc_url; $sslverify = true; if (preg_match('/local/',$this->codoc_url)) { $sslverify = false; $host = 'https://host.docker.internal'; } $url = sprintf("%s/api/v1/paywall/%s%s",$host,$site_usercode,$path); $http = new WP_Http(); try { $response = $http->request( $url, [ 'sslverify' => $sslverify, 'method' => $method, 'timeout' => 10, 'headers' => $headers, 'body' => $body, ] ); if ( is_wp_error($response) || $response['response']['code'] != 200 ) { //何もしない //var_dump( $response ); } } catch(Exception $e) { } if (!is_wp_error($response) and isset($response['body']) and is_string($response['body']) and is_array(json_decode($response['body'], true)) and (json_last_error() == JSON_ERROR_NONE)) { return json_decode($response['body']); } else { return null; } } public function setAuthInfo($params = [ "usercode" => null, "token" => null ]) { if ($params["usercode"]) { $this->usercode = $params["usercode"]; } if ($params["token"]) { $this->token = $params["token"]; } return true; } public function get_token($params = ["fetch_token_key" => null],$auth_info = ["usercode" => null, "token" => null]) { $this->setAuthInfo($auth_info); return $this->callAPI('GET','/token',[ "fetch_token_key" => $params["fetch_token_key"] ]); } public function get_user_info($params = [] ,$auth_info = ["usercode" => null, "token" => null]) { $this->setAuthInfo($auth_info); return $this->callAPI('GET',''); } public function get_support_entry($params = [] ,$auth_info = ["usercode" => null, "token" => null]) { $this->setAuthInfo($auth_info); return $this->callAPI('GET','/support_entry'); } // API側と統一した文字数上限のバリデーション。エラーメッセージの配列を返す (エラー無しなら空配列) public function validate_entry_lengths($api_params) { $errors = []; $title = isset($api_params['title']) ? $api_params['title'] : ''; $body_free = isset($api_params['body_free']) ? $api_params['body_free'] : ''; $body_paywalled = isset($api_params['body_paywalled']) ? $api_params['body_paywalled'] : ''; $binded_url = isset($api_params['binded_url']) ? $api_params['binded_url'] : ''; if (mb_strlen($title) > CODOC_MAX_TITLE_LENGTH) { // Translators: %1$d is the max length, %2$d is the current length. $errors[] = sprintf(__('Title exceeds the maximum length of %1$d characters (current: %2$d).', 'codoc'), CODOC_MAX_TITLE_LENGTH, mb_strlen($title)); } $len_body_free = mb_strlen($body_free); $len_body_paywalled = mb_strlen($body_paywalled); if ($len_body_free > CODOC_MAX_BODY_FREE_LENGTH) { $errors[] = sprintf(__('Free area exceeds the maximum length of %1$d characters (current: %2$d).', 'codoc'), CODOC_MAX_BODY_FREE_LENGTH, $len_body_free); } if ($len_body_paywalled > CODOC_MAX_BODY_PAYWALLED_LENGTH) { $errors[] = sprintf(__('Paid area exceeds the maximum length of %1$d characters (current: %2$d).', 'codoc'), CODOC_MAX_BODY_PAYWALLED_LENGTH, $len_body_paywalled); } $len_body_total = $len_body_free + $len_body_paywalled; if ($len_body_total > CODOC_MAX_BODY_LENGTH) { $errors[] = sprintf(__('Total body exceeds the maximum length of %1$d characters (current: %2$d).', 'codoc'), CODOC_MAX_BODY_LENGTH, $len_body_total); } if (mb_strlen($binded_url) > CODOC_MAX_BINDED_URL_LENGTH) { $errors[] = sprintf(__('URL exceeds the maximum length of %1$d characters (current: %2$d).', 'codoc'), CODOC_MAX_BINDED_URL_LENGTH, mb_strlen($binded_url)); } return $errors; } public function sync_entry($params = [ "post_title" => null, "post_content" => null, "post_status" => null, # 0, 1 "post_permalink" => null, "codoc_entry_code" => null, ],$auth_info = ["usercode" => null, "token" => null]) { $this->setAuthInfo($auth_info); $post_content = $params['post_content']; preg_match('/wp:codoc\/codoc-block +?({.*})/',$post_content,$matches); // GUTENBERG で指定されたjson(記事情報)がない場合 if (!$matches) { return; } // GUTENBERG で保存している内容を取得 $codoc_info = json_decode($matches[1],true); // codoc タグがない場合はなにもしない //preg_match('/(]+>(?:.+|)<\/span>)/',$post_content,$matches); preg_match('/(<(?:span|div)[^>]+data-id="codoc-tag"(?:[^>]+|)>(?:.+|)<\/(?:span|div)>)/',$post_content,$matches); if (!$matches) { return; } // TODO: split or html parse? HTMLがつながってる場合はおかしくなる // classic(tinymce)版の場合p、もしくはdivにかこまれている $end_tag_regex = '/<(?:div|p)>::CODOC_WP_END_PAYWALL::<\/(?:div|p)>/'; $before_splited = preg_split($end_tag_regex,$post_content); $post_content = $before_splited[0]; $splited = preg_split('/(?:<(?:div|p)(?:[^>]+|)>|)<\!-- +wp:codoc\/codoc-block .*<\!-- +\/wp:codoc\/codoc-block +-->(?:<\/(?:div|p)>|)/s',$post_content); $status = $params['post_status']; # 0: 非公開 1: 公開 2:限定公開(パスワードの場合は限定公開で同期する) // codoc設定で限定公開を有効で、公開の場合は 限定公開にする if ($status == 1 and isset($codoc_info['statusLimited']) and $codoc_info['statusLimited']) { $status = 2; } # $body_free = ''; # $body_paywalled = ''; # if (count($splited) >= 2) { # # } $binded_url = $params['post_permalink']; $CODOC_SETTINGS = get_option(CODOC_SETTINGS_OPTION_NAME); if (isset($CODOC_SETTINGS['str_replace_binded_url_from']) and isset($CODOC_SETTINGS['str_replace_binded_url_to']) and $CODOC_SETTINGS['str_replace_binded_url_from']) { $binded_url = str_replace(sanitize_text_field($CODOC_SETTINGS['str_replace_binded_url_from']), sanitize_text_field($CODOC_SETTINGS['str_replace_binded_url_to']), $binded_url); } $api_params = [ 'title' => $params['post_title'], 'body_free' => $splited[0], 'body_paywalled' => $splited[1], 'status' => $status, 'binded_url' => $binded_url, 'show_price' => isset($codoc_info['showPrice']) ? ($codoc_info['showPrice'] ? 1 : 0) : 0, 'price' => isset($codoc_info['price']) ? $codoc_info['price'] : 100, 'limited' => isset($codoc_info['limited']) ? ($codoc_info['limited'] ? 1 : 0) : 0, 'limited_count' => isset($codoc_info['limitedCount']) ? $codoc_info['limitedCount'] : 1, 'affiliate_mode' => isset($codoc_info['affiliateMode']) ? ($codoc_info['affiliateMode'] ? 1 : 0) : 0, 'affiliate_rate' => isset($codoc_info['affiliateRate']) ? $codoc_info['affiliateRate'] : '0.0500', 'show_support' => isset($codoc_info['showSupport']) ? ($codoc_info['showSupport'] ? 1 : 0) : 0, 'show_paywalled_support' => isset($codoc_info['showPaywalledSupport']) ? ($codoc_info['showPaywalledSupport'] ? 1 : 0) : 0, 'subscriptions' => isset($codoc_info['subscriptions']) ? array_keys($codoc_info['subscriptions']) : [], ]; // API側と統一した文字数バリデーション $errors = $this->validate_entry_lengths($api_params); if ($errors) { set_transient('codoc_entry_validation_errors', $errors, MINUTE_IN_SECONDS * 5); return null; } $entryCode = $params['codoc_entry_code']; $res = null; if ($entryCode) { $res = $this->callAPI('PUT','/entries/' . $entryCode ,$api_params); } else { $res = $this->callAPI('POST', '/entries', $api_params); } return $res; } function post_thumbnail( $params = [ "file_path" => null, "boundary" => null, "codoc_entry_code" => null, ], $auth_info = ["usercode" => null, "token" => null] ) { $this->setAuthInfo($auth_info); $file_path = $params['file_path']; $boundary = $params['boundary']; # ランダム変数24桁 wp_generate_password(24); $res = null; if (is_readable($file_path)) { $name = 'file'; $payload = ''; $payload .= '--' . $boundary; $payload .= "\r\n"; $payload .= 'Content-Disposition: form-data; name="' . $name . '"; filename="' . basename( $file_path ) . '"' . "\r\n"; $payload .= "Content-Type: application/octet-stream\r\n"; $payload .= "Content-Transfer-Encoding: binary\r\n"; $payload .= "\r\n"; $payload .= file_get_contents( $file_path ); $payload .= "\r\n"; $payload .= '--' . $boundary . '--'; $entryCode = $params['codoc_entry_code']; $res = $this->callAPI( 'POST', '/entries/' . $entryCode . '/thumbnail', $payload, ['content-type' => 'multipart/form-data; boundary=' . $boundary] ); } return $res; } function reset_thumbnail( $params = [ "codoc_entry_code" => null, ],$auth_info = ["usercode" => null, "token" => null] ) { $this->setAuthInfo($auth_info); $entryCode = $params['codoc_entry_code']; $res = $this->callAPI( 'POST', '/entries/' . $entryCode . '/thumbnail', [ "reset" => 1 ] ); return $res; } function filter_content( $params = [ "post_content" => null, "preview" => null, "codoc_entry_code" => null, "codoc_settings" => null, "is_amp_endpoint" => null, "post_permalink" => null, "codoc_support_entry_code" => null, ]) { $CODOC_SETTINGS = $params['codoc_settings']; $has_codoc_tag = '/(<(span|div)[^>]+data-id="codoc-tag"(?:[^>]+|)>(?:.+|)<\/(?:div|span)>)/'; $post_content = $params['post_content']; // codoc タグがあるかどうか (202001:span -> div に変更) preg_match($has_codoc_tag,$post_content,$matches); $show_support_message = ''; if (isset($CODOC_SETTINGS['show_support_message'])) { $show_support_message = $CODOC_SETTINGS['show_support_message']; } $show_support_categories = ''; if (isset($CODOC_SETTINGS['show_support_categories'])) { $show_support_categories = $CODOC_SETTINGS['show_support_categories']; } $show_support_location = 'bottom'; if (isset($CODOC_SETTINGS['show_support_location'])) { $show_support_location = $CODOC_SETTINGS['show_support_location']; } // デフォルト値をうめておく if (!isset($CODOC_SETTINGS['support_button_text'])) { $CODOC_SETTINGS['support_button_text'] = 'サポートする'; } if (!isset($CODOC_SETTINGS['show_like'])) { $CODOC_SETTINGS['show_like'] = 1; } if (!isset($CODOC_SETTINGS['show_about_codoc'])) { $CODOC_SETTINGS['show_about_codoc'] = 1; } if (!isset($CODOC_SETTINGS['show_powered_by'])) { $CODOC_SETTINGS['show_powered_by'] = 1; } if (!isset($CODOC_SETTINGS['show_created_by'])) { $CODOC_SETTINGS['show_created_by'] = 1; } if (!isset($CODOC_SETTINGS['show_copyright'])) { $CODOC_SETTINGS['show_copyright'] = 1; } $show_support_entry = (is_single() && preg_grep("/(?:$show_support_categories)/",array_map( function($c) { return $c->name; },get_the_category(get_post()->ID)))); if (!$matches && ($support_entry_code = $params['codoc_support_entry_code']) && $show_support_entry) { $support_tag = sprintf( '
', htmlspecialchars($show_support_message,ENT_QUOTES), $support_entry_code, $CODOC_SETTINGS['support_button_text'], $CODOC_SETTINGS['show_like'], $CODOC_SETTINGS['show_about_codoc'], $CODOC_SETTINGS['show_powered_by'], $CODOC_SETTINGS['show_created_by'], $CODOC_SETTINGS['show_copyright'] ); $post_content = $show_support_location == 'bottom' ? $post_content . $support_tag : $support_tag . $post_content ; } // data-wp-plugin-ver が無いタグは分割しない if (!$matches) { return $post_content; } // codocタグで分割 (202001: gutenbergで直前のdivタグを削除) $tag_regex = '/(?:]+|)>|)<(?:span|div)[^>]+data-id="codoc-tag"(?:[^>]+|)>(?:.+|)<\/(?:span|div)>(?:<\/div>|)/'; $end_tag_regex = '/<(?:div|p)>::CODOC_WP_END_PAYWALL::<\/(?:div|p)>/'; if ( $params['preview'] ) { $post_content = preg_replace($tag_regex,'
ここから上は無料で表示されます
',$post_content); $post_content = preg_replace($end_tag_regex,'
ここから下は無料で表示されます
',$post_content); return $post_content; } // codoc タグの前後で分ける $before_splited = preg_split($end_tag_regex,$post_content); $splited = preg_split($tag_regex,$before_splited[0]); // codoc タグにID属性のentrycodeとdata-without-body(無料分を非表示)をつける $entryCodeFormated = sprintf('"codoc-entry-%s" ',$params['codoc_entry_code']); // 文言系の設定をつける $tagAttributes = ''; if (isset($CODOC_SETTINGS['show_like']) and $CODOC_SETTINGS['show_like'] != 1) { $tagAttributes = $tagAttributes . sprintf(' data-show-like="%s"',$CODOC_SETTINGS['show_like']); } if (isset($CODOC_SETTINGS['show_about_codoc']) and $CODOC_SETTINGS['show_about_codoc'] != 1) { $tagAttributes = $tagAttributes . sprintf(' data-show-about-codoc="%s"',$CODOC_SETTINGS['show_about_codoc']); } if (isset($CODOC_SETTINGS['show_powered_by']) and $CODOC_SETTINGS['show_powered_by'] != 1) { $tagAttributes = $tagAttributes . sprintf(' data-show-powered-by="%s"',$CODOC_SETTINGS['show_powered_by']); } if (isset($CODOC_SETTINGS['show_created_by']) and $CODOC_SETTINGS['show_created_by'] != 1) { $tagAttributes = $tagAttributes . sprintf(' data-show-created-by="%s"',$CODOC_SETTINGS['show_created_by']); } if (isset($CODOC_SETTINGS['show_copyright']) and $CODOC_SETTINGS['show_copyright'] != 1) { $tagAttributes = $tagAttributes . sprintf(' data-show-copyright="%s"',$CODOC_SETTINGS['show_copyright']); } if (isset($CODOC_SETTINGS['entry_button_text']) and $CODOC_SETTINGS['entry_button_text'] != '') { $tagAttributes = $tagAttributes . sprintf(' data-entry-button-text="%s"',$CODOC_SETTINGS['entry_button_text']); } if (isset($CODOC_SETTINGS['subscription_button_text']) and $CODOC_SETTINGS['subscription_button_text'] != '') { $tagAttributes = $tagAttributes . sprintf(' data-subscription-button-text="%s"',$CODOC_SETTINGS['subscription_button_text']); } if (isset($CODOC_SETTINGS['support_button_text']) and $CODOC_SETTINGS['support_button_text'] != '') { $tagAttributes = $tagAttributes . sprintf(' data-support-button-text="%s"',$CODOC_SETTINGS['support_button_text']); } if (isset($CODOC_SETTINGS['subscription_message']) and $CODOC_SETTINGS['subscription_message'] != '') { $tagAttributes = $tagAttributes . sprintf(' data-subscription-message="%s"',$CODOC_SETTINGS['subscription_message']); } if (isset($CODOC_SETTINGS['support_message']) and $CODOC_SETTINGS['support_message'] != '') { $tagAttributes = $tagAttributes . sprintf(' data-support-message="%s"',$CODOC_SETTINGS['support_message']); } if (isset($CODOC_SETTINGS['codoc_tag_attributes']) and $CODOC_SETTINGS['codoc_tag_attributes'] != '') { $tagAttributes = $tagAttributes . sprintf(' %s',$CODOC_SETTINGS['codoc_tag_attributes']); } $codoc_tag = preg_replace('/<((?:span|div)[^>]+)data-id="[^"]+"((?:[^>]+|))>/','<${1} ${2} data-without-body="1" id=' . $entryCodeFormated . $tagAttributes . ">", $matches[1]); # THE THOR の page-lp.php が " " をけずるための対策 # "div class" -> "div class" $codoc_tag = preg_replace('/div +class/','div class',$codoc_tag); if ($params['is_amp_endpoint']) { $format = preg_replace_callback('/(<(?:span|div)[^>]+>)((?:[^<>]+|))(<\/(?:span|div)>)/',function($matches) { $format = $matches[1] . '%s' . $matches[3]; return sprintf($format,($matches[2] ? $matches[2] : '続きを読む')); },$codoc_tag); $codoc_tag = sprintf($format,$params["post_permalink"]); } # タグの前後にHTMLを挿入 if (!isset($CODOC_SETTINGS['str_before_codoc_tag'])) { $CODOC_SETTINGS['str_before_codoc_tag'] = ''; } if (!isset($CODOC_SETTINGS['str_after_codoc_tag'])) { $CODOC_SETTINGS['str_after_codoc_tag'] = ''; } // 無料部分のみ表示 $post_content = $splited[0] . sprintf( '%s
%s
%s', $CODOC_SETTINGS['str_before_codoc_tag'], $codoc_tag, $CODOC_SETTINGS['str_after_codoc_tag'] ); // ::CODOC_WP_END_PAYWALL:: の後を足す if (isset($before_splited[1]) and $before_splited[1]) { $post_content .= $before_splited[1]; } return $post_content; } /* // Cookieの中のトークンを取得 $_CODOC->util->get_paywall_token_code(); */ function get_paywall_token_code() { if (isset($_COOKIE['codocTokenCode']) and $codocTokenCode = $_COOKIE['codocTokenCode']) { return $codocTokenCode; } return null; } /* $_CODOC->util->check_login($mode); // cookie / strict $response = $_CODOC->util->check_login('strict_with_data'); if ($response->status and $response->user) { echo $reponse->user->code; } */ function check_login($mode = 'cookie') { if ($codocTokenCode = $this->get_paywall_token_code()) { if ($mode == 'cookie') { // cookieの中にtokenがあればOK return true; } if ($mode == 'strict' or $mode == 'strict_with_data') { // ログイン状態を問い合わせ $res = $this->callPaywallAPI('GET','/users',["paywall_token_code" => $codocTokenCode ]); if ($res->status) { if ($mode == 'strict_with_data') { return $res; } else { return true; } } else { return false; } } } return false; } /* $_CODOC->util->check_owned($entry_code); */ function check_owned($entry_code = null) { if (!$entry_code) { return false; } $res = $this->callPaywallAPI('GET','/entries/' . $entry_code, ["paywall_token_code" => $this->get_paywall_token_code(), "check_owned" => 1 ]); if ($res->status) { return true; } return false; } /* $_CODOC->util->health_check(); */ function health_check() { $sslverify = true; $host = $this->codoc_url; if (preg_match('/local/',$this->codoc_url)) { $sslverify = false; $host = 'https://host.docker.internal'; } $url = sprintf("%s/codoc_health_check",$host); $http = new WP_Http(); try { $response = $http->request( $url, [ 'sslverify' => $sslverify, 'method' => 'GET', 'timeout' => 10, 'headers' => [], 'body' => [], ] ); } catch(Exception $e) { } if ($response and $response['response']['code'] == '200') { return true; } else { return null; } } }