Skip to content

Commit cacd441

Browse files
committed
⚡️Adds OrcidPublicationsRepository and PublicationsHelper
Moved the Orcid import process from the Profile model to a new OrcidPublicationsRepository class. This class implements an interface and utilizes the new Publications helper to handle publication formatting methods to return author names in the APA citation format but scalable, to allow the addition of new formatting options.
1 parent 61f70fd commit cacd441

File tree

10 files changed

+624
-67
lines changed

10 files changed

+624
-67
lines changed

.phpactor.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "/Applications/Tinkerwell.app/Contents/Resources/phpactor/phpactor.schema.json",
3+
"language_server_psalm.enabled": false
4+
}

app/Helpers/Publication.php

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<?php
2+
3+
namespace App\Helpers;
4+
5+
use Illuminate\Support\Arr;
6+
7+
class Publication
8+
{
9+
/** @var array Default citation formats (keyed in order) */
10+
const CITATION_FORMAT_AUTHOR_NAMES = [
11+
'APA' => ['last_name', 'comma', 'space', 'first_initial', 'space', 'middle_initial'],
12+
//'MLA' => ['last_name', 'comma', 'space', 'first_name'], // Example ONLY, for testing purposes
13+
//'Chicago' => ['first_name', 'space', 'last_name'], // Example ONLY, for testing purposes
14+
];
15+
16+
/** @var array */
17+
const REGEX_PATTERNS = [
18+
'APA' => "/^[A-Za-z][\s\w\p{L}\p{M}áéíóúüññÑ'-]+,\s[A-Z][\.\s\b][\s]?[A-Z]?[\.\s\b]?$/",
19+
'last_name_initials' => "/^[A-Za-z][\s\w\p{L}\p{M}áéíóúüññÑ'-]+,?\s[A-Z]{1,2}\b$/",
20+
'first_name_last_name' => "/^[A-Za-z][\s\w\p{L}\p{M}áéíóúüññÑ'-]+\s[A-Za-z][\s\w\p{L}\p{M}áéíóúüññÑ'-]+$/",
21+
// 'MLA' => "/^[\p{L}ñÑ'., -]+, [\p{Lu}ñÑ]\. ?[\p{Lu}ñÑ]?\.?$/", // Example ONLY, for testing purposes
22+
// 'Chicago' => "/^[\p{L}ñÑ'., -]+, [\p{Lu}ñÑ]\. ?[\p{Lu}ñÑ]?\.?$/", // Example ONLY, for testing purposes
23+
];
24+
25+
/** @var array */
26+
const SEPARATORS = [
27+
'comma' => ',',
28+
'ellipsis' => '...',
29+
'ampersand' => '&',
30+
'space' => ' ',
31+
'period' => '.',
32+
];
33+
34+
public static function citationFormats(): array
35+
{
36+
return static::CITATION_FORMAT_AUTHOR_NAMES;
37+
}
38+
39+
public static function citationFormatRegex(): array
40+
{
41+
return static::REGEX_PATTERNS;
42+
}
43+
44+
public static function matchesRegexPattern($citation_format, $formatted_author_name) : int
45+
{
46+
return preg_match(static::citationFormatRegex()[$citation_format], $formatted_author_name);
47+
}
48+
49+
public static function firstName(array $author_name_array, $pattern = null) : string
50+
{
51+
if (!is_null($pattern) && $pattern === 'last_name_initials') {
52+
return $author_name_array[1];
53+
}
54+
return $author_name_array[0];
55+
}
56+
57+
public static function lastName(array $author_name_array, $pattern = null) : string
58+
{
59+
if (!is_null($pattern) && $pattern === 'last_name_initials') {
60+
return $author_name_array[0];
61+
}
62+
63+
if (count($author_name_array) == 3) {
64+
return $author_name_array[2];
65+
}
66+
return Arr::last($author_name_array);
67+
}
68+
69+
public static function middleName(array $author_name_array, $pattern = null) : string
70+
{
71+
if (!is_null($pattern) && $pattern === 'last_name_initials') {
72+
return strlen($author_name_array[1]) == 2 ? $author_name_array[1][1] : '';
73+
}
74+
75+
if (count($author_name_array) == 3) {
76+
return $author_name_array[1];
77+
}
78+
return '';
79+
}
80+
81+
public static function initial(string $name) : string
82+
{
83+
return strlen($name) > 0 ? "{$name[0]}." : '';
84+
}
85+
86+
/**
87+
* Return a string with the authors names in APA format
88+
* @param array $authors
89+
* @return string
90+
*/
91+
public static function formatAuthorsApa(array $authors) : string
92+
{
93+
$authors = static::formatAuthorsNames($authors);
94+
95+
$string_authors_names = "";
96+
$greater_than_20 = false;
97+
$authors_count = count($authors);
98+
99+
if ($authors_count > 1) {
100+
$last = $authors[$authors_count - 1];
101+
102+
if ($authors_count >= 20) {
103+
$greater_than_20 = true;
104+
array_splice($authors, 20);
105+
}
106+
else {
107+
array_splice($authors, $authors_count - 1);
108+
}
109+
110+
foreach ($authors as $key => $author) {
111+
$string_authors_names = "{$string_authors_names} {$author['APA']}";
112+
113+
if ($key < count($authors) - 1) {
114+
$string_authors_names = $string_authors_names . static::SEPARATORS['comma'] . static::SEPARATORS['space'];
115+
}
116+
else {
117+
if ($greater_than_20) {
118+
$string_authors_names = $string_authors_names . static::SEPARATORS['space'] . static::SEPARATORS['ellipsis'] . static::SEPARATORS['space'];
119+
}
120+
else {
121+
$string_authors_names = $string_authors_names . static::SEPARATORS['space'] . static::SEPARATORS['ampersand'] . static::SEPARATORS['space'];
122+
}
123+
$string_authors_names = "{$string_authors_names} {$last['APA']}";
124+
}
125+
}
126+
}
127+
else {
128+
$string_authors_names = $authors[0]['APA'];
129+
}
130+
131+
return $string_authors_names;
132+
}
133+
134+
/**
135+
* Return a string with the authors names in MLA format
136+
* @param array $authors
137+
*/
138+
public static function formatAuthorsMla(array $authors)
139+
{
140+
141+
}
142+
143+
/**
144+
* Return a string with the authors names in Chicago format
145+
* @param array $authors
146+
*/
147+
public static function formatAuthorsChicago(array $authors)
148+
{
149+
150+
}
151+
152+
/**
153+
* Receive an array of author names, assuming each name is either already formatted or in the form of First Name Middle initial. Last Name
154+
* Return an array formatted author name for each citation format
155+
*
156+
* @param $author_names
157+
* @return array
158+
*/
159+
public static function formatAuthorsNames(array $author_names): array
160+
{
161+
/** @var array<array> */
162+
$formatted_author_names = [];
163+
164+
foreach ($author_names as $author_name) {
165+
$raw_author_name = trim($author_name);
166+
167+
foreach (array_keys(static::citationFormats()) as $key => $citation_format) {
168+
//If matches given citation format pattern
169+
if (static::matchesRegexPattern($citation_format, $raw_author_name)) {
170+
$formatted_author_name[$citation_format] = ucwords($raw_author_name);
171+
} //If matches last name first initial middle initial pattern
172+
elseif (static::matchesRegexPattern('last_name_initials', $raw_author_name)) {
173+
$formatted_author_name[$citation_format] = static::formatAuthorName($citation_format, $author_name, 'last_name_initials');
174+
}
175+
else { //If matches any other pattern, it will use the first name last name pattern by default to format the name
176+
$formatted_author_name[$citation_format] = static::formatAuthorName($citation_format, $author_name);
177+
178+
}
179+
}
180+
181+
$formatted_author_names[] = $formatted_author_name;
182+
}
183+
return $formatted_author_names;
184+
}
185+
186+
/**
187+
* Format an author name according to a given citation format
188+
*
189+
* @param $citation_format
190+
* @param $full_name
191+
* @return string
192+
*/
193+
public static function formatAuthorName($citation_format, $full_name, $pattern = null) : string
194+
{
195+
$result = '';
196+
$format_components = static::citationFormats()[$citation_format];
197+
$full_name_array = explode(" ", $full_name);
198+
$first_name = static::firstName($full_name_array, $pattern);
199+
$middle_name = static::middleName($full_name_array, $pattern);
200+
$last_name = static::lastName($full_name_array, $pattern);
201+
$first_initial = static::initial($first_name);
202+
$middle_initial = static::initial($middle_name);
203+
$comma = static::SEPARATORS['comma'];
204+
$space = static::SEPARATORS['space'];
205+
206+
foreach ($format_components as $key => $value) {
207+
$result = $result . array_values(compact($value))[0];
208+
}
209+
210+
return trim($result);
211+
}
212+
213+
}

