<?php
/**
* @file classes/search/ArticleSearch.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class ArticleSearch
*
* @ingroup search
*
* @see ArticleSearchDAO
*
* @brief Class for retrieving article search results.
*
*/
namespace APP\search;
use APP\core\Application;
use APP\core\Request;
use APP\core\Services;
use APP\facades\Repo;
use APP\issue\IssueAction;
use PKP\db\DAORegistry;
use PKP\facades\Locale;
use PKP\plugins\Hook;
use PKP\search\SubmissionSearch;
use PKP\submission\PKPSubmission;
class ArticleSearch extends SubmissionSearch
{
/**
* See SubmissionSearch::getSparseArray()
*/
public function getSparseArray($unorderedResults, $orderBy, $orderDir, $exclude)
{
// Calculate a well-ordered (unique) score.
$resultCount = count($unorderedResults);
$i = 0;
$contextIds = [];
foreach ($unorderedResults as $submissionId => &$data) {
// Reference is necessary to permit modification
$data['score'] = ($resultCount * $data['count']) + $i++;
$contextIds[] = $data['journal_id'];
}
// If we got a primary sort order then apply it and use score as secondary
// order only.
// NB: We apply order after merging and before paging/formatting. Applying
// order before merging would require us to retrieve dependent objects for
// results being purged later. Doing everything in a closed SQL is not
// possible (e.g. for authors). Applying sort order after paging and
// formatting is not possible as we have to order the whole list before
// slicing it. So this seems to be the most appropriate place, although we
// may have to retrieve some objects again when formatting results.
$orderedResults = [];
$contextDao = Application::getContextDAO();
$contextTitles = [];
if ($orderBy == 'popularityAll' || $orderBy == 'popularityMonth') {
// Retrieve a metrics report for all submissions.
$filter = [
'submissionIds' => array_keys($unorderedResults),
'contextIds' => $contextIds,
'assocTypes' => [Application::ASSOC_TYPE_SUBMISSION, Application::ASSOC_TYPE_SUBMISSION_FILE]
];
if ($orderBy == 'popularityMonth') {
$oneMonthAgo = date('Ymd', strtotime('-1 month'));
$today = date('Ymd');
$filter['dateStart'] = $oneMonthAgo;
$filter['dateEnd'] = $today;
}
$rawReport = Services::get('publicationStats')->getTotals($filter);
foreach ($rawReport as $row) {
$unorderedResults[$row->submission_id]['metric'] = $row->metric;
}
}
$i = 0; // Used to prevent ties from clobbering each other
$authorUserGroups = Repo::userGroup()->getCollector()->filterByRoleIds([\PKP\security\Role::ROLE_ID_AUTHOR])->getMany();
foreach ($unorderedResults as $submissionId => $data) {
// Exclude unwanted IDs.
if (in_array($submissionId, $exclude)) {
continue;
}
switch ($orderBy) {
case 'authors':
$submission = Repo::submission()->get($submissionId);
$orderKey = $submission->getCurrentPublication()->getAuthorString($authorUserGroups);
break;
case 'title':
$submission = Repo::submission()->get($submissionId);
$orderKey = '';
if (!empty($submission->getCurrentPublication())) {
$orderKey = $submission->getCurrentPublication()->getLocalizedData('title');
}
break;
case 'journalTitle':
if (!isset($contextTitles[$data['journal_id']])) {
$context = $contextDao->getById($data['journal_id']);
$contextTitles[$data['journal_id']] = $context->getLocalizedName();
}
$orderKey = $contextTitles[$data['journal_id']];
break;
case 'issuePublicationDate':
case 'publicationDate':
$orderKey = $data[$orderBy];
break;
case 'popularityAll':
case 'popularityMonth':
$orderKey = ($data['metric'] ?? 0);
break;
default: // order by score.
$orderKey = $data['score'];
}
if (!isset($orderedResults[$orderKey])) {
$orderedResults[$orderKey] = [];
}
$orderedResults[$orderKey][$data['score'] + $i++] = $submissionId;
}
// Order the results by primary order.
if (strtolower($orderDir) == 'asc') {
ksort($orderedResults);
} else {
krsort($orderedResults);
}
// Order the result by secondary order and flatten it.
$finalOrder = [];
foreach ($orderedResults as $orderKey => $submissionIds) {
if (count($submissionIds) == 1) {
$finalOrder[] = array_pop($submissionIds);
} else {
if (strtolower($orderDir) == 'asc') {
ksort($submissionIds);
} else {
krsort($submissionIds);
}
$finalOrder = array_merge($finalOrder, array_values($submissionIds));
}
}
return $finalOrder;
}
/**
* Retrieve the search filters from the request.
*
* @param Request $request
*
* @return array All search filters (empty and active)
*/
public function getSearchFilters($request)
{
$searchFilters = [
'query' => $request->getUserVar('query'),
'searchJournal' => $request->getUserVar('searchJournal'),
'abstract' => $request->getUserVar('abstract'),
'authors' => $request->getUserVar('authors'),
'title' => $request->getUserVar('title'),
'galleyFullText' => $request->getUserVar('galleyFullText'),
'discipline' => $request->getUserVar('discipline'),
'subject' => $request->getUserVar('subject'),
'type' => $request->getUserVar('type'),
'coverage' => $request->getUserVar('coverage'),
'indexTerms' => $request->getUserVar('indexTerms')
];
// Is this a simplified query from the navigation
// block plugin?
$simpleQuery = $request->getUserVar('simpleQuery');
if (!empty($simpleQuery)) {
// In the case of a simplified query we get the
// filter type from a drop-down.
$searchType = $request->getUserVar('searchField');
if (array_key_exists($searchType, $searchFilters)) {
$searchFilters[$searchType] = $simpleQuery;
}
}
// Publishing dates.
$fromDate = $request->getUserDateVar('dateFrom', 1, 1);
$searchFilters['fromDate'] = (is_null($fromDate) ? null : date('Y-m-d H:i:s', $fromDate));
$toDate = $request->getUserDateVar('dateTo', 32, 12, null, 23, 59, 59);
$searchFilters['toDate'] = (is_null($toDate) ? null : date('Y-m-d H:i:s', $toDate));
// Instantiate the context.
$context = $request->getContext();
$siteSearch = !((bool)$context);
if ($siteSearch) {
$contextDao = Application::getContextDAO();
if (!empty($searchFilters['searchJournal'])) {
$context = $contextDao->getById($searchFilters['searchJournal']);
} elseif (array_key_exists('journalTitle', $request->getUserVars())) {
$contexts = $contextDao->getAll(true);
while ($context = $contexts->next()) {
if (in_array(
$request->getUserVar('journalTitle'),
(array) $context->getName(null)
)) {
break;
}
}
}
}
$searchFilters['searchJournal'] = $context;
$searchFilters['siteSearch'] = $siteSearch;
return $searchFilters;
}
/**
* Load the keywords array from a given search filter.
*
* @param array $searchFilters Search filters as returned from
* ArticleSearch::getSearchFilters()
*
* @return array Keyword array as required by SubmissionSearch::retrieveResults()
*/
public function getKeywordsFromSearchFilters($searchFilters)
{
$indexFieldMap = $this->getIndexFieldMap();
$indexFieldMap[SubmissionSearch::SUBMISSION_SEARCH_INDEX_TERMS] = 'indexTerms';
$keywords = [];
if (isset($searchFilters['query'])) {
$keywords[''] = $searchFilters['query'];
}
foreach ($indexFieldMap as $bitmap => $searchField) {
if (isset($searchFilters[$searchField]) && !empty($searchFilters[$searchField])) {
$keywords[$bitmap] = $searchFilters[$searchField];
}
}
return $keywords;
}
/**
* See SubmissionSearch::formatResults()
*
* @param array $results
* @param \PKP\user\User $user optional (if availability information is desired)
*
* @return array An array with the articles, published submissions,
* issue, journal, section and the issue availability.
*/
public function formatResults($results, $user = null)
{
$contextDao = Application::getContextDAO();
$publishedSubmissionCache = [];
$articleCache = [];
$issueCache = [];
$issueAvailabilityCache = [];
$contextCache = [];
$sectionCache = [];
$returner = [];
foreach ($results as $articleId) {
// Get the article, storing in cache if necessary.
if (!isset($articleCache[$articleId])) {
$submission = Repo::submission()->get($articleId);
$publishedSubmissionCache[$articleId] = $submission;
$articleCache[$articleId] = $submission;
}
$article = $articleCache[$articleId];
$publishedSubmission = $publishedSubmissionCache[$articleId];
if ($publishedSubmission && $article) {
$sectionId = $article->getSectionId();
if (!isset($sectionCache[$sectionId])) {
$sectionCache[$sectionId] = Repo::section()->get($sectionId, $article->getData('contextId'));
}
// Get the context, storing in cache if necessary.
$contextId = $article->getData('contextId');
if (!isset($contextCache[$contextId])) {
$contextCache[$contextId] = $contextDao->getById($contextId);
}
// Get the issue, storing in cache if necessary.
$issueId = $publishedSubmission->getCurrentPublication()->getData('issueId');
if ($issueId && !isset($issueCache[$issueId])) {
$issue = Repo::issue()->get($issueId);
$issueCache[$issueId] = $issue;
$issueAction = new IssueAction();
$issueAvailabilityCache[$issueId] = !$issueAction->subscriptionRequired($issue, $contextCache[$contextId]) || $issueAction->subscribedUser($user, $contextCache[$contextId], $issueId, $articleId) || $issueAction->subscribedDomain(Application::get()->getRequest(), $contextCache[$contextId], $issueId, $articleId);
}
// Only display articles from published issues.
if (!isset($issueCache[$issueId]) || !$issueCache[$issueId]->getPublished()) {
continue;
}
// Store the retrieved objects in the result array.
$returner[] = [
'article' => $article,
'publishedSubmission' => $publishedSubmissionCache[$articleId],
'issue' => $issueCache[$issueId],
'journal' => $contextCache[$contextId],
'issueAvailable' => $issueAvailabilityCache[$issueId],
'section' => $sectionCache[$sectionId]
];
}
}
return $returner;
}
/**
* Identify similarity terms for a given submission.
*
* @param int $submissionId
*
* @return null|array An array of string keywords or null
* if some kind of error occurred.
*/
public function getSimilarityTerms($submissionId)
{
// Check whether a search plugin provides terms for a similarity search.
$searchTerms = [];
$result = Hook::call('ArticleSearch::getSimilarityTerms', [$submissionId, &$searchTerms]);
// If no plugin implements the hook then use the subject keywords
// of the submission for a similarity search.
if ($result === false) {
// Retrieve the article.
$article = Repo::submission()->get($submissionId);
if ($article->getData('status') === PKPSubmission::STATUS_PUBLISHED) {
// Retrieve keywords (if any).
$submissionSubjectDao = DAORegistry::getDAO('SubmissionKeywordDAO'); /** @var \PKP\submission\SubmissionKeywordDAO $submissionSubjectDao */
$allSearchTerms = array_filter($submissionSubjectDao->getKeywords($article->getCurrentPublication()->getId(), [Locale::getLocale(), $article->getLocale(), Locale::getPrimaryLocale()]));
foreach ($allSearchTerms as $locale => $localeSearchTerms) {
$searchTerms += $localeSearchTerms;
}
}
}
return $searchTerms;
}
public function getIndexFieldMap()
{
return [
SubmissionSearch::SUBMISSION_SEARCH_AUTHOR => 'authors',
SubmissionSearch::SUBMISSION_SEARCH_TITLE => 'title',
SubmissionSearch::SUBMISSION_SEARCH_ABSTRACT => 'abstract',
SubmissionSearch::SUBMISSION_SEARCH_GALLEY_FILE => 'galleyFullText',
SubmissionSearch::SUBMISSION_SEARCH_DISCIPLINE => 'discipline',
SubmissionSearch::SUBMISSION_SEARCH_SUBJECT => 'subject',
SubmissionSearch::SUBMISSION_SEARCH_KEYWORD => 'keyword',
SubmissionSearch::SUBMISSION_SEARCH_TYPE => 'type',
SubmissionSearch::SUBMISSION_SEARCH_COVERAGE => 'coverage'
];
}
/**
* See SubmissionSearch::getResultSetOrderingOptions()
*/
public function getResultSetOrderingOptions($request)
{
$resultSetOrderingOptions = [
'score' => __('search.results.orderBy.relevance'),
'authors' => __('search.results.orderBy.author'),
'issuePublicationDate' => __('search.results.orderBy.issue'),
'publicationDate' => __('search.results.orderBy.date'),
'title' => __('search.results.orderBy.article')
];
// Only show the "popularity" options if we have a default metric.
$resultSetOrderingOptions['popularityAll'] = __('search.results.orderBy.popularityAll');
$resultSetOrderingOptions['popularityMonth'] = __('search.results.orderBy.popularityMonth');
// Only show the "journal title" option if we have several journals.
$context = $request->getContext();
if (!$context) {
$resultSetOrderingOptions['journalTitle'] = __('search.results.orderBy.journal');
}
// Let plugins mangle the search ordering options.
Hook::call(
'SubmissionSearch::getResultSetOrderingOptions',
[$context, &$resultSetOrderingOptions]
);
return $resultSetOrderingOptions;
}
/**
* See SubmissionSearch::getDefaultOrderDir()
*/
public function getDefaultOrderDir($orderBy)
{
$orderDir = 'asc';
if (in_array($orderBy, ['score', 'publicationDate', 'issuePublicationDate', 'popularityAll', 'popularityMonth'])) {
$orderDir = 'desc';
}
return $orderDir;
}
/**
* See SubmissionSearch::getSearchDao()
*/
protected function getSearchDao()
{
return DAORegistry::getDAO('ArticleSearchDAO');
}
}
if (!PKP_STRICT_MODE) {
class_alias('\APP\search\ArticleSearch', '\ArticleSearch');
}
|