Skip to content

Commit

Permalink
Merge pull request #3874 from jonasraoni/feature/main/8710-address-re…
Browse files Browse the repository at this point in the history
…commend-by-similarity-performance

Feature/main/8710 address recommend by similarity performance
  • Loading branch information
jonasraoni authored Aug 25, 2023
2 parents b6380ba + d805189 commit b1a84d3
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 83 deletions.
151 changes: 88 additions & 63 deletions plugins/generic/recommendBySimilarity/RecommendBySimilarityPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,99 +15,124 @@
namespace APP\plugins\generic\recommendBySimilarity;

use APP\core\Application;
use APP\facades\Repo;
use APP\handler\Handler;
use APP\search\ArticleSearch;
use APP\submission\Collector;
use APP\submission\Submission;
use APP\template\TemplateManager;
use PKP\plugins\GenericPlugin;
use PKP\plugins\Hook;

define('RECOMMEND_BY_SIMILARITY_PLUGIN_COUNT', 10);

class RecommendBySimilarityPlugin extends GenericPlugin
{
//
// Implement template methods from Plugin.
//
private const DEFAULT_RECOMMENDATION_COUNT = 10;
private const MAX_SEARCH_KEYWORDS = 20;

/**
* @copydoc Plugin::register()
*
* @param null|mixed $mainContextId
*/
public function register($category, $path, $mainContextId = null)
public function register($category, $path, $mainContextId = null): bool
{
$success = parent::register($category, $path, $mainContextId);
if (Application::isUnderMaintenance()) {
return $success;
if (!parent::register($category, $path, $mainContextId)) {
return false;
}

if ($success && $this->getEnabled($mainContextId)) {
Hook::add('Templates::Article::Footer::PageFooter', $this->callbackTemplateArticlePageFooter(...));
if (!Application::isUnderMaintenance() && $this->getEnabled($mainContextId)) {
Hook::add('Templates::Article::Footer::PageFooter', function (string $hookName, array $params): bool {
$output = & $params[2];
$output .= $this->buildTemplate();
return Hook::CONTINUE;
});
}
return $success;
return true;
}

/**
* @see Plugin::getDisplayName()
* Builds the template with the recommended submissions or null if the linked submission has no keywords
*
* @see templates/article/footer.tpl
*/
public function getDisplayName()
private function buildTemplate(): ?string
{
return __('plugins.generic.recommendBySimilarity.displayName');
$templateManager = TemplateManager::getManager();
$submissionId = $templateManager->getTemplateVars('article')->getId();

// If there's no keywords, quit
if (!strlen($searchPhrase = implode(' ', (new ArticleSearch())->getSimilarityTerms($submissionId)))) {
return null;
}

$request = Application::get()->getRequest();
$router = $request->getRouter();
$context = $router->getContext($request);

$rangeInfo = Handler::getRangeInfo($request, 'articlesBySimilarity');
$rangeInfo->setCount(static::DEFAULT_RECOMMENDATION_COUNT);

// Prepares the collector to retrieve similar submissions
$collector = Repo::submission()
->getCollector()
->excludeIds([$submissionId])
->filterByContextIds([$context->getId()])
->filterByStatus([Submission::STATUS_PUBLISHED])
->searchPhrase($searchPhrase, static::MAX_SEARCH_KEYWORDS);

$offset = ($rangeInfo->getPage() - 1) * $rangeInfo->getCount();
$submissionCount = $collector->getCount();

$submissions = $collector
->limit($rangeInfo->getCount())
->offset($offset)
->orderBy(Collector::ORDERBY_SEARCH_RANKING)
->getMany();

// Load the linked issues
$issues = Repo::issue()->getCollector()
->filterByContextIds([$context->getId()])
->filterByIssueIds(
$submissions->map(fn (Submission $submission) => $submission->getCurrentPublication()->getData('issueId'))
->unique()
->toArray()
)
->getMany();

$nextPage = $rangeInfo->getPage() * $rangeInfo->getCount() < $submissionCount
? $request->url(path: $submissionId, params: ['articlesBySimilarityPage' => $rangeInfo->getPage() + 1])
: null;
$previousPage = $rangeInfo->getPage() > 1
? $request->url(path: $submissionId, params: ['articlesBySimilarityPage' => $rangeInfo->getPage() - 1])
: null;

$templateManager->assign('articlesBySimilarity', (object) [
'submissions' => $submissions,
'query' => $searchPhrase,
'issues' => $issues,
'start' => $offset + 1,
'end' => $offset + $submissions->count(),
'total' => $submissionCount,
'nextUrl' => $nextPage,
'previousUrl' => $previousPage
]);

return $templateManager->fetch($this->getTemplateResource('articleFooter.tpl'));
}

/**
* @see Plugin::getDescription()
* @copydoc Plugin::getDisplayName()
*/
public function getDescription()
public function getDisplayName(): string
{
return __('plugins.generic.recommendBySimilarity.description');
return __('plugins.generic.recommendBySimilarity.displayName');
}


//
// View level hook implementations.
//
/**
* @see templates/article/footer.tpl
* @copydoc Plugin::getDescription()
*/
public function callbackTemplateArticlePageFooter($hookName, $params)
public function getDescription(): string
{
$smarty = & $params[1];
$output = & $params[2];

// Identify similarity terms for the given article.
$displayedArticle = $smarty->getTemplateVars('article');
$articleId = $displayedArticle->getId();
$articleSearch = new ArticleSearch();
$searchTerms = $articleSearch->getSimilarityTerms($articleId);
if (empty($searchTerms)) {
return false;
}

// If we got similarity terms then execute a search with...
// ... request, journal and error messages, ...
$request = Application::get()->getRequest();
$router = $request->getRouter();
$journal = $router->getContext($request);
$error = null;
// ... search keywords ...
$query = implode(' ', $searchTerms);
$keywords = [null => $query];
// ... and pagination.
$rangeInfo = Handler::getRangeInfo($request, 'articlesBySimilarity');
$rangeInfo->setCount(RECOMMEND_BY_SIMILARITY_PLUGIN_COUNT);
$smarty->assign([
'articlesBySimilarity' => $articleSearch->retrieveResults(
$request,
$journal,
$keywords,
$error,
null,
null,
$rangeInfo,
[$articleId]
),
'articlesBySimilarityQuery' => $query,
]);
$output .= $smarty->fetch($this->getTemplateResource('articleFooter.tpl'));
return false;
return __('plugins.generic.recommendBySimilarity.description');
}
}
47 changes: 28 additions & 19 deletions plugins/generic/recommendBySimilarity/templates/articleFooter.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,47 @@
* A template to be included via Templates::Article::Footer::PageFooter hook.
*}
<div id="articlesBySimilarityList">
{if !$articlesBySimilarity->wasEmpty()}
<h3>{translate key="plugins.generic.recommendBySimilarity.heading"}</h3>

{if !$articlesBySimilarity->submissions->isEmpty()}
<h3>
<a name="articlesBySimilarity">{translate key="plugins.generic.recommendBySimilarity.heading"}</a>
</h3>
<ul>
{iterate from=articlesBySimilarity item=articleBySimilarity}
{assign var=submission value=$articleBySimilarity.publishedSubmission}
{assign var=article value=$articleBySimilarity.article}
{assign var=issue value=$articleBySimilarity.issue}
{assign var=journal value=$articleBySimilarity.journal}
{foreach from=$articlesBySimilarity->submissions item=submission}
{assign var=publication value=$submission->getCurrentPublication()}
{assign var=issue value=$articlesBySimilarity->issues->get($publication->getData('issueId'))}

<li>
{foreach from=$article->getCurrentPublication()->getData('authors') item=author}
{foreach from=$publication->getData('authors') item=author}
{$author->getFullName()|escape},
{/foreach}
<a href="{url journal=$journal->getPath() page="article" op="view" path=$submission->getBestId()}">
{$article->getLocalizedTitle()|strip_unsafe_html}
</a>,
<a href="{url journal=$journal->getPath() page="issue" op="view" path=$issue->getBestIssueId()}">
{$journal->getLocalizedName()|escape}: {$issue->getIssueIdentification()|escape}
<a href="{url journal=$currentContext->getPath() page="article" op="view" path=$submission->getBestId()}">
{$submission->getLocalizedTitle()|strip_unsafe_html}
</a>
{if $issue},
<a href="{url journal=$currentContext->getPath() page="issue" op="view" path=$issue->getBestIssueId()}">
{$currentContext->getLocalizedName()|escape}: {$issue->getIssueIdentification()|escape}
</a>
{/if}
</li>
{/iterate}
{/foreach}
</ul>
<p id="articlesBySimilarityPages">
{page_links anchor="articlesBySimilarity" iterator=$articlesBySimilarity name="articlesBySimilarity"}
{include
file="frontend/components/pagination.tpl"
prevUrl=$articlesBySimilarity->previousUrl
nextUrl=$articlesBySimilarity->nextUrl
showingStart=$articlesBySimilarity->start
showingEnd=$articlesBySimilarity->end
total=$articlesBySimilarity->total
}
</p>
<p id="articlesBySimilaritySearch">
{capture assign="advancedSearchLink"}{strip}
<a href="{url page="search" query=$articlesBySimilarityQuery}">
{capture assign="articlesBySimilaritySearchLink"}{strip}
<a href="{url page="search" op="search" query=$articlesBySimilarity->query}">
{translate key="plugins.generic.recommendBySimilarity.advancedSearch"}
</a>
{/strip}{/capture}
{translate key="plugins.generic.recommendBySimilarity.advancedSearchIntro" advancedSearchLink=$advancedSearchLink}
{translate key="plugins.generic.recommendBySimilarity.advancedSearchIntro" advancedSearchLink=$articlesBySimilaritySearchLink}
</p>
{/if}
</div>

0 comments on commit b1a84d3

Please sign in to comment.