aituRelatedPosts(); } // treated as regular method since PHP 5.3.3 // PHP 4 style constructor function aituRelatedPosts() { $this->resetAdminOptions(); $persistentOptions = get_option(self::ADMIN_OPTION_NAME); if (is_array($persistentOptions)) { foreach ($this->adminOptions as $key => $value) { if (array_key_exists($key, $persistentOptions)) { $this->adminOptions[$key] = $persistentOptions[$key]; } } } // get_bloginfo('url') deprecated since WordPress 3.0.0. $this->sitePrefix = function_exists('home_url') ? home_url() : get_bloginfo('url'); } function getOrigTitle() { global $post; return isset($post->post_title) ? $post->post_title : ''; } function showNotifyMessage() { $notifyMessage = $this->popNotifyMessage(); if ($notifyMessage) { $this->printHtmlNotifyMessage(array($notifyMessage)); } } function registerOptionsPage () { add_options_page('爱徒智能相关文章插件', '爱徒相关文章', 'activate_plugins', basename(__FILE__), array($this, 'printAdminPage')); } function addPluginActionLinks($links, $file) { if ($file == plugin_basename(__FILE__)) { $links[] = '' . __('Settings') . ''; } return $links; } function addToRssContent($content) { $newContent = $content; if ($this->getAdminOption(self::DISPLAY_IN_FEED) && is_feed()) { global $post; // 1. Both "rss excerpt mode" and "rss full text mode" will call the_excerpt_rss() // function first to generate feed description. // 2. "the_excerpt_rss" apply filters process: // (1. Only run when empty(post->excerpt) && post_password_required(post)) // the_content (Get the whole content and then extract excerpt. // We shouldn't add related items here since the content will // be processed by wp_trim_excerpt() later) -> // (2) // the_excerpt_rss (Here will add our related items content). // 3. "rss full text mode" applies the filters on "the_content" hook again after // the_excerpt_rss() to generate the feed content. However this time we should // add related items content. // 4. Because wordpress 2.5.x don't have function post_password_required and we have // to do a lot to check if the current user is admin, we don't show rss related items // if post excerpt is empty and it's a password required post. if (!has_excerpt() && !in_array($post->ID, $this->canFilledRssContentPostIds)) { $this->canFilledRssContentPostIds[] = $post->ID; } else { $relatedItemsHtml = $this->getRelatedItemsHtml(); if ($relatedItemsHtml) { $newContent .= $relatedItemsHtml; } } } return $newContent; } private function getRelatedItemsHtml() { // Don't do anything for 30 seconds if last request failed. if (isset($this->lastRssRequestFailedTime) && time() - $this->lastRssRequestFailedTime < 30) { return; } $encodedUrl = urlencode(get_permalink()); $encodedTitle = urlencode($this->getOrigTitle()); $num = $this->getAdminOption(self::NUM_POSTS); $encodedSitePrefix = urlencode($this->sitePrefix); global $wp_version; $path = "/ext/relatedItemsRss.htm?type=1&url=$encodedUrl&title=$encodedTitle&num=$num&sitePrefix=$encodedSitePrefix&mode=3&v=" . self::VERSION . "&pf=WordPress$wp_version"; $relatedItemsHtml = $this->httpGet(self::AITU_SERVER, $path); if ($relatedItemsHtml) { return $relatedItemsHtml; } else { $this->lastRssRequestFailedTime = time(); } } private function httpGet($server, $path) { global $wp_version; $response = ''; if (function_exists('wp_remote_get')) { // wp_remote_get function was added in WordPress 2.7 $addr = $server . $path; // Default: method: GET, timeout: 5s, redirection: 5, httpversion: 1.0, blocking: true, body: null, cookies: array() $args = array( 'user-agent' => apply_filters('http_headers_useragent', 'WordPress/' . $wp_version . '; ' . $_SERVER['HTTP_USER_AGENT']), // TODO: use batch fetch related items instead of lower the timeout. 'timeout' => 1 ); $raw_response = wp_remote_get($addr, $args); if (!is_wp_error($raw_response) && 200 == $raw_response['response']['code']) { $response = trim($raw_response['body']); } } // For: // 1. WordPress version < 2.7 // 2. For some reason the client php is misconfigurated, the wp_remote_get function can not work properly. // Currently know wp_remote_get($url, $args) will crash if wordpress do request with WP_Http_ExtHTTP(in /wp-includes/http.php) and the PECL extension is not bundled with PHP. // (empty($response) === true) if match one of the above reasons. if (empty($response) && function_exists('fsockopen')) { $urlComponents = parse_url($server); $host = $urlComponents['host'] . ($urlComponents['port'] ? ':' . $urlComponents['port'] : ''); $http_request = "GET $path HTTP/1.0\r\n"; $http_request .= "Host: $host\r\n"; $http_request .= "Content-Type: application/x-www-form-urlencoded; charset=" . get_option('blog_charset') . "\r\n"; $http_request .= 'User-Agent: WordPress/' . $wp_version . '; ' . $_SERVER['HTTP_USER_AGENT'] . "\r\n\r\n"; $answer = ''; // The connection timeout is 1 second. // TODO: recover this to 5s and remove stream_set_timeout() setting when we support betch fetch related items. if( false != ($fs = @fsockopen($host, 80, $errno, $errstr, 1)) && is_resource($fs)) { // The timeout for reading data over the socket is 1 second. stream_set_timeout($fs, 1); fwrite($fs, $http_request); while (!feof($fs)) { $answer .= fgets($fs, 1160); // One TCP-IP packet } fclose($fs); $answer = explode("\r\n\r\n", $answer, 2); // Response Header: $answer[0] // Response Content: $answer[1] if (preg_match('#HTTP/.*? 200#', $answer[0])) { $response = trim($answer[1]); } } } return $response; } function addAituContent($content) { $newContent = $content; if ($this->canDisplayAituContent()) { $escapedUrl = $this->htmlEscape(get_permalink()); $escapedTitle = $this->htmlEscape($this->getOrigTitle()); $escapedPic = $this->htmlEscape($this->getPostThumbnailSrc()); // The first line in 'AITU_HOOK' must be an empty line. Because some blogs use 'Embeds'(http://codex.wordpress.org/Embeds) // in the post content and the embed must be on its own line. // For example, if the 'embed' happen to add before our 51aitu code, // then we have to make sure 51aitu code doesn't follow that within the same line. $newContent .= << AITU_HOOK; } global $wp_query; if ($wp_query->current_post + 1 == $wp_query->post_count) { $newContent = $this->addScriptInPage($newContent); } return $newContent; } function echoAituScript() { echo $this->createAituScript(); } function echoVerificationMeta() { $code = $this->getVerificationCode(); if ($code) { echo "\n"; } else echo "\n"; } private function addScriptInPage($content) { if ($this->getAdminOption(self::SCRIPT_IN_FOOTER)) { add_action('wp_footer', array($this, 'echoAituScript')); return $content; } else { return $content . $this->createAituScript(); } } private function createAituScript() { $enableCustomPos = $this->getAdminOption(self::ENABLE_CUSTOM_POS) ? 'true' : 'false'; $numPosts = $this->getAdminOption(self::NUM_POSTS); $server = self::AITU_SERVER; $script = ''; if (self::AITU_DEBUG) { $script = ""; } global $wp_version; $params = array( 'num' => $numPosts, 'mode' => 3, // Use the DisplayMode.AUTO as default 'displayInFeed' => $this->getAdminOption(self::DISPLAY_IN_FEED), // 1=true, 0=false 'version' => self::VERSION, 'pf' => 'WordPress' . $wp_version ); $queryParams = ''; foreach ($params as $name => $value) { $value = urlencode($value); $queryParams .= "&$name=$value"; } // Do not break the following html code into lines between each html tag. // One default filter in wordpress will replace "\n" with "
" between html tags. $script .= << 爱徒相关文章插件,快速提升流量

