@@ -35,12 +35,9 @@ public function sync(Project $project, array $citations, User $user): array
3535 foreach ($ citations as $ citationData ) {
3636 $ this ->validateCitationData ($ citationData );
3737
38- $ doi = $ citationData ['doi ' ];
39-
40- if (! is_null ($ doi )) {
41- $ citation = $ this ->findOrCreateCitation ($ project , $ citationData , $ doi );
42- $ processedCitations [] = $ citation ;
43- }
38+ $ citation = $ this ->findOrCreateCitation ($ project , $ citationData );
39+ $ this ->rememberCitation ($ project , $ citation );
40+ $ processedCitations [] = $ citation ;
4441 }
4542
4643 DB ::transaction (function () use ($ project , $ processedCitations , $ user ): void {
@@ -61,7 +58,7 @@ private function validateCitationData(array $citationData): void
6158 {
6259 Validator::make ($ citationData , [
6360 'title ' => ['required ' , 'string ' ],
64- 'doi ' => ['required ' , 'string ' ],
61+ 'doi ' => ['nullable ' , 'string ' ],
6562 'authors ' => ['required ' , 'string ' ],
6663 'citation_text ' => ['nullable ' , 'string ' ],
6764 ])->validate ();
@@ -72,11 +69,31 @@ private function validateCitationData(array $citationData): void
7269 *
7370 * @param array<string, mixed> $citationData
7471 */
75- private function findOrCreateCitation (Project $ project , array $ citationData, string $ doi ): Citation
72+ private function findOrCreateCitation (Project $ project , array $ citationData ): Citation
7673 {
77- $ existingCitation = $ project ->citations ->filter (function ($ citation ) use ($ doi ) {
78- return $ doi === $ citation ->doi ;
79- })->first ();
74+ $ doi = $ this ->normalizeDoi ($ citationData ['doi ' ] ?? null );
75+ $ title = $ this ->normalizeText ($ citationData ['title ' ] ?? null );
76+ $ authors = $ this ->normalizeText ($ citationData ['authors ' ] ?? null );
77+
78+ $ existingCitation = null ;
79+
80+ // 1. Try to match by ID first (explicit reference)
81+ if (! empty ($ citationData ['id ' ])) {
82+ $ existingCitation = $ project ->citations ->firstWhere ('id ' , (int ) $ citationData ['id ' ]);
83+ }
84+
85+ // 2. Try to match by DOI (if not null/empty)
86+ if (! $ existingCitation && ! is_null ($ doi )) {
87+ $ existingCitation = $ project ->citations ->firstWhere ('doi ' , $ doi );
88+ }
89+
90+ // 3. Try to match by title + authors (content-based matching for missing DOI)
91+ if (! $ existingCitation && ! is_null ($ title ) && ! is_null ($ authors )) {
92+ $ existingCitation = $ project ->citations ->first (function ($ citation ) use ($ title , $ authors ): bool {
93+ return $ this ->normalizeText ($ citation ->title ) === $ title
94+ && $ this ->normalizeText ($ citation ->authors ) === $ authors ;
95+ });
96+ }
8097
8198 if ($ existingCitation ) {
8299 $ existingCitation ->update ($ this ->prepareCitationAttributes ($ citationData ));
@@ -96,10 +113,49 @@ private function findOrCreateCitation(Project $project, array $citationData, str
96113 private function prepareCitationAttributes (array $ citationData ): array
97114 {
98115 return [
99- 'doi ' => $ citationData ['doi ' ] ?? null ,
100- 'title ' => $ citationData ['title ' ] ?? null ,
101- 'authors ' => $ citationData ['authors ' ] ?? null ,
102- 'citation_text ' => $ citationData ['citation_text ' ] ?? null ,
116+ 'doi ' => $ this -> normalizeDoi ( $ citationData ['doi ' ] ?? null ) ,
117+ 'title ' => $ this -> normalizeText ( $ citationData ['title ' ] ?? null ) ,
118+ 'authors ' => $ this -> normalizeText ( $ citationData ['authors ' ] ?? null ) ,
119+ 'citation_text ' => $ this -> normalizeText ( $ citationData ['citation_text ' ] ?? null ) ,
103120 ];
104121 }
122+
123+ private function rememberCitation (Project $ project , Citation $ citation ): void
124+ {
125+ $ citations = $ project ->citations ;
126+
127+ if (! $ citations ->contains ('id ' , $ citation ->id )) {
128+ $ project ->setRelation ('citations ' , $ citations ->push ($ citation ));
129+ }
130+ }
131+
132+ private function normalizeDoi (mixed $ doi ): ?string
133+ {
134+ if (! is_string ($ doi )) {
135+ return null ;
136+ }
137+
138+ $ normalizedDoi = trim ($ doi );
139+
140+ if ($ normalizedDoi === '' ) {
141+ return null ;
142+ }
143+
144+ return $ normalizedDoi ;
145+ }
146+
147+ private function normalizeText (mixed $ value ): ?string
148+ {
149+ if (! is_string ($ value )) {
150+ return null ;
151+ }
152+
153+ $ normalizedValue = trim ($ value );
154+
155+ if ($ normalizedValue === '' ) {
156+ return null ;
157+ }
158+
159+ return $ normalizedValue ;
160+ }
105161}
0 commit comments