app/Http/Controllers/ProfilesController.php

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,13 @@ public function create(Request $request, User $user, LdapHelperContract $ldap):
225225
*/
226226
public function edit(Profile $profile, string $section): View|ViewContract|RedirectResponse
227227
{
228-
//dont manage auto-managed publications
228+
//If auto-managed publications
229229
if ($section == 'publications' && $profile->hasOrcidManagedPublications()) {
230-
$profile->updateORCID();
231-
return redirect()
232-
->route('profiles.show', $profile->slug)
233-
->with('flash_message', 'Publications updated via ORCID.');
230+
if ($profile->updateORCID()) {
231+
return redirect()->route('profiles.show', $profile->slug)->with('flash_message', 'Publications updated via ORCID.');
232+
} else {
233+
return redirect()->route('profiles.show', $profile->slug)->with('flash_message', 'Error updating your ORCID publications.');
234+
}
234235
}
235236

236237
$data = $profile->data()->$section()->get();
@@ -251,11 +252,11 @@ public function edit(Profile $profile, string $section): View|ViewContract|Redir
251252
*/
252253
public function orcid(Profile $profile): RedirectResponse
253254
{
254-
if ($profile->updateORCID()) {
255-
return redirect()->route('profiles.show', $profile->slug)->with('flash_message', 'Publications updated via ORCID.');
256-
} else {
257-
return redirect()->route('profiles.show', $profile->slug)->with('flash_message', 'Error updating your ORCID publications.');
258-
}
255+
if ($profile->updateORCID()) {
256+
return redirect()->route('profiles.show', $profile->slug)->with('flash_message', 'Publications updated via ORCID.');
257+
} else {
258+
return redirect()->route('profiles.show', $profile->slug)->with('flash_message', 'Error updating your ORCID publications.');
259+
}
259260
}
260261

261262
/**

app/Profile.php

Lines changed: 32 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use App\ProfileData;
66
use App\ProfileStudent;
7+
use App\Repositories\OrcidPublicationsRepository;
78
use App\Student;
89
use App\User;
910
use Illuminate\Database\Eloquent\Model;
@@ -18,6 +19,7 @@
1819
use Illuminate\Database\Eloquent\Factories\HasFactory;
1920
use Illuminate\Database\Eloquent\Builder;
2021
use Illuminate\Database\Eloquent\SoftDeletes;
22+
use Illuminate\Support\Arr;
2123
use Illuminate\Support\Facades\Cache;
2224

2325
/**
@@ -58,6 +60,8 @@ class Profile extends Model implements HasMedia, Auditable
5860
*/
5961
protected $casts = [
6062
'public' => 'boolean',
63+
'contributors' => 'array',
64+
'authors' => 'array',
6165
];
6266

6367
/**
@@ -173,64 +177,12 @@ public function hasOrcidManagedPublications()
173177

174178
public function updateORCID()
175179
{
176-
$orc_id = $this->information()->get(array('data'))->toArray()[0]['data']['orc_id'];
180+
$publicationsManager = new OrcidPublicationsRepository($this);
177181

178-
if(is_null($orc_id)){
179-
//can't update if we don't know your ID
180-
return false;
181-
}
182-
183-
$orc_url = "https://pub.orcid.org/v2.0/" . $orc_id . "/activities";
184-
185-
$client = new Client();
186-
187-
$res = $client->get($orc_url, [
188-
'headers' => [
189-
'Authorization' => 'Bearer ' . config('ORCID_TOKEN'),
190-
'Accept' => 'application/json'
191-
],
192-
'http_errors' => false, // don't throw exceptions for 4xx,5xx responses
193-
]);
194-
195-
//an error of some sort
196-
if($res->getStatusCode() != 200){
197-
return false;
198-
}
199-
200-
$datum = json_decode($res->getBody()->getContents(), true);
201-
202-
foreach($datum['works']['group'] as $record){
203-
$url = NULL;
204-
foreach($record['external-ids']['external-id'] as $ref){
205-
if($ref['external-id-type'] == "eid"){
206-
$url = "https://www.scopus.com/record/display.uri?origin=resultslist&eid=" . $ref['external-id-value'];
207-
}
208-
else if($ref['external-id-type'] == "doi"){
209-
$url = "http://doi.org/" . $ref['external-id-value'];
210-
}
211-
}
212-
$record = ProfileData::firstOrCreate([
213-
'profile_id' => $this->id,
214-
'type' => 'publications',
215-
'data->title' => $record['work-summary'][0]['title']['title']['value'],
216-
'sort_order' => $record['work-summary'][0]['publication-date']['year']['value'] ?? null,
217-
],[
218-
'data' => [
219-
'url' => $url,
220-
'title' => $record['work-summary'][0]['title']['title']['value'],
221-
'year' => $record['work-summary'][0]['publication-date']['year']['value'] ?? null,
222-
'type' => ucwords(strtolower(str_replace('_', ' ', $record['work-summary'][0]['type']))),
223-
'status' => 'Published'
224-
],
225-
]);
226-
}
227-
228-
Cache::tags(['profile_data'])->flush();
229-
230-
//ran through process successfully
231-
return true;
182+
return $publicationsManager->syncPublications();
232183
}
233184

185+
234186
public function updateDatum($section, $request)
235187
{
236188
$sort_order = count($request->data ?? []) + 1;
@@ -375,6 +327,21 @@ protected function registerImageThumbnails(Media $media = null, $name, $width, $
375327
// Query Scopes //
376328
//////////////////
377329

330+
/**
331+
* Profiles with orcid sync on
332+
*
333+
* @param \Illuminate\Database\Eloquent\Builder $query
334+
* @return \Illuminate\Database\Eloquent\Builder
335+
*
336+
*/
337+
public function scopeWithOrcidSyncOn($query) : Builder {
338+
return $query->whereHas('data', function ($data) {
339+
$data
340+
->where('type', 'information')
341+
->where('data', 'like', '%"orc_id_managed": "1"%');
342+
});
343+
}
344+
378345
/**
379346
* Query scope for public Profiles
380347
*
@@ -596,6 +563,16 @@ public function getApiUrlAttribute()
596563
return route('api.index', ['person' => $this->slug, 'with_data' => true]);
597564
}
598565

566+
/**
567+
* Get the profile ORCID ID
568+
*/
569+
public function getOrcidAttribute()
570+
{
571+
$orc_id = $this->information()->get(array('data'))->toArray()[0]['data']['orc_id'];
572+
573+
return $orc_id ?? null;
574+
}
575+
599576
///////////////
600577
// Relations //
601578
///////////////

0 commit comments

Comments
 (0)