use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\MorphTo;
+/**
+ * @property int $id
+ * @property string $name
+ * @property string $value
+ * @property int $order
+ */
class Tag extends Model
{
use HasFactory;
{
use HasFactory;
- public $searchFactor = 1.5;
+ public $searchFactor = 1.2;
protected $fillable = ['name', 'description'];
protected $hidden = ['restricted', 'pivot', 'image_id', 'deleted_at'];
protected $table = 'bookshelves';
- public $searchFactor = 1.5;
+ public $searchFactor = 1.2;
protected $fillable = ['name', 'description', 'image_id'];
{
use HasFactory;
- public $searchFactor = 1.5;
+ public $searchFactor = 1.2;
protected $fillable = ['name', 'description', 'priority', 'book_id'];
protected $hidden = ['restricted', 'pivot', 'deleted_at'];
namespace BookStack\Entities\Tools;
+use BookStack\Actions\Tag;
use BookStack\Entities\EntityProvider;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
$entityModel->newQuery()
->select($selectFields)
+ ->with(['tags:id,name,value,entity_id,entity_type'])
->chunk($chunkSize, $chunkCallback);
}
}
return $scoresByTerm;
}
+ /**
+ * Create a scored term map from the given set of entity tags.
+ *
+ * @param Tag[] $tags
+ *
+ * @returns array<string, int>
+ */
+ protected function generateTermScoreMapFromTags(array $tags): array
+ {
+ $scoreMap = [];
+ $names = [];
+ $values = [];
+
+ foreach($tags as $tag) {
+ $names[] = $tag->name;
+ $values[] = $tag->value;
+ }
+
+ $nameMap = $this->generateTermScoreMapFromText(implode(' ', $names), 3);
+ $valueMap = $this->generateTermScoreMapFromText(implode(' ', $values), 5);
+
+ return $this->mergeTermScoreMaps($nameMap, $valueMap);
+ }
+
/**
* For the given text, return an array where the keys are the unique term words
* and the values are the frequency of that term.
protected function entityToTermDataArray(Entity $entity): array
{
$nameTermsMap = $this->generateTermScoreMapFromText($entity->name, 40 * $entity->searchFactor);
+ $tagTermsMap = $this->generateTermScoreMapFromTags($entity->tags->all());
if ($entity instanceof Page) {
$bodyTermsMap = $this->generateTermScoreMapFromHtml($entity->html);
$bodyTermsMap = $this->generateTermScoreMapFromText($entity->description, $entity->searchFactor);
}
- $mergedScoreMap = $this->mergeTermScoreMaps($nameTermsMap, $bodyTermsMap);
+ $mergedScoreMap = $this->mergeTermScoreMaps($nameTermsMap, $bodyTermsMap, $tagTermsMap);
$dataArray = [];
$entityId = $entity->id;
<h6>TermG</h6>
']);
- $entityRelationCols = ['entity_id' => $page->id, 'entity_type' => 'BookStack\\Page'];
- $scoreByTerm = SearchTerm::query()->where($entityRelationCols)->pluck('score', 'term');
+ $scoreByTerm = $page->searchTerms()->pluck('score', 'term');
$this->assertEquals(1, $scoreByTerm->get('TermA'));
$this->assertEquals(10, $scoreByTerm->get('TermB'));
<p>TermA</p>
']);
- $entityRelationCols = ['entity_id' => $page->id, 'entity_type' => 'BookStack\\Page'];
- $scoreByTerm = SearchTerm::query()->where($entityRelationCols)->pluck('score', 'term');
+ $scoreByTerm = $page->searchTerms()->pluck('score', 'term');
// Scores 40 for being in the name then 1 for being in the content
$this->assertEquals(41, $scoreByTerm->get('TermA'));
}
+
+ public function test_tag_names_and_values_are_indexed_for_search()
+ {
+ $page = $this->newPage(['name' => 'PageA', 'html' => '<p>content</p>', 'tags' => [
+ ['name' => 'Animal', 'value' => 'MeowieCat'],
+ ['name' => 'SuperImportant'],
+ ]]);
+
+ $scoreByTerm = $page->searchTerms()->pluck('score', 'term');
+ $this->assertEquals(5, $scoreByTerm->get('MeowieCat'));
+ $this->assertEquals(3, $scoreByTerm->get('Animal'));
+ $this->assertEquals(3, $scoreByTerm->get('SuperImportant'));
+ }
}