Open Journal Systems  3.3.0
ArticleHandler.inc.php
1 <?php
2 
17 import('classes.handler.Handler');
18 
19 use \Firebase\JWT\JWT;
20 
21 class ArticleHandler extends Handler {
23  var $context;
24 
26  var $issue;
27 
29  var $article;
30 
33 
36 
38  var $galley;
39 
41  var $fileId;
42 
43 
47  function authorize($request, &$args, $roleAssignments) {
48  // Permit the use of the Authorization header and an API key for access to unpublished/subscription content
49  if ($header = array_search('Authorization', array_flip(getallheaders()))) {
50  list($bearer, $jwt) = explode(' ', $header);
51  if (strcasecmp($bearer, 'Bearer') == 0) {
52  $apiToken = json_decode(JWT::decode($jwt, Config::getVar('security', 'api_key_secret', ''), array('HS256')));
53  $this->setApiToken($apiToken);
54  }
55  }
56 
57  import('lib.pkp.classes.security.authorization.ContextRequiredPolicy');
58  $this->addPolicy(new ContextRequiredPolicy($request));
59 
60  import('classes.security.authorization.OjsJournalMustPublishPolicy');
61  $this->addPolicy(new OjsJournalMustPublishPolicy($request));
62 
63  return parent::authorize($request, $args, $roleAssignments);
64  }
65 
70  function initialize($request, $args = array()) {
71  $urlPath = empty($args) ? 0 : array_shift($args);
72 
73  // Get the submission that matches the requested urlPath
74  $submission = Services::get('submission')->getByUrlPath($urlPath, $request->getContext()->getId());
75 
76  if (!$submission && ctype_digit((string) $urlPath)) {
77  $submission = Services::get('submission')->get($urlPath);
78  if (!$submission || $request->getContext()->getId() != $submission->getContextId()) $submission = null;
79  }
80 
81  if (!$submission || $submission->getData('status') !== STATUS_PUBLISHED) {
82  $request->getDispatcher()->handle404();
83  }
84 
85  // If the urlPath does not match the urlPath of the current
86  // publication, redirect to the current URL
87  $currentUrlPath = $submission->getBestId();
88  if ($currentUrlPath && $currentUrlPath != $urlPath) {
89  $newArgs = array_merge([$currentUrlPath], $args);
90  $request->redirect(null, $request->getRequestedPage(), $request->getRequestedOp(), $newArgs);
91  }
92 
93  $this->article = $submission;
94 
95  // Get the requested publication or if none requested get the current publication
96  $subPath = empty($args) ? 0 : array_shift($args);
97  if ($subPath === 'version') {
98  $publicationId = (int) array_shift($args);
99  $galleyId = empty($args) ? 0 : array_shift($args);
100  foreach ((array) $this->article->getData('publications') as $publication) {
101  if ($publication->getId() === $publicationId) {
102  $this->publication = $publication;
103  }
104  }
105  if (!$this->publication) {
106  $request->getDispatcher()->handle404();
107  }
108  } else {
109  $this->publication = $this->article->getCurrentPublication();
110  $galleyId = $subPath;
111  }
112 
113  if ($this->publication->getData('status') !== STATUS_PUBLISHED) {
114  $request->getDispatcher()->handle404();
115  }
116 
117  if ($galleyId && in_array($request->getRequestedOp(), ['view', 'download'])) {
118  $galleys = (array) $this->publication->getData('galleys');
119  foreach ($galleys as $galley) {
120  if ($galley->getBestGalleyId() == $galleyId) {
121  $this->galley = $galley;
122  break;
123  }
124  }
125  // Redirect to the most recent version of the submission if the request
126  // points to an outdated galley but doesn't use the specific versioned
127  // URL. This can happen when a galley's urlPath is changed between versions.
128  if (!$this->galley) {
129  $publications = $submission->getPublishedPublications();
130  foreach ($publications as $publication) {
131  foreach ((array) $publication->getData('galleys') as $galley) {
132  if ($galley->getBestGalleyId() == $galleyId) {
133  $request->redirect(null, $request->getRequestedPage(), $request->getRequestedOp(), [$submission->getBestId()]);
134  }
135  }
136  }
137  $request->getDispatcher()->handle404();
138  }
139 
140  // Store the file id if it exists
141  if (!empty($args)) {
142  $this->fileId = array_shift($args);
143  }
144  }
145 
146  if ($this->publication->getData('issueId')) {
147  $issueDao = DAORegistry::getDAO('IssueDAO'); /* @var $issueDao IssueDAO */
148  $this->issue = $issueDao->getById($this->publication->getData('issueId'), $submission->getData('contextId'), true);
149  }
150  }
151 
157  function view($args, $request) {
158  $context = $request->getContext();
159  $user = $request->getUser();
163  $templateMgr = TemplateManager::getManager($request);
164  $templateMgr->assign(array(
165  'issue' => $issue,
166  'article' => $article,
167  'publication' => $publication,
168  'firstPublication' => reset($article->getData('publications')),
169  'currentPublication' => $article->getCurrentPublication(),
170  'galley' => $this->galley,
171  'fileId' => $this->fileId,
172  ));
173  $this->setupTemplate($request);
174 
175  $sectionDao = DAORegistry::getDAO('SectionDAO'); /* @var $sectionDao SectionDAO */
176  $templateMgr->assign([
177  'ccLicenseBadge' => Application::get()->getCCLicenseBadge($publication->getData('licenseUrl')),
178  'publication' => $publication,
179  'section' => $sectionDao->getById($publication->getData('sectionId')),
180  ]);
181 
182  if ($this->galley && !$this->userCanViewGalley($request, $article->getId(), $this->galley->getId())) {
183  fatalError('Cannot view galley.');
184  }
185 
186  $categoryDao = DAORegistry::getDAO('CategoryDAO'); /* @var $categoryDao CategoryDAO */
187  $templateMgr->assign([
188  'categories' => $categoryDao->getByPublicationId($publication->getId())->toArray()
189  ]);
190 
191  // Get galleys sorted into primary and supplementary groups
192  $galleys = $publication->getData('galleys');
193  $primaryGalleys = array();
194  $supplementaryGalleys = array();
195  if ($galleys) {
196  $genreDao = DAORegistry::getDAO('GenreDAO'); /* @var $genreDao GenreDAO */
197  $primaryGenres = $genreDao->getPrimaryByContextId($context->getId())->toArray();
198  $primaryGenreIds = array_map(function($genre) {
199  return $genre->getId();
200  }, $primaryGenres);
201  $supplementaryGenres = $genreDao->getBySupplementaryAndContextId(true, $context->getId())->toArray();
202  $supplementaryGenreIds = array_map(function($genre) {
203  return $genre->getId();
204  }, $supplementaryGenres);
205 
206  foreach ($galleys as $galley) {
207  $remoteUrl = $galley->getRemoteURL();
208  $file = $galley->getFile();
209  if (!$remoteUrl && !$file) {
210  continue;
211  }
212  if ($remoteUrl || in_array($file->getGenreId(), $primaryGenreIds)) {
213  $primaryGalleys[] = $galley;
214  } elseif (in_array($file->getGenreId(), $supplementaryGenreIds)) {
215  $supplementaryGalleys[] = $galley;
216  }
217  }
218  }
219  $templateMgr->assign(array(
220  'primaryGalleys' => $primaryGalleys,
221  'supplementaryGalleys' => $supplementaryGalleys,
222  ));
223 
224  // Citations
225  if ($publication->getData('citationsRaw')) {
226  $citationDao = DAORegistry::getDAO('CitationDAO'); /* @var $citationDao CitationDAO */
227  $parsedCitations = $citationDao->getByPublicationId($publication->getId());
228  $templateMgr->assign([
229  'parsedCitations' => $parsedCitations->toArray(),
230  ]);
231  }
232 
233  // Assign deprecated values to the template manager for
234  // compatibility with older themes
235  $templateMgr->assign([
236  'licenseTerms' => $context->getLocalizedData('licenseTerms'),
237  'licenseUrl' => $publication->getData('licenseUrl'),
238  'copyrightHolder' => $publication->getData('copyrightHolder'),
239  'copyrightYear' => $publication->getData('copyrightYear'),
240  'pubIdPlugins' => PluginRegistry::loadCategory('pubIds', true),
241  'keywords' => $publication->getData('keywords'),
242  ]);
243 
244  // Fetch and assign the galley to the template
245  if ($this->galley && $this->galley->getRemoteURL()) $request->redirectUrl($this->galley->getRemoteURL());
246 
247  if (empty($this->galley)) {
248  // No galley: Prepare the article landing page.
249 
250  // Ask robots not to index outdated versions and point to the canonical url for the latest version
251  if ($publication->getId() !== $article->getCurrentPublication()->getId()) {
252  $templateMgr->addHeader('noindex', '<meta name="robots" content="noindex">');
253  $url = $request->getDispatcher()->url($request, ROUTE_PAGE, null, 'article', 'view', $article->getBestId());
254  $templateMgr->addHeader('canonical', '<link rel="canonical" href="' . $url . '">');
255  }
256 
257  // Get the subscription status if displaying the abstract;
258  // if access is open, we can display links to the full text.
259  import('classes.issue.IssueAction');
260 
261  // The issue may not exist, if this is an editorial user
262  // and scheduling hasn't been completed yet for the article.
263  $issueAction = new IssueAction();
264  $subscriptionRequired = false;
265  if ($issue) {
266  $subscriptionRequired = $issueAction->subscriptionRequired($issue, $context);
267  }
268 
269  $subscribedUser = $issueAction->subscribedUser($user, $context, isset($issue) ? $issue->getId() : null, isset($article) ? $article->getId() : null);
270  $subscribedDomain = $issueAction->subscribedDomain($request, $context, isset($issue) ? $issue->getId() : null, isset($article) ? $article->getId() : null);
271 
272  $completedPaymentDao = DAORegistry::getDAO('OJSCompletedPaymentDAO'); /* @var $completedPaymentDao OJSCompletedPaymentDAO */
273  $templateMgr->assign('hasAccess',
274  !$subscriptionRequired ||
275  $publication->getData('accessStatus') == ARTICLE_ACCESS_OPEN ||
276  $subscribedUser || $subscribedDomain ||
277  ($user && $issue && $completedPaymentDao->hasPaidPurchaseIssue($user->getId(), $issue->getId())) ||
278  ($user && $completedPaymentDao->hasPaidPurchaseArticle($user->getId(), $article->getId()))
279  );
280 
281  $paymentManager = Application::get()->getPaymentManager($context);
282  if ( $paymentManager->onlyPdfEnabled() ) {
283  $templateMgr->assign('restrictOnlyPdf', true);
284  }
285  if ( $paymentManager->purchaseArticleEnabled() ) {
286  $templateMgr->assign('purchaseArticleEnabled', true);
287  }
288 
289  if (!HookRegistry::call('ArticleHandler::view', array(&$request, &$issue, &$article, $publication))) {
290  return $templateMgr->display('frontend/pages/article.tpl');
291  }
292  } else {
293 
294  // Ask robots not to index outdated versions
295  if ($publication->getId() !== $article->getCurrentPublication()->getId()) {
296  $templateMgr->addHeader('noindex', '<meta name="robots" content="noindex">');
297  }
298 
299  // Galley: Prepare the galley file download.
300  if (!HookRegistry::call('ArticleHandler::view::galley', array(&$request, &$issue, &$this->galley, &$article, $publication))) {
301  if ($this->publication->getId() !== $this->article->getCurrentPublication()->getId()) {
302  $redirectPath = [
303  $article->getBestId(),
304  'version',
305  $publication->getId(),
306  $this->galley->getBestGalleyId()
307  ];
308  } else {
309  $redirectPath = [
310  $article->getBestId(),
311  $this->galley->getBestGalleyId()
312  ];
313  }
314  $request->redirect(null, null, 'download', $redirectPath);
315  }
316  }
317  }
318 
325  function viewFile($args, $request) {
326  $articleId = isset($args[0]) ? $args[0] : 0;
327  $galleyId = isset($args[1]) ? $args[1] : 0;
328  $fileId = isset($args[2]) ? (int) $args[2] : 0;
329  header('HTTP/1.1 301 Moved Permanently');
330  $request->redirect(null, null, 'download', array($articleId, $galleyId, $fileId));
331  }
332 
339  function downloadSuppFile($args, $request) {
340  $articleId = isset($args[0]) ? $args[0] : 0;
341  $article = Services::get('submission')->get($articleId);
342  if (!$article) {
343  $dispatcher = $request->getDispatcher();
344  $dispatcher->handle404();
345  }
346  $suppId = isset($args[1]) ? $args[1] : 0;
347  $submissionFileDao = DAORegistry::getDAO('SubmissionFileDAO'); /* @var $submissionFileDao SubmissionFileDAO */
348  $submissionFiles = $submissionFileDao->getBySubmissionId($articleId);
349  foreach ($submissionFiles as $submissionFile) {
350  if ($submissionFile->getData('old-supp-id') == $suppId) {
351  $articleGalleyDao = DAORegistry::getDAO('ArticleGalleyDAO'); /* @var $articleGalleyDao ArticleGalleyDAO */
352  $articleGalleys = $articleGalleyDao->getByPublicationId($article->getCurrentPublication()->getId());
353  while ($articleGalley = $articleGalleys->next()) {
354  $galleyFile = $articleGalley->getFile();
355  if ($galleyFile && $galleyFile->getFileId() == $submissionFile->getFileId()) {
356  header('HTTP/1.1 301 Moved Permanently');
357  $request->redirect(null, null, 'download', array($articleId, $articleGalley->getId(), $submissionFile->getFileId()));
358  }
359  }
360  }
361  }
362  $dispatcher = $request->getDispatcher();
363  $dispatcher->handle404();
364  }
365 
371  function download($args, $request) {
372 
373  if (!isset($this->galley)) $request->getDispatcher()->handle404();
374  if ($this->galley->getRemoteURL()) $request->redirectUrl($this->galley->getRemoteURL());
375  else if ($this->userCanViewGalley($request, $this->article->getId(), $this->galley->getId())) {
376  if (!$this->fileId) {
377  $submissionFile = $this->galley->getFile();
378  if ($submissionFile) {
379  $this->fileId = $submissionFile->getFileId();
380  // The file manager expects the real article id. Extract it from the submission file.
381  }
382  }
383 
384  // If no file ID could be determined, treat it as a 404.
385  if (!$this->fileId) $request->getDispatcher()->handle404();
386 
387  // If the file ID is not the galley's file ID, ensure it is a dependent file, or else 404.
388  if ($this->fileId != $this->galley->getFileId()) {
389  $submissionFileDao = DAORegistry::getDAO('SubmissionFileDAO'); /* @var $submissionFileDao SubmissionFileDAO */
390  $dependentFileIds = array_map(
391  function($f) {return $f->getFileId();},
392  $submissionFileDao->getLatestRevisionsByAssocId(ASSOC_TYPE_SUBMISSION_FILE, $this->galley->getFileId(), $this->article->getId(), SUBMISSION_FILE_DEPENDENT)
393  );
394  if (!in_array($this->fileId, $dependentFileIds)) $request->getDispatcher()->handle404();
395  }
396 
397  if (!HookRegistry::call('ArticleHandler::download', array($this->article, &$this->galley, &$this->fileId))) {
398  import('lib.pkp.classes.file.SubmissionFileManager');
399  $submissionFileManager = new SubmissionFileManager($this->article->getContextId(), $this->article->getId());
400  $submissionFileManager->downloadById($this->fileId, null, $request->getUserVar('inline')?true:false);
401  }
402  } else {
403  header('HTTP/1.0 403 Forbidden');
404  echo '403 Forbidden<br>';
405  }
406  }
407 
414  function userCanViewGalley($request, $articleId, $galleyId = null) {
415 
416  import('classes.issue.IssueAction');
417  $issueAction = new IssueAction();
418 
419  $context = $request->getContext();
420  $submission = $this->article;
422  $contextId = $context->getId();
423  $user = $request->getUser();
424  $userId = $user?$user->getId():0;
425 
426  // If this is an editorial user who can view unpublished/unscheduled
427  // articles, bypass further validation. Likewise for its author.
428  if ($submission && $issueAction->allowedPrePublicationAccess($context, $submission, $user)) {
429  return true;
430  }
431 
432  // Make sure the reader has rights to view the article/issue.
433  if ($issue && $issue->getPublished() && $submission->getStatus() == STATUS_PUBLISHED) {
434  $subscriptionRequired = $issueAction->subscriptionRequired($issue, $context);
435  $isSubscribedDomain = $issueAction->subscribedDomain($request, $context, $issue->getId(), $submission->getId());
436 
437  // Check if login is required for viewing.
438  if (!$isSubscribedDomain && !Validation::isLoggedIn() && $context->getData('restrictArticleAccess') && isset($galleyId) && $galleyId) {
440  }
441 
442  // bypass all validation if subscription based on domain or ip is valid
443  // or if the user is just requesting the abstract
444  if ( (!$isSubscribedDomain && $subscriptionRequired) && (isset($galleyId) && $galleyId) ) {
445 
446  // Subscription Access
447  $subscribedUser = $issueAction->subscribedUser($user, $context, $issue->getId(), $submission->getId());
448 
449  import('classes.payment.ojs.OJSPaymentManager');
450  $paymentManager = Application::get()->getPaymentManager($context);
451 
452  $purchasedIssue = false;
453  if (!$subscribedUser && $paymentManager->purchaseIssueEnabled()) {
454  $completedPaymentDao = DAORegistry::getDAO('OJSCompletedPaymentDAO'); /* @var $completedPaymentDao OJSCompletedPaymentDAO */
455  $purchasedIssue = $completedPaymentDao->hasPaidPurchaseIssue($userId, $issue->getId());
456  }
457 
458  if (!(!$subscriptionRequired || $submission->getCurrentPublication()->getData('accessStatus') == ARTICLE_ACCESS_OPEN || $subscribedUser || $purchasedIssue)) {
459 
460  if ( $paymentManager->purchaseArticleEnabled() || $paymentManager->membershipEnabled() ) {
461  /* if only pdf files are being restricted, then approve all non-pdf galleys
462  * and continue checking if it is a pdf galley */
463  if ( $paymentManager->onlyPdfEnabled() ) {
464 
465  if ($this->galley && !$this->galley->isPdfGalley() ) {
466  $this->issue = $issue;
467  $this->article = $submission;
468  return true;
469  }
470  }
471 
472  if (!Validation::isLoggedIn()) {
473  Validation::redirectLogin('payment.loginRequired.forArticle');
474  }
475 
476  /* if the article has been paid for then forget about everything else
477  * and just let them access the article */
478  $completedPaymentDao = DAORegistry::getDAO('OJSCompletedPaymentDAO'); /* @var $completedPaymentDao OJSCompletedPaymentDAO */
479  $dateEndMembership = $user->getSetting('dateEndMembership', 0);
480  if ($completedPaymentDao->hasPaidPurchaseArticle($userId, $submission->getId())
481  || (!is_null($dateEndMembership) && $dateEndMembership > time())) {
482  $this->issue = $issue;
483  $this->article = $submission;
484  return true;
485  } elseif ($paymentManager->purchaseArticleEnabled()) {
486  $queuedPayment = $paymentManager->createQueuedPayment($request, PAYMENT_TYPE_PURCHASE_ARTICLE, $user->getId(), $submission->getId(), $context->getData('purchaseArticleFee'));
487  $paymentManager->queuePayment($queuedPayment);
488 
489  $paymentForm = $paymentManager->getPaymentForm($queuedPayment);
490  $paymentForm->display($request);
491  exit;
492  }
493  }
494 
495  if (!isset($galleyId) || $galleyId) {
496  if (!Validation::isLoggedIn()) {
497  Validation::redirectLogin('reader.subscriptionRequiredLoginText');
498  }
499  $request->redirect(null, 'about', 'subscriptions');
500  }
501  }
502  }
503  } else {
504  $request->redirect(null, 'search');
505  }
506  return true;
507  }
508 
513  function setupTemplate($request) {
514  parent::setupTemplate($request);
515  AppLocale::requireComponents(LOCALE_COMPONENT_PKP_READER, LOCALE_COMPONENT_PKP_SUBMISSION);
516  }
517 }
ArticleHandler\userCanViewGalley
userCanViewGalley($request, $articleId, $galleyId=null)
Definition: ArticleHandler.inc.php:414
SubmissionFileManager
Helper class for database-backed submission file management tasks.
Definition: SubmissionFileManager.inc.php:30
AppLocale\requireComponents
static requireComponents()
Definition: env1/MockAppLocale.inc.php:56
ArticleHandler\$context
$context
Definition: ArticleHandler.inc.php:23
ArticleHandler\$issue
$issue
Definition: ArticleHandler.inc.php:26
PKPHandler\setApiToken
setApiToken($apiToken)
Definition: PKPHandler.inc.php:568
Validation\redirectLogin
static redirectLogin($message=null)
Definition: Validation.inc.php:168
ArticleHandler
Handle requests for article functions.
Definition: ArticleHandler.inc.php:21
Validation\isLoggedIn
static isLoggedIn()
Definition: Validation.inc.php:376
DAORegistry\getDAO
static & getDAO($name, $dbconn=null)
Definition: DAORegistry.inc.php:57
ArticleHandler\$galley
$galley
Definition: ArticleHandler.inc.php:38
ArticleHandler\view
view($args, $request)
Definition: ArticleHandler.inc.php:157
ArticleHandler\downloadSuppFile
downloadSuppFile($args, $request)
Definition: ArticleHandler.inc.php:339
PluginRegistry\loadCategory
static loadCategory($category, $enabledOnly=false, $mainContextId=null)
Definition: PluginRegistry.inc.php:103
IssueAction
IssueAction class.
Definition: IssueAction.inc.php:17
ArticleHandler\$publication
$publication
Definition: ArticleHandler.inc.php:35
ArticleHandler\initialize
initialize($request, $args=array())
Definition: ArticleHandler.inc.php:70
Config\getVar
static getVar($section, $key, $default=null)
Definition: Config.inc.php:35
ArticleHandler\authorize
authorize($request, &$args, $roleAssignments)
Definition: ArticleHandler.inc.php:47
PKPTemplateManager\getManager
static & getManager($request=null)
Definition: PKPTemplateManager.inc.php:1239
ArticleHandler\viewFile
viewFile($args, $request)
Definition: ArticleHandler.inc.php:325
ArticleHandler\download
download($args, $request)
Definition: ArticleHandler.inc.php:371
ArticleHandler\setupTemplate
setupTemplate($request)
Definition: ArticleHandler.inc.php:513
OjsJournalMustPublishPolicy
Access policy to limit access to journals that do not publish online.
Definition: OjsJournalMustPublishPolicy.inc.php:18
ArticleHandler\$fileId
$fileId
Definition: ArticleHandler.inc.php:41
ArticleHandler\$article
$article
Definition: ArticleHandler.inc.php:29
PKPApplication\get
static get()
Definition: PKPApplication.inc.php:235
PKPHandler\addPolicy
addPolicy($authorizationPolicy, $addToTop=false)
Definition: PKPHandler.inc.php:157
fatalError
if(!function_exists('import')) fatalError($reason)
Definition: functions.inc.php:32
HookRegistry\call
static call($hookName, $args=null)
Definition: HookRegistry.inc.php:86
Handler
Base request handler application class.
Definition: Handler.inc.php:18
ContextRequiredPolicy
Policy to deny access if a context cannot be found in the request.
Definition: ContextRequiredPolicy.inc.php:17
ArticleHandler\$categories
$categories
Definition: ArticleHandler.inc.php:32
PKPServices\get
static get($service)
Definition: PKPServices.inc.php:49