2021-12-04 00:05:10 +01:00
|
|
|
<?php
|
2023-01-07 00:06:20 +01:00
|
|
|
|
2021-12-04 00:05:10 +01:00
|
|
|
namespace Grav\Plugin;
|
|
|
|
|
2023-01-07 00:06:20 +01:00
|
|
|
use Grav\Common\Cache;
|
|
|
|
use Grav\Common\Config\Config;
|
|
|
|
use Grav\Common\Debugger;
|
2021-12-04 00:05:10 +01:00
|
|
|
use Grav\Common\Page\Interfaces\PageInterface;
|
2023-01-07 00:06:20 +01:00
|
|
|
use Grav\Common\Page\Pages;
|
|
|
|
use Grav\Common\Plugin;
|
2021-12-04 00:05:10 +01:00
|
|
|
|
|
|
|
class RelatedPagesPlugin extends Plugin
|
|
|
|
{
|
|
|
|
protected $related_pages = [];
|
|
|
|
|
|
|
|
/** @var Config $config */
|
|
|
|
protected $config;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function getSubscribedEvents()
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
'onPluginsInitialized' => ['onPluginsInitialized', 0]
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize configuration
|
|
|
|
*/
|
|
|
|
public function onPluginsInitialized()
|
|
|
|
{
|
|
|
|
if ($this->isAdmin()) {
|
|
|
|
$this->active = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->config = $this->grav['config']->get('plugins.relatedpages');
|
|
|
|
|
|
|
|
$this->enable([
|
|
|
|
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
|
|
|
|
'onPageInitialized' => ['onPageInitialized', 0]
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add current directory to twig lookup paths.
|
|
|
|
*/
|
|
|
|
public function onTwigTemplatePaths()
|
|
|
|
{
|
|
|
|
$this->grav['twig']->twig_paths[] = __DIR__ . '/templates';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function onPageInitialized()
|
|
|
|
{
|
|
|
|
/** @var Cache $cache */
|
|
|
|
$cache = $this->grav['cache'];
|
|
|
|
/** @var Pages $pages */
|
|
|
|
$pages = $this->grav['pages'];
|
|
|
|
/** @var PageInterface $page */
|
|
|
|
$page = $this->grav['page'];
|
|
|
|
/** @var Debugger $debugger */
|
|
|
|
$debugger = $this->grav['debugger'];
|
|
|
|
|
|
|
|
$config = $this->config;
|
|
|
|
|
|
|
|
|
|
|
|
$this->enable([
|
|
|
|
'onTwigSiteVariables' => ['onTwigSiteVariables', 0]
|
|
|
|
]);
|
|
|
|
|
2023-01-07 00:06:20 +01:00
|
|
|
$cache_id = md5('relatedpages' . $page->path() . $cache->getKey());
|
2021-12-04 00:05:10 +01:00
|
|
|
$this->related_pages = $cache->fetch($cache_id);
|
|
|
|
|
|
|
|
if ($this->related_pages === false) {
|
|
|
|
|
|
|
|
// get all the pages
|
|
|
|
$collection = $page->collection($config['filter']);
|
|
|
|
|
2023-01-07 00:06:20 +01:00
|
|
|
//If the header of a page has a type and it is included in the excluded types remove it from the collection
|
|
|
|
foreach ($collection as $pageKey) {
|
|
|
|
$header = $pageKey->header();
|
|
|
|
if (
|
|
|
|
property_exists($header, 'type') &&
|
|
|
|
array_key_exists('filter', $config) &&
|
|
|
|
array_key_exists('excluded_types', $config['filter']) &&
|
|
|
|
in_array($header->type, $config['filter']['excluded_types'])
|
|
|
|
) {
|
|
|
|
$collection->remove($pageKey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-04 00:05:10 +01:00
|
|
|
// perform check if page must be in filter values
|
2023-01-07 00:06:20 +01:00
|
|
|
if ($config['page_in_filter'] && !array_key_exists($page->path(), $collection->toArray())) {
|
2021-12-04 00:05:10 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset array
|
|
|
|
$this->related_pages = [];
|
|
|
|
$debugger->addMessage('RelatedPages Plugin cache miss. Rebuilding...');
|
|
|
|
|
|
|
|
// check for explicit related pages
|
|
|
|
if ($config['explicit_pages']['process']) {
|
|
|
|
$page_header = $page->header();
|
|
|
|
if (isset($page_header->related_pages)) {
|
|
|
|
$explicit_pages = [];
|
|
|
|
$score = $config['explicit_pages']['score'];
|
|
|
|
|
|
|
|
foreach ($page_header->related_pages as $slug) {
|
|
|
|
$item = $pages->dispatch($slug);
|
|
|
|
if ($item) {
|
|
|
|
$explicit_pages[$item->path()] = $score;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$this->mergeRelatedPages($explicit_pages);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for taxonomy and content
|
|
|
|
$process_taxonomy2taxonomy = $config['taxonomy_match']['taxonomy_taxonomy']['process'];
|
|
|
|
$process_taxonomy2content = $config['taxonomy_match']['taxonomy_content']['process'];
|
|
|
|
$process_content = $config['content_match']['process'];
|
|
|
|
|
|
|
|
if ($process_taxonomy2taxonomy || $process_taxonomy2content || $process_content) {
|
|
|
|
$taxonomy_taxonomy_matches = [];
|
|
|
|
$taxonomy_content_matches = [];
|
|
|
|
$content_matches = [];
|
|
|
|
$page_taxonomies = $page->taxonomy();
|
|
|
|
|
|
|
|
foreach ($collection as $item) {
|
|
|
|
if ($page === $item) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// count taxonomy to taxonomy matches
|
|
|
|
if ($process_taxonomy2taxonomy) {
|
|
|
|
|
|
|
|
// Check for multiple taxonomies.
|
|
|
|
$taxonomy_list = $config['taxonomy_match']['taxonomy'];
|
|
|
|
// Support the single value by converting it to array.
|
2023-01-07 00:06:20 +01:00
|
|
|
if (!\is_array($taxonomy_list)) {
|
2021-12-04 00:05:10 +01:00
|
|
|
$taxonomy_list = array($taxonomy_list);
|
|
|
|
}
|
|
|
|
$score_scale = $config['taxonomy_match']['taxonomy_taxonomy']['score_scale'];
|
2023-01-07 00:06:20 +01:00
|
|
|
|
2021-12-04 00:05:10 +01:00
|
|
|
$score = 0;
|
|
|
|
$has_matches = false;
|
|
|
|
foreach ($taxonomy_list as $taxonomy) {
|
|
|
|
if (isset($page_taxonomies[$taxonomy])) {
|
|
|
|
$page_taxonomy = $page_taxonomies[$taxonomy];
|
|
|
|
$item_taxonomies = $item->taxonomy();
|
|
|
|
|
|
|
|
if (isset($item_taxonomies[$taxonomy])) {
|
|
|
|
$item_taxonomy = $item_taxonomies[$taxonomy];
|
|
|
|
$count = count(array_intersect($page_taxonomy, $item_taxonomy));
|
|
|
|
|
|
|
|
if ($count > 0) {
|
|
|
|
if (array_key_exists($count, $score_scale)) {
|
|
|
|
$score += $score_scale[$count];
|
|
|
|
} else {
|
|
|
|
$score += max(array_keys($score_scale));
|
|
|
|
}
|
2023-01-07 00:06:20 +01:00
|
|
|
|
2021-12-04 00:05:10 +01:00
|
|
|
$has_matches = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-07 00:06:20 +01:00
|
|
|
|
2021-12-04 00:05:10 +01:00
|
|
|
if ($has_matches) {
|
|
|
|
$taxonomy_taxonomy_matches[$item->path()] = $score;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// count taxonomy to content matches
|
|
|
|
if ($process_taxonomy2content) {
|
|
|
|
|
|
|
|
// Check for multiple taxonomies.
|
|
|
|
$taxonomy_list = $config['taxonomy_match']['taxonomy'];
|
|
|
|
// Support the single value by converting it to array.
|
2023-01-07 00:06:20 +01:00
|
|
|
if (!is_array($taxonomy_list)) {
|
2021-12-04 00:05:10 +01:00
|
|
|
$taxonomy_list = array($taxonomy_list);
|
|
|
|
}
|
|
|
|
$score_scale = $config['taxonomy_match']['taxonomy_content']['score_scale'];
|
|
|
|
|
|
|
|
$score = 0;
|
|
|
|
$has_matches = false;
|
|
|
|
foreach ($taxonomy_list as $taxonomy) {
|
|
|
|
if (isset($page_taxonomies[$taxonomy])) {
|
|
|
|
$page_taxonomy = $page_taxonomies[$taxonomy];
|
2023-01-07 00:06:20 +01:00
|
|
|
$count = $this->substringCountArray($item->title() . ' ' . $item->rawMarkdown(), $page_taxonomy);
|
2021-12-04 00:05:10 +01:00
|
|
|
|
|
|
|
if ($count > 0) {
|
|
|
|
if (array_key_exists($count, $score_scale)) {
|
|
|
|
$score += $score_scale[$count];
|
|
|
|
} else {
|
|
|
|
$score += max(array_keys($score_scale));
|
|
|
|
}
|
2023-01-07 00:06:20 +01:00
|
|
|
|
2021-12-04 00:05:10 +01:00
|
|
|
$has_matches = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-07 00:06:20 +01:00
|
|
|
|
2021-12-04 00:05:10 +01:00
|
|
|
if ($has_matches) {
|
|
|
|
$taxonomy_content_matches[$item->path()] = $score;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// compute score of content to content matches
|
|
|
|
if ($process_content) {
|
|
|
|
similar_text($page->rawMarkdown(), $item->rawMarkdown(), $score);
|
|
|
|
$content_matches[$item->path()] = (int)$score;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$this->mergeRelatedPages($taxonomy_taxonomy_matches);
|
|
|
|
$this->mergeRelatedPages($taxonomy_content_matches);
|
|
|
|
$this->mergeRelatedPages($content_matches);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort the resulting list
|
|
|
|
arsort($this->related_pages, SORT_NUMERIC);
|
|
|
|
|
|
|
|
// Shorten the resulting list if configured
|
|
|
|
if ($config['limit'] > 0) {
|
|
|
|
$this->related_pages = array_slice($this->related_pages, 0, $config['limit']);
|
|
|
|
}
|
|
|
|
|
|
|
|
$cache->save($cache_id, $this->related_pages);
|
|
|
|
} else {
|
|
|
|
$debugger->addMessage("RelatedPages Plugin cache hit.");
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* if enabled on this page, load the JS + CSS theme.
|
|
|
|
*/
|
|
|
|
public function onTwigSiteVariables()
|
|
|
|
{
|
|
|
|
$this->grav['twig']->twig_vars['related_pages'] = $this->related_pages;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function substringCountArray($haystack, array $needle)
|
|
|
|
{
|
|
|
|
$count = 0;
|
|
|
|
foreach ((array)$needle as $substring) {
|
|
|
|
$count += substr_count(strtolower($haystack), strtolower($substring));
|
|
|
|
}
|
|
|
|
return $count;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function mergeRelatedPages(array $pages)
|
|
|
|
{
|
2023-01-07 00:06:20 +01:00
|
|
|
foreach ((array)$pages as $path => $score) {
|
2021-12-04 00:05:10 +01:00
|
|
|
$page_exists = array_key_exists($path, $this->related_pages);
|
|
|
|
if ($score > $this->config['score_threshold'] &&
|
|
|
|
(!$page_exists || ($page_exists && $score > $this->related_pages[$path]))) {
|
|
|
|
$this->related_pages[$path] = $score;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|