Skip to content

Commit

Permalink
Merge pull request #5 from BioKIC/master
Browse files Browse the repository at this point in the history
pull from BioKIC/Symbiota
  • Loading branch information
egbot authored Oct 11, 2022
2 parents edd7be3 + 907c025 commit 040ff84
Show file tree
Hide file tree
Showing 118 changed files with 3,882 additions and 2,027 deletions.
19 changes: 4 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,19 @@ This fork of the Symbiota code is actively being developed by the Biodiversity K
Even though BioKIC code developments are regularly pushed back to this repository, we recommend that you download/fork code directly from the
BioKIC/Symbiota repository (https://github.com/BioKIC/Symbiota) to ensure that you obtain the most recently code changes.


# Welcome to the Symbiota code repository

### ABOUT THIS SOFTWARE

The Symbiota Software Project is building a library of webtools to aid biologists in establishing specimen based virtual floras and faunas. This project developed from the realization that complex, information rich biodiversity portals are best built through collaborative efforts between software developers, biologist, wildlife managers, and citizen scientist. The central premise of this open source software project is that through a partnership between software engineers and scientific community, higher quality and more publicly useful biodiversity portals can be built. An open source software framework allows the technicians to create the tools, thus freeing the biologist to concentrate their efforts on the curation of quality datasets. In this manor, we can create
something far greater than a single entity is capable of doing on their own.

More information about this project can be accessed through:

(https://symbiota.org)

More information about this project can be accessed through [https://symbiota.org](https://symbiota.org).
For documentation and user guides please visit [Symbiota Docs](https://symbiota.org/docs).

## ACKNOWLEDGEMENTS

Symbiota has been generously funded by the National Science
Foundation (DBI-0743827) from 15 July 2008 to 30 June 2011
(Estimated). The Global Institute of Sustainability
(GIOS) at Arizona State University has also been a major
supporters of the Symbiota initiative since the very beginning.
Arizona State University Vascular Plant and Lichen Herbarium have
been intricately involved in the development from the start.
Sky Island Alliance and the Arizona-Sonora Desert Museum have both
been long-term participants in the development of this product.
Symbiota has been generously funded by the National Science Foundation (DBI-0743827) from 15 July 2008 to 30 June 2011 (Estimated). The Global Institute of Sustainability (GIOS) at Arizona State University has also been a major supporters of the Symbiota initiative since the very beginning. Arizona State University Vascular Plant and Lichen Herbarium have been intricately involved in the development from the start. Sky Island Alliance and the Arizona-Sonora Desert Museum have both been long-term participants in the development of this product.

## FEATURES

Expand All @@ -43,7 +32,7 @@ been long-term participants in the development of this product.

## LIMITATIONS

* Tested thoroughly on Linux and Window operating systems
* Tested thoroughly on Linux and Windows operating systems
* Code should work with an PHP enabled web server, though central development and testing done using Apache HTTP Server


Expand Down
3 changes: 2 additions & 1 deletion admin/portalindex.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ function validateHandshakeForm(f){
echo '<div><label>Path</label>: '.$profileArr['path'].'</div>';
echo '<div><label>Query string</label>: '.$profileArr['queryStr'].'</div>';
echo '<div><label>Stored procedure (cleaning)</label>: '.$profileArr['cleanUpSp'].'</div>';
echo '<div>Go to <a href="../collections/admin/specuploadmap.php?uploadtype=13&uspid='.$uspid.'&collid='.$collid.'" target="_blank">Import Profile</a></div>';
echo '<div>Display all <a href="../collections/admin/specuploadmanagement.php?collid='.$collid.'" target="_blank">Import Profiles</a></div>';
echo '<div>Initiate <a href="../collections/admin/specuploadmap.php?uploadtype=13&uspid='.$uspid.'&collid='.$collid.'" target="_blank">Data Import</a></div>';
echo '</div>';
}
}
Expand Down
27 changes: 15 additions & 12 deletions api/app/Http/Controllers/InstallationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,16 @@ public function pingPortal(Request $request){
* )
*/
public function portalHandshake($id, Request $request){
$responseArr = array();
$responseArr = array( 'status' => 500, 'error' => 'Unknown error' );
$portalObj = PortalIndex::where('guid',$id)->get();
if($portalObj->count()){
$responseArr['status'] = true;
$responseArr['status'] = 200;
$responseArr['message'] = 'Portal previously registered';
unset($responseArr['error']);
}
elseif($id == $_ENV['PORTAL_GUID']){
//Make sure touch isn't referring to self
$responseArr['status'] = false;
$responseArr['status'] = 400;
$responseArr['error'] = 'Registration failed: handshake is referencing self';
}
elseif($request->has('endpoint')){
Expand All @@ -186,8 +187,9 @@ public function portalHandshake($id, Request $request){
try {
//Register remote
$portalObj = PortalIndex::create($remote);
$responseArr['status'] = true;
$responseArr['status'] = 200;
$responseArr['message'] = 'Remote portal registered successfully';
unset($responseArr['error']);
//Register all portals listed within remote, if not alreay registered
$urlInstallation = $baseUrl.'/api/v2/installation';
if($remoteInstallationArr = $this->getAPIResponce($urlInstallation)){
Expand All @@ -207,28 +209,29 @@ public function portalHandshake($id, Request $request){
}
}
}
$responseArr['Current registered remotes obtained from remote library'] = $currentRegistered;
$responseArr['Additional new registrations obtained from remote library'] = $newRegistration;
$responseArr['Currently registered remotes'] = $currentRegistered;
$responseArr['Newly registrations remotes'] = $newRegistration;
}
else $responseArr['error'] = 'Unable to obtain remote installation listing: '.$urlInstallation;
} catch(\Illuminate\Database\QueryException $ex){
$responseArr['status'] = false;
}
catch(\Illuminate\Database\QueryException $ex){
$responseArr['status'] = 503;
$responseArr['error'] = 'Registration failed: Unable insert database record: '.$ex->getMessage();
}
}
else{
$responseArr['status'] = false;
$responseArr['status'] = 422;
$responseArr['error'] = 'Registration failed: Supplied and returned remote GUIDs not matching ('.$id.' != '.$remote['guid'].') ';
}
}
else{
$responseArr['status'] = false;
$responseArr['error'] = 'Registration failed: Unable to obtain data from endpoint: '.$urlPing;
$responseArr['status'] = 422;
$responseArr['error'] = 'Registration failed: Endpoint illegal or non-functional: '.$urlPing;
}
}
}
else{
$responseArr['status'] = false;
$responseArr['status'] = 422;
$responseArr['error'] = 'Registration failed: Unable to obtain portal endpoint';
}
$responseArr['results'] = $portalObj;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function __construct(){
* in="query",
* description="Annoration type (internal, external) ",
* required=true,
* @OA\Schema(type="string", default="internal", "enum": ["internal","external"])
* @OA\Schema(type="string", default="internal", enum = {"internal", "external"})
* ),
* @OA\Parameter(
* name="source",
Expand Down
88 changes: 62 additions & 26 deletions api/app/Http/Controllers/OccurrenceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Models\Occurrence;
use App\Models\PortalIndex;
use App\Models\PortalOccurrence;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

Expand Down Expand Up @@ -56,7 +57,6 @@ public function __construct(){
* required=false,
* @OA\Schema(type="string")
* ),
* ),
* @OA\Parameter(
* name="collid",
* in="query",
Expand Down Expand Up @@ -196,9 +196,12 @@ public function showOneOccurrence($id, Request $request){
]);
$id = $this->getOccid($id);
$occurrence = Occurrence::find($id);
if($occurrence) $occurrence->recordID = DB::table('guidoccurrences')->where('occid',$id)->value('guid');
if($request->input('includeMedia')) $occurrence->media;
if($request->input('includeIdentifications')) $occurrence->identification;
if($occurrence){
$occurrence->recordID = DB::table('guidoccurrences')->where('occid', $id)->value('guid');
if(!$occurrence->occurrenceID) $occurrence->occurrenceID = $occurrence->recordID;
if($request->input('includeMedia')) $occurrence->media;
if($request->input('includeIdentifications')) $occurrence->identification;
}
return response()->json($occurrence);
}