AITU_SCRIPT; return $script; } private function htmlEscape($str) { return htmlspecialchars(html_entity_decode($str, ENT_QUOTES, 'UTF-8'), ENT_QUOTES, 'UTF-8'); } private function getPostThumbnailSrc() { // function 'get_post_thumbnail_id' need blog support if (!function_exists('get_post_thumbnail_id')) { return; } $image_info = wp_get_attachment_image_src(get_post_thumbnail_id(), 'full'); if ($image_info) { return $image_info[0]; } } private function canDisplayAituContent() { // We can identify the live-blogging post by checking if Live Blogging plugin is activated and the shortcode in the content. // The related items will show in such live blogging post only one time generally, // but if the theme call the filter on 'the_content' hook or run another 'The Loop' to fetch the post content using in some other cases before to display, // the related items will not show. // In brief, the related items show one time at most in each post. //for debug return true; global $post; if (array_key_exists($post->ID, $this->excludePostIds)) { return false; } global $shortcode_tags; // Container for storing shortcode tags and their hook to call for the shortcode if (!empty($shortcode_tags) && is_array($shortcode_tags) && array_key_exists('liveblog', $shortcode_tags) && strpos(get_the_content(), '[liveblog]') !== false) { $this->excludePostIds[$post->ID] = 1; } return get_post_status($post->ID) == 'publish' && get_post_type() == 'post' && // In some pages e.g. "attachment page" should not display related items empty($post->post_password) && !is_preview() && // When create a new post and press the preview button before publish it, // the post's permalink is not the correct form as the setting in "Permalink". // We have to prevent the related items displaying in these pages. !is_feed() && !is_page() && // In some pages e.g. "about me" also should not display. (is_single() || !$this->getAdminOption(self::SINGLE_PAGE_ONLY)); } private function resetAdminOptions () { $this->adminOptions = array( self::NUM_POSTS => 4, self::SINGLE_PAGE_ONLY => true, self::DISPLAY_IN_FEED => true, self::ENABLE_CUSTOM_POS => false, self::SCRIPT_IN_FOOTER => true); } private function popNotifyMessage() { $notifyMessage = get_option(self::NOTIFY_MESSAGE_CONTAINER); if ($notifyMessage) { update_option(self::NOTIFY_MESSAGE_CONTAINER, ''); } return $notifyMessage; } // This function is always executed by one user(admin).So no synchronization problem may occur. private function pushNotifyMessage($message, $type = 'updated') { if ($legacyMessage = get_option(self::NOTIFY_MESSAGE_CONTAINER)) { echo 'NOTIFY_MESSAGE_CONTAINER is not empty, legacy message: ' . $legacyMessage->getMessage(); return false; } update_option(self::NOTIFY_MESSAGE_CONTAINER, new Aitu_Notify_Message($message, $type)); } private function setVerificationCode($code) { update_option(self::VERIFICATION_CODE, $code); } private function getVerificationCode() { return get_option(self::VERIFICATION_CODE); } private function saveAdminOptions() { update_option(self::ADMIN_OPTION_NAME, $this->adminOptions); } private function getAdminOption($key) { return $this->adminOptions[$key]; } // echo true will be 1, echo false will be 0 // But echo !true will be null. So it have to strictly convert false to '0'. private function getBooleanInStr($boolean) { return $boolean ? '1' : '0'; } private function addReplacementsForCheckboxState($replacementsArr, $checkboxOptionNames) { if (!is_array($checkboxOptionNames)) { return false; } foreach ($checkboxOptionNames as $optionName) { $checkState = $this->getAdminOption($optionName); $replacementsArr[$optionName . '_checked_' . $this->getBooleanInStr($checkState)] = 'checked="checked"'; $replacementsArr[$optionName . '_checked_' . $this->getBooleanInStr(!$checkState)] = ''; } return $replacementsArr; } private function printHtmlAdminOptionsPage() { $adminOptionTemplate = new Aitu_Template('adminOptionsPanel.html'); $replacements = array('request_uri' => $_SERVER["REQUEST_URI"]); $replacements = $this->addReplacementsForCheckboxState($replacements, array(self::SINGLE_PAGE_ONLY, self::DISPLAY_IN_FEED, self::ENABLE_CUSTOM_POS, self::SCRIPT_IN_FOOTER)); $numPost = $this->getAdminOption(self::NUM_POSTS); // We allow the num of related items from 1 to 12. for ($i = 1; $i <= 12; $i++) { if ($i == $numPost) { $replaceStr = 'selected="selected"'; } else { $replaceStr = ''; } $replacements['num_posts_selected_' . $i] = $replaceStr; } $replacements['verification_code'] = $this->getVerificationCode(); $adminOptionTemplate->addReplacements($replacements); echo $adminOptionTemplate->render(); } function printAdminPage() { $OptionsUpdatedMessage = array(); if (array_key_exists('add_verification_meta', $_POST)) { // WARNING: This check is not reliable if the theme doesn't call get_header() to fetch the contents of header.php. // REASON: We don't know in which file of the theme call the function get_header(), although in almost every case themes will call it. if ($this->isHookFuncInTemplate('header.php', 'wp_head')) { $code = $_POST[self::VERIFICATION_CODE]; $this->setVerificationCode($code); if ($code) { $OptionsUpdatedMessage[] = new Aitu_Notify_Message('已成功添加认证代码,现在请回到爱徒网站管理中心,点击“验证”按钮即可完成博客认证。'); } } else { $OptionsUpdatedMessage[] = new Aitu_Notify_Message('此主题可能不支持将代码嵌入到header区域,请使用其他方法进行博客认证。', 'error'); } } else { if (array_key_exists('update_aitu_settings', $_POST)) { foreach ($this->adminOptions as $key => $value) { if (array_key_exists($key, $_POST)) { $this->adminOptions[$key] = $_POST[$key]; } } $OptionsUpdatedMessage[] = new Aitu_Notify_Message('设置已更新.'); } else if (array_key_exists('reset_aitu_settings', $_POST)) { $this->resetAdminOptions(); $OptionsUpdatedMessage[] = new Aitu_Notify_Message('设置已重置.'); } if ($this->adminOptions[self::SCRIPT_IN_FOOTER] // The wp_footer action is theme-dependent. // If the theme-defined file footer.php doesn't call wp_footer(), the action will not fire. && !$this->isScriptInFooterSupported()) { $OptionsUpdatedMessage[] = new Aitu_Notify_Message('正在使用的主题可能不支持将加载脚本置于文档末尾, 建议不要开启此功能。', 'error'); } $this->saveAdminOptions(); } $this->printHtmlNotifyMessage($OptionsUpdatedMessage); $this->printHtmlAdminOptionsPage(); } private function printHtmlNotifyMessage($notifyMessages) { if (empty($notifyMessages) || !is_array($notifyMessages)) { return false; } foreach($notifyMessages as $msg) { $type = $msg->getType(); $message = $msg->getMessage(); echo "

$message

"; } } private function isHookFuncInTemplate($template, $func) { global $wp_version; $location = ''; if (strcmp($wp_version, '2.7.0') >= 0) { // Only WordPress >= 2.7.0 support footer.php located in style sheet path // and it's the first path being checked in wordpress's source code. $probableLocation = get_stylesheet_directory() . '/' . $template; if (file_exists($probableLocation)) { $location = $probableLocation; } } if (!$location) { $probableLocation = get_template_directory() . '/' . $template; if (file_exists($probableLocation)) { $location = $probableLocation; } } // this template doesn't include in current theme and wordpress will use the default template instead. // Hook function must exist in the default template if (!$location) { return true; } $contents = file_get_contents($location); return $contents !== false ? (strpos($contents, $func . '()') !== false) : false; } // WARNING: This function is not reliable if theme doesn't call get_footer() to fetch the contents of footer.php. // REASON: We don't know in which file of the theme call the function get_footer(), although in almost every case themes will call it. private function isScriptInFooterSupported() { return $this->isHookFuncInTemplate('footer.php', 'wp_footer'); } function checkFooterScriptSupportedByTheme($theme) { if ($this->getAdminOption(self::SCRIPT_IN_FOOTER) && !$this->isScriptInFooterSupported()) { $this->pushNotifyMessage('此主题不支持将爱徒相关文章脚本置于文档末尾, 可能会导致相关文章无法显示, 建议关闭此功能.', 'error'); } } function doActivation() { // now script in footer is the default setting and we need to check if it supported when activation. if (!$this->isScriptInFooterSupported()) { $this->adminOptions[self::SCRIPT_IN_FOOTER] = false; $this->saveAdminOptions(); } $this->pushNotifyMessage('爱徒相关文章插件: 1 如果您的博客装有缓存(cache)插件, 请在首次启用插件时清空缓存. 2 如果是第一次启用,请在启用后到爱徒网站管理中心注册并提交网站获取验证码 3 请到后台设置-爱徒相关文章,设置验证码 4 回到爱徒网站管理中心点击“开始验证”即可。此时爱徒爬虫会立刻去获取贵站的文章信息, 这时切记不要停掉插件, 以免影响文章的相关性.装完可来爱徒网站管理中心查看收录状况.'); } function finalize() { delete_option(self::ADMIN_OPTION_NAME); delete_option(self::NOTIFY_MESSAGE_CONTAINER); delete_option(self::VERIFICATION_CODE); } } class Aitu_Notify_Message { private $message; private $type; function __construct($message, $type = 'updated') { $this->Aitu_Notify_Message($message, $type); } function Aitu_Notify_Message($message, $type = 'updated') { if ($type != 'error') { $type = 'updated'; } $this->message = $message; $this->type = $type; } function getMessage() { return $this->message; } function getType() { return $this->type; } } class Aitu_Template { private $htmlStrTemplate; private $keyValuePair = array(); // PHP 5 Constructors function __construct($templateName) { $this->Aitu_Template($templateName); } // treated as regular method since PHP 5.3.3 function Aitu_Template($templateName) { $this->htmlStrTemplate = file_get_contents($this->aitu_get_config_file_path() . aituRelatedPosts::PLUGIN_PATH . '/templates/' . $templateName); } function addReplacements($keyValuePair) { foreach ($keyValuePair as $key => $value) { $this->keyValuePair[$this->getReplacementStr($key)] = $value; } } function render() { return str_replace(array_keys($this->keyValuePair), array_values($this->keyValuePair), $this->htmlStrTemplate); } private function getReplacementStr($key) { return '{{' . $key . '}}'; } private function aitu_get_config_file_path() { $pathname = $_SERVER['SCRIPT_FILENAME']; preg_match("/^(.+)wp-admin/Ui", $pathname, $matches); // Here if $matches[0] == 'C:/workspace/www/wordpress/wordpress/wp-admin', than matches[1] == 'C:/workspace/www/wordpress/wordpress/'. return $matches[1]; } } $aitu_incompatible_plugins_in_rss = array('ozh-better-feed/wp_ozh_betterfeed.php', 'rejected-wp-keyword-link-rejected/wp_keywordlink.php'); $aitu_incompatible_plugins_in_content = array('markdown-for-wordpress-and-bbpress/markdown.php'); function aitu_has_incompatible_plugins($incompatiblePlugins = array()) { foreach($incompatiblePlugins as $plugin) { if (is_plugin_active($plugin)) { return true; } } return false; } require_once(ABSPATH . 'wp-admin/includes/plugin.php'); $aitu_related_posts = new aituRelatedPosts(); add_action('wp_head', array($aitu_related_posts, 'echoVerificationMeta')); add_action('admin_notices', array($aitu_related_posts, 'showNotifyMessage')); add_action('admin_menu', array($aitu_related_posts, 'registerOptionsPage')); // We need to place our content as near to the front as possible, so set the priority 1. add_action('the_content', array($aitu_related_posts, 'addAituContent'), aitu_has_incompatible_plugins($aitu_incompatible_plugins_in_content) ? 99999 : 1); add_action('switch_theme', array($aitu_related_posts, 'checkFooterScriptSupportedByTheme')); // function add_filter($tag, $function_to_add, $priority, $num_accepted_args) add_filter('plugin_action_links', array($aitu_related_posts, 'addPluginActionLinks'), 10, 2); add_filter('the_excerpt_rss', array($aitu_related_posts, 'addToRssContent')); // In order to avoid some plugins removing or modifing our related items in rss, we try to delay to add our content as late as possible. // Default priority is 10. add_filter('the_content', array($aitu_related_posts, 'addToRssContent'), aitu_has_incompatible_plugins($aitu_incompatible_plugins_in_rss) ? 99999 : 10); register_activation_hook(__FILE__, array($aitu_related_posts, 'doActivation')); register_deactivation_hook(__FILE__, array($aitu_related_posts, 'finalize')); } else { function classConflictException() { echo '

插件冲突。您的博客正在运行一个与“爱徒相关文章插件”定义了相同类名的插件,只有在关闭冲突插件以后“爱徒相关文章插件”才能正常启用。

'; } add_action('admin_notices', 'classConflictException'); } ?>