core = &$core; $this->name = __($this->name, 'yarpp'); } function add_signature( $query ) { $query->yarpp_cache_type = $this->name; } /** * GENERAL CACHE CONTROL */ public function is_yarpp_time() { return $this->yarpp_time; } public function flush() { } public function setup() { } public function upgrade( $last_version ) { } /* * POST CACHE CONTROL */ /* * Note: return value changed in 3.4 * return YARPP_NO_RELATED | YARPP_RELATED | YARPP_DONT_RUN | false if no good input */ function enforce($reference_ID, $force = false) { /** * @since 3.5.3 Don't compute on revisions. * wp_is_post_revision will return the id of the revision parent instead. */ if ($the_post = wp_is_post_revision($reference_ID)) $reference_ID = $the_post; if (!is_int($reference_ID)) return false; $status = $this->is_cached($reference_ID); $status = apply_filters('yarpp_cache_enforce_status', $status, $reference_ID); // There's a stop signal: if ($status === YARPP_DONT_RUN) return YARPP_DONT_RUN; // If not cached, process now: if ($status === YARPP_NOT_CACHED || $force) $status = $this->update((int) $reference_ID); // status now will be YARPP_NO_RELATED | YARPP_RELATED // There are no related posts if ($status === YARPP_NO_RELATED) return YARPP_NO_RELATED; // There are results return YARPP_RELATED; } /** * @param int $reference_ID * @return string YARPP_NO_RELATED | YARPP_RELATED | YARPP_NOT_CACHED */ public function is_cached($reference_ID) { return YARPP_NOT_CACHED; } public function clear($reference_ID) { } /* * POST STATUS INTERACTIONS */ /** * Clear the cache for this entry and for all posts which are "related" to it. * @since 3.2 This is called when a post is deleted. */ function delete_post($post_ID) { // Clear the cache for this post. $this->clear((int) $post_ID); // Find all "peers" which list this post as a related post and clear their caches if ($peers = $this->related(null, (int) $post_ID)) $this->clear($peers); } /** * @since 3.2.1 Handle various post_status transitions */ function transition_post_status( $new_status, $old_status, $post ) { $post_ID = $post->ID; /** * @since 3.4 Don't compute on revisions * @since 3.5 Compute on the parent instead */ if ($the_post = wp_is_post_revision($post_ID)) $post_ID = $the_post; // Un-publish if ($old_status === 'publish' && $new_status !== 'publish') { // Find all "peers" which list this post as a related post and clear their caches if ($peers = $this->related(null, (int) $post_ID)) $this->clear($peers); } // Publish if ($old_status !== 'publish' && $new_status === 'publish') { /* * Find everything which is related to this post, and clear them, * so that this post might show up as related to them. */ if ($related = $this->related($post_ID, null)) $this->clear($related); } /** * @since 3.4 Simply clear the cache on save; don't recompute. */ $this->clear((int) $post_ID); } function set_score_override_flag($q) { if ($this->is_yarpp_time()) { $this->score_override = (isset($q->query_vars['orderby']) && $q->query_vars['orderby'] === 'score'); if (!empty($q->query_vars['showposts'])) { $this->online_limit = $q->query_vars['showposts']; } else { $this->online_limit = false; } } else { $this->score_override = false; $this->online_limit = false; } } /** * SQL! */ protected function sql($reference_ID = false, $args = array()) { global $wpdb, $post; if (is_object($post) && !$reference_ID) { $reference_ID = $post->ID; } if (!is_object($post) || $reference_ID != $post->ID) { $reference_post = get_post($reference_ID); } else { $reference_post = $post; } $options = array( 'threshold', 'show_pass_post', 'past_only', 'weight', 'require_tax', 'exclude', 'recent', 'limit' ); extract($this->core->parse_args($args, $options)); // The maximum number of items we'll ever want to cache $limit = max($limit, $this->core->get_option('rss_limit')); // Fetch keywords $keywords = $this->get_keywords($reference_ID); // SELECT $newsql = "SELECT $reference_ID as reference_ID, ID, "; //post_title, post_date, post_content, post_excerpt, $newsql .= 'ROUND(0'; if (isset($weight['body']) && isset($weight['body']) && (int) $weight['body']) { $newsql .= " + (MATCH (post_content) AGAINST ('".esc_sql($keywords['body'])."')) * ".absint($weight['body']); } if (isset($weight['body']) && isset($weight['title']) && (int) $weight['title']) { $newsql .= " + (MATCH (post_title) AGAINST ('".esc_sql($keywords['title'])."')) * ".absint($weight['title']); } // Build tax criteria query parts based on the weights foreach ((array) $weight['tax'] as $tax => $tax_weight) { $newsql .= " + ".$this->tax_criteria($reference_ID, $tax)." * ".intval($tax_weight); } $newsql .= ',1) as score'; $newsql .= "\n from $wpdb->posts \n"; $exclude_tt_ids = wp_parse_id_list($exclude); if (count($exclude_tt_ids) || count((array) $weight['tax']) || count($require_tax)) { $newsql .= "left join $wpdb->term_relationships as terms on ( terms.object_id = $wpdb->posts.ID ) \n"; } /* * Where */ $newsql .= " where post_status in ( 'publish', 'static' ) and ID != '$reference_ID'"; /** * @since 3.1.8 Revised $past_only option */ if ($past_only) $newsql .= " and post_date <= '$reference_post->post_date' "; if (!$show_pass_post) $newsql .= " and post_password ='' "; if ((bool) $recent) $newsql .= " and post_date > date_sub(now(), interval {$recent}) "; $newsql .= " and post_type = 'post'"; // GROUP BY $newsql .= "\n group by ID \n"; // HAVING // number_format fix suggested by vkovalcik! :) $safethreshold = number_format(max($threshold,0.1), 2, '.', ''); /** * @since 3.5.3: ID=0 is a special value; never save such a result. */ $newsql .= " having score >= $safethreshold and ID != 0"; if (count($exclude_tt_ids)) { $newsql .= " and bit_or(terms.term_taxonomy_id in (".join(',', $exclude_tt_ids).")) = 0"; } foreach ((array) $require_tax as $tax => $number) { $newsql .= ' and '.$this->tax_criteria($reference_ID, $tax).' >= '.intval($number); } $newsql .= " order by score desc limit $limit"; if (isset($args['post_type'])) { $post_types = (array) $args['post_type']; } else { $post_types = $this->core->get_post_types(); } $queries = array(); foreach ($post_types as $post_type) { $queries[] = '('.str_replace("post_type = 'post'", "post_type = '{$post_type}'", $newsql).')'; } $sql = implode(' union ', $queries); if ($this->core->debug) echo ""; $this->last_sql = $sql; return $sql; } private function tax_criteria($reference_ID, $taxonomy) { $terms = get_the_terms($reference_ID, $taxonomy); // if there are no terms of that tax if (false === $terms) return '(1 = 0)'; $tt_ids = wp_list_pluck($terms, 'term_taxonomy_id'); return "count(distinct if( terms.term_taxonomy_id in (".join(',',$tt_ids)."), terms.term_taxonomy_id, null ))"; } /* * KEYWORDS */ /** * @param int $ID * @param string $type body | title | all * @return string|array depending on whether "all" were requested or not */ public function get_keywords($ID, $type = 'all') { if (!$ID = absint($ID)) return false; $keywords = array( 'body' => $this->body_keywords($ID), 'title' => $this->title_keywords($ID) ); if (empty($keywords)) return false; if ($type === 'all') return $keywords; return $keywords[$type]; } protected function title_keywords($ID, $max = 20) { return apply_filters('yarpp_title_keywords', $this->extract_keywords(get_the_title($ID), $max, $ID), $max, $ID); } protected function body_keywords( $ID, $max = 20 ) { global $wp_current_filter; $filter_count = array_count_values( $wp_current_filter ); if ( ! empty( $filter_count['the_content'] ) && $filter_count['the_content'] > 1 ) { return ''; } $post = get_post( $ID ); if ( empty( $post ) ) { return ''; } return apply_filters( 'yarpp_body_keywords', $this->extract_keywords( apply_filters( 'the_content', $post->post_content ), $max, $ID ), $max, $ID ); } private function extract_keywords( $html, $max = 20, $ID = 0 ) { /** * @filter yarpp_extract_keywords * * Use this filter to override YARPP's built-in keyword computation * Return values should be a string of space-delimited words * * @param $keywords * @param $html unfiltered HTML content * @param (int) $max maximum number of keywords * @param (int) $ID */ if ($keywords = apply_filters('yarpp_extract_keywords', false, $html, $max, $ID)) return $keywords; if (defined('WPLANG')) { switch ( substr(WPLANG, 0, 2) ) { case 'de': $lang = 'de_DE'; break; case 'it': $lang = 'it_IT'; break; case 'pl': $lang = 'pl_PL'; break; case 'bg': $lang = 'bg_BG'; break; case 'fr': $lang = 'fr_FR'; break; case 'cs': $lang = 'cs_CZ'; break; case 'nl': $lang = 'nl_NL'; break; default: $lang = 'en_US'; break; } } else { $lang = 'en_US'; } $words_file = YARPP_DIR.'/lang/words-'.$lang.'.php'; if (file_exists($words_file)) include($words_file); if (!isset($overusedwords)) $overusedwords = array(); // strip tags and html entities $text = preg_replace('/&(#x[0-9a-f]+|#[0-9]+|[a-zA-Z]+);/', '', strip_tags($html) ); // 3.2.2: ignore soft hyphens // Requires PHP 5: http://bugs.php.net/bug.php?id=25670 $softhyphen = html_entity_decode('­',ENT_NOQUOTES,'UTF-8'); $text = str_replace($softhyphen, '', $text); $charset = get_option('blog_charset'); if ( function_exists('mb_split') && !empty($charset) ) { mb_regex_encoding($charset); $wordlist = mb_split('\s*\W+\s*', mb_strtolower($text, $charset)); } else $wordlist = preg_split('%\s*\W+\s*%', strtolower($text)); // Build an array of the unique words and number of times they occur. $tokens = array_count_values($wordlist); // Remove the stop words from the list. $overusedwords = apply_filters( 'yarpp_keywords_overused_words', $overusedwords ); if ( is_array($overusedwords) ) { foreach ($overusedwords as $word) { unset($tokens[$word]); } } // Remove words which are only a letter foreach (array_keys($tokens) as $word) { if ( function_exists('mb_strlen') ) if (mb_strlen($word) < 2) unset($tokens[$word]); else if (strlen($word) < 2) unset($tokens[$word]); } arsort($tokens, SORT_NUMERIC); $types = array_keys($tokens); if (count($types) > $max) $types = array_slice($types, 0, $max); return implode(' ', $types); } }