post_author; // Get last N published posts by same author // phpcs:disable WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- Small dataset (5 posts max), exclude is necessary to avoid analyzing the current post $posts = get_posts(array( 'author' => $user_id, 'post_type' => array('post', 'page'), 'post_status' => 'publish', 'numberposts' => $posts_to_analyze, 'orderby' => 'post_date', 'order' => 'DESC', 'exclude' => array($post_id) // Exclude current post )); // phpcs:enable WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude if (empty($posts)) { return $this->analyze_single_post($post); } // Analyze all posts $all_content = ''; $titles = array(); $total_words = 0; $total_sentences = 0; $total_paragraphs = 0; $all_words = array(); foreach ($posts as $post_obj) { $content = $post_obj->post_content; $clean_content = wp_strip_all_tags($content); $all_content .= ' ' . $clean_content; $titles[] = $post_obj->post_title; // Basic metrics $words = str_word_count($clean_content); $sentences = $this->count_sentences($clean_content); $paragraphs = substr_count($content, '

') + substr_count($content, ''); $total_words += $words; $total_sentences += $sentences; $total_paragraphs += $paragraphs; // Collect all words for vocabulary analysis $post_words = str_word_count(strtolower($clean_content), 1); $all_words = array_merge($all_words, $post_words); } // Calculate averages $avg_sentence_length = $total_sentences > 0 ? $total_words / $total_sentences : 0; $avg_paragraph_length = $total_paragraphs > 0 ? $total_words / $total_paragraphs : 0; // Analyze vocabulary $unique_words = array_unique($all_words); $vocabulary_ratio = count($unique_words) / max(count($all_words), 1); // Determine reading level $reading_level = $this->calculate_reading_level($avg_sentence_length, $vocabulary_ratio); // Analyze POV and tense $pov_analysis = $this->analyze_pov_tense($all_content); // Analyze tone $tone_analysis = $this->analyze_tone($all_content, $titles); // Extract common phrases $common_phrases = $this->extract_common_phrases($all_content); // Generate tone rules $tone_rules = $this->generate_tone_rules($tone_analysis, $pov_analysis, $avg_sentence_length); // Build voice profile $voice_profile = array( 'style' => $this->determine_writing_style($avg_sentence_length, $vocabulary_ratio, $pov_analysis), 'tone' => $tone_analysis['primary_tone'], 'sentence_structure' => $this->determine_sentence_structure($avg_sentence_length), 'common_phrases' => $common_phrases, 'vocabulary_level' => $reading_level, 'pov' => $pov_analysis['primary_pov'], 'tense' => $pov_analysis['primary_tense'], 'tone_rules' => $tone_rules, 'reading_level' => $reading_level, 'style_notes' => $this->generate_style_notes($tone_analysis, $pov_analysis, $avg_sentence_length), 'examples' => array_slice($titles, 0, 3), 'analyzed_at' => current_time('mysql'), 'posts_analyzed' => count($posts), 'avg_sentence_length' => round($avg_sentence_length, 1), 'avg_paragraph_length' => round($avg_paragraph_length, 1), 'vocabulary_ratio' => round($vocabulary_ratio, 3) ); return $voice_profile; } /** * Analyze a single post when no other posts are available */ private function analyze_single_post($post) { $content = $post->post_content; $clean_content = wp_strip_all_tags($content); $words = str_word_count($clean_content); $sentences = $this->count_sentences($clean_content); $avg_sentence_length = $sentences > 0 ? $words / $sentences : 0; $pov_analysis = $this->analyze_pov_tense($clean_content); $tone_analysis = $this->analyze_tone($clean_content, array($post->post_title)); $common_phrases = $this->extract_common_phrases($clean_content); return array( 'style' => $this->determine_writing_style($avg_sentence_length, 0.6, $pov_analysis), 'tone' => $tone_analysis['primary_tone'], 'sentence_structure' => $this->determine_sentence_structure($avg_sentence_length), 'common_phrases' => $common_phrases, 'vocabulary_level' => 'Moderate', 'pov' => $pov_analysis['primary_pov'], 'tense' => $pov_analysis['primary_tense'], 'tone_rules' => array('maintain consistent tone', 'use clear structure'), 'reading_level' => 'G8-10', 'style_notes' => 'Based on single post analysis', 'examples' => array($post->post_title), 'analyzed_at' => current_time('mysql'), 'posts_analyzed' => 1, 'avg_sentence_length' => round($avg_sentence_length, 1), 'avg_paragraph_length' => round($words / 5, 1), // Estimate 'vocabulary_ratio' => 0.6 ); } /** * Count sentences in text */ private function count_sentences($text) { $sentences = preg_split('/[.!?]+/', $text); return count(array_filter($sentences, 'trim')); } /** * Calculate reading level */ private function calculate_reading_level($avg_sentence_length, $vocabulary_ratio) { if ($avg_sentence_length < 12 && $vocabulary_ratio < 0.5) { return 'G6-8'; } elseif ($avg_sentence_length < 18 && $vocabulary_ratio < 0.7) { return 'G8-10'; } elseif ($avg_sentence_length < 25 && $vocabulary_ratio < 0.8) { return 'G10-12'; } else { return 'G12+'; } } /** * Analyze POV and tense */ private function analyze_pov_tense($content) { $content_lower = strtolower($content); // POV analysis $first_person = preg_match_all('/\b(i|me|my|mine|myself|we|us|our|ours|ourselves)\b/i', $content_lower); $second_person = preg_match_all('/\b(you|your|yours|yourself|yourselves)\b/i', $content_lower); $third_person = preg_match_all('/\b(he|she|it|they|him|her|them|his|hers|its|their|theirs)\b/i', $content_lower); // Tense analysis $present = preg_match_all('/\b(am|is|are|be|being|been|have|has|had|do|does|did|will|can|may|must|should)\b/i', $content_lower); $past = preg_match_all('/\b(was|were|had|did|could|would|should|might)\b/i', $content_lower); // Determine primary POV if ($first_person > $second_person && $first_person > $third_person) { $primary_pov = 'first_person'; } elseif ($second_person > $first_person && $second_person > $third_person) { $primary_pov = 'second_person'; } else { $primary_pov = 'third_person'; } // Determine primary tense $primary_tense = $present > $past ? 'present' : 'past'; return array( 'primary_pov' => $primary_pov, 'primary_tense' => $primary_tense, 'first_person_count' => $first_person, 'second_person_count' => $second_person, 'third_person_count' => $third_person, 'present_count' => $present, 'past_count' => $past ); } /** * Analyze tone */ private function analyze_tone($content, $titles) { $content_lower = strtolower($content); $titles_lower = array_map('strtolower', $titles); // Tone indicators $tone_indicators = array( 'professional' => array('research', 'study', 'analysis', 'data', 'evidence', 'conclusion'), 'conversational' => array('you know', 'actually', 'basically', 'obviously', 'clearly'), 'encouraging' => array('can', 'will', 'should', 'try', 'help', 'support', 'guide'), 'authoritative' => array('must', 'need to', 'essential', 'critical', 'important', 'key'), 'friendly' => array('welcome', 'hello', 'thanks', 'appreciate', 'enjoy', 'love'), 'educational' => array('learn', 'understand', 'explain', 'demonstrate', 'show', 'teach'), 'analytical' => array('however', 'therefore', 'consequently', 'furthermore', 'moreover'), 'casual' => array('hey', 'cool', 'awesome', 'great', 'nice', 'good') ); $tone_scores = array(); foreach ($tone_indicators as $tone => $indicators) { $score = 0; foreach ($indicators as $indicator) { $score += substr_count($content_lower, $indicator); } $tone_scores[$tone] = $score; } // Find primary tone $primary_tone = array_keys($tone_scores, max($tone_scores))[0]; // Get secondary tones arsort($tone_scores); $secondary_tones = array_slice(array_keys($tone_scores), 1, 2); return array( 'primary_tone' => $primary_tone, 'secondary_tones' => $secondary_tones, 'tone_scores' => $tone_scores ); } /** * Extract common phrases */ private function extract_common_phrases($content) { $sentences = preg_split('/[.!?]+/', $content); $phrases = array(); foreach ($sentences as $sentence) { $sentence = trim($sentence); $words = str_word_count(strtolower($sentence), 1); // Extract 3-6 word phrases for ($i = 0; $i <= count($words) - 3; $i++) { $phrase = implode(' ', array_slice($words, $i, 3)); if (strlen($phrase) > 10 && !in_array($phrase, $phrases)) { $phrases[] = $phrase; } } } // Return most common phrases (up to 5) return array_slice($phrases, 0, 5); } /** * Generate tone rules */ private function generate_tone_rules($tone_analysis, $pov_analysis, $avg_sentence_length) { $rules = array(); // Tone rules $rules[] = "Maintain {$tone_analysis['primary_tone']} tone throughout"; if (!empty($tone_analysis['secondary_tones'])) { $rules[] = "Incorporate {$tone_analysis['secondary_tones'][0]} elements when appropriate"; } // POV rules $rules[] = "Use {$pov_analysis['primary_pov']} perspective consistently"; // Sentence structure rules if ($avg_sentence_length < 15) { $rules[] = "Keep sentences concise and direct"; } elseif ($avg_sentence_length > 25) { $rules[] = "Break up long sentences for clarity"; } else { $rules[] = "Maintain balanced sentence length"; } return $rules; } /** * Determine writing style */ private function determine_writing_style($avg_sentence_length, $vocabulary_ratio, $pov_analysis) { if ($avg_sentence_length < 12) { return 'Concise and direct'; } elseif ($avg_sentence_length < 20) { return 'Balanced and readable'; } elseif ($avg_sentence_length < 30) { return 'Detailed and comprehensive'; } else { return 'Extensive and thorough'; } } /** * Determine sentence structure */ private function determine_sentence_structure($avg_sentence_length) { if ($avg_sentence_length < 15) { return 'Short, punchy sentences'; } elseif ($avg_sentence_length < 25) { return 'Medium-length, balanced sentences'; } else { return 'Varied sentence lengths with complex structures'; } } /** * Generate style notes */ private function generate_style_notes($tone_analysis, $pov_analysis, $avg_sentence_length) { $notes = array(); $notes[] = "Primary tone: {$tone_analysis['primary_tone']}"; $notes[] = "POV: {$pov_analysis['primary_pov']}"; $notes[] = "Average sentence length: " . round($avg_sentence_length, 1) . " words"; if ($avg_sentence_length < 15) { $notes[] = "Prefers concise writing"; } elseif ($avg_sentence_length > 25) { $notes[] = "Uses detailed explanations"; } return implode('; ', $notes); } /** * Get author voice profile for a post */ public function get_author_voice_profile($post_id) { $profile = get_post_meta($post_id, '_aeo_bob_author_voice_profile', true); return $profile ? $profile : false; } /** * Update author voice profile for a post */ public function update_author_voice_profile($post_id) { $profile = $this->build_author_voice_profile($post_id); if ($profile) { update_post_meta($post_id, '_aeo_bob_author_voice_profile', $profile); return $profile; } return false; } }