Skip to content

Commit 008a5b6

Browse files
Merge pull request #56 from lepidus/stable-3_3_0
Update thoth schema integration (OMP 3.3.0)
2 parents 48b2e4c + b332840 commit 008a5b6

68 files changed

Lines changed: 2621 additions & 560 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
# Thoth OMP Plugin
44

5-
Work in Progress Integration of OMP and [Thoth](https://thoth.pub/) for communication and synchronization of book data between the two platforms.
5+
[![Current Version](https://img.shields.io/badge/version-v0.3.0.0-blue)](https://github.com/thoth-pub/thoth-omp-plugin/releases)
6+
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-green.svg)](https://www.gnu.org/licenses/gpl-3.0)
7+
[![OMP compatibility](https://img.shields.io/badge/OMP-3.3-blue)](https://pkp.sfu.ca/software/omp/)
8+
9+
Integrates [OMP (Open Monograph Press)](https://pkp.sfu.ca/software/omp/) with [Thoth](https://thoth.pub/), an open metadata management platform for books. This plugin enables the registration and synchronization of book- and chapter-level metadata directly from OMP into Thoth, where it can be disseminated in multiple industry-standard formats including ONIX, MARC, KBART, and Crossref XML.
610

711
## Compatibility
812

@@ -16,9 +20,9 @@ This plugin is compatible with the following PKP applications:
1620

1721
1. **api_key_secret**
1822

19-
The OMP instance must have the `api_key_secret` configuration set up, you may contact your system administrator to do that (see [this post](https://forum.pkp.sfu.ca/t/how-to-generate-a-api-key-secret-code-in-ojs-3/72008)).
23+
The OMP instance must have the `api_key_secret` configuration set up. You may contact your system administrator to do that (see [this post](https://forum.pkp.sfu.ca/t/how-to-generate-a-api-key-secret-code-in-ojs-3/72008)).
2024

21-
This is required to use the API credentials provided, that are stored encrypted in the OMP database.
25+
This is required to store the Thoth personal access token encrypted in the OMP database.
2226

2327
## Installation
2428

@@ -30,52 +34,59 @@ This is required to use the API credentials provided, that are stored encrypted
3034

3135
## Usage
3236

33-
### Guidelines
37+
### Configuration
3438

35-
- Only basic HTML tags are preserved (`<strong>`, `<mark>`, `<em>`, `<i>`, `<u>`, `<sup>`, `<sub>`, `<ul>`, `<ol>` and `<li>`); all others will be removed
36-
- ISBN must be properly formatted (e.g., 978-3-16-148410-0)
37-
- To avoid incorrect assignment of affiliations in Thoth, is required the use of the [ROR plugin](https://github.com/withanage/ror) to fill the affiliations in OMP.
39+
After enabling the plugin, go to the plugin settings and fill in:
3840

39-
### Configuration
41+
- **Personal access token**: A valid Thoth personal access token used to authenticate API requests.
42+
- **Custom Thoth API**: Check this option to use a custom Thoth API instead of the official one.
43+
- **Thoth API URL**: The URL of the custom Thoth API (only required when the custom API option is enabled).
4044

41-
To configure the plugin:
45+
<img src="/docs/images/plugin_settings.png" alt="Plugin settings form with personal access token, custom API and URL fields" width="700">
4246

43-
- **E-mail** and **Password**: Enter the credentials for a Thoth account to connect with the API.
44-
- **Test Environment**: Check this option if you are using a local instance of the Thoth API for testing purposes.
47+
### Registering Monographs
4548

46-
![settings](/images/settings.png)
49+
#### Unpublished Monographs
4750

48-
### Managing Monographs
51+
Register metadata in Thoth during the publishing process by selecting the option to register metadata in the publish modal and choosing an imprint.
4952

50-
- **Unpublished Monographs**: Register metadata in Thoth during the publishing process by selecting the option to register metadata in the publish modal and choosing an imprint.
53+
<img src="/docs/images/register_field.png" alt="Publish modal with Thoth registration option" width="700">
5154

52-
![publish](/images/publish.png)
55+
#### Published Monographs
5356

54-
- **Published Monographs**: Register metadata for published monographs by using the 'Register' button next to the publication status.
57+
Register metadata for already-published monographs by using the 'Register' button next to the publication status.
5558

56-
![button](/images/button.png)
57-
![register](/images/register.png)
59+
<img src="/docs/images/register_button.png" alt="Register button in the publication workflow" width="700">
60+
<img src="/docs/images/register_modal.png" alt="Registration modal with imprint selection" width="700">
5861

5962
### Updating Metadata
6063

61-
To update metadata in Thoth, unpublish the monograph, edit the data, and the changes will be automatically updated in Thoth.
64+
Once a monograph is registered, metadata updates are **automatic**. Unpublish the monograph, edit the data, and the changes will be synchronized with Thoth upon republication.
65+
66+
It is also possible to manually update the metadata in Thoth by clicking the 'Update Metadata' button next to the publication status.
6267

6368
### Accessing Thoth Book Records
6469

65-
After metadata is published, a link to the book on Thoth will appear at the top of the publication.
70+
After metadata is registered, a link to the book on Thoth will appear at the top of the publication workflow.
71+
72+
<img src="/docs/images/view_button.png" alt="View link to the Thoth book record" width="700">
6673

67-
![link](/images/link.png)
74+
### Bulk Registration
6875

69-
### Bulk register
76+
On the Thoth management page, you can submit a selection of titles from OMP into Thoth in bulk.
7077

71-
On the Thoth page, you can bulk submit a selection of titles from OMP into Thoth.
78+
<img src="/docs/images/bulk_register_page.png" alt="Thoth management page with bulk registration" width="700">
79+
80+
### Guidelines
7281

73-
![page](/images/page.png)
82+
- Only basic HTML tags are preserved in text fields: `<strong>`, `<mark>`, `<em>`, `<i>`, `<u>`, `<sup>`, `<sub>`, `<ul>`, `<ol>`, and `<li>`. All other tags will be stripped.
83+
- ISBN must be properly formatted as ISBN-13 (e.g., `978-3-16-148410-0`).
84+
- To avoid incorrect affiliation assignment in Thoth, use the [ROR plugin](https://github.com/withanage/ror) to populate affiliations in OMP.
7485

7586
## OMP-Thoth Mapping
7687

7788
<details>
78-
<summary>Click here to see the data relationship between Thoth and OMP</summary>
89+
<summary>Click here to see the data relationship between OMP and Thoth</summary>
7990

8091
| OMP | | | Thoth | | |
8192
| ----------------- | ------------------ | - | ---------------------- | ------------------- | ----------- |
@@ -132,6 +143,6 @@ Developed by [Lepidus Tecnologia](https://github.com/lepidus).
132143

133144
This plugin is licensed under the GNU General Public License v3.0 - [See the License file.](/LICENSE)
134145

135-
Copyright (c) 2024-2025 Lepidus Tecnologia
146+
Copyright (c) 2024-2026 Lepidus Tecnologia
136147

137-
Copyright (c) 2024-2025 Thoth
148+
Copyright (c) 2024-2026 Thoth Open Metadata

ThothSettingsForm.inc.php

Lines changed: 80 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ class ThothSettingsForm extends Form
2828
private $plugin;
2929

3030
private const SETTINGS = [
31-
'email',
32-
'password',
33-
'testEnvironment',
31+
'token',
32+
'customThothApi',
33+
'customThothApiUrl',
3434
];
3535

3636
public function __construct($plugin, $contextId)
@@ -45,22 +45,58 @@ public function __construct($plugin, $contextId)
4545
$form = $this;
4646
$this->addCheck(new FormValidatorCustom(
4747
$this,
48-
'password',
48+
'customThothApiUrl',
4949
'required',
50-
'plugins.generic.thoth.settings.invalidCredentials',
51-
function ($password) use ($form) {
52-
$email = trim($this->getData('email'));
53-
$testEnvironment = $this->getData('testEnvironment');
50+
'plugins.generic.thoth.settings.customThothApiUrl.required',
51+
function ($customThothApiUrl) {
52+
if (!$this->getData('customThothApi')) {
53+
return true;
54+
}
55+
return !empty(trim($customThothApiUrl));
56+
}
57+
));
5458

59+
$this->addCheck(new FormValidatorCustom(
60+
$this,
61+
'customThothApiUrl',
62+
'optional',
63+
'plugins.generic.thoth.settings.customThothApiUrl.invalid',
64+
function ($customThothApiUrl) {
65+
if (!$this->getData('customThothApi') || !trim($customThothApiUrl)) {
66+
return true;
67+
}
68+
return filter_var(trim($customThothApiUrl), FILTER_VALIDATE_URL) !== false;
69+
}
70+
));
71+
72+
$this->addCheck(new FormValidatorCustom(
73+
$this,
74+
'customThothApiUrl',
75+
'optional',
76+
'plugins.generic.thoth.settings.customThothApiUrl.unreachable',
77+
function ($customThothApiUrl) {
78+
if (!$this->getData('customThothApi')) {
79+
return true;
80+
}
81+
return $this->validateCustomThothApiUrl(trim($customThothApiUrl));
82+
}
83+
));
84+
85+
$this->addCheck(new FormValidatorCustom(
86+
$this,
87+
'token',
88+
'required',
89+
'plugins.generic.thoth.settings.invalidCredentials',
90+
function ($token) use ($form) {
5591
$httpConfig = [];
56-
if ($testEnvironment) {
57-
$httpConfig['base_uri'] = 'http://localhost:8000/';
92+
if ($this->getData('customThothApi') && $this->getData('customThothApiUrl')) {
93+
$httpConfig['base_uri'] = trim($this->getData('customThothApiUrl'));
5894
}
5995

6096
$client = new Client($httpConfig);
6197

6298
try {
63-
$client->login($email, $password);
99+
$client->setToken(trim($token))->me();
64100
} catch (QueryException $e) {
65101
return false;
66102
}
@@ -74,13 +110,20 @@ function ($password) use ($form) {
74110

75111
public function initData()
76112
{
113+
$encryption = new DataEncryption();
114+
77115
foreach (self::SETTINGS as $setting) {
78-
if ($setting == 'password') {
79-
$encryption = new DataEncryption();
80-
$password = $this->plugin->getSetting($this->contextId, $setting);
81-
$this->_data[$setting] = ($encryption->secretConfigExists() && $password) ?
82-
$encryption->decryptString($password) :
83-
null;
116+
if ($setting == 'token') {
117+
$token = $this->plugin->getSetting($this->contextId, $setting);
118+
if ($encryption->secretConfigExists() && $token) {
119+
try {
120+
$this->_data[$setting] = $encryption->decryptString($token);
121+
} catch (Exception $e) {
122+
$this->_data[$setting] = '';
123+
}
124+
} else {
125+
$this->_data[$setting] = null;
126+
}
84127
continue;
85128
}
86129
$this->_data[$setting] = $this->plugin->getSetting($this->contextId, $setting);
@@ -101,21 +144,35 @@ public function fetch($request, $template = null, $display = false)
101144

102145
public function execute(...$functionArgs)
103146
{
104-
$this->encryptPassword();
147+
$this->encryptToken();
105148
foreach (self::SETTINGS as $setting) {
106149
$this->plugin->updateSetting($this->contextId, $setting, trim($this->getData($setting)), 'string');
107150
}
108151
parent::execute(...$functionArgs);
109152
}
110153

111-
private function encryptPassword()
154+
private function encryptToken()
112155
{
113156
$encryption = new DataEncryption();
114-
$password = $this->getData('password');
157+
$token = trim($this->getData('token'));
158+
159+
if (!$encryption->textIsEncrypted($token)) {
160+
$encryptedToken = $encryption->encryptString($token);
161+
$this->setData('token', $encryptedToken);
162+
}
163+
}
164+
165+
private function validateCustomThothApiUrl($customThothApiUrl)
166+
{
167+
if (!$customThothApiUrl) {
168+
return false;
169+
}
115170

116-
if (!$encryption->textIsEncrypted($password)) {
117-
$encryptedPassword = $encryption->encryptString($password);
118-
$this->setData('password', $encryptedPassword);
171+
try {
172+
(new Client(['base_uri' => $customThothApiUrl]))->publisherCount();
173+
return true;
174+
} catch (Exception $e) {
175+
return false;
119176
}
120177
}
121178
}

classes/api/ThothEndpoint.inc.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public function register($slimRequest, $response, $args)
9898
$this->handleNotification($request, $submission, true, $disableNotification);
9999
} catch (QueryException $e) {
100100
$thothBookService->deleteRegisteredEntry();
101-
$this->handleNotification($request, $submission, false, $disableNotification, $e->getMessage());
101+
$this->handleNotification($request, $submission, false, $disableNotification, $e);
102102
$failure['errors'][] = __('plugins.generic.thoth.register.error.log', ['reason' => $e->getMessage()]);
103103
return $response->withStatus(403)->withJson($failure);
104104
}

classes/container/providers/ThothRepositoryProvider.inc.php

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
import('plugins.generic.thoth.classes.factories.ThothLocationFactory');
2727
import('plugins.generic.thoth.classes.factories.ThothPublicationFactory');
2828
import('plugins.generic.thoth.classes.repositories.ThothAccountRepository');
29+
import('plugins.generic.thoth.classes.repositories.ThothAbstractRepository');
2930
import('plugins.generic.thoth.classes.repositories.ThothAffiliationRepository');
31+
import('plugins.generic.thoth.classes.repositories.ThothBiographyRepository');
3032
import('plugins.generic.thoth.classes.repositories.ThothBookRepository');
3133
import('plugins.generic.thoth.classes.repositories.ThothChapterRepository');
3234
import('plugins.generic.thoth.classes.repositories.ThothContributionRepository');
@@ -38,6 +40,7 @@
3840
import('plugins.generic.thoth.classes.repositories.ThothPublicationRepository');
3941
import('plugins.generic.thoth.classes.repositories.ThothReferenceRepository');
4042
import('plugins.generic.thoth.classes.repositories.ThothSubjectRepository');
43+
import('plugins.generic.thoth.classes.repositories.ThothTitleRepository');
4144
import('plugins.generic.thoth.classes.repositories.ThothWorkRelationRepository');
4245
import('plugins.generic.thoth.classes.repositories.ThothWorkRepository');
4346

@@ -50,33 +53,46 @@ public function register($container)
5053
$pluginSettingsDao = & DAORegistry::getDAO('PluginSettingsDAO');
5154
$contextId = Application::get()->getRequest()->getContext()->getId();
5255

53-
$testEnvironment = $pluginSettingsDao->getSetting($contextId, 'ThothPlugin', 'testEnvironment');
54-
$email = $pluginSettingsDao->getSetting($contextId, 'ThothPlugin', 'email');
55-
$password = $pluginSettingsDao->getSetting($contextId, 'ThothPlugin', 'password') ?? '';
56+
$customThothApi = $pluginSettingsDao->getSetting($contextId, 'ThothPlugin', 'customThothApi');
57+
$customThothApiUrl = $pluginSettingsDao->getSetting($contextId, 'ThothPlugin', 'customThothApiUrl');
58+
$token = $pluginSettingsDao->getSetting($contextId, 'ThothPlugin', 'token') ?? '';
59+
$decryptedToken = '';
60+
61+
if ($token) {
62+
try {
63+
$decryptedToken = $encryption->decryptString($token);
64+
} catch (Exception $e) {
65+
$decryptedToken = '';
66+
}
67+
}
5668

5769
return [
58-
'testEnvironment' => $testEnvironment,
59-
'email' => $email,
60-
'password' => $encryption->decryptString($password)
70+
'customThothApi' => $customThothApi,
71+
'customThothApiUrl' => $customThothApiUrl,
72+
'token' => $decryptedToken
6173
];
6274
});
6375

6476
$container->set('client', function ($container) {
6577
$config = $container->get('config');
6678

6779
$httpConfig = [];
68-
if ($config['testEnvironment']) {
69-
$httpConfig['base_uri'] = 'http://localhost:8000/';
80+
if ($config['customThothApi'] && $config['customThothApiUrl']) {
81+
$httpConfig['base_uri'] = trim($config['customThothApiUrl']);
7082
}
7183

7284
$client = new Client($httpConfig);
73-
return $client->login($config['email'], $config['password']);
85+
return $client->setToken($config['token']);
7486
});
7587

7688
$container->set('accountRepository', function ($container) {
7789
return new ThothAccountRepository($container->get('client'));
7890
});
7991

92+
$container->set('abstractRepository', function ($container) {
93+
return new ThothAbstractRepository($container->get('client'));
94+
});
95+
8096
$container->set('affiliationRepository', function ($container) {
8197
return new ThothAffiliationRepository($container->get('client'));
8298
});
@@ -85,6 +101,10 @@ public function register($container)
85101
return new ThothBookRepository($container->get('client'));
86102
});
87103

104+
$container->set('biographyRepository', function ($container) {
105+
return new ThothBiographyRepository($container->get('client'));
106+
});
107+
88108
$container->set('chapterRepository', function ($container) {
89109
return new ThothChapterRepository($container->get('client'));
90110
});
@@ -125,6 +145,10 @@ public function register($container)
125145
return new ThothSubjectRepository($container->get('client'));
126146
});
127147

148+
$container->set('titleRepository', function ($container) {
149+
return new ThothTitleRepository($container->get('client'));
150+
});
151+
128152
$container->set('workRelationRepository', function ($container) {
129153
return new ThothWorkRelationRepository($container->get('client'));
130154
});

0 commit comments

Comments
 (0)