Expand Down Expand Up @@ -262,7 +265,7 @@ public function showOneOccurrenceMedia($id, Request $request){
}

/**
* @OA\Get(
* @off_OA\Get(
* path="/api/v2/occurrence/{identifier}/reharvest",
* operationId="/api/v2/occurrence/identifier/reharvest",
* tags={""},
Expand All @@ -280,38 +283,71 @@ public function showOneOccurrenceMedia($id, Request $request){
* ),
* @OA\Response(
* response="400",
* description="Error: Bad request. Occurrence identifier is required.",
* description="Error: Bad request: Occurrence identifier is required, API can only be triggered locally (at this time).",
* ),
* @OA\Response(
* response="500",
* description="Error: unable to locate record",
* ),
* )
*/
public function oneOccurrenceReharvest($id, Request $request){
$responseArr = array();
$host = '';
if(!empty($GLOBALS['SERVER_HOST'])) $host = $GLOBALS['SERVER_HOST'];
else $host = $_SERVER['SERVER_NAME'];
if($host && $request->getHttpHost() != $host){
$responseArr['status'] = 400;
$responseArr['error'] = 'At this time, API call can only be triggered locally';
return response()->json($responseArr);
}
$id = $this->getOccid($id);
$occurrence = Occurrence::find($id);
if(!$occurrence){
$responseArr['status'] = 500;
$responseArr['error'] = 'Unable to locate occurrence record (occid = '.$id.')';
return response()->json($responseArr);
}
if($occurrence->collection->managementType == 'Live Data'){
$responseArr['status'] = 400;
$responseArr['error'] = 'Updating a Live Managed record is not allowed ';
return response()->json($responseArr);
}
$publications = $occurrence->portalPublications;
if($occurrence->collection->managementType == 'Snapshot'){
foreach($publications as $pub){
if($pub->direction == 'import'){
$sourcePortalID = $pub->portalID;
$targetOccid = $pub->pivot->targetOccid;
if($sourcePortalID && $targetOccid){
//Get remote occurrence data
$url = PortalIndex::where('portalID', $sourcePortalID)->value('urlRoot');
$url .= '/api/v2/occurrence/'.$targetOccid;
$remoteOccurrence = $this->getAPIResponce($url);
$response = $this->update($id, new Request($remoteOccurrence));
//print_r($response);
echo '<br>status: '.$response->status().'<br>';
print_r($response->getData());
//echo ($response->isDirty()?'changed':'not changed').'<br>';
//echo 'changes: '.$response->getChanges().'<br>';
$responseArr['status'] = $response->status();
foreach($publications as $pub){
if($pub->direction == 'import'){
$sourcePortalID = $pub->portalID;
$remoteOccid = $pub->pivot->remoteOccid;
if($sourcePortalID && $remoteOccid){
//Get remote occurrence data
$urlRoot = PortalIndex::where('portalID', $sourcePortalID)->value('urlRoot');
$url = $urlRoot.'/api/v2/occurrence/'.$remoteOccid;
if($remoteOccurrence = $this->getAPIResponce($url)){
unset($remoteOccurrence['modified']);
if(!$remoteOccurrence['occurrenceRemarks']) unset($remoteOccurrence['occurrenceRemarks']);
unset($remoteOccurrence['dynamicProperties']);
$updateObj = $this->update($id, new Request($remoteOccurrence));
$ts = date('Y-m-d H:i:s');
$changeArr = $updateObj->getOriginalContent()->getChanges();
$responseArr['status'] = $updateObj->status();
$responseArr['dataStatus'] = ($changeArr?count($changeArr).' fields modified':'nothing modified');
$responseArr['fieldsModified'] = $changeArr;
$responseArr['sourceDateLastModified'] = $remoteOccurrence['dateLastModified'];
$responseArr['dateLastModified'] = $ts;
$responseArr['sourceCollectionUrl'] = $urlRoot.'/collections/misc/collprofiles.php?collid='.$remoteOccurrence['collid'];
$responseArr['sourceRecordUrl'] = $urlRoot.'/collections/individual/index.php?occid='.$remoteOccid;
//Reset Portal Occurrence refreshDate
$portalOccur = PortalOccurrence::where('occid', $id)->where('pubid', $pub->pubid)->first();
$portalOccur->refreshTimestamp = $ts;
$portalOccur->save();
}
else {
$responseArr['status'] = 400;
$responseArr['error'] = 'Unable to locate remote/source occurrence (sourceID = '.$id.')';
$responseArr['sourceUrl'] = $url;
}
}
}
} else {
$responseArr['status'] = false;
$responseArr['message'] = 'Unable to refresh a Live Managed occurrence record ';
}
return response()->json($responseArr);
}
Expand Down
4 changes: 2 additions & 2 deletions api/app/Http/Controllers/TaxonomyDescriptionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function __construct(){
}

/**
* @_off_OA\Get(
* @OA\Get(
* path="/api/v2/taxonomy/{identifier}/description",
* operationId="/api/v2/taxonomy/identifier/description",
* tags={""},
Expand Down Expand Up @@ -74,7 +74,7 @@ public function showAllDescriptions($id, Request $request){
}

/**
* @_off_OA\Get(
* @OA\Get(
* path="/api/v2/taxonomy/{identifier}/description/{identifier}",
* operationId="/api/v2/taxonomy/identifier/description/identifier",
* tags={""},
Expand Down
7 changes: 4 additions & 3 deletions api/app/Models/Occurrence.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ class Occurrence extends Model{
'associatedTaxa', 'dynamicProperties', 'verbatimAttributes', 'behavior', 'reproductiveCondition', 'cultivationStatus', 'establishmentMeans', 'lifeStage', 'sex', 'individualCount',
'samplingProtocol', 'samplingEffort', 'preparations', 'locationID', 'continent', 'parentLocationID', 'country', 'stateProvince', 'county', 'municipality', 'waterBody', 'islandGroup',
'island', 'countryCode', 'locality', 'localitySecurity', 'localitySecurityReason', 'decimalLatitude', 'decimalLongitude', 'geodeticDatum', 'coordinateUncertaintyInMeters',
'footprintWKT', 'coordinatePrecision', 'locationRemarks', 'verbatimCoordinates', 'georeferencedBy', 'georeferencedDate', 'georeferenceProtocol', 'georeferenceSources',
'footprintWKT', 'locationRemarks', 'verbatimCoordinates', 'georeferencedBy', 'georeferencedDate', 'georeferenceProtocol', 'georeferenceSources',
'georeferenceVerificationStatus', 'georeferenceRemarks', 'minimumElevationInMeters', 'maximumElevationInMeters', 'verbatimElevation', 'minimumDepthInMeters', 'maximumDepthInMeters',
'verbatimDepth', 'availability', 'disposition', 'storageLocation', 'modified', 'language', 'processingstatus', 'recordEnteredBy', 'duplicateQuantity', 'labelProject'];
protected $hidden = [ 'scientificName', 'recordedbyid', 'associatedOccurrences', 'previousIdentifications', 'dynamicFields', 'institutionID', 'collectionID', 'genericcolumn1', 'genericcolumn2' ];
protected $hidden = [ 'scientificName', 'recordedbyid', 'observerUid', 'labelProject', 'processingStatus', 'recordEnteredBy', 'associatedOccurrences', 'previousIdentifications',
'verbatimCoordinateSystem', 'coordinatePrecision', 'dynamicFields', 'institutionID', 'collectionID', 'genericcolumn1', 'genericcolumn2' ];

public function collection(){
return $this->belongsTo(Collection::class, 'collid', 'collid');
Expand Down Expand Up @@ -50,7 +51,7 @@ public function guid(){
}

public function portalPublications(){
return $this->belongsToMany(PortalPublication::class, 'portaloccurrences', 'occid', 'pubid')->withPivot('targetOccid');;
return $this->belongsToMany(PortalPublication::class, 'portaloccurrences', 'occid', 'pubid')->withPivot('remoteOccid');;
}


Expand Down
3 changes: 1 addition & 2 deletions api/app/Models/PortalOccurrence.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ class PortalOccurrence extends Model{

protected $table = 'portaloccurrences';
protected $primaryKey = 'portalOccurrencesID';
protected $fillable = ['occid', 'pubid', 'remoteOccid', 'verification', 'refreshTimestamp' ];
public $timestamps = false;

protected $fillable = [ 'occid', 'portalID', 'pubid', 'targetOccid', 'verification', 'refreshtimestamp' ];

public function portalIndex() {
return $this->belongsTo(PortalIndex::class, 'portalID', 'portalID');
}
Expand Down
8 changes: 5 additions & 3 deletions api/routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
});

$router->get('/v2', function () use ($router) {
return $router->app->version();
return redirect('/v2/documentation');;
});

$router->group(['prefix' => 'v2'], function () use ($router) {
Expand All @@ -27,6 +27,8 @@
$router->get('collection/{id}', ['uses' => 'CollectionController@showOneCollection']);

$router->get('occurrence/search', ['uses' => 'OccurrenceController@showAllOccurrences']);
//Temporarily keep following route until new documentation is created. The one above will be keep so that I follows GBIF API layout
$router->get('occurrence', ['uses' => 'OccurrenceController@showAllOccurrences']);
$router->get('occurrence/{id}', ['uses' => 'OccurrenceController@showOneOccurrence']);
$router->get('occurrence/{id}/media', ['uses' => 'OccurrenceController@showOneOccurrenceMedia']);
$router->get('occurrence/{id}/identification', ['uses' => 'OccurrenceController@showOneOccurrenceIdentifications']);
Expand All @@ -51,8 +53,8 @@
//$router->delete('media/{id}', ['uses' => 'MediaController@delete']);
//$router->put('media/{id}', ['uses' => 'MediaController@update']);

//$router->get('taxonomy', ['uses' => 'TaxonomyController@showAllTaxa']);
//$router->get('taxonomy/{id}', ['uses' => 'TaxonomyController@showOneTaxon']);
$router->get('taxonomy', ['uses' => 'TaxonomyController@showAllTaxa']);
$router->get('taxonomy/{id}', ['uses' => 'TaxonomyController@showOneTaxon']);
//$router->get('taxonomy/{id}/description', ['uses' => 'TaxonomyController@showAllDescriptions']);
//$router->get('taxonomy/{id}/description/{id}', ['uses' => 'TaxonomyDescriptionController@showOneDescription']);
});
Loading

0 comments on commit 040ff84

Please sign in to comment.