Skip to content
← Back to Functions
Code

Content Similarity Scorer

Scores similarity between two text records using token overlap and normalized terms.

Function signature

ogScoreContentSimilarity(first_text, second_text, options = array())

Categories

  • Security

Parameters

first_textFirst text value to compare.second_textSecond text value to compare.optionsOptional stop_words and min_token_length settings. Recognized keys: `min_token_length`, `stop_words`.

Return value

Short public-safe status message.

  • score
  • score_percent
  • first_token_count
  • second_token_count
  • shared_token_count
  • shared_terms

Compatibility

Existing function name and call order preserved; metadata signature corrected to source.

Minimum PHP version: 7.4

Security notes

Validate request method, identity, permissions, and caller-owned allowlists before use; keep secrets and internal paths out of public output.

Code

<?php

/*
 * Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
 * Free for personal and internal use. Paid project use requires visible credit
 * to Jeffery L. Paris. Corporate use requires a paid license fee unless a
 * separate written license states otherwise.
 */

/**
 * Scores similarity between two text records using token overlap and normalized terms.
 *
 * Primary use case: Duplicate content detection and related items.
 * Typical inputs: text A, text B, scoring options.
 * Typical output: score from 0 to 1.
 *
 * Implementation note: Use as heuristic; not legal plagiarism detection.
 *
 * @param string $first_text First text value to compare.
 * @param string $second_text Second text value to compare.
 * @param array $options Optional stop_words and min_token_length settings.
 * @return array Structured result data with score, token counts, and shared terms.
 */
function ogScoreContentSimilarity($first_text, $second_text, $options = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	if (!is_array($options)) {
		$options = array();
	}

	$first_text = trim(strip_tags((string)$first_text));
	$second_text = trim(strip_tags((string)$second_text));

	if (empty($first_text)) {
		$result['message'] = 'First text is empty.';
		return $result;
	}

	if (empty($second_text)) {
		$result['message'] = 'Second text is empty.';
		return $result;
	}

	$min_token_length = 3;
	if (!empty($options['min_token_length'])) {
		$min_token_length = (int)$options['min_token_length'];
	}
	if ($min_token_length < 1) {
		$min_token_length = 1;
	}

	$stop_words = array('the', 'and', 'for', 'with', 'this', 'that', 'from', 'you', 'your', 'are', 'was', 'were', 'has', 'have');
	if (!empty($options['stop_words']) && is_array($options['stop_words'])) {
		$stop_words = $options['stop_words'];
	}

	$first_words = preg_split('/[^a-z0-9]+/i', strtolower($first_text));
	$second_words = preg_split('/[^a-z0-9]+/i', strtolower($second_text));
	$first_tokens = array();
	$second_tokens = array();

	foreach ($first_words as $word) {
		$word = trim($word);
		if (strlen($word) >= $min_token_length && !in_array($word, $stop_words, true)) {
			$first_tokens[$word] = true;
		}
	}

	foreach ($second_words as $word) {
		$word = trim($word);
		if (strlen($word) >= $min_token_length && !in_array($word, $stop_words, true)) {
			$second_tokens[$word] = true;
		}
	}

	$first_keys = array_keys($first_tokens);
	$second_keys = array_keys($second_tokens);
	$shared = array_values(array_intersect($first_keys, $second_keys));
	$union = array_values(array_unique(array_merge($first_keys, $second_keys)));
	$score = 0;

	if (count($union) > 0) {
		$score = count($shared) / count($union);
	}

	$result['success'] = true;
	$result['message'] = 'Content similarity scored.';
	$result['data'] = array(
		'score' => $score,
		'score_percent' => round($score * 100, 2),
		'first_token_count' => count($first_keys),
		'second_token_count' => count($second_keys),
		'shared_token_count' => count($shared),
		'shared_terms' => $shared
	);

	return $result;
}