Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds new features #23

Open
wants to merge 47 commits into
base: stable-3_3_0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
1226215
WIP: Get galley annotation number through Hypothesis API
JhonathanLepidus Jul 20, 2022
4270fca
Finishes HypothesisClient class
JhonathanLepidus Jul 21, 2022
f91a402
Prepares the including of custom template in preprint landing page
JhonathanLepidus Jul 22, 2022
62a38d8
Creates the template to add the annotation viewer to landing page
JhonathanLepidus Jul 22, 2022
ea10bc6
Removes left margin of annotation viewer
JhonathanLepidus Jul 26, 2022
c755f8f
Switch annotation viewer addin strategy
JhonathanLepidus Jul 26, 2022
9912be6
Removes commented code
JhonathanLepidus Jul 26, 2022
80d50b4
Brings Hypothesis API call to the template
JhonathanLepidus Jul 27, 2022
f6e5fbd
Adds Hypothesis config template
JhonathanLepidus Aug 2, 2022
9a1eab7
Uses load event for configuring Hypothesis client
JhonathanLepidus Aug 3, 2022
2b64509
Hypothesis sidebar opens when clicking in annotation number
JhonathanLepidus Aug 4, 2022
a502236
Redesigns annotation viewer to use the same link of the galley
JhonathanLepidus Aug 5, 2022
4334c65
Creates HypothesisHandler class
JhonathanLepidus Aug 19, 2022
f15100b
Adds page to list submissions with annotations
JhonathanLepidus Aug 19, 2022
4b05163
Fixes bug occurred when galleys did not have file
JhonathanLepidus Aug 22, 2022
a118d42
Requests Hypothesis API for annotations by submissions chunks
JhonathanLepidus Aug 22, 2022
f16d57d
Fixes problem with submissions groups annotations requests
JhonathanLepidus Aug 30, 2022
dbf5996
Get submission object for submissions with annotations
JhonathanLepidus Aug 30, 2022
6f11bb0
Stores submissions with annotations in cache
JhonathanLepidus Aug 31, 2022
8486eb3
Set annotations cache to updated through scheduled task
JhonathanLepidus Sep 12, 2022
e98980b
Fixes bug when adding annotation number viewer
JhonathanLepidus Sep 12, 2022
09e1351
Creates page for listing of submissions with annotations
JhonathanLepidus Sep 12, 2022
2e81266
Sets locales for annotations page
JhonathanLepidus Sep 12, 2022
be5214a
Fix bug occurred for submissions without publications
JhonathanLepidus Sep 13, 2022
45a2971
Handler returns submissions' annotations
JhonathanLepidus Sep 14, 2022
e50a87f
Sets annotations retrieving from cache
JhonathanLepidus Sep 16, 2022
d8214ac
Set up the display of annotations
richardauzier-afk Sep 19, 2022
55ccaea
Adds styling for annotations
JhonathanLepidus Sep 19, 2022
e73ffd9
Adds Annotation class
JhonathanLepidus Sep 22, 2022
8229782
Retrieves annotation data from API response
JhonathanLepidus Sep 22, 2022
4f17353
Exhibits new annotation info in annotations page
JhonathanLepidus Sep 22, 2022
f7bc301
WIP: Read more for annotation targets
JhonathanLepidus Sep 26, 2022
521a488
Adds read more button for annotation targets
JhonathanLepidus Sep 26, 2022
84fc02e
Adds read more button for annotation content
JhonathanLepidus Sep 26, 2022
95c2247
Adds submission metadata to annotations page
JhonathanLepidus Sep 26, 2022
3486656
Remove search box
lucasDevLepidus Oct 4, 2022
8e32bfa
Adds order by date of last annotation
JhonathanLepidus Feb 3, 2023
a0a8554
Adds ordering by date published
JhonathanLepidus Feb 3, 2023
bd4a865
Adds select to order of submissions in annotations page
JhonathanLepidus Feb 6, 2023
f1f146a
OrderBy is selected according to page's URL
JhonathanLepidus Feb 6, 2023
7e7a1a3
Changes annotations page ordering when one is selected
JhonathanLepidus Feb 7, 2023
27783eb
Keeps selected order through annotations pagination
JhonathanLepidus Feb 7, 2023
58431e3
Refactors code of annotations ordering functions
JhonathanLepidus Feb 14, 2023
ba8d71e
Changes default ordering to order by last annotation
JhonathanLepidus Mar 9, 2023
b7f9328
Escapes annotations content in annotations page
JhonathanLepidus Feb 7, 2024
3c8c56e
Makes plugin fully compatible with OJS and OPS
JhonathanLepidus Mar 22, 2024
b98e0b6
Minor fixes in annotations page
JhonathanLepidus Mar 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 94 additions & 5 deletions HypothesisPlugin.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@ class HypothesisPlugin extends GenericPlugin {
*/
function register($category, $path, $mainContextId = null) {
if (parent::register($category, $path, $mainContextId)) {
HookRegistry::register('ArticleHandler::download',array(&$this, 'callback'));
HookRegistry::register('ArticleHandler::download', array(&$this, 'callback'));
HookRegistry::register('TemplateManager::display', array(&$this, 'callbackTemplateDisplay'));
HookRegistry::register('TemplateManager::display', [$this, 'addAnnotationNumberViewers']);
HookRegistry::register('LoadHandler', array($this, 'addAnnotationsHandler'));
HookRegistry::register('LoadComponentHandler', array($this, 'setupHypothesisHandler'));
HookRegistry::register('AcronPlugin::parseCronTab', [$this, 'addTasksToCrontab']);

$this->addHandlerURLToJavaScript();

return true;
}
return false;
Expand Down Expand Up @@ -55,12 +62,13 @@ function callbackTemplateDisplay($hookName, $args) {
$templateMgr = $args[0];
$template = $args[1];
$plugin = 'plugins-generic-pdfJsViewer';
$submissiontpl = 'submissionGalley.tpl';
$issuetpl = 'issueGalley.tpl';
$submissionGalleyTpl = 'submissionGalley.tpl';
$issueGalleyTpl = 'issueGalley.tpl';

// template path contains the plugin path, and ends with the tpl file
if ( (strpos($template, $plugin) !== false) && ( (strpos($template, ':'.$submissiontpl, -1 - strlen($submissiontpl)) !== false) || (strpos($template, ':'.$issuetpl, -1 - strlen($issuetpl)) !== false))) {
if ( (strpos($template, $plugin) !== false) && ( (strpos($template, ':'.$submissionGalleyTpl, -1 - strlen($submissionGalleyTpl)) !== false) || (strpos($template, ':'.$issueGalleyTpl, -1 - strlen($issueGalleyTpl)) !== false))) {
$templateMgr->registerFilter("output", array($this, 'changePdfjsPath'));
$templateMgr->registerFilter("output", array($this, 'addHypothesisConfig'));
}
return false;
}
Expand All @@ -76,6 +84,83 @@ function changePdfjsPath($output, $templateMgr) {
return $newOutput;
}

/**
* Adds Hypothesis tab configuration so sidebar opens automatically when PDF has annotations
* @param $output string
* @param $templateMgr TemplateManager
* @return $string
*/
public function addHypothesisConfig($output, $templateMgr) {
if (preg_match('/<div[^>]+id="pdfCanvasContainer/', $output, $matches, PREG_OFFSET_CAPTURE)) {
$posMatch = $matches[0][1];
$config = $templateMgr->fetch($this->getTemplateResource('hypothesisConfig.tpl'));

$output = substr_replace($output, $config, $posMatch, 0);
$templateMgr->unregisterFilter('output', array($this, 'addHypothesisConfig'));
}
return $output;
}

public function addAnnotationNumberViewers($hookName, $args) {
$templateMgr = $args[0];
$template = $args[1];
$pagesToInsert = [
'frontend/pages/indexServer.tpl',
'frontend/pages/preprint.tpl',
'frontend/pages/preprints.tpl',
'frontend/pages/sections.tpl',
'frontend/pages/indexJournal.tpl',
'frontend/pages/article.tpl',
'frontend/pages/issue.tpl'
];

if (in_array($template, $pagesToInsert)) {
$request = Application::get()->getRequest();

$jsUrl = $request->getBaseUrl() . '/' . $this->getPluginPath() . '/js/addAnnotationViewers.js';
$styleUrl = $request->getBaseUrl() . '/' . $this->getPluginPath() . '/styles/annotationViewer.css';

$templateMgr->addJavascript('AddAnnotationViewers', $jsUrl, ['contexts' => 'frontend']);
$templateMgr->addStyleSheet('AnnotationViewerStyleSheet', $styleUrl, ['contexts' => 'frontend']);
}

return false;
}

public function addAnnotationsHandler($hookName, $args) {
$page = $args[0];
if ($page == 'annotations') {
$this->import('pages.annotations.AnnotationsHandler');
define('HANDLER_CLASS', 'AnnotationsHandler');
return true;
}
return false;
}

public function setupHypothesisHandler($hookName, $args) {
$component = &$args[0];
if ($component == 'plugins.generic.hypothesis.controllers.HypothesisHandler') {
return true;
}
return false;
}

public function addTasksToCrontab($hookName, $args) {
$taskFilesPath = &$args[0];
$taskFilesPath[] = $this->getPluginPath() . DIRECTORY_SEPARATOR . 'scheduledTasks.xml';
return false;
}

public function addHandlerURLToJavaScript()
{
$request = Application::get()->getRequest();
$templateMgr = TemplateManager::getManager($request);
$handlerUrl = $request->getDispatcher()->url($request, ROUTE_COMPONENT, null, 'plugins.generic.hypothesis.controllers.HypothesisHandler');
$data = ['hypothesisHandlerUrl' => $handlerUrl];

$templateMgr->addJavaScript('HypothesisHandler', 'app = ' . json_encode($data) . ';', ['contexts' => 'frontend', 'inline' => true]);
}

/**
* Get the display name of this plugin
* @return string
Expand All @@ -91,5 +176,9 @@ function getDisplayName() {
function getDescription() {
return __('plugins.generic.hypothesis.description');
}

function getStyleSheet() {
return $this->getPluginPath() . '/styles/annotationViewer.css';
}
}

20 changes: 20 additions & 0 deletions classes/Annotation.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

class Annotation {
public $user;
public $dateCreated;
public $target;
public $content;

public function __construct(string $user, string $dateCreated, string $target, string $content) {
$this->user = $user;
$this->dateCreated = $dateCreated;
$this->target = $target;
$this->content = $content;
}

public static function __set_state($dump) {
$obj = new Annotation($dump['user'], $dump['dateCreated'], $dump['target'], $dump['content']);
return $obj;
}
}
23 changes: 23 additions & 0 deletions classes/HypothesisDAO.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

import('lib.pkp.classes.db.DAO');

use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Support\Collection;

class HypothesisDAO extends DAO {
public function getDatePublished($submissionId): string {
$result = Capsule::table('submissions')
->where('submission_id', $submissionId)
->select('current_publication_id')
->first();
$currentPublicationId = get_object_vars($result)['current_publication_id'];

$result = Capsule::table('publications')
->where('publication_id', $currentPublicationId)
->select('date_published')
->first();

return get_object_vars($result)['date_published'];
}
}
134 changes: 134 additions & 0 deletions classes/HypothesisHelper.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

import('plugins.generic.hypothesis.classes.SubmissionAnnotations');
import('classes.submission.Submission');

class HypothesisHelper {

public function getSubmissionsAnnotations($contextId) {
$submissions = Services::get('submission')->getMany([
'contextId' => $contextId,
'status' => STATUS_PUBLISHED
]);

$groupsRequests = $this->getSubmissionsGroupsRequests($submissions, $contextId);
$submissionsAnnotations = [];
foreach ($groupsRequests as $groupRequest) {
$groupResponse = $this->getRequestAnnotations($groupRequest);
if (!is_null($groupResponse) && $groupResponse['total'] > 0) {
$submissionsAnnotations = array_merge(
$submissionsAnnotations,
$this->groupSubmissionsAnnotations($groupResponse)
);
}
}

return $submissionsAnnotations;
}

private function getSubmissionsGroupsRequests($submissions, $contextId) {
$requests = [];
$requestPrefix = $currentRequest = "https://hypothes.is/api/search?limit=200&group=__world__";
$maxRequestLength = 4094;

foreach ($submissions as $submission) {
$submissionRequestParams = $this->getSubmissionRequestParams($submission, $contextId);

if(!is_null($submissionRequestParams)) {
if(strlen($currentRequest.$submissionRequestParams) < $maxRequestLength) {
$currentRequest .= $submissionRequestParams;
}
else {
$requests[] = $currentRequest;
$currentRequest = $requestPrefix . $submissionRequestParams;
}
}
}

return $requests;
}

private function getSubmissionRequestParams($submission, $contextId) {
$submissionRequestParams = "";
$publication = $submission->getCurrentPublication();

if(is_null($publication))
return null;

$galleys = $publication->getData('galleys');
foreach ($galleys as $galley) {
if ($galley->getFileType() == 'application/pdf') {
$galleyDownloadURL = $this->getGalleyDownloadURL($contextId, $submission, $galley);
if(!is_null($galleyDownloadURL)) {
$submissionRequestParams .= "&uri={$galleyDownloadURL}";
}
}
}

return $submissionRequestParams;
}

private function getRequestAnnotations($requestURL) {
$ch = curl_init($requestURL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
if(!$output || substr($output, 1, 8) != '"total":') return null;

return json_decode($output, true);
}

private function groupSubmissionsAnnotations($groupResponse) {
$submissionsAnnotations = [];

foreach ($groupResponse['rows'] as $annotationResponse) {
$urlBySlash = explode("/", $annotationResponse['links']['incontext']);
$submissionId = (int) $urlBySlash[count($urlBySlash) - 3];

if(!array_key_exists($submissionId, $submissionsAnnotations)) {
$submissionsAnnotations[$submissionId] = new SubmissionAnnotations($submissionId);
}

$annotation = $this->getAnnotation($annotationResponse);
$submissionsAnnotations[$submissionId]->addAnnotation($annotation);
}

return $submissionsAnnotations;
}

private function getAnnotation($annotationResponse): Annotation {
$user = substr($annotationResponse['user'], 5, strlen($annotationResponse['user']) - 17);
$dateCreated = $annotationResponse['created'];
$content = $annotationResponse['text'];

$target = "";
if(isset($annotationResponse['target'][0]['selector'])) {
foreach ($annotationResponse['target'][0]['selector'] as $selector) {
if($selector['type'] == 'TextQuoteSelector') {
$target = $selector['exact'];
break;
}
}
}

return new Annotation($user, $dateCreated, $target, $content);
}

public function getGalleyDownloadURL($contextId, $submission, $galley) {
$request = Application::get()->getRequest();
$indexUrl = $request->getIndexUrl();
$context = Services::get('context')->get($contextId);
$contextPath = $context->getPath();
$submissionType = (Application::getName() == 'ojs2' ? 'article' : 'preprint');

$submissionFile = $galley->getFile();
if(is_null($submissionFile))
return null;

$submissionBestId = $submission->getBestId();
$galleyBestId = $galley->getBestGalleyId();
$fileId = $submissionFile->getId();

return $indexUrl . "/$contextPath/$submissionType/download/$submissionBestId/$galleyBestId/$fileId";
}

}
24 changes: 24 additions & 0 deletions classes/SubmissionAnnotations.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

import('plugins.generic.hypothesis.classes.Annotation');

class SubmissionAnnotations {
public $submissionId;
public $submission;
public $annotations;

public function __construct(int $submissionId) {
$this->submissionId = $submissionId;
$this->annotations = [];
}

public static function __set_state($dump) {
$obj = new SubmissionAnnotations($dump['submissionId']);
$obj->annotations = $dump['annotations'];
return $obj;
}

public function addAnnotation(Annotation $annotation) {
$this->annotations[] = $annotation;
}
}
33 changes: 33 additions & 0 deletions classes/tasks/UpdateAnnotationsCache.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

import('lib.pkp.classes.scheduledTask.ScheduledTask');

class UpdateAnnotationsCache extends ScheduledTask {

public function executeActions() {
$contextIds = Services::get('context')->getIds([
'isEnabled' => true,
]);

foreach ($contextIds as $contextId) {
$cacheManager = CacheManager::getManager();
$cache = $cacheManager->getFileCache(
$contextId,
'submissions_annotations',
[$this, 'cacheDismiss']
);

$cache->flush();
$hypothesisHandler = new HypothesisHandler();
$cache->setEntireCache($hypothesisHandler->getSubmissionsAnnotations($contextId));
}

return true;
}

function cacheDismiss() {
return null;
}
}


27 changes: 27 additions & 0 deletions controllers/HypothesisHandler.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

import('classes.handler.Handler');

class HypothesisHandler extends Handler {
public function getAnnotationViewerData($args, $request) {
$galleyUrl = $args['galleyUrl'];
$explodedUrl = explode('/', $galleyUrl);
$galleyId = (int) end($explodedUrl);

$galleyDao = DAORegistry::getDAO('ArticleGalleyDAO');
$galley = $galleyDao->getById($galleyId);
if(!$galley) {
return json_encode(null);
}

$fileId = $galley->getFile()->getId();
$galleyDownloadUrl = str_replace('view', 'download', $galleyUrl);
$galleyDownloadUrl .= "/$fileId";

return json_encode([
'downloadUrl' => $galleyDownloadUrl,
'annotationMsg' => __('plugins.generic.hypothesis.annotation'),
'annotationsMsg' => __('plugins.generic.hypothesis.annotations')
]);
}
}
Loading